/* This file is part of Clementine. Copyright 2010, David Sansome Clementine 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 3 of the License, or (at your option) any later version. Clementine 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 Clementine. If not, see . */ #include "playlist.h" #include "playlistlistcontainer.h" #include "playlistlistmodel.h" #include "playlistmanager.h" #include "ui_playlistlistcontainer.h" #include "core/application.h" #include "core/logging.h" #include "core/player.h" #include "ui/iconloader.h" #include #include #include #include #include #include #include class PlaylistListSortFilterModel : public QSortFilterProxyModel { public: explicit PlaylistListSortFilterModel(QObject* parent) : QSortFilterProxyModel(parent) { } bool lessThan(const QModelIndex& left, const QModelIndex& right) const { // Compare the display text first. const int ret = left.data().toString().localeAwareCompare( right.data().toString()); if (ret < 0) return true; if (ret > 0) return false; // Now use the source model row order to ensure we always get a // deterministic sorting even when two items are named the same. return left.row() < right.row(); } }; PlaylistListContainer::PlaylistListContainer(QWidget* parent) : QWidget(parent), app_(NULL), ui_(new Ui_PlaylistListContainer), menu_(NULL), action_new_folder_(new QAction(this)), action_new_playlist_(NULL), action_remove_(new QAction(this)), action_load_playlist_(NULL), action_save_playlist_(NULL), model_(new PlaylistListModel(this)), proxy_(new PlaylistListSortFilterModel(this)), loaded_icons_(false), active_playlist_id_(-1) { ui_->setupUi(this); ui_->tree->setAttribute(Qt::WA_MacShowFocusRect, false); action_new_folder_->setText(tr("New folder")); action_remove_->setText(tr("Delete")); ui_->new_folder->setDefaultAction(action_new_folder_); ui_->remove->setDefaultAction(action_remove_); connect(action_new_folder_, SIGNAL(triggered()), SLOT(NewFolderClicked())); connect(action_remove_, SIGNAL(triggered()), SLOT(DeleteClicked())); connect(model_, SIGNAL(PlaylistPathChanged(int,QString)), SLOT(PlaylistPathChanged(int,QString))); proxy_->setSourceModel(model_); proxy_->setDynamicSortFilter(true); proxy_->sort(0); ui_->tree->setModel(proxy_); connect(ui_->tree->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(ViewIndexSelected(QModelIndex))); model_->invisibleRootItem()->setData(PlaylistListModel::Type_Folder, PlaylistListModel::Role_Type); } PlaylistListContainer::~PlaylistListContainer() { delete ui_; } void PlaylistListContainer::showEvent(QShowEvent* e) { // Loading icons is expensive so only do it when the view is first opened if (loaded_icons_) { return; } loaded_icons_ = true; action_new_folder_->setIcon(IconLoader::Load("folder-new")); action_remove_->setIcon(IconLoader::Load("edit-delete")); model_->SetIcons(IconLoader::Load("view-media-playlist"), IconLoader::Load("folder")); // Apply these icons to items that have already been created. RecursivelySetIcons(model_->invisibleRootItem()); QWidget::showEvent(e); } void PlaylistListContainer::RecursivelySetIcons(QStandardItem* parent) const { for (int i=0 ; irowCount() ; ++i) { QStandardItem* child = parent->child(i); switch (child->data(PlaylistListModel::Role_Type).toInt()) { case PlaylistListModel::Type_Folder: child->setIcon(model_->folder_icon()); RecursivelySetIcons(child); break; case PlaylistListModel::Type_Playlist: child->setIcon(model_->playlist_icon()); break; } } } void PlaylistListContainer::SetApplication(Application* app) { app_ = app; PlaylistManager* manager = app_->playlist_manager(); Player* player = app_->player(); connect(manager, SIGNAL(PlaylistAdded(int,QString)), SLOT(PlaylistAdded(int,QString))); connect(manager, SIGNAL(PlaylistDeleted(int)), SLOT(PlaylistDeleted(int))); connect(manager, SIGNAL(PlaylistRenamed(int,QString)), SLOT(PlaylistRenamed(int,QString))); connect(manager, SIGNAL(CurrentChanged(Playlist*)), SLOT(CurrentChanged(Playlist*))); connect(manager, SIGNAL(ActiveChanged(Playlist*)), SLOT(ActiveChanged(Playlist*))); connect(model_, SIGNAL(PlaylistRenamed(int,QString)), manager, SLOT(Rename(int,QString))); connect(player, SIGNAL(Paused()), SLOT(ActivePaused())); connect(player, SIGNAL(Playing()), SLOT(ActivePlaying())); connect(player, SIGNAL(Stopped()), SLOT(ActiveStopped())); // Get all playlists, even ones that are hidden in the UI. foreach (const PlaylistBackend::Playlist& p, app->playlist_backend()->GetAllPlaylists()) { QStandardItem* playlist_item = model_->NewPlaylist(p.name, p.id); QStandardItem* parent_folder = model_->FolderByPath(p.ui_path); parent_folder->appendRow(playlist_item); } } void PlaylistListContainer::SetActions(QAction* new_playlist, QAction* load_playlist, QAction* save_playlist) { // Set the actions on the buttons in the toolbar. ui_->new_playlist->setDefaultAction(new_playlist); ui_->load_playlist->setDefaultAction(load_playlist); ui_->save_playlist->setDefaultAction(save_playlist); action_new_playlist_ = new_playlist; action_load_playlist_ = load_playlist; action_save_playlist_ = save_playlist; } void PlaylistListContainer::NewFolderClicked() { QString name = QInputDialog::getText(this, tr("New folder"), tr("Enter the name of the folder")); if (name.isEmpty()) { return; } name.replace("/", " "); model_->invisibleRootItem()->appendRow(model_->NewFolder(name)); } void PlaylistListContainer::PlaylistAdded(int id, const QString& name) { if (model_->PlaylistById(id)) { // We know about this playlist already - it was probably one of the open // ones that was loaded on startup. return; } const QString& ui_path = app_->playlist_manager()->playlist(id)->ui_path(); QStandardItem* playlist_item = model_->NewPlaylist(name, id); QStandardItem* parent_folder = model_->FolderByPath(ui_path); parent_folder->appendRow(playlist_item); } void PlaylistListContainer::PlaylistRenamed(int id, const QString& new_name) { QStandardItem* item = model_->PlaylistById(id); if (!item) { return; } item->setText(new_name); } void PlaylistListContainer::PlaylistDeleted(int id) { QStandardItem* item = model_->PlaylistById(id); if (item) { QStandardItem* parent = item->parent(); if (!parent) { parent = model_->invisibleRootItem(); } parent->removeRow(item->row()); } } void PlaylistListContainer::ActiveChanged(Playlist* new_playlist) { const int new_id = new_playlist->id(); if (new_id != active_playlist_id_) { UpdateActiveIcon(active_playlist_id_, QIcon()); } active_playlist_id_ = new_id; } void PlaylistListContainer::CurrentChanged(Playlist* new_playlist) { if (!new_playlist) { return; } // Focus this playlist in the tree QStandardItem* item = model_->PlaylistById(new_playlist->id()); if (!item) { return; } QModelIndex index = proxy_->mapFromSource(item->index()); ui_->tree->selectionModel()->setCurrentIndex( index, QItemSelectionModel::ClearAndSelect); ui_->tree->scrollTo(index); } void PlaylistListContainer::PlaylistPathChanged(int id, const QString& new_path) { // Update the path in the database app_->playlist_backend()->SetPlaylistUiPath(id, new_path); } void PlaylistListContainer::ViewIndexSelected(const QModelIndex& proxy_index) { const QModelIndex& index = proxy_->mapToSource(proxy_index); // Is it a playlist? if (index.data(PlaylistListModel::Role_Type).toInt() == PlaylistListModel::Type_Playlist) { app_->playlist_manager()->SetCurrentOrOpen( index.data(PlaylistListModel::Role_PlaylistId).toInt()); } } void PlaylistListContainer::DeleteClicked() { QSet ids; QList folders_to_delete; foreach (const QModelIndex& proxy_index, ui_->tree->selectionModel()->selectedRows(0)) { const QModelIndex& index = proxy_->mapToSource(proxy_index); // Is it a playlist? switch (index.data(PlaylistListModel::Role_Type).toInt()) { case PlaylistListModel::Type_Playlist: ids << index.data(PlaylistListModel::Role_PlaylistId).toInt(); break; case PlaylistListModel::Type_Folder: // Find all the playlists inside. RecursivelyFindPlaylists(index, &ids); folders_to_delete << index; break; } } // Make sure the user really wants to delete all these playlists. if (ids.count() > 1) { const int button = QMessageBox::question(this, tr("Delete playlists"), tr("You are about to delete %1 playlists, are you sure?").arg(ids.count()), QMessageBox::Yes, QMessageBox::Cancel); if (button != QMessageBox::Yes) { return; } } // Delete the playlists foreach (int id, ids) { app_->playlist_manager()->Delete(id); } // Delete the top-level folders. foreach (const QPersistentModelIndex& index, folders_to_delete) { if (index.isValid()) { model_->removeRow(index.row(), index.parent()); } } } void PlaylistListContainer::RecursivelyFindPlaylists( const QModelIndex& parent, QSet* ids) const { switch (parent.data(PlaylistListModel::Role_Type).toInt()) { case PlaylistListModel::Type_Playlist: ids->insert(parent.data(PlaylistListModel::Role_PlaylistId).toInt()); break; case PlaylistListModel::Type_Folder: for (int i=0 ; irowCount(parent) ; ++i) { RecursivelyFindPlaylists(parent.child(i, 0), ids); } break; } } void PlaylistListContainer::contextMenuEvent(QContextMenuEvent* e) { if (!menu_) { menu_ = new QMenu(this); menu_->addAction(action_new_playlist_); menu_->addAction(action_new_folder_); menu_->addAction(action_remove_); menu_->addSeparator(); menu_->addAction(action_load_playlist_); menu_->addAction(action_save_playlist_); } menu_->popup(e->globalPos()); } void PlaylistListContainer::ActivePlaying() { if (padded_play_icon_.isNull()) { QPixmap pixmap(":tiny-start.png"); QPixmap new_pixmap(QSize(pixmap.height(), pixmap.height())); new_pixmap.fill(Qt::transparent); QPainter p(&new_pixmap); p.drawPixmap((new_pixmap.width() - pixmap.width()) / 2, 0, pixmap.width(), pixmap.height(), pixmap); p.end(); padded_play_icon_.addPixmap(new_pixmap); } UpdateActiveIcon(active_playlist_id_, padded_play_icon_); } void PlaylistListContainer::ActivePaused() { UpdateActiveIcon(active_playlist_id_, QIcon(":tiny-pause.png")); } void PlaylistListContainer::ActiveStopped() { UpdateActiveIcon(active_playlist_id_, QIcon()); } void PlaylistListContainer::UpdateActiveIcon(int id, const QIcon& icon) { if (id == -1) { return; } QStandardItem* item = model_->PlaylistById(id); if (!item) { return; } if (icon.isNull()) { item->setIcon(model_->playlist_icon()); } else { item->setIcon(icon); } }