diff --git a/autotests/viewmanagertest.cpp b/autotests/viewmanagertest.cpp --- a/autotests/viewmanagertest.cpp +++ b/autotests/viewmanagertest.cpp @@ -73,7 +73,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -116,7 +116,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -158,7 +158,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -197,7 +197,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -222,7 +222,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -238,7 +238,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -268,7 +268,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(switchRecentlyPlayedTracksViewSpy.count(), 0); @@ -307,7 +307,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(switchRecentlyPlayedTracksViewSpy.count(), 0); 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/AlbumView.qml b/src/qml/AlbumView.qml --- a/src/qml/AlbumView.qml +++ b/src/qml/AlbumView.qml @@ -28,6 +28,7 @@ property alias secondaryTitle: albumGridView.secondaryTitle property alias image: albumGridView.image property alias databaseId: albumGridView.databaseId + property alias showSection: albumGridView.showSection DataModel { id: realModel @@ -98,7 +99,7 @@ allowArtistNavigation: true 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/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() { @@ -130,6 +130,7 @@ image: imageUrl, databaseId: databaseId, stackView: browseStackView, + 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 @@ -70,7 +70,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/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: 'CD ' + 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 @@ -45,6 +45,7 @@ qml/MetaDataDelegate.qml qml/ContextViewLyrics.qml qml/ViewSelectorDelegate.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 @@ -49,6 +49,13 @@ Q_ENUM(ViewsType) + enum AlbumViewStyle { + NoDiscHeaders, + DiscHeaders, + }; + + Q_ENUM(AlbumViewStyle) + explicit ViewManager(QObject *parent = nullptr); Q_SIGNALS: @@ -68,7 +75,8 @@ ElisaUtils::PlayListEntryType dataType); void switchOneAlbumView(ViewManager::ViewsType viewType, int expectedDepth, - const QString &mainTitle, const QUrl &imageUrl, const QString &secondaryTitle, qulonglong databaseId); + const QString &mainTitle, const QUrl &imageUrl, const QString &secondaryTitle, + qulonglong databaseId, AlbumViewStyle showDiscHeaders); void switchAllTracksView(ViewManager::ViewsType viewType, int expectedDepth, const QString &mainTitle, const QUrl &imageUrl, ElisaUtils::PlayListEntryType dataType); @@ -88,7 +96,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); @@ -111,7 +119,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); @@ -149,6 +157,7 @@ QString mTargetArtistName; QString mTargetGenreName; QUrl mTargetImageUrl; + ViewManager::AlbumViewStyle mAlbumDiscHeader = ViewManager::NoDiscHeaders; qulonglong mTargetDatabaseId = 0; }; diff --git a/src/viewmanager.cpp b/src/viewmanager.cpp --- a/src/viewmanager.cpp +++ b/src/viewmanager.cpp @@ -68,12 +68,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); @@ -167,28 +167,30 @@ } 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 switchOneAlbumView(mTargetView, 2, - mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, mTargetDatabaseId); + Q_EMIT switchOneAlbumView(mTargetView, 2, mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, + mTargetDatabaseId, mAlbumDiscHeader); } else if (mCurrentView == ViewsType::OneArtist && mCurrentArtistName == mTargetAlbumAuthor) { mTargetView = ViewsType::OneAlbumFromArtist; - Q_EMIT switchOneAlbumView(mTargetView, 3, - mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, mTargetDatabaseId); + Q_EMIT switchOneAlbumView(mTargetView, 3, mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, + mTargetDatabaseId, mAlbumDiscHeader); } else if (mCurrentView == ViewsType::OneArtist && mCurrentArtistName != mTargetAlbumAuthor) { mTargetView = ViewsType::OneAlbumFromArtist; Q_EMIT popOneView(); } else if (mCurrentView == ViewsType::OneArtistFromGenre) { mTargetView = ViewsType::OneAlbumFromArtistAndGenre; - Q_EMIT switchOneAlbumView(mTargetView, 4, - mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, mTargetDatabaseId); + Q_EMIT switchOneAlbumView(mTargetView, 4, mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, + mTargetDatabaseId, mAlbumDiscHeader); } else { mTargetView = ViewsType::OneAlbum; Q_EMIT openGridView(ViewsType::AllAlbums, 1, {}, {}, {}, ElisaUtils::Album, @@ -292,7 +294,8 @@ { mCurrentView = ViewsType::AllAlbums; if (mTargetView == ViewsType::OneAlbum) { - Q_EMIT switchOneAlbumView(mTargetView, 2, mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, mTargetDatabaseId); + Q_EMIT switchOneAlbumView(mTargetView, 2, mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, + mTargetDatabaseId, mAlbumDiscHeader); } } @@ -333,8 +336,8 @@ } else if (mTargetView == ViewsType::OneAlbumFromArtist) { mCurrentView = ViewsType::OneArtist; - Q_EMIT switchOneAlbumView(mTargetView, 3, - mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, mTargetDatabaseId); + Q_EMIT switchOneAlbumView(mTargetView, 3, mTargetAlbumTitle, mTargetImageUrl, mTargetAlbumAuthor, + mTargetDatabaseId, mAlbumDiscHeader); } }