diff --git a/CMakeLists.txt b/CMakeLists.txt index 3250d5105..c5320e5c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ set(DEBIAN_PACKAGE_DESCRIPTION "Qt4/KDE4 Client for MPD") set(DEBIAN_PACKAGE_SECTION "kde4") set(CPACK_SOURCE_GENERATOR "TBZ2") set(CPACK_PACKAGE_VERSION_MAJOR "0") -set(CPACK_PACKAGE_VERSION_MINOR "5") -set(CPACK_PACKAGE_VERSION_PATCH "1") +set(CPACK_PACKAGE_VERSION_MINOR "6") +set(CPACK_PACKAGE_VERSION_PATCH "0") set(CPACK_PACKAGE_CONTACT "Craig Drummond ") set(CANTATA_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") set(CANTATA_VERSION_FULL "${CANTATA_VERSION}.${CPACK_PACKAGE_VERSION_PATCH}") @@ -70,7 +70,9 @@ SET( CANTATA_SRCS widgets/timeslider.cpp widgets/actionlabel.cpp widgets/playqueueview.cpp + widgets/groupedview.cpp widgets/messagewidget.cpp + widgets/actionitemdelegate.cpp lyrics/lyricspage.cpp lyrics/lyricsettings.cpp lyrics/ultimatelyricsprovider.cpp @@ -128,6 +130,7 @@ SET( CANTATA_MOC_HDRS widgets/timeslider.h widgets/actionlabel.h widgets/playqueueview.h + widgets/groupedview.h widgets/messagewidget.h lyrics/lyricspage.h lyrics/lyricsettings.h diff --git a/ChangeLog b/ChangeLog index 3abc27638..c03f5474f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +0.6.0 +----- +1. Grouped style for playlists. + 0.5.1 ----- 1. Reduce QMutex usage - have MPDStats/MPDStatus emitted as objects, and diff --git a/gui/albumspage.cpp b/gui/albumspage.cpp index 634a035a5..c54bf56d9 100644 --- a/gui/albumspage.cpp +++ b/gui/albumspage.cpp @@ -198,15 +198,16 @@ void AlbumsPage::itemActivated(const QModelIndex &) void AlbumsPage::searchItems() { - QString genre=0==genreCombo->currentIndex() ? QString() : genreCombo->currentText(); + QString genre=genreCombo->currentIndex()<=0 ? QString() : genreCombo->currentText(); QString filter=view->searchText().trimmed(); if (filter.isEmpty() && genre.isEmpty()) { + bool wasEmpty=proxy.isEmpty(); proxy.setFilterEnabled(false); proxy.setFilterGenre(genre); if (!proxy.filterRegExp().isEmpty()) { proxy.setFilterRegExp(QString()); - } else { + } else if (!wasEmpty) { proxy.invalidate(); } } else { diff --git a/gui/interfacesettings.cpp b/gui/interfacesettings.cpp index fe605d129..a64710117 100644 --- a/gui/interfacesettings.cpp +++ b/gui/interfacesettings.cpp @@ -26,13 +26,64 @@ #include "itemview.h" #include #include +#ifdef ENABLE_KDE_SUPPORT +#include +#endif + +static void addViewTypes(QComboBox *box, bool iconMode=false, bool groupedTree=false) +{ + #ifdef ENABLE_KDE_SUPPORT + box->addItem(i18n("Tree"), ItemView::Mode_Tree); + if (groupedTree) { + box->addItem(i18n("Grouped Tree"), ItemView::Mode_GroupedTree); + } + box->addItem(i18n("List"), ItemView::Mode_List); + if (iconMode) { + box->addItem(i18n("Icon/List"), ItemView::Mode_IconTop); + } + #else + box->addItem(tr("Tree"), ItemView::Mode_Tree); + if (groupedTree) { + box->addItem(tr("Grouped Tree"), ItemView::Mode_GroupedTree); + } + box->addItem(i18n("List")tr, ItemView::Mode_List); + if (iconMode) { + box->addItem(tr("Icon/List"), ItemView::Mode_IconTop); + } + #endif +} + +static void selectEntry(QComboBox *box, int v) +{ + for (int i=1; icount(); ++i) { + if (box->itemData(i).toInt()==v) { + box->setCurrentIndex(i); + return; + } + } + +} + +static inline int getViewType(QComboBox *box) +{ + return box->itemData(box->currentIndex()).toInt(); +} InterfaceSettings::InterfaceSettings(QWidget *p) : QWidget(p) { setupUi(this); + addViewTypes(libraryView); + addViewTypes(albumsView, true); + addViewTypes(folderView); + addViewTypes(playlistsView, false, true); + addViewTypes(streamsView); + #ifdef ENABLE_DEVICES_SUPPORT + addViewTypes(devicesView); + #endif connect(albumsView, SIGNAL(currentIndexChanged(int)), SLOT(albumsViewChanged())); connect(albumsCoverSize, SIGNAL(currentIndexChanged(int)), SLOT(albumsCoverSizeChanged())); + connect(playlistsView, SIGNAL(currentIndexChanged(int)), SLOT(playListsStyleChanged())); connect(playQueueGrouped, SIGNAL(currentIndexChanged(int)), SLOT(playQueueGroupedChanged())); #ifndef ENABLE_DEVICES_SUPPORT devicesView->setVisible(false); @@ -44,19 +95,20 @@ InterfaceSettings::InterfaceSettings(QWidget *p) void InterfaceSettings::load() { - libraryView->setCurrentIndex(Settings::self()->libraryView()); + selectEntry(libraryView, Settings::self()->libraryView()); libraryCoverSize->setCurrentIndex(Settings::self()->libraryCoverSize()); libraryYear->setChecked(Settings::self()->libraryYear()); - albumsView->setCurrentIndex(Settings::self()->albumsView()); + selectEntry(albumsView, Settings::self()->albumsView()); albumsCoverSize->setCurrentIndex(Settings::self()->albumsCoverSize()); albumFirst->setCurrentIndex(Settings::self()->albumFirst() ? 0 : 1); - folderView->setCurrentIndex(Settings::self()->folderView()); - playlistsView->setCurrentIndex(Settings::self()->playlistsView()); - streamsView->setCurrentIndex(Settings::self()->streamsView()); + selectEntry(folderView, Settings::self()->folderView()); + selectEntry(playlistsView, Settings::self()->playlistsView()); + playListsStartClosed->setChecked(Settings::self()->playListsStartClosed()); + selectEntry(streamsView, Settings::self()->streamsView()); groupSingle->setChecked(Settings::self()->groupSingle()); #ifdef ENABLE_DEVICES_SUPPORT showDeleteAction->setChecked(Settings::self()->showDeleteAction()); - devicesView->setCurrentIndex(Settings::self()->devicesView()); + selectEntry(devicesView, Settings::self()->devicesView()); #endif playQueueGrouped->setCurrentIndex(Settings::self()->playQueueGrouped() ? 1 : 0); playQueueAutoExpand->setChecked(Settings::self()->playQueueAutoExpand()); @@ -64,24 +116,26 @@ void InterfaceSettings::load() playQueueScroll->setChecked(Settings::self()->playQueueScroll()); albumsViewChanged(); albumsCoverSizeChanged(); + playListsStyleChanged(); playQueueGroupedChanged(); } void InterfaceSettings::save() { - Settings::self()->saveLibraryView(libraryView->currentIndex()); + Settings::self()->saveLibraryView(getViewType(libraryView)); Settings::self()->saveLibraryCoverSize(libraryCoverSize->currentIndex()); Settings::self()->saveLibraryYear(libraryYear->isChecked()); - Settings::self()->saveAlbumsView(albumsView->currentIndex()); + Settings::self()->saveAlbumsView(getViewType(albumsView)); Settings::self()->saveAlbumsCoverSize(albumsCoverSize->currentIndex()); Settings::self()->saveAlbumFirst(0==albumFirst->currentIndex()); - Settings::self()->saveFolderView(folderView->currentIndex()); - Settings::self()->savePlaylistsView(playlistsView->currentIndex()); - Settings::self()->saveStreamsView(streamsView->currentIndex()); + Settings::self()->saveFolderView(getViewType(folderView)); + Settings::self()->savePlaylistsView(getViewType(playlistsView)); + Settings::self()->savePlayListsStartClosed(playListsStartClosed->isChecked()); + Settings::self()->saveStreamsView(getViewType(streamsView)); Settings::self()->saveGroupSingle(groupSingle->isChecked()); #ifdef ENABLE_DEVICES_SUPPORT Settings::self()->saveShowDeleteAction(showDeleteAction->isChecked()); - Settings::self()->saveDevicesView(devicesView->currentIndex()); + Settings::self()->saveDevicesView(getViewType(devicesView)); #endif Settings::self()->savePlayQueueGrouped(1==playQueueGrouped->currentIndex()); Settings::self()->savePlayQueueAutoExpand(playQueueAutoExpand->isChecked()); @@ -91,14 +145,14 @@ void InterfaceSettings::save() void InterfaceSettings::albumsViewChanged() { - if (ItemView::Mode_IconTop==albumsView->currentIndex() && 0==albumsCoverSize->currentIndex()) { + if (ItemView::Mode_IconTop==getViewType(albumsView) && 0==albumsCoverSize->currentIndex()) { albumsCoverSize->setCurrentIndex(2); } } void InterfaceSettings::albumsCoverSizeChanged() { - if (ItemView::Mode_IconTop==albumsView->currentIndex() && 0==albumsCoverSize->currentIndex()) { + if (ItemView::Mode_IconTop==getViewType(albumsView) && 0==albumsCoverSize->currentIndex()) { albumsView->setCurrentIndex(1); } } @@ -111,3 +165,9 @@ void InterfaceSettings::playQueueGroupedChanged() playQueueStartClosedLabel->setEnabled(1==playQueueGrouped->currentIndex()); } +void InterfaceSettings::playListsStyleChanged() +{ + bool grouped=getViewType(playlistsView)==ItemView::Mode_GroupedTree; + playListsStartClosed->setEnabled(grouped); + playListsStartClosedLabel->setEnabled(grouped); +} diff --git a/gui/interfacesettings.h b/gui/interfacesettings.h index fd6a71bf9..15eea8a8b 100644 --- a/gui/interfacesettings.h +++ b/gui/interfacesettings.h @@ -40,6 +40,7 @@ public: private Q_SLOTS: void albumsViewChanged(); void albumsCoverSizeChanged(); + void playListsStyleChanged(); void playQueueGroupedChanged(); }; diff --git a/gui/interfacesettings.ui b/gui/interfacesettings.ui index 6d92bc2c0..9c0b6124b 100644 --- a/gui/interfacesettings.ui +++ b/gui/interfacesettings.ui @@ -22,9 +22,12 @@ 0 + + true + - Library View + Library @@ -38,18 +41,7 @@ - - - - Tree - - - - - List - - - + @@ -106,7 +98,7 @@ - Albums View + Albums @@ -120,23 +112,7 @@ - - - - Tree - - - - - List - - - - - Icon/List - - - + @@ -198,11 +174,51 @@ + + + Playlists + + + + + + Style: + + + playlistsView + + + + + + + + + + Initially collapse albums: + + + playListsStartClosed + + + + + + + + + + + + Other Views + + QFormLayout::ExpandingFieldsGrow + @@ -214,44 +230,9 @@ - - - - Tree - - - - - List - - - + - - - Playlists view style: - - - playlistsView - - - - - - - - Tree - - - - - List - - - - - Streams view style: @@ -261,21 +242,10 @@ - - - - - Tree - - - - - List - - - + + - + Devices view style: @@ -285,19 +255,8 @@ - - - - - Tree - - - - - List - - - + + @@ -432,8 +391,9 @@ albumsView albumsCoverSize albumFirst - folderView playlistsView + playListsStartClosed + folderView streamsView devicesView groupSingle diff --git a/gui/librarypage.cpp b/gui/librarypage.cpp index d4d7f542d..a43698661 100644 --- a/gui/librarypage.cpp +++ b/gui/librarypage.cpp @@ -207,15 +207,16 @@ void LibraryPage::itemDoubleClicked(const QModelIndex &) void LibraryPage::searchItems() { - QString genre=0==genreCombo->currentIndex() ? QString() : genreCombo->currentText(); + QString genre=genreCombo->currentIndex()<=0 ? QString() : genreCombo->currentText(); QString filter=view->searchText().trimmed(); if (filter.isEmpty() && genre.isEmpty()) { + bool wasEmpty=proxy.isEmpty(); proxy.setFilterEnabled(false); proxy.setFilterGenre(genre); if (!proxy.filterRegExp().isEmpty()) { proxy.setFilterRegExp(QString()); - } else { + } else if (!wasEmpty) { proxy.invalidate(); } } else { diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index f39113d79..2a4daeac7 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -785,6 +785,7 @@ MainWindow::MainWindow(QWidget *parent) playQueue->setGrouped(Settings::self()->playQueueGrouped()); playQueue->setAutoExpand(Settings::self()->playQueueAutoExpand()); playQueue->setStartClosed(Settings::self()->playQueueStartClosed()); + playlistsPage->setStartClosed(Settings::self()->playQueueStartClosed()); connect(MPDConnection::self(), SIGNAL(statsUpdated(const MPDStats &)), this, SLOT(updateStats())); connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus())); @@ -907,7 +908,7 @@ MainWindow::MainWindow(QWidget *parent) albumsPage->setView(Settings::self()->albumsView()); AlbumsModel::setUseLibrarySizes(Settings::self()->albumsView()!=ItemView::Mode_IconTop); AlbumsModel::self()->setAlbumFirst(Settings::self()->albumFirst()); - playlistsPage->setView(0==Settings::self()->playlistsView()); + playlistsPage->setView(Settings::self()->playlistsView()); streamsPage->setView(0==Settings::self()->streamsView()); folderPage->setView(0==Settings::self()->folderView()); #ifdef ENABLE_DEVICES_SUPPORT @@ -1207,7 +1208,7 @@ void MainWindow::updateSettings() } libraryPage->setView(0==Settings::self()->libraryView()); - playlistsPage->setView(0==Settings::self()->playlistsView()); + playlistsPage->setView(Settings::self()->playlistsView()); streamsPage->setView(0==Settings::self()->streamsView()); folderPage->setView(0==Settings::self()->folderView()); if (folderPage->isVisible()) { @@ -1233,6 +1234,12 @@ void MainWindow::updateSettings() playQueue->updateRows(usingProxy ? playQueueModel.rowCount()+10 : playQueueModel.currentSongRow(), !usingProxy && autoScrollPlayQueue && MPDState_Playing==MPDStatus::self()->state()); } + + wasStartClosed=playlistsPage->isStartClosed(); + playlistsPage->setStartClosed(Settings::self()->playListsStartClosed()); + if (ItemView::Mode_GroupedTree==Settings::self()->playlistsView() && wasStartClosed!=playlistsPage->isStartClosed()) { + playlistsPage->updateRows(); + } } #ifndef ENABLE_KDE_SUPPORT @@ -1438,11 +1445,7 @@ void MainWindow::updatePlaylist(const QList &songs) } } - QSet controlledSongIds=playQueue->getControlledSongIds(); - QSet keys=playQueueModel.updatePlaylist(songs, controlledSongIds); - if (controlledSongIds.count()) { - playQueue->setControlled(keys); - } + playQueue->setControlledAlbums(playQueueModel.updatePlaylist(songs, playQueue->getControlledAlbums())); playQueue->updateRows(usingProxy ? playQueueModel.rowCount()+10 : playQueueModel.currentSongRow(), false); // reselect song ids or minrow if songids were not found (songs removed) diff --git a/gui/playlistspage.cpp b/gui/playlistspage.cpp index bb71c5e25..bbafa4f0e 100644 --- a/gui/playlistspage.cpp +++ b/gui/playlistspage.cpp @@ -61,6 +61,7 @@ PlaylistsPage::PlaylistsPage(MainWindow *p) MainWindow::initButton(libraryUpdate); MainWindow::initButton(replacePlaylist); + view->allowGroupedView(); #ifdef ENABLE_KDE_SUPPORT view->setTopText(i18n("Playlists")); #else @@ -92,6 +93,8 @@ PlaylistsPage::PlaylistsPage(MainWindow *p) connect(this, SIGNAL(removeFromPlaylist(const QString &, const QList &)), MPDConnection::self(), SLOT(removeFromPlaylist(const QString &, const QList &))); connect(p->savePlaylistAction, SIGNAL(activated()), this, SLOT(savePlaylist())); connect(renamePlaylistAction, SIGNAL(triggered()), this, SLOT(renamePlaylist())); + connect(PlaylistsModel::self(), SIGNAL(updated(const QModelIndex &)), this, SLOT(updated(const QModelIndex &))); + connect(PlaylistsModel::self(), SIGNAL(playlistRemoved(quint32)), view, SLOT(collectionRemoved(quint32))); MainWindow::initButton(menuButton); menuButton->setPopupMode(QToolButton::InstantPopup); QMenu *menu=new QMenu(this); @@ -107,6 +110,21 @@ PlaylistsPage::~PlaylistsPage() { } +void PlaylistsPage::setStartClosed(bool sc) +{ + view->setStartClosed(sc); +} + +bool PlaylistsPage::isStartClosed() +{ + return view->isStartClosed(); +} + +void PlaylistsPage::updateRows() +{ + view->updateRows(); +} + void PlaylistsPage::refresh() { PlaylistsModel::self()->getPlaylists(); @@ -258,7 +276,7 @@ void PlaylistsPage::itemDoubleClicked(const QModelIndex &index) QModelIndexList indexes; indexes.append(index); addItemsToPlayQueue(indexes); - } + } } void PlaylistsPage::addItemsToPlayQueue(const QModelIndexList &indexes) @@ -323,15 +341,16 @@ void PlaylistsPage::controlActions() void PlaylistsPage::searchItems() { - QString genre=0==genreCombo->currentIndex() ? QString() : genreCombo->currentText(); + QString genre=genreCombo->currentIndex()<=0 ? QString() : genreCombo->currentText(); QString filter=view->searchText().trimmed(); if (filter.isEmpty() && genre.isEmpty()) { + bool wasEmpty=proxy.isEmpty(); proxy.setFilterEnabled(false); proxy.setFilterGenre(genre); if (!proxy.filterRegExp().isEmpty()) { proxy.setFilterRegExp(QString()); - } else { + } else if (!wasEmpty) { proxy.invalidate(); } } else { @@ -345,6 +364,11 @@ void PlaylistsPage::searchItems() } } +void PlaylistsPage::updated(const QModelIndex &index) +{ + view->updateRows(proxy.mapFromSource(index)); +} + void PlaylistsPage::updateGenres(const QSet &g) { if (genreCombo->count() && g==genres) { diff --git a/gui/playlistspage.h b/gui/playlistspage.h index e3c8940e4..d852c41d5 100644 --- a/gui/playlistspage.h +++ b/gui/playlistspage.h @@ -35,11 +35,14 @@ public: PlaylistsPage(MainWindow *p); virtual ~PlaylistsPage(); + void setStartClosed(bool sc); + bool isStartClosed(); + void updateRows(); void refresh(); void clear(); QStringList selectedFiles() const; void addSelectionToPlaylist(); - void setView(bool tree) { view->setMode(tree ? ItemView::Mode_Tree : ItemView::Mode_List); } + void setView(int mode) { view->setMode((ItemView::Mode)mode); } Q_SIGNALS: // These are for communicating with MPD object (which is in its own thread, so need to talk via signal/slots) @@ -64,6 +67,7 @@ private Q_SLOTS: void renamePlaylist(); void itemDoubleClicked(const QModelIndex &index); void searchItems(); + void updated(const QModelIndex &index); private: Action *renamePlaylistAction; diff --git a/gui/settings.cpp b/gui/settings.cpp index 7e37fbc8c..91584fa6b 100644 --- a/gui/settings.cpp +++ b/gui/settings.cpp @@ -324,7 +324,7 @@ int Settings::devicesView() int Settings::version() { if (-1==ver) { - QStringList parts=GET_STRING("version", QLatin1String("0.0.0")).split('.'); + QStringList parts=GET_STRING("version", QLatin1String(PACKAGE_VERSION)).split('.'); if (3==parts.size()) { ver=CANTATA_MAKE_VERSION(parts.at(0).toInt(), parts.at(1).toInt(), parts.at(2).toInt()); } else { @@ -381,6 +381,11 @@ bool Settings::playQueueScroll() return GET_BOOL("playQueueScroll", true); } +bool Settings::playListsStartClosed() +{ + return GET_BOOL("playListsStartClosed", true); +} + void Settings::saveConnectionHost(const QString &v) { SET_VALUE("connectionHost", v); @@ -625,6 +630,11 @@ void Settings::savePlayQueueScroll(bool v) SET_VALUE("playQueueScroll", v); } +void Settings::savePlayListsStartClosed(bool v) +{ + SET_VALUE("playListsStartClosed", v); +} + void Settings::save(bool force) { if (force) { diff --git a/gui/settings.h b/gui/settings.h index 56e8d32be..fce686f06 100644 --- a/gui/settings.h +++ b/gui/settings.h @@ -102,6 +102,7 @@ public: bool playQueueAutoExpand(); bool playQueueStartClosed(); bool playQueueScroll(); + bool playListsStartClosed(); void saveConnectionHost(const QString &v); void saveConnectionPasswd(const QString &v); @@ -149,6 +150,7 @@ public: void savePlayQueueAutoExpand(bool v); void savePlayQueueStartClosed(bool v); void savePlayQueueScroll(bool v); + void savePlayListsStartClosed(bool v); void save(bool force=false); #ifdef ENABLE_KDE_SUPPORT bool openWallet(); diff --git a/gui/streamspage.cpp b/gui/streamspage.cpp index 10607a835..5022692c2 100644 --- a/gui/streamspage.cpp +++ b/gui/streamspage.cpp @@ -436,15 +436,16 @@ void StreamsPage::controlActions() void StreamsPage::searchItems() { - QString genre=0==genreCombo->currentIndex() ? QString() : genreCombo->currentText(); + QString genre=genreCombo->currentIndex()<=0 ? QString() : genreCombo->currentText(); QString filter=view->searchText().trimmed(); if (filter.isEmpty() && genre.isEmpty()) { + bool wasEmpty=proxy.isEmpty(); proxy.setFilterEnabled(false); proxy.setFilterGenre(genre); if (!proxy.filterRegExp().isEmpty()) { proxy.setFilterRegExp(QString()); - } else { + } else if (!wasEmpty) { proxy.invalidate(); } } else { diff --git a/models/albumsproxymodel.h b/models/albumsproxymodel.h index 69b42c175..a6715c311 100644 --- a/models/albumsproxymodel.h +++ b/models/albumsproxymodel.h @@ -34,6 +34,9 @@ public: void setFilterGenre(const QString &genre); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool isEmpty() const { + return filterGenre.isEmpty() && filterRegExp().isEmpty(); + } private: bool filterAcceptsAlbum(AlbumsModel::Item *item) const; diff --git a/models/musiclibraryproxymodel.h b/models/musiclibraryproxymodel.h index 634fede1d..507ee8153 100644 --- a/models/musiclibraryproxymodel.h +++ b/models/musiclibraryproxymodel.h @@ -41,6 +41,9 @@ public: bool lessThan(const QModelIndex &left, const QModelIndex &right) const; // void setFilterField(int field); void setFilterGenre(const QString &genre); + bool isEmpty() const { + return filterGenre.isEmpty() && filterRegExp().isEmpty(); + } private: bool filterAcceptsRoot(const MusicLibraryItem * const item) const; diff --git a/models/playlistsmodel.cpp b/models/playlistsmodel.cpp index 66b5b5761..e475abfa8 100644 --- a/models/playlistsmodel.cpp +++ b/models/playlistsmodel.cpp @@ -29,6 +29,7 @@ #include #include "playlistsmodel.h" #include "itemview.h" +#include "groupedview.h" #ifdef ENABLE_KDE_SUPPORT #include #include @@ -60,6 +61,7 @@ PlaylistsModel::PlaylistsModel(QObject *parent) connect(MPDConnection::self(), SIGNAL(playlistsRetrieved(const QList &)), this, SLOT(setPlaylists(const QList &))); connect(MPDConnection::self(), SIGNAL(playlistInfoRetrieved(const QString &, const QList &)), this, SLOT(playlistInfoRetrieved(const QString &, const QList &))); connect(MPDConnection::self(), SIGNAL(removedFromPlaylist(const QString &, const QList &)), this, SLOT(removedFromPlaylist(const QString &, const QList &))); + connect(MPDConnection::self(), SIGNAL(playlistRenamed(const QString &, const QString &)), this, SLOT(playlistRenamed(const QString &, const QString &))); connect(this, SIGNAL(listPlaylists()), MPDConnection::self(), SLOT(listPlaylists())); connect(this, SIGNAL(playlistInfo(const QString &)), MPDConnection::self(), SLOT(playlistInfo(const QString &))); connect(this, SIGNAL(addToPlaylist(const QString &, const QStringList &)), MPDConnection::self(), SLOT(addToPlaylist(const QString &, const QStringList &))); @@ -154,6 +156,24 @@ QVariant PlaylistsModel::data(const QModelIndex &index, int role) const PlaylistItem *pl=static_cast(item); switch(role) { + case GroupedView::Role_IsCollection: + return true; + case GroupedView::Role_CollectionId: + return pl->key; + case GroupedView::Role_Key: + return 0; + case GroupedView::Role_Song: { + QVariant var; + var.setValue(Song()); + return var; + } + case GroupedView::Role_AlbumDuration: + return pl->totalTime(); + case GroupedView::Role_SongCount: + return pl->songs.count(); + case GroupedView::Role_CurrentStatus: + case GroupedView::Role_Status: + return (int)GroupedView::State_Default; case Qt::DisplayRole: if (!pl->loaded) { pl->loaded=true; @@ -187,6 +207,60 @@ QVariant PlaylistsModel::data(const QModelIndex &index, int role) const SongItem *s=static_cast(item); switch (role) { + case GroupedView::Role_IsCollection: + return false; + case GroupedView::Role_CollectionId: + return s->parent->key; + case GroupedView::Role_Key: + return s->key; + case GroupedView::Role_Song: { + QVariant var; + var.setValue(*s); + return var; + } + case GroupedView::Role_AlbumDuration: { + quint32 d=s->time; + for (int i=index.row()+1; iparent->songs.count(); ++i) { + const SongItem *song = s->parent->songs.at(i); + if (song->key!=s->key) { + break; + } + d+=song->time; + } + if (index.row()>1) { + for (int i=index.row()-1; i<=0; ++i) { + const SongItem *song = s->parent->songs.at(i); + if (song->key!=s->key) { + break; + } + d+=song->time; + } + } + return d; + } + case GroupedView::Role_SongCount:{ + quint32 count=1; + for (int i=index.row()+1; iparent->songs.count(); ++i) { + const SongItem *song = s->parent->songs.at(i); + if (song->key!=s->key) { + break; + } + count++; + } + if (index.row()>1) { + for (int i=index.row()-1; i<=0; ++i) { + const SongItem *song = s->parent->songs.at(i); + if (song->key!=s->key) { + break; + } + count++; + } + } + return count; + } + case GroupedView::Role_CurrentStatus: + case GroupedView::Role_Status: + return (int)GroupedView::State_Default; case Qt::DisplayRole: case Qt::ToolTipRole: { QString text=s->entryName(); @@ -402,7 +476,7 @@ void PlaylistsModel::setPlaylists(const QList &playlists) } beginResetModel(); foreach (const Playlist &p, playlists) { - items.append(new PlaylistItem(p.name)); + items.append(new PlaylistItem(p.name, allocateKey())); } endResetModel(); updateItemMenu(); @@ -432,6 +506,8 @@ void PlaylistsModel::setPlaylists(const QList &playlists) if (pl) { int index=items.indexOf(pl); beginRemoveRows(parent, index, index); + usedKeys.remove(pl->key); + emit playlistRemoved(pl->key); delete items.takeAt(index); endRemoveRows(); } @@ -440,7 +516,7 @@ void PlaylistsModel::setPlaylists(const QList &playlists) if (added.count()) { beginInsertRows(parent, items.count(), items.count()+added.count()-1); foreach (const QString &p, added) { - items.append(new PlaylistItem(p)); + items.append(new PlaylistItem(p, allocateKey())); } endInsertRows(); } @@ -457,12 +533,14 @@ void PlaylistsModel::playlistInfoRetrieved(const QString &name, const QListsongs.append(new SongItem(s, pl)); } endInsertRows(); updateItemMenu(); + emit updated(idx); } else if (songs.isEmpty()) { beginRemoveRows(createIndex(items.indexOf(pl), 0, pl), 0, pl->songs.count()-1); pl->clearSongs(); @@ -495,6 +573,7 @@ void PlaylistsModel::playlistInfoRetrieved(const QString &name, const QListupdateGenres(); } else { @@ -588,6 +667,15 @@ void PlaylistsModel::emitAddToExisting() } } +void PlaylistsModel::playlistRenamed(const QString &from, const QString &to) +{ + PlaylistItem *pl=getPlaylist(from); + + if (pl) { + pl->name=to; + } +} + void PlaylistsModel::updateItemMenu() { if (!itemMenu) { @@ -624,6 +712,11 @@ PlaylistsModel::PlaylistItem * PlaylistsModel::getPlaylist(const QString &name) void PlaylistsModel::clearPlaylists() { + foreach (PlaylistItem *p, items) { + usedKeys.remove(p->key); + emit playlistRemoved(p->key); + } + qDeleteAll(items); items.clear(); updateGenreList(); @@ -639,6 +732,18 @@ void PlaylistsModel::updateGenreList() emit updateGenres(genres); } +quint32 PlaylistsModel::allocateKey() +{ + for(quint32 k=1; k<0xFFFFFFFE; ++k) { + if (!usedKeys.contains(k)) { + usedKeys.insert(k); + return k; + } + } + + return 0xFFFFFFFF; +} + PlaylistsModel::PlaylistItem::~PlaylistItem() { clearSongs(); diff --git a/models/playlistsmodel.h b/models/playlistsmodel.h index 315aaf334..f666801ee 100644 --- a/models/playlistsmodel.h +++ b/models/playlistsmodel.h @@ -56,8 +56,8 @@ public: struct PlaylistItem : public Item { - PlaylistItem() : loaded(false), time(0) { } - PlaylistItem(const QString &n) : name(n), loaded(false), time(0) { } + PlaylistItem(quint32 k) : loaded(false), time(0), key(k) { } + PlaylistItem(const QString &n, quint32 k) : name(n), loaded(false), time(0), key(k) { } virtual ~PlaylistItem(); bool isPlaylist() { return true; } void updateGenres(); @@ -69,6 +69,7 @@ public: QList songs; QSet genres; quint32 time; + quint32 key; }; static PlaylistsModel * self(); @@ -107,6 +108,8 @@ Q_SIGNALS: void addToNew(); void addToExisting(const QString &name); void updateGenres(const QSet &genres); + void updated(const QModelIndex &idx); + void playlistRemoved(quint32 key); private Q_SLOTS: void setPlaylists(const QList &playlists); @@ -114,15 +117,18 @@ private Q_SLOTS: void removedFromPlaylist(const QString &name, const QList &positions); void movedInPlaylist(const QString &name, int from, int to); void emitAddToExisting(); + void playlistRenamed(const QString &from, const QString &to); private: void updateGenreList(); void updateItemMenu(); PlaylistItem * getPlaylist(const QString &name); void clearPlaylists(); + quint32 allocateKey(); private: QList items; + QSet usedKeys; QMenu *itemMenu; }; diff --git a/models/playlistsproxymodel.h b/models/playlistsproxymodel.h index 6db8be7eb..4bcdb95bd 100644 --- a/models/playlistsproxymodel.h +++ b/models/playlistsproxymodel.h @@ -37,6 +37,9 @@ public: void setFilterGenre(const QString &genre); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool isEmpty() const { + return filterGenre.isEmpty() && filterRegExp().isEmpty(); + } private: QString filterGenre; diff --git a/models/playqueuemodel.cpp b/models/playqueuemodel.cpp index cf674148f..7090822ac 100644 --- a/models/playqueuemodel.cpp +++ b/models/playqueuemodel.cpp @@ -36,7 +36,7 @@ #include #endif #include "playqueuemodel.h" -#include "playqueueview.h" +#include "groupedview.h" #include "mpdconnection.h" #include "mpdparseutils.h" #include "mpdstats.h" @@ -187,16 +187,20 @@ QVariant PlayQueueModel::data(const QModelIndex &index, int role) const // } switch (role) { - case PlayQueueView::Role_Key: + case GroupedView::Role_IsCollection: + return false; + case GroupedView::Role_CollectionId: + return 0; + case GroupedView::Role_Key: return songs.at(index.row()).key; - case PlayQueueView::Role_Id: + case GroupedView::Role_Id: return songs.at(index.row()).id; - case PlayQueueView::Role_Song: { + case GroupedView::Role_Song: { QVariant var; var.setValue(songs.at(index.row())); return var; } - case PlayQueueView::Role_AlbumDuration: { + case GroupedView::Role_AlbumDuration: { const Song &first = songs.at(index.row()); quint32 d=first.time; for (int i=index.row()+1; i PlayQueueModel::updatePlaylist(const QList &songList, QSet controlledIds) +QSet PlayQueueModel::updatePlaylist(const QList &songList, QSet controlledAlbums) { TF_DEBUG QSet newIds; + QSet controlledIds; QSet keys; foreach (const Song &s, songList) { newIds.insert(s.id); } + // Map from album keys, into song ids... + foreach (const Song &s, songs) { + if (controlledAlbums.contains(s.key)) { + controlledIds.insert(s.id); + } + } + if (songs.isEmpty() || songList.isEmpty()) { beginResetModel(); songs=songList; diff --git a/models/playqueuemodel.h b/models/playqueuemodel.h index 7d82df057..c1a2b5431 100644 --- a/models/playqueuemodel.h +++ b/models/playqueuemodel.h @@ -72,6 +72,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &) const; QVariant data(const QModelIndex &, int) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); void updateCurrentSong(quint32 id); qint32 getIdByRow(qint32 row) const; qint32 getPosByRow(qint32 row) const; @@ -89,8 +90,7 @@ public: void setState(MPDState st); bool isGrouped() const { return grouped; } void setGrouped(bool g); - void setDropAdjust(quint32 a) { dropAdjust=a; } - QSet updatePlaylist(const QList &songList, QSet controlledIds); + QSet updatePlaylist(const QList &songList, QSet controlled); public Q_SLOTS: void addItems(const QStringList &items, int row); diff --git a/models/streamsproxymodel.h b/models/streamsproxymodel.h index 9f8002b04..bfe61cc52 100644 --- a/models/streamsproxymodel.h +++ b/models/streamsproxymodel.h @@ -32,6 +32,9 @@ public: StreamsProxyModel(QObject *parent = 0); void setFilterGenre(const QString &genre); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool isEmpty() const { + return filterGenre.isEmpty() && filterRegExp().isEmpty(); + } private: QString filterGenre; diff --git a/mpd/mpdconnection.cpp b/mpd/mpdconnection.cpp index 2054e72cf..4f3542594 100644 --- a/mpd/mpdconnection.cpp +++ b/mpd/mpdconnection.cpp @@ -756,7 +756,9 @@ void MPDConnection::renamePlaylist(const QString oldName, const QString newName) data += " "; data += encodeName(newName); - if (!sendCommand(data, false).ok) { + if (sendCommand(data, false).ok) { + emit playlistRenamed(oldName, newName); + } else { #ifdef ENABLE_KDE_SUPPORT emit error(i18n("Failed to rename %1 to %2").arg(oldName).arg(newName)); #else diff --git a/mpd/mpdconnection.h b/mpd/mpdconnection.h index a3c4a46eb..16dcc9412 100644 --- a/mpd/mpdconnection.h +++ b/mpd/mpdconnection.h @@ -218,6 +218,7 @@ Q_SIGNALS: void dirViewUpdated(DirViewItemRoot * root); void playlistsRetrieved(const QList &data); void playlistInfoRetrieved(const QString &name, const QList &songs); + void playlistRenamed(const QString &from, const QString &to); void removedFromPlaylist(const QString &name, const QList &positions); void movedInPlaylist(const QString &name, int from, int to); void databaseUpdated(); diff --git a/widgets/actionitemdelegate.cpp b/widgets/actionitemdelegate.cpp new file mode 100644 index 000000000..9236392e9 --- /dev/null +++ b/widgets/actionitemdelegate.cpp @@ -0,0 +1,162 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 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 "actionitemdelegate.h" +#include "itemview.h" +#include +#include +#include +#include + +const int ActionItemDelegate::constBorder = 1; +const int ActionItemDelegate::constActionBorder = 4; +const int ActionItemDelegate::constActionIconSize=16; + +QRect ActionItemDelegate::calcActionRect(bool rtl, bool iconMode, const QRect &rect) +{ + return rtl + ? iconMode + ? QRect(rect.x()+constActionBorder, + rect.y()+constActionBorder, + constActionIconSize, constActionIconSize) + : QRect(rect.x()+constActionBorder, + rect.y()+((rect.height()-constActionIconSize)/2), + constActionIconSize, constActionIconSize) + : iconMode + ? QRect(rect.x()+rect.width()-(constActionIconSize+constActionBorder), + rect.y()+constActionBorder, + constActionIconSize, constActionIconSize) + : QRect(rect.x()+rect.width()-(constActionIconSize+constActionBorder), + rect.y()+((rect.height()-constActionIconSize)/2), + constActionIconSize, constActionIconSize); +} + +void ActionItemDelegate::adjustActionRect(bool rtl, bool iconMode, QRect &rect) +{ + if (rtl) { + if (iconMode) { + rect.adjust(0, constActionIconSize+constActionBorder, 0, constActionIconSize+constActionBorder); + } else { + rect.adjust(constActionIconSize+constActionBorder, 0, constActionIconSize+constActionBorder, 0); + } + } else { + if (iconMode) { + rect.adjust(0, constActionIconSize+constActionBorder, 0, constActionIconSize+constActionBorder); + } else { + rect.adjust(-(constActionIconSize+constActionBorder), 0, -(constActionIconSize+constActionBorder), 0); + } + } +} + +bool ActionItemDelegate::hasActions(const QModelIndex &index, int actLevel) +{ + if (actLevel<0) { + return true; + } + + int level=0; + + QModelIndex idx=index; + while(idx.parent().isValid()) { + if (++level>actLevel) { + return false; + } + idx=idx.parent(); + } + return true; +} + +// static QPainterPath buildPath(const QRectF &r, double radius) +// { +// QPainterPath path; +// double diameter(radius*2); +// +// path.moveTo(r.x()+r.width(), r.y()+r.height()-radius); +// path.arcTo(r.x()+r.width()-diameter, r.y(), diameter, diameter, 0, 90); +// path.arcTo(r.x(), r.y(), diameter, diameter, 90, 90); +// path.arcTo(r.x(), r.y()+r.height()-diameter, diameter, diameter, 180, 90); +// path.arcTo(r.x()+r.width()-diameter, r.y()+r.height()-diameter, diameter, diameter, 270, 90); +// return path; +// } + +static void drawBgnd(QPainter *painter, const QRect &rx) +{ + QRectF r(rx.x()-0.5, rx.y()-0.5, rx.width()+1, rx.height()+1); + QPainterPath p;//(buildPath(r, r.width()/2.0)); + QColor c(Qt::white); + + p.addEllipse(r); + painter->setRenderHint(QPainter::Antialiasing, true); + c.setAlphaF(0.75); + painter->fillPath(p, c); +// c.setAlphaF(0.95); +// painter->setPen(c); +// painter->drawPath(p); + painter->setRenderHint(QPainter::Antialiasing, false); +} + +void ActionItemDelegate::drawIcons(QPainter *painter, const QRect &r, bool mouseOver, bool rtl, bool iconMode, const QModelIndex &index) const +{ + double opacity=painter->opacity(); + if (!mouseOver) { + painter->setOpacity(opacity*0.2); + } + + QRect actionRect=calcActionRect(rtl, iconMode, r); + + if (act1) { + QPixmap pix=act1->icon().pixmap(QSize(constActionIconSize, constActionIconSize)); + if (!pix.isNull() && actionRect.width()>=pix.width()/* && r.x()>=0 && r.y()>=0*/) { + drawBgnd(painter, actionRect); + painter->drawPixmap(actionRect.x()+(actionRect.width()-pix.width())/2, + actionRect.y()+(actionRect.height()-pix.height())/2, pix); + } + } + + if (act1 && act2) { + adjustActionRect(rtl, iconMode, actionRect); + QPixmap pix=act2->icon().pixmap(QSize(constActionIconSize, constActionIconSize)); + if (!pix.isNull() && actionRect.width()>=pix.width()/* && r.x()>=0 && r.y()>=0*/) { + drawBgnd(painter, actionRect); + painter->drawPixmap(actionRect.x()+(actionRect.width()-pix.width())/2, + actionRect.y()+(actionRect.height()-pix.height())/2, pix); + } + } + + if (act1 && act2 && toggle) { + QString iconName=index.data(ItemView::Role_ToggleIconName).toString(); + if (!iconName.isEmpty()) { + adjustActionRect(rtl, iconMode, actionRect); + QPixmap pix=QIcon::fromTheme(iconName).pixmap(QSize(constActionIconSize, constActionIconSize)); + if (!pix.isNull() && actionRect.width()>=pix.width()/* && r.x()>=0 && r.y()>=0*/) { + drawBgnd(painter, actionRect); + painter->drawPixmap(actionRect.x()+(actionRect.width()-pix.width())/2, + actionRect.y()+(actionRect.height()-pix.height())/2, pix); + } + } + } + if (!mouseOver) { + painter->setOpacity(opacity); + } +} + diff --git a/widgets/actionitemdelegate.h b/widgets/actionitemdelegate.h new file mode 100644 index 000000000..4aba7d959 --- /dev/null +++ b/widgets/actionitemdelegate.h @@ -0,0 +1,62 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 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. + */ + +#ifndef ACTIONITEMDELEGATE_H +#define ACTIONITEMDELEGATE_H + +#include + +class QAction; + +class ActionItemDelegate : public QStyledItemDelegate +{ +public: + ActionItemDelegate(QObject *p, QAction *a1, QAction *a2, QAction *t, int actionLevel) + : QStyledItemDelegate(p) + , act1(a1) + , act2(a2) + , toggle(t) + , actLevel(actionLevel) { + } + + virtual ~ActionItemDelegate() { + } + + static const int constBorder; + static const int constActionBorder; + static const int constActionIconSize; + + static QRect calcActionRect(bool rtl, bool iconMode, const QRect &rect); + static void adjustActionRect(bool rtl, bool iconMode, QRect &rect); + static bool hasActions(const QModelIndex &index, int actLevel); + + void drawIcons(QPainter *painter, const QRect &r, bool mouseOver, bool rtl, bool iconMode, const QModelIndex &index) const; + + QAction *act1; + QAction *act2; + QAction *toggle; + int actLevel; +}; + +#endif + diff --git a/widgets/groupedview.cpp b/widgets/groupedview.cpp new file mode 100644 index 000000000..61d66d000 --- /dev/null +++ b/widgets/groupedview.cpp @@ -0,0 +1,657 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 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 "covers.h" +#include "groupedview.h" +#include "mpdstatus.h" +#include "song.h" +#include "actionitemdelegate.h" +#include "itemview.h" +#include +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_KDE_SUPPORT +#include +#endif + +static const int constCoverSize=32; +static const int constIconSize=16; +static const int constBorder=1; + +enum Type { + AlbumHeader, + AlbumTrack +}; + +static Type getType(const QModelIndex &index) +{ + QModelIndex prev=index.row()>0 ? index.sibling(index.row()-1, 0) : QModelIndex(); + quint16 thisKey=index.data(GroupedView::Role_Key).toUInt(); + quint16 prevKey=prev.isValid() ? prev.data(GroupedView::Role_Key).toUInt() : Song::constNullKey; + + return thisKey==prevKey ? AlbumTrack : AlbumHeader; +} + +static bool isAlbumHeader(const QModelIndex &index) +{ + return !index.data(GroupedView::Role_IsCollection).toBool() && AlbumHeader==getType(index); +} + +class GroupedViewDelegate : public ActionItemDelegate +{ +public: + GroupedViewDelegate(GroupedView *p, QAction *a1, QAction *a2, QAction *t, int actionLevel) + : ActionItemDelegate(p, a1, a2, t, actionLevel) + , view(p) + { + } + + virtual ~GroupedViewDelegate() + { + } + + QSize sizeHint(Type type, bool isCollection) const + { + int textHeight = QApplication::fontMetrics().height(); + + if (isCollection || AlbumHeader==type) { + return QSize(64, qMax(constCoverSize, (qMax(constIconSize, textHeight)*2))+(3*constBorder)); + } + return QSize(64, qMax(constIconSize, textHeight)+(2*constBorder)); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + if (0==index.column()) { + return sizeHint(getType(index), index.data(GroupedView::Role_IsCollection).toBool()); + } + return QStyledItemDelegate::sizeHint(option, index); + } + + inline QString formatNumber(int num) const + { + QString text=QString::number(num); + return num<10 ? "0"+text : text; + } + + static void drawFadedLine(QPainter *p, const QRect &r, const QColor &col) + { + QPoint start(r.x(), r.y()); + QPoint end(r.x()+r.width()-1, r.y()); + QLinearGradient grad(start, end); + QColor c(col); + c.setAlphaF(0.45); + QColor fade(c); + const int fadeSize=64; + double fadePos=1.0-((r.width()-fadeSize)/(r.width()*1.0)); + bool rtl=Qt::RightToLeft==QApplication::layoutDirection(); + + fade.setAlphaF(0.0); + grad.setColorAt(0, rtl ? fade : c); + if(fadePos>=0 && fadePos<=1.0) { + if (rtl) { + grad.setColorAt(fadePos, c); + } else { + grad.setColorAt(1.0-fadePos, c); + } + } + grad.setColorAt(1, rtl ? c : fade); + p->save(); + p->setPen(QPen(QBrush(grad), 1)); + p->drawLine(start, end); + p->restore(); + } + + static QPainterPath buildPath(const QRectF &r, double radius) + { + QPainterPath path; + double diameter(radius*2); + + path.moveTo(r.x()+r.width(), r.y()+r.height()-radius); + path.arcTo(r.x()+r.width()-diameter, r.y(), diameter, diameter, 0, 90); + path.arcTo(r.x(), r.y(), diameter, diameter, 90, 90); + path.arcTo(r.x(), r.y()+r.height()-diameter, diameter, diameter, 180, 90); + path.arcTo(r.x()+r.width()-diameter, r.y()+r.height()-diameter, diameter, diameter, 270, 90); + return path; + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + if (!index.isValid()) { + return; + } + + Type type=getType(index); + bool isCollection=index.data(GroupedView::Role_IsCollection).toBool(); + Song song=index.data(GroupedView::Role_Song).value(); + int state=index.data(GroupedView::Role_Status).toInt(); + + if (!isCollection && AlbumHeader==type) { + QStyleOptionViewItem opt(option); + painter->save(); + painter->setOpacity(0.75); + QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0L); + painter->restore(); + painter->save(); + painter->setClipRect(option.rect.adjusted(0, option.rect.height()/2, 0, 0), Qt::IntersectClip); + QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0L); + painter->restore(); + if (!state && !view->isExpanded(song.key) && view->isCurrentAlbum(song.key)) { + state=index.data(GroupedView::Role_CurrentStatus).toInt(); + } + } else { + QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0L); + } + QString title; + QString track; + QString duration=Song::formattedTime(song.time); + bool stream=!isCollection && song.isStream(); + QString trackTitle=!song.albumartist.isEmpty() && song.albumartist != song.artist + ? song.title + " - " + song.artist + : song.title; + QFont f(QApplication::font()); + if (stream) { + f.setItalic(true); + } + QFontMetrics fm(f); + int textHeight=fm.height(); + quint32 collection=index.data(GroupedView::Role_CollectionId).toUInt(); + + if (isCollection) { + title=index.data(Qt::DisplayRole).toString(); + } else if (AlbumHeader==type) { + QString album=song.album; + + if (stream && album.isEmpty() && song.albumArtist().isEmpty()) { + title=song.file; + if (song.title.isEmpty()) { + trackTitle=QString(); + } + } + if (song.year>0) { + album+=QString(" (%1)").arg(song.year); + } + if (title.isEmpty()) { + #ifdef ENABLE_KDE_SUPPORT + title=i18nc("artist - album", "%1 - %2", song.albumArtist(), album); + #else + title=tr("%1 - %2").arg(song.albumArtist()).arg(album); + #endif + track=formatNumber(song.track)+QChar(' ')+trackTitle; + } + } else { + track=formatNumber(song.track)+QChar(' ')+trackTitle; + } + + painter->save(); + painter->setFont(f); + QColor col(option.palette.color(option.state&QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); + QTextOption textOpt(Qt::AlignVCenter); + QRect r(option.rect.adjusted(constBorder+4, constBorder, -(constBorder+4), -constBorder)); + bool rtl=Qt::RightToLeft==QApplication::layoutDirection(); + + if (state) { + QRectF border(option.rect.x()+1.5, option.rect.y()+1.5, option.rect.width()-3, option.rect.height()-3); + if (!title.isEmpty()) { + border.adjust(0, textHeight+constBorder, 0, 0); + } + QLinearGradient g(border.topLeft(), border.bottomLeft()); + QColor gradCol(QApplication::palette().color(QPalette::Highlight)); + gradCol.setAlphaF(option.state&QStyle::State_Selected ? 0.6 : 0.45); + g.setColorAt(0, gradCol.dark(165)); + g.setColorAt(1, gradCol.light(165)); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->fillPath(buildPath(border, 3), g); + painter->setPen(QPen(gradCol, 1)); + painter->drawPath(buildPath(border.adjusted(-1, -1, 1, 1), 3.5)); + painter->setPen(QPen(QApplication::palette().color(QPalette::HighlightedText), 1)); + painter->drawPath(buildPath(border, 3)); + painter->setRenderHint(QPainter::Antialiasing, false); + } + + painter->setPen(col); + bool showTrackDuration=true; + + if (isCollection || AlbumHeader==type) { + QPixmap pix; + // Draw cover... + if (isCollection) { + pix=index.data(Qt::DecorationRole).value().pixmap(constCoverSize, constCoverSize); + } else { + QPixmap *cover=Covers::self()->get(song, constCoverSize); + pix=cover ? *cover : QIcon::fromTheme(stream ? "applications-internet" : "media-optical-audio").pixmap(constCoverSize, constCoverSize); + } + + if (rtl) { + painter->drawPixmap(r.x()+r.width()-(pix.width()+constBorder), r.y()+((r.height()-pix.height())/2), pix.width(), pix.height(), pix); + r.adjust(0, 0, -(constCoverSize+constBorder), 0); + } else { + painter->drawPixmap(r.x()-2, r.y()+((r.height()-pix.height())/2), pix.width(), pix.height(), pix); + r.adjust(constCoverSize+constBorder, 0, 0, 0); + } + QString totalDuration=Song::formattedTime(index.data(GroupedView::Role_AlbumDuration).toUInt()); + QRect duratioRect(r.x(), r.y(), r.width(), textHeight); + int totalDurationWidth=fm.width(totalDuration)+8; + QRect textRect(r.x(), r.y(), r.width()-totalDurationWidth, textHeight); + QFont tf(f); + tf.setBold(true); + tf.setItalic(true); + title = QFontMetrics(tf).elidedText(title, Qt::ElideRight, textRect.width(), QPalette::WindowText); + painter->setFont(tf); + painter->drawText(textRect, title, textOpt); + painter->drawText(duratioRect, totalDuration, QTextOption(Qt::AlignVCenter|Qt::AlignRight)); + drawFadedLine(painter, r.adjusted(0, (r.height()/2)-1, 0, 0), col); + r.adjust(0, textHeight+constBorder, 0, 0); + r=QRect(r.x(), r.y()+r.height()-(textHeight+1), r.width(), textHeight); + painter->setFont(f); + if (rtl) { + r.adjust(0, 0, (constCoverSize+constBorder), 0); + } + + if (isCollection || !view->isExpanded(song.key, collection)) { + showTrackDuration=false; + #ifdef ENABLE_KDE_SUPPORT + track=i18np("1 Track", "%1 Tracks", index.data(GroupedView::Role_SongCount).toUInt()); + #else + track=tr("%1 Track(s)").arg(index.data(GroupedView::Role_SongCount).toUInt()); + #endif + } + } else if (!rtl) { + r.adjust(constCoverSize+constBorder, 0, 0, 0); + } + + if (state) { + int size=9; + QRect ir(r.x()-(size+6), r.y()+(((r.height()-size)/2.0)+0.5), size, size); + switch (state) { + case GroupedView::State_Stopped: + painter->fillRect(ir, Qt::white); + painter->fillRect(ir.adjusted(1, 1, -1, -1), Qt::black); + break; + case GroupedView::State_Paused: + painter->fillRect(ir, Qt::white); + painter->fillRect(ir.x()+1, ir.y()+1, 3, size-2, Qt::black); + painter->fillRect(ir.x()+size-4, ir.y()+1, 3, size-2, Qt::black); + break; + case GroupedView::State_Playing: { + ir.adjust(2, 0, -2, 0); + QPoint p1[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-4), ir.y()+4), QPoint(ir.x(), ir.y()+(ir.height()-1)), QPoint(ir.x()-2, ir.y()+(ir.height()-1)) }; + QPoint p2[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-4), ir.y()+4), QPoint(ir.x(), ir.y()+ir.height()), QPoint(ir.x()-2, ir.y()+ir.height()) }; + painter->save(); + painter->setBrush(Qt::white); + painter->setPen(Qt::white); + painter->drawPolygon(p1, 5); + painter->setBrush(Qt::black); + painter->drawPolygon(p2, 5); + painter->restore(); + break; + } + } + painter->setPen(col); + } + int durationWidth=showTrackDuration ? fm.width(duration)+8 : 0; + QRect duratioRect(r.x(), r.y(), r.width(), textHeight); + QRect textRect(r.x(), r.y(), r.width()-durationWidth, textHeight); + track = fm.elidedText(track, Qt::ElideRight, textRect.width(), QPalette::WindowText); + painter->drawText(textRect, track, textOpt); + if (showTrackDuration) { + painter->drawText(duratioRect, duration, QTextOption(Qt::AlignVCenter|Qt::AlignRight)); + } + + if ((option.state & QStyle::State_MouseOver) && hasActions(index, actLevel)) { + drawIcons(painter, option.rect, true, rtl, false, index); + } + painter->restore(); + } + +private: + GroupedView *view; +}; + +GroupedView::GroupedView(QWidget *parent) + : TreeView(parent) + , startClosed(true) + , autoExpand(true) + , filterActive(false) + , currentAlbum(Song::constNullKey) +{ + setContextMenuPolicy(Qt::CustomContextMenu); + setAcceptDrops(true); + setDragDropOverwriteMode(false); + setDragDropMode(QAbstractItemView::DragDrop); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setDropIndicatorShown(true); + setHeaderHidden(true); + setRootIsDecorated(false); + setSelectionBehavior(SelectRows); + connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); + setStyleSheet("QTreeView::branch { border: 0px; }"); +} + +GroupedView::~GroupedView() +{ +} + +void GroupedView::init(QAction *a1, QAction *a2, QAction *t, int actionLevel) +{ + setItemDelegate(new GroupedViewDelegate(this, a1, a2, t, actionLevel)); +} + +void GroupedView::setFilterActive(bool f) +{ + if (f==filterActive) { + return; + } + filterActive=f; + if (filterActive && model()) { + quint32 count=model()->rowCount(); + for (quint32 i=0; iindex(i, 0); + if (model()->hasChildren(idx)) { + quint32 childCount=model()->rowCount(idx); + for (quint32 c=0; cmodel()->rowCount(parent)) { + return; + } + + if (filterActive && model() && MPDState_Playing==MPDStatus::self()->state()) { + if (scroll) { + scrollTo(model()->index(row, 0, parent), QAbstractItemView::PositionAtCenter); + } + return; + } + + currentAlbum=model()->index(row, 0, parent).data(GroupedView::Role_Key).toUInt(); + updateRows(parent); + if (MPDState_Playing==MPDStatus::self()->state() && scroll) { + scrollTo(model()->index(row, 0, parent), QAbstractItemView::PositionAtCenter); + } +} + +void GroupedView::updateRows(const QModelIndex &parent) +{ + if (!model()) { + return; + } + + qint32 count=model()->rowCount(parent); + quint16 lastKey=Song::constNullKey; + quint32 collection=parent.data(GroupedView::Role_CollectionId).toUInt(); + + for (qint32 i=0; iindex(i, 0, parent).data(GroupedView::Role_Key).toUInt(); + bool hide=key==lastKey && + !(key==currentAlbum && autoExpand) && + ( ( startClosed && !controlledAlbums[collection].contains(key)) || + ( !startClosed && controlledAlbums[collection].contains(key))); + setRowHidden(i, parent, hide); + lastKey=key; + } +} + +void GroupedView::updateCollectionRows() +{ + if (!model()) { + return; + } + + currentAlbum=Song::constNullKey; + qint32 count=model()->rowCount(); + for (int i=0; iindex(i, 0)); + } +} + +void GroupedView::toggle(const QModelIndex &idx) +{ + quint16 indexKey=idx.data(GroupedView::Role_Key).toUInt(); + + if (indexKey==currentAlbum && autoExpand) { + return; + } + + quint32 collection=idx.data(GroupedView::Role_CollectionId).toUInt(); + bool toBeHidden=false; + if (controlledAlbums[collection].contains(indexKey)) { + controlledAlbums[collection].remove(indexKey); + toBeHidden=startClosed; + } else { + controlledAlbums[collection].insert(indexKey); + toBeHidden=!startClosed; + } + + if (model()) { + quint32 count=model()->rowCount(idx.parent()); + for (quint32 i=idx.row()+1; iindex(i, 0, idx.parent()).data(GroupedView::Role_Key).toUInt(); + if (indexKey==key) { + setRowHidden(i, idx.parent(), toBeHidden); + } else { + break; + } + } + } +} + +// Calculate list of selected indexes. If a collapsed album is selected, we also pretend all of its tracks +// are selected. +QModelIndexList GroupedView::selectedIndexes() const +{ + QModelIndexList indexes = TreeView::selectedIndexes(); + QModelIndexList allIndexes; + quint32 rowCount=model()->rowCount(); + + foreach (const QModelIndex &idx, indexes) { + quint16 key=idx.data(GroupedView::Role_Key).toUInt(); + allIndexes.append(idx); + if (!isExpanded(key)) { + for (quint32 i=idx.row()+1; irect().contains(event->pos())) { + QModelIndex idx=TreeView::indexAt(event->pos()); + if (idx.isValid() && isAlbumHeader(idx)) { + QRect rect(visualRect(idx)); + if (event->pos().y()>(rect.y()+(rect.height()/2))) { + quint16 key=idx.data(GroupedView::Role_Key).toUInt(); + if (!isExpanded(key)) { + parent=idx.parent(); + quint32 rowCount=model()->rowCount(parent); + for (quint32 i=idx.row()+1; isetData(parent, dropRowAdjust, Role_DropAdjust); + } + } + } + } + + TreeView::dropEvent(event); + model()->setData(parent, 0, Role_DropAdjust); +} + +void GroupedView::coverRetrieved(const QString &artist, const QString &album) +{ + if (filterActive) { + return; + } + + quint32 count=model()->rowCount(); + quint16 lastKey=Song::constNullKey; + + for (quint32 i=0; iindex(i, 0); + if (!index.isValid()) { + continue; + } + if (model()->hasChildren(index)) { + quint32 childCount=model()->rowCount(index); + for (quint32 c=0; cindex(i, 0, index); + if (!child.isValid()) { + continue; + } + quint16 key=child.data(GroupedView::Role_Key).toUInt(); + + if (key!=lastKey && !isRowHidden(i, QModelIndex())) { + Song song=child.data(GroupedView::Role_Song).value(); + if (song.albumArtist()==artist && song.album==album) { + dataChanged(child, child); + } + } + lastKey=key; + } + } else { + quint16 key=index.data(GroupedView::Role_Key).toUInt(); + + if (key!=lastKey && !isRowHidden(i, QModelIndex())) { + Song song=index.data(GroupedView::Role_Song).value(); + if (song.albumArtist()==artist && song.album==album) { + dataChanged(index, index); + } + } + lastKey=key; + } + } +} + +void GroupedView::collectionRemoved(quint32 key) +{ + controlledAlbums.remove(key); +} + +void GroupedView::itemClicked(const QModelIndex &idx) +{ + if (isAlbumHeader(idx)) { + QRect indexRect(visualRect(idx)); + QRect icon(indexRect.x()+constBorder+4, indexRect.y()+constBorder+((indexRect.height()-constCoverSize)/2), + constCoverSize, constCoverSize); + QRect header(indexRect); + header.setHeight(header.height()/2); + header.moveTo(viewport()->mapToGlobal(QPoint(header.x(), header.y()))); + icon.moveTo(viewport()->mapToGlobal(QPoint(icon.x(), icon.y()))); + if (icon.contains(QCursor::pos())) { + toggle(idx); + } else if (header.contains(QCursor::pos())) { + QModelIndexList list; + unsigned int key=idx.data(GroupedView::Role_Key).toUInt(); + QModelIndex i=idx.sibling(idx.row()+1, 0); + QModelIndexList sel=selectedIndexes(); + QItemSelectionModel *selModel=selectionModel(); + QModelIndexList unsel; + + while (i.isValid() && i.data(GroupedView::Role_Key).toUInt()==key) { + #if 0 // The following does not seem to work from the grouped playlist view - the 2nd row never get selected! + if (!sel.contains(i)) { + unsel.append(i); + } + #else + unsel.append(i); + #endif + list.append(i); + i=i.sibling(i.row()+1, 0); + } + if (list.count()) { + #if 0 // Commendted out as (as noted below) unselection is not working, and we always add to 'unsel' above (because of playlists) + if (unsel.isEmpty()) { // TODO: This is not working!!! + foreach(const QModelIndex &i, list) { + selModel->select(i, QItemSelectionModel::Deselect|QItemSelectionModel::Rows); + } + } else { + foreach(const QModelIndex &i, unsel) { + selModel->select(i, QItemSelectionModel::Select|QItemSelectionModel::Rows); + } + } + #else + if (!unsel.isEmpty()) { + foreach(const QModelIndex &i, unsel) { + selModel->select(i, QItemSelectionModel::Select|QItemSelectionModel::Rows); + } + } + #endif + } + } + } +} diff --git a/widgets/groupedview.h b/widgets/groupedview.h new file mode 100644 index 000000000..ec3d279a2 --- /dev/null +++ b/widgets/groupedview.h @@ -0,0 +1,93 @@ +/* + * Cantata + * + * Copyright (c) 2011-2012 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. + */ + +#ifndef GROUPEDVIEW_H +#define GROUPEDVIEW_H + +#include +#include "treeview.h" + +class GroupedView : public TreeView +{ + Q_OBJECT + +public: + enum Status { + State_Default, + State_Playing, + State_Paused, + State_Stopped + }; + + enum Roles { + Role_Key = Qt::UserRole+512, + Role_Id, + Role_Song, + Role_AlbumDuration, + Role_Status, + Role_CurrentStatus, + Role_SongCount, + + Role_IsCollection, + Role_CollectionId, + Role_DropAdjust + }; + + GroupedView(QWidget *parent=0); + virtual ~GroupedView(); + + void init(QAction *a1=0, QAction *a2=0, QAction *t=0, int actionLevel=0); + void setFilterActive(bool f); + bool isFilterActive() const { return filterActive; } + void setAutoExpand(bool ae) { autoExpand=ae; } + bool isAutoExpand() const { return autoExpand; } + void setStartClosed(bool sc); + bool isStartClosed() const { return startClosed; } + QSet getControlledAlbums(quint32 collection=0) const { return controlledAlbums[collection]; } + void setControlledAlbums(const QSet &keys, quint32 collection=0) { controlledAlbums[collection]=keys; } + void updateRows(qint32 row, bool scroll, const QModelIndex &parent=QModelIndex()); + void updateRows(const QModelIndex &parent); + void updateCollectionRows(); + bool isCurrentAlbum(quint16 key) const { return key==currentAlbum; } + bool isExpanded(quint16 key, quint32 collection=0) const { return filterActive || + (autoExpand && currentAlbum==key) || + (startClosed && controlledAlbums[collection].contains(key)) || + (!startClosed && !controlledAlbums[collection].contains(key)); } + void toggle(const QModelIndex &idx); + QModelIndexList selectedIndexes() const; + void dropEvent(QDropEvent *event); + void coverRetrieved(const QString &artist, const QString &album); + void collectionRemoved(quint32 key); + +private Q_SLOTS: + void itemClicked(const QModelIndex &index); + +private: + bool startClosed; + bool autoExpand; + bool filterActive; + quint16 currentAlbum; + QMap > controlledAlbums; +}; + +#endif diff --git a/widgets/itemview.cpp b/widgets/itemview.cpp index 7230468b3..1c5eedf19 100644 --- a/widgets/itemview.cpp +++ b/widgets/itemview.cpp @@ -22,12 +22,13 @@ */ #include "itemview.h" +#include "groupedview.h" #include "mainwindow.h" #include "covers.h" #include "proxymodel.h" +#include "actionitemdelegate.h" #include #include -#include #include #include #include @@ -57,38 +58,6 @@ bool EscapeKeyEventHandler::eventFilter(QObject *obj, QEvent *event) return QObject::eventFilter(obj, event); } -// static QPainterPath buildPath(const QRectF &r, double radius) -// { -// QPainterPath path; -// double diameter(radius*2); -// -// path.moveTo(r.x()+r.width(), r.y()+r.height()-radius); -// path.arcTo(r.x()+r.width()-diameter, r.y(), diameter, diameter, 0, 90); -// path.arcTo(r.x(), r.y(), diameter, diameter, 90, 90); -// path.arcTo(r.x(), r.y()+r.height()-diameter, diameter, diameter, 180, 90); -// path.arcTo(r.x()+r.width()-diameter, r.y()+r.height()-diameter, diameter, diameter, 270, 90); -// return path; -// } - -static void drawBgnd(QPainter *painter, const QRect &rx) -{ - QRectF r(rx.x()-0.5, rx.y()-0.5, rx.width()+1, rx.height()+1); - QPainterPath p;//(buildPath(r, r.width()/2.0)); - QColor c(Qt::white); - - p.addEllipse(r); - painter->setRenderHint(QPainter::Antialiasing, true); - c.setAlphaF(0.75); - painter->fillPath(p, c); -// c.setAlphaF(0.95); -// painter->setPen(c); -// painter->drawPath(p); - painter->setRenderHint(QPainter::Antialiasing, false); -} - -static const int constBorder = 1; -static const int constActionBorder = 4; -static const int constActionIconSize=16; static const int constImageSize=22; static const int constDevImageSize=32; @@ -97,69 +66,11 @@ static inline double subTextAlpha(bool selected) return selected ? 0.7 : 0.6; } -QRect calcActionRect(bool rtl, bool iconMode, const QRect &rect) -{ - return rtl - ? iconMode - ? QRect(rect.x()+constActionBorder, - rect.y()+constActionBorder, - constActionIconSize, constActionIconSize) - : QRect(rect.x()+constActionBorder, - rect.y()+((rect.height()-constActionIconSize)/2), - constActionIconSize, constActionIconSize) - : iconMode - ? QRect(rect.x()+rect.width()-(constActionIconSize+constActionBorder), - rect.y()+constActionBorder, - constActionIconSize, constActionIconSize) - : QRect(rect.x()+rect.width()-(constActionIconSize+constActionBorder), - rect.y()+((rect.height()-constActionIconSize)/2), - constActionIconSize, constActionIconSize); -} - -static void adjustActionRect(bool rtl, bool iconMode, QRect &rect) -{ - if (rtl) { - if (iconMode) { - rect.adjust(0, constActionIconSize+constActionBorder, 0, constActionIconSize+constActionBorder); - } else { - rect.adjust(constActionIconSize+constActionBorder, 0, constActionIconSize+constActionBorder, 0); - } - } else { - if (iconMode) { - rect.adjust(0, constActionIconSize+constActionBorder, 0, constActionIconSize+constActionBorder); - } else { - rect.adjust(-(constActionIconSize+constActionBorder), 0, -(constActionIconSize+constActionBorder), 0); - } - } -} - -static bool hasActions(const QModelIndex &index, int actLevel) -{ - if (actLevel<0) { - return true; - } - - int level=0; - - QModelIndex idx=index; - while(idx.parent().isValid()) { - if (++level>actLevel) { - return false; - } - idx=idx.parent(); - } - return true; -} - -class ListDelegate : public QStyledItemDelegate +class ListDelegate : public ActionItemDelegate { public: - ListDelegate(QObject *p, QAction *a1, QAction *a2, QAction *t, int actionLevel) - : QStyledItemDelegate(p) - , act1(a1) - , act2(a2) - , toggle(t) - , actLevel(actionLevel) + ListDelegate(QAbstractItemView *p, QAction *a1, QAction *a2, QAction *t, int actionLevel) + : ActionItemDelegate(p, a1, a2, t, actionLevel) { } @@ -319,62 +230,12 @@ public: painter->restore(); } - - void drawIcons(QPainter *painter, const QRect &r, bool mouseOver, bool rtl, bool iconMode, const QModelIndex &index) const - { - double opacity=painter->opacity(); - if (!mouseOver) { - painter->setOpacity(opacity*0.2); - } - - QRect actionRect=calcActionRect(rtl, iconMode, r); - if (act1) { - QPixmap pix=act1->icon().pixmap(QSize(constActionIconSize, constActionIconSize)); - if (!pix.isNull() && actionRect.width()>=pix.width()/* && r.x()>=0 && r.y()>=0*/) { - drawBgnd(painter, actionRect); - painter->drawPixmap(actionRect.x()+(actionRect.width()-pix.width())/2, - actionRect.y()+(actionRect.height()-pix.height())/2, pix); - } - } - - if (act1 && act2) { - adjustActionRect(rtl, iconMode, actionRect); - QPixmap pix=act2->icon().pixmap(QSize(constActionIconSize, constActionIconSize)); - if (!pix.isNull() && actionRect.width()>=pix.width()/* && r.x()>=0 && r.y()>=0*/) { - drawBgnd(painter, actionRect); - painter->drawPixmap(actionRect.x()+(actionRect.width()-pix.width())/2, - actionRect.y()+(actionRect.height()-pix.height())/2, pix); - } - } - - if (act1 && act2 && toggle) { - QString iconName=index.data(ItemView::Role_ToggleIconName).toString(); - - if (!iconName.isEmpty()) { - adjustActionRect(rtl, iconMode, actionRect); - QPixmap pix=QIcon::fromTheme(iconName).pixmap(QSize(constActionIconSize, constActionIconSize)); - if (!pix.isNull() && actionRect.width()>=pix.width()/* && r.x()>=0 && r.y()>=0*/) { - drawBgnd(painter, actionRect); - painter->drawPixmap(actionRect.x()+(actionRect.width()-pix.width())/2, - actionRect.y()+(actionRect.height()-pix.height())/2, pix); - } - } - } - if (!mouseOver) { - painter->setOpacity(opacity); - } - } - - QAction *act1; - QAction *act2; - QAction *toggle; - int actLevel; }; class TreeDelegate : public ListDelegate { public: - TreeDelegate(QObject *p, QAction *a1, QAction *a2, QAction *t, int actionLevel) + TreeDelegate(QAbstractItemView *p, QAction *a1, QAction *a2, QAction *t, int actionLevel) : ListDelegate(p, a1, a2, t, actionLevel) { } @@ -474,6 +335,7 @@ ItemView::ItemView(QWidget *p) , toggle(0) , currentLevel(0) , mode(Mode_Tree) + , groupedView(0) { setupUi(this); #ifdef ENABLE_KDE_SUPPORT @@ -495,6 +357,22 @@ ItemView::~ItemView() { } +void ItemView::allowGroupedView() +{ + if (!groupedView) { + groupedView=new GroupedView(stackedWidget); + treeLayout->addWidget(groupedView); + connect(groupedView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); + #ifdef ENABLE_KDE_SUPPORT + if (KGlobalSettings::singleClick()) { + connect(groupedView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); + } + #endif + connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); + connect(groupedView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); + } +} + void ItemView::init(QAction *a1, QAction *a2, QAction *t, int actionLevel) { if (act1 || act2 || toggle) { @@ -505,8 +383,11 @@ void ItemView::init(QAction *a1, QAction *a2, QAction *t, int actionLevel) act2=a2; toggle=t; actLevel=actionLevel; - listView->setItemDelegate(new ListDelegate(this, a1, a2, toggle, actionLevel)); - treeView->setItemDelegate(new TreeDelegate(this, a1, a2, toggle, actionLevel)); + listView->setItemDelegate(new ListDelegate(listView, a1, a2, toggle, actionLevel)); + treeView->setItemDelegate(new TreeDelegate(treeView, a1, a2, toggle, actionLevel)); + if (groupedView) { + groupedView->init(a1, a2, toggle, actionLevel); + } connect(treeSearch, SIGNAL(returnPressed()), this, SLOT(delaySearchItems())); connect(treeSearch, SIGNAL(textChanged(const QString)), this, SLOT(delaySearchItems())); connect(listSearch, SIGNAL(returnPressed()), this, SLOT(delaySearchItems())); @@ -532,10 +413,17 @@ void ItemView::addAction(QAction *act) { treeView->addAction(act); listView->addAction(act); + if (groupedView) { + groupedView->addAction(act); + } } void ItemView::setMode(Mode m) { + if (m<0 || m>Mode_GroupedTree || (Mode_GroupedTree==m && !groupedView)) { + m=Mode_Tree; + } + if (m==mode) { return; } @@ -546,9 +434,24 @@ void ItemView::setMode(Mode m) if (Mode_Tree==mode) { treeView->setModel(itemModel); listView->setModel(0); + if (groupedView) { + groupedView->setHidden(true); + groupedView->setModel(0); + } + treeView->setHidden(false); + itemModel->setRootIndex(QModelIndex()); + } else if (Mode_GroupedTree==mode) { + treeView->setModel(0); + listView->setModel(0); + groupedView->setHidden(false); + treeView->setHidden(true); + groupedView->setModel(itemModel); itemModel->setRootIndex(QModelIndex()); } else { treeView->setModel(0); + if (groupedView) { + groupedView->setModel(0); + } listView->setModel(itemModel); setLevel(0); listView->setRootIndex(QModelIndex()); @@ -561,7 +464,7 @@ void ItemView::setMode(Mode m) listView->setWordWrap(false); } } - stackedWidget->setCurrentIndex(Mode_Tree==mode ? 0 : 1); + stackedWidget->setCurrentIndex(Mode_Tree==mode || Mode_GroupedTree==mode ? 0 : 1); #ifdef ENABLE_KDE_SUPPORT if (spinner) { spinner->setWidget(view()->viewport()); @@ -574,7 +477,12 @@ void ItemView::setMode(Mode m) QModelIndexList ItemView::selectedIndexes() const { - return Mode_Tree==mode ? treeView->selectedIndexes() : listView->selectedIndexes(); + if (Mode_Tree==mode) { + return treeView->selectedIndexes(); + } else if(Mode_GroupedTree==mode) { + return groupedView->selectedIndexes(); + } + return listView->selectedIndexes(); } void ItemView::setLevel(int l, bool haveChildren) @@ -622,7 +530,13 @@ void ItemView::setLevel(int l, bool haveChildren) QAbstractItemView * ItemView::view() const { - return Mode_Tree==mode ? (QAbstractItemView *)treeView : (QAbstractItemView *)listView; + if (Mode_Tree==mode) { + return treeView; + } else if(Mode_GroupedTree==mode) { + return groupedView; + } else { + return listView; + } } void ItemView::setModel(ProxyModel *m) @@ -633,7 +547,7 @@ void ItemView::setModel(ProxyModel *m) QString ItemView::searchText() const { - return Mode_Tree==mode ? treeSearch->text() : listSearch->text(); + return Mode_Tree==mode || Mode_GroupedTree==mode ? treeSearch->text() : listSearch->text(); } void ItemView::setTopText(const QString &text) @@ -681,10 +595,50 @@ void ItemView::setGridSize(const QSize &sz) iconGridSize=sz; } +void ItemView::update() +{ + if (Mode_Tree==mode) { + treeView->update(); + } else if(Mode_GroupedTree==mode) { + groupedView->update(); + } else { + listView->update(); + } +} + void ItemView::setDeleteAction(QAction *act) { listView->installEventFilter(new DeleteKeyEventHandler(listView, act)); treeView->installEventFilter(new DeleteKeyEventHandler(treeView, act)); + if (groupedView) { + groupedView->installEventFilter(new DeleteKeyEventHandler(treeView, act)); + } +} + +void ItemView::setStartClosed(bool sc) +{ + if (groupedView) { + groupedView->setStartClosed(sc); + } +} + +bool ItemView::isStartClosed() +{ + return groupedView ? groupedView->isStartClosed() : false; +} + +void ItemView::updateRows() +{ + if (groupedView) { + groupedView->updateCollectionRows(); + } +} + +void ItemView::updateRows(const QModelIndex &idx) +{ + if (groupedView) { + groupedView->updateRows(idx); + } } void ItemView::showSpinner() @@ -711,9 +665,16 @@ void ItemView::hideSpinner() #endif } +void ItemView::collectionRemoved(quint32 key) +{ + if (groupedView) { + groupedView->collectionRemoved(key); + } +} + void ItemView::backActivated() { - if (Mode_Tree==mode) { + if (Mode_Tree==mode || Mode_GroupedTree==mode) { return; } setLevel(currentLevel-1); @@ -740,9 +701,9 @@ QAction * ItemView::getAction(const QModelIndex &index) bool haveToggle = toggle && !index.data(ItemView::Role_ToggleIconName).toString().isEmpty(); if (Mode_Tree!=mode || showCapacity) { if (iconMode) { - rect.adjust(constBorder, constBorder, -constBorder, -constBorder); + rect.adjust(ActionItemDelegate::constBorder, ActionItemDelegate::constBorder, -ActionItemDelegate::constBorder, -ActionItemDelegate::constBorder); } else { - rect.adjust(constBorder+3, 0, -(constBorder+3), 0); + rect.adjust(ActionItemDelegate::constBorder+3, 0, -(ActionItemDelegate::constBorder+3), 0); } } @@ -751,22 +712,22 @@ QAction * ItemView::getAction(const QModelIndex &index) rect.adjust(0, 0, 0, -(textHeight+8)); } - QRect actionRect=calcActionRect(rtl, iconMode, rect); + QRect actionRect=ActionItemDelegate::calcActionRect(rtl, iconMode, rect); QRect actionRect2(actionRect); - adjustActionRect(rtl, iconMode, actionRect2); + ActionItemDelegate::adjustActionRect(rtl, iconMode, actionRect2); if (act1 && actionRect.contains(QCursor::pos())) { return act1; } - adjustActionRect(rtl, iconMode, actionRect); + ActionItemDelegate::adjustActionRect(rtl, iconMode, actionRect); if (act2 && actionRect.contains(QCursor::pos())) { return act2; } if (haveToggle) { - adjustActionRect(rtl, iconMode, actionRect); + ActionItemDelegate::adjustActionRect(rtl, iconMode, actionRect); if (toggle && actionRect.contains(QCursor::pos())) { return toggle; @@ -778,7 +739,7 @@ QAction * ItemView::getAction(const QModelIndex &index) void ItemView::itemClicked(const QModelIndex &index) { - if (hasActions(index, actLevel)) { + if (ActionItemDelegate::hasActions(index, actLevel)) { QAction *act=getAction(index); if (act) { act->trigger(); @@ -788,7 +749,7 @@ void ItemView::itemClicked(const QModelIndex &index) void ItemView::itemActivated(const QModelIndex &index) { - if (hasActions(index, actLevel)) { + if (ActionItemDelegate::hasActions(index, actLevel)) { QAction *act=getAction(index); if (act) { return; @@ -797,6 +758,10 @@ void ItemView::itemActivated(const QModelIndex &index) if (Mode_Tree==mode) { treeView->setExpanded(index, !treeView->isExpanded(index)); + } else if (Mode_GroupedTree==mode) { + if (!index.parent().isValid()) { + groupedView->setExpanded(index, !groupedView->TreeView::isExpanded(index)); + } } else if (index.isValid() && index.child(0, 0).isValid()) { prevTopIndex=listView->indexAt(QPoint(0, 0)); if (qobject_cast(listView->model())) { @@ -820,7 +785,8 @@ void ItemView::itemActivated(const QModelIndex &index) void ItemView::delaySearchItems() { - if ((Mode_Tree==mode && treeSearch->text().isEmpty()) || (Mode_Tree!=mode && listSearch->text().isEmpty())) { + bool isTree=Mode_Tree==mode || Mode_GroupedTree==mode; + if ((isTree && treeSearch->text().isEmpty()) || (!isTree && listSearch->text().isEmpty())) { if (searchTimer) { searchTimer->stop(); } diff --git a/widgets/itemview.h b/widgets/itemview.h index c7a90a617..26beb6ecb 100644 --- a/widgets/itemview.h +++ b/widgets/itemview.h @@ -34,6 +34,7 @@ class KPixmapSequenceOverlayPainter; class ProxyModel; class QAction; class QTimer; +class GroupedView; class EscapeKeyEventHandler : public QObject { @@ -59,6 +60,7 @@ public: Mode_Tree, Mode_List, Mode_IconTop, + Mode_GroupedTree, Mode_Count }; @@ -78,6 +80,7 @@ public: ItemView(QWidget *p); virtual ~ItemView(); + void allowGroupedView(); void init(QAction *a1, QAction *a2, int actionLevel=-1) { init(a1, a2, 0, actionLevel); } void init(QAction *a1, QAction *a2, QAction *toggle, int actionLevel=-1); void addAction(QAction *act); @@ -96,13 +99,19 @@ public: void setDragDropMode(QAbstractItemView::DragDropMode v); void setGridSize(const QSize &sz); QSize gridSize() const { return listView->gridSize(); } - void update() { Mode_Tree==mode ? treeView->update() : listView->update(); } + void update(); void setDeleteAction(QAction *act); void setRootIsDecorated(bool v) { treeView->setRootIsDecorated(v); } + void setStartClosed(bool sc); + bool isStartClosed(); + void updateRows(); + void updateRows(const QModelIndex &idx); + public Q_SLOTS: void showSpinner(); void hideSpinner(); + void collectionRemoved(quint32 key); Q_SIGNALS: void searchItems(); @@ -133,6 +142,7 @@ private: QModelIndex prevTopIndex; QSize iconGridSize; QSize listGridSize; + GroupedView *groupedView; #ifdef ENABLE_KDE_SUPPORT bool spinnerActive; KPixmapSequenceOverlayPainter *spinner; diff --git a/widgets/itemview.ui b/widgets/itemview.ui index 6fba90270..67e03d8b5 100644 --- a/widgets/itemview.ui +++ b/widgets/itemview.ui @@ -23,7 +23,7 @@ 0 - + 0 diff --git a/widgets/playqueueview.cpp b/widgets/playqueueview.cpp index 6dae7cf1f..f67349d97 100644 --- a/widgets/playqueueview.cpp +++ b/widgets/playqueueview.cpp @@ -24,612 +24,18 @@ #include "playqueueview.h" #include "playqueuemodel.h" #include "covers.h" -#include "listview.h" +#include "groupedview.h" #include "treeview.h" #include "settings.h" #include "mpdstatus.h" #include "httpserver.h" -#include -#include -#include -#include -#include #include #include #include -#include -#include #ifdef ENABLE_KDE_SUPPORT #include #endif -static const int constCoverSize=32; -static const int constIconSize=16; -static const int constBorder=1; - -enum Type { - AlbumHeader, - AlbumTrack -}; - -/* - * QScrollView seems to have *many* issues when items are hidden! And sometimes when not... - * 1. scrollTo() is broken when we have hidden items (see https://bugreports.qt-project.org/browse/QTBUG-21115) - * 2. The model using beginRemoveRows() seems to cause the view to scroll back to the top! - * - * So, as a work-around, just use a treeview! - */ -// #define USE_LISTVIEW_FOR_LIST - -#ifdef USE_LISTVIEW_FOR_LIST -#define LIST_PARENT ListView -#define FIX_SCROLL_TO -#define ROW_HIDDEN(A) isRowHidden(A) -#define SET_ROW_HIDDEN(A, B) setRowHidden(A, B) -#define LIST_DEFAULTS setUniformItemSizes(false); -#else -#define LIST_PARENT TreeView -#define ROW_HIDDEN(A) isRowHidden(A, QModelIndex()) -#define SET_ROW_HIDDEN(A, B) setRowHidden(A, QModelIndex(), B) -#define LIST_DEFAULTS \ - setRootIsDecorated(false); \ - setHeaderHidden(true); \ - setIndentation(0); \ - setItemsExpandable(false); \ - setExpandsOnDoubleClick(false); -#endif - -class PlayQueueListView : public LIST_PARENT -{ -public: - PlayQueueListView(QWidget *parent=0); - virtual ~PlayQueueListView(); - - void setFilterActive(bool f); - bool isFilterActive() const { return filterActive; } - void setAutoExpand(bool ae) { autoExpand=ae; } - bool isAutoExpand() const { return autoExpand; } - void setStartClosed(bool sc); - bool isStartClosed() const { return startClosed; } - QSet getControlledSongIds() const { return controlledSongIds; } - void setControlled(const QSet &keys) { controlledAlbums=keys; } - void updateRows(qint32 row, bool scroll); - bool isCurrentAlbum(quint16 key) const { return key==currentAlbum; } - bool isExpanded(quint16 key) const { return filterActive || - (autoExpand && currentAlbum==key) || - (startClosed && controlledAlbums.contains(key)) || - (!startClosed && !controlledAlbums.contains(key)); } - void toggle(const QModelIndex &idx); - QModelIndexList selectedIndexes() const; - void dropEvent(QDropEvent *event); - void coverRetrieved(const QString &artist, const QString &album); - -private: - bool startClosed; - bool autoExpand; - bool filterActive; - quint16 currentAlbum; - QSet controlledSongIds; - QSet controlledAlbums; -}; - -static Type getType(const QModelIndex &index) -{ - QModelIndex prev=index.row()>0 ? index.sibling(index.row()-1, 0) : QModelIndex(); - quint16 thisKey=index.data(PlayQueueView::Role_Key).toUInt(); - quint16 prevKey=prev.isValid() ? prev.data(PlayQueueView::Role_Key).toUInt() : Song::constNullKey; - - return thisKey==prevKey ? AlbumTrack : AlbumHeader; -} - -class PlayQueueDelegate : public QStyledItemDelegate -{ -public: - PlayQueueDelegate(PlayQueueListView *p) - : QStyledItemDelegate(p) - , view(p) - { - } - - virtual ~PlayQueueDelegate() - { - } - - QSize sizeHint(Type type) const - { - int textHeight = QApplication::fontMetrics().height(); - - if (AlbumHeader==type) { - return QSize(64, qMax(constCoverSize, (qMax(constIconSize, textHeight)*2))+(3*constBorder)); - } - return QSize(64, qMax(constIconSize, textHeight)+(2*constBorder)); - } - - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const - { - if (0==index.column()) { - return sizeHint(getType(index)); - } - return QStyledItemDelegate::sizeHint(option, index); - } - - inline QString formatNumber(int num) const - { - QString text=QString::number(num); - return num<10 ? "0"+text : text; - } - - static void drawFadedLine(QPainter *p, const QRect &r, const QColor &col) - { - QPoint start(r.x(), r.y()); - QPoint end(r.x()+r.width()-1, r.y()); - QLinearGradient grad(start, end); - QColor c(col); - c.setAlphaF(0.45); - QColor fade(c); - const int fadeSize=64; - double fadePos=1.0-((r.width()-fadeSize)/(r.width()*1.0)); - bool rtl=Qt::RightToLeft==QApplication::layoutDirection(); - - fade.setAlphaF(0.0); - grad.setColorAt(0, rtl ? fade : c); - if(fadePos>=0 && fadePos<=1.0) { - if (rtl) { - grad.setColorAt(fadePos, c); - } else { - grad.setColorAt(1.0-fadePos, c); - } - } - grad.setColorAt(1, rtl ? c : fade); - p->save(); - p->setPen(QPen(QBrush(grad), 1)); - p->drawLine(start, end); - p->restore(); - } - - static QPainterPath buildPath(const QRectF &r, double radius) - { - QPainterPath path; - double diameter(radius*2); - - path.moveTo(r.x()+r.width(), r.y()+r.height()-radius); - path.arcTo(r.x()+r.width()-diameter, r.y(), diameter, diameter, 0, 90); - path.arcTo(r.x(), r.y(), diameter, diameter, 90, 90); - path.arcTo(r.x(), r.y()+r.height()-diameter, diameter, diameter, 180, 90); - path.arcTo(r.x()+r.width()-diameter, r.y()+r.height()-diameter, diameter, diameter, 270, 90); - return path; - } - - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const - { - if (!index.isValid()) { - return; - } - - Type type=getType(index); - Song song=index.data(PlayQueueView::Role_Song).value(); - int state=index.data(PlayQueueView::Role_Status).toInt(); - - if (AlbumHeader==type) { - QStyleOptionViewItem opt(option); - painter->save(); - painter->setOpacity(0.75); - QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0L); - painter->restore(); - painter->save(); - painter->setClipRect(option.rect.adjusted(0, option.rect.height()/2, 0, 0), Qt::IntersectClip); - QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0L); - painter->restore(); - if (!state && !view->isExpanded(song.key) && view->isCurrentAlbum(song.key)) { - state=index.data(PlayQueueView::Role_CurrentStatus).toInt(); - } - } else { - QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0L); - } - QString title; - QString track; - QString duration=Song::formattedTime(song.time); - bool stream=song.isStream(); - QString trackTitle=!song.albumartist.isEmpty() && song.albumartist != song.artist - ? song.title + " - " + song.artist - : song.title; - QFont f(QApplication::font()); - if (stream) { - f.setItalic(true); - } - QFontMetrics fm(f); - int textHeight=fm.height(); - - if(AlbumHeader==type) { - QString album=song.album; - - if (stream && album.isEmpty() && song.albumArtist().isEmpty()) { - title=song.file; - if (song.title.isEmpty()) { - trackTitle=QString(); - } - } - if (song.year>0) { - album+=QString(" (%1)").arg(song.year); - } - if (title.isEmpty()) { - #ifdef ENABLE_KDE_SUPPORT - title=i18nc("artist - album", "%1 - %2", song.albumArtist(), album); - #else - title=tr("%1 - %2").arg(song.albumArtist()).arg(album); - #endif - track=formatNumber(song.track)+QChar(' ')+trackTitle; - } - } else { - track=formatNumber(song.track)+QChar(' ')+trackTitle; - } - - painter->save(); - painter->setFont(f); - QColor col(option.palette.color(option.state&QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); - QTextOption textOpt(Qt::AlignVCenter); - QRect r(option.rect.adjusted(constBorder+4, constBorder, -(constBorder+4), -constBorder)); - bool rtl=Qt::RightToLeft==QApplication::layoutDirection(); - - if (state) { - QRectF border(option.rect.x()+1.5, option.rect.y()+1.5, option.rect.width()-3, option.rect.height()-3); - if (!title.isEmpty()) { - border.adjust(0, textHeight+constBorder, 0, 0); - } - QLinearGradient g(border.topLeft(), border.bottomLeft()); - QColor gradCol(QApplication::palette().color(QPalette::Highlight)); - gradCol.setAlphaF(option.state&QStyle::State_Selected ? 0.6 : 0.45); - g.setColorAt(0, gradCol.dark(165)); - g.setColorAt(1, gradCol.light(165)); - painter->setRenderHint(QPainter::Antialiasing, true); - painter->fillPath(buildPath(border, 3), g); - painter->setPen(QPen(gradCol, 1)); - painter->drawPath(buildPath(border.adjusted(-1, -1, 1, 1), 3.5)); - painter->setPen(QPen(QApplication::palette().color(QPalette::HighlightedText), 1)); - painter->drawPath(buildPath(border, 3)); - painter->setRenderHint(QPainter::Antialiasing, false); - } - - painter->setPen(col); - bool showTrackDuration=true; - - if (AlbumHeader==type) { - // Draw cover... - QPixmap *cover=Covers::self()->get(song, constCoverSize); - QPixmap pix=cover ? *cover : QIcon::fromTheme(stream ? "applications-internet" : "media-optical-audio").pixmap(constCoverSize, constCoverSize); - - if (rtl) { - painter->drawPixmap(r.x()+r.width()-(pix.width()+constBorder), r.y()+((r.height()-pix.height())/2), pix.width(), pix.height(), pix); - r.adjust(0, 0, -(constCoverSize+constBorder), 0); - } else { - painter->drawPixmap(r.x()-2, r.y()+((r.height()-pix.height())/2), pix.width(), pix.height(), pix); - r.adjust(constCoverSize+constBorder, 0, 0, 0); - } - QString totalDuration=Song::formattedTime(index.data(PlayQueueView::Role_AlbumDuration).toUInt()); - QRect duratioRect(r.x(), r.y(), r.width(), textHeight); - int totalDurationWidth=fm.width(totalDuration)+8; - QRect textRect(r.x(), r.y(), r.width()-totalDurationWidth, textHeight); - QFont tf(f); - tf.setBold(true); - tf.setItalic(true); - title = QFontMetrics(tf).elidedText(title, Qt::ElideRight, textRect.width(), QPalette::WindowText); - painter->setFont(tf); - painter->drawText(textRect, title, textOpt); - painter->drawText(duratioRect, totalDuration, QTextOption(Qt::AlignVCenter|Qt::AlignRight)); - drawFadedLine(painter, r.adjusted(0, (r.height()/2)-1, 0, 0), col); - r.adjust(0, textHeight+constBorder, 0, 0); - r=QRect(r.x(), r.y()+r.height()-(textHeight+1), r.width(), textHeight); - painter->setFont(f); - if (rtl) { - r.adjust(0, 0, (constCoverSize+constBorder), 0); - } - - if (!view->isExpanded(song.key)) { - showTrackDuration=false; - #ifdef ENABLE_KDE_SUPPORT - track=i18np("1 Track", "%1 Tracks", index.data(PlayQueueView::Role_SongCount).toUInt()); - #else - track=tr("%1 Track(s)").arg(index.data(PlayQueueView::Role_SongCount).toUInt()); - #endif - } - } else if (!rtl) { - r.adjust(constCoverSize+constBorder, 0, 0, 0); - } - - if (state) { - int size=9; - QRect ir(r.x()-(size+6), r.y()+(((r.height()-size)/2.0)+0.5), size, size); - switch (state) { - case PlayQueueView::State_Stopped: - painter->fillRect(ir, Qt::white); - painter->fillRect(ir.adjusted(1, 1, -1, -1), Qt::black); - break; - case PlayQueueView::State_Paused: - painter->fillRect(ir, Qt::white); - painter->fillRect(ir.x()+1, ir.y()+1, 3, size-2, Qt::black); - painter->fillRect(ir.x()+size-4, ir.y()+1, 3, size-2, Qt::black); - break; - case PlayQueueView::State_Playing: { - ir.adjust(2, 0, -2, 0); - QPoint p1[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-4), ir.y()+4), QPoint(ir.x(), ir.y()+(ir.height()-1)), QPoint(ir.x()-2, ir.y()+(ir.height()-1)) }; - QPoint p2[5]={ QPoint(ir.x()-2, ir.y()-1), QPoint(ir.x(), ir.y()-1), QPoint(ir.x()+(size-4), ir.y()+4), QPoint(ir.x(), ir.y()+ir.height()), QPoint(ir.x()-2, ir.y()+ir.height()) }; - painter->save(); - painter->setBrush(Qt::white); - painter->setPen(Qt::white); - painter->drawPolygon(p1, 5); - painter->setBrush(Qt::black); - painter->drawPolygon(p2, 5); - painter->restore(); - break; - } - } - painter->setPen(col); - } - int durationWidth=showTrackDuration ? fm.width(duration)+8 : 0; - QRect duratioRect(r.x(), r.y(), r.width(), textHeight); - QRect textRect(r.x(), r.y(), r.width()-durationWidth, textHeight); - track = fm.elidedText(track, Qt::ElideRight, textRect.width(), QPalette::WindowText); - painter->drawText(textRect, track, textOpt); - if (showTrackDuration) { - painter->drawText(duratioRect, duration, QTextOption(Qt::AlignVCenter|Qt::AlignRight)); - } - painter->restore(); - } - -private: - PlayQueueListView *view; -}; - -PlayQueueListView::PlayQueueListView(QWidget *parent) - : LIST_PARENT(parent) - , startClosed(true) - , autoExpand(true) - , filterActive(false) - , currentAlbum(Song::constNullKey) -{ - setContextMenuPolicy(Qt::CustomContextMenu); - setAcceptDrops(true); - setDragDropOverwriteMode(false); - setDragDropMode(QAbstractItemView::DragDrop); - setSelectionMode(QAbstractItemView::ExtendedSelection); - setDropIndicatorShown(true); - LIST_DEFAULTS - setSelectionBehavior(SelectRows); - setItemDelegate(new PlayQueueDelegate(this)); -} - -PlayQueueListView::~PlayQueueListView() -{ -} - -void PlayQueueListView::setFilterActive(bool f) -{ - if (f==filterActive) { - return; - } - filterActive=f; - if (filterActive && model()) { - quint32 count=model()->rowCount(); - for (quint32 i=0; imodel()->rowCount()) { - return; - } - - if (filterActive && model() && MPDState_Playing==MPDStatus::self()->state()) { - if (scroll) { - scrollTo(model()->index(row, 0), QAbstractItemView::PositionAtCenter); - } - return; - } - - qint32 count=model()->rowCount(); - quint16 lastKey=Song::constNullKey; - - #ifdef FIX_SCROLL_TO - // scrollTo() is broken if some rows are hidden! - quint32 titleHeight=scroll ? static_cast(itemDelegate())->sizeHint(AlbumHeader).height() : 0; - quint32 trackHeight=scroll ? static_cast(itemDelegate())->sizeHint(AlbumTrack).height() : 0; - quint32 totalHeight=0; - quint32 heightToRow=0; - bool haveHidden=false; - #endif - - currentAlbum=model()->index(row, 0).data(PlayQueueView::Role_Key).toUInt(); - for (qint32 i=0; iindex(i, 0).data(PlayQueueView::Role_Key).toUInt(); - bool hide=key==lastKey && - !(key==currentAlbum && autoExpand) && - ( ( startClosed && !controlledAlbums.contains(key)) || - ( !startClosed && controlledAlbums.contains(key))); - SET_ROW_HIDDEN(i, hide); - - #ifdef FIX_SCROLL_TO - if (hide && istate() && scroll) { - #ifdef FIX_SCROLL_TO - if (!haveHidden) { - scrollTo(model()->index(row, 0), QAbstractItemView::PositionAtCenter); - } else if(totalHeight) { - unsigned int viewHeight=viewport()->size().height(); - unsigned int halfViewHeight=viewHeight/2; - unsigned int max=verticalScrollBar()->maximum(); - unsigned int min=verticalScrollBar()->minimum(); - if (heightToRowsetValue(min); - } else if ((heightToRow)>(totalHeight-halfViewHeight)) { - verticalScrollBar()->setValue(max); - } else { - unsigned int pos=min+(((max-min)*((1.0*(heightToRow-halfViewHeight))/(1.0*(totalHeight-viewHeight))))+0.5); - verticalScrollBar()->setValue(posmax ? max : pos)); - } - } - #else - scrollTo(model()->index(row, 0), QAbstractItemView::PositionAtCenter); - #endif - } -} - -void PlayQueueListView::toggle(const QModelIndex &idx) -{ - quint16 indexKey=idx.data(PlayQueueView::Role_Key).toUInt(); - - if (indexKey==currentAlbum && autoExpand) { - return; - } - - qint32 id=idx.data(PlayQueueView::Role_Id).toInt(); - bool toBeHidden=false; - if (controlledAlbums.contains(indexKey)) { - controlledAlbums.remove(indexKey); - controlledSongIds.remove(id); - toBeHidden=startClosed; - } else { - controlledAlbums.insert(indexKey); - controlledSongIds.insert(id); - toBeHidden=!startClosed; - } - - if (model()) { - quint32 count=model()->rowCount(); - for (quint32 i=idx.row()+1; iindex(i, 0).data(PlayQueueView::Role_Key).toUInt(); - if (indexKey==key) { - SET_ROW_HIDDEN(i, toBeHidden); - } else { - break; - } - } - } -} - -// Calculate list of selected indexes. If a collapsed album is selected, we also pretend all of its tracks -// are selected. -QModelIndexList PlayQueueListView::selectedIndexes() const -{ - QModelIndexList indexes = LIST_PARENT::selectedIndexes(); - QModelIndexList allIndexes; - quint32 rowCount=model()->rowCount(); - - foreach (const QModelIndex &idx, indexes) { - quint16 key=idx.data(PlayQueueView::Role_Key).toUInt(); - allIndexes.append(idx); - if (!isExpanded(key)) { - for (quint32 i=idx.row()+1; i(model()); - if (m && viewport()->rect().contains(event->pos())) { - QModelIndex idx=LIST_PARENT::indexAt(event->pos()); - if (idx.isValid() && AlbumHeader==getType(idx)) { - QRect rect(visualRect(idx)); - if (event->pos().y()>(rect.y()+(rect.height()/2))) { - quint16 key=idx.data(PlayQueueView::Role_Key).toUInt(); - if (!isExpanded(key)) { - quint32 rowCount=model()->rowCount(); - for (quint32 i=idx.row()+1; isetDropAdjust(dropRowAdjust); - } - } - } - } - - LIST_PARENT::dropEvent(event); - m->setDropAdjust(0); -} - -void PlayQueueListView::coverRetrieved(const QString &artist, const QString &album) -{ - if (filterActive) { - return; - } - - qint32 count=model()->rowCount(); - quint16 lastKey=Song::constNullKey; - - for (qint32 i=0; iindex(i, 0); - quint16 key=index.data(PlayQueueView::Role_Key).toUInt(); - - if (key!=lastKey && !ROW_HIDDEN(i)) { - Song song=index.data(PlayQueueView::Role_Song).value(); - if (song.albumArtist()==artist && song.album==album) { - dataChanged(index, index); - } - } - lastKey=key; - } -} - PlayQueueTreeView::PlayQueueTreeView(QWidget *parent) : TreeView(parent) , menu(0) @@ -752,16 +158,19 @@ void PlayQueueTreeView::toggleHeaderItem(bool visible) PlayQueueView::PlayQueueView(QWidget *parent) : QStackedWidget(parent) { - listView=new PlayQueueListView(this); + groupedView=new GroupedView(this); + groupedView->setIndentation(0); + groupedView->setItemsExpandable(false); + groupedView->setExpandsOnDoubleClick(false); + groupedView->init(); treeView=new PlayQueueTreeView(this); - addWidget(listView); + addWidget(groupedView); addWidget(treeView); setCurrentWidget(treeView); - connect(listView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool))); + connect(groupedView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool))); connect(treeView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool))); - connect(listView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &))); + connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &))); connect(treeView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &))); - connect(listView, SIGNAL(clicked(const QModelIndex &)), SLOT(itemClicked(const QModelIndex &))); connect(Covers::self(), SIGNAL(coverRetrieved(const QString &, const QString &)), this, SLOT(coverRetrieved(const QString &, const QString &))); } @@ -778,80 +187,80 @@ void PlayQueueView::saveHeader() void PlayQueueView::setGrouped(bool g) { - bool grouped=listView==currentWidget(); + bool grouped=groupedView==currentWidget(); if (g!=grouped) { if (grouped) { - treeView->setModel(listView->model()); + treeView->setModel(groupedView->model()); treeView->initHeader(); - listView->setModel(0); + groupedView->setModel(0); } else { treeView->saveHeader(); - listView->setModel(treeView->model()); + groupedView->setModel(treeView->model()); treeView->setModel(0); } grouped=g; - setCurrentWidget(grouped ? static_cast(listView) : static_cast(treeView)); + setCurrentWidget(grouped ? static_cast(groupedView) : static_cast(treeView)); } } void PlayQueueView::setAutoExpand(bool ae) { - listView->setAutoExpand(ae); + groupedView->setAutoExpand(ae); } bool PlayQueueView::isAutoExpand() const { - return listView->isAutoExpand(); + return groupedView->isAutoExpand(); } void PlayQueueView::setStartClosed(bool sc) { - listView->setStartClosed(sc); + groupedView->setStartClosed(sc); } bool PlayQueueView::isStartClosed() const { - return listView->isStartClosed(); + return groupedView->isStartClosed(); } -QSet PlayQueueView::getControlledSongIds() const +QSet PlayQueueView::getControlledAlbums() const { - return currentWidget()==listView ? listView->getControlledSongIds() : QSet(); + return currentWidget()==groupedView ? groupedView->getControlledAlbums() : QSet(); } -void PlayQueueView::setControlled(const QSet &keys) +void PlayQueueView::setControlledAlbums(const QSet &keys) { - listView->setControlled(keys); + groupedView->setControlledAlbums(keys); } void PlayQueueView::setFilterActive(bool f) { - listView->setFilterActive(f); + groupedView->setFilterActive(f); } void PlayQueueView::updateRows(qint32 row, bool scroll) { - if (currentWidget()==listView) { - listView->updateRows(row, scroll); + if (currentWidget()==groupedView) { + groupedView->updateRows(row, scroll); } } void PlayQueueView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint) { - if (currentWidget()==listView && !listView->isFilterActive()) { + if (currentWidget()==groupedView && !groupedView->isFilterActive()) { return; } if (MPDState_Playing==MPDStatus::self()->state()) { -// listView->scrollTo(index, hint); +// groupedView->scrollTo(index, hint); treeView->scrollTo(index, hint); } } void PlayQueueView::setModel(QAbstractItemModel *m) { - if (currentWidget()==listView) { - listView->setModel(m); + if (currentWidget()==groupedView) { + groupedView->setModel(m); } else { treeView->setModel(m); } @@ -859,7 +268,7 @@ void PlayQueueView::setModel(QAbstractItemModel *m) void PlayQueueView::addAction(QAction *a) { - listView->addAction(a); + groupedView->addAction(a); treeView->addAction(a); } @@ -870,29 +279,29 @@ void PlayQueueView::setFocus() QAbstractItemModel * PlayQueueView::model() { - return currentWidget()==listView ? listView->model() : treeView->model(); + return currentWidget()==groupedView ? groupedView->model() : treeView->model(); } void PlayQueueView::setContextMenuPolicy(Qt::ContextMenuPolicy policy) { - listView->setContextMenuPolicy(policy); + groupedView->setContextMenuPolicy(policy); treeView->setContextMenuPolicy(policy); } bool PlayQueueView::haveUnSelectedItems() { - return currentWidget()==listView ? listView->haveUnSelectedItems() : treeView->haveUnSelectedItems(); + return currentWidget()==groupedView ? groupedView->haveUnSelectedItems() : treeView->haveUnSelectedItems(); } QItemSelectionModel * PlayQueueView::selectionModel() const { - return currentWidget()==listView ? listView->selectionModel() : treeView->selectionModel(); + return currentWidget()==groupedView ? groupedView->selectionModel() : treeView->selectionModel(); } void PlayQueueView::setCurrentIndex(const QModelIndex &index) { - if (currentWidget()==listView) { - listView->setCurrentIndex(index); + if (currentWidget()==groupedView) { + groupedView->setCurrentIndex(index); } else { treeView->setCurrentIndex(index); } @@ -910,61 +319,19 @@ QAbstractItemView * PlayQueueView::tree() QAbstractItemView * PlayQueueView::list() { - return listView; + return groupedView; } QModelIndexList PlayQueueView::selectedIndexes() const { - return listView==currentWidget() ? listView->selectedIndexes() : selectionModel()->selectedRows(); -} - -void PlayQueueView::itemClicked(const QModelIndex &idx) -{ - if (listView==currentWidget() && AlbumHeader==getType(idx)) { - QRect indexRect(listView->visualRect(idx)); - QRect icon(indexRect.x()+constBorder+4, indexRect.y()+constBorder+((indexRect.height()-constCoverSize)/2), - constCoverSize, constCoverSize); - QRect header(indexRect); - header.setHeight(header.height()/2); - header.moveTo(listView->viewport()->mapToGlobal(QPoint(header.x(), header.y()))); - icon.moveTo(listView->viewport()->mapToGlobal(QPoint(icon.x(), icon.y()))); - if (icon.contains(QCursor::pos())) { - listView->toggle(idx); - } else if (header.contains(QCursor::pos())) { - QModelIndexList list; - unsigned int key=idx.data(PlayQueueView::Role_Key).toUInt(); - QModelIndex i=idx.sibling(idx.row()+1, 0); - QModelIndexList sel=listView->selectedIndexes(); - QItemSelectionModel *selModel=listView->selectionModel(); - QModelIndexList unsel; - - while (i.isValid() && i.data(PlayQueueView::Role_Key).toUInt()==key) { - if (!sel.contains(i)) { - unsel.append(i); - } - list.append(i); - i=i.sibling(i.row()+1, 0); - } - if (list.count()) { - if (unsel.isEmpty()) { // TODO: This is not working!!! - foreach(const QModelIndex &i, list) { - selModel->select(i, QItemSelectionModel::Deselect|QItemSelectionModel::Rows); - } - } else { - foreach(const QModelIndex &i, unsel) { - selModel->select(i, QItemSelectionModel::Select|QItemSelectionModel::Rows); - } - } - } - } - } + return groupedView==currentWidget() ? groupedView->selectedIndexes() : selectionModel()->selectedRows(); } void PlayQueueView::coverRetrieved(const QString &artist, const QString &album) { - if (listView!=currentWidget()) { + if (groupedView!=currentWidget()) { return; } - listView->coverRetrieved(artist, album); + groupedView->coverRetrieved(artist, album); } diff --git a/widgets/playqueueview.h b/widgets/playqueueview.h index 6c7900fbe..208eb282c 100644 --- a/widgets/playqueueview.h +++ b/widgets/playqueueview.h @@ -29,7 +29,7 @@ #include #include "treeview.h" -class PlayQueueListView; +class GroupedView; class QAbstractItemModel; class QAction; class QItemSelectionModel; @@ -61,23 +61,6 @@ class PlayQueueView : public QStackedWidget Q_OBJECT public: - enum Status { - State_Default, - State_Playing, - State_Paused, - State_Stopped - }; - - enum Roles { - Role_Key = Qt::UserRole+512, - Role_Id, - Role_Song, - Role_AlbumDuration, - Role_Status, - Role_CurrentStatus, - Role_SongCount - }; - PlayQueueView(QWidget *parent=0); virtual ~PlayQueueView(); @@ -90,8 +73,8 @@ public: bool isAutoExpand() const; void setStartClosed(bool sc); bool isStartClosed() const; - QSet getControlledSongIds() const; - void setControlled(const QSet &keys); + QSet getControlledAlbums() const; + void setControlledAlbums(const QSet &keys); void setFilterActive(bool f); void updateRows(qint32 row, bool scroll); void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint); @@ -113,11 +96,10 @@ Q_SIGNALS: void doubleClicked(const QModelIndex &); private Q_SLOTS: - void itemClicked(const QModelIndex &idx); void coverRetrieved(const QString &artist, const QString &album); private: - PlayQueueListView *listView; + GroupedView *groupedView; PlayQueueTreeView *treeView; };