From cdccbf471a88a34d7673712f6f6bead559dcc5be Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sun, 9 Jun 2024 22:40:26 +0300 Subject: [PATCH] "Group by" exists, group sorting rule is separate from sorting rule. Very WIP and buggy --- src/dolphincontextmenu.cpp | 5 +- src/dolphinmainwindow.cpp | 3 +- src/kitemviews/kfileitemmodel.cpp | 145 +++++++++++++---- src/kitemviews/kfileitemmodel.h | 16 +- src/kitemviews/kitemmodelbase.cpp | 52 +++++- src/kitemviews/kitemmodelbase.h | 53 ++++++- src/settings/applyviewpropsjob.cpp | 2 + .../contextmenu/contextmenusettingspage.cpp | 4 + src/settings/dolphin_contextmenusettings.kcfg | 4 + ...dolphin_directoryviewpropertysettings.kcfg | 17 +- src/settings/dolphinsettingsdialog.cpp | 1 + src/views/dolphinview.cpp | 48 ++++++ src/views/dolphinview.h | 22 +++ src/views/dolphinviewactionhandler.cpp | 150 ++++++++++++++++-- src/views/dolphinviewactionhandler.h | 16 ++ src/views/viewproperties.cpp | 26 +++ src/views/viewproperties.h | 6 + 17 files changed, 507 insertions(+), 63 deletions(-) diff --git a/src/dolphincontextmenu.cpp b/src/dolphincontextmenu.cpp index b4aade6b9a..bc00af7cc5 100644 --- a/src/dolphincontextmenu.cpp +++ b/src/dolphincontextmenu.cpp @@ -305,10 +305,13 @@ void DolphinContextMenu::addViewportContextMenu() } addSeparator(); - // Insert 'Sort By' and 'View Mode' + // Insert 'Sort By', 'Group By' and 'View Mode' if (ContextMenuSettings::showSortBy()) { addAction(m_mainWindow->actionCollection()->action(QStringLiteral("sort"))); } + if (ContextMenuSettings::showGroupBy()) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("group"))); + } if (ContextMenuSettings::showViewMode()) { addAction(m_mainWindow->actionCollection()->action(QStringLiteral("view_mode"))); } diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index 3b3d6b8d4f..cb94e86572 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -1496,6 +1496,7 @@ void DolphinMainWindow::updateHamburgerMenu() } menu->addAction(ac->action(QStringLiteral("show_hidden_files"))); menu->addAction(ac->action(QStringLiteral("sort"))); + menu->addAction(ac->action(QStringLiteral("group"))); menu->addAction(ac->action(QStringLiteral("additional_info"))); if (!GeneralSettings::showStatusBar() || !GeneralSettings::showZoomSlider()) { menu->addAction(ac->action(QStringLiteral("zoom"))); @@ -2316,7 +2317,7 @@ void DolphinMainWindow::setupDockWidgets() placesDock->setLocked(lock); placesDock->setObjectName(QStringLiteral("placesDock")); placesDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - + m_placesPanel = new PlacesPanel(placesDock); m_placesPanel->setCustomContextMenuActions({lockLayoutAction}); placesDock->setWidget(m_placesPanel); diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index c694da9f25..ad0fb31a7f 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -34,7 +34,7 @@ Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex) // #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(QObject *parent) - : KItemModelBase("text", parent) + : KItemModelBase("text", "text", parent) , m_dirLister(nullptr) , m_sortDirsFirst(true) , m_sortHiddenLast(false) @@ -387,7 +387,10 @@ QList> KFileItemModel::groups() const QElapsedTimer timer; timer.start(); #endif - switch (typeForRole(sortRole())) { + switch (typeForRole(groupRole())) { + case NoRole: + m_groups.clear(); + break; case NameRole: m_groups = nameRoleGroups(); break; @@ -421,7 +424,7 @@ QList> KFileItemModel::groups() const m_groups = ratingRoleGroups(); break; default: - m_groups = genericStringRoleGroups(sortRole()); + m_groups = genericStringRoleGroups(groupRole()); break; } @@ -886,6 +889,39 @@ void KFileItemModel::removeFilteredChildren(const KItemRangeList &itemRanges) } } +KFileItemModel::RoleInfo KFileItemModel::roleInformation(const QByteArray &role) +{ + static QHash information; + if (information.isEmpty()) { + int count = 0; + const RoleInfoMap *map = rolesInfoMap(count); + for (int i = 0; i < count; ++i) { + RoleInfo info; + info.role = map[i].role; + info.translation = map[i].roleTranslation.toString(); + if (!map[i].groupTranslation.isEmpty()) { + info.group = map[i].groupTranslation.toString(); + } else { + // For top level roles, groupTranslation is 0. We must make sure that + // info.group is an empty string then because the code that generates + // menus tries to put the actions into sub menus otherwise. + info.group = QString(); + } + info.requiresBaloo = map[i].requiresBaloo; + info.requiresIndexer = map[i].requiresIndexer; + if (!map[i].tooltipTranslation.isEmpty()) { + info.tooltip = map[i].tooltipTranslation.toString(); + } else { + info.tooltip = QString(); + } + + information.insert(map[i].role, info); + } + } + + return information.value(role); +} + QList KFileItemModel::rolesInformation() { static QList rolesInfo; @@ -894,24 +930,7 @@ QList KFileItemModel::rolesInformation() const RoleInfoMap *map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { if (map[i].roleType != NoRole) { - RoleInfo info; - info.role = map[i].role; - info.translation = map[i].roleTranslation.toString(); - if (!map[i].groupTranslation.isEmpty()) { - info.group = map[i].groupTranslation.toString(); - } else { - // For top level roles, groupTranslation is 0. We must make sure that - // info.group is an empty string then because the code that generates - // menus tries to put the actions into sub menus otherwise. - info.group = QString(); - } - info.requiresBaloo = map[i].requiresBaloo; - info.requiresIndexer = map[i].requiresIndexer; - if (!map[i].tooltipTranslation.isEmpty()) { - info.tooltip = map[i].tooltipTranslation.toString(); - } else { - info.tooltip = QString(); - } + RoleInfo info = roleInformation(map[i].role); rolesInfo.append(info); } } @@ -942,11 +961,40 @@ void KFileItemModel::onSortRoleChanged(const QByteArray ¤t, const QByteArr } } -void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) { Q_UNUSED(current) Q_UNUSED(previous) - resortAllItems(); + + if (resortItems) { + resortAllItems(); + } +} + +void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) +{ + Q_UNUSED(previous) + m_groupRole = typeForRole(current); + + if (!m_requestRole[m_sortRole]) { + QSet newRoles = m_roles; + newRoles << current; + setRoles(newRoles); + } + + if (resortItems) { + resortAllItems(); + } +} + +void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + + if (resortItems) { + resortAllItems(); + } } void KFileItemModel::loadSortingSettings() @@ -1001,8 +1049,44 @@ void KFileItemModel::resortAllItems() m_items.clear(); m_items.reserve(itemCount); - // Resort the items - sort(m_itemData.begin(), m_itemData.end()); + const QList> oldGroups = m_groups; + + if (groupedSorting() && m_groupRole) { + // Hacky way to implement grouped sorting without rewriting more code. + // 1. Sort all items by grouping criteria. "Folders first" priority to be ignored. + // 2. Generate groups, which will stay usable after in-group sorting. + // 3. Perform sorts by the original sorting criteria inside groups. + + RoleType originalSortRole = m_sortRole; + Qt::SortOrder originalSortOrder = sortOrder(); + bool originalSortDirsFirst = m_sortDirsFirst; + m_sortRole = m_groupRole; + setSortOrder(groupOrder(), false); + m_sortDirsFirst = false; + + sort(m_itemData.begin(), m_itemData.end()); + m_groups.clear(); + groups(); + + m_sortRole = originalSortRole; + setSortOrder(originalSortOrder, false); + m_sortDirsFirst = originalSortDirsFirst; + + int lastIndex = 0, newIndex = 0; + for (int i = 0; i < m_groups.count() - 1; ++i) { + qCritical() << m_groups[i]; + fflush(stderr); + newIndex = m_groups[i + 1].first; + sort(m_itemData.begin() + lastIndex, m_itemData.begin() + newIndex); + lastIndex = newIndex; + } + } else { + if (!m_groups.isEmpty()) { + m_groups.clear(); + } + sort(m_itemData.begin(), m_itemData.end()); + } + for (int i = 0; i < itemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -1037,15 +1121,12 @@ void KFileItemModel::resortAllItems() Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes); } else if (groupedSorting()) { - // The groups might have changed even if the order of the items has not. - const QList> oldGroups = m_groups; - m_groups.clear(); - if (groups() != oldGroups) { + if (m_groups != oldGroups) { Q_EMIT groupsChanged(); } } -#ifdef KFILEITEMMODEL_DEBUG +#ifdef KFILEITEMMODEL_DEBUGf qCDebug(DolphinDebug) << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed(); #endif } @@ -1559,6 +1640,8 @@ void KFileItemModel::insertItems(QList &newItems) std::reverse(itemRanges.begin(), itemRanges.end()); } + // N + //resortAllItems(); // The indexes in m_items are not correct anymore. Therefore, we clear m_items. // It will be re-populated with the updated indices if index(const QUrl&) is called. m_items.clear(); @@ -2725,7 +2808,7 @@ const KFileItemModel::RoleInfoMap *KFileItemModel::rolesInfoMap(int &count) static const RoleInfoMap rolesInfoMap[] = { // clang-format off // | role | roleType | role translation | group translation | requires Baloo | requires indexer - { nullptr, NoRole, KLazyLocalizedString(), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, + { nullptr, NoRole, kli18nc("@label", "None"), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, { "text", NameRole, kli18nc("@label", "Name"), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, { "size", SizeRole, kli18nc("@label", "Size"), KLazyLocalizedString(), KLazyLocalizedString(), false, false }, { "modificationtime", ModificationTimeRole, kli18nc("@label", "Modified"), KLazyLocalizedString(), kli18nc("@tooltip", "The date format can be selected in settings."), false, false }, diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index ce58f89acc..6cbfab6037 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -196,6 +196,13 @@ public: bool requiresIndexer; }; + /** + * @return Provides static information for a role that is supported + * by KFileItemModel. Some roles can only be determined if + * Baloo is enabled and/or the Baloo indexing is enabled. + */ + static RoleInfo roleInformation(const QByteArray &role); + /** * @return Provides static information for all available roles that * are supported by KFileItemModel. Some roles can only be @@ -286,12 +293,14 @@ Q_SIGNALS: protected: void onGroupedSortingChanged(bool current) override; void onSortRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = true) override; - void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override; + void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true) override; + void onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = true) override; + void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true) override; private Q_SLOTS: /** - * Resorts all items dependent on the set sortRole(), sortOrder() - * and foldersFirst() settings. + * Resorts all items dependent on the set sortRole(), sortOrder(), + * groupRole(), groupOrder() and foldersFirst() settings. */ void resortAllItems(); @@ -542,6 +551,7 @@ private: bool m_sortHiddenLast; RoleType m_sortRole; + RoleType m_groupRole; int m_sortingProgressPercent; // Value of directorySortingProgress() signal QSet m_roles; diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index 9fdecafb86..b846318ee7 100644 --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -13,14 +13,18 @@ KItemModelBase::KItemModelBase(QObject *parent) , m_groupedSorting(false) , m_sortRole() , m_sortOrder(Qt::AscendingOrder) + , m_groupRole() + , m_groupOrder(Qt::AscendingOrder) { } -KItemModelBase::KItemModelBase(const QByteArray &sortRole, QObject *parent) +KItemModelBase::KItemModelBase(const QByteArray &sortRole, const QByteArray &groupRole, QObject *parent) : QObject(parent) , m_groupedSorting(false) , m_sortRole(sortRole) , m_sortOrder(Qt::AscendingOrder) + , m_groupRole(groupRole) + , m_groupOrder(Qt::AscendingOrder) { } @@ -64,16 +68,41 @@ QByteArray KItemModelBase::sortRole() const return m_sortRole; } -void KItemModelBase::setSortOrder(Qt::SortOrder order) +void KItemModelBase::setSortOrder(Qt::SortOrder order, bool resortItems) { if (order != m_sortOrder) { const Qt::SortOrder previous = m_sortOrder; m_sortOrder = order; - onSortOrderChanged(order, previous); + onSortOrderChanged(order, previous, resortItems); Q_EMIT sortOrderChanged(order, previous); } } +void KItemModelBase::setGroupRole(const QByteArray &role, bool regroupItems) +{ + if (role != m_groupRole) { + const QByteArray previous = m_groupRole; + m_groupRole = role; + onGroupRoleChanged(role, previous, regroupItems); + Q_EMIT groupRoleChanged(role, previous); + } +} + +QByteArray KItemModelBase::groupRole() const +{ + return m_groupRole; +} + +void KItemModelBase::setGroupOrder(Qt::SortOrder order, bool resortItems) +{ + if (order != m_groupOrder) { + const Qt::SortOrder previous = m_groupOrder; + m_groupOrder = order; + onGroupOrderChanged(order, previous, resortItems); + Q_EMIT groupOrderChanged(order, previous); + } +} + QString KItemModelBase::roleDescription(const QByteArray &role) const { return QString::fromLatin1(role); @@ -151,10 +180,25 @@ void KItemModelBase::onSortRoleChanged(const QByteArray ¤t, const QByteArr Q_UNUSED(resortItems) } -void KItemModelBase::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +void KItemModelBase::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) { Q_UNUSED(current) Q_UNUSED(previous) + Q_UNUSED(resortItems) +} + +void KItemModelBase::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + Q_UNUSED(resortItems) +} + +void KItemModelBase::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + Q_UNUSED(resortItems) } QUrl KItemModelBase::url(int index) const diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h index 42a9c54c94..c336a07265 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -41,7 +41,7 @@ class DOLPHIN_EXPORT KItemModelBase : public QObject public: explicit KItemModelBase(QObject *parent = nullptr); - explicit KItemModelBase(const QByteArray &sortRole, QObject *parent = nullptr); + explicit KItemModelBase(const QByteArray &sortRole, const QByteArray &groupRole, QObject *parent = nullptr); ~KItemModelBase() override; /** @return The number of items. */ @@ -81,9 +81,26 @@ public: * called so that model-implementations can react on the sort order change. Afterwards the * signal sortOrderChanged() will be emitted. */ - void setSortOrder(Qt::SortOrder order); + void setSortOrder(Qt::SortOrder order, bool resortItems = true); Qt::SortOrder sortOrder() const; + /** + * Sets the group-role to \a role. The method KItemModelBase::onGroupRoleChanged() will be + * called so that model-implementations can react on the group-role change. Afterwards the + * signal groupRoleChanged() will be emitted. + * The implementation should regroup only if \a regroupItems is true. + */ + void setGroupRole(const QByteArray &role, bool regroupItems = true); + QByteArray groupRole() const; + + /** + * Sets the group order to \a order. The method KItemModelBase::onGroupOrderChanged() will be + * called so that model-implementations can react on the group order change. Afterwards the + * signal groupOrderChanged() will be emitted. + */ + void setGroupOrder(Qt::SortOrder order, bool resortItems = true); + Qt::SortOrder groupOrder() const; + /** * @return Translated description for the \p role. The description is e.g. used * for the header in KItemListView. @@ -247,6 +264,8 @@ Q_SIGNALS: void groupedSortingChanged(bool current); void sortRoleChanged(const QByteArray ¤t, const QByteArray &previous); void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); + void groupRoleChanged(const QByteArray ¤t, const QByteArray &previous); + void groupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); protected: /** @@ -274,12 +293,35 @@ protected: * itemsRemoved() signal for all items, reorder the items internally and to emit a * itemsInserted() signal afterwards. */ - virtual void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); + virtual void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true); + + /** + * Is invoked if the sort role has been changed by KItemModelBase::setSortRole(). Allows + * to react on the changed sort role before the signal sortRoleChanged() will be emitted. + * The implementation must assure that the items are sorted by the role given by \a current. + * Usually the most efficient way is to emit a + * itemsRemoved() signal for all items, reorder the items internally and to emit a + * itemsInserted() signal afterwards. + * The implementation should resort only if \a regroupItems is true. + */ + virtual void onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = true); + + /** + * Is invoked if the sort order has been changed by KItemModelBase::setSortOrder(). Allows + * to react on the changed sort order before the signal sortOrderChanged() will be emitted. + * The implementation must assure that the items are sorted by the order given by \a current. + * Usually the most efficient way is to emit a + * itemsRemoved() signal for all items, reorder the items internally and to emit a + * itemsInserted() signal afterwards. + */ + virtual void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true); private: bool m_groupedSorting; QByteArray m_sortRole; Qt::SortOrder m_sortOrder; + QByteArray m_groupRole; + Qt::SortOrder m_groupOrder; }; inline Qt::SortOrder KItemModelBase::sortOrder() const @@ -287,4 +329,9 @@ inline Qt::SortOrder KItemModelBase::sortOrder() const return m_sortOrder; } +inline Qt::SortOrder KItemModelBase::groupOrder() const +{ + return m_groupOrder; +} + #endif diff --git a/src/settings/applyviewpropsjob.cpp b/src/settings/applyviewpropsjob.cpp index 2a2b4bfe4f..21c30af022 100644 --- a/src/settings/applyviewpropsjob.cpp +++ b/src/settings/applyviewpropsjob.cpp @@ -24,6 +24,8 @@ ApplyViewPropsJob::ApplyViewPropsJob(const QUrl &dir, const ViewProperties &view m_viewProps->setHiddenFilesShown(viewProps.hiddenFilesShown()); m_viewProps->setSortRole(viewProps.sortRole()); m_viewProps->setSortOrder(viewProps.sortOrder()); + m_viewProps->setGroupRole(viewProps.groupRole()); + m_viewProps->setGroupOrder(viewProps.groupOrder()); KIO::ListJob *listJob = KIO::listRecursive(dir, KIO::HideProgressInfo); connect(listJob, &KIO::ListJob::entries, this, &ApplyViewPropsJob::slotEntries); diff --git a/src/settings/contextmenu/contextmenusettingspage.cpp b/src/settings/contextmenu/contextmenusettingspage.cpp index c401c2d6b6..64b78d2bdc 100644 --- a/src/settings/contextmenu/contextmenusettingspage.cpp +++ b/src/settings/contextmenu/contextmenusettingspage.cpp @@ -110,6 +110,8 @@ bool ContextMenuSettingsPage::entryVisible(const QString &id) return ContextMenuSettings::showAddToPlaces(); } else if (id == "sort") { return ContextMenuSettings::showSortBy(); + } else if (id == "group") { + return ContextMenuSettings::showGroupBy(); } else if (id == "view_mode") { return ContextMenuSettings::showViewMode(); } else if (id == "open_in_new_tab") { @@ -138,6 +140,8 @@ void ContextMenuSettingsPage::setEntryVisible(const QString &id, bool visible) ContextMenuSettings::setShowAddToPlaces(visible); } else if (id == "sort") { ContextMenuSettings::setShowSortBy(visible); + } else if (id == "group") { + ContextMenuSettings::setShowGroupBy(visible); } else if (id == "view_mode") { ContextMenuSettings::setShowViewMode(visible); } else if (id == "open_in_new_tab") { diff --git a/src/settings/dolphin_contextmenusettings.kcfg b/src/settings/dolphin_contextmenusettings.kcfg index 6e45d9bcdf..ac7e8f9ae3 100644 --- a/src/settings/dolphin_contextmenusettings.kcfg +++ b/src/settings/dolphin_contextmenusettings.kcfg @@ -18,6 +18,10 @@ true + + + true + true diff --git a/src/settings/dolphin_directoryviewpropertysettings.kcfg b/src/settings/dolphin_directoryviewpropertysettings.kcfg index f4d2883696..620983b19f 100644 --- a/src/settings/dolphin_directoryviewpropertysettings.kcfg +++ b/src/settings/dolphin_directoryviewpropertysettings.kcfg @@ -36,7 +36,7 @@ When this option is enabled, the sorted items are categorized into groups. - false + true @@ -52,6 +52,19 @@ Qt::DescendingOrder + + + This option defines which attribute (text, size, date, etc.) grouping is performed on. + + + + + + Qt::AscendingOrder + Qt::AscendingOrder + Qt::DescendingOrder + + true @@ -84,5 +97,3 @@ - - diff --git a/src/settings/dolphinsettingsdialog.cpp b/src/settings/dolphinsettingsdialog.cpp index c564dd196c..0fd4328057 100644 --- a/src/settings/dolphinsettingsdialog.cpp +++ b/src/settings/dolphinsettingsdialog.cpp @@ -65,6 +65,7 @@ DolphinSettingsDialog::DolphinSettingsDialog(const QUrl &url, QWidget *parent, K actions, {QStringLiteral("add_to_places"), QStringLiteral("sort"), + QStringLiteral("group"), QStringLiteral("view_mode"), QStringLiteral("open_in_new_tab"), QStringLiteral("open_in_new_window"), diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index d42d9cfcd8..9dedb9661c 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -503,6 +503,42 @@ Qt::SortOrder DolphinView::sortOrder() const return m_model->sortOrder(); } +void DolphinView::setGroupRole(const QByteArray &role) +{ + if (role != groupRole()) { + ViewProperties props(viewPropertiesUrl()); + props.setGroupRole(role); + + KItemModelBase *model = m_container->controller()->model(); + model->setGroupRole(role); + + Q_EMIT groupRoleChanged(role); + } +} + +QByteArray DolphinView::groupRole() const +{ + const KItemModelBase *model = m_container->controller()->model(); + return model->groupRole(); +} + +void DolphinView::setGroupOrder(Qt::SortOrder order) +{ + if (groupOrder() != order) { + ViewProperties props(viewPropertiesUrl()); + props.setGroupOrder(order); + + m_model->setGroupOrder(order); + + Q_EMIT groupOrderChanged(order); + } +} + +Qt::SortOrder DolphinView::groupOrder() const +{ + return m_model->groupOrder(); +} + void DolphinView::setSortFoldersFirst(bool foldersFirst) { if (sortFoldersFirst() != foldersFirst) { @@ -2114,6 +2150,18 @@ void DolphinView::applyViewProperties(const ViewProperties &props) Q_EMIT sortOrderChanged(sortOrder); } + const QByteArray groupRole = props.groupRole(); + if (groupRole != m_model->groupRole()) { + m_model->setGroupRole(groupRole); + Q_EMIT groupRoleChanged(groupRole); + } + + const Qt::SortOrder groupOrder = props.groupOrder(); + if (groupOrder != m_model->groupOrder()) { + m_model->setGroupOrder(groupOrder); + Q_EMIT groupOrderChanged(groupOrder); + } + const bool sortFoldersFirst = props.sortFoldersFirst(); if (sortFoldersFirst != m_model->sortDirectoriesFirst()) { m_model->setSortDirectoriesFirst(sortFoldersFirst); diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index b55e2ee9be..f3c0189bf9 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -51,6 +51,8 @@ class QRegularExpression; * - show hidden files * - show previews * - enable grouping + * - grouping order + * - grouping type */ class DOLPHIN_EXPORT DolphinView : public QWidget { @@ -219,6 +221,20 @@ public: void setSortOrder(Qt::SortOrder order); Qt::SortOrder sortOrder() const; + /** + * Updates the view properties of the current URL to the + * grouping given by \a role. + */ + void setGroupRole(const QByteArray &role); + QByteArray groupRole() const; + + /** + * Updates the view properties of the current URL to the + * sort order given by \a order. + */ + void setGroupOrder(Qt::SortOrder order); + Qt::SortOrder groupOrder() const; + /** Sets a separate sorting with folders first (true) or a mixed sorting of files and folders (false). */ void setSortFoldersFirst(bool foldersFirst); bool sortFoldersFirst() const; @@ -522,6 +538,12 @@ Q_SIGNALS: /** Is emitted if the sort order (ascending or descending) has been changed. */ void sortOrderChanged(Qt::SortOrder order); + /** Is emitted if the grouping by name, size or date has been changed. */ + void groupRoleChanged(const QByteArray &role); + + /** Is emitted if the group order (ascending or descending) has been changed. */ + void groupOrderChanged(Qt::SortOrder order); + /** * Is emitted if the sorting of files and folders (separate with folders * first or mixed) has been changed. diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 2934e80058..7a3c758a57 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -33,6 +33,7 @@ DolphinViewActionHandler::DolphinViewActionHandler(KActionCollection *collection , m_actionCollection(collection) , m_currentView(nullptr) , m_sortByActions() + , m_groupByActions() , m_visibleRoles() { Q_ASSERT(m_actionCollection); @@ -58,6 +59,8 @@ void DolphinViewActionHandler::setCurrentView(DolphinView *view) connect(view, &DolphinView::groupedSortingChanged, this, &DolphinViewActionHandler::slotGroupedSortingChanged); connect(view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewActionHandler::slotHiddenFilesShownChanged); connect(view, &DolphinView::sortRoleChanged, this, &DolphinViewActionHandler::slotSortRoleChanged); + connect(view, &DolphinView::groupRoleChanged, this, &DolphinViewActionHandler::slotGroupRoleChanged); + connect(view, &DolphinView::groupOrderChanged, this, &DolphinViewActionHandler::slotGroupOrderChanged); connect(view, &DolphinView::zoomLevelChanged, this, &DolphinViewActionHandler::slotZoomLevelChanged); connect(view, &DolphinView::writeStateChanged, this, &DolphinViewActionHandler::slotWriteStateChanged); slotWriteStateChanged(view->isFolderWritable()); @@ -278,24 +281,54 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac QActionGroup *group = new QActionGroup(sortByActionMenu); group->setExclusive(true); - KToggleAction *ascendingAction = m_actionCollection->add(QStringLiteral("ascending")); - ascendingAction->setActionGroup(group); - connect(ascendingAction, &QAction::triggered, this, [this] { + KToggleAction *sortAscendingAction = m_actionCollection->add(QStringLiteral("sort_ascending")); + sortAscendingAction->setActionGroup(group); + connect(sortAscendingAction, &QAction::triggered, this, [this] { m_currentView->setSortOrder(Qt::AscendingOrder); }); - KToggleAction *descendingAction = m_actionCollection->add(QStringLiteral("descending")); - descendingAction->setActionGroup(group); - connect(descendingAction, &QAction::triggered, this, [this] { + KToggleAction *sortDescendingAction = m_actionCollection->add(QStringLiteral("sort_descending")); + sortDescendingAction->setActionGroup(group); + connect(sortDescendingAction, &QAction::triggered, this, [this] { m_currentView->setSortOrder(Qt::DescendingOrder); }); - sortByActionMenu->addAction(ascendingAction); - sortByActionMenu->addAction(descendingAction); + sortByActionMenu->addAction(sortAscendingAction); + sortByActionMenu->addAction(sortDescendingAction); sortByActionMenu->addSeparator(); sortByActionMenu->addAction(sortFoldersFirst); sortByActionMenu->addAction(sortHiddenLast); + // View -> Group By + QActionGroup *groupByActionGroup = createFileItemRolesActionGroup(QStringLiteral("group_by_")); + + KActionMenu *groupByActionMenu = m_actionCollection->add(QStringLiteral("group")); + groupByActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); + groupByActionMenu->setText(i18nc("@action:inmenu View", "Group By")); + groupByActionMenu->setPopupMode(QToolButton::InstantPopup); + + const auto groupByActionGroupActions = groupByActionGroup->actions(); + for (QAction *action : groupByActionGroupActions) { + groupByActionMenu->addAction(action); + } + + groupByActionMenu->addSeparator(); + + KToggleAction *groupAscendingAction = m_actionCollection->add(QStringLiteral("group_ascending")); + groupAscendingAction->setActionGroup(group); + connect(groupAscendingAction, &QAction::triggered, this, [this] { + m_currentView->setGroupOrder(Qt::AscendingOrder); + }); + + KToggleAction *groupDescendingAction = m_actionCollection->add(QStringLiteral("group_descending")); + groupDescendingAction->setActionGroup(group); + connect(groupDescendingAction, &QAction::triggered, this, [this] { + m_currentView->setGroupOrder(Qt::DescendingOrder); + }); + + groupByActionMenu->addAction(groupAscendingAction); + groupByActionMenu->addAction(groupDescendingAction); + // View -> Additional Information QActionGroup *visibleRolesGroup = createFileItemRolesActionGroup(QStringLiteral("show_")); @@ -344,12 +377,15 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QString &groupPrefix) { const bool isSortGroup = (groupPrefix == QLatin1String("sort_by_")); - Q_ASSERT(isSortGroup || groupPrefix == QLatin1String("show_")); + const bool isGroupGroup = (groupPrefix == QLatin1String("group_by_")); + Q_ASSERT(isSortGroup || isGroupGroup || groupPrefix == QLatin1String("show_")); QActionGroup *rolesActionGroup = new QActionGroup(m_actionCollection); - rolesActionGroup->setExclusive(isSortGroup); + rolesActionGroup->setExclusive(isSortGroup || isGroupGroup); if (isSortGroup) { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); + } else if (isGroupGroup) { + connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotGroupTriggered); } else { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } @@ -364,9 +400,13 @@ QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QSt indexingEnabled = config.fileIndexingEnabled(); #endif - const QList rolesInfo = KFileItemModel::rolesInformation(); + QList rolesInfo = KFileItemModel::rolesInformation(); + // Unlike sorting, grouping is optional. If creating for group_by_, include a None role. + if (isGroupGroup) + rolesInfo.append(KFileItemModel::roleInformation(nullptr)); + for (const KFileItemModel::RoleInfo &info : rolesInfo) { - if (!isSortGroup && info.role == "text") { + if (!isSortGroup && !isGroupGroup && info.role == "text") { // It should not be possible to hide the "text" role continue; } @@ -384,9 +424,11 @@ QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QSt groupMenu->setActionGroup(rolesActionGroup); groupMenuGroup = new QActionGroup(groupMenu); - groupMenuGroup->setExclusive(isSortGroup); + groupMenuGroup->setExclusive(isSortGroup || isGroupGroup); if (isSortGroup) { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); + } else if (isGroupGroup) { + connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotGroupTriggered); } else { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } @@ -404,6 +446,8 @@ QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QSt if (isSortGroup) { m_sortByActions.insert(info.role, action); + } else if (isGroupGroup) { + m_groupByActions.insert(info.role, action); } else { m_visibleRoles.insert(info.role, action); } @@ -508,6 +552,8 @@ void DolphinViewActionHandler::updateViewActions() slotVisibleRolesChanged(m_currentView->visibleRoles(), QList()); slotGroupedSortingChanged(m_currentView->groupedSorting()); slotSortRoleChanged(m_currentView->sortRole()); + slotGroupRoleChanged(m_currentView->groupRole()); + slotGroupOrderChanged(m_currentView->groupOrder()); slotZoomLevelChanged(m_currentView->zoomLevel(), -1); // Updates the "show_hidden_files" action state and icon @@ -548,8 +594,17 @@ void DolphinViewActionHandler::toggleSortHiddenLast() void DolphinViewActionHandler::slotSortOrderChanged(Qt::SortOrder order) { - QAction *descending = m_actionCollection->action(QStringLiteral("descending")); - QAction *ascending = m_actionCollection->action(QStringLiteral("ascending")); + QAction *descending = m_actionCollection->action(QStringLiteral("sort_descending")); + QAction *ascending = m_actionCollection->action(QStringLiteral("sort_ascending")); + const bool sortDescending = (order == Qt::DescendingOrder); + descending->setChecked(sortDescending); + ascending->setChecked(!sortDescending); +} + +void DolphinViewActionHandler::slotGroupOrderChanged(Qt::SortOrder order) +{ + QAction *descending = m_actionCollection->action(QStringLiteral("group_descending")); + QAction *ascending = m_actionCollection->action(QStringLiteral("group_ascending")); const bool sortDescending = (order == Qt::DescendingOrder); descending->setChecked(sortDescending); ascending->setChecked(!sortDescending); @@ -674,8 +729,8 @@ void DolphinViewActionHandler::slotSortRoleChanged(const QByteArray &role) } } - QAction *descending = m_actionCollection->action(QStringLiteral("descending")); - QAction *ascending = m_actionCollection->action(QStringLiteral("ascending")); + QAction *descending = m_actionCollection->action(QStringLiteral("sort_descending")); + QAction *ascending = m_actionCollection->action(QStringLiteral("sort_ascending")); if (role == "text" || role == "type" || role == "extension" || role == "tags" || role == "comment") { descending->setText(i18nc("Sort descending", "Z-A")); @@ -697,6 +752,41 @@ void DolphinViewActionHandler::slotSortRoleChanged(const QByteArray &role) slotSortOrderChanged(m_currentView->sortOrder()); } +void DolphinViewActionHandler::slotGroupRoleChanged(const QByteArray &role) +{ + KToggleAction *action = m_groupByActions.value(role); + if (action) { + action->setChecked(true); + + if (!action->icon().isNull()) { + QAction *groupByMenu = m_actionCollection->action(QStringLiteral("group")); + groupByMenu->setIcon(action->icon()); + } + } + + QAction *descending = m_actionCollection->action(QStringLiteral("group_descending")); + QAction *ascending = m_actionCollection->action(QStringLiteral("group_ascending")); + + if (role == "text" || role == "type" || role == "extension" || role == "tags" || role == "comment") { + descending->setText(i18nc("Group descending", "Z-A")); + ascending->setText(i18nc("Group ascending", "A-Z")); + } else if (role == "size") { + descending->setText(i18nc("Group descending", "Largest First")); + ascending->setText(i18nc("Group ascending", "Smallest First")); + } else if (role == "modificationtime" || role == "creationtime" || role == "accesstime") { + descending->setText(i18nc("Group descending", "Newest First")); + ascending->setText(i18nc("Group ascending", "Oldest First")); + } else if (role == "rating") { + descending->setText(i18nc("Group descending", "Highest First")); + ascending->setText(i18nc("Group ascending", "Lowest First")); + } else { + descending->setText(i18nc("Group descending", "Descending")); + ascending->setText(i18nc("Group ascending", "Ascending")); + } + + slotGroupOrderChanged(m_currentView->groupOrder()); +} + void DolphinViewActionHandler::slotZoomLevelChanged(int current, int previous) { Q_UNUSED(previous) @@ -738,6 +828,32 @@ void DolphinViewActionHandler::slotSortTriggered(QAction *action) m_currentView->setSortRole(role); } +void DolphinViewActionHandler::slotGroupTriggered(QAction *action) +{ + // The radiobuttons of the "Group By"-menu are split between the main-menu + // and several sub-menus. Because of this they don't have a common + // action-group that assures an exclusive toggle-state between the main-menu + // actions and the sub-menu-actions. If an action gets checked, it must + // be assured that all other actions get unchecked, except the ascending/ + // descending actions + for (QAction *groupAction : std::as_const(m_sortByActions)) { + KActionMenu *actionMenu = qobject_cast(groupAction); + if (actionMenu) { + const auto actions = actionMenu->menu()->actions(); + for (QAction *subAction : actions) { + subAction->setChecked(false); + } + } else if (groupAction->actionGroup()) { + groupAction->setChecked(false); + } + } + action->setChecked(true); + + // Apply the activated sort-role to the view + const QByteArray role = action->data().toByteArray(); + m_currentView->setGroupRole(role); +} + void DolphinViewActionHandler::slotAdjustViewProperties() { Q_EMIT actionBeingHandled(); diff --git a/src/views/dolphinviewactionhandler.h b/src/views/dolphinviewactionhandler.h index f36b3d1d04..93ed10e2be 100644 --- a/src/views/dolphinviewactionhandler.h +++ b/src/views/dolphinviewactionhandler.h @@ -158,6 +158,16 @@ private Q_SLOTS: */ void slotSortRoleChanged(const QByteArray &role); + /** + * Updates the state of the 'Group Ascending/Descending' action. + */ + void slotGroupOrderChanged(Qt::SortOrder order); + + /** + * Updates the state of the 'Group by' actions. + */ + void slotGroupRoleChanged(const QByteArray &role); + /** * Updates the state of the 'Zoom In' and 'Zoom Out' actions. */ @@ -179,6 +189,11 @@ private Q_SLOTS: */ void slotVisibleRolesChanged(const QList ¤t, const QList &previous); + /** + * Changes the grouping of the current view. + */ + void slotGroupTriggered(QAction *); + /** * Switches between sorting by groups or not. */ @@ -274,6 +289,7 @@ private: DolphinView *m_currentView; QHash m_sortByActions; + QHash m_groupByActions; QHash m_visibleRoles; }; diff --git a/src/views/viewproperties.cpp b/src/views/viewproperties.cpp index cc1325fbb3..ea89dc4f5c 100644 --- a/src/views/viewproperties.cpp +++ b/src/views/viewproperties.cpp @@ -253,6 +253,32 @@ Qt::SortOrder ViewProperties::sortOrder() const return static_cast(m_node->sortOrder()); } +void ViewProperties::setGroupRole(const QByteArray &role) +{ + if (m_node->groupRole() != role) { + m_node->setGroupRole(role); + update(); + } +} + +QByteArray ViewProperties::groupRole() const +{ + return m_node->groupRole().toLatin1(); +} + +void ViewProperties::setGroupOrder(Qt::SortOrder groupOrder) +{ + if (m_node->groupOrder() != groupOrder) { + m_node->setGroupOrder(groupOrder); + update(); + } +} + +Qt::SortOrder ViewProperties::groupOrder() const +{ + return static_cast(m_node->groupOrder()); +} + void ViewProperties::setSortFoldersFirst(bool foldersFirst) { if (m_node->sortFoldersFirst() != foldersFirst) { diff --git a/src/views/viewproperties.h b/src/views/viewproperties.h index 29827c38b6..0c0452d7ae 100644 --- a/src/views/viewproperties.h +++ b/src/views/viewproperties.h @@ -59,6 +59,12 @@ public: void setSortOrder(Qt::SortOrder sortOrder); Qt::SortOrder sortOrder() const; + void setGroupRole(const QByteArray &role); + QByteArray groupRole() const; + + void setGroupOrder(Qt::SortOrder groupOrder); + Qt::SortOrder groupOrder() const; + void setSortFoldersFirst(bool foldersFirst); bool sortFoldersFirst() const;