/* * Cantata * * Copyright (c) 2011-2017 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "playlistspage.h" #include "models/playlistsmodel.h" #include "mpd-interface/mpdconnection.h" #include "support/messagebox.h" #include "support/inputdialog.h" #include "widgets/icons.h" #include "stdactions.h" #include "customactions.h" #include "support/actioncollection.h" #include "support/configuration.h" #include "widgets/tableview.h" #include "widgets/spacerwidget.h" #include "widgets/menubutton.h" #include "dynamic/dynamic.h" #include "dynamic/dynamicpage.h" #include "settings.h" #include #include class PlaylistTableView : public TableView { public: PlaylistTableView(QWidget *p) : TableView(QLatin1String("playlist"), p) { setUseSimpleDelegate(); setIndentation(fontMetrics().width(QLatin1String("XX"))); } virtual ~PlaylistTableView() { } }; StoredPlaylistsPage::StoredPlaylistsPage(QWidget *p) : SinglePageWidget(p) { renamePlaylistAction = new Action(Icons::self()->editIcon, tr("Rename"), this); removeDuplicatesAction=new Action(tr("Remove Duplicates"), this); removeDuplicatesAction->setEnabled(false); view->allowGroupedView(); view->allowTableView(new PlaylistTableView(view)); view->setAcceptDrops(true); view->setDragDropOverwriteMode(false); view->setDragDropMode(QAbstractItemView::DragDrop); proxy.setSourceModel(PlaylistsModel::self()); view->setModel(&proxy); view->setDeleteAction(StdActions::self()->removeAction); view->alwaysShowHeader(); connect(view, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(itemDoubleClicked(const QModelIndex &))); connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions())); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(this, SIGNAL(loadPlaylist(const QString &, bool)), MPDConnection::self(), SLOT(loadPlaylist(const QString &, bool))); connect(this, SIGNAL(removePlaylist(const QString &)), MPDConnection::self(), SLOT(removePlaylist(const QString &))); connect(this, SIGNAL(savePlaylist(const QString &)), MPDConnection::self(), SLOT(savePlaylist(const QString &))); connect(this, SIGNAL(renamePlaylist(const QString &, const QString &)), MPDConnection::self(), SLOT(renamePlaylist(const QString &, const QString &))); connect(this, SIGNAL(removeFromPlaylist(const QString &, const QList &)), MPDConnection::self(), SLOT(removeFromPlaylist(const QString &, const QList &))); connect(StdActions::self()->savePlayQueueAction, SIGNAL(triggered()), this, SLOT(savePlaylist())); connect(renamePlaylistAction, SIGNAL(triggered()), this, SLOT(renamePlaylist())); connect(removeDuplicatesAction, SIGNAL(triggered()), this, SLOT(removeDuplicates())); connect(PlaylistsModel::self(), SIGNAL(updated(const QModelIndex &)), this, SLOT(updated(const QModelIndex &))); connect(PlaylistsModel::self(), SIGNAL(playlistRemoved(quint32)), view, SLOT(collectionRemoved(quint32))); intitiallyCollapseAction=new Action(tr("Initially Collapse Albums"), this); intitiallyCollapseAction->setCheckable(true); Configuration config(metaObject()->className()); view->setMode(ItemView::Mode_DetailedTree); view->load(config); intitiallyCollapseAction->setChecked(view->isStartClosed()); connect(intitiallyCollapseAction, SIGNAL(toggled(bool)), SLOT(setStartClosed(bool))); MenuButton *menu=new MenuButton(this); menu->addAction(createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List << ItemView::Mode_GroupedTree << ItemView::Mode_Table)); menu->addAction(intitiallyCollapseAction); init(ReplacePlayQueue|AppendToPlayQueue, QList() << menu); view->addAction(StdActions::self()->addToStoredPlaylistAction); view->addAction(CustomActions::self()); #ifdef ENABLE_DEVICES_SUPPORT view->addAction(StdActions::self()->copyToDeviceAction); #endif view->addAction(renamePlaylistAction); view->addAction(StdActions::self()->removeAction); view->addAction(removeDuplicatesAction); connect(view, SIGNAL(updateToPlayQueue(QModelIndex,bool)), this, SLOT(updateToPlayQueue(QModelIndex,bool))); } StoredPlaylistsPage::~StoredPlaylistsPage() { Configuration config(metaObject()->className()); view->save(config); } void StoredPlaylistsPage::updateRows() { view->updateRows(); } void StoredPlaylistsPage::clear() { PlaylistsModel::self()->clear(); } //QStringList StoredPlaylistsPage::selectedFiles() const //{ // QModelIndexList indexes=view->selectedIndexes(); // if (indexes.isEmpty()) { // return QStringList(); // } // // QModelIndexList mapped; // foreach (const QModelIndex &idx, indexes) { // mapped.append(proxy.mapToSource(idx)); // } // // return PlaylistsModel::self()->filenames(mapped, true); //} void StoredPlaylistsPage::addSelectionToPlaylist(const QString &name, int action, quint8 priorty) { addItemsToPlayList(view->selectedIndexes(), name, action, priorty); } void StoredPlaylistsPage::setView(int mode) { PlaylistsModel::self()->setMultiColumn(ItemView::Mode_Table==mode); SinglePageWidget::setView(mode); intitiallyCollapseAction->setEnabled(ItemView::Mode_GroupedTree==mode); } void StoredPlaylistsPage::removeItems() { QSet remPlaylists; QMap > remSongs; QModelIndexList selected = view->selectedIndexes(); foreach(const QModelIndex &index, selected) { if(index.isValid()) { QModelIndex realIndex(proxy.mapToSource(index)); if(realIndex.isValid()) { PlaylistsModel::Item *item=static_cast(realIndex.internalPointer()); if (item->isPlaylist()) { PlaylistsModel::PlaylistItem *pl=static_cast(item); if (!pl->isSmartPlaylist) { remPlaylists.insert(pl->name); } if (remSongs.contains(pl->name)) { remSongs.remove(pl->name); } } else { PlaylistsModel::SongItem *song=static_cast(item); if (!song->parent->isSmartPlaylist && !remPlaylists.contains(song->parent->name)) { remSongs[song->parent->name].append(song->parent->songs.indexOf(song)); } } } } } // Should not happen! if (remPlaylists.isEmpty() && remSongs.isEmpty()) { return; } if (remPlaylists.count() && MessageBox::No==MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the selected playlists?\n\nThis cannot be undone."), tr("Remove Playlists"), StdGuiItem::remove(), StdGuiItem::cancel())) { return; } foreach (const QString &pl, remPlaylists) { emit removePlaylist(pl); } QMap >::ConstIterator it=remSongs.constBegin(); QMap >::ConstIterator end=remSongs.constEnd(); for (; it!=end; ++it) { emit removeFromPlaylist(it.key(), it.value()); } } void StoredPlaylistsPage::savePlaylist() { QString name = InputDialog::getText(tr("Playlist Name"), tr("Enter a name for the playlist:"), QString(), 0, this); if (!name.isEmpty()) { if (PlaylistsModel::self()->exists(name)) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("A playlist named '%1' already exists!\n\nOverwrite?").arg(name), tr("Overwrite Playlist"), StdGuiItem::overwrite(), StdGuiItem::cancel())) { return; } else { emit removePlaylist(name); } } emit savePlaylist(name); } } void StoredPlaylistsPage::renamePlaylist() { const QModelIndexList items = view->selectedIndexes(false); // Dont need sorted selection here... if (1==items.size()) { QModelIndex index = proxy.mapToSource(items.first()); PlaylistsModel::Item *item=static_cast(index.internalPointer()); if (!item->isPlaylist()) { return; } QString name = static_cast(item)->name; QString newName = InputDialog::getText(tr("Rename Playlist"), tr("Enter new name for playlist:"), name, 0, this); if (!newName.isEmpty() && name!=newName) { if (PlaylistsModel::self()->exists(newName)) { if (MessageBox::No==MessageBox::warningYesNo(this, tr("A playlist named '%1' already exists!\n\nOverwrite?").arg(newName), tr("Overwrite Playlist"), StdGuiItem::overwrite(), StdGuiItem::cancel())) { return; } else { emit removePlaylist(newName); } } emit renamePlaylist(name, newName); } } } void StoredPlaylistsPage::removeDuplicates() { const QModelIndexList items = view->selectedIndexes(false); // Dont need sorted selection here... if (1==items.size()) { QModelIndex index = proxy.mapToSource(items.first()); PlaylistsModel::Item *item=static_cast(index.internalPointer()); if (!item->isPlaylist()) { return; } PlaylistsModel::PlaylistItem *pl=static_cast(item); QMap > map; for (int i=0; isongs.count(); ++i) { Song song=*(pl->songs.at(i)); song.id=i; map[song.albumKey()+"-"+song.artistSong()+"-"+song.track].append(song); } QList toRemove; foreach (const QString &key, map.keys()) { QList values=map.value(key); if (values.size()>1) { Song::sortViaType(values); for (int i=1; iname, toRemove); } } } void StoredPlaylistsPage::itemDoubleClicked(const QModelIndex &index) { if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this) || !static_cast(proxy.mapToSource(index).internalPointer())->isPlaylist()) { QModelIndexList indexes; indexes.append(index); addItemsToPlayList(indexes, QString(), MPDConnection::Append); } } void StoredPlaylistsPage::addItemsToPlayList(const QModelIndexList &indexes, const QString &name, int action, quint8 priorty) { if (indexes.isEmpty()) { return; } // If we only have 1 item selected, see if it is a playlist. If so, we might be able to // just ask MPD to load it... if (name.isEmpty() && 1==indexes.count() && 0==priorty && !proxy.enabled() && MPDConnection::Append==action) { QModelIndex idx=proxy.mapToSource(*(indexes.begin())); PlaylistsModel::Item *item=static_cast(idx.internalPointer()); if (item->isPlaylist()) { emit loadPlaylist(static_cast(item)->name, false); return; } } if (!name.isEmpty()) { foreach (const QModelIndex &idx, indexes) { QModelIndex m=proxy.mapToSource(idx); PlaylistsModel::Item *item=static_cast(m.internalPointer()); if ( (item->isPlaylist() && static_cast(item)->name==name) || (!item->isPlaylist() && static_cast(item)->parent->name==name) ) { MessageBox::error(this, tr("Cannot add songs from '%1' to '%2'").arg(name).arg(name)); return; } } } QStringList files=PlaylistsModel::self()->filenames(proxy.mapToSource(indexes)); if (!files.isEmpty()) { if (name.isEmpty()) { emit add(files, action, priorty); } else { emit addSongsToPlaylist(name, files); } view->clearSelection(); } } #ifdef ENABLE_DEVICES_SUPPORT QList StoredPlaylistsPage::selectedSongs(bool allowPlaylists) const { Q_UNUSED(allowPlaylists) QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return PlaylistsModel::self()->songs(proxy.mapToSource(selected)); } void StoredPlaylistsPage::addSelectionToDevice(const QString &udi) { QList songs=selectedSongs(); if (!songs.isEmpty()) { emit addToDevice(QString(), udi, songs); view->clearSelection(); } } #endif void StoredPlaylistsPage::controlActions() { QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... bool enableActions=selected.count()>0; bool allSmartPlaylists=false; bool canRename=false; if (1==selected.count()) { QModelIndex index = proxy.mapToSource(selected.first()); PlaylistsModel::Item *item=static_cast(index.internalPointer()); if (item) { if (item->isPlaylist()) { if (static_cast(item)->isSmartPlaylist) { allSmartPlaylists=true; } else { canRename=true; } } else if (static_cast(item)->parent->isSmartPlaylist) { allSmartPlaylists=true; } } } else if (selected.count()<=200) { allSmartPlaylists=true; foreach (const QModelIndex &index, selected) { PlaylistsModel::Item *item=static_cast(proxy.mapToSource(index).internalPointer()); if (item && !(item->isPlaylist() ? static_cast(item)->isSmartPlaylist : static_cast(item)->parent->isSmartPlaylist)) { allSmartPlaylists=false; break; } } } renamePlaylistAction->setEnabled(canRename); removeDuplicatesAction->setEnabled(enableActions && !allSmartPlaylists); StdActions::self()->removeAction->setEnabled(enableActions && !allSmartPlaylists); StdActions::self()->enableAddToPlayQueue(enableActions); StdActions::self()->addToStoredPlaylistAction->setEnabled(enableActions); #ifdef ENABLE_DEVICES_SUPPORT StdActions::self()->copyToDeviceAction->setEnabled(enableActions && !allSmartPlaylists); #endif } void StoredPlaylistsPage::doSearch() { QString text=view->searchText().trimmed(); bool updated=proxy.update(text); if (proxy.enabled() && !proxy.filterText().isEmpty()) { view->expandAll(); } if(updated) { view->updateRows(); } } void StoredPlaylistsPage::updated(const QModelIndex &index) { view->updateRows(proxy.mapFromSource(index)); } void StoredPlaylistsPage::headerClicked(int level) { if (0==level) { emit close(); } } void StoredPlaylistsPage::setStartClosed(bool sc) { view->setStartClosed(sc); } void StoredPlaylistsPage::updateToPlayQueue(const QModelIndex &idx, bool replace) { QStringList files=PlaylistsModel::self()->filenames(proxy.mapToSource(QModelIndexList() << idx, false)); if (!files.isEmpty()) { emit add(files, replace ? MPDConnection::ReplaceAndplay : MPDConnection::Append, 0); } } PlaylistsPage::PlaylistsPage(QWidget *p) : MultiPageWidget(p) { stored=new StoredPlaylistsPage(this); addPage(PlaylistsModel::self()->name(), PlaylistsModel::self()->icon(), PlaylistsModel::self()->title(), PlaylistsModel::self()->descr(), stored); dynamic=new DynamicPage(this); addPage(Dynamic::self()->name(), Dynamic::self()->icon(), Dynamic::self()->title(), Dynamic::self()->descr(), dynamic); connect(stored, SIGNAL(addToDevice(QString,QString,QList)), SIGNAL(addToDevice(QString,QString,QList))); Configuration config(metaObject()->className()); load(config); } PlaylistsPage::~PlaylistsPage() { Configuration config(metaObject()->className()); save(config); } #ifdef ENABLE_DEVICES_SUPPORT void PlaylistsPage::addSelectionToDevice(const QString &udi) { if (stored==currentWidget()) { stored->addSelectionToDevice(udi); } } #endif