]> cloud.milkyroute.net Git - dolphin.git/commitdiff
"Group by" exists, group sorting rule is separate from sorting rule. Very WIP and...
authorZakhar Afonin <aza22u419@student.bmstu.ru>
Sun, 9 Jun 2024 19:40:26 +0000 (22:40 +0300)
committerZakhar Afonin <aza22u419@student.bmstu.ru>
Sun, 9 Jun 2024 19:40:26 +0000 (22:40 +0300)
17 files changed:
src/dolphincontextmenu.cpp
src/dolphinmainwindow.cpp
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/kitemviews/kitemmodelbase.cpp
src/kitemviews/kitemmodelbase.h
src/settings/applyviewpropsjob.cpp
src/settings/contextmenu/contextmenusettingspage.cpp
src/settings/dolphin_contextmenusettings.kcfg
src/settings/dolphin_directoryviewpropertysettings.kcfg
src/settings/dolphinsettingsdialog.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h
src/views/dolphinviewactionhandler.cpp
src/views/dolphinviewactionhandler.h
src/views/viewproperties.cpp
src/views/viewproperties.h

index b4aade6b9a81eb361f4a7a38d15e2f09cb892641..bc00af7cc5009d7022c836e93e722d256c2d5ec3 100644 (file)
@@ -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")));
     }
index 3b3d6b8d4f198b1a036e10c521ecee7ffa5ddeec..cb94e86572962edc05b8df2720444d3f3ef58b04 100644 (file)
@@ -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);
index c694da9f256f041f91d1ed0712032474db7266c8..ad0fb31a7f703dfae86ddf0b369d2e15f2b2ea79 100644 (file)
@@ -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<QPair<int, QVariant>> 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<QPair<int, QVariant>> 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<QByteArray, RoleInfo> 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::RoleInfo> KFileItemModel::rolesInformation()
 {
     static QList<RoleInfo> rolesInfo;
@@ -894,24 +930,7 @@ QList<KFileItemModel::RoleInfo> 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 &current, 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 &current, const QByteArray &previous, bool resortItems)
+{
+    Q_UNUSED(previous)
+    m_groupRole = typeForRole(current);
+
+    if (!m_requestRole[m_sortRole]) {
+        QSet<QByteArray> 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<QPair<int, QVariant>> 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<QPair<int, QVariant>> 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<ItemData *> &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 },
index ce58f89acc96ff2334dd0cb7f6c99f9601cb3571..6cbfab6037aa25d2e25efa05423e1f155da6de14 100644 (file)
@@ -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 &current, 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 &current, 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<QByteArray> m_roles;
 
index 9fdecafb864425cc887ca47b1e164b1a04cdb769..b846318ee799e8327c62b271d3b87ab2cc816ffc 100644 (file)
@@ -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 &current, 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 &current, 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
index 42a9c54c94ca915474a305bffee18c309cd59914..c336a0726585249281cbde2a4b227041ba949814 100644 (file)
@@ -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 &current, const QByteArray &previous);
     void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
+    void groupRoleChanged(const QByteArray &current, 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 &current, 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
index 2a2b4bfe4f5ef0c30a7fe6e4a3f374b1af65c027..21c30af022b0b3803e0ad395689ed9be988f0f3f 100644 (file)
@@ -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);
index c401c2d6b6b1a4947bf1316eb7794475acfa9f85..64b78d2bdc9fb37d3e20bbeeee18105968fef0f6 100644 (file)
@@ -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") {
index 6e45d9bcdf80f4c9321992803f0ac5ebc5b1ae0a..ac7e8f9ae3a5e2a2ad8052a0bc544cea8605169d 100644 (file)
             <label>Show 'Sort By' in context menu.</label>
             <default>true</default>
         </entry>
+        <entry name="ShowGroupBy" type="Bool">
+            <label>Show 'Group By' in context menu.</label>
+            <default>true</default>
+        </entry>
         <entry name="ShowViewMode" type="Bool">
             <label>Show 'View Mode' in context menu.</label>
             <default>true</default>
index f4d288369605528ab559a897119be875ab8f720c..620983b19fcd3de92276842f70e26ae5a088f340 100644 (file)
@@ -36,7 +36,7 @@
         <entry name="GroupedSorting" type="Bool" >
             <label context="@label">Grouped Sorting</label>
             <whatsthis context="@info:whatsthis">When this option is enabled, the sorted items are categorized into groups.</whatsthis>
