/* SPDX-FileCopyrightText: 2018 (c) Matthieu Gallien SPDX-License-Identifier: LGPL-3.0-or-later */ #include "viewmanager.h" #include "viewslistdata.h" #include "viewconfigurationdata.h" #include "viewsLogging.h" #include "models/datamodel.h" #include "models/gridviewproxymodel.h" #if KFKIO_FOUND #include "models/filebrowsermodel.h" #include "models/filebrowserproxymodel.h" #endif #include "elisa_settings.h" #include #include #include class ViewManagerPrivate { public: ViewsListData *mViewsListData = nullptr; QMap mChildViews = { {ElisaUtils::Album, {{}, QUrl{QStringLiteral("image://icon/view-media-track")}, ViewManager::TrackView, ViewManager::GenericDataModel, ElisaUtils::FilterById, ElisaUtils::Track, DataTypes::TrackNumberRole, {DataTypes::TrackNumberRole, Qt::DisplayRole}, {i18nc("@title:inmenu", "Track Number"), i18nc("@title:inmenu", "Title")}, Qt::AscendingOrder, {i18nc("@item:inmenu", "Ascending"), i18nc("@item:inmenu", "Descending"), i18nc("@item:inmenu", "A-Z"), i18nc("@item:inmenu", "Z-A")}, ViewManager::SingleAlbum, ViewManager::DiscHeaders}}, {ElisaUtils::Genre, {{}, QUrl{QStringLiteral("image://icon/view-media-artist")}, ViewManager::GridView, ViewManager::GenericDataModel, ElisaUtils::FilterByGenre, ElisaUtils::Artist, Qt::DisplayRole, {Qt::DisplayRole}, {i18nc("@title:inmenu", "Name")}, Qt::AscendingOrder, {i18nc("@item:inmenu", "A-Z"), i18nc("@item:inmenu", "Z-A")}, QUrl{QStringLiteral("image://icon/view-media-artist")}, ViewManager::DelegateWithoutSecondaryText, ViewManager::ViewHideRating}}, {ElisaUtils::Artist, {{}, QUrl{QStringLiteral("image://icon/view-media-album-cover")}, ViewManager::GridView, ViewManager::GenericDataModel, ElisaUtils::FilterByArtist, ElisaUtils::Album, DataTypes::TitleRole, {DataTypes::TitleRole, DataTypes::GenreRole, DataTypes::YearRole}, {i18nc("@title:inmenu", "Title"), i18nc("@title:inmenu", "Genre"), i18nc("@title:inmenu", "Year")}, Qt::AscendingOrder, {i18nc("@item:inmenu", "A-Z"), i18nc("@item:inmenu", "Z-A"), i18nc("@item:inmenu", "A-Z"), i18nc("@item:inmenu", "Z-A"), i18nc("@item:inmenu", "Oldest First"), i18nc("@item:inmenu", "Newest First")}, QUrl{QStringLiteral("image://icon/media-default-album")}, ViewManager::DelegateWithSecondaryText, ViewManager::ViewShowRating}}, {ElisaUtils::Container, {{}, QUrl{QStringLiteral("image://icon/folder")}, ViewManager::GridView, ViewManager::FileBrowserModel, ElisaUtils::FilterByPath, ElisaUtils::FileName, Qt::DisplayRole, {Qt::DisplayRole}, {i18nc("@title:inmenu", "Name")}, Qt::AscendingOrder, {i18nc("@item:inmenu", "A-Z"), i18nc("@item:inmenu", "Z-A")}, QUrl{QStringLiteral("image://icon/folder")}, ViewManager::DelegateWithoutSecondaryText, ViewManager::ViewHideRating}}, }; int mViewIndex = -1; int mInitialIndex = -1; QString mInitialFilesViewPath = QDir::rootPath(); QList mViewParametersStack = (mViewsListData ? QList{mViewsListData->viewParameters(0)} : QList{}); }; ViewManager::ViewManager(QObject *parent) : QObject(parent) , d(std::make_unique()) { } int ViewManager::viewIndex() const { return d->mViewIndex; } int ViewManager::initialIndex() const { return d->mInitialIndex; } ViewsListData *ViewManager::viewsData() const { return d->mViewsListData; } QString ViewManager::initialFilesViewPath() const { return d->mInitialFilesViewPath; } ViewManager::~ViewManager() = default; void ViewManager::openView(int viewIndex) { qCDebug(orgKdeElisaViews()) << "ViewManager::openView" << viewIndex << d->mViewParametersStack.size() << d->mViewsListData; if (!d->mViewsListData || d->mViewsListData->isEmpty()) { return; } d->mViewParametersStack.clear(); if (viewIndex < 0 || viewIndex >= d->mViewsListData->count()) { viewIndex = 0; } auto viewParameters = d->mViewsListData->viewParameters(viewIndex); qCDebug(orgKdeElisaViews()) << "ViewManager::openView" << "changing view" << viewIndex; d->mViewIndex = viewIndex; Q_EMIT viewIndexChanged(); applyFilter(viewParameters, viewParameters.mMainTitle, viewParameters); if (viewParameters.mDataType == ElisaUtils::FileName) { // Folder view viewParameters.mDataFilter[DataTypes::FilePathRole] = QUrl::fromLocalFile(d->mInitialFilesViewPath); } openViewFromData(viewParameters); } void ViewManager::openChildView(const DataTypes::MusicDataType &fullData) { qCDebug(orgKdeElisaViews()) << "ViewManager::openChildView" << fullData[DataTypes::TitleRole] << fullData[DataTypes::SecondaryTextRole] << fullData[DataTypes::ImageUrlRole] << fullData[DataTypes::DatabaseIdRole] << fullData[DataTypes::FilePathRole] << fullData[DataTypes::ElementTypeRole] << fullData[DataTypes::IsSingleDiscAlbumRole] << d->mViewParametersStack.size(); if (!d->mViewParametersStack.size()) { return; } if (!d->mViewsListData || d->mViewsListData->isEmpty()) { return; } const auto &lastView = d->mViewParametersStack.back(); auto dataType = fullData[DataTypes::ElementTypeRole].value(); auto title = fullData[DataTypes::TitleRole].toString(); auto innerSecondaryTitle = fullData[DataTypes::ArtistRole].toString(); auto innerImage = fullData[DataTypes::ImageUrlRole].toUrl(); auto nextViewParameters = d->mChildViews[dataType]; nextViewParameters.mMainTitle = title; nextViewParameters.mSecondaryTitle = innerSecondaryTitle; nextViewParameters.mMainImage = innerImage; nextViewParameters.mDepth = d->mViewParametersStack.size() + 1; nextViewParameters.mDataFilter = fullData; if (lastView.mFilterType == ElisaUtils::FilterByGenre) { nextViewParameters.mFilterType = ElisaUtils::FilterByGenreAndArtist; } applyFilter(nextViewParameters, title, lastView); // Prevent the user from clicking the same view other and over again if (this->isSameView(nextViewParameters, lastView)) { return; } if (dataType == ElisaUtils::Album && nextViewParameters.mDataFilter[DataTypes::IsSingleDiscAlbumRole].toBool()) { nextViewParameters.mAlbumViewStyle = NoDiscHeaders; } openViewFromData(nextViewParameters); } void ViewManager::openAlbumView(const QString &title, const QString &artist, qulonglong databaseId, const QUrl &albumCoverUrl) { openChildView({{DataTypes::ElementTypeRole, ElisaUtils::Album}, {DataTypes::DatabaseIdRole, databaseId}, {DataTypes::TitleRole, title}, {DataTypes::ArtistRole, artist}, {DataTypes::ImageUrlRole, albumCoverUrl},}); } void ViewManager::openArtistView(const QString &artist) { openChildView({{DataTypes::ElementTypeRole, ElisaUtils::Artist}, {DataTypes::TitleRole, artist},}); } void ViewManager::openNowPlaying() { openView(0); } void ViewManager::openViewFromData(const ViewParameters &viewParamaters) { qCDebug(orgKdeElisaViews()) << "ViewManager::openViewFromData" << d->mViewParametersStack.size(); QAbstractItemModel *newModel = nullptr; QAbstractProxyModel *proxyModel = nullptr; switch (viewParamaters.mModelType) { case FileBrowserModel: { #if KFKIO_FOUND newModel = new ::FileBrowserModel; auto *realProxyModel = new FileBrowserProxyModel; proxyModel = realProxyModel; #else newModel = nullptr; proxyModel = nullptr; #endif break; } case GenericDataModel: newModel = new DataModel; proxyModel = new GridViewProxyModel; break; case UnknownModelType: qCDebug(orgKdeElisaViews()) << "ViewManager::openViewFromData" << "unknown model type"; break; } QQmlEngine::setObjectOwnership(newModel, QQmlEngine::JavaScriptOwnership); QQmlEngine::setObjectOwnership(proxyModel, QQmlEngine::JavaScriptOwnership); d->mViewParametersStack.push_back(viewParamaters); switch (viewParamaters.mViewPresentationType) { case ViewPresentationType::GridView: { qCDebug(orgKdeElisaViews()) << "ViewManager::openViewFromData" << viewParamaters.mViewPresentationType << viewParamaters.mFilterType << viewParamaters.mDepth << viewParamaters.mMainTitle << viewParamaters.mSecondaryTitle << viewParamaters.mMainImage << viewParamaters.mDataType << viewParamaters.mModelType << viewParamaters.mFallbackItemIcon << viewParamaters.mDataFilter << viewParamaters.mViewCanBeRated << viewParamaters.mShowSecondaryTextOnDelegates; auto configurationData = std::make_unique(viewParamaters.mFilterType, viewParamaters.mDepth, viewParamaters.mMainTitle, viewParamaters.mSecondaryTitle, viewParamaters.mMainImage, viewParamaters.mDataType, newModel, proxyModel, viewParamaters.mFallbackItemIcon, viewParamaters.mDataFilter, computePreferredSortRole(viewParamaters.mSortRole, viewParamaters.mFilterType), viewParamaters.mSortRoles, viewParamaters.mSortRoleNames, computePreferredSortOrder(viewParamaters.mSortOrder, viewParamaters.mFilterType), viewParamaters.mSortOrderNames, viewParamaters.mViewCanBeRated, viewParamaters.mShowSecondaryTextOnDelegates, computePreferredViewStyle(GridStyle, viewParamaters.mDataType)); QQmlEngine::setObjectOwnership(configurationData.get(), QQmlEngine::JavaScriptOwnership); Q_EMIT openGridView(configurationData.release()); break; } case ViewPresentationType::TrackView: { qCDebug(orgKdeElisaViews()) << "ViewManager::openViewFromData" << viewParamaters.mFilterType << viewParamaters.mDepth << viewParamaters.mMainTitle << viewParamaters.mSecondaryTitle << viewParamaters.mDataFilter[DataTypes::DatabaseIdRole] << viewParamaters.mMainImage << viewParamaters.mModelType << viewParamaters.mDataType << viewParamaters.mDataFilter << viewParamaters.mSortRole << viewParamaters.mSortOrder << viewParamaters.mAlbumCardinality << viewParamaters.mAlbumViewStyle; auto configurationData = std::make_unique(viewParamaters.mFilterType, viewParamaters.mDepth, viewParamaters.mMainTitle, viewParamaters.mSecondaryTitle, viewParamaters.mMainImage, viewParamaters.mDataType, newModel, proxyModel, viewParamaters.mDataFilter, computePreferredSortRole(viewParamaters.mSortRole, viewParamaters.mFilterType), viewParamaters.mSortRoles, viewParamaters.mSortRoleNames, computePreferredSortOrder(viewParamaters.mSortOrder, viewParamaters.mFilterType), viewParamaters.mSortOrderNames, viewParamaters.mAlbumCardinality, viewParamaters.mAlbumViewStyle); QQmlEngine::setObjectOwnership(configurationData.get(), QQmlEngine::JavaScriptOwnership); Q_EMIT openTrackView(configurationData.release()); break; } case ContextView: qCDebug(orgKdeElisaViews()) << "ViewManager::openViewFromData" << viewParamaters.mViewPresentationType << viewParamaters.mDepth << viewParamaters.mMainTitle << viewParamaters.mMainImage; Q_EMIT switchContextView(viewParamaters.mDepth, viewParamaters.mMainTitle, viewParamaters.mMainImage); break; case UnknownViewPresentation: qCDebug(orgKdeElisaViews()) << "ViewManager::openViewFromData" << "unknown view type"; break; } } void ViewManager::applyFilter(ViewParameters &nextViewParameters, QString title, const ViewParameters &lastView) const { 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] = std::move(title); break; case ElisaUtils::FilterByGenreAndArtist: nextViewParameters.mDataFilter = lastView.mDataFilter; nextViewParameters.mDataFilter[DataTypes::ArtistRole] = std::move(title); break; case ElisaUtils::FilterByArtist: nextViewParameters.mDataFilter[DataTypes::ArtistRole] = std::move(title); break; } } QString ViewManager::buildViewId() const { const auto &entryTypeMetaEnum = QMetaEnum::fromType(); const auto currView = d->mViewParametersStack.back(); const auto viewId = QString::fromLatin1(entryTypeMetaEnum.valueToKey(currView.mDataType)); return viewId; } QStringList::iterator ViewManager::findViewPreference(QStringList &list, const QString &viewId) const { auto itViewPreference = list.end(); for(itViewPreference = list.begin(); itViewPreference != list.end(); ++itViewPreference) { auto parts = QStringView(*itViewPreference).split(QStringLiteral("==")); if (parts.size() != 2) { continue; } if (parts[0] == viewId) { break; } } return itViewPreference; } template T ViewManager::computeViewPreference(const T initialValue, QStringList &preferences) const { const auto viewId = buildViewId(); const auto itViewPreference = findViewPreference(preferences, viewId); if (itViewPreference != preferences.end()) { const auto result = QStringView(*itViewPreference).split(QStringLiteral("==")); if (result.size() == 2) { const auto &sortOrderMetaEnum = QMetaEnum::fromType(); bool conversionOk; auto newValue = static_cast(sortOrderMetaEnum.keyToValue(result[1].toLatin1().data(), &conversionOk)); if (conversionOk) { return newValue; } } } return initialValue; } template void ViewManager::updateViewPreference(const T newValue, QStringList &preferences) const { const auto viewId = buildViewId(); const auto itViewPreference = findViewPreference(preferences, viewId); const auto &sortOrderMetaEnum = QMetaEnum::fromType(); const auto enumStringValue = sortOrderMetaEnum.valueToKey(newValue); if (!enumStringValue) { return; } const QString newSortOrderPreference = viewId + QStringLiteral("==") + QString::fromLatin1(enumStringValue); if (itViewPreference != preferences.end()) { (*itViewPreference) = newSortOrderPreference; } else { preferences.push_back(newSortOrderPreference); } } bool ViewManager::viewHasDefaultSortRole(const ElisaUtils::FilterType filterType) const { switch (filterType) { case ElisaUtils::FilterByRecentlyPlayed: case ElisaUtils::FilterByFrequentlyPlayed: case ElisaUtils::FilterById: // Tracks in album view return true; default: return false; } } bool ViewManager::viewHasDefaultViewStyle(const ElisaUtils::PlayListEntryType entryType) const { switch (entryType) { case ElisaUtils::Track: case ElisaUtils::Radio: return true; default: return false; } } Qt::SortOrder ViewManager::computePreferredSortOrder(Qt::SortOrder initialValue, ElisaUtils::FilterType filterType) const { if (viewHasDefaultSortRole(filterType)) { return initialValue; } auto currentSortOrderPreferences = Elisa::ElisaConfiguration::sortOrderPreferences(); return computeViewPreference(initialValue, currentSortOrderPreferences); } int ViewManager::computePreferredSortRole(int initialValue, ElisaUtils::FilterType filterType) const { if (viewHasDefaultSortRole(filterType)) { return initialValue; } auto currentSortRolePreferences = Elisa::ElisaConfiguration::sortRolePreferences(); return computeViewPreference(static_cast(initialValue), currentSortRolePreferences); } ViewManager::ViewStyle ViewManager::computePreferredViewStyle(ViewStyle initialValue, ElisaUtils::PlayListEntryType dataType) const { if (viewHasDefaultViewStyle(dataType)) { return initialValue; } auto currentViewStylePreferences = Elisa::ElisaConfiguration::viewStylePreferences(); return computeViewPreference(initialValue, currentViewStylePreferences); } void ViewManager::goBack() { qCDebug(orgKdeElisaViews()) << "ViewManager::goBack" << d->mViewParametersStack.size() << d->mViewsListData; if (d->mViewParametersStack.size() <= 1) { return; } Q_EMIT popOneView(); if (d->mViewParametersStack.size() > 1) { d->mViewParametersStack.pop_back(); } qCDebug(orgKdeElisaViews()) << "ViewManager::goBack" << d->mViewParametersStack.size(); } void ViewManager::setViewsData(ViewsListData *viewsData) { if (d->mViewsListData == viewsData) { return; } d->mViewsListData = viewsData; Q_EMIT viewsDataChanged(); if (d->mViewsListData && (d->mViewIndex < 0 || d->mViewIndex >= d->mViewsListData->count())) { d->mViewIndex = d->mInitialIndex; } if (d->mViewsListData && d->mViewIndex >= 0 && d->mViewIndex < d->mViewsListData->count()) { openView(d->mViewIndex); } } void ViewManager::setInitialIndex(int newIndex) { if (d->mInitialIndex == newIndex) { return; } d->mInitialIndex = newIndex; Q_EMIT initialIndexChanged(); if (d->mViewsListData && (d->mViewIndex < 0 || d->mViewIndex >= d->mViewsListData->count())) { d->mViewIndex = d->mInitialIndex; } if (d->mViewsListData && d->mViewIndex >= 0 && d->mViewIndex < d->mViewsListData->count()) { openView(d->mViewIndex); } } void ViewManager::setInitialFilesViewPath(const QString &initialPath) { if (d->mInitialFilesViewPath == initialPath) { return; } d->mInitialFilesViewPath = initialPath; Q_EMIT initialFilesViewPathChanged(); } void ViewManager::openInitialView() { openView(d->mInitialIndex); } void ViewManager::sortOrderChanged(Qt::SortOrder sortOrder) { if (viewHasDefaultSortRole(d->mViewParametersStack.back().mFilterType)) { return; } auto currentSortOrderPreferences = Elisa::ElisaConfiguration::sortOrderPreferences(); updateViewPreference(sortOrder, currentSortOrderPreferences); Elisa::ElisaConfiguration::setSortOrderPreferences(currentSortOrderPreferences); Elisa::ElisaConfiguration::self()->save(); } void ViewManager::sortRoleChanged(int sortRole) { if (viewHasDefaultSortRole(d->mViewParametersStack.back().mFilterType)) { return; } auto currentSortRolePreferences = Elisa::ElisaConfiguration::sortRolePreferences(); updateViewPreference(static_cast(sortRole), currentSortRolePreferences); Elisa::ElisaConfiguration::setSortRolePreferences(currentSortRolePreferences); Elisa::ElisaConfiguration::self()->save(); } void ViewManager::setViewStyle(const ViewStyle viewStyle) { if (viewHasDefaultViewStyle(d->mViewParametersStack.back().mDataType)) { return; } auto currentViewStylePreferences = Elisa::ElisaConfiguration::viewStylePreferences(); updateViewPreference(viewStyle, currentViewStylePreferences); Elisa::ElisaConfiguration::setViewStylePreferences(currentViewStylePreferences); Elisa::ElisaConfiguration::self()->save(); } bool ViewManager::isSameView(const ViewParameters ¤tView, const ViewParameters &otherView) const { if (currentView.mDataType != otherView.mDataType) { return false; } // Best case scenario is matching up database ids if (currentView.mDataFilter.hasDatabaseId() && otherView.mDataFilter.hasDatabaseId()) { return currentView.mDataFilter.databaseId() == otherView.mDataFilter.databaseId(); } // Unfortunately we don't have access to database ids for most of these types, so handle specific types differently. switch (currentView.mDataType) { case ElisaUtils::PlayListEntryType::Album: case ElisaUtils::PlayListEntryType::Artist: return currentView.mMainTitle == otherView.mMainTitle; default: return false; } } #include "moc_viewmanager.cpp"