diff --git a/collectionlist.cpp b/collectionlist.cpp index fe0847da..730e5842 100644 --- a/collectionlist.cpp +++ b/collectionlist.cpp @@ -1,611 +1,611 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "collectionlist.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playlistcollection.h" #include "splashscreen.h" #include "stringshare.h" #include "cache.h" #include "actioncollection.h" #include "tag.h" #include "viewmode.h" using ActionCollection::action; //////////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////////// CollectionList *CollectionList::m_list = 0; CollectionList *CollectionList::instance() { return m_list; } static QTime stopwatch; void CollectionList::startLoadingCachedItems() { if(!m_list) return; kDebug() << "Starting to load cached items"; stopwatch.start(); if(!Cache::instance()->prepareToLoadCachedItems()) { kError() << "Unable to setup to load cache... perhaps it doesn't exist?"; completedLoadingCachedItems(); return; } kDebug() << "Kicked off first batch"; QTimer::singleShot(0, this, SLOT(loadNextBatchCachedItems())); } void CollectionList::loadNextBatchCachedItems() { Cache *cache = Cache::instance(); bool done = false; for(int i = 0; i < 20; ++i) { FileHandle cachedItem(cache->loadNextCachedItem()); if(cachedItem.isNull()) { done = true; break; } // This may have already been created via a loaded playlist. if(!m_itemsDict.contains(cachedItem.absFilePath())) { CollectionListItem *newItem = new CollectionListItem(this, cachedItem); setupItem(newItem); } } SplashScreen::update(); if(!done) { QTimer::singleShot(0, this, SLOT(loadNextBatchCachedItems())); } else { completedLoadingCachedItems(); } } void CollectionList::completedLoadingCachedItems() { // The CollectionList is created with sorting disabled for speed. Re-enable // it here, and perform the sort. - KConfigGroup config(KGlobal::config(), "Playlists"); + KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); Qt::SortOrder order = Qt::DescendingOrder; if(config.readEntry("CollectionListSortAscending", true)) order = Qt::AscendingOrder; m_list->sortByColumn(config.readEntry("CollectionListSortColumn", 1), order); SplashScreen::finishedLoading(); kDebug() << "Finished loading cached items, took" << stopwatch.elapsed() << "ms"; kDebug() << m_itemsDict.size() << "items are in the CollectionList"; emit cachedItemsLoaded(); } void CollectionList::initialize(PlaylistCollection *collection) { if(m_list) return; // We have to delay initialization here because dynamic_cast or comparing to // the collection instance won't work in the PlaylistBox::Item initialization // won't work until the CollectionList is fully constructed. m_list = new CollectionList(collection); m_list->setName(i18n("Collection List")); collection->setupPlaylist(m_list, "folder-sound"); } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// CollectionListItem *CollectionList::createItem(const FileHandle &file, QTreeWidgetItem *, bool) { // It's probably possible to optimize the line below away, but, well, right // now it's more important to not load duplicate items. if(m_itemsDict.contains(file.absFilePath())) return 0; CollectionListItem *item = new CollectionListItem(this, file); if(!item->isValid()) { kError() << "CollectionList::createItem() -- A valid tag was not created for \"" << file.absFilePath() << "\"" << endl; delete item; return 0; } setupItem(item); return item; } void CollectionList::clearItems(const PlaylistItemList &items) { foreach(PlaylistItem *item, items) { delete item; } dataChanged(); } void CollectionList::setupTreeViewEntries(ViewMode *viewMode) const { TreeViewMode *treeViewMode = dynamic_cast(viewMode); if(!treeViewMode) { kWarning() << "Can't setup entries on a non-tree-view mode!\n"; return; } QList columnList; columnList << PlaylistItem::ArtistColumn; columnList << PlaylistItem::GenreColumn; columnList << PlaylistItem::AlbumColumn; foreach(int column, columnList) treeViewMode->addItems(m_columnTags[column]->keys(), column); } void CollectionList::slotNewItems(const KFileItemList &items) { QStringList files; for(KFileItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) files.append((*it).url().path()); addFiles(files); update(); } void CollectionList::slotRefreshItems(const QList > &items) { for(int i = 0; i < items.count(); ++i) { const KFileItem fileItem = items[i].second; CollectionListItem *item = lookup(fileItem.url().path()); if(item) { item->refreshFromDisk(); // If the item is no longer on disk, remove it from the collection. if(item->file().fileInfo().exists()) item->repaint(); else delete item; } } update(); } void CollectionList::slotDeleteItem(const KFileItem &item) { delete lookup(item.url().path()); } void CollectionList::saveItemsToCache() const { kDebug() << "Saving collection list to cache"; QString cacheFileName = KGlobal::dirs()->saveLocation("appdata") % QLatin1String("cache"); KSaveFile f(cacheFileName); if(!f.open(QIODevice::WriteOnly)) { kError() << "Error saving cache:" << f.errorString(); return; } QByteArray data; QDataStream s(&data, QIODevice::WriteOnly); s.setVersion(QDataStream::Qt_4_3); QHash::const_iterator it; for(it = m_itemsDict.begin(); it != m_itemsDict.end(); ++it) { s << it.key(); s << (*it)->file(); } QDataStream fs(&f); qint32 checksum = qChecksum(data.data(), data.size()); fs << qint32(Cache::playlistItemsCacheVersion) << checksum << data; f.close(); if(!f.finalize()) kError() << "Error saving cache:" << f.errorString(); } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void CollectionList::paste() { decode(QApplication::clipboard()->mimeData()); } void CollectionList::clear() { int result = KMessageBox::warningContinueCancel(this, i18n("Removing an item from the collection will also remove it from " "all of your playlists. Are you sure you want to continue?\n\n" "Note, however, that if the directory that these files are in is in " "your \"scan on startup\" list, they will be readded on startup.")); if(result == KMessageBox::Continue) { Playlist::clear(); emit signalCollectionChanged(); } } void CollectionList::slotCheckCache() { PlaylistItemList invalidItems; kDebug() << "Starting to check cached items for consistency"; stopwatch.start(); int i = 0; foreach(CollectionListItem *item, m_itemsDict) { if(!item->checkCurrent()) invalidItems.append(item); if(++i == (m_itemsDict.size() / 2)) kDebug() << "Checkpoint"; } clearItems(invalidItems); kDebug() << "Finished consistency check, took" << stopwatch.elapsed() << "ms"; } void CollectionList::slotRemoveItem(const QString &file) { delete m_itemsDict[file]; } void CollectionList::slotRefreshItem(const QString &file) { if(m_itemsDict[file]) m_itemsDict[file]->refresh(); } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// CollectionList::CollectionList(PlaylistCollection *collection) : Playlist(collection, true), m_columnTags(15, 0) { QAction *spaction = ActionCollection::actions()->addAction("showPlaying"); spaction->setText(i18n("Show Playing")); connect(spaction, SIGNAL(triggered(bool)), SLOT(slotShowPlaying())); connect(action("back")->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPopulateBackMenu())); connect(action("back")->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotPlayFromBackMenu(QAction*))); setSortingEnabled(false); // Temporarily disable sorting to add items faster. m_columnTags[PlaylistItem::ArtistColumn] = new TagCountDict; m_columnTags[PlaylistItem::AlbumColumn] = new TagCountDict; m_columnTags[PlaylistItem::GenreColumn] = new TagCountDict; // Even set to true it wouldn't work with this class due to other checks setAllowDuplicates(false); } CollectionList::~CollectionList() { - KConfigGroup config(KGlobal::config(), "Playlists"); + KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); config.writeEntry("CollectionListSortColumn", header()->sortIndicatorSection()); config.writeEntry("CollectionListSortAscending", header()->sortIndicatorOrder() == Qt::AscendingOrder); // In some situations the dataChanged signal from clearItems will cause observers to // subsequently try to access a deleted item. Since we're going away just remove all // observers. clearObservers(); // The CollectionListItems will try to remove themselves from the // m_columnTags member, so we must make sure they're gone before we // are. clearItems(items()); qDeleteAll(m_columnTags); m_columnTags.clear(); } void CollectionList::dropEvent(QDropEvent *e) { if(e->source() == this) return; // Don't rearrange in the CollectionList. else Playlist::dropEvent(e); } void CollectionList::dragMoveEvent(QDragMoveEvent *e) { if(e->source() != this) Playlist::dragMoveEvent(e); else e->setAccepted(false); } QString CollectionList::addStringToDict(const QString &value, int column) { if(column > m_columnTags.count() || value.trimmed().isEmpty()) return QString(); if(m_columnTags[column]->contains(value)) ++((*m_columnTags[column])[value]); else { m_columnTags[column]->insert(value, 1); emit signalNewTag(value, column); } return value; } QStringList CollectionList::uniqueSet(UniqueSetType t) const { int column; switch(t) { case Artists: column = PlaylistItem::ArtistColumn; break; case Albums: column = PlaylistItem::AlbumColumn; break; case Genres: column = PlaylistItem::GenreColumn; break; default: return QStringList(); } return m_columnTags[column]->keys(); } CollectionListItem *CollectionList::lookup(const QString &file) const { return m_itemsDict.value(file, 0); } void CollectionList::removeStringFromDict(const QString &value, int column) { if(column > m_columnTags.count() || value.trimmed().isEmpty()) return; if(m_columnTags[column]->contains(value) && --((*m_columnTags[column])[value])) // If the decrement goes to 0... { emit signalRemovedTag(value, column); m_columnTags[column]->remove(value); } } void CollectionList::addWatched(const QString &file) { m_dirWatch->addFile(file); } void CollectionList::removeWatched(const QString &file) { m_dirWatch->removeFile(file); } //////////////////////////////////////////////////////////////////////////////// // CollectionListItem public methods //////////////////////////////////////////////////////////////////////////////// void CollectionListItem::refresh() { int offset = CollectionList::instance()->columnOffset(); int columns = lastColumn() + offset + 1; data()->metadata.resize(columns); data()->cachedWidths.resize(columns); for(int i = offset; i < columns; i++) { setText(i, text(i)); int id = i - offset; if(id != TrackNumberColumn && id != LengthColumn) { // All columns other than track num and length need local-encoded data for sorting QString toLower = text(i).toLower(); // For some columns, we may be able to share some strings if((id == ArtistColumn) || (id == AlbumColumn) || (id == GenreColumn) || (id == YearColumn) || (id == CommentColumn)) { toLower = StringShare::tryShare(toLower); if(id != YearColumn && id != CommentColumn && data()->metadata[id] != toLower) { CollectionList::instance()->removeStringFromDict(data()->metadata[id], id); CollectionList::instance()->addStringToDict(text(i), id); } } data()->metadata[id] = toLower; } int newWidth = treeWidget()->fontMetrics().width(text(i)); if(newWidth != data()->cachedWidths[i]) playlist()->slotWeightDirty(i); data()->cachedWidths[i] = newWidth; } for(PlaylistItemList::Iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->playlist()->update(); (*it)->playlist()->dataChanged(); } if(treeWidget()->isVisible()) treeWidget()->viewport()->update(); CollectionList::instance()->dataChanged(); emit CollectionList::instance()->signalCollectionChanged(); } PlaylistItem *CollectionListItem::itemForPlaylist(const Playlist *playlist) { if(playlist == CollectionList::instance()) return this; PlaylistItemList::ConstIterator it; for(it = m_children.constBegin(); it != m_children.constEnd(); ++it) if((*it)->playlist() == playlist) return *it; return 0; } void CollectionListItem::updateCollectionDict(const QString &oldPath, const QString &newPath) { CollectionList *collection = CollectionList::instance(); if(!collection) return; collection->removeFromDict(oldPath); collection->addToDict(newPath, this); } void CollectionListItem::repaint() const { // FIXME repaint /*QItemDelegate::repaint(); for(PlaylistItemList::ConstIterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) (*it)->repaint();*/ } //////////////////////////////////////////////////////////////////////////////// // CollectionListItem protected methods //////////////////////////////////////////////////////////////////////////////// CollectionListItem::CollectionListItem(CollectionList *parent, const FileHandle &file) : PlaylistItem(parent), m_shuttingDown(false) { parent->addToDict(file.absFilePath(), this); data()->fileHandle = file; if(file.tag()) { refresh(); parent->dataChanged(); } else { kError() << "CollectionListItem::CollectionListItem() -- Tag() could not be created." << endl; } SplashScreen::increment(); } CollectionListItem::~CollectionListItem() { m_shuttingDown = true; foreach(PlaylistItem *item, m_children) delete item; CollectionList *l = CollectionList::instance(); if(l) { l->removeFromDict(file().absFilePath()); l->removeStringFromDict(file().tag()->album(), AlbumColumn); l->removeStringFromDict(file().tag()->artist(), ArtistColumn); l->removeStringFromDict(file().tag()->genre(), GenreColumn); } } void CollectionListItem::addChildItem(PlaylistItem *child) { m_children.append(child); } void CollectionListItem::removeChildItem(PlaylistItem *child) { if(!m_shuttingDown) m_children.removeAll(child); } bool CollectionListItem::checkCurrent() { if(!file().fileInfo().exists() || !file().fileInfo().isFile()) return false; if(!file().current()) { file().refresh(); refresh(); } return true; } // vim: set et sw=4 tw=0 sta: diff --git a/deletedialog.cpp b/deletedialog.cpp index 9955974d..e79004cc 100644 --- a/deletedialog.cpp +++ b/deletedialog.cpp @@ -1,137 +1,137 @@ /** * Copyright (C) 2004, 2008 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "deletedialog.h" #include "ui_deletedialogbase.h" #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////// // DeleteWidget implementation ////////////////////////////////////////////////////////////////////////////// DeleteWidget::DeleteWidget(QWidget *parent) : QWidget(parent), m_ui(new Ui::DeleteDialogBase) { m_ui->setupUi(this); setObjectName( QLatin1String("delete_dialog_widget" )); - KConfigGroup messageGroup(KGlobal::config()->group("FileRemover")); + KConfigGroup messageGroup(KSharedConfig::openConfig(), "FileRemover"); bool deleteInstead = messageGroup.readEntry("deleteInsteadOfTrash", false); slotShouldDelete(deleteInstead); m_ui->ddShouldDelete->setChecked(deleteInstead); // Forward on this signal. connect(m_ui->ddShouldDelete, SIGNAL(toggled(bool)), SIGNAL(signalShouldDelete(bool))); } void DeleteWidget::setFiles(const QStringList &files) { m_ui->ddFileList->clear(); m_ui->ddFileList->insertItems(0, files); m_ui->ddNumFiles->setText(i18np("1 file selected.", "%1 files selected.", files.count())); } bool DeleteWidget::shouldDelete() const { return m_ui->ddShouldDelete->isChecked(); } void DeleteWidget::slotShouldDelete(bool shouldDelete) { if(shouldDelete) { m_ui->ddDeleteText->setText(i18n("These items will be permanently " "deleted from your hard disk.")); m_ui->ddWarningIcon->setPixmap(KIconLoader::global()->loadIcon("dialog-warning", KIconLoader::Desktop, KIconLoader::SizeLarge)); } else { m_ui->ddDeleteText->setText(i18n("These items will be moved to the Trash Bin.")); m_ui->ddWarningIcon->setPixmap(KIconLoader::global()->loadIcon("user-trash-full", KIconLoader::Desktop, KIconLoader::SizeLarge)); } } ////////////////////////////////////////////////////////////////////////////// // DeleteDialog implementation ////////////////////////////////////////////////////////////////////////////// DeleteDialog::DeleteDialog(QWidget *parent) : KDialog(parent, Qt::MSWindowsFixedSizeDialogHint), m_trashGuiItem(i18n("&Send to Trash"), "user-trash-full") { setObjectName( QLatin1String("delete_dialog" )); setModal(true); setCaption(i18n("About to delete selected files")); setButtons(Ok | Cancel); setDefaultButton(Cancel); showButtonSeparator(true); m_widget = new DeleteWidget(this); setMainWidget(m_widget); m_widget->setMinimumSize(400, 300); // Trying to adjust for Qt bug with rich text where the layout is ignored // (something about not being able to get height-for-width on X11?) setMinimumSize(410, 326); adjustSize(); slotShouldDelete(shouldDelete()); connect(m_widget, SIGNAL(signalShouldDelete(bool)), SLOT(slotShouldDelete(bool))); } bool DeleteDialog::confirmDeleteList(const QStringList &condemnedFiles) { m_widget->setFiles(condemnedFiles); return exec() == QDialog::Accepted; } void DeleteDialog::setFiles(const QStringList &files) { m_widget->setFiles(files); } void DeleteDialog::accept() { - KConfigGroup messageGroup(KGlobal::config()->group("FileRemover")); + KConfigGroup messageGroup(KSharedConfig::openConfig(), "FileRemover"); // Save user's preference messageGroup.writeEntry("deleteInsteadOfTrash", shouldDelete()); messageGroup.sync(); KDialog::accept(); } void DeleteDialog::slotShouldDelete(bool shouldDelete) { setButtonGuiItem(Ok, shouldDelete ? KStandardGuiItem::del() : m_trashGuiItem); } // vim: set et sw=4 tw=0 sta: diff --git a/filerenamer.cpp b/filerenamer.cpp index 4513ca11..e5a178dd 100644 --- a/filerenamer.cpp +++ b/filerenamer.cpp @@ -1,1082 +1,1082 @@ /** * Copyright (C) 2004, 2007, 2009 Michael Pyne * Copyright (C) 2003 Frerich Raabe * Copyright (C) 2014 Arnold Dumas * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "filerenamer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tag.h" #include "filerenameroptions.h" #include "filehandle.h" #include "exampleoptions.h" #include "playlistitem.h" #include "playlist.h" // processEvents() #include "coverinfo.h" class ConfirmationDialog : public KDialog { public: ConfirmationDialog(const QMap &files, QWidget *parent = 0, const char *name = 0) : KDialog(parent) { setObjectName( QLatin1String( name ) ); setModal(true); setCaption(i18nc("warning about mass file rename", "Warning")); setButtons(Ok | Cancel); KVBox *vbox = new KVBox(this); setMainWidget(vbox); KVBox *hbox = new KVBox(vbox); QLabel *l = new QLabel(hbox); l->setPixmap(SmallIcon("dialog-warning", 32)); l = new QLabel(i18n("You are about to rename the following files. " "Are you sure you want to continue?"), hbox); hbox->setStretchFactor(l, 1); QTreeWidget *lv = new QTreeWidget(vbox); QStringList headers; headers << i18n("Original Name"); headers << i18n("New Name"); lv->setHeaderLabels(headers); lv->setRootIsDecorated(false); int lvHeight = 0; QMap::ConstIterator it = files.constBegin(); for(; it != files.constEnd(); ++it) { QTreeWidgetItem *item = new QTreeWidgetItem(lv); item->setText(0, it.key()); if (it.key() != it.value()) { item->setText(1, it.value()); } else { item->setText(1, i18n("No Change")); } lvHeight += lv->visualItemRect(item).height(); } lvHeight += lv->horizontalScrollBar()->height() + lv->header()->height(); lv->setMinimumHeight(qMin(lvHeight, 400)); resize(qMin(width(), 500), qMin(minimumHeight(), 400)); show(); } }; // // Implementation of ConfigCategoryReader // ConfigCategoryReader::ConfigCategoryReader() : CategoryReaderInterface(), m_currentItem(0) { - KConfigGroup config(KGlobal::config(), "FileRenamer"); + KConfigGroup config(KSharedConfig::openConfig(), "FileRenamer"); QList categoryOrder = config.readEntry("CategoryOrder", QList()); int categoryCount[NumTypes] = { 0 }; // Keep track of each category encountered. // Set a default: if(categoryOrder.isEmpty()) categoryOrder << Artist << Album << Title << Track; QList::ConstIterator catIt = categoryOrder.constBegin(); for(; catIt != categoryOrder.constEnd(); ++catIt) { int catCount = categoryCount[*catIt]++; TagType category = static_cast(*catIt); CategoryID catId(category, catCount); m_options[catId] = TagRenamerOptions(catId); m_categoryOrder << catId; } m_folderSeparators.fill(false, m_categoryOrder.count() - 1); QList checkedSeparators = config.readEntry("CheckedDirSeparators", QList()); QList::ConstIterator it = checkedSeparators.constBegin(); for(; it != checkedSeparators.constEnd(); ++it) { if(*it < m_folderSeparators.count()) m_folderSeparators[*it] = true; } m_musicFolder = config.readPathEntry("MusicFolder", "${HOME}/music"); m_separator = config.readEntry("Separator", " - "); } QString ConfigCategoryReader::categoryValue(TagType type) const { if(!m_currentItem) return QString(); Tag *tag = m_currentItem->file().tag(); switch(type) { case Track: return QString::number(tag->track()); case Year: return QString::number(tag->year()); case Title: return tag->title(); case Artist: return tag->artist(); case Album: return tag->album(); case Genre: return tag->genre(); default: return QString(); } } QString ConfigCategoryReader::prefix(const CategoryID &category) const { return m_options[category].prefix(); } QString ConfigCategoryReader::suffix(const CategoryID &category) const { return m_options[category].suffix(); } TagRenamerOptions::EmptyActions ConfigCategoryReader::emptyAction(const CategoryID &category) const { return m_options[category].emptyAction(); } QString ConfigCategoryReader::emptyText(const CategoryID &category) const { return m_options[category].emptyText(); } QList ConfigCategoryReader::categoryOrder() const { return m_categoryOrder; } QString ConfigCategoryReader::separator() const { return m_separator; } QString ConfigCategoryReader::musicFolder() const { return m_musicFolder; } int ConfigCategoryReader::trackWidth(int categoryNum) const { return m_options[CategoryID(Track, categoryNum)].trackWidth(); } bool ConfigCategoryReader::hasFolderSeparator(int index) const { if(index >= m_folderSeparators.count()) return false; return m_folderSeparators[index]; } bool ConfigCategoryReader::isDisabled(const CategoryID &category) const { return m_options[category].disabled(); } // // Implementation of FileRenamerWidget // FileRenamerWidget::FileRenamerWidget(QWidget *parent) : QWidget(parent), CategoryReaderInterface(), m_ui(new Ui::FileRenamerBase), m_exampleFromFile(false) { m_ui->setupUi(this); // This must be created before createTagRows() is called. m_exampleDialog = new ExampleOptionsDialog(this); createTagRows(); loadConfig(); // Add correct text to combo box. m_ui->m_category->clear(); for(int i = StartTag; i < NumTypes; ++i) { QString category = TagRenamerOptions::tagTypeText(static_cast(i)); m_ui->m_category->addItem(category); } connect(m_exampleDialog, SIGNAL(signalShown()), SLOT(exampleDialogShown())); connect(m_exampleDialog, SIGNAL(signalHidden()), SLOT(exampleDialogHidden())); connect(m_exampleDialog, SIGNAL(dataChanged()), SLOT(dataSelected())); connect(m_exampleDialog, SIGNAL(fileChanged(QString)), this, SLOT(fileSelected(QString))); exampleTextChanged(); } void FileRenamerWidget::loadConfig() { QList checkedSeparators; - KConfigGroup config(KGlobal::config(), "FileRenamer"); + KConfigGroup config(KSharedConfig::openConfig(), "FileRenamer"); for(int i = 0; i < m_rows.count(); ++i) m_rows[i].options = TagRenamerOptions(m_rows[i].category); checkedSeparators = config.readEntry("CheckedDirSeparators", QList()); foreach(int separator, checkedSeparators) { if(separator < m_folderSwitches.count()) m_folderSwitches[separator]->setChecked(true); } QString path = config.readEntry("MusicFolder", "${HOME}/music"); m_ui->m_musicFolder->setUrl(KUrl(path)); m_ui->m_musicFolder->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); m_ui->m_separator->setEditText(config.readEntry("Separator", " - ")); } void FileRenamerWidget::saveConfig() { - KConfigGroup config(KGlobal::config(), "FileRenamer"); + KConfigGroup config(KSharedConfig::openConfig(), "FileRenamer"); QList checkedSeparators; QList categoryOrder; for(int i = 0; i < m_rows.count(); ++i) { int rowId = idOfPosition(i); // Write out in GUI order, not m_rows order m_rows[rowId].options.saveConfig(m_rows[rowId].category.categoryNumber); categoryOrder += m_rows[rowId].category.category; } for(int i = 0; i < m_folderSwitches.count(); ++i) if(m_folderSwitches[i]->isChecked() == true) checkedSeparators += i; config.writeEntry("CheckedDirSeparators", checkedSeparators); config.writeEntry("CategoryOrder", categoryOrder); config.writePathEntry("MusicFolder", m_ui->m_musicFolder->url().path()); config.writeEntry("Separator", m_ui->m_separator->currentText()); config.sync(); } FileRenamerWidget::~FileRenamerWidget() { } int FileRenamerWidget::addRowCategory(TagType category) { static QIcon up = SmallIcon("go-up"); static QIcon down = SmallIcon("go-down"); // Find number of categories already of this type. int categoryCount = 0; for(int i = 0; i < m_rows.count(); ++i) if(m_rows[i].category.category == category) ++categoryCount; Row row; row.category = CategoryID(category, categoryCount); row.position = m_rows.count(); int id = row.position; QFrame *frame = new QFrame(m_mainFrame); QHBoxLayout *frameLayout = new QHBoxLayout(frame); frameLayout->setMargin(3); row.widget = frame; frame->setFrameShape(QFrame::Box); frame->setLineWidth(1); QBoxLayout *mainFrameLayout = static_cast(m_mainFrame->layout()); mainFrameLayout->addWidget(frame, 1); QFrame *buttons = new QFrame(frame); QVBoxLayout *buttonLayout = new QVBoxLayout(buttons); frameLayout->addWidget(buttons); buttons->setFrameStyle(QFrame::Plain | QFrame::Box); buttons->setLineWidth(1); row.upButton = new QPushButton(buttons); row.downButton = new QPushButton(buttons); row.upButton->setIcon(up); row.downButton->setIcon(down); row.upButton->setFlat(true); row.downButton->setFlat(true); upMapper->connect(row.upButton, SIGNAL(clicked()), SLOT(map())); upMapper->setMapping(row.upButton, id); downMapper->connect(row.downButton, SIGNAL(clicked()), SLOT(map())); downMapper->setMapping(row.downButton, id); buttonLayout->addWidget(row.upButton); buttonLayout->addWidget(row.downButton); QString labelText = QString("%1").arg(TagRenamerOptions::tagTypeText(category)); QLabel *label = new QLabel(labelText, frame); frameLayout->addWidget(label, 1); label->setAlignment(Qt::AlignCenter); QVBoxLayout *optionLayout = new QVBoxLayout; frameLayout->addLayout(optionLayout); row.enableButton = new QPushButton(i18nc("remove music genre from file renamer", "Remove"), frame); optionLayout->addWidget(row.enableButton); toggleMapper->connect(row.enableButton, SIGNAL(clicked()), SLOT(map())); toggleMapper->setMapping(row.enableButton, id); row.optionsButton = new QPushButton(i18nc("file renamer genre options", "Options"), frame); optionLayout->addWidget(row.optionsButton); mapper->connect(row.optionsButton, SIGNAL(clicked()), SLOT(map())); mapper->setMapping(row.optionsButton, id); row.widget->show(); m_rows.append(row); // Disable add button if there's too many rows. if(m_rows.count() == MAX_CATEGORIES) m_ui->m_insertCategory->setEnabled(false); return id; } void FileRenamerWidget::moveSignalMappings(int oldId, int newId) { mapper->setMapping(m_rows[oldId].optionsButton, newId); downMapper->setMapping(m_rows[oldId].downButton, newId); upMapper->setMapping(m_rows[oldId].upButton, newId); toggleMapper->setMapping(m_rows[oldId].enableButton, newId); } bool FileRenamerWidget::removeRow(int id) { if(id >= m_rows.count()) { kWarning() << "Trying to remove row, but " << id << " is out-of-range.\n"; return false; } if(m_rows.count() == 1) { kError() << "Can't remove last row of File Renamer.\n"; return false; } // Remove widget. Don't delete it since it appears QSignalMapper may still need it. m_rows[id].widget->deleteLater(); m_rows[id].widget = 0; m_rows[id].enableButton = 0; m_rows[id].upButton = 0; m_rows[id].optionsButton = 0; m_rows[id].downButton = 0; int checkboxPosition = 0; // Remove first checkbox. // If not the first row, remove the checkbox before it. if(m_rows[id].position > 0) checkboxPosition = m_rows[id].position - 1; // The checkbox is contained within a layout widget, so the layout // widget is the one the needs to die. delete m_folderSwitches[checkboxPosition]->parent(); m_folderSwitches.erase(&m_folderSwitches[checkboxPosition]); // Go through all the rows and if they have the same category and a // higher categoryNumber, decrement the number. Also update the // position identifier. for(int i = 0; i < m_rows.count(); ++i) { if(i == id) continue; // Don't mess with ourself. if((m_rows[id].category.category == m_rows[i].category.category) && (m_rows[id].category.categoryNumber < m_rows[i].category.categoryNumber)) { --m_rows[i].category.categoryNumber; } // Items are moving up. if(m_rows[id].position < m_rows[i].position) --m_rows[i].position; } // Every row after the one we delete will have a different identifier, since // the identifier is simply its index into m_rows. So we need to re-do the // signal mappings for the affected rows. for(int i = id + 1; i < m_rows.count(); ++i) moveSignalMappings(i, i - 1); m_rows.erase(&m_rows[id]); // Make sure we update the buttons of affected rows. m_rows[idOfPosition(0)].upButton->setEnabled(false); m_rows[idOfPosition(m_rows.count() - 1)].downButton->setEnabled(false); // We can insert another row now, make sure GUI is updated to match. m_ui->m_insertCategory->setEnabled(true); QTimer::singleShot(0, this, SLOT(exampleTextChanged())); return true; } void FileRenamerWidget::addFolderSeparatorCheckbox() { QWidget *temp = new QWidget(m_mainFrame); m_mainFrame->layout()->addWidget(temp); QHBoxLayout *l = new QHBoxLayout(temp); QCheckBox *cb = new QCheckBox(i18n("Insert folder separator"), temp); m_folderSwitches.append(cb); l->addWidget(cb, 0, Qt::AlignCenter); cb->setChecked(false); connect(cb, SIGNAL(toggled(bool)), SLOT(exampleTextChanged())); temp->show(); } void FileRenamerWidget::createTagRows() { - KConfigGroup config(KGlobal::config(), "FileRenamer"); + KConfigGroup config(KSharedConfig::openConfig(), "FileRenamer"); QList categoryOrder = config.readEntry("CategoryOrder", QList()); if(categoryOrder.isEmpty()) categoryOrder << Artist << Album << Title << Track; // Setup arrays. m_rows.reserve(categoryOrder.count()); m_folderSwitches.reserve(categoryOrder.count() - 1); mapper = new QSignalMapper(this); mapper->setObjectName( QLatin1String("signal mapper" )); toggleMapper = new QSignalMapper(this); toggleMapper->setObjectName( QLatin1String("toggle mapper" )); upMapper = new QSignalMapper(this); upMapper->setObjectName( QLatin1String("up button mapper" )); downMapper = new QSignalMapper(this); downMapper->setObjectName( QLatin1String("down button mapper" )); connect(mapper, SIGNAL(mapped(int)), SLOT(showCategoryOption(int))); connect(toggleMapper, SIGNAL(mapped(int)), SLOT(slotRemoveRow(int))); connect(upMapper, SIGNAL(mapped(int)), SLOT(moveItemUp(int))); connect(downMapper, SIGNAL(mapped(int)), SLOT(moveItemDown(int))); m_mainFrame = new QFrame(m_ui->m_mainView); m_ui->m_mainView->setWidget(m_mainFrame); m_ui->m_mainView->setWidgetResizable(true); QVBoxLayout *frameLayout = new QVBoxLayout(m_mainFrame); frameLayout->setMargin(10); frameLayout->setSpacing(5); // OK, the deal with the categoryOrder variable is that we need to create // the rows in the order that they were saved in (the order given by categoryOrder). // The signal mappers operate according to the row identifier. To find the position of // a row given the identifier, use m_rows[id].position. To find the id of a given // position, use idOfPosition(position). QList::ConstIterator it = categoryOrder.constBegin(); for(; it != categoryOrder.constEnd(); ++it) { if(*it < StartTag || *it >= NumTypes) { kError() << "Invalid category encountered in file renamer configuration.\n"; continue; } if(m_rows.count() == MAX_CATEGORIES) { kError() << "Maximum number of File Renamer tags reached, bailing.\n"; break; } TagType i = static_cast(*it); addRowCategory(i); // Insert the directory separator checkbox if this isn't the last // item. QList::ConstIterator dup(it); // Check for last item if(++dup != categoryOrder.constEnd()) addFolderSeparatorCheckbox(); } m_rows.first().upButton->setEnabled(false); m_rows.last().downButton->setEnabled(false); // If we have maximum number of categories already, don't let the user // add more. if(m_rows.count() >= MAX_CATEGORIES) m_ui->m_insertCategory->setEnabled(false); } void FileRenamerWidget::exampleTextChanged() { // Just use .mp3 as an example if(m_exampleFromFile && (m_exampleFile.isEmpty() || !FileHandle(m_exampleFile).tag()->isValid())) { m_ui->m_exampleText->setText(i18n("No file selected, or selected file has no tags.")); return; } m_ui->m_exampleText->setText(FileRenamer::fileName(*this) + ".mp3"); } QString FileRenamerWidget::fileCategoryValue(TagType category) const { FileHandle file(m_exampleFile); Tag *tag = file.tag(); switch(category) { case Track: return QString::number(tag->track()); case Year: return QString::number(tag->year()); case Title: return tag->title(); case Artist: return tag->artist(); case Album: return tag->album(); case Genre: return tag->genre(); default: return QString(); } } QString FileRenamerWidget::categoryValue(TagType category) const { if(m_exampleFromFile) return fileCategoryValue(category); const ExampleOptions *example = m_exampleDialog->widget(); switch (category) { case Track: return example->m_exampleTrack->text(); case Year: return example->m_exampleYear->text(); case Title: return example->m_exampleTitle->text(); case Artist: return example->m_exampleArtist->text(); case Album: return example->m_exampleAlbum->text(); case Genre: return example->m_exampleGenre->text(); default: return QString(); } } QList FileRenamerWidget::categoryOrder() const { QList list; // Iterate in GUI row order. for(int i = 0; i < m_rows.count(); ++i) { int rowId = idOfPosition(i); list += m_rows[rowId].category; } return list; } bool FileRenamerWidget::hasFolderSeparator(int index) const { if(index >= m_folderSwitches.count()) return false; return m_folderSwitches[index]->isChecked(); } void FileRenamerWidget::moveItem(int id, MovementDirection direction) { QWidget *l = m_rows[id].widget; int bottom = m_rows.count() - 1; int pos = m_rows[id].position; int newPos = (direction == MoveUp) ? pos - 1 : pos + 1; // Item we're moving can't go further down after this. if((pos == (bottom - 1) && direction == MoveDown) || (pos == bottom && direction == MoveUp)) { int idBottomRow = idOfPosition(bottom); int idAboveBottomRow = idOfPosition(bottom - 1); m_rows[idBottomRow].downButton->setEnabled(true); m_rows[idAboveBottomRow].downButton->setEnabled(false); } // We're moving the top item, do some button switching. if((pos == 0 && direction == MoveDown) || (pos == 1 && direction == MoveUp)) { int idTopItem = idOfPosition(0); int idBelowTopItem = idOfPosition(1); m_rows[idTopItem].upButton->setEnabled(true); m_rows[idBelowTopItem].upButton->setEnabled(false); } // This is the item we're swapping with. int idSwitchWith = idOfPosition(newPos); QWidget *w = m_rows[idSwitchWith].widget; // Update the table of widget rows. std::swap(m_rows[id].position, m_rows[idSwitchWith].position); // Move the item two spaces above/below its previous position. It has to // be 2 spaces because of the checkbox. QBoxLayout *layout = dynamic_cast(m_mainFrame->layout()); if ( !layout ) return; layout->removeWidget(l); layout->insertWidget(2 * newPos, l); // Move the top item two spaces in the opposite direction, for a similar // reason. layout->removeWidget(w); layout->insertWidget(2 * pos, w); layout->invalidate(); QTimer::singleShot(0, this, SLOT(exampleTextChanged())); } int FileRenamerWidget::idOfPosition(int position) const { if(position >= m_rows.count()) { kError() << "Search for position " << position << " out-of-range.\n"; return -1; } for(int i = 0; i < m_rows.count(); ++i) if(m_rows[i].position == position) return i; kError() << "Unable to find identifier for position " << position << endl; return -1; } int FileRenamerWidget::findIdentifier(const CategoryID &category) const { for(int index = 0; index < m_rows.count(); ++index) if(m_rows[index].category == category) return index; kError() << "Unable to find match for category " << TagRenamerOptions::tagTypeText(category.category) << ", number " << category.categoryNumber << endl; return MAX_CATEGORIES; } void FileRenamerWidget::enableAllUpButtons() { for(int i = 0; i < m_rows.count(); ++i) m_rows[i].upButton->setEnabled(true); } void FileRenamerWidget::enableAllDownButtons() { for(int i = 0; i < m_rows.count(); ++i) m_rows[i].downButton->setEnabled(true); } void FileRenamerWidget::showCategoryOption(int id) { TagOptionsDialog *dialog = new TagOptionsDialog(this, m_rows[id].options, m_rows[id].category.categoryNumber); if(dialog->exec() == QDialog::Accepted) { m_rows[id].options = dialog->options(); exampleTextChanged(); } delete dialog; } void FileRenamerWidget::moveItemUp(int id) { moveItem(id, MoveUp); } void FileRenamerWidget::moveItemDown(int id) { moveItem(id, MoveDown); } void FileRenamerWidget::toggleExampleDialog() { m_exampleDialog->setHidden(!m_exampleDialog->isHidden()); } void FileRenamerWidget::insertCategory() { TagType category = static_cast(m_ui->m_category->currentIndex()); if(m_ui->m_category->currentIndex() < 0 || category >= NumTypes) { kError() << "Trying to add unknown category somehow.\n"; return; } // We need to enable the down button of the current bottom row since it // can now move down. int idBottom = idOfPosition(m_rows.count() - 1); m_rows[idBottom].downButton->setEnabled(true); addFolderSeparatorCheckbox(); // Identifier of new row. int id = addRowCategory(category); // Set its down button to be disabled. m_rows[id].downButton->setEnabled(false); m_mainFrame->layout()->invalidate(); m_ui->m_mainView->update(); // Now update according to the code in loadConfig(). m_rows[id].options = TagRenamerOptions(m_rows[id].category); exampleTextChanged(); } void FileRenamerWidget::exampleDialogShown() { m_ui->m_showExample->setText(i18n("Hide Renamer Test Dialog")); } void FileRenamerWidget::exampleDialogHidden() { m_ui->m_showExample->setText(i18n("Show Renamer Test Dialog")); } void FileRenamerWidget::fileSelected(const QString &file) { m_exampleFromFile = true; m_exampleFile = file; exampleTextChanged(); } void FileRenamerWidget::dataSelected() { m_exampleFromFile = false; exampleTextChanged(); } QString FileRenamerWidget::separator() const { return m_ui->m_separator->currentText(); } QString FileRenamerWidget::musicFolder() const { return m_ui->m_musicFolder->url().path(); } void FileRenamerWidget::slotRemoveRow(int id) { // Remove the given identified row. if(!removeRow(id)) kError() << "Unable to remove row " << id << endl; } // // Implementation of FileRenamer // FileRenamer::FileRenamer() { } void FileRenamer::rename(PlaylistItem *item) { PlaylistItemList list; list.append(item); rename(list); } void FileRenamer::rename(const PlaylistItemList &items) { ConfigCategoryReader reader; QStringList errorFiles; QMap map; QMap itemMap; for(PlaylistItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) { reader.setPlaylistItem(*it); QString oldFile = (*it)->file().absFilePath(); QString extension = (*it)->file().fileInfo().suffix(); QString newFile = fileName(reader) + '.' + extension; if(oldFile != newFile) { map[oldFile] = newFile; itemMap[oldFile] = *it; } } if(itemMap.isEmpty() || ConfirmationDialog(map).exec() != QDialog::Accepted) return; QApplication::setOverrideCursor(Qt::WaitCursor); for(QMap::ConstIterator it = map.constBegin(); it != map.constEnd(); ++it) { if(moveFile(it.key(), it.value())) { itemMap[it.key()]->setFile(it.value()); itemMap[it.key()]->refresh(); setFolderIcon(it.value(), itemMap[it.key()]); } else errorFiles << i18n("%1 to %2", it.key(), it.value()); processEvents(); } QApplication::restoreOverrideCursor(); if(!errorFiles.isEmpty()) KMessageBox::errorList(0, i18n("The following rename operations failed:\n"), errorFiles); } bool FileRenamer::moveFile(const QString &src, const QString &dest) { kDebug() << "Moving file " << src << " to " << dest; if(src == dest) return false; // Escape URL. KUrl srcURL = KUrl(src); KUrl dstURL = KUrl(dest); // Clean it. srcURL.cleanPath(); dstURL.cleanPath(); // Make sure it is valid. if(!srcURL.isValid() || !dstURL.isValid()) return false; // Get just the directory. KUrl dir = dstURL; dir.setFileName(QString()); // Create the directory. if(!KStandardDirs::exists(dir.path())) if(!KStandardDirs::makeDir(dir.path())) { kError() << "Unable to create directory " << dir.path() << endl; return false; } // Move the file. KIO::Job *job = KIO::file_move(srcURL, dstURL); return KIO::NetAccess::synchronousRun(job, 0); } void FileRenamer::setFolderIcon(const KUrl &dst, const PlaylistItem *item) { if(item->file().tag()->album().isEmpty() || !item->file().coverInfo()->hasCover()) { return; } KUrl dstURL = dst; dstURL.cleanPath(); // Split path, and go through each path element. If a path element has // the album information, set its folder icon. QStringList elements = dstURL.directory().split('/', QString::SkipEmptyParts); QString path; for(QStringList::ConstIterator it = elements.constBegin(); it != elements.constEnd(); ++it) { path.append('/' + (*it)); kDebug() << "Checking path: " << path; if((*it).contains(item->file().tag()->album() ) && !QFile::exists(path + "/.directory")) { // Seems to be a match, let's set the folder icon for the current // path. First we should write out the file. QPixmap thumb = item->file().coverInfo()->pixmap(CoverInfo::Thumbnail); thumb.save(path + "/.juk-thumbnail.png", "PNG"); KDesktopFile dirFile(path + "/.directory"); KConfigGroup desktopGroup(dirFile.desktopGroup()); if(!desktopGroup.hasKey("Icon")) { desktopGroup.writePathEntry("Icon", QString("%1/.juk-thumbnail.png").arg(path)); dirFile.sync(); } return; } } } /** * Returns iterator pointing to the last item enabled in the given list with * a non-empty value (or is required to be included). */ QList::ConstIterator lastEnabledItem(const QList &list, const CategoryReaderInterface &interface) { QList::ConstIterator it = list.constBegin(); QList::ConstIterator last = list.constEnd(); for(; it != list.constEnd(); ++it) { if(interface.isRequired(*it) || (!interface.isDisabled(*it) && !interface.categoryValue((*it).category).isEmpty())) { last = it; } } return last; } QString FileRenamer::fileName(const CategoryReaderInterface &interface) { const QList categoryOrder = interface.categoryOrder(); const QString separator = interface.separator(); const QString folder = interface.musicFolder(); QList::ConstIterator lastEnabled; int i = 0; QStringList list; QChar dirSeparator (QDir::separator()); // Use lastEnabled to properly handle folder separators. lastEnabled = lastEnabledItem(categoryOrder, interface); bool pastLast = false; // Toggles to true once we've passed lastEnabled. for(QList::ConstIterator it = categoryOrder.constBegin(); it != categoryOrder.constEnd(); ++it, ++i) { if(it == lastEnabled) pastLast = true; if(interface.isDisabled(*it)) continue; QString value = interface.value(*it); // The user can use the folder separator checkbox to add folders, so don't allow // slashes that slip in to accidentally create new folders. Should we filter this // back out when showing it in the GUI? value.replace('/', "%2f"); if(!pastLast && interface.hasFolderSeparator(i)) value.append(dirSeparator); if(interface.isRequired(*it) || !value.isEmpty()) list.append(value); } // Construct a single string representation, handling strings ending in // '/' specially QString result; for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); /* Empty */) { result += *it; ++it; // Manually advance iterator to check for end-of-list. // Add separator unless at a directory boundary if(it != list.constEnd() && !(*it).startsWith(dirSeparator) && // Check beginning of next item. !result.endsWith(dirSeparator)) { result += separator; } } return QString(folder + dirSeparator + result); } // vim: set et sw=4 tw=0 sta: diff --git a/juk.cpp b/juk.cpp index d9fcca2e..8f9b4290 100644 --- a/juk.cpp +++ b/juk.cpp @@ -1,631 +1,631 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * Copyright (C) 2008, 2009 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "juk.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "slideraction.h" #include "statuslabel.h" #include "splashscreen.h" #include "systemtray.h" #include "keydialog.h" #include "tagguesserconfigdlg.h" #include "filerenamerconfigdlg.h" #include "scrobbler.h" #include "scrobbleconfigdlg.h" #include "actioncollection.h" #include "cache.h" #include "playlistsplitter.h" #include "collectionlist.h" #include "covermanager.h" #include "tagtransactionmanager.h" using namespace ActionCollection; JuK* JuK::m_instance; template void deleteAndClear(T *&ptr) { delete ptr; ptr = 0; } //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// JuK::JuK(const QStringList &filesToOpen, QWidget *parent) : KXmlGuiWindow(parent, Qt::WindowFlags(Qt::WA_DeleteOnClose)), m_splitter(0), m_statusLabel(0), m_systemTray(0), m_player(new PlayerManager), m_scrobbler(0), m_shuttingDown(false), m_filesToOpen(filesToOpen) { // Expect segfaults if you change this order. m_instance = this; readSettings(); if(m_showSplash && !m_startDocked && Cache::cacheFileExists()) { if(SplashScreen* splash = SplashScreen::instance()) { splash->show(); qApp->processEvents(); } } setupActions(); setupLayout(); - bool firstRun = !KGlobal::config()->hasGroup("MainWindow"); + bool firstRun = !KSharedConfig::openConfig()->hasGroup("MainWindow"); if(firstRun) { - KConfigGroup mainWindowConfig(KGlobal::config(), "MainWindow"); + KConfigGroup mainWindowConfig(KSharedConfig::openConfig(), "MainWindow"); KConfigGroup playToolBarConfig(&mainWindowConfig, "Toolbar playToolBar"); playToolBarConfig.writeEntry("ToolButtonStyle", "IconOnly"); } QSize defaultSize(800, 480); if(QApplication::isRightToLeft()) setupGUI(defaultSize, ToolBar | Save | Create, "jukui-rtl.rc"); else setupGUI(defaultSize, ToolBar | Save | Create); // Center the GUI if this is our first run ever. if(firstRun) { QRect r = rect(); r.moveCenter(QApplication::desktop()->screenGeometry().center()); move(r.topLeft()); } connect(m_splitter, SIGNAL(guiReady()), SLOT(slotSetupSystemTray())); readConfig(); setupGlobalAccels(); activateScrobblerIfEnabled(); connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), SLOT(slotAboutToQuit())); // slotCheckCache loads the cached entries first to populate the collection list QTimer::singleShot(0, this, SLOT(slotClearOldCovers())); QTimer::singleShot(0, CollectionList::instance(), SLOT(startLoadingCachedItems())); QTimer::singleShot(0, this, SLOT(slotProcessArgs())); } JuK::~JuK() { } JuK* JuK::JuKInstance() { return m_instance; } PlayerManager *JuK::playerManager() const { return m_player; } void JuK::coverDownloaded(const QPixmap &cover) { QString event(cover.isNull() ? "coverFailed" : "coverDownloaded"); KNotification *notification = new KNotification(event, this); notification->setPixmap(cover); notification->setFlags(KNotification::CloseOnTimeout); if(cover.isNull()) notification->setText(i18n("Your album art failed to download.")); else notification->setText(i18n("Your album art has finished downloading.")); notification->sendEvent(); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// void JuK::setupLayout() { new TagTransactionManager(this); kDebug() << "Creating GUI"; QTime stopwatch; stopwatch.start(); m_splitter = new PlaylistSplitter(m_player, this); setCentralWidget(m_splitter); m_statusLabel = new StatusLabel(m_splitter->playlist(), statusBar()); connect(CollectionList::instance(), SIGNAL(signalCollectionChanged()), m_statusLabel, SLOT(updateData())); statusBar()->addWidget(m_statusLabel, 1); m_player->setStatusLabel(m_statusLabel); m_splitter->setFocus(); kDebug() << "GUI created in" << stopwatch.elapsed() << "ms"; } void JuK::setupActions() { KActionCollection *collection = ActionCollection::actions(); // Setup KDE standard actions that JuK uses. KStandardAction::quit(this, SLOT(slotQuit()), collection); KStandardAction::undo(this, SLOT(slotUndo()), collection); KStandardAction::cut(collection); KStandardAction::copy(collection); KStandardAction::paste(collection); QAction *clear = KStandardAction::clear(collection); KStandardAction::selectAll(collection); KStandardAction::keyBindings(this, SLOT(slotEditKeys()), collection); // Setup the menu which handles the random play options. KActionMenu *actionMenu = collection->add("actionMenu"); actionMenu->setText(i18n("&Random Play")); actionMenu->setIcon(KIcon( QLatin1String( "media-playlist-shuffle" ))); actionMenu->setDelayed(false); QActionGroup* randomPlayGroup = new QActionGroup(this); QAction *act = collection->add("disableRandomPlay"); act->setText(i18n("&Disable Random Play")); act->setIcon(KIcon( QLatin1String( "go-down" ))); act->setActionGroup(randomPlayGroup); actionMenu->addAction(act); m_randomPlayAction = collection->add("randomPlay"); m_randomPlayAction->setText(i18n("Use &Random Play")); m_randomPlayAction->setIcon(KIcon( QLatin1String( "media-playlist-shuffle" ))); m_randomPlayAction->setActionGroup(randomPlayGroup); actionMenu->addAction(m_randomPlayAction); act = collection->add("albumRandomPlay"); act->setText(i18n("Use &Album Random Play")); act->setIcon(KIcon( QLatin1String( "media-playlist-shuffle" ))); act->setActionGroup(randomPlayGroup); connect(act, SIGNAL(triggered(bool)), SLOT(slotCheckAlbumNextAction(bool))); actionMenu->addAction(act); act = collection->addAction("removeFromPlaylist", clear, SLOT(clear())); act->setText(i18n("Remove From Playlist")); act->setIcon(KIcon( QLatin1String( "list-remove" ))); act = collection->add("crossfadeTracks"); act->setText(i18n("Crossfade Between Tracks")); connect(act, SIGNAL(triggered(bool)), m_player, SLOT(setCrossfadeEnabled(bool))); act = collection->addAction("play", m_player, SLOT(play())); act->setText(i18n("&Play")); act->setIcon(KIcon( QLatin1String( "media-playback-start" ))); act = collection->addAction("pause", m_player, SLOT(pause())); act->setText(i18n("P&ause")); act->setIcon(KIcon( QLatin1String( "media-playback-pause" ))); act = collection->addAction("stop", m_player, SLOT(stop())); act->setText(i18n("&Stop")); act->setIcon(KIcon( QLatin1String( "media-playback-stop" ))); act = new KToolBarPopupAction(KIcon( QLatin1String( "media-skip-backward") ), i18nc("previous track", "Previous" ), collection); collection->addAction("back", act); connect(act, SIGNAL(triggered(bool)), m_player, SLOT(back())); act = collection->addAction("forward", m_player, SLOT(forward())); act->setText(i18nc("next track", "&Next")); act->setIcon(KIcon( QLatin1String( "media-skip-forward" ))); act = collection->addAction("loopPlaylist"); act->setText(i18n("&Loop Playlist")); act->setCheckable(true); act = collection->add("resizeColumnsManually"); act->setText(i18n("&Resize Playlist Columns Manually")); // the following are not visible by default act = collection->addAction("mute", m_player, SLOT(mute())); act->setText(i18nc("silence playback", "Mute")); act->setIcon(KIcon( QLatin1String( "audio-volume-muted" ))); act = collection->addAction("volumeUp", m_player, SLOT(volumeUp())); act->setText(i18n("Volume Up")); act->setIcon(KIcon( QLatin1String( "audio-volume-high" ))); act = collection->addAction("volumeDown", m_player, SLOT(volumeDown())); act->setText(i18n("Volume Down")); act->setIcon(KIcon( QLatin1String( "audio-volume-low" ))); act = collection->addAction("playPause", m_player, SLOT(playPause())); act->setText(i18n("Play / Pause")); act->setIcon(KIcon( QLatin1String( "media-playback-start" ))); act = collection->addAction("seekForward", m_player, SLOT(seekForward())); act->setText(i18n("Seek Forward")); act->setIcon(KIcon( QLatin1String( "media-seek-forward" ))); act = collection->addAction("seekBack", m_player, SLOT(seekBack())); act->setText(i18n("Seek Back")); act->setIcon(KIcon( QLatin1String( "media-seek-backward" ))); act = collection->addAction("showHide", this, SLOT(slotShowHide())); act->setText(i18n("Show / Hide")); ////////////////////////////////////////////////// // settings menu ////////////////////////////////////////////////// m_toggleSplashAction = collection->add("showSplashScreen"); m_toggleSplashAction->setText(i18n("Show Splash Screen on Startup")); m_toggleSystemTrayAction = collection->add("toggleSystemTray"); m_toggleSystemTrayAction->setText(i18n("&Dock in System Tray")); connect(m_toggleSystemTrayAction, SIGNAL(triggered(bool)), SLOT(slotToggleSystemTray(bool))); m_toggleDockOnCloseAction = collection->add("dockOnClose"); m_toggleDockOnCloseAction->setText(i18n("&Stay in System Tray on Close")); m_togglePopupsAction = collection->add("togglePopups"); m_togglePopupsAction->setText(i18n("Popup &Track Announcement")); act = collection->add("saveUpcomingTracks"); act->setText(i18n("Save &Play Queue on Exit")); act = collection->addAction("tagGuesserConfig", this, SLOT(slotConfigureTagGuesser())); act->setText(i18n("&Tag Guesser...")); act = collection->addAction("fileRenamerConfig", this, SLOT(slotConfigureFileRenamer())); act->setText(i18n("&File Renamer...")); act = collection->addAction("scrobblerConfig", this, SLOT(slotConfigureScrobbling())); act->setText(i18n("&Configure scrobbling...")); ////////////////////////////////////////////////// // just in the toolbar ////////////////////////////////////////////////// collection->addAction("trackPositionAction", new TrackPositionAction(i18n("Track Position"), this)); collection->addAction("volumeAction", new VolumeAction(i18n("Volume"), this)); ActionCollection::actions()->addAssociatedWidget(this); foreach (QAction* action, ActionCollection::actions()->actions()) action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } void JuK::slotSetupSystemTray() { if(m_toggleSystemTrayAction && m_toggleSystemTrayAction->isChecked()) { kDebug() << "Setting up systray"; QTime stopwatch; stopwatch.start(); m_systemTray = new SystemTray(m_player, this); m_systemTray->setObjectName( QLatin1String("systemTray" )); m_toggleDockOnCloseAction->setEnabled(true); m_togglePopupsAction->setEnabled(true); kDebug() << "Finished setting up systray, took" << stopwatch.elapsed() << "ms"; } else { m_systemTray = 0; m_toggleDockOnCloseAction->setEnabled(false); m_togglePopupsAction->setEnabled(false); } } void JuK::setupGlobalAccels() { KeyDialog::setupActionShortcut("play"); KeyDialog::setupActionShortcut("playPause"); KeyDialog::setupActionShortcut("stop"); KeyDialog::setupActionShortcut("back"); KeyDialog::setupActionShortcut("forward"); KeyDialog::setupActionShortcut("seekBack"); KeyDialog::setupActionShortcut("seekForward"); KeyDialog::setupActionShortcut("volumeUp"); KeyDialog::setupActionShortcut("volumeDown"); KeyDialog::setupActionShortcut("mute"); KeyDialog::setupActionShortcut("showHide"); KeyDialog::setupActionShortcut("forwardAlbum"); } void JuK::slotProcessArgs() { CollectionList::instance()->addFiles(m_filesToOpen); } void JuK::slotClearOldCovers() { // Find all saved covers from the previous run of JuK and clear them out, in case // we find our tracks in a different order this run, which would cause old saved // covers to be wrong. // See mpris2/mediaplayer2player.cpp QStringList oldFiles = KGlobal::dirs()->findAllResources("tmp", "juk-cover-*.png"); foreach(const QString &file, oldFiles) { kWarning() << "Removing old cover" << file; if(!QFile::remove(file)) { kError() << "Failed to remove old cover" << file; } } } void JuK::keyPressEvent(QKeyEvent *e) { if (e->key() >= Qt::Key_Back && e->key() <= Qt::Key_MediaLast) e->accept(); KXmlGuiWindow::keyPressEvent(e); } /** * These are settings that need to be know before setting up the GUI. */ void JuK::readSettings() { - KConfigGroup config(KGlobal::config(), "Settings"); + KConfigGroup config(KSharedConfig::openConfig(), "Settings"); m_showSplash = config.readEntry("ShowSplashScreen", true); m_startDocked = config.readEntry("StartDocked", false); } void JuK::readConfig() { // player settings - KConfigGroup playerConfig(KGlobal::config(), "Player"); + KConfigGroup playerConfig(KSharedConfig::openConfig(), "Player"); if(m_player) { const int maxVolume = 100; const int volume = playerConfig.readEntry("Volume", maxVolume); m_player->setVolume(volume * 0.01); if(ActionCollection::action("volumeAction")->button()) ActionCollection::action("volumeAction")->button()->refresh(); bool enableCrossfade = playerConfig.readEntry("CrossfadeTracks", true); m_player->setCrossfadeEnabled(enableCrossfade); //ActionCollection::action("crossfadeTracks")->setChecked(enableCrossfade); } // Default to no random play ActionCollection::action("disableRandomPlay")->setChecked(true); QString randomPlayMode = playerConfig.readEntry("RandomPlay", "Disabled"); if(randomPlayMode == "true" || randomPlayMode == "Normal") m_randomPlayAction->setChecked(true); else if(randomPlayMode == "AlbumRandomPlay") ActionCollection::action("albumRandomPlay")->setChecked(true); bool loopPlaylist = playerConfig.readEntry("LoopPlaylist", false); //ActionCollection::action("loopPlaylist")->setChecked(loopPlaylist); // general settings - KConfigGroup settingsConfig(KGlobal::config(), "Settings"); + KConfigGroup settingsConfig(KSharedConfig::openConfig(), "Settings"); bool dockInSystemTray = settingsConfig.readEntry("DockInSystemTray", true); m_toggleSystemTrayAction->setChecked(dockInSystemTray); bool dockOnClose = settingsConfig.readEntry("DockOnClose", true); m_toggleDockOnCloseAction->setChecked(dockOnClose); bool showPopups = settingsConfig.readEntry("TrackPopup", false); m_togglePopupsAction->setChecked(showPopups); m_toggleSplashAction->setChecked(m_showSplash); } void JuK::saveConfig() { // player settings - KConfigGroup playerConfig(KGlobal::config(), "Player"); + KConfigGroup playerConfig(KSharedConfig::openConfig(), "Player"); if (m_player) { playerConfig.writeEntry("Volume", static_cast(100.0 * m_player->volume())); } playerConfig.writeEntry("RandomPlay", m_randomPlayAction->isChecked()); KAction *a = ActionCollection::action("loopPlaylist"); playerConfig.writeEntry("LoopPlaylist", a->isChecked()); a = ActionCollection::action("crossfadeTracks"); playerConfig.writeEntry("CrossfadeTracks", a->isChecked()); a = ActionCollection::action("albumRandomPlay"); if(a->isChecked()) playerConfig.writeEntry("RandomPlay", "AlbumRandomPlay"); else if(m_randomPlayAction->isChecked()) playerConfig.writeEntry("RandomPlay", "Normal"); else playerConfig.writeEntry("RandomPlay", "Disabled"); // general settings - KConfigGroup settingsConfig(KGlobal::config(), "Settings"); + KConfigGroup settingsConfig(KSharedConfig::openConfig(), "Settings"); settingsConfig.writeEntry("ShowSplashScreen", m_toggleSplashAction->isChecked()); settingsConfig.writeEntry("StartDocked", m_startDocked); settingsConfig.writeEntry("DockInSystemTray", m_toggleSystemTrayAction->isChecked()); settingsConfig.writeEntry("DockOnClose", m_toggleDockOnCloseAction->isChecked()); settingsConfig.writeEntry("TrackPopup", m_togglePopupsAction->isChecked()); - KGlobal::config()->sync(); + KSharedConfig::openConfig()->sync(); } bool JuK::queryClose() { if(!m_shuttingDown && !qApp->isSavingSession() && m_systemTray && m_toggleDockOnCloseAction->isChecked()) { KMessageBox::information(this, i18n("Closing the main window will keep JuK running in the system tray. " "Use Quit from the File menu to quit the application."), i18n("Docking in System Tray"), "hideOnCloseInfo"); hide(); return false; } else { // Some phonon backends will crash on shutdown unless we've stopped // playback. if(m_player->playing()) m_player->stop(); // Save configuration data. m_startDocked = !isVisible(); saveConfig(); return true; } } //////////////////////////////////////////////////////////////////////////////// // private slot definitions //////////////////////////////////////////////////////////////////////////////// void JuK::slotShowHide() { setHidden(!isHidden()); } void JuK::slotAboutToQuit() { m_shuttingDown = true; deleteAndClear(m_systemTray); deleteAndClear(m_splitter); deleteAndClear(m_player); deleteAndClear(m_statusLabel); // Playlists depend on CoverManager, so CoverManager should shutdown as // late as possible CoverManager::shutdown(); } void JuK::slotQuit() { m_shuttingDown = true; kapp->quit(); } //////////////////////////////////////////////////////////////////////////////// // settings menu //////////////////////////////////////////////////////////////////////////////// void JuK::slotToggleSystemTray(bool enabled) { if(enabled && !m_systemTray) slotSetupSystemTray(); else if(!enabled && m_systemTray) { delete m_systemTray; m_systemTray = 0; m_toggleDockOnCloseAction->setEnabled(false); m_togglePopupsAction->setEnabled(false); } } void JuK::slotEditKeys() { KeyDialog::configure(ActionCollection::actions(), this); } void JuK::slotConfigureTagGuesser() { TagGuesserConfigDlg(this).exec(); } void JuK::slotConfigureFileRenamer() { FileRenamerConfigDlg(this).exec(); } void JuK::slotConfigureScrobbling() { ScrobbleConfigDlg(this).exec(); activateScrobblerIfEnabled(); } void JuK::activateScrobblerIfEnabled() { bool isScrobbling = Scrobbler::isScrobblingEnabled(); if (!m_scrobbler && isScrobbling) { m_scrobbler = new Scrobbler(this); connect (m_player, SIGNAL(signalItemChanged(FileHandle)), m_scrobbler, SLOT(nowPlaying(FileHandle))); } else if (m_scrobbler && !isScrobbling) { delete m_scrobbler; m_scrobbler = 0; } } void JuK::slotUndo() { TagTransactionManager::instance()->undo(); } void JuK::slotCheckAlbumNextAction(bool albumRandomEnabled) { // If album random play is enabled, then enable the Play Next Album action // unless we're not playing right now. if(albumRandomEnabled && !m_player->playing()) albumRandomEnabled = false; action("forwardAlbum")->setEnabled(albumRandomEnabled); } // vim: set et sw=4 tw=0 sta: diff --git a/keydialog.cpp b/keydialog.cpp index 8b740d9a..f5098f81 100644 --- a/keydialog.cpp +++ b/keydialog.cpp @@ -1,224 +1,224 @@ /** * Copyright (C) 2003 Stefan Asserhall * Copyright (C) 2007, 2008 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "keydialog.h" #include "actioncollection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Table of shortcut keys for each action, key group and three or four button modifier struct KeyDialog::KeyInfo { QString action; KShortcut shortcut[3]; }; const KeyDialog::KeyInfo KeyDialog::keyInfo[] = { { "playPause", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::Key_P), KShortcut(Qt::Key_MediaPlay) } }, { "stop", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::Key_S), KShortcut(Qt::Key_MediaStop) } }, { "back", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::Key_Left), KShortcut(Qt::Key_MediaPrevious) } }, { "forward", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::Key_Right), KShortcut(Qt::Key_MediaNext) } }, { "forwardAlbum", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::Key_Up), KShortcut(Qt::CTRL+Qt::Key_MediaNext) } }, { "seekBack", { KShortcut(), KShortcut(Qt::CTRL+Qt::SHIFT+Qt::ALT+Qt::Key_Left), KShortcut(Qt::SHIFT+Qt::Key_MediaPrevious) } }, { "seekForward", { KShortcut(), KShortcut(Qt::CTRL+Qt::SHIFT+Qt::ALT+Qt::Key_Right), KShortcut(Qt::SHIFT+Qt::Key_MediaNext) } }, { "volumeUp", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::SHIFT+Qt::Key_Up), KShortcut(Qt::Key_VolumeUp) } }, { "volumeDown", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::SHIFT+Qt::Key_Down), KShortcut(Qt::Key_VolumeDown) } }, { "mute", { KShortcut(), KShortcut(Qt::CTRL+Qt::ALT+Qt::Key_M), KShortcut(Qt::Key_VolumeMute) } }, { "showHide", { KShortcut(), KShortcut(), KShortcut() } } }; const uint KeyDialog::keyInfoCount = sizeof(KeyDialog::keyInfo) / sizeof(KeyDialog::keyInfo[0]); KeyDialog::KeyDialog(KActionCollection *actionCollection, QWidget *parent) : KDialog(parent), m_actionCollection(actionCollection) { setCaption(i18n("Configure Shortcuts")); setButtons(Default | Ok | Cancel); // Read key group from configuration - KConfigGroup config(KGlobal::config(), "Shortcuts"); + KConfigGroup config(KSharedConfig::openConfig(), "Shortcuts"); int selectedButton = config.readEntry("GlobalKeys", int(StandardKeys)); // Create widgets for key chooser KVBox *vbox = new KVBox(this); vbox->setSpacing(KDialog::spacingHint()); m_pKeyChooser = new KShortcutsEditor(actionCollection, vbox); // Create buttons to select key group QGroupBox *buttonBox = new QGroupBox(i18n("Global Shortcuts"), vbox); m_group = new QButtonGroup(buttonBox); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonBox->setLayout(buttonLayout); QRadioButton *radioButton = new QRadioButton(i18n("&No keys"), buttonBox); m_group->addButton(radioButton, NoKeys); buttonLayout->addWidget(radioButton); radioButton = new QRadioButton(i18n("&Standard keys"), buttonBox); m_group->addButton(radioButton, StandardKeys); buttonLayout->addWidget(radioButton); radioButton = new QRadioButton(i18n("&Multimedia keys"), buttonBox); m_group->addButton(radioButton, MultimediaKeys); buttonLayout->addWidget(radioButton); connect(m_group, SIGNAL(buttonClicked(int)), this, SLOT(slotKeys(int))); buttonBox->setWhatsThis( i18n("Here you can select the keys used as global shortcuts to control the player")); m_group->button(selectedButton)->setChecked(true); connect(this, SIGNAL(defaultClicked()), this, SLOT(slotDefault())); setMainWidget(vbox); resize(400, 500); // Make it bigger! } KeyDialog::~KeyDialog() { } int KeyDialog::configure() { // Show the dialog and save configuration if accepted int retcode = exec(); if(retcode == Accepted) { - KConfigGroup config(KGlobal::config(), "Shortcuts"); + KConfigGroup config(KSharedConfig::openConfig(), "Shortcuts"); config.writeEntry("GlobalKeys", m_group->checkedId()); - KGlobal::config()->sync(); + KSharedConfig::openConfig()->sync(); m_pKeyChooser->save(); } return retcode; } void KeyDialog::slotKeys(int group) { // Set modifier keys according to key group and modifier keys for(uint i = 0; i < keyInfoCount; i++) { KAction *a = ActionCollection::action(keyInfo[i].action); if(a) { KShortcut shortcut(keyInfo[i].shortcut[group]); a->setGlobalShortcut(shortcut, KAction::ActiveShortcut, KAction::NoAutoloading); } } // Update the key chooser widget. // TODO: When widget is fixed to note the update remove this bit so that // we don't drop user changes for no reason. m_pKeyChooser->clearCollections(); m_pKeyChooser->addCollection(m_actionCollection); } void KeyDialog::slotDefault() { // Select default keys - standard key group m_group->button(StandardKeys)->setChecked(true); slotKeys(StandardKeys); m_pKeyChooser->allDefault(); } int KeyDialog::configure(KActionCollection *actionCollection, QWidget *parent) { // Create and show dialog - update connections if accepted return KeyDialog(actionCollection, parent).configure(); } void KeyDialog::setupActionShortcut(const QString &actionName) { // Find and insert a standard key KShortcut shortcut = KShortcut(); // Find out what type is selected so we know what keys to setup. - KConfigGroup config(KGlobal::config(), "Shortcuts"); + KConfigGroup config(KSharedConfig::openConfig(), "Shortcuts"); int selectedKeys = config.readEntry("GlobalKeys", int(StandardKeys)); for(uint i = 0; i < keyInfoCount; i++) { if(keyInfo[i].action == actionName) { shortcut = keyInfo[i].shortcut[selectedKeys]; break; } } if(shortcut.isEmpty()) return; // We have no shortcut to set. KAction *a = ActionCollection::action(actionName); if(a) a->setGlobalShortcut(shortcut); } // vim: set et sw=4 tw=0 sta: diff --git a/lyricswidget.cpp b/lyricswidget.cpp index e414404d..e960d519 100644 --- a/lyricswidget.cpp +++ b/lyricswidget.cpp @@ -1,155 +1,155 @@ /** * Copyright (C) 2012 Martin Sandsmark * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "lyricswidget.h" #include "tag.h" #include "actioncollection.h" LyricsWidget::LyricsWidget(QWidget* parent): QTextBrowser(parent), m_networkAccessManager(new QNetworkAccessManager), m_lyricsCurrent(false) { setMinimumWidth(200); setReadOnly(true); setWordWrapMode(QTextOption::WordWrap); setOpenExternalLinks(true); KToggleAction *show = new KToggleAction(KIcon(QLatin1String("view-media-lyrics")), i18n("Show &Lyrics"), this); ActionCollection::actions()->addAction("showLyrics", show); connect(show, SIGNAL(toggled(bool)), this, SLOT(setVisible(bool))); - KConfigGroup config(KGlobal::config(), "LyricsWidget"); + KConfigGroup config(KSharedConfig::openConfig(), "LyricsWidget"); bool shown = config.readEntry("Show", true); show->setChecked(shown); setVisible(shown); } LyricsWidget::~LyricsWidget() { delete m_networkAccessManager; saveConfig(); } void LyricsWidget::saveConfig() { - KConfigGroup config(KGlobal::config(), "LyricsWidget"); + KConfigGroup config(KSharedConfig::openConfig(), "LyricsWidget"); config.writeEntry("Show", ActionCollection::action("showLyrics")->isChecked()); } void LyricsWidget::makeLyricsRequest() { m_lyricsCurrent = true; if(m_playingFile.isNull()) { setHtml(i18n("No file playing.")); return; } setHtml(i18n("Loading...")); QUrl listUrl("http://lyrics.wikia.com/api.php"); listUrl.addQueryItem("action", "lyrics"); listUrl.addQueryItem("func", "getSong"); listUrl.addQueryItem("fmt", "xml"); listUrl.addQueryItem("artist", m_playingFile.tag()->artist()); listUrl.addQueryItem("song", m_playingFile.tag()->title()); m_title = m_playingFile.tag()->artist() + " – " + m_playingFile.tag()->title(); connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(receiveListReply(QNetworkReply*))); m_networkAccessManager->get(QNetworkRequest(listUrl)); } void LyricsWidget::playing(const FileHandle &file) { m_playingFile = file; m_lyricsCurrent = false; if(isVisible()) { makeLyricsRequest(); } } void LyricsWidget::showEvent(QShowEvent *) { if(!m_lyricsCurrent) { makeLyricsRequest(); } } void LyricsWidget::receiveListReply(QNetworkReply* reply) { disconnect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(receiveListReply(QNetworkReply*))); if (reply->error() != QNetworkReply::NoError) { kWarning() << "Error while fetching lyrics: " << reply->errorString(); setHtml(i18n("Error while retrieving lyrics!")); return; } QDomDocument document; document.setContent(reply); QString artist = document.elementsByTagName("artist").at(0).toElement().text(); QString title = document.elementsByTagName("song").at(0).toElement().text(); QUrl url("http://lyrics.wikia.com/api.php"); url.addQueryItem("action", "query"); url.addQueryItem("prop", "revisions"); url.addQueryItem("rvprop", "content"); url.addQueryItem("format", "xml"); url.addQueryItem("titles", artist + ':' + title); connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(receiveLyricsReply(QNetworkReply*))); m_networkAccessManager->get(QNetworkRequest(url)); } void LyricsWidget::receiveLyricsReply(QNetworkReply* reply) { disconnect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(receiveLyricsReply(QNetworkReply*))); if (reply->error() != QNetworkReply::NoError) { kWarning() << "Error while fetching lyrics: " << reply->errorString(); setHtml(i18n("Error while retrieving lyrics!")); return; } QString content = QString::fromUtf8(reply->readAll()); int lIndex = content.indexOf("<lyrics>"); int rIndex = content.indexOf("</lyrics>"); if (lIndex == -1 || rIndex == -1) { kWarning() << Q_FUNC_INFO << "Unable to find lyrics in text"; setText(i18n("No lyrics available.")); return; } lIndex += 15; // We skip the tag content = content.mid(lIndex, rIndex - lIndex).trimmed(); content.replace('\n', "
"); //setText(content); setHtml("