-            <default>false</default>
+            <default>true</default>
         </entry>
 
         <entry name="SortRole" type="String" >
             <max code="true">Qt::DescendingOrder</max>
         </entry>
 
+        <entry name="GroupRole" type="String" >
+            <label context="@label">Group files by</label>
+            <whatsthis context="@info:whatsthis">This option defines which attribute (text, size, date, etc.) grouping is performed on.</whatsthis>
+            <default></default>
+        </entry>
+
+        <entry name="GroupOrder" type="Int" >
+            <label context="@label">Order in which to group files</label>
+            <default code="true">Qt::AscendingOrder</default>
+            <min code="true">Qt::AscendingOrder</min>
+            <max code="true">Qt::DescendingOrder</max>
+        </entry>
+
         <entry name="SortFoldersFirst" type="Bool" >
             <label context="@label">Show folders first when sorting files and folders</label>
             <default>true</default>
@@ -84,5 +97,3 @@
         </entry>
     </group>
 </kcfg>
-
-
index c564dd196c0b692232c8fc8bc8a052e2f5f2f611..0fd4328057bd21b4ba566eb82d5f29b032d2af8b 100644 (file)
@@ -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"),
index d42d9cfcd81d7fe9f909548f16766e38c26259ff..9dedb9661cd50cb569bce2fb427f044d1b4735be 100644 (file)
@@ -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);
index b55e2ee9becc5b2463f28c59acd7456f79509481..f3c0189bf9ee19adba94956dc354103e9ad8be4b 100644 (file)
@@ -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.
index 2934e800582e28dd48d7da835a8361d4cf56e93a..7a3c758a57890e8f6a433f38134829163f1ffc07 100644 (file)
@@ -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<KToggleAction>(QStringLiteral("ascending"));
-    ascendingAction->setActionGroup(group);
-    connect(ascendingAction, &QAction::triggered, this, [this] {
+    KToggleAction *sortAscendingAction = m_actionCollection->add<KToggleAction>(QStringLiteral("sort_ascending"));
+    sortAscendingAction->setActionGroup(group);
+    connect(sortAscendingAction, &QAction::triggered, this, [this] {
         m_currentView->setSortOrder(Qt::AscendingOrder);
     });
 
-    KToggleAction *descendingAction = m_actionCollection->add<KToggleAction>(QStringLiteral("descending"));
-    descendingAction->setActionGroup(group);
-    connect(descendingAction, &QAction::triggered, this, [this] {
+    KToggleAction *sortDescendingAction = m_actionCollection->add<KToggleAction>(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<KActionMenu>(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<KToggleAction>(QStringLiteral("group_ascending"));
+    groupAscendingAction->setActionGroup(group);
+    connect(groupAscendingAction, &QAction::triggered, this, [this] {
+        m_currentView->setGroupOrder(Qt::AscendingOrder);
+    });
+
+    KToggleAction *groupDescendingAction = m_actionCollection->add<KToggleAction>(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<KFileItemModel::RoleInfo> rolesInfo = KFileItemModel::rolesInformation();
+    QList<KFileItemModel::RoleInfo> 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<QByteArray>());
     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<KActionMenu *>(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();
index f36b3d1d04277a05463b8e559e373d32fd40662f..93ed10e2beec2dd134495715fa9b5a931ca9a769 100644 (file)
@@ -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<QByteArray> &current, const QList<QByteArray> &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<QByteArray, KToggleAction *> m_sortByActions;
+    QHash<QByteArray, KToggleAction *> m_groupByActions;
     QHash<QByteArray, KToggleAction *> m_visibleRoles;
 };
 
index cc1325fbb3ea1561c12afbd48afca85861d810ad..ea89dc4f5c5f42298bea94bb2e44447b21c83587 100644 (file)
@@ -253,6 +253,32 @@ Qt::SortOrder ViewProperties::sortOrder() const
     return static_cast<Qt::SortOrder>(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<Qt::SortOrder>(m_node->groupOrder());
+}
+
 void ViewProperties::setSortFoldersFirst(bool foldersFirst)
 {
     if (m_node->sortFoldersFirst() != foldersFirst) {
index 29827c38b61c4175e37e366bd07b3c092740dec8..0c0452d7aea0fe11be16afda688151322d0a08ef 100644 (file)
@@ -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;