diff options
| -rw-r--r-- | src/dolphinpart.rc | 4 | ||||
| -rw-r--r-- | src/dolphinui.rc | 4 | ||||
| -rw-r--r-- | src/dolphinuiforphones.rc | 4 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistview.cpp | 14 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemlistview.h | 1 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodel.cpp | 78 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodel.h | 3 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodelrolesupdater.cpp | 8 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.cpp | 18 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.h | 1 | ||||
| -rw-r--r-- | src/kitemviews/kitemmodelbase.cpp | 41 | ||||
| -rw-r--r-- | src/kitemviews/kitemmodelbase.h | 9 | ||||
| -rw-r--r-- | src/settings/applyviewpropsjob.cpp | 1 | ||||
| -rw-r--r-- | src/settings/dolphin_directoryviewpropertysettings.kcfg | 6 | ||||
| -rw-r--r-- | src/settings/viewmodes/generalviewsettingspage.cpp | 2 | ||||
| -rw-r--r-- | src/tests/kfileitemmodeltest.cpp | 82 | ||||
| -rw-r--r-- | src/views/dolphinview.cpp | 33 | ||||
| -rw-r--r-- | src/views/dolphinview.h | 10 | ||||
| -rw-r--r-- | src/views/dolphinviewactionhandler.cpp | 106 | ||||
| -rw-r--r-- | src/views/dolphinviewactionhandler.h | 22 | ||||
| -rw-r--r-- | src/views/viewproperties.cpp | 14 | ||||
| -rw-r--r-- | src/views/viewproperties.h | 3 |
22 files changed, 412 insertions, 52 deletions
diff --git a/src/dolphinpart.rc b/src/dolphinpart.rc index 5f57f586a..b5452c1ea 100644 --- a/src/dolphinpart.rc +++ b/src/dolphinpart.rc @@ -1,6 +1,6 @@ <?xml version="1.0"?> <!DOCTYPE gui SYSTEM "kpartgui.dtd"> -<gui name="dolphinpart" version="18" translationDomain="dolphin"> +<gui name="dolphinpart" version="19" translationDomain="dolphin"> <MenuBar> <Menu name="edit"><text>&Edit</text> <Action name="new_menu"/> @@ -23,9 +23,9 @@ </Menu> <Menu name="view"><text>&View</text> <Action name="sort" /> + <Action name="group_by" /> <Action name="additional_info" /> <Action name="show_preview" /> - <Action name="show_in_groups" /> <Action name="show_hidden_files" /> <Separator/> <Action name="view_properties" /> diff --git a/src/dolphinui.rc b/src/dolphinui.rc index 93666b6bb..a22e77e89 100644 --- a/src/dolphinui.rc +++ b/src/dolphinui.rc @@ -1,6 +1,6 @@ <?xml version="1.0"?> <!DOCTYPE gui SYSTEM "kpartgui.dtd"> -<gui name="dolphin" version="48"> +<gui name="dolphin" version="49"> <MenuBar> <Menu name="file"> <Action name="new_menu" /> @@ -41,10 +41,10 @@ <Action name="view_zoom_out"/> <Separator/> <Action name="sort" /> + <Action name="group_by" /> <Action name="view_mode" /> <Action name="additional_info" /> <Action name="show_preview" /> - <Action name="show_in_groups" /> <Action name="show_hidden_files" /> <Action name="act_as_admin" /> <Separator/> diff --git a/src/dolphinuiforphones.rc b/src/dolphinuiforphones.rc index b2a9fc6fe..06e9e3694 100644 --- a/src/dolphinuiforphones.rc +++ b/src/dolphinuiforphones.rc @@ -1,6 +1,6 @@ <?xml version="1.0"?> <!DOCTYPE gui SYSTEM "kpartgui.dtd"> -<gui name="dolphin" version="5"> +<gui name="dolphin" version="6"> <MenuBar> <Menu name="file"> <Action name="new_menu" /> @@ -41,10 +41,10 @@ <Action name="view_zoom_out"/> <Separator/> <Action name="sort" /> + <Action name="group_by" /> <Action name="view_mode" /> <Action name="additional_info" /> <Action name="show_preview" /> - <Action name="show_in_groups" /> <Action name="show_hidden_files" /> <Action name="act_as_admin" /> <Separator/> diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index 3a02d537d..585a972a6 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -354,6 +354,15 @@ void KFileItemListView::slotSortRoleChanged(const QByteArray ¤t, const QBy KStandardItemListView::slotSortRoleChanged(current, previous); } +void KFileItemListView::slotGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous) +{ + if (!visibleRoles().contains(current)) { + applyRolesToModel(); + } + + KItemListView::slotGroupRoleChanged(current, previous); +} + void KFileItemListView::triggerVisibleIndexRangeUpdate() { if (!model()) { @@ -441,6 +450,11 @@ void KFileItemListView::applyRolesToModel() // Assure that the role that is used for sorting will be determined roles.insert(fileItemModel->sortRole()); + const QByteArray rawGroupRole = fileItemModel->rawGroupRole(); + if (!rawGroupRole.isEmpty()) { + roles.insert(rawGroupRole); + } + fileItemModel->setRoles(roles); m_modelRolesUpdater->setRoles(roles); } diff --git a/src/kitemviews/kfileitemlistview.h b/src/kitemviews/kfileitemlistview.h index eae00f1f0..203daca09 100644 --- a/src/kitemviews/kfileitemlistview.h +++ b/src/kitemviews/kfileitemlistview.h @@ -93,6 +93,7 @@ protected: protected Q_SLOTS: void slotItemsRemoved(const KItemRangeList &itemRanges) override; void slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous) override; + void slotGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous) override; private Q_SLOTS: void triggerVisibleIndexRangeUpdate(); diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index ba33254c0..f4b51ce72 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -639,7 +639,7 @@ QList<QPair<int, QVariant>> KFileItemModel::groups() const QElapsedTimer timer; timer.start(); #endif - switch (typeForRole(sortRole())) { + switch (typeForRole(groupRole())) { case NameRole: m_groups = nameRoleGroups(); break; @@ -673,7 +673,7 @@ QList<QPair<int, QVariant>> KFileItemModel::groups() const m_groups = ratingRoleGroups(); break; default: - m_groups = genericStringRoleGroups(sortRole()); + m_groups = genericStringRoleGroups(groupRole()); break; } @@ -1232,6 +1232,24 @@ void KFileItemModel::onGroupedSortingChanged(bool current) m_groups.clear(); } +void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) +{ + Q_UNUSED(previous) + const RoleType roleType = typeForRole(current); + + if (!m_requestRole[roleType]) { + QSet<QByteArray> newRoles = m_roles; + newRoles << current; + setRoles(newRoles); + } + + m_groups.clear(); + + if (resortItems) { + resortAllItems(); + } +} + void KFileItemModel::onSortRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) { Q_UNUSED(previous) @@ -1927,7 +1945,7 @@ void KFileItemModel::removeItems(const KItemRangeList &itemRanges, RemoveItemsBe QList<KFileItemModel::ItemData *> KFileItemModel::createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const { - if (m_sortRole == TypeRole) { + if (m_sortRole == TypeRole || typeForRole(rawGroupRole()) == TypeRole) { // Try to resolve the MIME-types synchronously to prevent a reordering of // the items when sorting by type (per default MIME-types are resolved // asynchronously by KFileItemModelRolesUpdater). @@ -1991,6 +2009,17 @@ void KFileItemModel::prepareItemsForSorting(QList<ItemData *> &itemDataList) // DateRole). break; } + + if (typeForRole(rawGroupRole()) == TypeRole && m_sortRole != TypeRole) { + for (ItemData *itemData : std::as_const(itemDataList)) { + if (itemData->values.isEmpty()) { + const KFileItem &item = itemData->item; + if (item.isDir() || item.isMimeTypeKnown()) { + itemData->values = retrieveData(itemData->item, itemData->parent); + } + } + } + } } int KFileItemModel::expandedParentsCount(const ItemData *data) @@ -2046,7 +2075,8 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i // Trigger a resorting if necessary. Note that this can happen even if the sort // role has not changed at all because the file name can be used as a fallback. if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole)) - || (changedRoles.contains("count") && sortRole() == "size")) { // "count" is used in the "size" sort role, so this might require a resorting. + || (changedRoles.contains("count") && sortRole() == "size") // "count" is used in the "size" sort role, so this might require a resorting. + || (groupedSorting() && !rawGroupRole().isEmpty() && changedRoles.contains(groupRole()))) { for (const KItemRange &range : itemRanges) { bool needsResorting = false; @@ -2077,7 +2107,7 @@ void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList &i } } - if (groupedSorting() && changedRoles.contains(sortRole())) { + if (groupedSorting() && (changedRoles.contains(sortRole()) || (!rawGroupRole().isEmpty() && changedRoles.contains(groupRole())))) { // The position is still correct, but the groups might have changed // if the changed item is either the first or the last item in a // group. @@ -2307,6 +2337,20 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem &item, return data; } +QString KFileItemModel::groupKeyForItem(const ItemData *item, const QByteArray &role) const +{ + switch (typeForRole(role)) { + case ModificationTimeRole: + case CreationTimeRole: + case AccessTimeRole: + return QDateTime::fromSecsSinceEpoch(item->values.value(role).toLongLong()).date().toString(Qt::ISODate); + case DeletionTimeRole: + return item->values.value("deletiontime").toDateTime().date().toString(Qt::ISODate); + default: + return item->values.value(role).toString(); + } +} + bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const { int result = 0; @@ -2364,6 +2408,17 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla } } + if (groupedSorting() && !rawGroupRole().isEmpty() && groupRole() != sortRole()) { + const QByteArray gRole = groupRole(); + const QString groupValueA = groupKeyForItem(a, gRole); + const QString groupValueB = groupKeyForItem(b, gRole); + const int groupResult = stringCompare(groupValueA, groupValueB, collator); + if (groupResult != 0) { + // Groups are always in ascending order regardless of the sort order setting. + return groupResult < 0; + } + } + result = sortRoleCompare(a, b, collator); return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; @@ -2375,15 +2430,16 @@ void KFileItemModel::sort(const QList<KFileItemModel::ItemData *>::iterator &beg return lessThan(a, b, m_collator); }; - if (m_sortRole == NameRole || isRoleValueNatural(m_sortRole)) { - // Sorting by string can be expensive, in particular if natural sorting is - // enabled. Use all CPU cores to speed up the sorting process. + const QByteArray rawRole = rawGroupRole(); + const bool groupKeyIsString = !rawRole.isEmpty() && groupRole() != sortRole() + && isRoleValueNatural(typeForRole(rawRole)); + // String-based sorts benefit from parallelism; non-string sorts use one thread + // to avoid non-reentrant comparison functions (bug 312679). + const bool primaryKeyIsString = m_sortRole == NameRole || isRoleValueNatural(m_sortRole) || groupKeyIsString; + if (primaryKeyIsString) { static const int numberOfThreads = QThread::idealThreadCount(); parallelMergeSort(begin, end, lambdaLessThan, numberOfThreads); } else { - // Sorting by other roles is quite fast. Use only one thread to prevent - // problems caused by non-reentrant comparison functions, see - // https://bugs.kde.org/show_bug.cgi?id=312679 mergeSort(begin, end, lambdaLessThan); } } diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 2a6e710c3..0ac5d9261 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -293,6 +293,7 @@ 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 onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = true) override; private Q_SLOTS: /** @@ -447,6 +448,8 @@ private: */ bool lessThan(const ItemData *a, const ItemData *b, const QCollator &collator) const; + QString groupKeyForItem(const ItemData *item, const QByteArray &role) const; + /** * Sorts the items between \a begin and \a end using the comparison * function lessThan(). diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 38fa58436..33cf6341c 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -281,6 +281,14 @@ void KFileItemModelRolesUpdater::setPaused(bool paused) void KFileItemModelRolesUpdater::setRoles(const QSet<QByteArray> &roles) { if (m_roles != roles) { + const QSet<QByteArray> addedRoles = roles - m_roles; + for (const QByteArray &role : addedRoles) { + if (m_resolvableRoles.contains(role)) { + m_finishedItems.clear(); + break; + } + } + m_roles = roles; #if HAVE_BALOO diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index b87216d4b..d24c2426d 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1477,8 +1477,8 @@ void KItemListView::slotItemsChanged(const KItemRangeList &itemRanges, const QSe } } - if (m_grouped && roles.contains(m_model->sortRole())) { - // The sort-role has been changed which might result + if (m_grouped && roles.contains(m_model->groupRole())) { + // The group-role data has been changed which might result // in modified group headers updateVisibleGroupHeaders(); doLayout(NoAnimation); @@ -1543,6 +1543,16 @@ void KItemListView::slotSortRoleChanged(const QByteArray ¤t, const QByteAr } } +void KItemListView::slotGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + if (m_grouped) { + updateVisibleGroupHeaders(); + doLayout(NoAnimation); + } +} + void KItemListView::slotCurrentChanged(int current, int previous) { // In SingleSelection mode (e.g., in the Places Panel), the current item is @@ -1826,6 +1836,7 @@ void KItemListView::setModel(KItemModelBase *model) disconnect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); disconnect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); disconnect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); + disconnect(m_model, &KItemModelBase::groupRoleChanged, this, &KItemListView::slotGroupRoleChanged); m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count())); } @@ -1843,6 +1854,7 @@ void KItemListView::setModel(KItemModelBase *model) connect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); connect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); connect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); + connect(m_model, &KItemModelBase::groupRoleChanged, this, &KItemListView::slotGroupRoleChanged); const int itemCount = m_model->count(); if (itemCount > 0) { @@ -2251,7 +2263,7 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget *widget) const int groupIndex = groupIndexForItem(index); Q_ASSERT(groupIndex >= 0); groupHeader->setData(groups.at(groupIndex).second); - groupHeader->setRole(model()->sortRole()); + groupHeader->setRole(model()->groupRole()); groupHeader->setStyleOption(m_styleOption); groupHeader->setScrollOrientation(scrollOrientation()); groupHeader->setItemIndex(index); diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 8dc83d528..20c34ceea 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -441,6 +441,7 @@ protected Q_SLOTS: virtual void slotGroupedSortingChanged(bool current); virtual void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); virtual void slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous); + virtual void slotGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous); virtual void slotCurrentChanged(int current, int previous); virtual void slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous); diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index 82e491220..a4f7b0890 100644 --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -50,13 +50,41 @@ bool KItemModelBase::groupedSorting() const void KItemModelBase::setSortRole(const QByteArray &role, bool resortItems) { if (role != m_sortRole) { - const QByteArray previous = m_sortRole; + const QByteArray previousSortRole = m_sortRole; m_sortRole = role; - onSortRoleChanged(role, previous, resortItems); - Q_EMIT sortRoleChanged(role, previous); + onSortRoleChanged(role, previousSortRole, resortItems); + Q_EMIT sortRoleChanged(role, previousSortRole); + + if (m_groupRole.isEmpty()) { + onGroupRoleChanged(role, previousSortRole, false); + Q_EMIT groupRoleChanged(role, previousSortRole); + } + } +} + +void KItemModelBase::setGroupRole(const QByteArray &role) +{ + if (role != m_groupRole) { + const QByteArray previousEffective = groupRole(); + m_groupRole = role; + const QByteArray currentEffective = groupRole(); + if (currentEffective != previousEffective) { + onGroupRoleChanged(currentEffective, previousEffective); + Q_EMIT groupRoleChanged(currentEffective, previousEffective); + } } } +QByteArray KItemModelBase::groupRole() const +{ + return m_groupRole.isEmpty() ? m_sortRole : m_groupRole; +} + +QByteArray KItemModelBase::rawGroupRole() const +{ + return m_groupRole; +} + QByteArray KItemModelBase::sortRole() const { return m_sortRole; @@ -149,6 +177,13 @@ void KItemModelBase::onSortRoleChanged(const QByteArray ¤t, const QByteArr 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::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current) diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h index 42a9c54c9..68d1d5d8a 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -76,6 +76,10 @@ public: void setSortRole(const QByteArray &role, bool resortItems = true); QByteArray sortRole() const; + void setGroupRole(const QByteArray &role); + QByteArray groupRole() const; + QByteArray rawGroupRole() const; + /** * Sets the sort order to \a order. The method KItemModelBase::onSortOrderChanged() will be * called so that model-implementations can react on the sort order change. Afterwards the @@ -248,6 +252,8 @@ Q_SIGNALS: void sortRoleChanged(const QByteArray ¤t, const QByteArray &previous); void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); + void groupRoleChanged(const QByteArray ¤t, const QByteArray &previous); + protected: /** * Is invoked if the grouped sorting has been changed by KItemModelBase::setGroupedSorting(). Allows @@ -266,6 +272,8 @@ protected: */ virtual void onSortRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems = 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. @@ -279,6 +287,7 @@ protected: private: bool m_groupedSorting; QByteArray m_sortRole; + QByteArray m_groupRole; Qt::SortOrder m_sortOrder; }; diff --git a/src/settings/applyviewpropsjob.cpp b/src/settings/applyviewpropsjob.cpp index 2a2b4bfe4..aa9f37c1e 100644 --- a/src/settings/applyviewpropsjob.cpp +++ b/src/settings/applyviewpropsjob.cpp @@ -23,6 +23,7 @@ ApplyViewPropsJob::ApplyViewPropsJob(const QUrl &dir, const ViewProperties &view m_viewProps->setPreviewsShown(viewProps.previewsShown()); m_viewProps->setHiddenFilesShown(viewProps.hiddenFilesShown()); m_viewProps->setSortRole(viewProps.sortRole()); + m_viewProps->setGroupRole(viewProps.groupRole()); m_viewProps->setSortOrder(viewProps.sortOrder()); KIO::ListJob *listJob = KIO::listRecursive(dir, KIO::HideProgressInfo); diff --git a/src/settings/dolphin_directoryviewpropertysettings.kcfg b/src/settings/dolphin_directoryviewpropertysettings.kcfg index 3b23918ef..9719ba602 100644 --- a/src/settings/dolphin_directoryviewpropertysettings.kcfg +++ b/src/settings/dolphin_directoryviewpropertysettings.kcfg @@ -51,6 +51,12 @@ <default>text</default> </entry> + <entry name="GroupRole" type="String" > + <label context="@label">Group files by</label> + <whatsthis context="@info:whatsthis">This option defines which attribute items are grouped by when grouped sorting is enabled. An empty value means the grouping criterion follows the sort criterion.</whatsthis> + <default></default> + </entry> + <entry name="SortOrder" type="Int" > <label context="@label">Order in which to sort files</label> <default code="true">Qt::AscendingOrder</default> diff --git a/src/settings/viewmodes/generalviewsettingspage.cpp b/src/settings/viewmodes/generalviewsettingspage.cpp index fe1f135d6..4b155a38d 100644 --- a/src/settings/viewmodes/generalviewsettingspage.cpp +++ b/src/settings/viewmodes/generalviewsettingspage.cpp @@ -119,7 +119,7 @@ GeneralViewSettingsPage::GeneralViewSettingsPage(const QUrl &url, QWidget *paren "create_file", "show_preview", "show_hidden_files", - "show_in_groups", + "group_by", "view_properties"}; // create actions combo-box and add actions diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index eb8e3b4c5..bd4a60c09 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -84,6 +84,10 @@ private Q_SLOTS: void testGeneralParentChildRelationships(); void testNameRoleGroups(); void testNameRoleGroupsWithExpandedItems(); + void testGroupRoleFallsBackToSortRole(); + void testGroupRoleIndependentFromSortRole(); + void testGroupRoleNotResetBySortRoleChange(); + void testGroupRoleChangedSignal(); void testInconsistentModel(); void testChangeRolesForFilteredItems(); void testChangeSortRoleWhileFiltering(); @@ -2879,6 +2883,84 @@ QUrl KFileItemModelTest::subDir(QUrl parent, const QString &relativePath) return parent.resolved(QUrl(relativePath)); } +void KFileItemModelTest::testGroupRoleFallsBackToSortRole() +{ + QCOMPARE(m_model->rawGroupRole(), QByteArray()); + QCOMPARE(m_model->groupRole(), m_model->sortRole()); + + m_model->setSortRole("size"); + QCOMPARE(m_model->groupRole(), QByteArray("size")); + QCOMPARE(m_model->rawGroupRole(), QByteArray()); + + m_model->setGroupRole("text"); + QCOMPARE(m_model->groupRole(), QByteArray("text")); + QCOMPARE(m_model->rawGroupRole(), QByteArray("text")); + QCOMPARE(m_model->sortRole(), QByteArray("size")); + + m_model->setGroupRole(QByteArray()); + QCOMPARE(m_model->rawGroupRole(), QByteArray()); + QCOMPARE(m_model->groupRole(), QByteArray("size")); +} + +void KFileItemModelTest::testGroupRoleNotResetBySortRoleChange() +{ + m_model->setGroupRole("type"); + QCOMPARE(m_model->rawGroupRole(), QByteArray("type")); + + m_model->setSortRole("size"); + QCOMPARE(m_model->rawGroupRole(), QByteArray("type")); + QCOMPARE(m_model->groupRole(), QByteArray("type")); + + m_model->setSortRole("modificationtime"); + QCOMPARE(m_model->rawGroupRole(), QByteArray("type")); +} + +void KFileItemModelTest::testGroupRoleIndependentFromSortRole() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"b.txt", "c.txt", "d.txt", "e.txt"}); + + m_model->setGroupedSorting(true); + m_model->setGroupRole("text"); + m_model->setSortRole("text"); + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + QList<QPair<int, QVariant>> expectedGroups; + expectedGroups << QPair<int, QVariant>(0, QLatin1String("B")); + expectedGroups << QPair<int, QVariant>(1, QLatin1String("C")); + expectedGroups << QPair<int, QVariant>(2, QLatin1String("D")); + expectedGroups << QPair<int, QVariant>(3, QLatin1String("E")); + QCOMPARE(m_model->groups(), expectedGroups); + + m_model->setSortRole("size"); + QCOMPARE(m_model->groupRole(), QByteArray("text")); + QCOMPARE(m_model->groups(), expectedGroups); +} + +void KFileItemModelTest::testGroupRoleChangedSignal() +{ + QSignalSpy groupRoleChangedSpy(m_model, &KFileItemModel::groupRoleChanged); + QVERIFY(groupRoleChangedSpy.isValid()); + + m_model->setGroupRole("size"); + QCOMPARE(groupRoleChangedSpy.count(), 1); + QCOMPARE(groupRoleChangedSpy.at(0).at(0).toByteArray(), QByteArray("size")); + QCOMPARE(groupRoleChangedSpy.at(0).at(1).toByteArray(), QByteArray("text")); + groupRoleChangedSpy.clear(); + + m_model->setGroupRole("size"); + QCOMPARE(groupRoleChangedSpy.count(), 0); + + m_model->setGroupRole(QByteArray()); + QCOMPARE(groupRoleChangedSpy.count(), 1); + groupRoleChangedSpy.clear(); + + m_model->setSortRole("size"); + QCOMPARE(groupRoleChangedSpy.count(), 1); +} + QTEST_MAIN(KFileItemModelTest) #include "kfileitemmodeltest.moc" diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 6882df5d3..e14227198 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -549,6 +549,33 @@ QByteArray DolphinView::sortRole() const return model->sortRole(); } +void DolphinView::setGroupRole(const QByteArray &role) +{ + KItemModelBase *model = m_container->controller()->model(); + if (role == model->rawGroupRole()) { + return; + } + + ViewProperties props(viewPropertiesUrl()); + props.setGroupRole(role); + + model->setGroupRole(role); + + Q_EMIT groupRoleChanged(role); +} + +QByteArray DolphinView::groupRole() const +{ + const KItemModelBase *model = m_container->controller()->model(); + return model->groupRole(); +} + +QByteArray DolphinView::rawGroupRole() const +{ + const KItemModelBase *model = m_container->controller()->model(); + return model->rawGroupRole(); +} + void DolphinView::setSortOrder(Qt::SortOrder order) { if (sortOrder() != order) { @@ -2405,6 +2432,12 @@ void DolphinView::applyViewProperties(const ViewProperties &props) Q_EMIT sortRoleChanged(sortRole); } + const QByteArray groupRole = props.groupRole(); + if (groupRole != m_model->rawGroupRole()) { + m_model->setGroupRole(groupRole); + Q_EMIT groupRoleChanged(groupRole); + } + const Qt::SortOrder sortOrder = props.sortOrder(); if (sortOrder != m_model->sortOrder()) { m_model->setSortOrder(sortOrder); diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 35276f9ac..b0ab6ab6a 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -218,6 +218,10 @@ public: void setSortRole(const QByteArray &role); QByteArray sortRole() const; + void setGroupRole(const QByteArray &role); + QByteArray groupRole() const; + QByteArray rawGroupRole() const; + /** * Updates the view properties of the current URL to the * sort order given by \a order. @@ -578,6 +582,12 @@ Q_SIGNALS: /** Is emitted if the sorting by name, size or date has been changed. */ void sortRoleChanged(const QByteArray &role); + /** + * Is emitted when the explicit group role changes. Passes the raw role — + * an empty byte array means grouping follows the sort role (see rawGroupRole()). + */ + void groupRoleChanged(const QByteArray &role); + /** Is emitted if the sort order (ascending or descending) has been changed. */ void sortOrderChanged(Qt::SortOrder order); diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 7317ca79c..2aed32d8e 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -59,6 +59,7 @@ void DolphinViewActionHandler::setCurrentView(DolphinView *view) connect(view, &DolphinView::sortHiddenLastChanged, this, &DolphinViewActionHandler::slotSortHiddenLastChanged); connect(view, &DolphinView::visibleRolesChanged, this, &DolphinViewActionHandler::slotVisibleRolesChanged); connect(view, &DolphinView::groupedSortingChanged, this, &DolphinViewActionHandler::slotGroupedSortingChanged); + connect(view, &DolphinView::groupRoleChanged, this, &DolphinViewActionHandler::slotGroupRoleChanged); connect(view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewActionHandler::slotHiddenFilesShownChanged); connect(view, &DolphinView::sortRoleChanged, this, &DolphinViewActionHandler::slotSortRoleChanged); connect(view, &DolphinView::zoomLevelChanged, this, &DolphinViewActionHandler::slotZoomLevelChanged); @@ -327,11 +328,40 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac visibleRolesMenu->addAction(action); } - KToggleAction *showInGroups = m_actionCollection->add<KToggleAction>(QStringLiteral("show_in_groups")); - showInGroups->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); - showInGroups->setText(i18nc("@action:inmenu View", "Show in Groups")); - showInGroups->setWhatsThis(i18nc("@info:whatsthis", "This groups files and folders by their first letter.")); - connect(showInGroups, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleGroupedSorting); + KActionMenu *groupByActionMenu = m_actionCollection->add<KActionMenu>(QStringLiteral("group_by")); + groupByActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); + groupByActionMenu->setText(i18nc("@action:inmenu View", "Group By")); + groupByActionMenu->setWhatsThis(i18nc("@info:whatsthis", + "This groups files and folders by a chosen attribute. " + "\"None\" disables grouping. " + "\"Same as Sort\" groups by the active sort criterion.")); + groupByActionMenu->setPopupMode(QToolButton::InstantPopup); + + QActionGroup *groupByActionGroup = new QActionGroup(m_actionCollection); + groupByActionGroup->setExclusive(true); + connect(groupByActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotGroupByTriggered); + + KToggleAction *groupByNone = m_actionCollection->add<KToggleAction>(QStringLiteral("group_by_none")); + groupByNone->setActionGroup(groupByActionGroup); + groupByNone->setText(i18nc("@action:inmenu Group By", "None")); + groupByNone->setData(QByteArray()); + m_groupByActions.insert(QByteArray(), groupByNone); + groupByActionMenu->addAction(groupByNone); + + KToggleAction *groupBySameAsSort = m_actionCollection->add<KToggleAction>(QStringLiteral("group_by_same_as_sort")); + groupBySameAsSort->setActionGroup(groupByActionGroup); + groupBySameAsSort->setText(i18nc("@action:inmenu Group By", "Same as Sort")); + groupBySameAsSort->setData(QByteArray("same_as_sort")); + m_groupByActions.insert(QByteArray("same_as_sort"), groupBySameAsSort); + groupByActionMenu->addAction(groupBySameAsSort); + + groupByActionMenu->addSeparator(); + + QActionGroup *groupRolesGroup = createFileItemRolesActionGroup(QStringLiteral("group_")); + const auto groupRolesGroupActions = groupRolesGroup->actions(); + for (QAction *action : groupRolesGroupActions) { + groupByActionMenu->addAction(action); + } KToggleAction *showHiddenFiles = m_actionCollection->add<KToggleAction>(QStringLiteral("show_hidden_files")); showHiddenFiles->setIcon(QIcon::fromTheme(QStringLiteral("view-visible"))); @@ -370,9 +400,9 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac viewSettings->addSeparator(); viewSettings->addAction(zoomWidgetAction); viewSettings->addAction(sortByActionMenu); + viewSettings->addAction(groupByActionMenu); viewSettings->addAction(visibleRolesMenu); viewSettings->addAction(showPreview); - viewSettings->addAction(showInGroups); viewSettings->addAction(showHiddenFiles); viewSettings->addAction(adjustViewProps); viewSettings->setPopupMode(QToolButton::ToolButtonPopupMode::MenuButtonPopup); @@ -384,14 +414,18 @@ 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_")); + Q_ASSERT(isSortGroup || isGroupGroup || groupPrefix == QLatin1String("show_")); QActionGroup *rolesActionGroup = new QActionGroup(m_actionCollection); - rolesActionGroup->setExclusive(isSortGroup); + rolesActionGroup->setExclusive(isSortGroup || isGroupGroup); KActionCategory *category; if (isSortGroup) { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); category = new KActionCategory(i18nc("@item:intable, Heading of a list of fields", "Sort by Field"), m_actionCollection); + } else if (isGroupGroup) { + connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotGroupByTriggered); + category = new KActionCategory(i18nc("@item:intable, Heading of a list of fields", "Group by Field"), m_actionCollection); } else { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); category = new KActionCategory(i18nc("@item:intable, Heading of a list of fields", "Show Field"), m_actionCollection); @@ -409,7 +443,7 @@ QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QSt const QList<KFileItemModel::RoleInfo> rolesInfo = KFileItemModel::rolesInformation(); 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; } @@ -427,9 +461,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::slotGroupByTriggered); } else { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } @@ -447,6 +483,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); } @@ -555,7 +593,7 @@ void DolphinViewActionHandler::updateViewActions() slotSortFoldersFirstChanged(m_currentView->sortFoldersFirst()); slotSortHiddenLastChanged(m_currentView->sortHiddenLast()); slotVisibleRolesChanged(m_currentView->visibleRoles(), QList<QByteArray>()); - slotGroupedSortingChanged(m_currentView->groupedSorting()); + updateGroupByActions(); slotSortRoleChanged(m_currentView->sortRole()); slotZoomLevelChanged(m_currentView->zoomLevel(), -1); @@ -652,15 +690,51 @@ void DolphinViewActionHandler::slotVisibleRolesChanged(const QList<QByteArray> & } } -void DolphinViewActionHandler::toggleGroupedSorting(bool grouped) +void DolphinViewActionHandler::slotGroupByTriggered(QAction *action) +{ + const QByteArray data = action->data().toByteArray(); + if (data.isEmpty()) { + m_currentView->setGroupedSorting(false); + } else if (data == "same_as_sort") { + m_currentView->setGroupedSorting(true); + m_currentView->setGroupRole(QByteArray()); + } else { + m_currentView->setGroupedSorting(true); + m_currentView->setGroupRole(data); + } +} + +void DolphinViewActionHandler::slotGroupedSortingChanged(bool /*groupedSorting*/) { - m_currentView->setGroupedSorting(grouped); + updateGroupByActions(); } -void DolphinViewActionHandler::slotGroupedSortingChanged(bool groupedSorting) +void DolphinViewActionHandler::slotGroupRoleChanged(const QByteArray & /*role*/) { - QAction *showInGroupsAction = m_actionCollection->action(QStringLiteral("show_in_groups")); - showInGroupsAction->setChecked(groupedSorting); + updateGroupByActions(); +} + +void DolphinViewActionHandler::updateGroupByActions() +{ + if (!m_currentView) { + return; + } + + const bool grouped = m_currentView->groupedSorting(); + const QByteArray rawRole = m_currentView->rawGroupRole(); + + QByteArray checkedKey; + if (!grouped) { + checkedKey = QByteArray(); + } else if (rawRole.isEmpty()) { + checkedKey = "same_as_sort"; + } else { + checkedKey = rawRole; + } + + for (const auto &[key, action] : m_groupByActions.asKeyValueRange()) { + action->setChecked(key == checkedKey); + } } void DolphinViewActionHandler::toggleShowHiddenFiles(bool show) diff --git a/src/views/dolphinviewactionhandler.h b/src/views/dolphinviewactionhandler.h index 20d62cf64..b15a73933 100644 --- a/src/views/dolphinviewactionhandler.h +++ b/src/views/dolphinviewactionhandler.h @@ -186,15 +186,9 @@ private Q_SLOTS: */ void slotVisibleRolesChanged(const QList<QByteArray> ¤t, const QList<QByteArray> &previous); - /** - * Switches between sorting by groups or not. - */ - void toggleGroupedSorting(bool); - - /** - * Updates the state of the 'Categorized sorting' menu action. - */ - void slotGroupedSortingChanged(bool sortCategorized); + void slotGroupByTriggered(QAction *action); + void slotGroupedSortingChanged(bool groupedSorting); + void slotGroupRoleChanged(const QByteArray &role); /** * Switches between showing and hiding of hidden marked files @@ -252,10 +246,11 @@ private: /** * Creates an action-group out of all roles from KFileItemModel. * Dependent on the group-prefix either a radiobutton-group is - * created for sorting (prefix is "sort_by_") or a checkbox-group + * created for sorting (prefix is "sort_by_"), a radiobutton-group + * is created for group-by (prefix is "group_"), or a checkbox-group * is created for additional information (prefix is "show_"). - * The changes of actions are reported to slotSortTriggered() or - * toggleAdditionalInfo(). + * The changes of actions are reported to slotSortTriggered(), + * slotGroupByTriggered(), or toggleAdditionalInfo() respectively. */ QActionGroup *createFileItemRolesActionGroup(const QString &groupPrefix); @@ -277,11 +272,14 @@ private: */ KToggleAction *detailsModeAction(); + void updateGroupByActions(); + KActionCollection *m_actionCollection; DolphinView *m_currentView; QHash<QByteArray, KToggleAction *> m_sortByActions; QHash<QByteArray, KToggleAction *> m_visibleRoles; + QHash<QByteArray, KToggleAction *> m_groupByActions; }; #endif /* DOLPHINVIEWACTIONHANDLER_H */ diff --git a/src/views/viewproperties.cpp b/src/views/viewproperties.cpp index 9cd834991..f392f45c5 100644 --- a/src/views/viewproperties.cpp +++ b/src/views/viewproperties.cpp @@ -353,6 +353,19 @@ QByteArray ViewProperties::sortRole() const return m_node->sortRole().toLatin1(); } +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::setSortOrder(Qt::SortOrder sortOrder) { if (m_node->sortOrder() != sortOrder) { @@ -510,6 +523,7 @@ void ViewProperties::setDirProperties(const ViewProperties &props) setHiddenFilesShown(props.hiddenFilesShown()); setGroupedSorting(props.groupedSorting()); setSortRole(props.sortRole()); + setGroupRole(props.groupRole()); setSortOrder(props.sortOrder()); setSortFoldersFirst(props.sortFoldersFirst()); setSortHiddenLast(props.sortHiddenLast()); diff --git a/src/views/viewproperties.h b/src/views/viewproperties.h index 622a1c87e..801d33845 100644 --- a/src/views/viewproperties.h +++ b/src/views/viewproperties.h @@ -60,6 +60,9 @@ public: void setSortRole(const QByteArray &role); QByteArray sortRole() const; + void setGroupRole(const QByteArray &role); + QByteArray groupRole() const; + void setSortOrder(Qt::SortOrder sortOrder); Qt::SortOrder sortOrder() const; |
