diff --git a/autotests/viewmanagertest.cpp b/autotests/viewmanagertest.cpp --- a/autotests/viewmanagertest.cpp +++ b/autotests/viewmanagertest.cpp @@ -66,7 +66,7 @@ QSignalSpy switchOffAllViewsSpy(&viewManager, &ViewManager::switchOffAllViews); QSignalSpy popOneViewSpy(&viewManager, &ViewManager::popOneView); - viewManager.openChildView(QStringLiteral("album1"), QStringLiteral("artist1"), {}, 12, ElisaUtils::Album); + viewManager.openChildView(QStringLiteral("album1"), QStringLiteral("artist1"), {}, 12, ElisaUtils::Album, ViewManager::DiscHeaders); QCOMPARE(openGridViewSpy.count(), 1); QCOMPARE(openListViewSpy.count(), 0); @@ -100,7 +100,7 @@ QSignalSpy switchOffAllViewsSpy(&viewManager, &ViewManager::switchOffAllViews); QSignalSpy popOneViewSpy(&viewManager, &ViewManager::popOneView); - viewManager.openChildView(QStringLiteral("artist1"), {}, {}, 0, ElisaUtils::Artist); + viewManager.openChildView(QStringLiteral("artist1"), {}, {}, 0, ElisaUtils::Artist, ViewManager::DiscHeaders); QCOMPARE(openGridViewSpy.count(), 1); QCOMPARE(openListViewSpy.count(), 0); @@ -133,7 +133,7 @@ QSignalSpy switchOffAllViewsSpy(&viewManager, &ViewManager::switchOffAllViews); QSignalSpy popOneViewSpy(&viewManager, &ViewManager::popOneView); - viewManager.openChildView(QStringLiteral("genre1"), {}, {}, 0, ElisaUtils::Genre); + viewManager.openChildView(QStringLiteral("genre1"), {}, {}, 0, ElisaUtils::Genre, ViewManager::NoDiscHeaders); QCOMPARE(openGridViewSpy.count(), 1); QCOMPARE(openListViewSpy.count(), 0); @@ -163,7 +163,7 @@ QSignalSpy switchOffAllViewsSpy(&viewManager, &ViewManager::switchOffAllViews); QSignalSpy popOneViewSpy(&viewManager, &ViewManager::popOneView); - viewManager.openChildView(QStringLiteral("genre1"), {}, {}, 0, ElisaUtils::Genre); + viewManager.openChildView(QStringLiteral("genre1"), {}, {}, 0, ElisaUtils::Genre, ViewManager::NoDiscHeaders); QCOMPARE(openGridViewSpy.count(), 1); QCOMPARE(openListViewSpy.count(), 0); @@ -182,7 +182,7 @@ QCOMPARE(switchOffAllViewsSpy.count(), 0); QCOMPARE(popOneViewSpy.count(), 0); - viewManager.openChildView(QStringLiteral("genre1"), {}, {}, 0, ElisaUtils::Genre); + viewManager.openChildView(QStringLiteral("genre1"), {}, {}, 0, ElisaUtils::Genre, ViewManager::NoDiscHeaders); QCOMPARE(openGridViewSpy.count(), 2); QCOMPARE(openListViewSpy.count(), 0); @@ -195,7 +195,7 @@ viewManager.viewIsLoaded(ViewManager::AllArtistsFromGenre); - viewManager.openChildView(QStringLiteral("artist1"), {}, {}, 0, ElisaUtils::Artist); + viewManager.openChildView(QStringLiteral("artist1"), {}, {}, 0, ElisaUtils::Artist, ViewManager::NoDiscHeaders); QCOMPARE(openGridViewSpy.count(), 3); QCOMPARE(openListViewSpy.count(), 0); @@ -219,7 +219,7 @@ QSignalSpy switchOffAllViewsSpy(&viewManager, &ViewManager::switchOffAllViews); QSignalSpy popOneViewSpy(&viewManager, &ViewManager::popOneView); - viewManager.openChildView(QStringLiteral("artist1"), {}, {}, 0, ElisaUtils::Artist); + viewManager.openChildView(QStringLiteral("artist1"), {}, {}, 0, ElisaUtils::Artist, ViewManager::NoDiscHeaders); QCOMPARE(openGridViewSpy.count(), 1); QCOMPARE(openListViewSpy.count(), 0); @@ -249,7 +249,7 @@ QCOMPARE(switchOffAllViewsSpy.count(), 0); QCOMPARE(popOneViewSpy.count(), 0); - viewManager.openChildView(QStringLiteral("album1"), QStringLiteral("artist2"), {}, 0, ElisaUtils::Album); + viewManager.openChildView(QStringLiteral("album1"), QStringLiteral("artist2"), {}, 0, ElisaUtils::Album, ViewManager::DiscHeaders); QCOMPARE(openGridViewSpy.count(), 2); QCOMPARE(openListViewSpy.count(), 0); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -385,6 +385,7 @@ qml/MetaDataDelegate.qml qml/MediaTrackDelegate.qml + qml/TracksDiscHeader.qml qml/MediaAlbumTrackDelegate.qml qml/MediaTrackMetadataView.qml qml/GridBrowserView.qml diff --git a/src/databaseinterface.cpp b/src/databaseinterface.cpp --- a/src/databaseinterface.cpp +++ b/src/databaseinterface.cpp @@ -3070,6 +3070,20 @@ "GROUP_CONCAT(tracks.`ArtistName`, ', ') as AllArtists, " "MAX(tracks.`Rating`) as HighestRating, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres, " + "(" + "SELECT " + "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " + "FROM " + "`Tracks` tracks2 " + "WHERE " + "tracks2.`AlbumTitle` = album.`Title` AND " + "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " + "(tracks2.`AlbumArtistName` IS NULL AND " + "album.`ArtistName` IS NULL" + ")" + ") AND " + "tracks2.`AlbumPath` = album.`AlbumPath` " + ") as `IsSingleDiscAlbum`, " "( " "SELECT tracksCover.`FileName` " "FROM " @@ -3131,6 +3145,20 @@ "GROUP_CONCAT(tracks.`ArtistName`, ', ') as AllArtists, " "MAX(tracks.`Rating`) as HighestRating, " "GROUP_CONCAT(genres.`Name`, ', ') as AllGenres, " + "(" + "SELECT " + "COUNT(DISTINCT tracks2.DiscNumber) <= 1 " + "FROM " + "`Tracks` tracks2 " + "WHERE " + "tracks2.`AlbumTitle` = album.`Title` AND " + "(tracks2.`AlbumArtistName` = album.`ArtistName` OR " + "(tracks2.`AlbumArtistName` IS NULL AND " + "album.`ArtistName` IS NULL" + ")" + ") AND " + "tracks2.`AlbumPath` = album.`AlbumPath` " + ") as `IsSingleDiscAlbum`, " "( " "SELECT tracksCover.`FileName` " "FROM " diff --git a/src/mediaplaylist.h b/src/mediaplaylist.h --- a/src/mediaplaylist.h +++ b/src/mediaplaylist.h @@ -113,6 +113,7 @@ PlayCounter, PlayFrequency, ElementTypeRole, + LyricsRole, IsValidRole, TrackDataRole, CountRole, diff --git a/src/qml/ContentView.qml b/src/qml/ContentView.qml --- a/src/qml/ContentView.qml +++ b/src/qml/ContentView.qml @@ -35,12 +35,12 @@ } function openArtist(name) { - viewManager.openChildView(name, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist) + viewManager.openChildView(name, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist, ViewManager.NoDiscHeaders) } - function openAlbum(album, artist, image, albumID) { + function openAlbum(album, artist, image, albumID, showDiscHeader) { image = !image ? elisaTheme.defaultAlbumImage : image; - viewManager.openChildView(album, artist, image, albumID, ElisaUtils.Album); + viewManager.openChildView(album, artist, image, albumID, ElisaUtils.Album, showDiscHeader); } function openNowPlaying() { @@ -105,6 +105,7 @@ sortAscending: sortOrder, stackView: browseStackView, displaySingleAlbum: displaySingleAlbum, + showSection: showDiscHeaders, opacity: 0, }) } diff --git a/src/qml/DataGridView.qml b/src/qml/DataGridView.qml --- a/src/qml/DataGridView.qml +++ b/src/qml/DataGridView.qml @@ -77,7 +77,7 @@ ElisaUtils.ReplacePlayList, ElisaUtils.TriggerPlay) - onOpen: viewManager.openChildView(innerMainTitle, innerSecondaryTitle, innerImage, databaseId, dataType) + onOpen: viewManager.openChildView(innerMainTitle, innerSecondaryTitle, innerImage, databaseId, dataType, showDiscHeader) onGoBack: viewManager.goBack() diff --git a/src/qml/DataListView.qml b/src/qml/DataListView.qml --- a/src/qml/DataListView.qml +++ b/src/qml/DataListView.qml @@ -29,6 +29,7 @@ property alias mainTitle: listView.mainTitle property alias secondaryTitle: listView.secondaryTitle property int databaseId + property alias showSection: listView.showSection property alias image: listView.image property var modelType property alias sortRole: proxyModel.sortRole @@ -147,7 +148,7 @@ allowArtistNavigation: isSubPage onShowArtist: { - viewManager.openChildView(secondaryTitle, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist) + viewManager.openChildView(secondaryTitle, '', elisaTheme.artistIcon, 0, ElisaUtils.Artist, ViewManager.NoDiscHeaders) } onGoBack: viewManager.goBack() diff --git a/src/qml/GridBrowserView.qml b/src/qml/GridBrowserView.qml --- a/src/qml/GridBrowserView.qml +++ b/src/qml/GridBrowserView.qml @@ -40,7 +40,7 @@ signal enqueue(int databaseId, string name) signal replaceAndPlay(int databaseId, string name) - signal open(string innerMainTitle, string innerSecondaryTitle, url innerImage, int databaseId, var dataType) + signal open(string innerMainTitle, string innerSecondaryTitle, url innerImage, int databaseId, var dataType, var showDiscHeader) signal goBack() ColumnLayout { @@ -154,7 +154,7 @@ onReplaceAndPlay: gridView.replaceAndPlay(databaseId, name) onOpen: gridView.open(model.display, model.secondaryText, (model && model.imageUrl && model.imageUrl.toString() !== "" ? model.imageUrl : defaultIcon), - model.databaseId, model.dataType) + model.databaseId, model.dataType, (model.isSingleDiscAlbum ? ViewManager.NoDiscHeaders : ViewManager.DiscHeaders)) onSelected: { forceActiveFocus() contentDirectoryView.currentIndex = model.index diff --git a/src/qml/ListBrowserView.qml b/src/qml/ListBrowserView.qml --- a/src/qml/ListBrowserView.qml +++ b/src/qml/ListBrowserView.qml @@ -33,6 +33,7 @@ property alias image: navigationBar.image property int databaseId property alias delegate: contentDirectoryView.delegate + property bool showSection: false property alias contentModel: contentDirectoryView.model property alias expandedFilterView: navigationBar.expandedFilterView property alias showRating: navigationBar.showRating @@ -114,6 +115,15 @@ currentIndex: -1 + section.property: (showSection ? 'discNumber' : '') + section.criteria: ViewSection.FullString + section.labelPositioning: ViewSection.InlineLabels + section.delegate: TracksDiscHeader { + discNumber: section + width: scrollBar.visible ? (!LayoutMirroring.enabled ? contentDirectoryView.width - scrollBar.width : contentDirectoryView.width) : contentDirectoryView.width + height: elisaTheme.delegateHeight + } + ScrollBar.vertical: ScrollBar { id: scrollBar } diff --git a/src/qml/TracksDiscHeader.qml b/src/qml/TracksDiscHeader.qml new file mode 100644 --- /dev/null +++ b/src/qml/TracksDiscHeader.qml @@ -0,0 +1,42 @@ +/* + * Copyright 2016-2017 Matthieu Gallien + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.2 + +Rectangle { + property int discNumber + + color: myPalette.mid + + LabelWithToolTip { + id: discHeaderLabel + + text: i18nc("disc header text when showing an album", "Disc %1", discNumber) + + font.weight: Font.Bold + font.italic: true + color: myPalette.text + + verticalAlignment: Text.AlignVCenter + + anchors.fill: parent + anchors.leftMargin: elisaTheme.layoutHorizontalMargin + + elide: Text.ElideRight + } +} diff --git a/src/resources.qrc b/src/resources.qrc --- a/src/resources.qrc +++ b/src/resources.qrc @@ -42,6 +42,7 @@ qml/MetaDataDelegate.qml qml/ViewSelectorDelegate.qml qml/HeaderFooterToolbar.qml + qml/TracksDiscHeader.qml windows/WindowsTheme.qml diff --git a/src/viewmanager.h b/src/viewmanager.h --- a/src/viewmanager.h +++ b/src/viewmanager.h @@ -60,6 +60,13 @@ static const bool SingleAlbum = true; static const bool MultipleAlbum = false; + enum AlbumViewStyle { + NoDiscHeaders, + DiscHeaders, + }; + + Q_ENUM(AlbumViewStyle) + explicit ViewManager(QObject *parent = nullptr); Q_SIGNALS: @@ -73,7 +80,7 @@ void openListView(ViewManager::ViewsType viewType, ElisaUtils::FilterType filterType, int expectedDepth, const QString &mainTitle, const QString &secondaryTitle, qulonglong databaseId, const QUrl &imageUrl, ElisaUtils::PlayListEntryType dataType, QVariant sortRole, - ViewManager::SortOrder sortOrder, bool displaySingleAlbum); + ViewManager::SortOrder sortOrder, bool displaySingleAlbum, ViewManager::AlbumViewStyle showDiscHeaders); void switchFilesBrowserView(ViewManager::ViewsType viewType, int expectedDepth, const QString &mainTitle, const QUrl &imageUrl); @@ -90,7 +97,7 @@ void openChildView(const QString &innerMainTitle, const QString & innerSecondaryTitle, const QUrl &innerImage, qulonglong databaseId, - ElisaUtils::PlayListEntryType dataType); + ElisaUtils::PlayListEntryType dataType, ViewManager::AlbumViewStyle albumDiscHeader); void viewIsLoaded(ViewManager::ViewsType viewType); @@ -113,7 +120,7 @@ void openFilesBrowser(const QString &mainTitle, const QUrl &imageUrl); void openOneAlbum(const QString &albumTitle, const QString &albumAuthor, - const QUrl &albumCover, qulonglong albumDatabaseId); + const QUrl &albumCover, qulonglong albumDatabaseId, ViewManager::AlbumViewStyle albumDiscHeader); void openOneArtist(const QString &artistName, const QUrl &artistImageUrl, qulonglong artistDatabaseId); @@ -139,19 +146,20 @@ void filesBrowserViewIsLoaded(); - ViewsType mCurrentView = ViewsType::NoViews; QString mCurrentAlbumTitle; QString mCurrentAlbumAuthor; QString mCurrentArtistName; QString mCurrentGenreName; - ViewsType mTargetView = ViewsType::NoViews; QString mTargetAlbumTitle; QString mTargetAlbumAuthor; QString mTargetArtistName; QString mTargetGenreName; QUrl mTargetImageUrl; qulonglong mTargetDatabaseId = 0; + ViewsType mTargetView = ViewsType::NoViews; + ViewsType mCurrentView = ViewsType::NoViews; + ViewManager::AlbumViewStyle mAlbumDiscHeader = ViewManager::NoDiscHeaders; }; diff --git a/src/viewmanager.cpp b/src/viewmanager.cpp --- a/src/viewmanager.cpp +++ b/src/viewmanager.cpp @@ -70,12 +70,12 @@ void ViewManager::openChildView(const QString &innerMainTitle, const QString &innerSecondaryTitle, const QUrl &innerImage, qulonglong databaseId, - ElisaUtils::PlayListEntryType dataType) + ElisaUtils::PlayListEntryType dataType, AlbumViewStyle albumDiscHeader) { switch(dataType) { case ElisaUtils::Album: - openOneAlbum(innerMainTitle, innerSecondaryTitle, innerImage, databaseId); + openOneAlbum(innerMainTitle, innerSecondaryTitle, innerImage, databaseId, albumDiscHeader); break; case ElisaUtils::Artist: openOneArtist(innerMainTitle, innerImage, databaseId); @@ -147,7 +147,7 @@ if (mCurrentView != mTargetView) { Q_EMIT openListView(mTargetView, ElisaUtils::FilterByRecentlyPlayed, 1, mainTitle, {}, 0, imageUrl, ElisaUtils::Track, DatabaseInterface::LastPlayDate, - SortOrder::SortDescending, MultipleAlbum); + SortOrder::SortDescending, MultipleAlbum, NoDiscHeaders); } } @@ -157,7 +157,8 @@ if (mCurrentView != mTargetView) { Q_EMIT openListView(mTargetView, ElisaUtils::FilterByFrequentlyPlayed, 1, mainTitle, {}, - 0, imageUrl, ElisaUtils::Track, DatabaseInterface::PlayFrequency, SortOrder::SortDescending, MultipleAlbum); + 0, imageUrl, ElisaUtils::Track, DatabaseInterface::PlayFrequency, + SortOrder::SortDescending, MultipleAlbum, NoDiscHeaders); } } @@ -172,31 +173,33 @@ } void ViewManager::openOneAlbum(const QString &albumTitle, const QString &albumAuthor, - const QUrl &albumCover, qulonglong albumDatabaseId) + const QUrl &albumCover, qulonglong albumDatabaseId, + AlbumViewStyle albumDiscHeader) { mTargetAlbumTitle = albumTitle; mTargetAlbumAuthor = albumAuthor; mTargetDatabaseId = albumDatabaseId; mTargetImageUrl = albumCover; + mAlbumDiscHeader = albumDiscHeader; if (mCurrentView == ViewsType::AllAlbums) { mTargetView = ViewsType::OneAlbum; Q_EMIT openListView(mTargetView, ElisaUtils::FilterById, 2, mTargetAlbumTitle, mTargetAlbumAuthor, mTargetDatabaseId, mTargetImageUrl, ElisaUtils::Track, {}, - SortOrder::NoSort, SingleAlbum); + SortOrder::NoSort, SingleAlbum, mAlbumDiscHeader); } else if (mCurrentView == ViewsType::OneArtist && mCurrentArtistName == mTargetAlbumAuthor) { mTargetView = ViewsType::OneAlbumFromArtist; Q_EMIT openListView(mTargetView, ElisaUtils::FilterById, 3, mTargetAlbumTitle, mTargetAlbumAuthor, mTargetDatabaseId, mTargetImageUrl, ElisaUtils::Track, {}, - SortOrder::NoSort, SingleAlbum); + SortOrder::NoSort, SingleAlbum, mAlbumDiscHeader); } else if (mCurrentView == ViewsType::OneArtist && mCurrentArtistName != mTargetAlbumAuthor) { mTargetView = ViewsType::OneAlbumFromArtist; Q_EMIT popOneView(); } else if (mCurrentView == ViewsType::OneArtistFromGenre) { mTargetView = ViewsType::OneAlbumFromArtistAndGenre; Q_EMIT openListView(mTargetView, ElisaUtils::FilterById, 4, mTargetAlbumTitle, mTargetAlbumAuthor, mTargetDatabaseId, mTargetImageUrl, ElisaUtils::Track, {}, - SortOrder::NoSort, SingleAlbum); + SortOrder::NoSort, SingleAlbum, mAlbumDiscHeader); } else { mTargetView = ViewsType::OneAlbum; Q_EMIT openGridView(ViewsType::AllAlbums, ElisaUtils::NoFilter, 1, {}, {}, {}, ElisaUtils::Album, @@ -252,7 +255,7 @@ if (mCurrentView != mTargetView) { Q_EMIT openListView(mTargetView, ElisaUtils::NoFilter, 1, mainTitle, {}, 0, imageUrl, ElisaUtils::Track, Qt::DisplayRole, - SortOrder::SortAscending, MultipleAlbum); + SortOrder::SortAscending, MultipleAlbum, NoDiscHeaders); } } @@ -304,7 +307,7 @@ if (mTargetView == ViewsType::OneAlbum) { Q_EMIT openListView(mTargetView, ElisaUtils::FilterById, 2, mTargetAlbumTitle, mTargetAlbumAuthor, mTargetDatabaseId, mTargetImageUrl, ElisaUtils::Track, Qt::DisplayRole, - SortOrder::SortAscending, MultipleAlbum); + SortOrder::SortAscending, MultipleAlbum, NoDiscHeaders); } } @@ -347,7 +350,7 @@ Q_EMIT openListView(mTargetView, ElisaUtils::FilterById, 3, mTargetAlbumTitle, mTargetAlbumAuthor, mTargetDatabaseId, mTargetImageUrl, ElisaUtils::Track, Qt::DisplayRole, - SortOrder::SortAscending, MultipleAlbum); + SortOrder::SortAscending, MultipleAlbum, NoDiscHeaders); } }