Commit adfd2e03 authored by Matthieu Gallien's avatar Matthieu Gallien

let one category be embedded in the ViewSelector

Summary:
only possible for all albums, all artists and all genres

related to T11154

Test Plan:
after adding
[Views]
EmbeddedView=3

Genres are added to ViewSelector list

properly display icons in views list including albums (default icon and cover)

introduce ViewsListData to handle data of the list of views

to be used by both the model and the ViewManager class handling the
switch between views

if config changed, embedded category property may have changed

separate ViewsListData object from ViewsModel and use it

will allow sharing with the ViewsManager object

ViewsModel no longer have any properties
parent b0727f21
......@@ -75,8 +75,6 @@ set(mediaplaylistTest_SOURCES
../src/elisautils.cpp
)
ecm_qt_declare_logging_category(mediaplaylistTest_SOURCES HEADER elisa_debug.h IDENTIFIER lcModelTest CATEGORY_NAME qt.modeltest)
ecm_add_test(${mediaplaylistTest_SOURCES}
TEST_NAME "mediaplaylistTest"
LINK_LIBRARIES
......@@ -117,8 +115,6 @@ set(datamodeltest_SOURCES
datamodeltest.cpp
)
ecm_qt_declare_logging_category(datamodeltest_SOURCES HEADER elisa_debug.h IDENTIFIER lcModelTest CATEGORY_NAME qt.modeltest)
ecm_add_test(${datamodeltest_SOURCES}
TEST_NAME "datamodeltest"
LINK_LIBRARIES
......@@ -127,12 +123,22 @@ ecm_add_test(${datamodeltest_SOURCES}
target_include_directories(datamodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src)
set(viewsmodeltest_SOURCES
viewsmodeltest.cpp
)
ecm_add_test(${viewsmodeltest_SOURCES}
TEST_NAME "viewsmodeltest"
LINK_LIBRARIES
Qt5::Test elisaLib Qt5::CorePrivate
)
target_include_directories(viewsmodeltest PRIVATE ${CMAKE_SOURCE_DIR}/src)
set(trackmetadatamodeltest_SOURCES
trackmetadatamodeltest.cpp
)
ecm_qt_declare_logging_category(trackmetadatamodeltest_SOURCES HEADER elisa_debug.h IDENTIFIER lcModelTest CATEGORY_NAME qt.modeltest)
ecm_add_test(${trackmetadatamodeltest_SOURCES}
TEST_NAME "trackmetadatamodeltest"
LINK_LIBRARIES
......
......@@ -1125,6 +1125,8 @@ private Q_SLOTS:
musicDb.insertTracksList(mNewTracks, mNewCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 7);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1138,6 +1140,8 @@ private Q_SLOTS:
musicDb.removeTracksList({firstTrack[DataTypes::ResourceRole].toUrl()});
beginRemoveRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 6);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1184,6 +1188,8 @@ private Q_SLOTS:
musicDb.insertTracksList(mNewTracks, mNewCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 7);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1207,6 +1213,8 @@ private Q_SLOTS:
musicDb.insertTracksList(newTracks, mNewCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 8);
QCOMPARE(beginInsertRowsSpy.count(), 2);
QCOMPARE(endInsertRowsSpy.count(), 2);
......@@ -1264,6 +1272,8 @@ private Q_SLOTS:
musicDb.insertTracksList(mNewTracks, mNewCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 1);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1323,6 +1333,8 @@ private Q_SLOTS:
musicDb.insertTracksList(mNewTracks, mNewCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 4);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1383,6 +1395,8 @@ private Q_SLOTS:
musicDb.insertTracksList(mNewTracks, mNewCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 2);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1442,6 +1456,8 @@ private Q_SLOTS:
artistsModel.initialize(nullptr, &musicDb, ElisaUtils::Track, ElisaUtils::FilterById, {}, {}, albumId, {});
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 4);
QCOMPARE(beginInsertRowsSpy.count(), 1);
QCOMPARE(endInsertRowsSpy.count(), 1);
......@@ -1474,6 +1490,8 @@ private Q_SLOTS:
musicDb.insertTracksList(newTracks, newCovers);
beginInsertRowsSpy.wait();
QCOMPARE(artistsModel.rowCount(), 5);
QCOMPARE(beginInsertRowsSpy.count(), 2);
QCOMPARE(endInsertRowsSpy.count(), 2);
......
This diff is collapsed.
......@@ -22,4 +22,14 @@
</default>
</entry>
</group>
<group name="Views">
<entry key="EmbeddedView" type="Enum">
<choices>
<choice name="NoView" />
<choice name="AllAlbums" />
<choice name="AllArtists" />
<choice name="AllGenres" />
</choices>
</entry>
</group>
</kcfg>
......@@ -45,6 +45,7 @@
#include <QUrl>
#include <QFileInfo>
#include <QDir>
#include <QFileSystemWatcher>
#include <QKeyEvent>
#include <QDebug>
#include <QFileSystemWatcher>
......@@ -61,14 +62,6 @@ public:
#endif
{
Q_UNUSED(parent)
auto configurationFileName = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
configurationFileName += QStringLiteral("/elisarc");
Elisa::ElisaConfiguration::instance(configurationFileName);
Elisa::ElisaConfiguration::self()->load();
Elisa::ElisaConfiguration::self()->save();
mConfigFileWatcher.addPath(configurationFileName);
}
#if defined KF5XmlGui_FOUND && KF5XmlGui_FOUND
......@@ -99,6 +92,14 @@ public:
ElisaApplication::ElisaApplication(QObject *parent) : QObject(parent), d(std::make_unique<ElisaApplicationPrivate>(this))
{
auto configurationFileName = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
configurationFileName += QStringLiteral("/elisarc");
Elisa::ElisaConfiguration::instance(configurationFileName);
Elisa::ElisaConfiguration::self()->load();
Elisa::ElisaConfiguration::self()->save();
d->mConfigFileWatcher.addPath(Elisa::ElisaConfiguration::self()->config()->name());
connect(Elisa::ElisaConfiguration::self(), &Elisa::ElisaConfiguration::configChanged,
this, &ElisaApplication::configChanged);
......@@ -303,6 +304,7 @@ void ElisaApplication::configChanged()
Q_EMIT showProgressOnTaskBarChanged();
Q_EMIT showSystemTrayIconChanged();
Q_EMIT embeddedViewChanged();
}
DataTypes::EntryDataList ElisaApplication::checkFileListAndMakeAbsolute(const DataTypes::EntryDataList &filesList,
......@@ -482,7 +484,7 @@ void ElisaApplication::installKeyEventFilter(QObject *object)
bool ElisaApplication::eventFilter(QObject *object, QEvent *event)
{
Q_UNUSED(object);
Q_UNUSED(object)
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
auto playPauseAction = d->mCollection.action(tr("Play-Pause"));
......@@ -548,4 +550,27 @@ bool ElisaApplication::showSystemTrayIcon() const
return currentConfiguration->showSystemTrayIcon();
}
ElisaUtils::PlayListEntryType ElisaApplication::embeddedView() const
{
ElisaUtils::PlayListEntryType result = ElisaUtils::Unknown;
switch (Elisa::ElisaConfiguration::self()->embeddedView())
{
case 0:
result = ElisaUtils::Unknown;
break;
case 1:
result = ElisaUtils::Album;
break;
case 2:
result = ElisaUtils::Artist;
break;
case 3:
result = ElisaUtils::Genre;
break;
}
return result;
}
#include "moc_elisaapplication.cpp"
......@@ -42,6 +42,10 @@ class ELISALIB_EXPORT ElisaApplication : public QObject
WRITE setArguments
NOTIFY argumentsChanged)
Q_PROPERTY(ElisaUtils::PlayListEntryType embeddedView
READ embeddedView
NOTIFY embeddedViewChanged)
Q_PROPERTY(MusicListenersManager *musicManager
READ musicManager
NOTIFY musicManagerChanged)
......@@ -113,6 +117,8 @@ public:
bool showSystemTrayIcon() const;
ElisaUtils::PlayListEntryType embeddedView() const;
Q_SIGNALS:
void argumentsChanged();
......@@ -143,6 +149,8 @@ Q_SIGNALS:
void commitDataRequest(QSessionManager &manager);
void embeddedViewChanged();
public Q_SLOTS:
void appHelpActivated();
......
......@@ -128,6 +128,7 @@ void ElisaQmlTestPlugin::registerTypes(const char *uri)
qmlRegisterType<TrackContextMetaDataModel>(uri, 1, 0, "TrackContextMetaDataModel");
qmlRegisterType<EditableTrackMetadataModel>(uri, 1, 0, "EditableTrackMetadataModel");
qmlRegisterType<ViewsModel>(uri, 1, 0, "ViewsModel");
qmlRegisterType<ViewsListData>(uri, 1, 0, "ViewsListData");
qmlRegisterType<GridViewProxyModel>(uri, 1, 0, "GridViewProxyModel");
#if defined KF5KIO_FOUND && KF5KIO_FOUND
......
......@@ -61,6 +61,14 @@ enum FilterType {
Q_ENUM_NS(FilterType)
enum IconUseColorOverlay
{
UseColorOverlay = true,
DoNotUseColorOverlay = false,
};
Q_ENUM_NS(IconUseColorOverlay)
}
#endif // ELISAUTILS_H
......@@ -15,6 +15,11 @@ public:
ViewsListData *mViewsData = nullptr;
ElisaUtils::PlayListEntryType mEmbeddedCategory = ElisaUtils::Unknown;
QMap<ElisaUtils::PlayListEntryType, QUrl> mDefaultIcons = {{ElisaUtils::Album, QUrl{QStringLiteral("image://icon/view-media-album-cover")}},
{ElisaUtils::Artist, QUrl{QStringLiteral("image://icon/view-media-artist")}},
{ElisaUtils::Genre, QUrl{QStringLiteral("image://icon/view-media-genre")}},};
};
ViewsModel::ViewsModel(QObject *parent)
......@@ -29,6 +34,10 @@ QHash<int, QByteArray> ViewsModel::roleNames() const
auto result = QAbstractListModel::roleNames();
result[ImageNameRole] = "image";
result[UseColorOverlayRole] = "useColorOverlay";
result[DatabaseIdRole] = "databaseId";
result[UseSecondTitleRole] = "useSecondTitle";
result[SecondTitleRole] = "secondTitle";
return result;
}
......@@ -75,6 +84,18 @@ QVariant ViewsModel::data(const QModelIndex &index, int role) const
case ColumnRoles::ImageNameRole:
result = d->mViewsData->iconUrl(index.row());
break;
case ColumnRoles::UseColorOverlayRole:
result = d->mViewsData->iconUseColorOverlay(index.row());
break;
case ColumnRoles::DatabaseIdRole:
result = d->mViewsData->databaseId(index.row());
break;
case ColumnRoles::SecondTitleRole:
result = d->mViewsData->secondTitle(index.row());
break;
case ColumnRoles::UseSecondTitleRole:
result = d->mViewsData->useSecondTitle(index.row());
break;
}
return result;
......@@ -117,10 +138,52 @@ void ViewsModel::setViewsData(ViewsListData *viewsData)
return;
}
beginResetModel();
if (viewsData) {
beginResetModel();
}
d->mViewsData = viewsData;
Q_EMIT viewsDataChanged();
endResetModel();
if (d->mViewsData) {
connect(d->mViewsData, &ViewsListData::dataAboutToBeAdded,
this, &ViewsModel::dataAboutToBeAdded);
connect(d->mViewsData, &ViewsListData::dataAdded,
this, &ViewsModel::dataAdded);
connect(d->mViewsData, &ViewsListData::dataAboutToBeRemoved,
this, &ViewsModel::dataAboutToBeRemoved);
connect(d->mViewsData, &ViewsListData::dataRemoved,
this, &ViewsModel::dataRemoved);
connect(d->mViewsData, &ViewsListData::dataModified,
this, &ViewsModel::dataModified);
endResetModel();
}
}
void ViewsModel::dataAboutToBeAdded(int startIndex, int lastIndex)
{
beginInsertRows({}, startIndex, lastIndex);
}
void ViewsModel::dataAdded()
{
endInsertRows();
}
void ViewsModel::dataAboutToBeRemoved(int startIndex, int lastIndex)
{
beginRemoveRows({}, startIndex, lastIndex);
}
void ViewsModel::dataRemoved()
{
endRemoveRows();
}
void ViewsModel::dataModified(int currentIndex)
{
Q_EMIT dataChanged(index(currentIndex, 0), index(currentIndex, 0));
}
......
......@@ -9,7 +9,8 @@
#include "elisaLib_export.h"
#include "viewmanager.h"
#include "elisautils.h"
#include "datatypes.h"
#include <QAbstractListModel>
......@@ -25,9 +26,13 @@ class ELISALIB_EXPORT ViewsModel : public QAbstractListModel
Q_PROPERTY(ViewsListData* viewsData READ viewsData WRITE setViewsData NOTIFY viewsDataChanged)
public:
enum ColumnRoles {
ImageNameRole = Qt::UserRole + 1,
SecondTitleRole,
UseSecondTitleRole,
DatabaseIdRole,
EntryTypeRole,
UseColorOverlayRole,
};
explicit ViewsModel(QObject *parent = nullptr);
......@@ -56,6 +61,18 @@ public Q_SLOTS:
void setViewsData(ViewsListData* viewsData);
private Q_SLOTS:
void dataAboutToBeAdded(int startIndex, int lastIndex);
void dataAdded();
void dataAboutToBeRemoved(int startIndex, int lastIndex);
void dataRemoved();
void dataModified(int currentIndex);
private:
std::unique_ptr<ViewsModelPrivate> d;
......
......@@ -146,6 +146,10 @@ RowLayout {
ViewsListData {
id: viewsData
manager: elisa.musicManager
database: elisa.musicManager.viewDatabase
embeddedCategory: elisa.embeddedView
}
ViewSelector {
......
......@@ -4,8 +4,8 @@
SPDX-License-Identifier: LGPL-3.0-or-later
*/
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQml.Models 2.2
import QtGraphicalEffects 1.0
import org.kde.kirigami 2.5 as Kirigami
......@@ -29,64 +29,68 @@ FocusScope {
implicitWidth: 225
ScrollView {
focus: true
ListView {
id: viewModeView
anchors.fill: parent
z: 2
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: viewModeView
Accessible.role: Accessible.List
Accessible.role: Accessible.List
focus: true
activeFocusOnTab: true
keyNavigationEnabled: true
focus: true
property bool ignoreCurrentItemChanges: false
activeFocusOnTab: true
keyNavigationEnabled: true
interactive: true
z: 2
ScrollBar.vertical: ScrollBar {
id: scrollBar
anchors.topMargin: Kirigami.Units.largeSpacing * 2
model: DelegateModel {
id: pageDelegateModel
policy: ScrollBar.AlwaysOn
active: true
interactive: true
}
delegate: ViewSelectorDelegate {
id: entry
boundsBehavior: Flickable.StopAtBounds
clip: true
height: Kirigami.Units.iconSizes.smallMedium + 3 * Kirigami.Units.smallSpacing
width: viewModeView.width
ScrollHelper {
id: scrollHelper
flickable: viewModeView
anchors.fill: viewModeView
}
focus: true
property bool ignoreCurrentItemChanges: false
isSelected: viewModeView.currentIndex === index
model: DelegateModel {
id: pageDelegateModel
onClicked: {
viewModeView.currentIndex = index
entry.forceActiveFocus()
}
}
}
delegate: ViewSelectorDelegate {
id: entry
footer: MouseArea {
height: Kirigami.Units.iconSizes.smallMedium + 3 * Kirigami.Units.smallSpacing
width: viewModeView.width
height: viewModeView.height - y
acceptedButtons: Qt.LeftButton
focus: true
isSelected: viewModeView.currentIndex === index
onClicked:
{
rootFocusScope.focus = true
colorizeIcon: model.useColorOverlay
image: model.image
title: model.display
secondTitle: model.secondTitle
useSecondTitle: model.useSecondTitle
databaseId: model.databaseId
onClicked: {
viewModeView.currentIndex = index
entry.forceActiveFocus()
}
}
onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentIndex)
}
onCurrentItemChanged: if (!ignoreCurrentItemChanges) switchView(currentIndex)
}
Connections {
......
......@@ -13,6 +13,12 @@ FocusScope {
id: rootItem
property bool isSelected
property alias colorizeIcon: viewIcon.colorizeIcon
property string title
property alias image: viewIcon.source
property int databaseId
property string secondTitle
property bool useSecondTitle
signal clicked()
......@@ -20,20 +26,22 @@ FocusScope {
id: backgroundHighlight
anchors.fill: parent
z: 1
z: 0
color: "transparent"
}
Accessible.role: Accessible.ListItem
Accessible.description: model.display
Accessible.name: model.display
Accessible.description: title
Accessible.name: title
MouseArea {
id: hoverArea
anchors.fill: parent
z: 2
z: 1
hoverEnabled: true
acceptedButtons: Qt.LeftButton
......@@ -50,11 +58,11 @@ FocusScope {
sourceComponent: ToolTip {
delay: Qt.styleHints.mousePressAndHoldInterval
text: model.display
text: title
visible: hoverArea && hoverArea.containsMouse && !nameLabel.visible
contentItem: Label {
text: model.display
text: title
color: myPalette.highlightedText
}
......@@ -80,7 +88,9 @@ FocusScope {
Image {
id: viewIcon
z: 2
property bool colorizeIcon
z: 1
anchors {
verticalCenter: parent.verticalCenter
......@@ -96,9 +106,7 @@ FocusScope {
height: Kirigami.Units.iconSizes.smallMedium
}
source: model.image
layer.enabled: true
layer.enabled: viewIcon.colorizeIcon
layer.effect: ColorOverlay {
color: nameLabel.color
}
......@@ -107,8 +115,6 @@ FocusScope {
LabelWithToolTip {
id: nameLabel
z: 2
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Kirigami.Units.largeSpacing
anchors.left: viewIcon.right
......@@ -116,7 +122,7 @@ FocusScope {
anchors.rightMargin: Kirigami.Units.largeSpacing
verticalAlignment: "AlignVCenter"
text: model.display
text: title
elide: Text.ElideRight
opacity: textOpacity
......
......@@ -96,6 +96,8 @@ ViewManager::~ViewManager() = default;
void ViewManager::openView(int viewIndex)
{
qCDebug(orgKdeElisaViews()) << "ViewManager::openView" << viewIndex << d->mViewParametersStack.size() << d->mViewsListData;
if (!d->mViewsListData) {
return;
}
......@@ -104,16 +106,22 @@ void ViewManager::openView(int viewIndex)
return;
}
qCDebug(orgKdeElisaViews()) << "ViewManager::openView" << viewIndex << d->mViewParametersStack.size();
if (viewIndex < 0 || viewIndex >= d->mViewsListData->count()) {
viewIndex = 0;
}
const auto &viewParameters = d->mViewsListData->viewParameters(viewIndex);
qCDebug(orgKdeElisaViews()) << "ViewManager::openView" << "selected view";
if (viewParameters != d->mViewParametersStack.back()) {
qCDebug(orgKdeElisaViews()) << "ViewManager::openView" << "changing view";
d->mViewIndex = viewIndex;
Q_EMIT viewIndexChanged();
d->mNextViewParameters = viewParameters;
openViewFromData(viewParameters);
applyFilter(d->mNextViewParameters, viewParameters.mMainTitle, d->mNextViewParameters);
openViewFromData(d->mNextViewParameters);
}
}
......@@ -133,6 +141,10 @@ void ViewManager::openChildView(const DataTypes::MusicDataType &fullData)
return;
}
if (!d->mViewsListData) {
return;
}
const auto &lastView = d->mViewParametersStack.back();
auto dataType = fullData[DataTypes::ElementTypeRole].value<ElisaUtils::PlayListEntryType>();
auto title = fullData[DataTypes::TitleRole].toString();
......@@ -151,26 +163,7 @@ void ViewManager::openChildView(const DataTypes::MusicDataType &fullData)
nextViewParameters.mFilterType = ElisaUtils::FilterByGenreAndArtist;
}
switch (nextViewParameters.mFilterType)
{
case ElisaUtils::NoFilter:
case ElisaUtils::FilterByRecentlyPlayed:
case ElisaUtils::FilterByFrequentlyPlayed:
case ElisaUtils::FilterByPath:
case ElisaUtils::FilterById:
case ElisaUtils::UnknownFilter:
break;
case ElisaUtils::FilterByGenre:
nextViewParameters.mDataFilter[DataTypes::GenreRole] = title;
break;
case ElisaUtils::FilterByGenreAndArtist:
nextViewParameters.mDataFilter = lastView.mDataFilter;
nextViewParameters.mDataFilter[DataTypes::ArtistRole] = title;
break;
case ElisaUtils::FilterByArtist:
nextViewParameters.mDataFilter[DataTypes::ArtistRole] = title;
break;
}
applyFilter(nextViewParameters, title, lastView);