" + m_title + "

" + content + i18n("

Lyrics provided by LyricWiki")); } diff --git a/main.cpp b/main.cpp index 5597656d..3375a6de 100644 --- a/main.cpp +++ b/main.cpp @@ -1,119 +1,119 @@ /** * Copyright (C) 2002-2007 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "juk.h" static const char description[] = I18N_NOOP("Jukebox and music manager by KDE"); static const char scott[] = I18N_NOOP("Author, chief dork and keeper of the funk"); static const char michael[] = I18N_NOOP("Assistant super-hero, fixer of many things"); static const char georg[] = I18N_NOOP("More KDE Platform 4 porting efforts"); static const char daniel[] = I18N_NOOP("System tray docking, \"inline\" tag editing,\nbug fixes, evangelism, moral support"); static const char tim[] = I18N_NOOP("GStreamer port"); static const char stefan[] = I18N_NOOP("Global keybindings support"); static const char stephen[] = I18N_NOOP("Track announcement popups"); static const char frerich[] = I18N_NOOP("Automagic track data guessing, bugfixes"); static const char zack[] = I18N_NOOP("More automagical things, now using MusicBrainz"); static const char adam[] = I18N_NOOP("Co-conspirator in MusicBrainz wizardry"); static const char matthias[] = I18N_NOOP("Friendly, neighborhood aRts guru"); static const char maks[] = I18N_NOOP("Making JuK friendlier to people with terabytes of music"); static const char antonio[] = I18N_NOOP("DCOP interface"); static const char allan[] = I18N_NOOP("FLAC and MPC support"); static const char nathan[] = I18N_NOOP("Album cover manager"); static const char pascal[] = I18N_NOOP("Gimper of splash screen"); static const char laurent[] = I18N_NOOP("Porting to KDE 4 when no one else was around"); static const char giorgos[] = I18N_NOOP("Badly-needed tag editor bugfixes."); static const char sandsmark[] = I18N_NOOP("Last.fm scrobbling support, lyrics, prepping for KDE Frameworks."); static const char sho[] = I18N_NOOP("MPRIS2 Interface implementation."); int main(int argc, char *argv[]) { QApplication a(argc, argv); KLocalizedString::setApplicationDomain("juk"); KAboutData aboutData(QStringLiteral("juk"), i18n("JuK"), QStringLiteral("3.14"), i18n(description), KAboutLicense::GPL, i18n("© 2002–2016, Scott Wheeler, Michael Pyne, and others"), QStringLiteral(""), QStringLiteral("http://www.kde.org/applications/multimedia/juk/")); aboutData.addAuthor(i18n("Scott Wheeler"), i18n(scott), "wheeler@kde.org"); aboutData.addAuthor(i18n("Michael Pyne"), i18n(michael), "mpyne@kde.org"); aboutData.addCredit(i18n("Γιώργος Κυλάφας (Giorgos Kylafas)"), i18n(giorgos), "gekylafas@gmail.com"); aboutData.addCredit(i18n("Daniel Molkentin"), i18n(daniel), "molkentin@kde.org"); aboutData.addCredit(i18n("Tim Jansen"), i18n(tim), "tim@tjansen.de"); aboutData.addCredit(i18n("Stefan Asserhäll"), i18n(stefan), "stefan.asserhall@telia.com"); aboutData.addCredit(i18n("Stephen Douglas"), i18n(stephen), "stephen_douglas@yahoo.com"); aboutData.addCredit(i18n("Frerich Raabe"), i18n(frerich), "raabe@kde.org"); aboutData.addCredit(i18n("Zack Rusin"), i18n(zack), "zack@kde.org"); aboutData.addCredit(i18n("Adam Treat"), i18n(adam), "manyoso@yahoo.com"); aboutData.addCredit(i18n("Matthias Kretz"), i18n(matthias), "kretz@kde.org"); aboutData.addCredit(i18n("Maks Orlovich"), i18n(maks), "maksim@kde.org"); aboutData.addCredit(i18n("Antonio Larrosa Jimenez"), i18n(antonio), "larrosa@kde.org"); aboutData.addCredit(i18n("Allan Sandfeld Jensen"), i18n(allan), "kde@carewolf.com"); aboutData.addCredit(i18n("Nathan Toone"), i18n(nathan), "nathan@toonetown.com"); aboutData.addCredit(i18n("Pascal Klein"), i18n(pascal), "4pascal@tpg.com.au"); aboutData.addCredit(i18n("Laurent Montel"), i18n(laurent), "montel@kde.org"); aboutData.addCredit(i18n("Georg Grabler"), i18n(georg), "georg@grabler.net"); aboutData.addCredit(i18n("Martin Sandsmark"), i18n(sandsmark), "martin.sandsmark@kde.org"); aboutData.addCredit(i18n("Eike Hein"), i18n(sho), "hein@kde.org"); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addVersionOption(); parser.addHelpOption(); parser.addPositionalArgument(QLatin1String("[file(s)]"), i18n("File(s) to open")); parser.process(a); aboutData.processCommandLine(&parser); // If this flag gets set then JuK will quit if you click the cover on the track // announcement popup when JuK is only in the system tray (the systray has no widget). a.setQuitOnLastWindowClosed(false); // Create the main window and such JuK *juk = new JuK(parser.positionalArguments()); if(a.isSessionRestored() && KMainWindow::canBeRestored(1)) juk->restore(1, false /* don't show */); - KConfigGroup config(KGlobal::config(), "Settings"); + KConfigGroup config(KSharedConfig::openConfig(), "Settings"); if(!config.readEntry("StartDocked", false)) { juk->show(); } else if(!a.isSessionRestored()) { QString message = i18n("JuK running in docked mode\nUse context menu in system tray to restore."); KNotification::event("dock_mode",i18n("JuK Docked"), message); } return a.exec(); } // vim: set et sw=4 tw=0 sta fileencoding=utf8: diff --git a/playlist.cpp b/playlist.cpp index 5f68475b..7c750900 100644 --- a/playlist.cpp +++ b/playlist.cpp @@ -1,2411 +1,2411 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * Copyright (C) 2008 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playlist.h" #include "juk-exception.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playlistitem.h" #include "playlistcollection.h" #include "playlistsearch.h" #include "mediafiles.h" #include "collectionlist.h" #include "filerenamer.h" #include "actioncollection.h" #include "tracksequencemanager.h" #include "tag.h" #include "k3bexporter.h" #include "upcomingplaylist.h" #include "deletedialog.h" #include "webimagefetcher.h" #include "coverinfo.h" #include "coverdialog.h" #include "tagtransactionmanager.h" #include "cache.h" using namespace ActionCollection; /** * Used to give every track added in the program a unique identifier. See * PlaylistItem */ quint32 g_trackID = 0; /** * Just a shortcut of sorts. */ static bool manualResize() { return action("resizeColumnsManually")->isChecked(); } /** * A tooltip specialized to show full filenames over the file name column. */ #ifdef __GNUC__ #warning disabling the tooltip for now #endif #if 0 class PlaylistToolTip : public QToolTip { public: PlaylistToolTip(QWidget *parent, Playlist *playlist) : QToolTip(parent), m_playlist(playlist) {} virtual void maybeTip(const QPoint &p) { PlaylistItem *item = static_cast(m_playlist->itemAt(p)); if(!item) return; QPoint contentsPosition = m_playlist->viewportToContents(p); int column = m_playlist->header()->sectionAt(contentsPosition.x()); if(column == m_playlist->columnOffset() + PlaylistItem::FileNameColumn || item->cachedWidths()[column] > m_playlist->columnWidth(column) || (column == m_playlist->columnOffset() + PlaylistItem::CoverColumn && item->file().coverInfo()->hasCover())) { QRect r = m_playlist->itemRect(item); int headerPosition = m_playlist->header()->sectionPos(column); r.setLeft(headerPosition); r.setRight(headerPosition + m_playlist->header()->sectionSize(column)); if(column == m_playlist->columnOffset() + PlaylistItem::FileNameColumn) tip(r, item->file().absFilePath()); else if(column == m_playlist->columnOffset() + PlaylistItem::CoverColumn) { Q3MimeSourceFactory *f = Q3MimeSourceFactory::defaultFactory(); f->setImage("coverThumb", QImage(item->file().coverInfo()->pixmap(CoverInfo::Thumbnail).convertToImage())); tip(r, "
"); } else tip(r, item->text(column)); } } private: Playlist *m_playlist; }; #endif //////////////////////////////////////////////////////////////////////////////// // Playlist::SharedSettings definition //////////////////////////////////////////////////////////////////////////////// bool Playlist::m_visibleChanged = false; bool Playlist::m_shuttingDown = false; /** * Shared settings between the playlists. */ class Playlist::SharedSettings { public: static SharedSettings *instance(); /** * Sets the default column order to that of Playlist @param p. */ void setColumnOrder(const Playlist *l); void toggleColumnVisible(int column); void setInlineCompletionMode(KGlobalSettings::Completion mode); /** * Apply the settings. */ void apply(Playlist *l) const; void sync() { writeConfig(); } protected: SharedSettings(); ~SharedSettings() {} private: void writeConfig(); static SharedSettings *m_instance; QList m_columnOrder; QVector m_columnsVisible; KGlobalSettings::Completion m_inlineCompletion; }; Playlist::SharedSettings *Playlist::SharedSettings::m_instance = 0; //////////////////////////////////////////////////////////////////////////////// // Playlist::SharedSettings public members //////////////////////////////////////////////////////////////////////////////// Playlist::SharedSettings *Playlist::SharedSettings::instance() { static SharedSettings settings; return &settings; } void Playlist::SharedSettings::setColumnOrder(const Playlist *l) { if(!l) return; m_columnOrder.clear(); for(int i = l->columnOffset(); i < l->columnCount(); ++i) m_columnOrder.append(l->header()->visualIndex(i)); // FIXME MISMATCH with apply writeConfig(); } void Playlist::SharedSettings::toggleColumnVisible(int column) { if(column >= m_columnsVisible.size()) m_columnsVisible.fill(true, column + 1); m_columnsVisible[column] = !m_columnsVisible[column]; writeConfig(); } void Playlist::SharedSettings::setInlineCompletionMode(KGlobalSettings::Completion mode) { m_inlineCompletion = mode; writeConfig(); } void Playlist::SharedSettings::apply(Playlist *l) const { if(!l) return; int offset = l->columnOffset(); int i = 0; //bool oldState = l->header()->blockSignals(true); foreach(int column, m_columnOrder) // FIXME this is broken l->header()->moveSection(i++ + offset, column + offset); // FIXME mismatch with setColumnOrder //l->header()->blockSignals(oldState); for(int i = 0; i < m_columnsVisible.size(); i++) { if(m_columnsVisible[i] && l->isColumnHidden(i + offset)) l->showColumn(i + offset, false); else if(!m_columnsVisible[i] && !l->isColumnHidden(i + offset)) l->hideColumn(i + offset, false); } l->updateLeftColumn(); // FIXME rename //l->renameLineEdit()->setCompletionMode(m_inlineCompletion); l->slotColumnResizeModeChanged(); } //////////////////////////////////////////////////////////////////////////////// // Playlist::ShareSettings protected members //////////////////////////////////////////////////////////////////////////////// Playlist::SharedSettings::SharedSettings() { - KConfigGroup config(KGlobal::config(), "PlaylistShared"); + KConfigGroup config(KSharedConfig::openConfig(), "PlaylistShared"); bool resizeColumnsManually = config.readEntry("ResizeColumnsManually", false); action("resizeColumnsManually")->setChecked(resizeColumnsManually); // Preallocate spaces so we don't need to check later. m_columnsVisible.fill(true, PlaylistItem::lastColumn() + 1); // save column order m_columnOrder = config.readEntry("ColumnOrder", QList()); QList l = config.readEntry("VisibleColumns", QList()); if(l.isEmpty()) { // Provide some default values for column visibility if none were // read from the configuration file. m_columnsVisible[PlaylistItem::BitrateColumn] = false; m_columnsVisible[PlaylistItem::CommentColumn] = false; m_columnsVisible[PlaylistItem::FileNameColumn] = false; m_columnsVisible[PlaylistItem::FullPathColumn] = false; } else { // Convert the int list into a bool list. m_columnsVisible.fill(false); for(int i = 0; i < l.size() && i < m_columnsVisible.size(); ++i) m_columnsVisible[i] = bool(l[i]); } m_inlineCompletion = KGlobalSettings::Completion( config.readEntry("InlineCompletionMode", int(KGlobalSettings::CompletionAuto))); } //////////////////////////////////////////////////////////////////////////////// // Playlist::SharedSettings private members //////////////////////////////////////////////////////////////////////////////// void Playlist::SharedSettings::writeConfig() { - KConfigGroup config(KGlobal::config(), "PlaylistShared"); + KConfigGroup config(KSharedConfig::openConfig(), "PlaylistShared"); config.writeEntry("ColumnOrder", m_columnOrder); QList l; for(int i = 0; i < m_columnsVisible.size(); i++) l.append(int(m_columnsVisible[i])); config.writeEntry("VisibleColumns", l); config.writeEntry("InlineCompletionMode", int(m_inlineCompletion)); config.writeEntry("ResizeColumnsManually", manualResize()); - KGlobal::config()->sync(); + KSharedConfig::openConfig()->sync(); } //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// PlaylistItemList Playlist::m_history; QVector Playlist::m_backMenuItems; int Playlist::m_leftColumn = 0; Playlist::Playlist(PlaylistCollection *collection, const QString &name, const QString &iconName) : QTreeWidget(collection->playlistStack()), m_collection(collection), m_fetcher(new WebImageFetcher(this)), m_selectedCount(0), m_allowDuplicates(true), m_applySharedSettings(true), m_columnWidthModeChanged(false), m_disableColumnWidthUpdates(true), m_time(0), m_widthsDirty(true), m_searchEnabled(true), m_lastSelected(0), m_playlistName(name), m_rmbMenu(0), m_toolTip(0), m_blockDataChanged(false) { setup(); collection->setupPlaylist(this, iconName); } Playlist::Playlist(PlaylistCollection *collection, const PlaylistItemList &items, const QString &name, const QString &iconName) : QTreeWidget(collection->playlistStack()), m_collection(collection), m_fetcher(new WebImageFetcher(this)), m_selectedCount(0), m_allowDuplicates(true), m_applySharedSettings(true), m_columnWidthModeChanged(false), m_disableColumnWidthUpdates(true), m_time(0), m_widthsDirty(true), m_searchEnabled(true), m_lastSelected(0), m_playlistName(name), m_rmbMenu(0), m_toolTip(0), m_blockDataChanged(false) { setup(); collection->setupPlaylist(this, iconName); createItems(items); } Playlist::Playlist(PlaylistCollection *collection, const QFileInfo &playlistFile, const QString &iconName) : QTreeWidget(collection->playlistStack()), m_collection(collection), m_fetcher(new WebImageFetcher(this)), m_selectedCount(0), m_allowDuplicates(true), m_applySharedSettings(true), m_columnWidthModeChanged(false), m_disableColumnWidthUpdates(true), m_time(0), m_widthsDirty(true), m_searchEnabled(true), m_lastSelected(0), m_fileName(playlistFile.canonicalFilePath()), m_rmbMenu(0), m_toolTip(0), m_blockDataChanged(false) { setup(); loadFile(m_fileName, playlistFile); collection->setupPlaylist(this, iconName); } Playlist::Playlist(PlaylistCollection *collection, bool delaySetup, int extraColumns) : QTreeWidget(collection->playlistStack()), m_collection(collection), m_fetcher(new WebImageFetcher(this)), m_selectedCount(0), m_allowDuplicates(true), m_applySharedSettings(true), m_columnWidthModeChanged(false), m_disableColumnWidthUpdates(true), m_time(0), m_widthsDirty(true), m_searchEnabled(true), m_lastSelected(0), m_rmbMenu(0), m_toolTip(0), m_blockDataChanged(false) { for(int i = 0; i < extraColumns; ++i) { addColumn(i18n("JuK")); // Placeholder text! } setup(); if(!delaySetup) collection->setupPlaylist(this, "audio-midi"); } Playlist::~Playlist() { // In some situations the dataChanged signal from clearItems will cause observers to // subsequently try to access a deleted item. Since we're going away just remove all // observers. clearObservers(); // clearItem() will take care of removing the items from the history, // so call clearItems() to make sure it happens. clearItems(items()); /* delete m_toolTip; */ if(!m_shuttingDown) m_collection->removePlaylist(this); } QString Playlist::name() const { if(m_playlistName.isEmpty()) return m_fileName.section(QDir::separator(), -1).section('.', 0, -2); else return m_playlistName; } FileHandle Playlist::currentFile() const { return playingItem() ? playingItem()->file() : FileHandle::null(); } int Playlist::time() const { // Since this method gets a lot of traffic, let's optimize for such. if(!m_addTime.isEmpty()) { foreach(const PlaylistItem *item, m_addTime) { if(item) m_time += item->file().tag()->seconds(); } m_addTime.clear(); } if(!m_subtractTime.isEmpty()) { foreach(const PlaylistItem *item, m_subtractTime) { if(item) m_time -= item->file().tag()->seconds(); } m_subtractTime.clear(); } return m_time; } void Playlist::playFirst() { TrackSequenceManager::instance()->setNextItem(static_cast( *QTreeWidgetItemIterator(const_cast(this), QTreeWidgetItemIterator::NotHidden))); action("forward")->trigger(); } void Playlist::playNextAlbum() { PlaylistItem *current = TrackSequenceManager::instance()->currentItem(); if(!current) return; // No next album if we're not already playing. QString currentAlbum = current->file().tag()->album(); current = TrackSequenceManager::instance()->nextItem(); while(current && current->file().tag()->album() == currentAlbum) current = TrackSequenceManager::instance()->nextItem(); TrackSequenceManager::instance()->setNextItem(current); action("forward")->trigger(); } void Playlist::playNext() { TrackSequenceManager::instance()->setCurrentPlaylist(this); setPlaying(TrackSequenceManager::instance()->nextItem()); } void Playlist::stop() { m_history.clear(); setPlaying(0); } void Playlist::playPrevious() { if(!playingItem()) return; bool random = action("randomPlay") && action("randomPlay")->isChecked(); PlaylistItem *previous = 0; if(random && !m_history.isEmpty()) { PlaylistItemList::Iterator last = --m_history.end(); previous = *last; m_history.erase(last); } else { m_history.clear(); previous = TrackSequenceManager::instance()->previousItem(); } if(!previous) previous = static_cast(playingItem()->itemAbove()); setPlaying(previous, false); } void Playlist::setName(const QString &n) { m_collection->addNameToDict(n); m_collection->removeNameFromDict(m_playlistName); m_playlistName = n; emit signalNameChanged(m_playlistName); } void Playlist::save() { if(m_fileName.isEmpty()) return saveAs(); QFile file(m_fileName); if(!file.open(QIODevice::WriteOnly)) return KMessageBox::error(this, i18n("Could not save to file %1.", m_fileName)); QTextStream stream(&file); QStringList fileList = files(); foreach(const QString &file, fileList) stream << file << endl; file.close(); } void Playlist::saveAs() { m_collection->removeFileFromDict(m_fileName); m_fileName = MediaFiles::savePlaylistDialog(name(), this); if(!m_fileName.isEmpty()) { m_collection->addFileToDict(m_fileName); // If there's no playlist name set, use the file name. if(m_playlistName.isEmpty()) emit signalNameChanged(name()); save(); } } void Playlist::updateDeletedItem(PlaylistItem *item) { m_members.remove(item->file().absFilePath()); m_search.clearItem(item); m_history.removeAll(item); m_addTime.removeAll(item); m_subtractTime.removeAll(item); } void Playlist::clearItem(PlaylistItem *item) { // Automatically updates internal structs via updateDeletedItem delete item; dataChanged(); } void Playlist::clearItems(const PlaylistItemList &items) { foreach(PlaylistItem *item, items) delete item; dataChanged(); } PlaylistItem *Playlist::playingItem() // static { return PlaylistItem::playingItems().isEmpty() ? 0 : PlaylistItem::playingItems().front(); } QStringList Playlist::files() const { QStringList list; for(QTreeWidgetItemIterator it(const_cast(this)); *it; ++it) list.append(static_cast(*it)->file().absFilePath()); return list; } PlaylistItemList Playlist::items() { return items(QTreeWidgetItemIterator::IteratorFlag(0)); } PlaylistItemList Playlist::visibleItems() { return items(QTreeWidgetItemIterator::NotHidden); } PlaylistItemList Playlist::selectedItems() { PlaylistItemList list; switch(m_selectedCount) { case 0: break; // case 1: // list.append(m_lastSelected); // break; default: list = items(QTreeWidgetItemIterator::Selected | QTreeWidgetItemIterator::NotHidden); break; } return list; } PlaylistItem *Playlist::firstChild() const { return static_cast(topLevelItem(0)); } void Playlist::updateLeftColumn() { int newLeftColumn = leftMostVisibleColumn(); if(m_leftColumn != newLeftColumn) { updatePlaying(); m_leftColumn = newLeftColumn; } } void Playlist::setItemsVisible(const PlaylistItemList &items, bool visible) // static { m_visibleChanged = true; foreach(PlaylistItem *playlistItem, items) playlistItem->setHidden(!visible); } void Playlist::setSearch(const PlaylistSearch &s) { m_search = s; if(!m_searchEnabled) return; setItemsVisible(s.matchedItems(), true); setItemsVisible(s.unmatchedItems(), false); TrackSequenceManager::instance()->iterator()->playlistChanged(); } void Playlist::setSearchEnabled(bool enabled) { if(m_searchEnabled == enabled) return; m_searchEnabled = enabled; if(enabled) { setItemsVisible(m_search.matchedItems(), true); setItemsVisible(m_search.unmatchedItems(), false); } else setItemsVisible(items(), true); } void Playlist::markItemSelected(PlaylistItem *item, bool selected) { if(selected && !item->isSelected()) { m_selectedCount++; m_lastSelected = item; } else if(!selected && item->isSelected()) m_selectedCount--; } void Playlist::synchronizePlayingItems(const PlaylistList &sources, bool setMaster) { foreach(const Playlist *p, sources) { if(p->playing()) { CollectionListItem *base = playingItem()->collectionItem(); for(QTreeWidgetItemIterator itemIt(this); *itemIt; ++itemIt) { PlaylistItem *item = static_cast(*itemIt); if(base == item->collectionItem()) { item->setPlaying(true, setMaster); PlaylistItemList playing = PlaylistItem::playingItems(); TrackSequenceManager::instance()->setCurrent(item); return; } } return; } } } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void Playlist::copy() { PlaylistItemList items = selectedItems(); KUrl::List urls; foreach(PlaylistItem *item, items) { urls << KUrl::fromPath(item->file().absFilePath()); } QMimeData *mimeData = new QMimeData; urls.populateMimeData(mimeData); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } void Playlist::paste() { decode(QApplication::clipboard()->mimeData(), static_cast(currentItem())); } void Playlist::clear() { PlaylistItemList l = selectedItems(); if(l.isEmpty()) l = items(); clearItems(l); } void Playlist::slotRefresh() { PlaylistItemList l = selectedItems(); if(l.isEmpty()) l = visibleItems(); QApplication::setOverrideCursor(Qt::WaitCursor); foreach(PlaylistItem *item, l) { item->refreshFromDisk(); if(!item->file().tag() || !item->file().fileInfo().exists()) { kDebug() << "Error while trying to refresh the tag. " << "This file has probably been removed." << endl; delete item->collectionItem(); } processEvents(); } QApplication::restoreOverrideCursor(); } void Playlist::slotRenameFile() { FileRenamer renamer; PlaylistItemList items = selectedItems(); if(items.isEmpty()) return; emit signalEnableDirWatch(false); m_blockDataChanged = true; renamer.rename(items); m_blockDataChanged = false; dataChanged(); emit signalEnableDirWatch(true); } void Playlist::slotViewCover() { const PlaylistItemList items = selectedItems(); if (items.isEmpty()) return; foreach(const PlaylistItem *item, items) item->file().coverInfo()->popup(); } void Playlist::slotRemoveCover() { PlaylistItemList items = selectedItems(); if(items.isEmpty()) return; int button = KMessageBox::warningContinueCancel(this, i18n("Are you sure you want to delete these covers?"), QString(), KGuiItem(i18n("&Delete Covers"))); if(button == KMessageBox::Continue) refreshAlbums(items); } void Playlist::slotShowCoverManager() { static CoverDialog *managerDialog = 0; if(!managerDialog) managerDialog = new CoverDialog(this); managerDialog->show(); } void Playlist::slotAddCover(bool retrieveLocal) { PlaylistItemList items = selectedItems(); if(items.isEmpty()) return; if(!retrieveLocal) { m_fetcher->setFile((*items.begin())->file()); m_fetcher->searchCover(); return; } KUrl file = KFileDialog::getImageOpenUrl( KUrl( "kfiledialog://homedir" ), this, i18n("Select Cover Image File")); if(file.isEmpty()) return; QString artist = items.front()->file().tag()->artist(); QString album = items.front()->file().tag()->album(); coverKey newId = CoverManager::addCover(file, artist, album); if(newId != CoverManager::NoMatch) refreshAlbums(items, newId); } // Called when image fetcher has added a new cover. void Playlist::slotCoverChanged(int coverId) { kDebug() << "Refreshing information for newly changed covers.\n"; refreshAlbums(selectedItems(), coverId); } void Playlist::slotGuessTagInfo(TagGuesser::Type type) { QApplication::setOverrideCursor(Qt::WaitCursor); const PlaylistItemList items = selectedItems(); setDynamicListsFrozen(true); m_blockDataChanged = true; foreach(PlaylistItem *item, items) { item->guessTagInfo(type); processEvents(); } // MusicBrainz queries automatically commit at this point. What would // be nice is having a signal emitted when the last query is completed. if(type == TagGuesser::FileName) TagTransactionManager::instance()->commit(); m_blockDataChanged = false; dataChanged(); setDynamicListsFrozen(false); QApplication::restoreOverrideCursor(); } void Playlist::slotReload() { QFileInfo fileInfo(m_fileName); if(!fileInfo.exists() || !fileInfo.isFile() || !fileInfo.isReadable()) return; clearItems(items()); loadFile(m_fileName, fileInfo); } void Playlist::slotWeightDirty(int column) { if(column < 0) { m_weightDirty.clear(); for(int i = 0; i < columnCount(); i++) { if(!isColumnHidden(i)) m_weightDirty.append(i); } return; } if(!m_weightDirty.contains(column)) m_weightDirty.append(column); } void Playlist::slotShowPlaying() { if(!playingItem()) return; Playlist *l = playingItem()->playlist(); l->clearSelection(); // Raise the playlist before selecting the items otherwise the tag editor // will not update when it gets the selectionChanged() notification // because it will think the user is choosing a different playlist but not // selecting a different item. m_collection->raise(l); l->setCurrentItem(playingItem()); l->scrollToItem(playingItem()); } void Playlist::slotColumnResizeModeChanged() { if(manualResize()) { header()->setSectionResizeMode(QHeaderView::Interactive); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } else { header()->setSectionResizeMode(QHeaderView::Fixed); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } if(!manualResize()) slotUpdateColumnWidths(); SharedSettings::instance()->sync(); } void Playlist::dataChanged() { if(m_blockDataChanged) return; PlaylistInterface::dataChanged(); } //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// void Playlist::removeFromDisk(const PlaylistItemList &items) { if(isVisible() && !items.isEmpty()) { QStringList files; foreach(const PlaylistItem *item, items) files.append(item->file().absFilePath()); DeleteDialog dialog(this); m_blockDataChanged = true; if(dialog.confirmDeleteList(files)) { bool shouldDelete = dialog.shouldDelete(); QStringList errorFiles; foreach(PlaylistItem *item, items) { if(playingItem() == item) action("forward")->trigger(); QString removePath = item->file().absFilePath(); if((!shouldDelete && KIO::NetAccess::synchronousRun(KIO::trash(removePath), this)) || (shouldDelete && QFile::remove(removePath))) { delete item->collectionItem(); } else errorFiles.append(item->file().absFilePath()); } if(!errorFiles.isEmpty()) { QString errorMsg = shouldDelete ? i18n("Could not delete these files") : i18n("Could not move these files to the Trash"); KMessageBox::errorList(this, errorMsg, errorFiles); } } m_blockDataChanged = false; dataChanged(); } } void Playlist::dragEnterEvent(QDragEnterEvent *e) { if(CoverDrag::isCover(e->mimeData())) { //setDropHighlighter(true); setDropIndicatorShown(false); e->accept(); return; } //setDropHighlighter(false); setDropIndicatorShown(true); const KUrl::List urls = KUrl::List::fromMimeData(e->mimeData()); if (urls.isEmpty()) { e->ignore(); return; } if(e->mimeData()->hasUrls()) e->acceptProposedAction(); return; } bool Playlist::acceptDrag(QDropEvent *e) const { return CoverDrag::isCover(e->mimeData()) || KUrl::List::canDecode(e->mimeData()); } void Playlist::decode(const QMimeData *s, PlaylistItem *item) { if(!KUrl::List::canDecode(s)) return; const KUrl::List urls = KUrl::List::fromMimeData(s); if(urls.isEmpty()) return; // handle dropped images if(!MediaFiles::isMediaFile(urls.front().path())) { QString file; if(urls.front().isLocalFile()) file = urls.front().path(); else KIO::NetAccess::download(urls.front(), file, 0); KMimeType::Ptr mimeType = KMimeType::findByPath(file); if(item && mimeType->name().startsWith(QLatin1String("image/"))) { item->file().coverInfo()->setCover(QImage(file)); refreshAlbum(item->file().tag()->artist(), item->file().tag()->album()); } KIO::NetAccess::removeTempFile(file); } QStringList fileList = MediaFiles::convertURLsToLocal(urls, this); addFiles(fileList, item); } bool Playlist::eventFilter(QObject *watched, QEvent *e) { if(watched == header()) { switch(e->type()) { case QEvent::MouseMove: { if((static_cast(e)->modifiers() & Qt::LeftButton) == Qt::LeftButton && !action("resizeColumnsManually")->isChecked()) { m_columnWidthModeChanged = true; action("resizeColumnsManually")->setChecked(true); slotColumnResizeModeChanged(); } break; } case QEvent::MouseButtonPress: { if(static_cast(e)->button() == Qt::RightButton) m_headerMenu->popup(QCursor::pos()); break; } case QEvent::MouseButtonRelease: { if(m_columnWidthModeChanged) { m_columnWidthModeChanged = false; notifyUserColumnWidthModeChanged(); } if(!manualResize() && m_widthsDirty) QTimer::singleShot(0, this, SLOT(slotUpdateColumnWidths())); break; } default: break; } } return QTreeWidget::eventFilter(watched, e); } void Playlist::keyPressEvent(QKeyEvent *event) { if(event->key() == Qt::Key_Up) { using ::operator|; QTreeWidgetItemIterator selected(this, QTreeWidgetItemIterator::Selected | QTreeWidgetItemIterator::NotHidden); if(*selected) { QTreeWidgetItemIterator visible(this, QTreeWidgetItemIterator::NotHidden); if(*selected == *visible) QApplication::postEvent(parent(), new FocusUpEvent); } } QTreeWidget::keyPressEvent(event); } QStringList Playlist::mimeTypes() const { return QStringList("text/uri-list"); } QMimeData* Playlist::mimeData(const QList items) const { KUrl::List urls; foreach(QTreeWidgetItem *item, items) { urls << KUrl::fromPath(static_cast(item)->file().absFilePath()); } QMimeData *urlDrag = new QMimeData(); urlDrag->setUrls(urls); return urlDrag; } bool Playlist::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action) { qDebug() << index; return true; } void Playlist::dropEvent(QDropEvent *e) { QPoint vp = e->pos(); PlaylistItem *item = static_cast(itemAt(vp)); // First see if we're dropping a cover, if so we can get it out of the // way early. if(item && CoverDrag::isCover(e->mimeData())) { coverKey id = CoverDrag::idFromData(e->mimeData()); // If the item we dropped on is selected, apply cover to all selected // items, otherwise just apply to the dropped item. if(item->isSelected()) { const PlaylistItemList selItems = selectedItems(); foreach(PlaylistItem *playlistItem, selItems) { playlistItem->file().coverInfo()->setCoverId(id); playlistItem->refresh(); } } else { item->file().coverInfo()->setCoverId(id); item->refresh(); } return; } // When dropping on the toUpper half of an item, insert before this item. // This is what the user expects, and also allows the insertion at // top of the list QRect rect = visualItemRect(item); if(!item) item = static_cast(topLevelItem(topLevelItemCount() - 1)); else if(vp.y() < rect.y() + rect.height() / 2) item = static_cast(item->itemAbove()); m_blockDataChanged = true; if(e->source() == this) { // Since we're trying to arrange things manually, turn off sorting. sortItems(columnCount() + 1, Qt::AscendingOrder); const QList items = QTreeWidget::selectedItems(); foreach(QTreeWidgetItem *listViewItem, items) { if(!item) { // Insert the item at the top of the list. This is a bit ugly, // but I don't see another way. takeItem(listViewItem); insertItem(listViewItem); } //else // listViewItem->moveItem(item); item = static_cast(listViewItem); } } else decode(e->mimeData(), item); m_blockDataChanged = false; dataChanged(); emit signalPlaylistItemsDropped(this); QTreeWidget::dropEvent(e); } void Playlist::showEvent(QShowEvent *e) { if(m_applySharedSettings) { SharedSettings::instance()->apply(this); m_applySharedSettings = false; } QTreeWidget::showEvent(e); } void Playlist::applySharedSettings() { m_applySharedSettings = true; } void Playlist::read(QDataStream &s) { s >> m_playlistName >> m_fileName; // m_fileName is probably empty. if(m_playlistName.isEmpty()) throw BICStreamException(); // Do not sort. Add the files in the order they were saved. setSortingEnabled(false); QStringList files; s >> files; QTreeWidgetItem *after = 0; m_blockDataChanged = true; foreach(const QString &file, files) { if(file.isEmpty()) throw BICStreamException(); after = createItem(FileHandle(file), after, false); } m_blockDataChanged = false; dataChanged(); m_collection->setupPlaylist(this, "audio-midi"); } void Playlist::paintEvent(QPaintEvent *pe) { // If there are columns that need to be updated, well, update them. if(!m_weightDirty.isEmpty() && !manualResize()) { calculateColumnWeights(); slotUpdateColumnWidths(); } QTreeWidget::paintEvent(pe); } void Playlist::resizeEvent(QResizeEvent *re) { // If the width of the view has changed, manually update the column // widths. if(re->size().width() != re->oldSize().width() && !manualResize()) slotUpdateColumnWidths(); QTreeWidget::resizeEvent(re); } void Playlist::insertItem(QTreeWidgetItem *item) { // Because we're called from the PlaylistItem ctor, item may not be a // PlaylistItem yet (it would be QListViewItem when being inserted. But, // it will be a PlaylistItem by the time it matters, but be careful if // you need to use the PlaylistItem from here. m_addTime.append(static_cast(item)); QTreeWidget::addTopLevelItem(item); } void Playlist::takeItem(QTreeWidgetItem *item) { // See the warning in Playlist::insertItem. m_subtractTime.append(static_cast(item)); int index = indexOfTopLevelItem(item); delete takeTopLevelItem(index); } void Playlist::addColumn(const QString &label, int) { m_columns.append(label); setHeaderLabels(m_columns); } PlaylistItem *Playlist::createItem(const FileHandle &file, QTreeWidgetItem *after, bool emitChanged) { return createItem(file, after, emitChanged); } void Playlist::createItems(const PlaylistItemList &siblings, PlaylistItem *after) { createItems(siblings, after); } void Playlist::addFiles(const QStringList &files, PlaylistItem *after) { if(!after) after = static_cast(topLevelItem(topLevelItemCount() - 1)); QApplication::setOverrideCursor(Qt::WaitCursor); m_blockDataChanged = true; FileHandleList queue; foreach(const QString &file, files) addFile(file, queue, true, &after); addFileHelper(queue, &after, true); m_blockDataChanged = false; slotWeightDirty(); dataChanged(); QApplication::restoreOverrideCursor(); } void Playlist::refreshAlbums(const PlaylistItemList &items, coverKey id) { QList< QPair > albums; bool setAlbumCovers = items.count() == 1; foreach(const PlaylistItem *item, items) { QString artist = item->file().tag()->artist(); QString album = item->file().tag()->album(); if(!albums.contains(qMakePair(artist, album))) albums.append(qMakePair(artist, album)); item->file().coverInfo()->setCoverId(id); if(setAlbumCovers) item->file().coverInfo()->applyCoverToWholeAlbum(true); } for(QList< QPair >::ConstIterator it = albums.constBegin(); it != albums.constEnd(); ++it) { refreshAlbum((*it).first, (*it).second); } } void Playlist::updatePlaying() const { foreach(const PlaylistItem *item, PlaylistItem::playingItems()) item->treeWidget()->viewport()->update(); } void Playlist::refreshAlbum(const QString &artist, const QString &album) { ColumnList columns; columns.append(PlaylistItem::ArtistColumn); PlaylistSearch::Component artistComponent(artist, false, columns, PlaylistSearch::Component::Exact); columns.clear(); columns.append(PlaylistItem::AlbumColumn); PlaylistSearch::Component albumComponent(album, false, columns, PlaylistSearch::Component::Exact); PlaylistSearch::ComponentList components; components.append(artist); components.append(album); PlaylistList playlists; playlists.append(CollectionList::instance()); PlaylistSearch search(playlists, components); const PlaylistItemList matches = search.matchedItems(); foreach(PlaylistItem *item, matches) item->refresh(); } void Playlist::hideColumn(int c, bool updateSearch) { foreach (QAction *action, m_headerMenu->actions()) { if(!action) continue; if (action->data().toInt() == c) { action->setChecked(false); break; } } if(isColumnHidden(c)) return; QTreeWidget::hideColumn(c); if(c == m_leftColumn) { updatePlaying(); m_leftColumn = leftMostVisibleColumn(); } if(!manualResize()) { slotUpdateColumnWidths(); viewport()->update(); } if(this != CollectionList::instance()) CollectionList::instance()->hideColumn(c, false); if(updateSearch) redisplaySearch(); } void Playlist::showColumn(int c, bool updateSearch) { foreach (QAction *action, m_headerMenu->actions()) { if(!action) continue; if (action->data().toInt() == c) { action->setChecked(true); break; } } if(!isColumnHidden(c)) return; QTreeWidget::showColumn(c); if(c == leftMostVisibleColumn()) { updatePlaying(); m_leftColumn = leftMostVisibleColumn(); } if(!manualResize()) { slotUpdateColumnWidths(); viewport()->update(); } if(this != CollectionList::instance()) CollectionList::instance()->showColumn(c, false); if(updateSearch) redisplaySearch(); } void Playlist::sortByColumn(int column, Qt::SortOrder order) { setSortingEnabled(true); QTreeWidget::sortByColumn(column, order); } void Playlist::slotInitialize() { addColumn(i18n("Track Name")); addColumn(i18n("Artist")); addColumn(i18n("Album")); addColumn(i18n("Cover")); addColumn(i18nc("cd track number", "Track")); addColumn(i18n("Genre")); addColumn(i18n("Year")); addColumn(i18n("Length")); addColumn(i18n("Bitrate")); addColumn(i18n("Comment")); addColumn(i18n("File Name")); addColumn(i18n("File Name (full path)")); // FIXME rename /*setRenameable(PlaylistItem::TrackColumn, true); setRenameable(PlaylistItem::ArtistColumn, true); setRenameable(PlaylistItem::AlbumColumn, true); setRenameable(PlaylistItem::TrackNumberColumn, true); setRenameable(PlaylistItem::GenreColumn, true); setRenameable(PlaylistItem::YearColumn, true);*/ setAllColumnsShowFocus(true); setSelectionMode(QTreeWidget::ExtendedSelection); header()->setSortIndicatorShown(true); m_columnFixedWidths.resize(columnCount()); ////////////////////////////////////////////////// // setup header RMB menu ////////////////////////////////////////////////// #ifdef __GNUC__ #warning should be fixed... #endif /* m_headerMenu->insertTitle(i18n("Show")); */ QAction *showAction; for(int i = 0; i < header()->count(); ++i) { if(i - columnOffset() == PlaylistItem::FileNameColumn) m_headerMenu->addSeparator(); showAction = new QAction(headerItem()->text(i), m_headerMenu); showAction->setData(i); showAction->setCheckable(true); showAction->setChecked(true); m_headerMenu->addAction(showAction); resizeColumnToContents(i); } connect(m_headerMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotToggleColumnVisible(QAction*))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotShowRMBMenu(QPoint))); // FIXME rename /*connect(this, SIGNAL(itemRenamed(QTreeWidgetItem*,QString,int)), this, SLOT(slotInlineEditDone(QTreeWidgetItem*,QString,int)));*/ connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotPlayCurrent())); // FIXME returnPressed /*connect(this, SIGNAL(returnPressed(QTreeWidgetItem*)), this, SLOT(slotPlayCurrent()));*/ // FIXME rename /*connect(renameLineEdit(), SIGNAL(completionModeChanged(KGlobalSettings::Completion)), this, SLOT(slotInlineCompletionModeChanged(KGlobalSettings::Completion)));*/ connect(action("resizeColumnsManually"), SIGNAL(triggered()), this, SLOT(slotColumnResizeModeChanged())); if(action("resizeColumnsManually")->isChecked()) { header()->setSectionResizeMode(QHeaderView::Interactive); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } else { header()->setSectionResizeMode(QHeaderView::Fixed); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } viewport()->setAcceptDrops(true); setDropIndicatorShown(true); setDragEnabled(true); m_disableColumnWidthUpdates = false; // FIXME tooltips //setShowToolTips(false); /* m_toolTip = new PlaylistToolTip(viewport(), this); */ } void Playlist::setupItem(PlaylistItem *item) { item->setTrackId(g_trackID); g_trackID++; if(!m_search.isEmpty()) item->setHidden(!m_search.checkItem(item)); if(topLevelItemCount() <= 2 && !manualResize()) { slotWeightDirty(); slotUpdateColumnWidths(); viewport()->update(); } } void Playlist::setDynamicListsFrozen(bool frozen) { m_collection->setDynamicListsFrozen(frozen); } CollectionListItem *Playlist::collectionListItem(const FileHandle &file) { if(!QFile::exists(file.absFilePath())) { kError() << "File" << file.absFilePath() << "does not exist."; return 0; } CollectionListItem *item = CollectionList::instance()->lookup(file.absFilePath()); if(!item) { item = CollectionList::instance()->createItem(file); } return item; } //////////////////////////////////////////////////////////////////////////////// // protected slots //////////////////////////////////////////////////////////////////////////////// void Playlist::slotPopulateBackMenu() const { if(!playingItem()) return; QMenu *menu = action("back")->menu(); menu->clear(); m_backMenuItems.clear(); m_backMenuItems.reserve(10); int count = 0; PlaylistItemList::ConstIterator it = m_history.constEnd(); QAction *action; while(it != m_history.constBegin() && count < 10) { ++count; --it; action = new QAction((*it)->file().tag()->title(), menu); action->setData(count - 1); menu->addAction(action); m_backMenuItems << *it; } } void Playlist::slotPlayFromBackMenu(QAction *backAction) const { int number = backAction->data().toInt(); if(number >= m_backMenuItems.size()) return; TrackSequenceManager::instance()->setNextItem(m_backMenuItems[number]); action("forward")->trigger(); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// void Playlist::setup() { setRootIsDecorated(false); setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), SIGNAL(sectionMoved(int,int,int)), this, SLOT(slotColumnOrderChanged(int,int,int))); connect(m_fetcher, SIGNAL(signalCoverChanged(int)), this, SLOT(slotCoverChanged(int))); // Prevent list of selected items from changing while internet search is in // progress. connect(this, SIGNAL(itemSelectionChanged()), m_fetcher, SLOT(abortSearch())); sortByColumn(1, Qt::AscendingOrder); // This apparently must be created very early in initialization for other // Playlist code requiring m_headerMenu. m_columnVisibleAction = new KActionMenu(i18n("&Show Columns"), this); ActionCollection::actions()->addAction("showColumns", m_columnVisibleAction); m_headerMenu = m_columnVisibleAction->menu(); header()->installEventFilter(this); // TODO: Determine if other stuff in setup must happen before slotInitialize(). // Explicitly call slotInitialize() so that the columns are added before // SharedSettings::apply() sets the visible and hidden ones. slotInitialize(); } void Playlist::loadFile(const QString &fileName, const QFileInfo &fileInfo) { QFile file(fileName); if(!file.open(QIODevice::ReadOnly)) return; QTextStream stream(&file); // Turn off non-explicit sorting. setSortingEnabled(false); PlaylistItem *after = 0; m_disableColumnWidthUpdates = true; m_blockDataChanged = true; while(!stream.atEnd()) { QString itemName = stream.readLine().trimmed(); QFileInfo item(itemName); if(item.isRelative()) item.setFile(QDir::cleanPath(fileInfo.absolutePath() + '/' + itemName)); if(item.exists() && item.isFile() && item.isReadable() && MediaFiles::isMediaFile(item.fileName())) { if(after) after = createItem(FileHandle(item, item.absoluteFilePath()), after, false); else after = createItem(FileHandle(item, item.absoluteFilePath()), 0, false); } } m_blockDataChanged = false; file.close(); dataChanged(); m_disableColumnWidthUpdates = false; } void Playlist::setPlaying(PlaylistItem *item, bool addToHistory) { if(playingItem() == item) return; if(playingItem()) { if(addToHistory) { if(playingItem()->playlist() == playingItem()->playlist()->m_collection->upcomingPlaylist()) m_history.append(playingItem()->collectionItem()); else m_history.append(playingItem()); } playingItem()->setPlaying(false); } TrackSequenceManager::instance()->setCurrent(item); #ifdef __GNUC__ #warning "kde4: port it" #endif //kapp->dcopClient()->emitDCOPSignal("Player", "trackChanged()", data); if(!item) return; item->setPlaying(true); bool enableBack = !m_history.isEmpty(); action("back")->menu()->setEnabled(enableBack); } bool Playlist::playing() const { return playingItem() && this == playingItem()->playlist(); } int Playlist::leftMostVisibleColumn() const { int i = 0; while(isColumnHidden(header()->visualIndex(i)) && i < PlaylistItem::lastColumn()) i++; return header()->visualIndex(i); } PlaylistItemList Playlist::items(QTreeWidgetItemIterator::IteratorFlags flags) { PlaylistItemList list; for(QTreeWidgetItemIterator it(this, flags); *it; ++it) list.append(static_cast(*it)); return list; } void Playlist::calculateColumnWeights() { if(m_disableColumnWidthUpdates) return; PlaylistItemList l = items(); QList::Iterator columnIt; QVector averageWidth(columnCount()); double itemCount = l.size(); QVector cachedWidth; // Here we're not using a real average, but averaging the squares of the // column widths and then using the square root of that value. This gives // a nice weighting to the longer columns without doing something arbitrary // like adding a fixed amount of padding. foreach(PlaylistItem *item, l) { cachedWidth = item->cachedWidths(); // Extra columns start at 0, but those weights aren't shared with all // items. for(int i = 0; i < columnOffset(); ++i) { averageWidth[i] += std::pow(double(columnWidth(i)), 2.0) / itemCount; } for(int column = columnOffset(); column < columnCount(); ++column) { averageWidth[column] += std::pow(double(cachedWidth[column - columnOffset()]), 2.0) / itemCount; } } if(m_columnWeights.isEmpty()) m_columnWeights.fill(-1, columnCount()); foreach(int column, m_weightDirty) { m_columnWeights[column] = int(std::sqrt(averageWidth[column]) + 0.5); } m_weightDirty.clear(); } void Playlist::addFile(const QString &file, FileHandleList &files, bool importPlaylists, PlaylistItem **after) { if(hasItem(file) && !m_allowDuplicates) return; addFileHelper(files, after); // Our biggest thing that we're fighting during startup is too many stats // of files. Make sure that we don't do one here if it's not needed. const CollectionListItem *item = CollectionList::instance()->lookup(file); if(item && !item->file().isNull()) { FileHandle cached(item->file()); cached.tag(); files.append(cached); return; } const QFileInfo fileInfo(QDir::cleanPath(file)); if(!fileInfo.exists()) return; const QString canonicalPath = fileInfo.canonicalFilePath(); if(fileInfo.isFile() && fileInfo.isReadable()) { if(MediaFiles::isMediaFile(file)) { FileHandle f(fileInfo, canonicalPath); f.tag(); files.append(f); } } if(importPlaylists && MediaFiles::isPlaylistFile(file) && !m_collection->containsPlaylistFile(canonicalPath)) { new Playlist(m_collection, fileInfo); return; } if(fileInfo.isDir()) { foreach(const QString &directory, m_collection->excludedFolders()) { if(canonicalPath.startsWith(directory)) return; // Exclude it } QDirIterator dirIterator(canonicalPath, QDir::AllEntries | QDir::NoDotAndDotDot); while(dirIterator.hasNext()) { // We set importPlaylists to the value from the add directories // dialog as we want to load all of the ones that the user has // explicitly asked for, but not those that we find in toLower // directories. addFile(dirIterator.next(), files, m_collection->importPlaylists(), after); } } } void Playlist::addFileHelper(FileHandleList &files, PlaylistItem **after, bool ignoreTimer) { static QTime time = QTime::currentTime(); // Process new items every 10 seconds, when we've loaded 1000 items, or when // it's been requested in the API. if(ignoreTimer || time.elapsed() > 10000 || (files.count() >= 1000 && time.elapsed() > 1000)) { time.restart(); const bool focus = hasFocus(); const bool visible = isVisible() && files.count() > 20; if(visible) m_collection->raiseDistraction(); foreach(const FileHandle &fileHandle, files) *after = createItem(fileHandle, *after, false); files.clear(); if(visible) m_collection->lowerDistraction(); if(focus) setFocus(); } } //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void Playlist::slotUpdateColumnWidths() { if(m_disableColumnWidthUpdates || manualResize()) return; // Make sure that the column weights have been initialized before trying to // update the columns. QList visibleColumns; for(int i = 0; i < columnCount(); i++) { if(!isColumnHidden(i)) visibleColumns.append(i); } if(count() == 0) { foreach(int column, visibleColumns) setColumnWidth(column, header()->fontMetrics().width(headerItem()->text(column)) + 10); return; } if(m_columnWeights.isEmpty()) return; // First build a list of minimum widths based on the strings in the listview // header. We won't let the width of the column go below this width. QVector minimumWidth(columnCount(), 0); int minimumWidthTotal = 0; // Also build a list of either the minimum *or* the fixed width -- whichever is // greater. QVector minimumFixedWidth(columnCount(), 0); int minimumFixedWidthTotal = 0; foreach(int column, visibleColumns) { minimumWidth[column] = header()->fontMetrics().width(headerItem()->text(column)) + 10; minimumWidthTotal += minimumWidth[column]; minimumFixedWidth[column] = qMax(minimumWidth[column], m_columnFixedWidths[column]); minimumFixedWidthTotal += minimumFixedWidth[column]; } // Make sure that the width won't get any smaller than this. We have to // account for the scrollbar as well. Since this method is called from the // resize event this will set a pretty hard toLower bound on the size. setMinimumWidth(minimumWidthTotal + verticalScrollBar()->width()); // If we've got enough room for the fixed widths (larger than the minimum // widths) then instead use those for our "minimum widths". if(minimumFixedWidthTotal < viewport()->width()) { minimumWidth = minimumFixedWidth; minimumWidthTotal = minimumFixedWidthTotal; } // We've got a list of columns "weights" based on some statistics gathered // about the widths of the items in that column. We need to find the total // useful weight to use as a divisor for each column's weight. double totalWeight = 0; foreach(int column, visibleColumns) totalWeight += m_columnWeights[column]; // Computed a "weighted width" for each visible column. This would be the // width if we didn't have to handle the cases of minimum and maximum widths. QVector weightedWidth(columnCount(), 0); foreach(int column, visibleColumns) weightedWidth[column] = int(double(m_columnWeights[column]) / totalWeight * viewport()->width() + 0.5); // The "extra" width for each column. This is the weighted width less the // minimum width or zero if the minimum width is greater than the weighted // width. QVector extraWidth(columnCount(), 0); // This is used as an indicator if we have any columns where the weighted // width is less than the minimum width. If this is false then we can // just use the weighted width with no problems, otherwise we have to // "readjust" the widths. bool readjust = false; // If we have columns where the weighted width is less than the minimum width // we need to steal that space from somewhere. The amount that we need to // steal is the "neededWidth". int neededWidth = 0; // While we're on the topic of stealing -- we have to have somewhere to steal // from. availableWidth is the sum of the amount of space beyond the minimum // width that each column has been allocated -- the sum of the values of // extraWidth[]. int availableWidth = 0; // Fill in the values discussed above. foreach(int column, visibleColumns) { if(weightedWidth[column] < minimumWidth[column]) { readjust = true; extraWidth[column] = 0; neededWidth += minimumWidth[column] - weightedWidth[column]; } else { extraWidth[column] = weightedWidth[column] - minimumWidth[column]; availableWidth += extraWidth[column]; } } // The adjustmentRatio is the amount of the "extraWidth[]" that columns will // actually be given. double adjustmentRatio = (double(availableWidth) - double(neededWidth)) / double(availableWidth); // This will be the sum of the total space that we actually use. Because of // rounding error this won't be the exact available width. int usedWidth = 0; // Now set the actual column widths. If the weighted widths are all greater // than the minimum widths, just use those, otherwise use the "reajusted // weighted width". foreach(int column, visibleColumns) { int width; if(readjust) { int adjustedExtraWidth = int(double(extraWidth[column]) * adjustmentRatio + 0.5); width = minimumWidth[column] + adjustedExtraWidth; } else width = weightedWidth[column]; setColumnWidth(column, width); usedWidth += width; } // Fill the remaining gap for a clean fit into the available space. int remainingWidth = viewport()->width() - usedWidth; setColumnWidth(visibleColumns.back(), columnWidth(visibleColumns.back()) + remainingWidth); m_widthsDirty = false; } void Playlist::slotAddToUpcoming() { m_collection->setUpcomingPlaylistEnabled(true); m_collection->upcomingPlaylist()->appendItems(selectedItems()); } void Playlist::slotShowRMBMenu(const QPoint &point) { QTreeWidgetItem *item = itemAt(point); int column = columnAt(point.x()); if(!item) return; // Create the RMB menu on demand. if(!m_rmbMenu) { // Probably more of these actions should be ported over to using KActions. m_rmbMenu = new QMenu(this); m_rmbMenu->addAction(SmallIcon("go-jump-today"), i18n("Add to Play Queue"), this, SLOT(slotAddToUpcoming())); m_rmbMenu->addSeparator(); if(!readOnly()) { m_rmbMenu->addAction( action("edit_cut") ); m_rmbMenu->addAction( action("edit_copy") ); m_rmbMenu->addAction( action("edit_paste") ); m_rmbMenu->addSeparator(); m_rmbMenu->addAction( action("removeFromPlaylist") ); } else m_rmbMenu->addAction( action("edit_copy") ); m_rmbEdit = m_rmbMenu->addAction(i18n("Edit"), this, SLOT(slotRenameTag())); m_rmbMenu->addAction( action("refresh") ); m_rmbMenu->addAction( action("removeItem") ); m_rmbMenu->addSeparator(); m_rmbMenu->addAction( action("guessTag") ); m_rmbMenu->addAction( action("renameFile") ); m_rmbMenu->addAction( action("coverManager") ); m_rmbMenu->addSeparator(); m_rmbMenu->addAction( SmallIcon("folder-new"), i18n("Create Playlist From Selected Items..."), this, SLOT(slotCreateGroup())); K3bExporter *exporter = new K3bExporter(this); KAction *k3bAction = exporter->action(); if(k3bAction) m_rmbMenu->addAction( k3bAction ); } // Ignore any columns added by subclasses. column -= columnOffset(); bool showEdit = (column == PlaylistItem::TrackColumn) || (column == PlaylistItem::ArtistColumn) || (column == PlaylistItem::AlbumColumn) || (column == PlaylistItem::TrackNumberColumn) || (column == PlaylistItem::GenreColumn) || (column == PlaylistItem::YearColumn); if(showEdit) m_rmbEdit->setText(i18n("Edit '%1'", item->text(column + columnOffset()))); m_rmbEdit->setVisible(showEdit); // Disable edit menu if only one file is selected, and it's read-only FileHandle file = static_cast(item)->file(); m_rmbEdit->setEnabled(file.fileInfo().isWritable() || selectedItems().count() > 1); // View cover is based on if there is a cover to see. We should only have // the remove cover option if the cover is in our database (and not directly // embedded in the file, for instance). action("viewCover")->setEnabled(file.coverInfo()->hasCover()); action("removeCover")->setEnabled(file.coverInfo()->coverId() != CoverManager::NoMatch); m_rmbMenu->popup(mapToGlobal(point)); m_currentColumn = column + columnOffset(); } void Playlist::slotRenameTag() { // setup completions and validators // FIXME rename /*CollectionList *list = CollectionList::instance(); KLineEdit *edit = renameLineEdit(); switch(m_currentColumn - columnOffset()) { case PlaylistItem::ArtistColumn: edit->completionObject()->setItems(list->uniqueSet(CollectionList::Artists)); break; case PlaylistItem::AlbumColumn: edit->completionObject()->setItems(list->uniqueSet(CollectionList::Albums)); break; case PlaylistItem::GenreColumn: { QStringList genreList; TagLib::StringList genres = TagLib::ID3v1::genreList(); for(TagLib::StringList::Iterator it = genres.begin(); it != genres.end(); ++it) genreList.append(TStringToQString((*it))); edit->completionObject()->setItems(genreList); break; } default: edit->completionObject()->clear(); break; } m_editText = currentItem()->text(m_currentColumn); rename(currentItem(), m_currentColumn);*/ } bool Playlist::editTag(PlaylistItem *item, const QString &text, int column) { Tag *newTag = TagTransactionManager::duplicateTag(item->file().tag()); switch(column - columnOffset()) { case PlaylistItem::TrackColumn: newTag->setTitle(text); break; case PlaylistItem::ArtistColumn: newTag->setArtist(text); break; case PlaylistItem::AlbumColumn: newTag->setAlbum(text); break; case PlaylistItem::TrackNumberColumn: { bool ok; int value = text.toInt(&ok); if(ok) newTag->setTrack(value); break; } case PlaylistItem::GenreColumn: newTag->setGenre(text); break; case PlaylistItem::YearColumn: { bool ok; int value = text.toInt(&ok); if(ok) newTag->setYear(value); break; } } TagTransactionManager::instance()->changeTagOnItem(item, newTag); return true; } void Playlist::slotInlineEditDone(QTreeWidgetItem *, const QString &, int column) { // FIXME rename /*QString text = renameLineEdit()->text(); bool changed = false; PlaylistItemList l = selectedItems(); // See if any of the files have a tag different from the input. for(PlaylistItemList::ConstIterator it = l.constBegin(); it != l.constEnd() && !changed; ++it) if((*it)->text(column - columnOffset()) != text) changed = true; if(!changed || (l.count() > 1 && KMessageBox::warningContinueCancel( 0, i18n("This will edit multiple files. Are you sure?"), QString(), KGuiItem(i18n("Edit")), KStandardGuiItem::cancel(), "DontWarnMultipleTags") == KMessageBox::Cancel)) { return; } foreach(PlaylistItem *item, l) editTag(item, text, column); TagTransactionManager::instance()->commit(); CollectionList::instance()->dataChanged(); dataChanged(); update();*/ } void Playlist::slotColumnOrderChanged(int, int from, int to) { if(from == 0 || to == 0) { updatePlaying(); m_leftColumn = header()->sectionPosition(0); } SharedSettings::instance()->setColumnOrder(this); } void Playlist::slotToggleColumnVisible(QAction *action) { int column = action->data().toInt(); if(isColumnHidden(column)) { int fileNameColumn = PlaylistItem::FileNameColumn + columnOffset(); int fullPathColumn = PlaylistItem::FullPathColumn + columnOffset(); if(column == fileNameColumn && !isColumnHidden(fullPathColumn)) { hideColumn(fullPathColumn, false); SharedSettings::instance()->toggleColumnVisible(fullPathColumn); } if(column == fullPathColumn && !isColumnHidden(fileNameColumn)) { hideColumn(fileNameColumn, false); SharedSettings::instance()->toggleColumnVisible(fileNameColumn); } } if(!isColumnHidden(column)) hideColumn(column); else showColumn(column); if(column >= columnOffset()) { SharedSettings::instance()->toggleColumnVisible(column - columnOffset()); } } void Playlist::slotCreateGroup() { QString name = m_collection->playlistNameDialog(i18n("Create New Playlist")); if(!name.isEmpty()) new Playlist(m_collection, selectedItems(), name); } void Playlist::notifyUserColumnWidthModeChanged() { KMessageBox::information(this, i18n("Manual column widths have been enabled. You can " "switch back to automatic column sizes in the view " "menu."), i18n("Manual Column Widths Enabled"), "ShowManualColumnWidthInformation"); } void Playlist::columnResized(int column, int, int newSize) { m_widthsDirty = true; m_columnFixedWidths[column] = newSize; } void Playlist::slotInlineCompletionModeChanged(KGlobalSettings::Completion mode) { SharedSettings::instance()->setInlineCompletionMode(mode); } void Playlist::slotPlayCurrent() { QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); PlaylistItem *next = static_cast(*it); TrackSequenceManager::instance()->setNextItem(next); action("forward")->trigger(); } //////////////////////////////////////////////////////////////////////////////// // helper functions //////////////////////////////////////////////////////////////////////////////// QDataStream &operator<<(QDataStream &s, const Playlist &p) { s << p.name(); s << p.fileName(); s << p.files(); return s; } QDataStream &operator>>(QDataStream &s, Playlist &p) { p.read(s); return s; } bool processEvents() { static QTime time = QTime::currentTime(); if(time.elapsed() > 100) { time.restart(); qApp->processEvents(); return true; } return false; } // vim: set et sw=4 tw=0 sta: diff --git a/playlistbox.cpp b/playlistbox.cpp index 74a95b13..163da7c6 100644 --- a/playlistbox.cpp +++ b/playlistbox.cpp @@ -1,901 +1,901 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playlistbox.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playlist.h" #include "collectionlist.h" #include "dynamicplaylist.h" #include "upcomingplaylist.h" #include "historyplaylist.h" #include "viewmode.h" #include "searchplaylist.h" #include "treeviewitemplaylist.h" #include "actioncollection.h" #include "cache.h" #include "k3bexporter.h" #include "tracksequencemanager.h" #include "tagtransactionmanager.h" #include "playermanager.h" #include "dbuscollectionproxy.h" using namespace ActionCollection; //////////////////////////////////////////////////////////////////////////////// // PlaylistBox public methods //////////////////////////////////////////////////////////////////////////////// PlaylistBox::PlaylistBox(PlayerManager *player, QWidget *parent, QStackedWidget *playlistStack) : QTreeWidget(parent), PlaylistCollection(player, playlistStack), m_viewModeIndex(0), m_hasSelection(false), m_doingMultiSelect(false), m_dropItem(0), m_showTimer(0) { readConfig(); setHeaderLabel("Playlists"); setRootIsDecorated(false); setContextMenuPolicy(Qt::CustomContextMenu); setDropIndicatorShown(true); header()->blockSignals(true); header()->hide(); header()->blockSignals(false); sortByColumn(0); // FIXME ? //setFullWidth(true); viewport()->setAcceptDrops(true); setDropIndicatorShown(true); setSelectionMode(QAbstractItemView::ExtendedSelection); m_contextMenu = new KMenu(this); K3bPlaylistExporter *exporter = new K3bPlaylistExporter(this); m_k3bAction = exporter->action(); m_contextMenu->addAction( action("file_new") ); m_contextMenu->addAction( action("renamePlaylist") ); m_contextMenu->addAction( action("editSearch") ); m_contextMenu->addAction( action("duplicatePlaylist") ); m_contextMenu->addAction( action("reloadPlaylist") ); m_contextMenu->addAction( action("deleteItemPlaylist") ); m_contextMenu->addAction( action("file_save") ); m_contextMenu->addAction( action("file_save_as") ); if(m_k3bAction) m_contextMenu->addAction( m_k3bAction ); m_contextMenu->addSeparator(); // add the view modes stuff KSelectAction *viewModeAction = new KSelectAction( KIcon("view-choose"), i18n("View Modes"), ActionCollection::actions()); ActionCollection::actions()->addAction("viewModeMenu", viewModeAction); ViewMode* viewmode = new ViewMode(this); m_viewModes.append(viewmode); viewModeAction->addAction(KIcon("view-list-details"), viewmode->name()); CompactViewMode* compactviewmode = new CompactViewMode(this); m_viewModes.append(compactviewmode); viewModeAction->addAction(KIcon("view-list-text"), compactviewmode->name()); TreeViewMode* treeviewmode = new TreeViewMode(this); m_viewModes.append(treeviewmode); viewModeAction->addAction(KIcon("view-list-tree"), treeviewmode->name()); CollectionList::initialize(this); viewModeAction->setCurrentItem(m_viewModeIndex); m_viewModes[m_viewModeIndex]->setShown(true); TrackSequenceManager::instance()->setCurrentPlaylist(CollectionList::instance()); raise(CollectionList::instance()); m_contextMenu->addAction( viewModeAction ); connect(viewModeAction, SIGNAL(triggered(int)), this, SLOT(slotSetViewMode(int))); connect(this, SIGNAL(itemSelectionChanged()), this, SLOT(slotPlaylistChanged())); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotDoubleClicked(QTreeWidgetItem*))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotShowContextMenu(QPoint))); TagTransactionManager *tagManager = TagTransactionManager::instance(); connect(tagManager, SIGNAL(signalAboutToModifyTags()), SLOT(slotFreezePlaylists())); connect(tagManager, SIGNAL(signalDoneModifyingTags()), SLOT(slotUnfreezePlaylists())); setupUpcomingPlaylist(); connect(CollectionList::instance(), SIGNAL(signalNewTag(QString,uint)), this, SLOT(slotAddItem(QString,uint))); connect(CollectionList::instance(), SIGNAL(signalRemovedTag(QString,uint)), this, SLOT(slotRemoveItem(QString,uint))); connect(CollectionList::instance(), SIGNAL(cachedItemsLoaded()), this, SLOT(slotLoadCachedPlaylists())); m_savePlaylistTimer = 0; KToggleAction *historyAction = new KToggleAction(KIcon("view-history"), i18n("Show &History"), ActionCollection::actions()); ActionCollection::actions()->addAction("showHistory", historyAction); connect(historyAction, SIGNAL(triggered(bool)), this, SLOT(slotSetHistoryPlaylistEnabled(bool))); m_showTimer = new QTimer(this); connect(m_showTimer, SIGNAL(timeout()), SLOT(slotShowDropTarget())); // hook up to the D-Bus (void) new DBusCollectionProxy(this, this); } PlaylistBox::~PlaylistBox() { PlaylistList l; CollectionList *collection = CollectionList::instance(); for(QTreeWidgetItemIterator it(topLevelItem(0)); *it; ++it) { Item *item = static_cast(*it); if(item->playlist() && item->playlist() != collection) l.append(item->playlist()); } Cache::savePlaylists(l); saveConfig(); } void PlaylistBox::raise(Playlist *playlist) { if(!playlist) return; Item *i = m_playlistDict.value(playlist, 0); if(i) { clearSelection(); setCurrentItem(i); setSingleItem(i); scrollToItem(currentItem()); } else PlaylistCollection::raise(playlist); slotPlaylistChanged(); } void PlaylistBox::duplicate() { Item *item = static_cast(currentItem()); if(!item || !item->playlist()) return; QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"), item->text(0)); if(name.isNull()) return; Playlist *p = new Playlist(this, name); p->createItems(item->playlist()->items()); } void PlaylistBox::scanFolders() { kDebug() << "Starting folder scan"; QTime stopwatch; stopwatch.start(); PlaylistCollection::scanFolders(); kDebug() << "Folder scan complete, took" << stopwatch.elapsed() << "ms"; kDebug() << "Startup complete!"; emit startupComplete(); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox public slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::paste() { Item *i = static_cast(currentItem()); decode(QApplication::clipboard()->mimeData(), i); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox protected methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::slotFreezePlaylists() { setDynamicListsFrozen(true); } void PlaylistBox::slotUnfreezePlaylists() { setDynamicListsFrozen(false); } void PlaylistBox::slotPlaylistDataChanged() { if(m_savePlaylistTimer) m_savePlaylistTimer->start(); // Restarts the timer if it's already running. } void PlaylistBox::slotSetHistoryPlaylistEnabled(bool enable) { setHistoryPlaylistEnabled(enable); } void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName) { setupPlaylist(playlist, iconName, 0); } void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName, Item *parentItem) { connect(playlist, SIGNAL(signalPlaylistItemsDropped(Playlist*)), SLOT(slotPlaylistItemsDropped(Playlist*))); PlaylistCollection::setupPlaylist(playlist, iconName); if(parentItem) new Item(parentItem, iconName, playlist->name(), playlist); else new Item(this, iconName, playlist->name(), playlist); } void PlaylistBox::removePlaylist(Playlist *playlist) { // Could be false if setup() wasn't run yet. if(m_playlistDict.contains(playlist)) { removeNameFromDict(m_playlistDict[playlist]->text(0)); delete m_playlistDict[playlist]; // Delete the Item* } removeFileFromDict(playlist->fileName()); m_playlistDict.remove(playlist); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::readConfig() { - KConfigGroup config(KGlobal::config(), "PlaylistBox"); + KConfigGroup config(KSharedConfig::openConfig(), "PlaylistBox"); m_viewModeIndex = config.readEntry("ViewMode", 0); } void PlaylistBox::saveConfig() { - KConfigGroup config(KGlobal::config(), "PlaylistBox"); + KConfigGroup config(KSharedConfig::openConfig(), "PlaylistBox"); config.writeEntry("ViewMode", action("viewModeMenu")->currentItem()); - KGlobal::config()->sync(); + KSharedConfig::openConfig()->sync(); } void PlaylistBox::remove() { ItemList items = selectedBoxItems(); if(items.isEmpty()) return; QStringList files; QStringList names; foreach(Item *item, items) { if(item && item->playlist()) { if (!item->playlist()->fileName().isEmpty() && QFileInfo(item->playlist()->fileName()).exists()) { files.append(item->playlist()->fileName()); } names.append(item->playlist()->name()); } } if(!files.isEmpty()) { int remove = KMessageBox::warningYesNoCancelList( this, i18n("Do you want to delete these files from the disk as well?"), files, QString(), KStandardGuiItem::del(), KGuiItem(i18n("Keep"))); if(remove == KMessageBox::Yes) { QStringList couldNotDelete; for(QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); ++it) { if(!QFile::remove(*it)) couldNotDelete.append(*it); } if(!couldNotDelete.isEmpty()) KMessageBox::errorList(this, i18n("Could not delete these files."), couldNotDelete); } else if(remove == KMessageBox::Cancel) return; } else if(items.count() > 1 || items.front()->playlist() != upcomingPlaylist()) { if(KMessageBox::warningContinueCancelList(this, i18n("Are you sure you want to remove these " "playlists from your collection?"), names, i18n("Remove Items?"), KGuiItem(i18n("&Remove"), "user-trash")) == KMessageBox::Cancel) { return; } } PlaylistList removeQueue; for(ItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) { if(*it != Item::collectionItem() && (*it)->playlist() && (!(*it)->playlist()->readOnly())) { removeQueue.append((*it)->playlist()); } } // FIXME removing items /*if(items.back()->nextSibling() && static_cast(items.back()->nextSibling())->playlist()) setSingleItem(items.back()->nextSibling()); else { Item *i = static_cast(items.front()->itemAbove()); while(i && !i->playlist()) i = static_cast(i->itemAbove()); if(!i) i = Item::collectionItem(); setSingleItem(i); }*/ for(PlaylistList::ConstIterator it = removeQueue.constBegin(); it != removeQueue.constEnd(); ++it) { if(*it != upcomingPlaylist()) delete *it; else { action("showUpcoming")->setChecked(false); setUpcomingPlaylistEnabled(false); } } } void PlaylistBox::setDynamicListsFrozen(bool frozen) { for(QList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) { (*it)->setDynamicListsFrozen(frozen); } } void PlaylistBox::slotSavePlaylists() { kDebug() << "Auto-saving playlists.\n"; PlaylistList l; CollectionList *collection = CollectionList::instance(); for(QTreeWidgetItemIterator it(topLevelItem(0)); *it; ++it) { Item *item = static_cast(*it); if(item->playlist() && item->playlist() != collection) l.append(item->playlist()); } Cache::savePlaylists(l); } void PlaylistBox::slotShowDropTarget() { if(!m_dropItem) { kError() << "Trying to show the playlist of a null item!\n"; return; } raise(m_dropItem->playlist()); } void PlaylistBox::slotAddItem(const QString &tag, unsigned column) { for(QList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) (*it)->addItems(QStringList(tag), column); } void PlaylistBox::slotRemoveItem(const QString &tag, unsigned column) { for(QList::Iterator it = m_viewModes.begin(); it != m_viewModes.end(); ++it) (*it)->removeItem(tag, column); } void PlaylistBox::decode(const QMimeData *s, Item *item) { if(!s || (item && item->playlist() && item->playlist()->readOnly())) return; const KUrl::List urls = KUrl::List::fromMimeData(s); if(!urls.isEmpty()) { QStringList files; for(KUrl::List::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it) files.append((*it).path()); if(item) { TreeViewItemPlaylist *playlistItem; playlistItem = dynamic_cast(item->playlist()); if(playlistItem) { playlistItem->retag(files, currentPlaylist()); TagTransactionManager::instance()->commit(); currentPlaylist()->update(); return; } } if(item && item->playlist()) item->playlist()->addFiles(files); else { QString name = playlistNameDialog(); if(!name.isNull()) { Playlist *p = new Playlist(this, name); p->addFiles(files); } } } } void PlaylistBox::dropEvent(QDropEvent *e) { m_showTimer->stop(); Item *i = static_cast(itemAt(e->pos())); decode(e->mimeData(), i); if(m_dropItem) { Item *old = m_dropItem; m_dropItem = 0; //old->repaint(); } e->acceptProposedAction(); } void PlaylistBox::dragEnterEvent(QDragEnterEvent *e) { e->acceptProposedAction(); } void PlaylistBox::dragMoveEvent(QDragMoveEvent *e) { // If we can decode the input source, there is a non-null item at the "move" // position, the playlist for that Item is non-null, is not the // selected playlist and is not the CollectionList, then accept the event. // // Otherwise, do not accept the event. if (!e->mimeData()->hasUrls()) { e->setAccepted(false); return; } Item *target = static_cast(itemAt(e->pos())); if(target) { if(target->playlist() && target->playlist()->readOnly()) return; // This is a semi-dirty hack to check if the items are coming from within // JuK. If they are not coming from a Playlist (or subclass) then the // dynamic_cast will fail and we can safely assume that the item is // coming from outside of JuK. if(dynamic_cast(e->source())) { if(target->playlist() && target->playlist() != CollectionList::instance() /*&& !target->isSelected()*/) { e->setAccepted(true); } else e->setAccepted(false); } else // the dropped items are coming from outside of JuK e->setAccepted(true); if(m_dropItem != target) { Item *old = m_dropItem; m_showTimer->stop(); if(e->isAccepted()) { m_dropItem = target; //target->repaint(); m_showTimer->setSingleShot(true); m_showTimer->start(1500); } else m_dropItem = 0; /*if(old) old->repaint();*/ } } else { // We're dragging over the whitespace. We'll use this case to make it // possible to create new lists. e->setAccepted(true); } } void PlaylistBox::dragLeaveEvent(QDragLeaveEvent *e) { if(m_dropItem) { Item *old = m_dropItem; m_dropItem = 0; //old->repaint(); } QTreeWidget::dragLeaveEvent(e); } void PlaylistBox::mousePressEvent(QMouseEvent *e) { if(e->button() == Qt::LeftButton) m_doingMultiSelect = true; QTreeWidget::mousePressEvent(e); } void PlaylistBox::mouseReleaseEvent(QMouseEvent *e) { if(e->button() == Qt::LeftButton) { m_doingMultiSelect = false; slotPlaylistChanged(); } QTreeWidget::mouseReleaseEvent(e); } void PlaylistBox::keyPressEvent(QKeyEvent *e) { if((e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) && e->modifiers() == Qt::ShiftModifier) m_doingMultiSelect = true; QTreeWidget::keyPressEvent(e); } void PlaylistBox::keyReleaseEvent(QKeyEvent *e) { if(m_doingMultiSelect && e->key() == Qt::Key_Shift) { m_doingMultiSelect = false; slotPlaylistChanged(); } QTreeWidget::keyReleaseEvent(e); } PlaylistBox::ItemList PlaylistBox::selectedBoxItems() const { ItemList l; for(QTreeWidgetItemIterator it(const_cast(this), QTreeWidgetItemIterator::Selected); *it; ++it) l.append(static_cast(*it)); return l; } void PlaylistBox::setSingleItem(QTreeWidgetItem *item) { setSelectionMode(QAbstractItemView::SingleSelection); setCurrentItem(item); setSelectionMode(QAbstractItemView::ExtendedSelection); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox private slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::slotPlaylistChanged() { // Don't update while the mouse is pressed down. if(m_doingMultiSelect) return; ItemList items = selectedBoxItems(); m_hasSelection = !items.isEmpty(); bool allowReload = false; PlaylistList playlists; for(ItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it) { Playlist *p = (*it)->playlist(); if(p) { if(p->canReload()) allowReload = true; playlists.append(p); } } bool singlePlaylist = playlists.count() == 1; if(playlists.isEmpty() || (singlePlaylist && (playlists.front() == CollectionList::instance() || playlists.front()->readOnly()))) { action("file_save")->setEnabled(false); action("file_save_as")->setEnabled(false); action("renamePlaylist")->setEnabled(false); action("deleteItemPlaylist")->setEnabled(false); } else { action("file_save")->setEnabled(true); action("file_save_as")->setEnabled(true); action("renamePlaylist")->setEnabled(playlists.count() == 1); action("deleteItemPlaylist")->setEnabled(true); } action("reloadPlaylist")->setEnabled(allowReload); action("duplicatePlaylist")->setEnabled(!playlists.isEmpty()); if(m_k3bAction) m_k3bAction->setEnabled(!playlists.isEmpty()); action("editSearch")->setEnabled(singlePlaylist && playlists.front()->searchIsEditable()); if(singlePlaylist) { PlaylistCollection::raise(playlists.front()); if(playlists.front() == upcomingPlaylist()) action("deleteItemPlaylist")->setText(i18n("Hid&e")); else action("deleteItemPlaylist")->setText(i18n("R&emove")); } else if(!playlists.isEmpty()) createDynamicPlaylist(playlists); } void PlaylistBox::slotDoubleClicked(QTreeWidgetItem *item) { if(!item) return; TrackSequenceManager *manager = TrackSequenceManager::instance(); Item *playlistItem = static_cast(item); manager->setCurrentPlaylist(playlistItem->playlist()); manager->setCurrent(0); // Reset playback PlaylistItem *next = manager->nextItem(); // Allow manager to choose if(next) { emit startFilePlayback(next->file()); playlistItem->playlist()->setPlaying(next); } else action("stop")->trigger(); } void PlaylistBox::slotShowContextMenu(const QPoint &point) { m_contextMenu->popup(mapToGlobal(point)); } void PlaylistBox::slotPlaylistItemsDropped(Playlist *p) { raise(p); } void PlaylistBox::slotSetViewMode(int index) { if(index == m_viewModeIndex) return; viewMode()->setShown(false); m_viewModeIndex = index; viewMode()->setShown(true); } void PlaylistBox::setupItem(Item *item) { m_playlistDict.insert(item->playlist(), item); viewMode()->queueRefresh(); } void PlaylistBox::setupUpcomingPlaylist() { - KConfigGroup config(KGlobal::config(), "Playlists"); + KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); bool enable = config.readEntry("showUpcoming", false); setUpcomingPlaylistEnabled(enable); action("showUpcoming")->setChecked(enable); } void PlaylistBox::slotLoadCachedPlaylists() { kDebug() << "Loading cached playlists."; QTime stopwatch; stopwatch.start(); Cache::loadPlaylists(this); kDebug() << "Cached playlists loaded, took" << stopwatch.elapsed() << "ms"; // Auto-save playlists after they change. m_savePlaylistTimer = new QTimer(this); m_savePlaylistTimer->setInterval(3000); // 3 seconds with no change? -> commit m_savePlaylistTimer->setSingleShot(true); connect(m_savePlaylistTimer, SIGNAL(timeout()), SLOT(slotSavePlaylists())); clearSelection(); setCurrentItem(m_playlistDict[CollectionList::instance()]); QTimer::singleShot(0, CollectionList::instance(), SLOT(slotCheckCache())); QTimer::singleShot(0, object(), SLOT(slotScanFolders())); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item protected methods //////////////////////////////////////////////////////////////////////////////// PlaylistBox::Item *PlaylistBox::Item::m_collectionItem = 0; PlaylistBox::Item::Item(PlaylistBox *listBox, const QString &icon, const QString &text, Playlist *l) : QObject(listBox), QTreeWidgetItem(listBox, QStringList(text)), PlaylistObserver(l), m_playlist(l), m_text(text), m_iconName(icon), m_sortedFirst(false) { init(); } PlaylistBox::Item::Item(Item *parent, const QString &icon, const QString &text, Playlist *l) : QObject(parent->listView()), QTreeWidgetItem(parent, QStringList(text)), PlaylistObserver(l), m_playlist(l), m_text(text), m_iconName(icon), m_sortedFirst(false) { init(); } PlaylistBox::Item::~Item() { } int PlaylistBox::Item::compare(QTreeWidgetItem *i, int col, bool) const { Item *otherItem = static_cast(i); PlaylistBox *playlistBox = static_cast(treeWidget()); if(m_playlist == playlistBox->upcomingPlaylist() && otherItem->m_playlist != CollectionList::instance()) return -1; if(otherItem->m_playlist == playlistBox->upcomingPlaylist() && m_playlist != CollectionList::instance()) return 1; if(m_sortedFirst && !otherItem->m_sortedFirst) return -1; else if(otherItem->m_sortedFirst && !m_sortedFirst) return 1; return text(col).toLower().localeAwareCompare(i->text(col).toLower()); } // FIXME paintcell /*void PlaylistBox::Item::paintCell(QPainter *painter, const QColorGroup &colorGroup, int column, int width, int align) { PlaylistBox *playlistBox = static_cast(listView()); playlistBox->viewMode()->paintCell(this, painter, colorGroup, column, width, align); }*/ void PlaylistBox::Item::setText(int column, const QString &text) { m_text = text; QTreeWidgetItem::setText(column, text); } void PlaylistBox::Item::setup() { listView()->viewMode()->setupItem(this); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item protected slots //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::Item::slotSetName(const QString &name) { if(listView()) { setText(0, name); setSelected(true); treeWidget()->sortItems(0, Qt::AscendingOrder); treeWidget()->scrollToItem(treeWidget()->currentItem()); //FIXME viewmode //listView()->viewMode()->queueRefresh(); } } void PlaylistBox::Item::updateCurrent() { } void PlaylistBox::Item::updateData() { listView()->slotPlaylistDataChanged(); } //////////////////////////////////////////////////////////////////////////////// // PlaylistBox::Item private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistBox::Item::init() { PlaylistBox *list = listView(); list->setupItem(this); int iconSize = list->viewModeIndex() == 0 ? 32 : 16; setIcon(0, SmallIcon(m_iconName, iconSize)); list->addNameToDict(m_text); if(m_playlist) { connect(m_playlist, SIGNAL(signalNameChanged(QString)), this, SLOT(slotSetName(QString))); connect(m_playlist, SIGNAL(signalEnableDirWatch(bool)), list->object(), SLOT(slotEnableDirWatch(bool))); } if(m_playlist == CollectionList::instance()) { m_sortedFirst = true; m_collectionItem = this; list->viewMode()->setupDynamicPlaylists(); } if(m_playlist == list->historyPlaylist() || m_playlist == list->upcomingPlaylist()) m_sortedFirst = true; } // vim: set et sw=4 tw=0 sta: diff --git a/playlistcollection.cpp b/playlistcollection.cpp index c607e4b0..d8f87f9b 100644 --- a/playlistcollection.cpp +++ b/playlistcollection.cpp @@ -1,1001 +1,1001 @@ /** * Copyright (C) 2004 Scott Wheeler * Copyright (C) 2009 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playlistcollection.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collectionlist.h" #include "actioncollection.h" #include "advancedsearchdialog.h" #include "coverinfo.h" #include "searchplaylist.h" #include "folderplaylist.h" #include "historyplaylist.h" #include "upcomingplaylist.h" #include "directorylist.h" #include "mediafiles.h" #include "playermanager.h" #include "tracksequencemanager.h" #include "juk.h" //Laurent: readd it //#include "collectionadaptor.h" using namespace ActionCollection; //////////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////////// PlaylistCollection *PlaylistCollection::m_instance = 0; // Returns all folders in input list with their canonical path, if available, or // unchanged if not. static QStringList canonicalizeFolderPaths(const QStringList &folders) { QStringList result; foreach(const QString &folder, folders) { QString canonicalFolder = QDir(folder).canonicalPath(); result << (!canonicalFolder.isEmpty() ? canonicalFolder : folder); } return result; } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// PlaylistCollection::PlaylistCollection(PlayerManager *player, QStackedWidget *playlistStack) : m_playlistStack(playlistStack), m_historyPlaylist(0), m_upcomingPlaylist(0), m_playerManager(player), m_importPlaylists(true), m_searchEnabled(true), m_playing(false), m_showMorePlaylist(0), m_belowShowMorePlaylist(0), m_dynamicPlaylist(0), m_belowDistraction(0), m_distraction(0) { //new CollectionAdaptor( this ); //QDBus::sessionBus().registerObject("/Collection",this ); m_instance = this; m_actionHandler = new ActionHandler(this); // KDirLister's auto error handling seems to crash JuK during startup in // readConfig(). m_dirLister.setAutoErrorHandlingEnabled(false, playlistStack); readConfig(); } PlaylistCollection::~PlaylistCollection() { saveConfig(); CollectionList::instance()->saveItemsToCache(); delete m_actionHandler; Playlist::setShuttingDown(); } QString PlaylistCollection::name() const { return currentPlaylist()->name(); } FileHandle PlaylistCollection::currentFile() const { return currentPlaylist()->currentFile(); } int PlaylistCollection::count() const { return currentPlaylist()->count(); } int PlaylistCollection::time() const { return currentPlaylist()->time(); } void PlaylistCollection::playFirst() { m_playing = true; currentPlaylist()->playFirst(); currentChanged(); } void PlaylistCollection::playNextAlbum() { m_playing = true; currentPlaylist()->playNextAlbum(); currentChanged(); } void PlaylistCollection::playPrevious() { m_playing = true; currentPlaylist()->playPrevious(); currentChanged(); } void PlaylistCollection::playNext() { m_playing = true; currentPlaylist()->playNext(); currentChanged(); } void PlaylistCollection::stop() { m_playing = false; currentPlaylist()->stop(); dataChanged(); } bool PlaylistCollection::playing() const { return m_playing; } QStringList PlaylistCollection::playlists() const { QStringList l; //(or qFindChildren() if you need MSVC 6 compatibility) const QList childList = m_playlistStack->findChildren("Playlist"); foreach(Playlist *p, childList) { l.append(p->name()); } return l; } void PlaylistCollection::createPlaylist(const QString &name) { raise(new Playlist(this, name)); } void PlaylistCollection::createDynamicPlaylist(const PlaylistList &playlists) { if(m_dynamicPlaylist) m_dynamicPlaylist->setPlaylists(playlists); else { m_dynamicPlaylist = new DynamicPlaylist(playlists, this, i18n("Dynamic List"), "audio-midi", false, true); PlaylistCollection::setupPlaylist(m_dynamicPlaylist, QString()); } PlaylistCollection::raise(m_dynamicPlaylist); } void PlaylistCollection::showMore(const QString &artist, const QString &album) { PlaylistList playlists; PlaylistSearch::ComponentList components; if(currentPlaylist() != CollectionList::instance() && currentPlaylist() != m_showMorePlaylist) { playlists.append(currentPlaylist()); } playlists.append(CollectionList::instance()); if(!artist.isNull()) { // Just setting off the artist stuff in its own block. ColumnList columns; columns.append(PlaylistItem::ArtistColumn); PlaylistSearch::Component c(artist, false, columns, PlaylistSearch::Component::Exact); components.append(c); } if(!album.isNull()) { ColumnList columns; columns.append(PlaylistItem::AlbumColumn); PlaylistSearch::Component c(album, false, columns, PlaylistSearch::Component::Exact); components.append(c); } PlaylistSearch search(playlists, components, PlaylistSearch::MatchAll); if(m_showMorePlaylist) m_showMorePlaylist->setPlaylistSearch(search); else m_showMorePlaylist = new SearchPlaylist(this, search, i18n("Now Playing"), false, true); // The call to raise() below will end up clearing m_belowShowMorePlaylist, // so cache the value we want it to have now. Playlist *belowShowMore = visiblePlaylist(); PlaylistCollection::setupPlaylist(m_showMorePlaylist, QString()); PlaylistCollection::raise(m_showMorePlaylist); m_belowShowMorePlaylist = belowShowMore; } void PlaylistCollection::removeTrack(const QString &playlist, const QStringList &files) { Playlist *p = playlistByName(playlist); PlaylistItemList itemList; if(!p) return; QStringList::ConstIterator it; for(it = files.begin(); it != files.end(); ++it) { CollectionListItem *item = CollectionList::instance()->lookup(*it); if(item) { PlaylistItem *playlistItem = item->itemForPlaylist(p); if(playlistItem) itemList.append(playlistItem); } } p->clearItems(itemList); } QString PlaylistCollection::playlist() const { return visiblePlaylist() ? visiblePlaylist()->name() : QString(); } QString PlaylistCollection::playingPlaylist() const { return currentPlaylist() && m_playing ? currentPlaylist()->name() : QString(); } void PlaylistCollection::setPlaylist(const QString &playlist) { Playlist *p = playlistByName(playlist); if(p) raise(p); } QStringList PlaylistCollection::playlistTracks(const QString &playlist) const { Playlist *p = playlistByName(playlist); if(p) return p->files(); return QStringList(); } QString PlaylistCollection::trackProperty(const QString &file, const QString &property) const { CollectionList *l = CollectionList::instance(); CollectionListItem *item = l->lookup(file); return item ? item->file().property(property) : QString(); } QPixmap PlaylistCollection::trackCover(const QString &file, const QString &size) const { if(size.toLower() != "small" && size.toLower() != "large") return QPixmap(); CollectionList *l = CollectionList::instance(); CollectionListItem *item = l->lookup(file); if(!item) return QPixmap(); if(size.toLower() == "small") return item->file().coverInfo()->pixmap(CoverInfo::Thumbnail); else return item->file().coverInfo()->pixmap(CoverInfo::FullSize); } void PlaylistCollection::open(const QStringList &l) { QStringList files = l; if(files.isEmpty()) files = MediaFiles::openDialog(JuK::JuKInstance()); if(files.isEmpty()) return; bool justPlaylists = true; for(QStringList::ConstIterator it = files.constBegin(); it != files.constEnd() && justPlaylists; ++it) justPlaylists = !MediaFiles::isPlaylistFile(*it); if(visiblePlaylist() == CollectionList::instance() || justPlaylists || KMessageBox::questionYesNo( JuK::JuKInstance(), i18n("Do you want to add these items to the current list or to the collection list?"), QString(), KGuiItem(i18nc("current playlist", "Current")), KGuiItem(i18n("Collection"))) == KMessageBox::No) { CollectionList::instance()->addFiles(files); } else { visiblePlaylist()->addFiles(files); } dataChanged(); } void PlaylistCollection::open(const QString &playlist, const QStringList &files) { Playlist *p = playlistByName(playlist); if(p) p->addFiles(files); } void PlaylistCollection::addFolder() { DirectoryList l(m_folderList, m_excludedFolderList, m_importPlaylists, JuK::JuKInstance()); // FIXME signal result //DirectoryList::Result result = l.exec(); /*if(result.status == QDialog::Accepted) { m_dirLister.blockSignals(true); const bool reload = m_importPlaylists != result.addPlaylists; m_importPlaylists = result.addPlaylists; m_excludedFolderList = canonicalizeFolderPaths(result.excludedDirs); foreach(const QString &dir, result.addedDirs) { m_dirLister.openUrl(KUrl::fromPath(dir), KDirLister::Keep); m_folderList.append(dir); } foreach(const QString &dir, result.removedDirs) { m_dirLister.stop(KUrl::fromPath(dir)); m_folderList.removeAll(dir); } if(reload) { open(m_folderList); } else if(!result.addedDirs.isEmpty()) { open(result.addedDirs); } saveConfig(); m_dirLister.blockSignals(false); }*/ } void PlaylistCollection::rename() { QString old = visiblePlaylist()->name(); QString name = playlistNameDialog(i18n("Rename"), old, false); m_playlistNames.remove(old); if(name.isEmpty()) return; visiblePlaylist()->setName(name); } void PlaylistCollection::duplicate() { QString name = playlistNameDialog(i18nc("verb, copy the playlist", "Duplicate"), visiblePlaylist()->name()); if(name.isEmpty()) return; raise(new Playlist(this, visiblePlaylist()->items(), name)); } void PlaylistCollection::save() { visiblePlaylist()->save(); } void PlaylistCollection::saveAs() { visiblePlaylist()->saveAs(); } void PlaylistCollection::reload() { if(visiblePlaylist() == CollectionList::instance()) CollectionList::instance()->addFiles(m_folderList); else visiblePlaylist()->slotReload(); } void PlaylistCollection::editSearch() { SearchPlaylist *p = dynamic_cast(visiblePlaylist()); if(!p) return; // FIXME signalResult /*AdvancedSearchDialog::Result r = AdvancedSearchDialog(p->name(), p->playlistSearch(), JuK::JuKInstance()).exec(); if(r.result == AdvancedSearchDialog::Accepted) { p->setPlaylistSearch(r.search); p->setName(r.playlistName); }*/ } void PlaylistCollection::removeItems() { visiblePlaylist()->slotRemoveSelectedItems(); } void PlaylistCollection::refreshItems() { visiblePlaylist()->slotRefresh(); } void PlaylistCollection::renameItems() { visiblePlaylist()->slotRenameFile(); } void PlaylistCollection::addCovers(bool fromFile) { visiblePlaylist()->slotAddCover(fromFile); dataChanged(); } void PlaylistCollection::removeCovers() { visiblePlaylist()->slotRemoveCover(); dataChanged(); } void PlaylistCollection::viewCovers() { visiblePlaylist()->slotViewCover(); } void PlaylistCollection::showCoverManager() { visiblePlaylist()->slotShowCoverManager(); } PlaylistItemList PlaylistCollection::selectedItems() { return visiblePlaylist()->selectedItems(); } void PlaylistCollection::scanFolders() { CollectionList::instance()->addFiles(m_folderList); if(CollectionList::instance()->count() == 0) addFolder(); enableDirWatch(true); } void PlaylistCollection::createPlaylist() { QString name = playlistNameDialog(); if(!name.isEmpty()) raise(new Playlist(this, name)); } void PlaylistCollection::createSearchPlaylist() { QString name = uniquePlaylistName(i18n("Search Playlist")); // FIXME signal result /*AdvancedSearchDialog::Result r = AdvancedSearchDialog(name, PlaylistSearch(), JuK::JuKInstance()).exec(); if(r.result == AdvancedSearchDialog::Accepted) raise(new SearchPlaylist(this, r.search, r.playlistName));*/ } void PlaylistCollection::createFolderPlaylist() { QString folder = KFileDialog::getExistingDirectory(); if(folder.isEmpty()) return; QString name = uniquePlaylistName(folder.mid(folder.lastIndexOf('/') + 1)); name = playlistNameDialog(i18n("Create Folder Playlist"), name); if(!name.isEmpty()) raise(new FolderPlaylist(this, folder, name)); } void PlaylistCollection::guessTagFromFile() { visiblePlaylist()->slotGuessTagInfo(TagGuesser::FileName); } void PlaylistCollection::guessTagFromInternet() { visiblePlaylist()->slotGuessTagInfo(TagGuesser::MusicBrainz); } void PlaylistCollection::setSearchEnabled(bool enable) { if(enable == m_searchEnabled) return; m_searchEnabled = enable; visiblePlaylist()->setSearchEnabled(enable); } HistoryPlaylist *PlaylistCollection::historyPlaylist() const { return m_historyPlaylist; } void PlaylistCollection::setHistoryPlaylistEnabled(bool enable) { if((enable && m_historyPlaylist) || (!enable && !m_historyPlaylist)) return; if(enable) { action("showHistory")->setChecked(true); m_historyPlaylist = new HistoryPlaylist(this); m_historyPlaylist->setName(i18n("History")); setupPlaylist(m_historyPlaylist, "view-history"); QObject::connect(m_playerManager, SIGNAL(signalItemChanged(FileHandle)), historyPlaylist(), SLOT(appendProposedItem(FileHandle))); } else { delete m_historyPlaylist; m_historyPlaylist = 0; } } UpcomingPlaylist *PlaylistCollection::upcomingPlaylist() const { return m_upcomingPlaylist; } void PlaylistCollection::setUpcomingPlaylistEnabled(bool enable) { if((enable && m_upcomingPlaylist) || (!enable && !m_upcomingPlaylist)) return; if(enable) { action("showUpcoming")->setChecked(true); if(!m_upcomingPlaylist) m_upcomingPlaylist = new UpcomingPlaylist(this); setupPlaylist(m_upcomingPlaylist, "go-jump-today"); } else { action("showUpcoming")->setChecked(false); bool raiseCollection = visiblePlaylist() == m_upcomingPlaylist; if(raiseCollection) { raise(CollectionList::instance()); } m_upcomingPlaylist->deleteLater(); m_upcomingPlaylist = 0; } } QObject *PlaylistCollection::object() const { return m_actionHandler; } Playlist *PlaylistCollection::currentPlaylist() const { if(m_belowDistraction) return m_belowDistraction; if(m_upcomingPlaylist && m_upcomingPlaylist->active()) return m_upcomingPlaylist; if(Playlist::playingItem()) return Playlist::playingItem()->playlist(); else return visiblePlaylist(); } Playlist *PlaylistCollection::visiblePlaylist() const { return qobject_cast(m_playlistStack->currentWidget()); } void PlaylistCollection::raise(Playlist *playlist) { if(m_showMorePlaylist && currentPlaylist() == m_showMorePlaylist) m_showMorePlaylist->lower(playlist); if(m_dynamicPlaylist && currentPlaylist() == m_dynamicPlaylist) m_dynamicPlaylist->lower(playlist); TrackSequenceManager::instance()->setCurrentPlaylist(playlist); playlist->applySharedSettings(); playlist->setSearchEnabled(m_searchEnabled); m_playlistStack->setCurrentWidget(playlist); clearShowMore(false); dataChanged(); } void PlaylistCollection::raiseDistraction() { if(m_belowDistraction) return; m_belowDistraction = currentPlaylist(); if(!m_distraction) { m_distraction = new QWidget(m_playlistStack); m_playlistStack->addWidget(m_distraction); } m_playlistStack->setCurrentWidget(m_distraction); } void PlaylistCollection::lowerDistraction() { if(!m_distraction) return; if(m_belowDistraction) m_playlistStack->setCurrentWidget(m_belowDistraction); m_belowDistraction = 0; } //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// QStackedWidget *PlaylistCollection::playlistStack() const { return m_playlistStack; } void PlaylistCollection::setupPlaylist(Playlist *playlist, const QString &) { if(!playlist->fileName().isEmpty()) m_playlistFiles.insert(playlist->fileName()); if(!playlist->name().isEmpty()) m_playlistNames.insert(playlist->name()); m_playlistStack->addWidget(playlist); QObject::connect(playlist, SIGNAL(itemSelectionChanged()), object(), SIGNAL(signalSelectedItemsChanged())); } bool PlaylistCollection::importPlaylists() const { return m_importPlaylists; } bool PlaylistCollection::containsPlaylistFile(const QString &file) const { return m_playlistFiles.contains(file); } bool PlaylistCollection::showMoreActive() const { return visiblePlaylist() == m_showMorePlaylist; } void PlaylistCollection::clearShowMore(bool raisePlaylist) { if(!m_showMorePlaylist) return; if(raisePlaylist) { if(m_belowShowMorePlaylist) raise(m_belowShowMorePlaylist); else raise(CollectionList::instance()); } m_belowShowMorePlaylist = 0; } void PlaylistCollection::enableDirWatch(bool enable) { QObject *collection = CollectionList::instance(); m_dirLister.disconnect(object()); if(enable) { QObject::connect(&m_dirLister, SIGNAL(newItems(KFileItemList)), object(), SLOT(slotNewItems(KFileItemList))); QObject::connect(&m_dirLister, SIGNAL(refreshItems(QList >)), collection, SLOT(slotRefreshItems(QList >))); QObject::connect(&m_dirLister, SIGNAL(deleteItem(KFileItem)), collection, SLOT(slotDeleteItem(KFileItem))); } } QString PlaylistCollection::playlistNameDialog(const QString &caption, const QString &suggest, bool forceUnique) const { bool ok; QString name = KInputDialog::getText( caption, i18n("Please enter a name for this playlist:"), forceUnique ? uniquePlaylistName(suggest) : suggest, &ok); return ok ? uniquePlaylistName(name) : QString(); } QString PlaylistCollection::uniquePlaylistName(const QString &suggest) const { if(suggest.isEmpty()) return uniquePlaylistName(); if(!m_playlistNames.contains(suggest)) return suggest; QString base = suggest; base.remove(QRegExp("\\s\\([0-9]+\\)$")); int count = 1; QString s = QString("%1 (%2)").arg(base).arg(count); while(m_playlistNames.contains(s)) { count++; s = QString("%1 (%2)").arg(base).arg(count); } return s; } void PlaylistCollection::addNameToDict(const QString &name) { m_playlistNames.insert(name); } void PlaylistCollection::addFileToDict(const QString &file) { m_playlistFiles.insert(file); } void PlaylistCollection::removeNameFromDict(const QString &name) { m_playlistNames.remove(name); } void PlaylistCollection::removeFileFromDict(const QString &file) { m_playlistFiles.remove(file); } void PlaylistCollection::dirChanged(const QString &path) { QString canonicalPath = QDir(path).canonicalPath(); if(canonicalPath.isEmpty()) return; foreach(const QString &excludedFolder, m_excludedFolderList) { if(canonicalPath.startsWith(excludedFolder)) return; } CollectionList::instance()->addFiles(QStringList(canonicalPath)); } Playlist *PlaylistCollection::playlistByName(const QString &name) const { for(int i = 0; i < m_playlistStack->count(); ++i) { Playlist *p = qobject_cast(m_playlistStack->widget(i)); if(p && p->name() == name) return p; } return 0; } void PlaylistCollection::newItems(const KFileItemList &list) const { // Make fast-path for the normal case if(m_excludedFolderList.isEmpty()) { CollectionList::instance()->slotNewItems(list); return; } // Slow case: Directories to exclude from consideration KFileItemList filteredList(list); foreach(const QString &excludedFolder, m_excludedFolderList) { QMutableListIterator filteredListIterator(filteredList); while(filteredListIterator.hasNext()) { const KFileItem fileItem = filteredListIterator.next(); if(fileItem.url().path().startsWith(excludedFolder)) filteredListIterator.remove(); } } CollectionList::instance()->slotNewItems(filteredList); } //////////////////////////////////////////////////////////////////////////////// // private methods //////////////////////////////////////////////////////////////////////////////// void PlaylistCollection::readConfig() { - KConfigGroup config(KGlobal::config(), "Playlists"); + KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); m_importPlaylists = config.readEntry("ImportPlaylists", true); m_folderList = config.readEntry("DirectoryList", QStringList()); m_excludedFolderList = canonicalizeFolderPaths( config.readEntry("ExcludeDirectoryList", QStringList())); foreach(const QString &folder, m_folderList) { m_dirLister.openUrl(folder, KDirLister::Keep); } } void PlaylistCollection::saveConfig() { - KConfigGroup config(KGlobal::config(), "Playlists"); + KConfigGroup config(KSharedConfig::openConfig(), "Playlists"); config.writeEntry("ImportPlaylists", m_importPlaylists); config.writeEntry("showUpcoming", action("showUpcoming")->isChecked()); config.writePathEntry("DirectoryList", m_folderList); config.writePathEntry("ExcludeDirectoryList", m_excludedFolderList); config.sync(); } //////////////////////////////////////////////////////////////////////////////// // ActionHanlder implementation //////////////////////////////////////////////////////////////////////////////// PlaylistCollection::ActionHandler::ActionHandler(PlaylistCollection *collection) : QObject(0), m_collection(collection) { setObjectName( QLatin1String("ActionHandler" )); KActionMenu *menu; // "New" menu menu = new KActionMenu(KIcon("document-new"), i18nc("new playlist", "&New"), actions()); actions()->addAction("file_new", menu); menu->addAction(createAction(i18n("&Empty Playlist..."), SLOT(slotCreatePlaylist()), "newPlaylist", "window-new", KShortcut(Qt::CTRL + Qt::Key_N))); menu->addAction(createAction(i18n("&Search Playlist..."), SLOT(slotCreateSearchPlaylist()), "newSearchPlaylist", "edit-find", KShortcut(Qt::CTRL + Qt::Key_F))); menu->addAction(createAction(i18n("Playlist From &Folder..."), SLOT(slotCreateFolderPlaylist()), "newDirectoryPlaylist", "document-open", KShortcut(Qt::CTRL + Qt::Key_D))); // Guess tag info menu #if HAVE_TUNEPIMP menu = new KActionMenu(i18n("&Guess Tag Information"), actions()); actions()->addAction("guessTag", menu); /* menu->setIcon(SmallIcon("wizard")); */ menu->addAction(createAction(i18n("From &File Name"), SLOT(slotGuessTagFromFile()), "guessTagFile", "document-import", KShortcut(Qt::CTRL + Qt::Key_G))); menu->addAction(createAction(i18n("From &Internet"), SLOT(slotGuessTagFromInternet()), "guessTagInternet", "network-server", KShortcut(Qt::CTRL + Qt::Key_I))); #else createAction(i18n("Guess Tag Information From &File Name"), SLOT(slotGuessTagFromFile()), "guessTag", "document-import", KShortcut(Qt::CTRL + Qt::Key_G)); #endif createAction(i18n("Play First Track"),SLOT(slotPlayFirst()), "playFirst"); createAction(i18n("Play Next Album"), SLOT(slotPlayNextAlbum()), "forwardAlbum", "go-down-search"); KStandardAction::open(this, SLOT(slotOpen()), actions()); KStandardAction::save(this, SLOT(slotSave()), actions()); KStandardAction::saveAs(this, SLOT(slotSaveAs()), actions()); createAction(i18n("Manage &Folders..."), SLOT(slotManageFolders()), "openDirectory", "folder-new"); createAction(i18n("&Rename..."), SLOT(slotRename()), "renamePlaylist", "edit-rename"); createAction(i18nc("verb, copy the playlist", "D&uplicate..."), SLOT(slotDuplicate()), "duplicatePlaylist", "edit-copy"); createAction(i18n("R&emove"), SLOT(slotRemove()), "deleteItemPlaylist", "user-trash"); createAction(i18n("Reload"), SLOT(slotReload()), "reloadPlaylist", "view-refresh"); createAction(i18n("Edit Search..."), SLOT(slotEditSearch()), "editSearch"); createAction(i18n("&Delete"), SLOT(slotRemoveItems()), "removeItem", "edit-delete"); createAction(i18n("Refresh"), SLOT(slotRefreshItems()), "refresh", "view-refresh"); createAction(i18n("&Rename File"), SLOT(slotRenameItems()), "renameFile", "document-save-as", KShortcut(Qt::CTRL + Qt::Key_R)); menu = new KActionMenu(i18n("Cover Manager"), actions()); actions()->addAction("coverManager", menu); /* menu->setIcon(SmallIcon("image-x-generic")); */ menu->addAction(createAction(i18n("&View Cover"), SLOT(slotViewCovers()), "viewCover", "document-preview")); menu->addAction(createAction(i18n("Get Cover From &File..."), SLOT(slotAddLocalCover()), "addCover", "document-import", KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_F))); menu->addAction(createAction(i18n("Get Cover From &Internet..."), SLOT(slotAddInternetCover()), "webImageCover", "network-server", KShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_G))); menu->addAction(createAction(i18n("&Delete Cover"), SLOT(slotRemoveCovers()), "removeCover", "edit-delete")); menu->addAction(createAction(i18n("Show Cover &Manager"), SLOT(slotShowCoverManager()), "showCoverManager")); KToggleAction *upcomingAction = new KToggleAction(KIcon("go-jump-today"), i18n("Show &Play Queue"), actions()); actions()->addAction("showUpcoming", upcomingAction); connect(upcomingAction, SIGNAL(triggered(bool)), this, SLOT(slotSetUpcomingPlaylistEnabled(bool))); } KAction *PlaylistCollection::ActionHandler::createAction(const QString &text, const char *slot, const char *name, const QString &icon, const KShortcut &shortcut) { KAction *action; if(icon.isNull()) action = new KAction(text, actions()); else action = new KAction(KIcon(icon), text, actions()); actions()->addAction(name, action); connect( action, SIGNAL(triggered(bool)), slot); action->setShortcut(shortcut); return action; } // vim: set et sw=4 tw=0 sta: diff --git a/playlistsplitter.cpp b/playlistsplitter.cpp index 044b1525..81564ce1 100644 --- a/playlistsplitter.cpp +++ b/playlistsplitter.cpp @@ -1,340 +1,340 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * Copyright (C) 2009 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "playlistsplitter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "searchwidget.h" #include "playlistsearch.h" #include "actioncollection.h" #include "tageditor.h" #include "collectionlist.h" #include "playermanager.h" #include "nowplaying.h" #include "playlistbox.h" #include "lyricswidget.h" #include "mpris2/mpris2.h" //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// PlaylistSplitter::PlaylistSplitter(PlayerManager *player, QWidget *parent) : QSplitter(Qt::Horizontal, parent), m_newVisible(0), m_playlistBox(0), m_searchWidget(0), m_playlistStack(0), m_editor(0), m_nowPlaying(0), m_player(player), m_lyricsWidget(0), m_editorSplitter(0) { setObjectName(QLatin1String("playlistSplitter")); setupActions(); setupLayout(); readConfig(); m_editor->slotUpdateCollection(); m_editor->setupObservers(); } PlaylistSplitter::~PlaylistSplitter() { saveConfig(); // TagEditor needs to write its configuration out while it's still valid, // destroy it now. delete m_editor; delete m_lyricsWidget; // NowPlaying depends on the PlaylistCollection, so kill it now. delete m_nowPlaying; m_nowPlaying = 0; delete m_searchWidget; // Take no chances here either. // Since we want to ensure that the shutdown process for the PlaylistCollection // (a base class for PlaylistBox) has a chance to write the playlists to disk // before they are deleted we're explicitly deleting the PlaylistBox here. delete m_playlistBox; } PlaylistInterface *PlaylistSplitter::playlist() const { return m_playlistBox; } bool PlaylistSplitter::eventFilter(QObject *, QEvent *event) { if(event->type() == FocusUpEvent::id) { m_searchWidget->setFocus(); return true; } return false; } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void PlaylistSplitter::setFocus() { m_searchWidget->setFocus(); } void PlaylistSplitter::slotFocusCurrentPlaylist() { Playlist *playlist = m_playlistBox->visiblePlaylist(); if(playlist) { playlist->setFocus(); playlist->clearSelection(); // Select the top visible (and matching) item. PlaylistItem *item = static_cast(playlist->itemAt(QPoint(0, 0))); if(!item) return; // A little bit of a hack to make QListView repaint things properly. Switch // to single selection mode, set the selection and then switch back. playlist->setSelectionMode(QTreeWidget::SingleSelection); playlist->markItemSelected(item, true); playlist->setCurrentItem(item); playlist->setSelectionMode(QTreeWidget::ExtendedSelection); } } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// Playlist *PlaylistSplitter::visiblePlaylist() const { return m_newVisible ? m_newVisible : m_playlistBox->visiblePlaylist(); } void PlaylistSplitter::setupActions() { KActionCollection* coll = ActionCollection::actions(); KToggleAction *showSearch = new KToggleAction(KIcon("edit-find"), i18n("Show &Search Bar"), this); coll->addAction("showSearch", showSearch); KAction *act = new KAction(KIcon("edit-clear"), i18n("Edit Track Search"), this); coll->addAction("editTrackSearch", act); act->setShortcut(Qt::Key_F6); connect(act, SIGNAL(triggered(bool)), SLOT(setFocus())); } void PlaylistSplitter::setupLayout() { setOpaqueResize(false); // Disable the GUI until startup is complete (as indicated by PlaylistBox) setEnabled(false); // Create a splitter to go between the playlists and the editor. m_editorSplitter = new QSplitter(Qt::Vertical, this); m_editorSplitter->setObjectName( QLatin1String("editorSplitter" )); // Make sure none of the optional widgets are collapsible, this causes the // widget to be essentially invisible but logically shown. this->setChildrenCollapsible(false); m_editorSplitter->setChildrenCollapsible(false); // Create the playlist and the editor. QWidget *top = new QWidget(m_editorSplitter); QVBoxLayout *topLayout = new QVBoxLayout(top); topLayout->setMargin(0); topLayout->setSpacing(0); m_playlistStack = new QStackedWidget(top); m_playlistStack->setObjectName( QLatin1String("playlistStack" )); m_playlistStack->installEventFilter(this); m_playlistStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_playlistStack->hide(); // Will be shown after CollectionList filled. m_editor = new TagEditor(m_editorSplitter); m_editor->setObjectName( QLatin1String("TagEditor" )); // Create the lyrics widget m_lyricsWidget = new LyricsWidget(this); insertWidget(2, m_lyricsWidget); // Create the PlaylistBox m_playlistBox = new PlaylistBox(m_player, this, m_playlistStack); m_playlistBox->setObjectName( QLatin1String( "playlistBox" ) ); connect(m_playlistBox->object(), SIGNAL(signalSelectedItemsChanged()), this, SLOT(slotPlaylistSelectionChanged())); connect(m_playlistBox, SIGNAL(signalPlaylistDestroyed(Playlist*)), m_editor, SLOT(slotPlaylistDestroyed(Playlist*))); connect(m_playlistBox, SIGNAL(startupComplete()), SLOT(slotEnable())); connect(m_playlistBox, SIGNAL(startFilePlayback(FileHandle)), m_player, SLOT(play(FileHandle))); m_player->setPlaylistInterface(m_playlistBox); // Let interested parties know we're ready connect(m_playlistBox, SIGNAL(startupComplete()), SIGNAL(guiReady())); insertWidget(0, m_playlistBox); m_nowPlaying = new NowPlaying(top, m_playlistBox); connect(m_player, SIGNAL(signalItemChanged(FileHandle)), m_nowPlaying, SLOT(slotUpdate(FileHandle))); connect(m_player, SIGNAL(signalItemChanged(FileHandle)), m_lyricsWidget, SLOT(playing(FileHandle))); // Create the search widget -- this must be done after the CollectionList is created. m_searchWidget = new SearchWidget(top); // auto-shortcuts don't seem to work and aren't needed anyway. KAcceleratorManager::setNoAccel(m_searchWidget); connect(m_searchWidget, SIGNAL(signalQueryChanged()), this, SLOT(slotShowSearchResults())); connect(m_searchWidget, SIGNAL(signalDownPressed()), this, SLOT(slotFocusCurrentPlaylist())); connect(m_searchWidget, SIGNAL(signalAdvancedSearchClicked()), m_playlistBox->object(), SLOT(slotCreateSearchPlaylist())); connect(m_searchWidget, SIGNAL(signalShown(bool)), m_playlistBox->object(), SLOT(slotSetSearchEnabled(bool))); connect(m_searchWidget, SIGNAL(returnPressed()), m_playlistBox->object(), SLOT(slotPlayFirst())); connect(ActionCollection::action("showSearch"), SIGNAL(toggled(bool)), m_searchWidget, SLOT(setEnabled(bool))); topLayout->addWidget(m_nowPlaying); topLayout->addWidget(m_searchWidget); topLayout->insertStretch(-1); // Force search bar to top while playlistStack hides topLayout->addWidget(m_playlistStack, 1); // Now that GUI setup is complete, add some auto-update signals. connect(CollectionList::instance(), SIGNAL(signalCollectionChanged()), m_editor, SLOT(slotUpdateCollection())); connect(m_playlistStack, SIGNAL(currentChanged(int)), this, SLOT(slotPlaylistChanged(int))); // Show the collection on startup. m_playlistBox->setCurrentItem(m_playlistBox->topLevelItem(0)); } void PlaylistSplitter::readConfig() { - KConfigGroup config(KGlobal::config(), "Splitter"); + KConfigGroup config(KSharedConfig::openConfig(), "Splitter"); QList splitterSizes = config.readEntry("PlaylistSplitterSizes",QList()); if(splitterSizes.isEmpty()) { splitterSizes.append(100); splitterSizes.append(640); } setSizes(splitterSizes); bool showSearch = config.readEntry("ShowSearch", true); ActionCollection::action("showSearch")->setChecked(showSearch); m_searchWidget->setHidden(!showSearch); splitterSizes = config.readEntry("EditorSplitterSizes",QList()); if(splitterSizes.isEmpty()) { // If no sizes were saved, use default sizes for the playlist and the // editor, respectively. The values are just hints for the actual size, // m_editorSplitter will distribute the space according to their // relative weight. splitterSizes.append(300); splitterSizes.append(200); } m_editorSplitter->setSizes(splitterSizes); } void PlaylistSplitter::saveConfig() { - KConfigGroup config(KGlobal::config(), "Splitter"); + KConfigGroup config(KSharedConfig::openConfig(), "Splitter"); config.writeEntry("PlaylistSplitterSizes", sizes()); config.writeEntry("ShowSearch", ActionCollection::action("showSearch")->isChecked()); config.writeEntry("EditorSplitterSizes", m_editorSplitter->sizes()); } void PlaylistSplitter::slotShowSearchResults() { PlaylistList playlists; playlists.append(visiblePlaylist()); PlaylistSearch search = m_searchWidget->search(playlists); visiblePlaylist()->setSearch(search); } void PlaylistSplitter::slotPlaylistSelectionChanged() { m_editor->slotSetItems(visiblePlaylist()->selectedItems()); } void PlaylistSplitter::slotPlaylistChanged(int i) { Playlist *p = qobject_cast(m_playlistStack->widget(i)); if(!p) return; m_newVisible = p; m_searchWidget->setSearch(p->search()); m_newVisible = 0; } void PlaylistSplitter::slotEnable() { kDebug() << "Enabling GUI"; QTime stopwatch; stopwatch.start(); setEnabled(true); // Ready to go. m_playlistStack->show(); kDebug() << "Finished enabling GUI, took" << stopwatch.elapsed() << "ms"; (void) new Mpris2(this); } // vim: set et sw=4 tw=0 sta: diff --git a/scrobbleconfigdlg.cpp b/scrobbleconfigdlg.cpp index 60fbe572..da43e71c 100644 --- a/scrobbleconfigdlg.cpp +++ b/scrobbleconfigdlg.cpp @@ -1,159 +1,159 @@ /** * Copyright (C) 2012 Martin Sandsmark * Copyright (C) 2014 Arnold Dumas * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "scrobbleconfigdlg.h" #include "scrobbler.h" #include #include #include #include #include #include #include #include #include ScrobbleConfigDlg::ScrobbleConfigDlg(QWidget* parent, Qt::WindowFlags f) : KDialog(parent, f) , m_wallet(0) { setWindowTitle(i18n("Configure scrobbling...")); setButtons(Apply | Cancel); m_passwordEdit = new KLineEdit(this); m_passwordEdit->setPasswordMode(true); m_usernameEdit = new KLineEdit(this); m_testButton = new QPushButton(i18n("Test login..."), this); m_testFeedbackLabel = new QLabel(""); QWidget *mainWidget = new QWidget(); QFormLayout *layout = new QFormLayout(); mainWidget->setLayout(layout); QLabel *infoLabel = new QLabel(i18n("Please enter your last.fm login credentials:")); infoLabel->setOpenExternalLinks(true); infoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); layout->addRow(infoLabel); layout->addRow(new QLabel(i18n("Username:")), m_usernameEdit); layout->addRow(new QLabel(i18n("Password:")), m_passwordEdit); layout->addRow(m_testButton); layout->addRow(m_testFeedbackLabel); connect(m_passwordEdit, SIGNAL(textEdited(QString)), this, SLOT(valuesChanged())); connect(m_usernameEdit, SIGNAL(textEdited(QString)), this, SLOT(valuesChanged())); connect(m_testButton, SIGNAL(clicked(bool)), this, SLOT(testLogin())); connect(this, SIGNAL(applyClicked()), this, SLOT(save())); setMainWidget(mainWidget); // Loading credentials using either KWallet or KConfigGroup. m_wallet = Scrobbler::openKWallet(); if (m_wallet) { QMap scrobblingCredentials; m_wallet->readMap("Scrobbling", scrobblingCredentials); if (scrobblingCredentials.contains("Username") && scrobblingCredentials.contains("Password")) { m_usernameEdit->setText(scrobblingCredentials.value("Username")); m_passwordEdit->setText(scrobblingCredentials.value("Password")); } } else { // Warning message, KWallet is safer than KConfig. KMessageBox::information(this, i18n("KWallet is unavailable, your Last.fm credentials will be stored without encryption."), i18n("KWallet is unavailable")); - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); m_usernameEdit->setText(config.readEntry("Username", "")); m_passwordEdit->setText(config.readEntry("Password", "")); } if (m_passwordEdit->text().isEmpty() || m_usernameEdit->text().isEmpty()) { button(Apply)->setEnabled(false); m_testButton->setEnabled(false); } } ScrobbleConfigDlg::~ScrobbleConfigDlg() { delete m_wallet; } void ScrobbleConfigDlg::valuesChanged() { if (m_usernameEdit->text().isEmpty() || m_passwordEdit->text().isEmpty()) m_testButton->setEnabled(false); else m_testButton->setEnabled(true); button(Apply)->setEnabled(false); } void ScrobbleConfigDlg::save() { QDialog::accept(); if (m_wallet) { QMap scrobblingCredentials; scrobblingCredentials.insert("Username", m_usernameEdit->text()); scrobblingCredentials.insert("Password", m_passwordEdit->text()); if (!m_wallet->writeMap("Scrobbling", scrobblingCredentials)) { kError() << "Couldn't save Last.fm credentials using KWallet."; } } else { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); config.writeEntry("Username", m_usernameEdit->text()); config.writeEntry("Password", m_passwordEdit->text()); } } void ScrobbleConfigDlg::testLogin() { m_testFeedbackLabel->setText(i18n("Validating login...")); Scrobbler *scrobbler = new Scrobbler(this); connect(scrobbler, SIGNAL(validAuth()), this, SLOT(validLogin())); connect(scrobbler, SIGNAL(invalidAuth()), this, SLOT(invalidLogin())); setEnabled(false); scrobbler->getAuthToken(m_usernameEdit->text(), m_passwordEdit->text()); } void ScrobbleConfigDlg::invalidLogin() { m_testFeedbackLabel->setText(i18n("Login invalid.")); setEnabled(true); sender()->deleteLater(); button(Apply)->setEnabled(false); } void ScrobbleConfigDlg::validLogin() { m_testFeedbackLabel->setText(i18n("Login valid.")); setEnabled(true); sender()->deleteLater(); button(Apply)->setEnabled(true); } diff --git a/scrobbler.cpp b/scrobbler.cpp index 2c201441..afbf1a8b 100644 --- a/scrobbler.cpp +++ b/scrobbler.cpp @@ -1,344 +1,344 @@ /** * Copyright (C) 2012 Martin Sandsmark * Copyright (C) 2014 Arnold Dumas * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "scrobbler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "tag.h" Scrobbler::Scrobbler(QObject* parent) : QObject(parent) , m_networkAccessManager(0) , m_wallet(0) { QByteArray sessionKey; m_wallet = Scrobbler::openKWallet(); if (m_wallet) { m_wallet->readEntry("SessionKey", sessionKey); } else { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); sessionKey.append(config.readEntry("SessionKey", "")); } if(sessionKey.isEmpty()) getAuthToken(); } Scrobbler::~Scrobbler() { delete m_wallet; } bool Scrobbler::isScrobblingEnabled() { QString username, password; if (KWallet::Wallet::folderDoesNotExist(KWallet::Wallet::LocalWallet(), "JuK")) { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); username = config.readEntry("Username", ""); password = config.readEntry("Password", ""); } else { KWallet::Wallet* wallet = Scrobbler::openKWallet(); if (wallet) { QMap scrobblingCredentials; wallet->readMap("Scrobbling", scrobblingCredentials); if (scrobblingCredentials.contains("Username") && scrobblingCredentials.contains("Password")) { username = scrobblingCredentials["Username"]; password = scrobblingCredentials["Password"]; } delete wallet; } } return (!username.isEmpty() && !password.isEmpty()); } KWallet::Wallet* Scrobbler::openKWallet() // static { const QString walletFolderName = "JuK"; KWallet::Wallet* wallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), 0); if (wallet) { if (!wallet->hasFolder(walletFolderName)) { if (!wallet->createFolder(walletFolderName)) { delete wallet; return 0; } } if (!wallet->setFolder(walletFolderName)) { delete wallet; return 0; } } return wallet; } QByteArray Scrobbler::md5(QByteArray data) { return QCryptographicHash::hash(data, QCryptographicHash::Md5) .toHex().rightJustified(32, '0').toLower(); } void Scrobbler::sign(QMap< QString, QString >& params) { params["api_key"] = "3e6ecbd7284883089e8f2b5b53b0aecd"; QString s; QMapIterator i(params); while(i.hasNext()) { i.next(); s += i.key() + i.value(); } s += "2cab3957b1f70d485e9815ac1ac94096"; //shared secret params["api_sig"] = md5(s.toUtf8()); } void Scrobbler::getAuthToken(QString username, QString password) { kDebug() << "Getting new auth token for user:" << username; QByteArray authToken = md5((username + md5(password.toUtf8())).toUtf8()); QMap params; params["method"] = "auth.getMobileSession"; params["authToken"] = authToken; params["username"] = username; QUrl url("http://ws.audioscrobbler.com/2.0/?"); sign(params); foreach(QString key, params.keys()) { url.addQueryItem(key, params[key]); } if (!m_networkAccessManager) m_networkAccessManager = new QNetworkAccessManager(this); QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url)); connect(reply, SIGNAL(finished()), this, SLOT(handleAuthenticationReply())); } void Scrobbler::getAuthToken() { QString username, password; if (m_wallet) { QMap scrobblingCredentials; m_wallet->readMap("Scrobbling", scrobblingCredentials); if (scrobblingCredentials.contains("Username") && scrobblingCredentials.contains("Password")) { username = scrobblingCredentials["Username"]; password = scrobblingCredentials["Password"]; } } else { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); username = config.readEntry("Username", ""); password = config.readEntry("Password", ""); } if(username.isEmpty() || password.isEmpty()) return; getAuthToken(username, password); } void Scrobbler::handleAuthenticationReply() { QNetworkReply* reply = qobject_cast(sender()); kDebug() << "got authentication reply"; if(reply->error() != QNetworkReply::NoError) { emit invalidAuth(); kWarning() << "Error while getting authentication reply" << reply->errorString(); return; } QDomDocument doc; QByteArray data = reply->readAll(); doc.setContent(data); QString sessionKey = doc.documentElement() .firstChildElement("session") .firstChildElement("key").text(); if(sessionKey.isEmpty()) { emit invalidAuth(); kWarning() << "Unable to get session key" << data; return; } if (m_wallet) { m_wallet->writeEntry("SessionKey", sessionKey.toUtf8()); } else { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); config.writeEntry("SessionKey", sessionKey); } emit validAuth(); } void Scrobbler::nowPlaying(const FileHandle& file) { QString sessionKey; if (m_wallet) { QByteArray sessionKeyByteArray; m_wallet->readEntry("SessionKey", sessionKeyByteArray); sessionKey = QString::fromLatin1(sessionKeyByteArray); } else { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); sessionKey = config.readEntry("SessionKey", ""); } if (!m_file.isNull()) { scrobble(); // Update time-played info for last track } QMap params; params["method"] = "track.updateNowPlaying"; params["sk"] = sessionKey; params["track"] = file.tag()->title(); params["artist"] = file.tag()->artist(); params["album"] = file.tag()->album(); params["trackNumber"] = QString::number(file.tag()->track()); params["duration"] = QString::number(file.tag()->seconds()); sign(params); post(params); m_file = file; // May be FileHandle::null() m_playbackTimer = QDateTime::currentDateTime(); } void Scrobbler::scrobble() { QString sessionKey; if (m_wallet) { QByteArray sessionKeyByteArray; m_wallet->readEntry("SessionKey", sessionKeyByteArray); sessionKey = QString::fromLatin1(sessionKeyByteArray); } else { - KConfigGroup config(KGlobal::config(), "Scrobbling"); + KConfigGroup config(KSharedConfig::openConfig(), "Scrobbling"); sessionKey = config.readEntry("SessionKey", ""); } if(sessionKey.isEmpty()) { getAuthToken(); return; } int halfDuration = m_file.tag()->seconds() / 2; int timeElapsed = m_playbackTimer.secsTo(QDateTime::currentDateTime()); if (timeElapsed < 30 || timeElapsed < halfDuration) { return; // API says not to scrobble if the user didn't play long enough } kDebug() << "Scrobbling" << m_file.tag()->title(); QMap params; params["method"] = "track.scrobble"; params["sk"] = sessionKey; params["track"] = m_file.tag()->title(); params["artist"] = m_file.tag()->artist(); params["album"] = m_file.tag()->album(); params["timestamp"] = QString::number(m_playbackTimer.toTime_t()); params["trackNumber"] = QString::number(m_file.tag()->track()); params["duration"] = QString::number(m_file.tag()->seconds()); sign(params); post(params); } void Scrobbler::post(QMap ¶ms) { if(!m_networkAccessManager) { return; } QUrl url("http://ws.audioscrobbler.com/2.0/"); QByteArray data; foreach(QString key, params.keys()) { data += QUrl::toPercentEncoding(key) + '=' + QUrl::toPercentEncoding(params[key]) + '&'; } QNetworkRequest req(url); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QNetworkReply *reply = m_networkAccessManager->post(req, data); connect(reply, SIGNAL(finished()), this, SLOT(handleResults())); } void Scrobbler::handleResults() { QNetworkReply* reply = qobject_cast(sender()); QByteArray data = reply->readAll(); if(data.contains("code=\"9\"")) // We need a new token getAuthToken(); } diff --git a/tageditor.cpp b/tageditor.cpp index 96cd86f4..ff9b3594 100644 --- a/tageditor.cpp +++ b/tageditor.cpp @@ -1,674 +1,674 @@ /** * Copyright (C) 2002-2004 Scott Wheeler * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "tageditor.h" #include "collectionlist.h" #include "playlistitem.h" #include "tag.h" #include "actioncollection.h" #include "tagtransactionmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef KeyRelease class FileNameValidator : public QValidator { public: FileNameValidator(QObject *parent, const char *name = 0) : QValidator(parent) { setObjectName( QLatin1String( name ) ); } virtual void fixup(QString &s) const { s.remove('/'); } virtual State validate(QString &s, int &) const { if(s.contains('/')) return Invalid; return Acceptable; } }; class FixedHLayout : public QHBoxLayout { public: FixedHLayout(QWidget *parent, int margin = 0, int spacing = -1, const char *name = 0) : QHBoxLayout(parent), m_width(-1) { setMargin(margin); setSpacing(spacing); setObjectName(QLatin1String(name)); } FixedHLayout(QLayout *parentLayout, int spacing = -1, const char *name = 0) : QHBoxLayout(), m_width(-1) { parentLayout->addItem(this); setSpacing(spacing); setObjectName(QLatin1String(name)); } void setWidth(int w = -1) { m_width = w == -1 ? QHBoxLayout::minimumSize().width() : w; } virtual QSize minimumSize() const { QSize s = QHBoxLayout::minimumSize(); s.setWidth(m_width); return s; } private: int m_width; }; class CollectionObserver : public PlaylistObserver { public: CollectionObserver(TagEditor *parent) : PlaylistObserver(CollectionList::instance()), m_parent(parent) { } virtual void updateData() { if(m_parent && m_parent->m_currentPlaylist && m_parent->isVisible()) m_parent->slotSetItems(m_parent->m_currentPlaylist->selectedItems()); } virtual void updateCurrent() {} private: TagEditor *m_parent; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// TagEditor::TagEditor(QWidget *parent) : QWidget(parent), m_currentPlaylist(0), m_observer(0), m_performingSave(false) { setupActions(); setupLayout(); readConfig(); m_dataChanged = false; m_collectionChanged = false; setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } TagEditor::~TagEditor() { delete m_observer; saveConfig(); } void TagEditor::setupObservers() { m_observer = new CollectionObserver(this); } //////////////////////////////////////////////////////////////////////////////// // public slots //////////////////////////////////////////////////////////////////////////////// void TagEditor::slotSetItems(const PlaylistItemList &list) { if(m_performingSave) return; // Store the playlist that we're setting because saveChangesPrompt // can delete the PlaylistItems in list. Playlist *itemPlaylist = 0; if(!list.isEmpty()) itemPlaylist = list.first()->playlist(); bool hadPlaylist = m_currentPlaylist != 0; saveChangesPrompt(); if(m_currentPlaylist) { disconnect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem*)), this, SLOT(slotItemRemoved(PlaylistItem*))); } if((hadPlaylist && !m_currentPlaylist) || !itemPlaylist) { m_currentPlaylist = 0; m_items.clear(); } else { m_currentPlaylist = itemPlaylist; // We can't use list here, it may not be valid m_items = itemPlaylist->selectedItems(); } if(m_currentPlaylist) { connect(m_currentPlaylist, SIGNAL(signalAboutToRemove(PlaylistItem*)), this, SLOT(slotItemRemoved(PlaylistItem*))); connect(m_currentPlaylist, SIGNAL(destroyed()), this, SLOT(slotPlaylistRemoved())); } if(isVisible()) slotRefresh(); else m_collectionChanged = true; } void TagEditor::slotRefresh() { // This method takes the list of currently selected m_items and tries to // figure out how to show that in the tag editor. The current strategy -- // the most common case -- is to just process the first item. Then we // check after that to see if there are other m_items and adjust accordingly. if(m_items.isEmpty() || !m_items.first()->file().tag()) { slotClear(); setEnabled(false); return; } setEnabled(true); PlaylistItem *item = m_items.first(); Q_ASSERT(item); Tag *tag = item->file().tag(); QFileInfo fi(item->file().absFilePath()); if(!fi.isWritable() && m_items.count() == 1) setEnabled(false); artistNameBox->setEditText(tag->artist()); trackNameBox->setText(tag->title()); albumNameBox->setEditText(tag->album()); fileNameBox->setText(item->file().fileInfo().fileName()); fileNameBox->setToolTip(item->file().absFilePath()); bitrateBox->setText(QString::number(tag->bitrate())); lengthBox->setText(tag->lengthString()); if(m_genreList.indexOf(tag->genre()) >= 0) genreBox->setCurrentIndex(m_genreList.indexOf(tag->genre()) + 1); else { genreBox->setCurrentIndex(0); genreBox->setEditText(tag->genre()); } trackSpin->setValue(tag->track()); yearSpin->setValue(tag->year()); commentBox->setPlainText(tag->comment()); // Start at the second item, since we've already processed the first. PlaylistItemList::Iterator it = m_items.begin(); ++it; // If there is more than one item in the m_items that we're dealing with... QList disabledForMulti; disabledForMulti << fileNameLabel << fileNameBox << lengthLabel << lengthBox << bitrateLabel << bitrateBox; foreach(QWidget *w, disabledForMulti) { w->setDisabled(m_items.size() > 1); if(m_items.size() > 1 && !w->inherits("QLabel")) QMetaObject::invokeMethod(w, "clear"); } if(it != m_items.end()) { foreach(QCheckBox *box, m_enableBoxes) { box->setChecked(true); box->show(); } // Yep, this is ugly. Loop through all of the files checking to see // if their fields are the same. If so, by default, enable their // checkbox. // Also, if there are more than 50 m_items, don't scan all of them. if(m_items.count() > 50) { m_enableBoxes[artistNameBox]->setChecked(false); m_enableBoxes[trackNameBox]->setChecked(false); m_enableBoxes[albumNameBox]->setChecked(false); m_enableBoxes[genreBox]->setChecked(false); m_enableBoxes[trackSpin]->setChecked(false); m_enableBoxes[yearSpin]->setChecked(false); m_enableBoxes[commentBox]->setChecked(false); } else { for(; it != m_items.end(); ++it) { tag = (*it)->file().tag(); if(tag) { if(artistNameBox->currentText() != tag->artist() && m_enableBoxes.contains(artistNameBox)) { artistNameBox->lineEdit()->clear(); m_enableBoxes[artistNameBox]->setChecked(false); } if(trackNameBox->text() != tag->title() && m_enableBoxes.contains(trackNameBox)) { trackNameBox->clear(); m_enableBoxes[trackNameBox]->setChecked(false); } if(albumNameBox->currentText() != tag->album() && m_enableBoxes.contains(albumNameBox)) { albumNameBox->lineEdit()->clear(); m_enableBoxes[albumNameBox]->setChecked(false); } if(genreBox->currentText() != tag->genre() && m_enableBoxes.contains(genreBox)) { genreBox->lineEdit()->clear(); m_enableBoxes[genreBox]->setChecked(false); } if(trackSpin->value() != tag->track() && m_enableBoxes.contains(trackSpin)) { trackSpin->setValue(0); m_enableBoxes[trackSpin]->setChecked(false); } if(yearSpin->value() != tag->year() && m_enableBoxes.contains(yearSpin)) { yearSpin->setValue(0); m_enableBoxes[yearSpin]->setChecked(false); } if(commentBox->toPlainText() != tag->comment() && m_enableBoxes.contains(commentBox)) { commentBox->clear(); m_enableBoxes[commentBox]->setChecked(false); } } } } } else { foreach(QCheckBox *box, m_enableBoxes) { box->setChecked(true); box->hide(); } } m_dataChanged = false; } void TagEditor::slotClear() { artistNameBox->lineEdit()->clear(); trackNameBox->clear(); albumNameBox->lineEdit()->clear(); genreBox->setCurrentIndex(0); fileNameBox->clear(); fileNameBox->setToolTip(QString()); trackSpin->setValue(0); yearSpin->setValue(0); lengthBox->clear(); bitrateBox->clear(); commentBox->clear(); } void TagEditor::slotUpdateCollection() { if(isVisible()) updateCollection(); else m_collectionChanged = true; } void TagEditor::updateCollection() { m_collectionChanged = false; CollectionList *list = CollectionList::instance(); if(!list) return; QStringList artistList = list->uniqueSet(CollectionList::Artists); artistList.sort(); artistNameBox->clear(); artistNameBox->addItems(artistList); artistNameBox->completionObject()->setItems(artistList); QStringList albumList = list->uniqueSet(CollectionList::Albums); albumList.sort(); albumNameBox->clear(); albumNameBox->addItems(albumList); albumNameBox->completionObject()->setItems(albumList); // Merge the list of genres found in tags with the standard ID3v1 set. StringHash genreHash; m_genreList = list->uniqueSet(CollectionList::Genres); foreach(const QString &genre, m_genreList) genreHash.insert(genre); TagLib::StringList genres = TagLib::ID3v1::genreList(); for(TagLib::StringList::Iterator it = genres.begin(); it != genres.end(); ++it) genreHash.insert(TStringToQString((*it))); m_genreList = genreHash.values(); m_genreList.sort(); genreBox->clear(); genreBox->addItem(QString()); genreBox->addItems(m_genreList); genreBox->completionObject()->setItems(m_genreList); // We've cleared out the original entries of these list boxes, re-read // the current item if one is selected. slotRefresh(); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// void TagEditor::readConfig() { // combo box completion modes - KConfigGroup config(KGlobal::config(), "TagEditor"); + KConfigGroup config(KSharedConfig::openConfig(), "TagEditor"); if(artistNameBox && albumNameBox) { readCompletionMode(config, artistNameBox, "ArtistNameBoxMode"); readCompletionMode(config, albumNameBox, "AlbumNameBoxMode"); readCompletionMode(config, genreBox, "GenreBoxMode"); } bool show = config.readEntry("Show", false); ActionCollection::action("showEditor")->setChecked(show); setVisible(show); TagLib::StringList genres = TagLib::ID3v1::genreList(); for(TagLib::StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it) m_genreList.append(TStringToQString((*it))); m_genreList.sort(); genreBox->clear(); genreBox->addItem(QString()); genreBox->addItems(m_genreList); genreBox->completionObject()->setItems(m_genreList); } void TagEditor::readCompletionMode(const KConfigGroup &config, KComboBox *box, const QString &key) { KGlobalSettings::Completion mode = KGlobalSettings::Completion(config.readEntry(key, (int)KGlobalSettings::CompletionAuto)); // FIXME tag completion //box->setCompletionMode(mode); } void TagEditor::saveConfig() { // combo box completion modes - KConfigGroup config(KGlobal::config(), "TagEditor"); + KConfigGroup config(KSharedConfig::openConfig(), "TagEditor"); if(artistNameBox && albumNameBox) { config.writeEntry("ArtistNameBoxMode", (int)artistNameBox->completionMode()); config.writeEntry("AlbumNameBoxMode", (int)albumNameBox->completionMode()); config.writeEntry("GenreBoxMode", (int)genreBox->completionMode()); } config.writeEntry("Show", ActionCollection::action("showEditor")->isChecked()); } void TagEditor::setupActions() { KToggleAction *show = new KToggleAction(KIcon(QLatin1String("document-properties")), i18n("Show &Tag Editor"), this); ActionCollection::actions()->addAction("showEditor", show); connect(show, SIGNAL(toggled(bool)), this, SLOT(setVisible(bool))); KAction *act = new KAction(KIcon(QLatin1String( "document-save")), i18n("&Save"), this); ActionCollection::actions()->addAction("saveItem", act); act->setShortcut(Qt::CTRL + Qt::Key_T); connect(act, SIGNAL(triggered(bool)), SLOT(slotSave())); } void TagEditor::setupLayout() { setupUi(this); foreach(QWidget *input, findChildren()) { if(input->inherits("QLineEdit") || input->inherits("QComboBox")) connect(input, SIGNAL(textChanged(QString)), this, SLOT(slotDataChanged())); if(input->inherits("QComboxBox")) connect(input, SIGNAL(activated(int)), this, SLOT(slotDataChanged())); if(input->inherits("QSpinBox")) connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotDataChanged())); if(input->inherits("QTextEdit")) connect(input, SIGNAL(textChanged()), this, SLOT(slotDataChanged())); } // Do some meta-programming to find the matching enable boxes foreach(QCheckBox *enable, findChildren(QRegExp("Enable"))) { enable->hide(); QRegExp re('^' + enable->objectName().replace("Enable", "") + "(Box|Spin)$"); QList targets = findChildren(re); Q_ASSERT(!targets.isEmpty()); m_enableBoxes[targets.front()] = enable; } // Make sure that the labels are as tall as the enable buttons so that the // layout doesn't jump around. foreach(QLabel *label, findChildren()) { if(m_enableBoxes.contains(label->buddy())) label->setMinimumHeight(m_enableBoxes[label->buddy()]->height()); } tagEditorLayout->setColumnMinimumWidth(1, 200); } void TagEditor::save(const PlaylistItemList &list) { if(!list.isEmpty() && m_dataChanged) { QApplication::setOverrideCursor(Qt::WaitCursor); m_dataChanged = false; m_performingSave = true; // The list variable can become corrupted if the playlist holding its // items dies, which is possible as we edit tags. So we need to copy // the end marker. PlaylistItemList::ConstIterator end = list.end(); for(PlaylistItemList::ConstIterator it = list.begin(); it != end; /* Deliberately missing */ ) { // Process items before we being modifying tags, as the dynamic // playlists will try to modify the file we edit if the tag changes // due to our alterations here. qApp->processEvents(QEventLoop::ExcludeUserInputEvents); PlaylistItem *item = *it; // The playlist can be deleted from under us if this is the last // item and we edit it so that it doesn't match the search, which // means we can't increment the iterator, so let's do it now. ++it; QString fileName = item->file().fileInfo().path() + QDir::separator() + fileNameBox->text(); if(list.count() > 1) fileName = item->file().fileInfo().absoluteFilePath(); Tag *tag = TagTransactionManager::duplicateTag(item->file().tag(), fileName); // A bit more ugliness. If there are multiple files that are // being modified, they each have a "enabled" checkbox that // says if that field is to be respected for the multiple // files. We have to check to see if that is enabled before // each field that we write. if(m_enableBoxes[artistNameBox]->isChecked()) tag->setArtist(artistNameBox->currentText()); if(m_enableBoxes[trackNameBox]->isChecked()) tag->setTitle(trackNameBox->text()); if(m_enableBoxes[albumNameBox]->isChecked()) tag->setAlbum(albumNameBox->currentText()); if(m_enableBoxes[trackSpin]->isChecked()) { if(trackSpin->text().isEmpty()) trackSpin->setValue(0); tag->setTrack(trackSpin->value()); } if(m_enableBoxes[yearSpin]->isChecked()) { if(yearSpin->text().isEmpty()) yearSpin->setValue(0); tag->setYear(yearSpin->value()); } if(m_enableBoxes[commentBox]->isChecked()) tag->setComment(commentBox->toPlainText()); if(m_enableBoxes[genreBox]->isChecked()) tag->setGenre(genreBox->currentText()); TagTransactionManager::instance()->changeTagOnItem(item, tag); } TagTransactionManager::instance()->commit(); CollectionList::instance()->dataChanged(); m_performingSave = false; QApplication::restoreOverrideCursor(); } } void TagEditor::saveChangesPrompt() { if(!isVisible() || !m_dataChanged || m_items.isEmpty()) return; QStringList files; foreach(const PlaylistItem *item, m_items) files.append(item->file().absFilePath()); if(KMessageBox::questionYesNoList(this, i18n("Do you want to save your changes to:\n"), files, i18n("Save Changes"), KStandardGuiItem::save(), KStandardGuiItem::discard(), "tagEditor_showSaveChangesBox") == KMessageBox::Yes) { save(m_items); } } void TagEditor::showEvent(QShowEvent *e) { if(m_collectionChanged) { updateCollection(); } QWidget::showEvent(e); } bool TagEditor::eventFilter(QObject *watched, QEvent *e) { QKeyEvent *ke = static_cast(e); if(watched->inherits("QSpinBox") && e->type() == QEvent::KeyRelease && ke->modifiers() == 0) slotDataChanged(); return false; } //////////////////////////////////////////////////////////////////////////////// // private slots //////////////////////////////////////////////////////////////////////////////// void TagEditor::slotDataChanged(bool c) { m_dataChanged = c; } void TagEditor::slotItemRemoved(PlaylistItem *item) { m_items.removeAll(item); if(m_items.isEmpty()) slotRefresh(); } void TagEditor::slotPlaylistDestroyed(Playlist *p) { if(m_currentPlaylist == p) { m_currentPlaylist = 0; slotSetItems(PlaylistItemList()); } } // vim: set et sw=4 tw=0 sta: diff --git a/tagguesser.cpp b/tagguesser.cpp index 4412e199..3f726ad3 100644 --- a/tagguesser.cpp +++ b/tagguesser.cpp @@ -1,230 +1,230 @@ /** * Copyright (C) 2003 Frerich Raabe * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "tagguesser.h" #include #include #include #include #include #include #include FileNameScheme::FileNameScheme(const QString &s) : m_regExp(), m_titleField(-1), m_artistField(-1), m_albumField(-1), m_trackField(-1), m_commentField(-1) { int fieldNumber = 1; int i = s.indexOf('%'); while (i > -1) { switch (s[ i + 1 ].toLatin1()) { case 't': m_titleField = fieldNumber++; break; case 'a': m_artistField = fieldNumber++; break; case 'A': m_albumField = fieldNumber++; break; case 'T': m_trackField = fieldNumber++; break; case 'c': m_commentField = fieldNumber++; break; default: break; } i = s.indexOf('%', i + 1); } m_regExp.setPattern(composeRegExp(s)); } bool FileNameScheme::matches(const QString &fileName) const { /* Strip extension ('.mp3') because '.' may be part of a title, and thus * does not work as a separator. */ QString stripped = fileName; stripped.truncate(stripped.lastIndexOf('.')); return m_regExp.exactMatch(stripped); } QString FileNameScheme::title() const { if(m_titleField == -1) return QString(); return m_regExp.capturedTexts()[ m_titleField ]; } QString FileNameScheme::artist() const { if(m_artistField == -1) return QString(); return m_regExp.capturedTexts()[ m_artistField ]; } QString FileNameScheme::album() const { if(m_albumField == -1) return QString(); return m_regExp.capturedTexts()[ m_albumField ]; } QString FileNameScheme::track() const { if(m_trackField == -1) return QString(); return m_regExp.capturedTexts()[ m_trackField ]; } QString FileNameScheme::comment() const { if(m_commentField == -1) return QString(); return m_regExp.capturedTexts()[ m_commentField ]; } QString FileNameScheme::composeRegExp(const QString &s) const { QHash substitutions; - KConfigGroup config(KGlobal::config(), "TagGuesser"); + KConfigGroup config(KSharedConfig::openConfig(), "TagGuesser"); substitutions[ 't' ] = config.readEntry("Title regexp", "([\\w\\s'&_,\\.]+)"); substitutions[ 'a' ] = config.readEntry("Artist regexp", "([\\w\\s'&_,\\.]+)"); substitutions[ 'A' ] = config.readEntry("Album regexp", "([\\w\\s'&_,\\.]+)"); substitutions[ 'T' ] = config.readEntry("Track regexp", "(\\d+)"); substitutions[ 'c' ] = config.readEntry("Comment regexp", "([\\w\\s_]+)"); QString regExp = QRegExp::escape(s.simplified()); regExp = ".*" + regExp; regExp.replace(' ', "\\s+"); regExp = KMacroExpander::expandMacros(regExp, substitutions); regExp += "[^/]*$"; return regExp; } QStringList TagGuesser::schemeStrings() { QStringList schemes; - KConfigGroup config(KGlobal::config(), "TagGuesser"); + KConfigGroup config(KSharedConfig::openConfig(), "TagGuesser"); schemes = config.readEntry("Filename schemes", QStringList()); if ( schemes.isEmpty() ) { schemes += "%a - (%T) - %t [%c]"; schemes += "%a - (%T) - %t (%c)"; schemes += "%a - (%T) - %t"; schemes += "%a - [%T] - %t [%c]"; schemes += "%a - [%T] - %t (%c)"; schemes += "%a - [%T] - %t"; schemes += "%a - %T - %t [%c]"; schemes += "%a - %T - %t (%c)"; schemes += "%a - %T - %t"; schemes += "(%T) %a - %t [%c]"; schemes += "(%T) %a - %t (%c)"; schemes += "(%T) %a - %t"; schemes += "[%T] %a - %t [%c]"; schemes += "[%T] %a - %t (%c)"; schemes += "[%T] %a - %t"; schemes += "%T %a - %t [%c]"; schemes += "%T %a - %t (%c)"; schemes += "%T %a - %t"; schemes += "(%a) %t [%c]"; schemes += "(%a) %t (%c)"; schemes += "(%a) %t"; schemes += "%a - %t [%c]"; schemes += "%a - %t (%c)"; schemes += "%a - %t"; schemes += "%a/%A/[%T] %t [%c]"; schemes += "%a/%A/[%T] %t (%c)"; schemes += "%a/%A/[%T] %t"; } return schemes; } void TagGuesser::setSchemeStrings(const QStringList &schemes) { - KSharedConfig::Ptr cfg = KGlobal::config(); + KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); KConfigGroup group(cfg, "TagGuesser"); group.writeEntry("Filename schemes", schemes); cfg->sync(); } TagGuesser::TagGuesser() { loadSchemes(); } TagGuesser::TagGuesser(const QString &absFileName) { loadSchemes(); guess(absFileName); } void TagGuesser::loadSchemes() { const QStringList schemes = schemeStrings(); QStringList::ConstIterator it = schemes.begin(); QStringList::ConstIterator end = schemes.end(); for ( ; it != end; ++it ) m_schemes += FileNameScheme( *it ); } void TagGuesser::guess(const QString &absFileName) { m_title.clear(); m_artist.clear(); m_album.clear(); m_track.clear(); m_comment.clear(); FileNameScheme::List::ConstIterator it = m_schemes.constBegin(); FileNameScheme::List::ConstIterator end = m_schemes.constEnd(); for (; it != end; ++it) { const FileNameScheme schema(*it); if(schema.matches(absFileName)) { m_title = capitalizeWords(schema.title().replace('_', " ")).trimmed(); m_artist = capitalizeWords(schema.artist().replace('_', " ")).trimmed(); m_album = capitalizeWords(schema.album().replace('_', " ")).trimmed(); m_track = schema.track().trimmed(); m_comment = schema.comment().replace('_', " ").trimmed(); break; } } } QString TagGuesser::capitalizeWords(const QString &s) { if(s.isEmpty()) return s; QString result = s; result[ 0 ] = result[ 0 ].toUpper(); const QRegExp wordRegExp("\\s\\w"); int i = result.indexOf( wordRegExp ); while ( i > -1 ) { result[ i + 1 ] = result[ i + 1 ].toUpper(); i = result.indexOf( wordRegExp, ++i ); } return result; } // vim: set et sw=4 tw=0 sta: diff --git a/tagrenameroptions.cpp b/tagrenameroptions.cpp index eee67bf9..7c6385ea 100644 --- a/tagrenameroptions.cpp +++ b/tagrenameroptions.cpp @@ -1,184 +1,184 @@ /** * Copyright (C) 2004 Michael Pyne * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "tagrenameroptions.h" #include #include #include #include #include #include TagRenamerOptions::TagRenamerOptions() : m_emptyAction(IgnoreEmptyTag), m_trackWidth(0), m_disabled(true), m_category(TagUnknown) { } TagRenamerOptions::TagRenamerOptions(const TagRenamerOptions &other) : m_prefix(other.m_prefix), m_suffix(other.m_suffix), m_emptyAction(other.m_emptyAction), m_emptyText(other.m_emptyText), m_trackWidth(other.m_trackWidth), m_disabled(other.m_disabled), m_category(other.m_category) { } TagRenamerOptions::TagRenamerOptions(const CategoryID &category) : m_category(category.category) { // Set some defaults bool disabled; unsigned categoryNum = category.categoryNumber; switch(category.category) { case Title: case Artist: case Genre: case Year: case Album: case Track: disabled = false; break; default: disabled = true; } // Make sure we don't use translated strings for the config file keys. QString typeKey = tagTypeText(category.category, false); - KConfigGroup config(KGlobal::config(), "FileRenamer"); + KConfigGroup config(KSharedConfig::openConfig(), "FileRenamer"); if(categoryNum > 0) typeKey.append(QString::number(categoryNum)); setSuffix(config.readEntry(QString("%1Suffix").arg(typeKey), QString())); setPrefix(config.readEntry(QString("%1Prefix").arg(typeKey), QString())); // Default the emptyAction to ignoring the empty tag. const QString emptyAction = config.readEntry(QString("%1EmptyAction").arg(typeKey), QString()).toLower(); setEmptyAction(IgnoreEmptyTag); if(emptyAction == "forceemptyinclude") setEmptyAction(ForceEmptyInclude); else if(emptyAction == "usereplacementvalue") setEmptyAction(UseReplacementValue); setEmptyText(config.readEntry(QString("%1EmptyText").arg(typeKey), QString())); setTrackWidth(config.readEntry(QString("%1TrackWidth").arg(typeKey), 0)); setDisabled(config.readEntry(QString("%1Disabled").arg(typeKey), disabled)); } QString TagRenamerOptions::tagTypeText(TagType type, bool translate) { const char *msg = 0, *context = 0; switch(type) { case Title: msg = I18N_NOOP2("song title", "Title"); context = "song title"; break; case Artist: msg = I18N_NOOP("Artist"); break; case Album: msg = I18N_NOOP("Album"); break; case Track: msg = I18N_NOOP2("cd track number", "Track"); context = "cd track number"; break; case Genre: msg = I18N_NOOP("Genre"); break; case Year: msg = I18N_NOOP("Year"); break; default: kWarning() << "I don't know what category we're looking up, this is a problem."; kWarning() << "The category ID is " << (unsigned) type; msg = I18N_NOOP2("unknown renamer category", "Unknown"); context = "unknown renamer category"; } if(translate) return context ? i18nc(context, msg) : i18n(msg); else return msg; } void TagRenamerOptions::saveConfig(unsigned categoryNum) const { // Make sure we don't use translated strings for the config file keys. QString typeKey = tagTypeText(false); if(categoryNum > 0) typeKey.append(QString::number(categoryNum)); - KConfigGroup config(KGlobal::config(), "FileRenamer"); + KConfigGroup config(KSharedConfig::openConfig(), "FileRenamer"); config.writeEntry(QString("%1Suffix").arg(typeKey), suffix()); config.writeEntry(QString("%1Prefix").arg(typeKey), prefix()); QString emptyStr; switch(emptyAction()) { case ForceEmptyInclude: emptyStr = "ForceEmptyInclude"; break; case IgnoreEmptyTag: emptyStr = "IgnoreEmptyTag"; break; case UseReplacementValue: emptyStr = "UseReplacementValue"; break; } config.writeEntry(QString("%1EmptyAction").arg(typeKey), emptyStr); config.writeEntry(QString("%1EmptyText").arg(typeKey), emptyText()); config.writeEntry(QString("%1Disabled").arg(typeKey), disabled()); if(category() == Track) config.writeEntry(QString("%1TrackWidth").arg(typeKey), trackWidth()); config.sync(); } TagType TagRenamerOptions::tagFromCategoryText(const QString &text) { for(unsigned i = StartTag; i < NumTypes; ++i) if(tagTypeText(static_cast(i), false) == text) return static_cast(i); return TagUnknown; } // vim: set et sw=4 tw=0 sta: