From cdccbf471a88a34d7673712f6f6bead559dcc5be Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sun, 9 Jun 2024 22:40:26 +0300 Subject: [PATCH 01/21] "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; From b5eea6e5c5ee83a94ef9f8910c839b16fce168d6 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Thu, 13 Jun 2024 09:33:52 +0300 Subject: [PATCH 02/21] Reverted resortAllItems() in favor of a group comparator for lessThan. Minor bug fixes --- src/dolphinpart.rc | 1 + src/dolphinui.rc | 1 + src/kitemviews/kfileitemmodel.cpp | 799 +++++++++++++++---------- src/kitemviews/kfileitemmodel.h | 14 + src/views/dolphinviewactionhandler.cpp | 23 +- 5 files changed, 521 insertions(+), 317 deletions(-) diff --git a/src/dolphinpart.rc b/src/dolphinpart.rc index d13f4aaed7..13f0f2172b 100644 --- a/src/dolphinpart.rc +++ b/src/dolphinpart.rc @@ -23,6 +23,7 @@ &View + diff --git a/src/dolphinui.rc b/src/dolphinui.rc index 2f7ea857d1..d884fe3004 100644 --- a/src/dolphinui.rc +++ b/src/dolphinui.rc @@ -43,6 +43,7 @@ + diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index ad0fb31a7f..34a113eded 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1049,44 +1049,8 @@ void KFileItemModel::resortAllItems() m_items.clear(); m_items.reserve(itemCount); - 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()); - } - + // Resort the items + sort(m_itemData.begin(), m_itemData.end()); for (int i = 0; i < itemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } @@ -1121,12 +1085,15 @@ void KFileItemModel::resortAllItems() Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes); } else if (groupedSorting()) { - if (m_groups != oldGroups) { + // 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) { Q_EMIT groupsChanged(); } } -#ifdef KFILEITEMMODEL_DEBUGf +#ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed(); #endif } @@ -1641,7 +1608,7 @@ void KFileItemModel::insertItems(QList &newItems) } // N - //resortAllItems(); + // 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(); @@ -2102,31 +2069,34 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla } } - // Show hidden files and folders last - if (m_sortHiddenLast) { - const bool isHiddenA = a->item.isHidden(); - const bool isHiddenB = b->item.isHidden(); - if (isHiddenA && !isHiddenB) { - return false; - } else if (!isHiddenA && isHiddenB) { - return true; + result = groupRoleCompare(a, b, collator); + if (result == 0) { + // Show hidden files and folders last + if (m_sortHiddenLast) { + const bool isHiddenA = a->item.isHidden(); + const bool isHiddenB = b->item.isHidden(); + if (isHiddenA && !isHiddenB) { + return false; + } else if (!isHiddenA && isHiddenB) { + return true; + } } - } - - if (m_sortDirsFirst - || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { - const bool isDirA = a->item.isDir(); - const bool isDirB = b->item.isDir(); - if (isDirA && !isDirB) { - return true; - } else if (!isDirA && isDirB) { - return false; + if (m_sortDirsFirst + || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { + const bool isDirA = a->item.isDir(); + const bool isDirB = b->item.isDir(); + if (isDirA && !isDirB) { + return true; + } else if (!isDirA && isDirB) { + return false; + } } + result = sortRoleCompare(a, b, collator); + result = (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; + } else { + result = (groupOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; } - - result = sortRoleCompare(a, b, collator); - - return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; + return result; } void KFileItemModel::sort(const QList::iterator &begin, const QList::iterator &end) const @@ -2317,6 +2287,141 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const return QString::compare(itemA.url().url(), itemB.url().url(), Qt::CaseSensitive); } +int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const +{ + // Unlike sortRoleCompare, this function can and often will return 0. + const KFileItem &itemA = a->item; + const KFileItem &itemB = b->item; + + int result = 0; + + switch (m_groupRole) { + case NoRole: + break; + case NameRole: { + QChar groupA = getNameRoleGroup(a, false).toChar(); + QChar groupB = getNameRoleGroup(b, false).toChar(); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + case SizeRole: { + int groupA = getSizeRoleGroup(a, false).toInt(); + int groupB = getSizeRoleGroup(b, false).toInt(); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + case ModificationTimeRole: { + int groupA = getTimeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + a, + false) + .toInt(); + int groupB = getTimeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + b, + false) + .toInt(); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + case CreationTimeRole: { + int groupA = getTimeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + a, + false) + .toInt(); + int groupB = getTimeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + b, + false) + .toInt(); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + case AccessTimeRole: { + int groupA = getTimeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + a, + false) + .toInt(); + int groupB = getTimeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + b, + false) + .toInt(); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + case DeletionTimeRole: { + int groupA = getTimeRoleGroup( + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + a, + false) + .toInt(); + int groupB = getTimeRoleGroup( + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + b, + false) + .toInt(); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + // case PermissionsRole: + // case RatingRole: + default: { + QString groupA = getGenericStringRoleGroup(groupRole(), a); + QString groupB = getGenericStringRoleGroup(groupRole(), b); + if (groupA < groupB) { + result = -1; + } else if (groupA > groupB) { + result = 1; + } + break; + } + } + return result; +} + int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCollator &collator) const { QMutexLocker collatorLock(s_collatorMutex()); @@ -2336,6 +2441,320 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol return QString::compare(a, b, Qt::CaseSensitive); } +QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asString) const +{ + const KFileItem item = itemData->item; + const QString name = item.text(); + QVariant newGroupValue; + // Use the first character of the name as group indication + QChar newFirstChar = name.at(0).toUpper(); + if (newFirstChar == QLatin1Char('~') && name.length() > 1) { + newFirstChar = name.at(1).toUpper(); + } + + if (newFirstChar.isLetter()) { + if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) { + // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group. + + // Try to find a matching group in the range 'A' to 'Z'. + static std::vector lettersAtoZ; + lettersAtoZ.reserve('Z' - 'A' + 1); + if (lettersAtoZ.empty()) { + for (char c = 'A'; c <= 'Z'; ++c) { + lettersAtoZ.push_back(QLatin1Char(c)); + } + } + + auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool { + return m_collator.compare(c1, c2) < 0; + }; + + std::vector::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan); + if (it != lettersAtoZ.end()) { + if (localeAwareLessThan(newFirstChar, *it)) { + // newFirstChar belongs to the group preceding *it. + // Example: for an umlaut 'A' in the German locale, *it would be 'B' now. + --it; + } + newGroupValue = *it; + } + + } else { + // Symbols from non Latin-based scripts + newGroupValue = newFirstChar; + } + } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) { + // Apply group '0 - 9' for any name that starts with a digit + if (asString) { + newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9"); + } else { + newGroupValue = QChar('0'); + } + } else { + if (asString) { + newGroupValue = i18nc("@title:group", "Others"); + } else { + newGroupValue = QChar('.'); + } + } + return newGroupValue; +} + +QVariant KFileItemModel::getSizeRoleGroup(const ItemData *itemData, bool asString) const +{ + const KFileItem item = itemData->item; + + KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U; + int newGroupValue = -1; // None + if (!item.isNull() && item.isDir()) { + if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { + newGroupValue = 0; // Folders + } else { + fileSize = itemData->values.value("size").toULongLong(); + } + } + + if (newGroupValue < 0) { + if (fileSize < 5 * 1024 * 1024) { // < 5 MB + newGroupValue = 1; // Small + } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB + newGroupValue = 2; // Medium + } else { + newGroupValue = 3; // Big + } + } + + if (asString) { + char const *groupNames[] = {"Folders", "Small", "Medium", "Big"}; + return i18nc("@title:group Size", groupNames[newGroupValue]); + } else { + return newGroupValue; + } +} + +QVariant KFileItemModel::getTimeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool asString) const +{ + const QDate currentDate = QDate::currentDate(); + const QDateTime fileTime = fileTimeCb(itemData); + const QDate fileDate = fileTime.date(); + const int daysDistance = fileDate.daysTo(currentDate); + + int intGroupValue; + QString strGroupValue; + + if (!asString) { + // Simplified grouping algorithm, preserving dates + // but not taking "pretty printing" into account + if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { + if (daysDistance < 7) { + intGroupValue = daysDistance; // Today, Yesterday and week days + } else if (daysDistance < 14) { + intGroupValue = 10; // One Week Ago + } else if (daysDistance < 21) { + intGroupValue = 20; // Two Weeks Ago + } else if (daysDistance < 28) { + intGroupValue = 30; // Three Weeks Ago + } else { + intGroupValue = 40; // Earlier This Month + } + } else { + const QDate lastMonthDate = currentDate.addMonths(-1); + if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) { + if (daysDistance < 7) { + intGroupValue = daysDistance; // Today, Yesterday and week days (Month, Year) + } else if (daysDistance < 14) { + intGroupValue = 9; // One Week Ago (Month, Year) + } else if (daysDistance < 21) { + intGroupValue = 19; // Two Weeks Ago (Month, Year) + } else if (daysDistance < 28) { + intGroupValue = 29; // Three Weeks Ago (Month, Year) + } else { + intGroupValue = 39; // Earlier on Month, Year + } + } else { + // The trick will fail for dates past April, 178956967 or before 1 AD. + intGroupValue = 2147483647 - (fileDate.year() * 12 + fileDate.month() - 1); // Month, Year; newer < older + } + } + return QVariant(intGroupValue); + } else { + if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { + switch (daysDistance / 7) { + case 0: + switch (daysDistance) { + case 0: + strGroupValue = i18nc("@title:group Date", "Today"); + break; + case 1: + strGroupValue = i18nc("@title:group Date", "Yesterday"); + break; + default: + strGroupValue = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd")); + strGroupValue = i18nc( + "Can be used to script translation of \"dddd\"" + "with context @title:group Date", + "%1", + strGroupValue); + } + break; + case 1: + strGroupValue = i18nc("@title:group Date", "One Week Ago"); + break; + case 2: + strGroupValue = i18nc("@title:group Date", "Two Weeks Ago"); + break; + case 3: + strGroupValue = i18nc("@title:group Date", "Three Weeks Ago"); + break; + case 4: + case 5: + strGroupValue = i18nc("@title:group Date", "Earlier this Month"); + break; + default: + Q_ASSERT(false); + } + } else { + const QDate lastMonthDate = currentDate.addMonths(-1); + if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) { + if (daysDistance == 1) { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a " + "part of the text that should not be formatted as a date", + "'Yesterday' (MMMM, yyyy)"); + const QString translatedFormat = format.toString(); + if (translatedFormat.count(QLatin1Char('\'')) == 2) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date", + "%1", + strGroupValue); + } else { + qCWarning(DolphinDebug).nospace() + << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org"; + const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } else if (daysDistance <= 7) { + strGroupValue = + fileTime.toString(i18nc("@title:group Date: " + "The week day name: dddd, MMMM is full month name " + "in current locale, and yyyy is full year number.", + "dddd (MMMM, yyyy)")); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"dddd (MMMM, yyyy)\" with context @title:group Date", + "%1", + strGroupValue); + } else if (daysDistance <= 7 * 2) { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a " + "part of the text that should not be formatted as a date", + "'One Week Ago' (MMMM, yyyy)"); + const QString translatedFormat = format.toString(); + if (translatedFormat.count(QLatin1Char('\'')) == 2) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date", + "%1", + strGroupValue); + } else { + qCWarning(DolphinDebug).nospace() + << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org"; + const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } else if (daysDistance <= 7 * 3) { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a " + "part of the text that should not be formatted as a date", + "'Two Weeks Ago' (MMMM, yyyy)"); + const QString translatedFormat = format.toString(); + if (translatedFormat.count(QLatin1Char('\'')) == 2) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", + "%1", + strGroupValue); + } else { + qCWarning(DolphinDebug).nospace() + << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org"; + const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } else if (daysDistance <= 7 * 4) { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a " + "part of the text that should not be formatted as a date", + "'Three Weeks Ago' (MMMM, yyyy)"); + const QString translatedFormat = format.toString(); + if (translatedFormat.count(QLatin1Char('\'')) == 2) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", + "%1", + strGroupValue); + } else { + qCWarning(DolphinDebug).nospace() + << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org"; + const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } else { + const KLocalizedString format = ki18nc( + "@title:group Date: " + "MMMM is full month name in current locale, and yyyy is " + "full year number. You must keep the ' don't use any fancy \" or « or similar. The ' is not shown to the user, it's there to mark a " + "part of the text that should not be formatted as a date", + "'Earlier on' MMMM, yyyy"); + const QString translatedFormat = format.toString(); + if (translatedFormat.count(QLatin1Char('\'')) == 2) { + strGroupValue = fileTime.toString(translatedFormat); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"'Earlier on' MMMM, yyyy\" with context @title:group Date", + "%1", + strGroupValue); + } else { + qCWarning(DolphinDebug).nospace() + << "A wrong translation was found: " << translatedFormat << ". Please file a bug report at bugs.kde.org"; + const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); + strGroupValue = fileTime.toString(untranslatedFormat); + } + } + } else { + strGroupValue = + fileTime.toString(i18nc("@title:group " + "The month and year: MMMM is full month name in current locale, " + "and yyyy is full year number", + "MMMM, yyyy")); + strGroupValue = i18nc( + "Can be used to script translation of " + "\"MMMM, yyyy\" with context @title:group Date", + "%1", + strGroupValue); + } + } + return QVariant(strGroupValue); + } +} + +QString KFileItemModel::getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const +{ + return itemData->values.value(role).toString(); +} + QList> KFileItemModel::nameRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); @@ -2350,61 +2769,14 @@ QList> KFileItemModel::nameRoleGroups() const continue; } - const QString name = m_itemData.at(i)->item.text(); + QString newGroupValue = getNameRoleGroup(m_itemData.at(i)).toString(); - // Use the first character of the name as group indication - QChar newFirstChar = name.at(0).toUpper(); - if (newFirstChar == QLatin1Char('~') && name.length() > 1) { - newFirstChar = name.at(1).toUpper(); + if (newGroupValue != groupValue) { + groupValue = newGroupValue; + groups.append(QPair(i, newGroupValue)); } - if (firstChar != newFirstChar) { - QString newGroupValue; - if (newFirstChar.isLetter()) { - if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) { - // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group. - - // Try to find a matching group in the range 'A' to 'Z'. - static std::vector lettersAtoZ; - lettersAtoZ.reserve('Z' - 'A' + 1); - if (lettersAtoZ.empty()) { - for (char c = 'A'; c <= 'Z'; ++c) { - lettersAtoZ.push_back(QLatin1Char(c)); - } - } - - auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool { - return m_collator.compare(c1, c2) < 0; - }; - - std::vector::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan); - if (it != lettersAtoZ.end()) { - if (localeAwareLessThan(newFirstChar, *it)) { - // newFirstChar belongs to the group preceding *it. - // Example: for an umlaut 'A' in the German locale, *it would be 'B' now. - --it; - } - newGroupValue = *it; - } - - } else { - // Symbols from non Latin-based scripts - newGroupValue = newFirstChar; - } - } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) { - // Apply group '0 - 9' for any name that starts with a digit - newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9"); - } else { - newGroupValue = i18nc("@title:group", "Others"); - } - - if (newGroupValue != groupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); - } - - firstChar = newFirstChar; - } + // firstChar = newFirstChar; } return groups; } @@ -2422,26 +2794,7 @@ QList> KFileItemModel::sizeRoleGroups() const continue; } - const KFileItem &item = m_itemData.at(i)->item; - KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U; - QString newGroupValue; - if (!item.isNull() && item.isDir()) { - if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { - newGroupValue = i18nc("@title:group Size", "Folders"); - } else { - fileSize = m_itemData.at(i)->values.value("size").toULongLong(); - } - } - - if (newGroupValue.isEmpty()) { - if (fileSize < 5 * 1024 * 1024) { // < 5 MB - newGroupValue = i18nc("@title:group Size", "Small"); - } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB - newGroupValue = i18nc("@title:group Size", "Medium"); - } else { - newGroupValue = i18nc("@title:group Size", "Big"); - } - } + QString newGroupValue = getSizeRoleGroup(m_itemData.at(i)).toString(); if (newGroupValue != groupValue) { groupValue = newGroupValue; @@ -2476,177 +2829,7 @@ QList> KFileItemModel::timeRoleGroups(const std::function> KFileItemModel::ratingRoleGroups() const if (isChildItem(i)) { continue; } + const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt(); if (newGroupValue != groupValue) { groupValue = newGroupValue; @@ -2764,7 +2948,8 @@ QList> KFileItemModel::genericStringRoleGroups(const QByteA if (isChildItem(i)) { continue; } - const QString newGroupValue = m_itemData.at(i)->values.value(role).toString(); + + const QString newGroupValue = getGenericStringRoleGroup(role, m_itemData.at(i)); if (newGroupValue != groupValue || isFirstGroupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 6cbfab6037..725ba71bdb 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -454,8 +454,22 @@ private: */ int sortRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const; + /** + * Helper method for lessThan() and expandedParentsCountCompare(): Compares + * the passed item-data using m_groupRole as criteria. Both items must + * have the same parent item, otherwise the comparison will be wrong. + */ + int groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const; + int stringCompare(const QString &a, const QString &b, const QCollator &collator) const; + QVariant getNameRoleGroup(const ItemData *itemData, bool asString = true) const; + QVariant getSizeRoleGroup(const ItemData *itemData, bool asString = true) const; + QVariant getTimeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool asString = true) const; + QVariant getPermissionRoleGroup(const ItemData *itemData, bool asString = true) const; + QVariant getRatingRoleGroup(const ItemData *itemData, bool asString = true) const; + QString getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const; + QList> nameRoleGroups() const; QList> sizeRoleGroups() const; QList> timeRoleGroups(const std::function &fileTimeCb) const; diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 7a3c758a57..7454ed0b94 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -278,17 +278,17 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac sortByActionMenu->addSeparator(); - QActionGroup *group = new QActionGroup(sortByActionMenu); - group->setExclusive(true); + QActionGroup *groupForSort = new QActionGroup(sortByActionMenu); + groupForSort->setExclusive(true); KToggleAction *sortAscendingAction = m_actionCollection->add(QStringLiteral("sort_ascending")); - sortAscendingAction->setActionGroup(group); + sortAscendingAction->setActionGroup(groupForSort); connect(sortAscendingAction, &QAction::triggered, this, [this] { m_currentView->setSortOrder(Qt::AscendingOrder); }); KToggleAction *sortDescendingAction = m_actionCollection->add(QStringLiteral("sort_descending")); - sortDescendingAction->setActionGroup(group); + sortDescendingAction->setActionGroup(groupForSort); connect(sortDescendingAction, &QAction::triggered, this, [this] { m_currentView->setSortOrder(Qt::DescendingOrder); }); @@ -314,14 +314,17 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac groupByActionMenu->addSeparator(); + QActionGroup *groupForGroup = new QActionGroup(groupByActionMenu); + groupForGroup->setExclusive(true); + KToggleAction *groupAscendingAction = m_actionCollection->add(QStringLiteral("group_ascending")); - groupAscendingAction->setActionGroup(group); + groupAscendingAction->setActionGroup(groupForGroup); connect(groupAscendingAction, &QAction::triggered, this, [this] { m_currentView->setGroupOrder(Qt::AscendingOrder); }); KToggleAction *groupDescendingAction = m_actionCollection->add(QStringLiteral("group_descending")); - groupDescendingAction->setActionGroup(group); + groupDescendingAction->setActionGroup(groupForGroup); connect(groupDescendingAction, &QAction::triggered, this, [this] { m_currentView->setGroupOrder(Qt::DescendingOrder); }); @@ -605,9 +608,9 @@ 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); + const bool groupDescending = (order == Qt::DescendingOrder); + descending->setChecked(groupDescending); + ascending->setChecked(!groupDescending); } void DolphinViewActionHandler::slotSortFoldersFirstChanged(bool foldersFirst) @@ -836,7 +839,7 @@ void DolphinViewActionHandler::slotGroupTriggered(QAction *action) // 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)) { + for (QAction *groupAction : std::as_const(m_groupByActions)) { KActionMenu *actionMenu = qobject_cast(groupAction); if (actionMenu) { const auto actions = actionMenu->menu()->actions(); From f2474c344a197ed5df886e65fdfce4980767fb59 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 15 Jun 2024 13:44:16 +0000 Subject: [PATCH 03/21] Apply 1 suggestion(s) to 1 file(s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Méven Car --- src/kitemviews/kfileitemmodel.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 34a113eded..564ac42ea1 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1607,8 +1607,6 @@ 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(); From 1682358bea562cb5382a118fa3a414cf78588be8 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 15 Jun 2024 13:54:42 +0000 Subject: [PATCH 04/21] Apply 1 suggestion(s) to 1 file(s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Méven Car --- src/settings/dolphin_directoryviewpropertysettings.kcfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/dolphin_directoryviewpropertysettings.kcfg b/src/settings/dolphin_directoryviewpropertysettings.kcfg index 620983b19f..4cc522b562 100644 --- a/src/settings/dolphin_directoryviewpropertysettings.kcfg +++ b/src/settings/dolphin_directoryviewpropertysettings.kcfg @@ -35,7 +35,7 @@ - When this option is enabled, the sorted items are categorized into groups. + When this option is enabled, the items are categorized into groups. true From 3e4392bed841ce5a91b2f3ecc418f340b51010b5 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 15 Jun 2024 13:55:01 +0000 Subject: [PATCH 05/21] Apply 1 suggestion(s) to 1 file(s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Méven Car --- src/views/dolphinviewactionhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 7454ed0b94..fadd2b3346 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -308,7 +308,7 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac groupByActionMenu->setPopupMode(QToolButton::InstantPopup); const auto groupByActionGroupActions = groupByActionGroup->actions(); - for (QAction *action : groupByActionGroupActions) { + for (const QAction *action : groupByActionGroupActions) { groupByActionMenu->addAction(action); } From 38d293ae410e60f25615bbca7e61596b7febf47d Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sun, 16 Jun 2024 13:02:24 +0300 Subject: [PATCH 06/21] Implemented ItemGroupInfo in place of QVariant in roleRoleGroup functions --- src/kitemviews/kfileitemmodel.cpp | 514 ++++++++++++------------- src/kitemviews/kfileitemmodel.h | 46 ++- src/views/dolphinviewactionhandler.cpp | 2 +- 3 files changed, 283 insertions(+), 279 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 564ac42ea1..1ab9cd0e41 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2288,134 +2288,103 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const QCollator &collator) const { // Unlike sortRoleCompare, this function can and often will return 0. - const KFileItem &itemA = a->item; - const KFileItem &itemB = b->item; - int result = 0; + int groupA, groupB; switch (m_groupRole) { case NoRole: break; - case NameRole: { - QChar groupA = getNameRoleGroup(a, false).toChar(); - QChar groupB = getNameRoleGroup(b, false).toChar(); - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + case NameRole: + groupA = nameRoleGroup(a, false).comparable; + groupB = nameRoleGroup(b, false).comparable; break; - } - case SizeRole: { - int groupA = getSizeRoleGroup(a, false).toInt(); - int groupB = getSizeRoleGroup(b, false).toInt(); - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + case SizeRole: + groupA = sizeRoleGroup(a, false).comparable; + groupB = sizeRoleGroup(b, false).comparable; break; - } - case ModificationTimeRole: { - int groupA = getTimeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::ModificationTime); - }, - a, - false) - .toInt(); - int groupB = getTimeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::ModificationTime); - }, - b, - false) - .toInt(); - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + case ModificationTimeRole: + groupA = timeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + a, + false) + .comparable; + groupB = timeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + b, + false) + .comparable; break; - } - case CreationTimeRole: { - int groupA = getTimeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::CreationTime); - }, - a, - false) - .toInt(); - int groupB = getTimeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::CreationTime); - }, - b, - false) - .toInt(); - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + case CreationTimeRole: + groupA = timeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + a, + false) + .comparable; + groupB = timeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + b, + false) + .comparable; break; - } - case AccessTimeRole: { - int groupA = getTimeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::AccessTime); - }, - a, - false) - .toInt(); - int groupB = getTimeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::AccessTime); - }, - b, - false) - .toInt(); - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + case AccessTimeRole: + groupA = timeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + a, + false) + .comparable; + groupB = timeRoleGroup( + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + b, + false) + .comparable; break; - } - case DeletionTimeRole: { - int groupA = getTimeRoleGroup( - [](const ItemData *item) { - return item->values.value("deletiontime").toDateTime(); - }, - a, - false) - .toInt(); - int groupB = getTimeRoleGroup( - [](const ItemData *item) { - return item->values.value("deletiontime").toDateTime(); - }, - b, - false) - .toInt(); - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + case DeletionTimeRole: + groupA = timeRoleGroup( + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + a, + false) + .comparable; + groupB = timeRoleGroup( + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + b, + false) + .comparable; break; - } // case PermissionsRole: // case RatingRole: default: { - QString groupA = getGenericStringRoleGroup(groupRole(), a); - QString groupB = getGenericStringRoleGroup(groupRole(), b); + QString strGroupA = genericStringRoleGroup(groupRole(), a); + QString strGroupB = genericStringRoleGroup(groupRole(), b); + if (strGroupA < strGroupB) { + result = -1; + } else if (strGroupA > strGroupB) { + result = 1; + } + break; + } + } + if (result == 0) { if (groupA < groupB) { result = -1; } else if (groupA > groupB) { result = 1; } - break; - } } return result; } @@ -2439,19 +2408,24 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol return QString::compare(a, b, Qt::CaseSensitive); } -QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asString) const +KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *itemData, bool withString) const { - const KFileItem item = itemData->item; - const QString name = item.text(); - QVariant newGroupValue; - // Use the first character of the name as group indication - QChar newFirstChar = name.at(0).toUpper(); - if (newFirstChar == QLatin1Char('~') && name.length() > 1) { - newFirstChar = name.at(1).toUpper(); - } + static ItemGroupInfo oldGroupInfo, groupInfo; + static QChar oldFirstChar, firstChar; - if (newFirstChar.isLetter()) { - if (m_collator.compare(newFirstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(newFirstChar, QChar(QLatin1Char('Z'))) <= 0) { + const QString name = itemData->item.text(); + + // Use the first character of the name as group indication + firstChar = name.at(0).toUpper(); + + if (firstChar == oldFirstChar) { + return oldGroupInfo; + } + if (firstChar == QLatin1Char('~') && name.length() > 1) { + firstChar = name.at(1).toUpper(); + } + if (firstChar.isLetter()) { + if (m_collator.compare(firstChar, QChar(QLatin1Char('A'))) >= 0 && m_collator.compare(firstChar, QChar(QLatin1Char('Z'))) <= 0) { // WARNING! Symbols based on latin 'Z' like 'Z' with acute are treated wrong as non Latin and put in a new group. // Try to find a matching group in the range 'A' to 'Z'. @@ -2467,146 +2441,163 @@ QVariant KFileItemModel::getNameRoleGroup(const ItemData *itemData, bool asStrin return m_collator.compare(c1, c2) < 0; }; - std::vector::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan); + std::vector::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), firstChar, localeAwareLessThan); if (it != lettersAtoZ.end()) { - if (localeAwareLessThan(newFirstChar, *it)) { + if (localeAwareLessThan(firstChar, *it)) { // newFirstChar belongs to the group preceding *it. // Example: for an umlaut 'A' in the German locale, *it would be 'B' now. --it; } - newGroupValue = *it; + if (withString) { + groupInfo.text = *it; + } + groupInfo.comparable = (*it).unicode(); } } else { // Symbols from non Latin-based scripts - newGroupValue = newFirstChar; + if (withString) { + groupInfo.text = firstChar; + } + groupInfo.comparable = firstChar.unicode(); } - } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) { + } else if (firstChar >= QLatin1Char('0') && firstChar <= QLatin1Char('9')) { // Apply group '0 - 9' for any name that starts with a digit - if (asString) { - newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9"); - } else { - newGroupValue = QChar('0'); + if (withString) { + groupInfo.text = i18nc("@title:group Groups that start with a digit", "0 - 9"); } + groupInfo.comparable = (int)'0'; } else { - if (asString) { - newGroupValue = i18nc("@title:group", "Others"); - } else { - newGroupValue = QChar('.'); + if (withString) { + groupInfo.text = i18nc("@title:group", "Others"); } + groupInfo.comparable = (int)'.'; } - return newGroupValue; + oldFirstChar = firstChar; + oldGroupInfo = groupInfo; + return groupInfo; } -QVariant KFileItemModel::getSizeRoleGroup(const ItemData *itemData, bool asString) const +KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *itemData, bool withString) const { - const KFileItem item = itemData->item; + static ItemGroupInfo oldGroupInfo, groupInfo; + static KIO::filesize_t oldFileSize, fileSize; - KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U; - int newGroupValue = -1; // None + const KFileItem item = itemData->item; + fileSize = !item.isNull() ? item.size() : ~0U; + + // Use the first character of the name as group indication + fileSize = item.size(); + + groupInfo.comparable = -1; // None if (!item.isNull() && item.isDir()) { if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { - newGroupValue = 0; // Folders + groupInfo.comparable = 0; // Folders } else { fileSize = itemData->values.value("size").toULongLong(); } } - - if (newGroupValue < 0) { + if (fileSize == oldFileSize) { + groupInfo = oldGroupInfo; + } + if (groupInfo.comparable < 0) { if (fileSize < 5 * 1024 * 1024) { // < 5 MB - newGroupValue = 1; // Small + groupInfo.comparable = 1; // Small } else if (fileSize < 10 * 1024 * 1024) { // < 10 MB - newGroupValue = 2; // Medium + groupInfo.comparable = 2; // Medium } else { - newGroupValue = 3; // Big + groupInfo.comparable = 3; // Big } } - if (asString) { + if (withString) { char const *groupNames[] = {"Folders", "Small", "Medium", "Big"}; - return i18nc("@title:group Size", groupNames[newGroupValue]); - } else { - return newGroupValue; + groupInfo.text = i18nc("@title:group Size", groupNames[groupInfo.comparable]); } + oldFileSize = fileSize; + oldGroupInfo = groupInfo; + return groupInfo; } -QVariant KFileItemModel::getTimeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool asString) const +KFileItemModel::ItemGroupInfo +KFileItemModel::timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString) const { + static ItemGroupInfo oldGroupInfo, groupInfo; + static QDate oldFileDate; + const QDate currentDate = QDate::currentDate(); const QDateTime fileTime = fileTimeCb(itemData); const QDate fileDate = fileTime.date(); const int daysDistance = fileDate.daysTo(currentDate); - int intGroupValue; - QString strGroupValue; + int intGroupInfo; + QString strGroupInfo; - if (!asString) { - // Simplified grouping algorithm, preserving dates - // but not taking "pretty printing" into account - if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { + // Simplified grouping algorithm, preserving dates + // but not taking "pretty printing" into account + if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { + if (daysDistance < 7) { + groupInfo.comparable = daysDistance; // Today, Yesterday and week days + } else if (daysDistance < 14) { + groupInfo.comparable = 10; // One Week Ago + } else if (daysDistance < 21) { + groupInfo.comparable = 20; // Two Weeks Ago + } else if (daysDistance < 28) { + groupInfo.comparable = 30; // Three Weeks Ago + } else { + groupInfo.comparable = 40; // Earlier This Month + } + } else { + const QDate lastMonthDate = currentDate.addMonths(-1); + if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) { if (daysDistance < 7) { - intGroupValue = daysDistance; // Today, Yesterday and week days + groupInfo.comparable = daysDistance; // Today, Yesterday and week days (Month, Year) } else if (daysDistance < 14) { - intGroupValue = 10; // One Week Ago + groupInfo.comparable = 9; // One Week Ago (Month, Year) } else if (daysDistance < 21) { - intGroupValue = 20; // Two Weeks Ago + groupInfo.comparable = 19; // Two Weeks Ago (Month, Year) } else if (daysDistance < 28) { - intGroupValue = 30; // Three Weeks Ago + groupInfo.comparable = 29; // Three Weeks Ago (Month, Year) } else { - intGroupValue = 40; // Earlier This Month + groupInfo.comparable = 39; // Earlier on Month, Year } } else { - const QDate lastMonthDate = currentDate.addMonths(-1); - if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) { - if (daysDistance < 7) { - intGroupValue = daysDistance; // Today, Yesterday and week days (Month, Year) - } else if (daysDistance < 14) { - intGroupValue = 9; // One Week Ago (Month, Year) - } else if (daysDistance < 21) { - intGroupValue = 19; // Two Weeks Ago (Month, Year) - } else if (daysDistance < 28) { - intGroupValue = 29; // Three Weeks Ago (Month, Year) - } else { - intGroupValue = 39; // Earlier on Month, Year - } - } else { - // The trick will fail for dates past April, 178956967 or before 1 AD. - intGroupValue = 2147483647 - (fileDate.year() * 12 + fileDate.month() - 1); // Month, Year; newer < older - } + // The trick will fail for dates past April, 178956967 or before 1 AD. + groupInfo.comparable = 2147483647 - (fileDate.year() * 12 + fileDate.month() - 1); // Month, Year; newer < older } - return QVariant(intGroupValue); - } else { + } + if (withString) { if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { switch (daysDistance / 7) { case 0: switch (daysDistance) { case 0: - strGroupValue = i18nc("@title:group Date", "Today"); + groupInfo.text = i18nc("@title:group Date", "Today"); break; case 1: - strGroupValue = i18nc("@title:group Date", "Yesterday"); + groupInfo.text = i18nc("@title:group Date", "Yesterday"); break; default: - strGroupValue = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd")); - strGroupValue = i18nc( + groupInfo.text = fileTime.toString(i18nc("@title:group Date: The week day name: dddd", "dddd")); + groupInfo.text = i18nc( "Can be used to script translation of \"dddd\"" "with context @title:group Date", "%1", - strGroupValue); + groupInfo.text); } break; case 1: - strGroupValue = i18nc("@title:group Date", "One Week Ago"); + groupInfo.text = i18nc("@title:group Date", "One Week Ago"); break; case 2: - strGroupValue = i18nc("@title:group Date", "Two Weeks Ago"); + groupInfo.text = i18nc("@title:group Date", "Two Weeks Ago"); break; case 3: - strGroupValue = i18nc("@title:group Date", "Three Weeks Ago"); + groupInfo.text = i18nc("@title:group Date", "Three Weeks Ago"); break; case 4: case 5: - strGroupValue = i18nc("@title:group Date", "Earlier this Month"); + groupInfo.text = i18nc("@title:group Date", "Earlier this Month"); break; default: Q_ASSERT(false); @@ -2623,29 +2614,29 @@ QVariant KFileItemModel::getTimeRoleGroup(const std::functionvalues.value(role).toString(); } @@ -2760,21 +2753,18 @@ QList> KFileItemModel::nameRoleGroups() const const int maxIndex = count() - 1; QList> groups; - QString groupValue; - QChar firstChar; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - QString newGroupValue = getNameRoleGroup(m_itemData.at(i)).toString(); + ItemGroupInfo newGroupInfo = nameRoleGroup(m_itemData.at(i)); - if (newGroupValue != groupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo.text)); } - - // firstChar = newFirstChar; } return groups; } @@ -2786,20 +2776,19 @@ QList> KFileItemModel::sizeRoleGroups() const const int maxIndex = count() - 1; QList> groups; - QString groupValue; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - QString newGroupValue = getSizeRoleGroup(m_itemData.at(i)).toString(); + ItemGroupInfo newGroupInfo = sizeRoleGroup(m_itemData.at(i)); - if (newGroupValue != groupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo.text)); } } - return groups; } @@ -2810,31 +2799,19 @@ QList> KFileItemModel::timeRoleGroups(const std::function> groups; - const QDate currentDate = QDate::currentDate(); - - QDate previousFileDate; - QString groupValue; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - const QDateTime fileTime = fileTimeCb(m_itemData.at(i)); - const QDate fileDate = fileTime.date(); - if (fileDate == previousFileDate) { - // The current item is in the same group as the previous item - continue; - } - previousFileDate = fileDate; + ItemGroupInfo newGroupInfo = timeRoleGroup(fileTimeCb, m_itemData.at(i)); - QString newGroupValue = getTimeRoleGroup(fileTimeCb, m_itemData.at(i)).toString(); - - if (newGroupValue != groupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo.text)); } } - return groups; } @@ -2845,8 +2822,8 @@ QList> KFileItemModel::permissionRoleGroups() const const int maxIndex = count() - 1; QList> groups; - QString permissionsString; - QString groupValue; + /*QString permissionsString; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; @@ -2900,12 +2877,13 @@ QList> KFileItemModel::permissionRoleGroups() const } others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2); - const QString newGroupValue = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others); - if (newGroupValue != groupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); + const ItemListGroup newGroupInfo = {0, + i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others)}; + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo)); } - } + }*/ return groups; } @@ -2917,19 +2895,19 @@ QList> KFileItemModel::ratingRoleGroups() const const int maxIndex = count() - 1; QList> groups; - int groupValue = -1; + /*ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt(); - if (newGroupValue != groupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); - } - } + ItemGroupInfo newGroupInfo = nameRoleGroup(fileTimeCb, m_itemData.at(i)).toString(); + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo)); + } + }*/ return groups; } @@ -2940,21 +2918,19 @@ QList> KFileItemModel::genericStringRoleGroups(const QByteA const int maxIndex = count() - 1; QList> groups; - bool isFirstGroupValue = true; - QString groupValue; + QString groupText; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - const QString newGroupValue = getGenericStringRoleGroup(role, m_itemData.at(i)); - if (newGroupValue != groupValue || isFirstGroupValue) { - groupValue = newGroupValue; - groups.append(QPair(i, newGroupValue)); - isFirstGroupValue = false; + QString newGroupText = genericStringRoleGroup(role, m_itemData.at(i)); + + if (newGroupText != groupText) { + groupText = newGroupText; + groups.append(QPair(i, newGroupText)); } } - return groups; } diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 725ba71bdb..152321f94f 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -113,8 +113,6 @@ public: QString roleDescription(const QByteArray &role) const override; - QList> groups() const override; - /** * @return The file-item for the index \a index. If the index is in a valid * range it is assured that the file-item is not null. The runtime @@ -198,7 +196,7 @@ public: /** * @return Provides static information for a role that is supported - * by KFileItemModel. Some roles can only be determined if + * 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); @@ -211,6 +209,8 @@ public: */ static QList rolesInformation(); + QList> groups() const override; + /** set to true to hide application/x-trash files */ void setShowTrashMime(bool show); @@ -374,6 +374,15 @@ private: ItemData *parent; }; + struct ItemGroupInfo { + int comparable; + QString text; + + bool operator==(const ItemGroupInfo &other) const; + bool operator!=(const ItemGroupInfo &other) const; + bool operator<(const ItemGroupInfo &other) const; + }; + enum RemoveItemsBehavior { KeepItemData, DeleteItemData, DeleteItemDataIfUnfiltered }; void insertItems(QList &items); @@ -463,12 +472,12 @@ private: int stringCompare(const QString &a, const QString &b, const QCollator &collator) const; - QVariant getNameRoleGroup(const ItemData *itemData, bool asString = true) const; - QVariant getSizeRoleGroup(const ItemData *itemData, bool asString = true) const; - QVariant getTimeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool asString = true) const; - QVariant getPermissionRoleGroup(const ItemData *itemData, bool asString = true) const; - QVariant getRatingRoleGroup(const ItemData *itemData, bool asString = true) const; - QString getGenericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const; + ItemGroupInfo nameRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo sizeRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString = true) const; + ItemGroupInfo permissionRoleGroup(const ItemData *itemData, bool withString = true) const; + ItemGroupInfo ratingRoleGroup(const ItemData *itemData, bool withString = true) const; + QString genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const; QList> nameRoleGroups() const; QList> sizeRoleGroups() const; @@ -624,4 +633,23 @@ inline bool KFileItemModel::isChildItem(int index) const } } +inline bool KFileItemModel::ItemGroupInfo::operator==(const ItemGroupInfo &other) const +{ + return comparable == other.comparable && text == other.text; +} + +inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other) const +{ + return comparable != other.comparable || text != other.text; +} + +inline bool KFileItemModel::ItemGroupInfo::operator<(const ItemGroupInfo &other) const +{ + if (comparable == other.comparable) { + return text < other.text; + } else { + return comparable < other.comparable; + } +} + #endif diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index fadd2b3346..7454ed0b94 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -308,7 +308,7 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac groupByActionMenu->setPopupMode(QToolButton::InstantPopup); const auto groupByActionGroupActions = groupByActionGroup->actions(); - for (const QAction *action : groupByActionGroupActions) { + for (QAction *action : groupByActionGroupActions) { groupByActionMenu->addAction(action); } From e9b056dcf080a12a09fa1c1a55eab79edb2164b5 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sun, 16 Jun 2024 14:27:11 +0300 Subject: [PATCH 07/21] Fixed grouping again, implemented permission and rating grouping. --- src/kitemviews/kfileitemmodel.cpp | 192 +++++++++++++++++------------- src/kitemviews/kfileitemmodel.h | 4 +- 2 files changed, 110 insertions(+), 86 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 1ab9cd0e41..5620460591 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2366,16 +2366,18 @@ int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const false) .comparable; break; - // case PermissionsRole: - // case RatingRole: + case PermissionsRole: + groupA = permissionRoleGroup(a, false).comparable; + groupB = permissionRoleGroup(b, false).comparable; + break; + case RatingRole: + groupA = ratingRoleGroup(a, false).comparable; + groupB = ratingRoleGroup(b, false).comparable; + break; default: { QString strGroupA = genericStringRoleGroup(groupRole(), a); QString strGroupB = genericStringRoleGroup(groupRole(), b); - if (strGroupA < strGroupB) { - result = -1; - } else if (strGroupA > strGroupB) { - result = 1; - } + result = stringCompare(strGroupA, strGroupB, collator); break; } } @@ -2410,8 +2412,10 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *itemData, bool withString) const { - static ItemGroupInfo oldGroupInfo, groupInfo; - static QChar oldFirstChar, firstChar; + static ItemGroupInfo oldGroupInfo; + static QChar oldFirstChar; + ItemGroupInfo groupInfo; + QChar firstChar; const QString name = itemData->item.text(); @@ -2480,15 +2484,14 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *itemData, bool withString) const { - static ItemGroupInfo oldGroupInfo, groupInfo; - static KIO::filesize_t oldFileSize, fileSize; + static ItemGroupInfo oldGroupInfo; + static KIO::filesize_t oldFileSize; + ItemGroupInfo groupInfo; + KIO::filesize_t fileSize; const KFileItem item = itemData->item; fileSize = !item.isNull() ? item.size() : ~0U; - // Use the first character of the name as group indication - fileSize = item.size(); - groupInfo.comparable = -1; // None if (!item.isNull() && item.isDir()) { if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { @@ -2498,7 +2501,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item } } if (fileSize == oldFileSize) { - groupInfo = oldGroupInfo; + return oldGroupInfo; } if (groupInfo.comparable < 0) { if (fileSize < 5 * 1024 * 1024) { // < 5 MB @@ -2522,17 +2525,15 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item KFileItemModel::ItemGroupInfo KFileItemModel::timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString) const { - static ItemGroupInfo oldGroupInfo, groupInfo; + static ItemGroupInfo oldGroupInfo; static QDate oldFileDate; + ItemGroupInfo groupInfo; const QDate currentDate = QDate::currentDate(); const QDateTime fileTime = fileTimeCb(itemData); const QDate fileDate = fileTime.date(); const int daysDistance = fileDate.daysTo(currentDate); - int intGroupInfo; - QString strGroupInfo; - // Simplified grouping algorithm, preserving dates // but not taking "pretty printing" into account if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { @@ -2553,13 +2554,13 @@ KFileItemModel::timeRoleGroup(const std::function & if (daysDistance < 7) { groupInfo.comparable = daysDistance; // Today, Yesterday and week days (Month, Year) } else if (daysDistance < 14) { - groupInfo.comparable = 9; // One Week Ago (Month, Year) + groupInfo.comparable = 11; // One Week Ago (Month, Year) } else if (daysDistance < 21) { - groupInfo.comparable = 19; // Two Weeks Ago (Month, Year) + groupInfo.comparable = 21; // Two Weeks Ago (Month, Year) } else if (daysDistance < 28) { - groupInfo.comparable = 29; // Three Weeks Ago (Month, Year) + groupInfo.comparable = 31; // Three Weeks Ago (Month, Year) } else { - groupInfo.comparable = 39; // Earlier on Month, Year + groupInfo.comparable = 41; // Earlier on Month, Year } } else { // The trick will fail for dates past April, 178956967 or before 1 AD. @@ -2626,7 +2627,7 @@ KFileItemModel::timeRoleGroup(const std::function & const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); groupInfo.text = fileTime.toString(untranslatedFormat); } - } else if (daysDistance <= 7) { + } else if (daysDistance < 7) { groupInfo.text = fileTime.toString(i18nc("@title:group Date: " "The week day name: dddd, MMMM is full month name " @@ -2637,7 +2638,7 @@ KFileItemModel::timeRoleGroup(const std::function & "\"dddd (MMMM, yyyy)\" with context @title:group Date", "%1", groupInfo.text); - } else if (daysDistance <= 7 * 2) { + } else if (daysDistance < 7 * 2) { const KLocalizedString format = ki18nc( "@title:group Date: " "MMMM is full month name in current locale, and yyyy is " @@ -2658,7 +2659,7 @@ KFileItemModel::timeRoleGroup(const std::function & const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); groupInfo.text = fileTime.toString(untranslatedFormat); } - } else if (daysDistance <= 7 * 3) { + } else if (daysDistance < 7 * 3) { const KLocalizedString format = ki18nc( "@title:group Date: " "MMMM is full month name in current locale, and yyyy is " @@ -2679,7 +2680,7 @@ KFileItemModel::timeRoleGroup(const std::function & const QString untranslatedFormat = format.toString({QLatin1String("en_US")}); groupInfo.text = fileTime.toString(untranslatedFormat); } - } else if (daysDistance <= 7 * 4) { + } else if (daysDistance < 7 * 4) { const KLocalizedString format = ki18nc( "@title:group Date: " "MMMM is full month name in current locale, and yyyy is " @@ -2741,6 +2742,77 @@ KFileItemModel::timeRoleGroup(const std::function & return groupInfo; } +KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData *itemData, bool withString) const +{ + static ItemGroupInfo oldGroupInfo; + static QFileDevice::Permissions oldPermissions; + ItemGroupInfo groupInfo; + + const QFileInfo info(itemData->item.url().toLocalFile()); + const QFileDevice::Permissions permissions = info.permissions(); + if (permissions == oldPermissions) { + return oldGroupInfo; + } + groupInfo.comparable = (int)permissions; + + if (withString) { + // Set user string + QString user; + if (permissions & QFile::ReadUser) { + user = i18nc("@item:intext Access permission, concatenated", "Read, "); + } + if (permissions & QFile::WriteUser) { + user += i18nc("@item:intext Access permission, concatenated", "Write, "); + } + if (permissions & QFile::ExeUser) { + user += i18nc("@item:intext Access permission, concatenated", "Execute, "); + } + user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.length() - 2); + + // Set group string + QString group; + if (permissions & QFile::ReadGroup) { + group = i18nc("@item:intext Access permission, concatenated", "Read, "); + } + if (permissions & QFile::WriteGroup) { + group += i18nc("@item:intext Access permission, concatenated", "Write, "); + } + if (permissions & QFile::ExeGroup) { + group += i18nc("@item:intext Access permission, concatenated", "Execute, "); + } + group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.length() - 2); + + // Set others string + QString others; + if (permissions & QFile::ReadOther) { + others = i18nc("@item:intext Access permission, concatenated", "Read, "); + } + if (permissions & QFile::WriteOther) { + others += i18nc("@item:intext Access permission, concatenated", "Write, "); + } + if (permissions & QFile::ExeOther) { + others += i18nc("@item:intext Access permission, concatenated", "Execute, "); + } + others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2); + groupInfo.text = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others); + } + oldPermissions = permissions; + oldGroupInfo = groupInfo; + return groupInfo; +} + +KFileItemModel::ItemGroupInfo KFileItemModel::ratingRoleGroup(const ItemData *itemData, bool withString) const +{ + ItemGroupInfo groupInfo; + groupInfo.comparable = itemData->values.value("rating", 0).toInt(); + if (withString) { + // Dolphin does not currently use string representation of star rating + // as stars are rendered as graphics in group headers. + groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated ") + QString::number(groupInfo.comparable); + } + return groupInfo; +} + QString KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const { return itemData->values.value(role).toString(); @@ -2822,69 +2894,19 @@ QList> KFileItemModel::permissionRoleGroups() const const int maxIndex = count() - 1; QList> groups; - /*QString permissionsString; ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - const ItemData *itemData = m_itemData.at(i); - const QString newPermissionsString = itemData->values.value("permissions").toString(); - if (newPermissionsString == permissionsString) { - continue; - } - permissionsString = newPermissionsString; + ItemGroupInfo newGroupInfo = permissionRoleGroup(m_itemData.at(i)); - const QFileInfo info(itemData->item.url().toLocalFile()); - - // Set user string - QString user; - if (info.permission(QFile::ReadUser)) { - user = i18nc("@item:intext Access permission, concatenated", "Read, "); - } - if (info.permission(QFile::WriteUser)) { - user += i18nc("@item:intext Access permission, concatenated", "Write, "); - } - if (info.permission(QFile::ExeUser)) { - user += i18nc("@item:intext Access permission, concatenated", "Execute, "); - } - user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.length() - 2); - - // Set group string - QString group; - if (info.permission(QFile::ReadGroup)) { - group = i18nc("@item:intext Access permission, concatenated", "Read, "); - } - if (info.permission(QFile::WriteGroup)) { - group += i18nc("@item:intext Access permission, concatenated", "Write, "); - } - if (info.permission(QFile::ExeGroup)) { - group += i18nc("@item:intext Access permission, concatenated", "Execute, "); - } - group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.length() - 2); - - // Set others string - QString others; - if (info.permission(QFile::ReadOther)) { - others = i18nc("@item:intext Access permission, concatenated", "Read, "); - } - if (info.permission(QFile::WriteOther)) { - others += i18nc("@item:intext Access permission, concatenated", "Write, "); - } - if (info.permission(QFile::ExeOther)) { - others += i18nc("@item:intext Access permission, concatenated", "Execute, "); - } - others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2); - - const ItemListGroup newGroupInfo = {0, - i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others)}; if (newGroupInfo != groupInfo) { groupInfo = newGroupInfo; - groups.append(QPair(i, newGroupInfo)); + groups.append(QPair(i, newGroupInfo.text)); } - }*/ - + } return groups; } @@ -2895,19 +2917,21 @@ QList> KFileItemModel::ratingRoleGroups() const const int maxIndex = count() - 1; QList> groups; - /*ItemGroupInfo groupInfo; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - ItemGroupInfo newGroupInfo = nameRoleGroup(fileTimeCb, m_itemData.at(i)).toString(); + ItemGroupInfo newGroupInfo = ratingRoleGroup(m_itemData.at(i)); if (newGroupInfo != groupInfo) { groupInfo = newGroupInfo; - groups.append(QPair(i, newGroupInfo)); + // Using the numeric representation because Dolphin has a special + // case for drawing stars. + groups.append(QPair(i, newGroupInfo.comparable)); } - }*/ + } return groups; } diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 152321f94f..9001e7bd56 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -113,6 +113,8 @@ public: QString roleDescription(const QByteArray &role) const override; + QList> groups() const override; + /** * @return The file-item for the index \a index. If the index is in a valid * range it is assured that the file-item is not null. The runtime @@ -209,8 +211,6 @@ public: */ static QList rolesInformation(); - QList> groups() const override; - /** set to true to hide application/x-trash files */ void setShowTrashMime(bool show); From 91273c8b03dbf184d7a9043d4f5ffe5b45c87b71 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sun, 16 Jun 2024 18:49:09 +0300 Subject: [PATCH 08/21] Implemented the possibility for sorting/grouping behaviors that are not tied to roles. Reintroducedd the original grouping as one of grouping options. --- src/kitemviews/kfileitemmodel.cpp | 32 ++++++++++++++++++++++---- src/kitemviews/kfileitemmodel.h | 3 +++ src/kitemviews/kitemlistview.cpp | 2 +- src/views/dolphinviewactionhandler.cpp | 15 +++++++++--- src/views/viewproperties.cpp | 2 ++ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 5620460591..a2e7c00758 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", "text", parent) + : KItemModelBase("text", "none", parent) , m_dirLister(nullptr) , m_sortDirsFirst(true) , m_sortHiddenLast(false) @@ -387,7 +387,14 @@ QList> KFileItemModel::groups() const QElapsedTimer timer; timer.start(); #endif - switch (typeForRole(groupRole())) { + QByteArray role = groupRole(); + if (typeForRole(role) == NoRole) { + // Handle extra grouping information + if (m_groupExtraInfo == "followSort") { + role = sortRole(); + } + } + switch (typeForRole(role)) { case NoRole: m_groups.clear(); break; @@ -424,7 +431,7 @@ QList> KFileItemModel::groups() const m_groups = ratingRoleGroups(); break; default: - m_groups = genericStringRoleGroups(groupRole()); + m_groups = genericStringRoleGroups(role); break; } @@ -949,6 +956,13 @@ void KFileItemModel::onSortRoleChanged(const QByteArray ¤t, const QByteArr { Q_UNUSED(previous) m_sortRole = typeForRole(current); + if (m_sortRole == NoRole) { + // Requested role not in list of roles. This could + // be used for indicating non-trivial sorting behavior + m_sortExtraInfo = current; + } else { + m_sortExtraInfo.clear(); + } if (!m_requestRole[m_sortRole]) { QSet newRoles = m_roles; @@ -975,8 +989,15 @@ void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteAr { Q_UNUSED(previous) m_groupRole = typeForRole(current); + if (m_groupRole == NoRole) { + // Requested role not in list of roles. This could + // be used for indicating non-trivial grouping behavior + m_groupExtraInfo = current; + } else { + m_groupExtraInfo.clear(); + } - if (!m_requestRole[m_sortRole]) { + if (!m_requestRole[m_groupRole]) { QSet newRoles = m_roles; newRoles << current; setRoles(newRoles); @@ -2293,7 +2314,8 @@ int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const int groupA, groupB; switch (m_groupRole) { case NoRole: - break; + // Non-trivial grouping behavior might be handled there in the future. + return 0; case NameRole: groupA = nameRoleGroup(a, false).comparable; groupB = nameRoleGroup(b, false).comparable; diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 9001e7bd56..ea72a48a46 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -575,6 +575,9 @@ private: RoleType m_sortRole; RoleType m_groupRole; + QByteArray m_sortExtraInfo; + QByteArray m_groupExtraInfo; + int m_sortingProgressPercent; // Value of directorySortingProgress() signal QSet m_roles; diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index afc392810e..52cd49bfba 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -2185,7 +2185,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/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 7454ed0b94..c215f1bf75 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -302,6 +302,18 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac // View -> Group By QActionGroup *groupByActionGroup = createFileItemRolesActionGroup(QStringLiteral("group_by_")); + KToggleAction *groupAsNone = m_actionCollection->add(QStringLiteral("group_none")); + groupAsNone->setData("none"); + groupAsNone->setActionGroup(groupByActionGroup); + groupAsNone->setText(i18nc("@label", "No grouping")); + m_groupByActions.insert("none", groupAsNone); + + KToggleAction *groupAsFollowSort = m_actionCollection->add(QStringLiteral("group_followSort")); + groupAsFollowSort->setData("followSort"); + groupAsFollowSort->setActionGroup(groupByActionGroup); + groupAsFollowSort->setText(i18nc("@label", "Follow sorting")); + m_groupByActions.insert("followSort", groupAsFollowSort); + KActionMenu *groupByActionMenu = m_actionCollection->add(QStringLiteral("group")); groupByActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); groupByActionMenu->setText(i18nc("@action:inmenu View", "Group By")); @@ -404,9 +416,6 @@ QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QSt #endif 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 && !isGroupGroup && info.role == "text") { diff --git a/src/views/viewproperties.cpp b/src/views/viewproperties.cpp index ea89dc4f5c..c5afe663d6 100644 --- a/src/views/viewproperties.cpp +++ b/src/views/viewproperties.cpp @@ -410,6 +410,8 @@ void ViewProperties::setDirProperties(const ViewProperties &props) setGroupedSorting(props.groupedSorting()); setSortRole(props.sortRole()); setSortOrder(props.sortOrder()); + setGroupRole(props.groupRole()); + setGroupOrder(props.groupOrder()); setSortFoldersFirst(props.sortFoldersFirst()); setSortHiddenLast(props.sortHiddenLast()); setVisibleRoles(props.visibleRoles()); From 187933a7a6acb7f50ab397f5170308b8aab76bbc Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Mon, 17 Jun 2024 00:42:46 +0300 Subject: [PATCH 09/21] Fixed crash on text/text sorting/grouping. Missed a collatorLock :/ --- src/kitemviews/kfileitemmodel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index a2e7c00758..afe8f71c4e 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2441,6 +2441,8 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item const QString name = itemData->item.text(); + QMutexLocker collatorLock(s_collatorMutex()); + // Use the first character of the name as group indication firstChar = name.at(0).toUpper(); From 8d30497f716d17e1062f0dea28d759e458b8397e Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Mon, 17 Jun 2024 12:09:21 +0300 Subject: [PATCH 10/21] Fix for necessary roles not being fetched before grouping. Visual polish --- src/kitemviews/kfileitemmodel.cpp | 31 +++++++++++++++++++++++++- src/views/dolphinviewactionhandler.cpp | 9 ++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index afe8f71c4e..10d9e50e34 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1690,7 +1690,7 @@ void KFileItemModel::removeItems(const KItemRangeList &itemRanges, RemoveItemsBe QList KFileItemModel::createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const { - if (m_sortRole == TypeRole) { + if (m_sortRole == TypeRole || m_groupRole == 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). @@ -1753,6 +1753,35 @@ void KFileItemModel::prepareItemsForSorting(QList &itemDataList) // DateRole). break; } + switch (m_groupRole) { + case ExtensionRole: + case PermissionsRole: + case OwnerRole: + case GroupRole: + case DestinationRole: + case PathRole: + case DeletionTimeRole: + for (ItemData *itemData : std::as_const(itemDataList)) { + if (itemData->values.isEmpty()) { + itemData->values = retrieveData(itemData->item, itemData->parent); + } + } + break; + + case 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); + } + } + } + break; + + default: + break; + } } int KFileItemModel::expandedParentsCount(const ItemData *data) diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index c215f1bf75..ccf4ca1194 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -796,6 +796,15 @@ void DolphinViewActionHandler::slotGroupRoleChanged(const QByteArray &role) ascending->setText(i18nc("Group ascending", "Ascending")); } + // Disable group order selector if grouping behavior does not support it + if (role == "none" || role == "followSort") { + descending->setEnabled(false); + ascending->setEnabled(false); + } else { + descending->setEnabled(true); + ascending->setEnabled(true); + } + slotGroupOrderChanged(m_currentView->groupOrder()); } From 6f6f080bb883afbfcd8e4208348630d73e4ba559 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Mon, 17 Jun 2024 13:04:16 +0300 Subject: [PATCH 11/21] "Group by - Type" now respects "Folders first" setting. Minor code cleanup --- src/kitemviews/kfileitemmodel.cpp | 155 ++++++++++++++++-------------- src/kitemviews/kfileitemmodel.h | 13 +-- 2 files changed, 85 insertions(+), 83 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 10d9e50e34..7d5b0f5184 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2340,104 +2340,99 @@ int KFileItemModel::groupRoleCompare(const ItemData *a, const ItemData *b, const // Unlike sortRoleCompare, this function can and often will return 0. int result = 0; - int groupA, groupB; + ItemGroupInfo groupA, groupB; switch (m_groupRole) { case NoRole: // Non-trivial grouping behavior might be handled there in the future. return 0; case NameRole: - groupA = nameRoleGroup(a, false).comparable; - groupB = nameRoleGroup(b, false).comparable; + groupA = nameRoleGroup(a, false); + groupB = nameRoleGroup(b, false); break; case SizeRole: - groupA = sizeRoleGroup(a, false).comparable; - groupB = sizeRoleGroup(b, false).comparable; + groupA = sizeRoleGroup(a, false); + groupB = sizeRoleGroup(b, false); break; case ModificationTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::ModificationTime); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::ModificationTime); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::ModificationTime); + }, + b, + false); break; case CreationTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::CreationTime); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::CreationTime); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::CreationTime); + }, + b, + false); break; case AccessTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::AccessTime); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->item.time(KFileItem::AccessTime); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->item.time(KFileItem::AccessTime); + }, + b, + false); break; case DeletionTimeRole: groupA = timeRoleGroup( - [](const ItemData *item) { - return item->values.value("deletiontime").toDateTime(); - }, - a, - false) - .comparable; + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + a, + false); groupB = timeRoleGroup( - [](const ItemData *item) { - return item->values.value("deletiontime").toDateTime(); - }, - b, - false) - .comparable; + [](const ItemData *item) { + return item->values.value("deletiontime").toDateTime(); + }, + b, + false); break; case PermissionsRole: - groupA = permissionRoleGroup(a, false).comparable; - groupB = permissionRoleGroup(b, false).comparable; + groupA = permissionRoleGroup(a, false); + groupB = permissionRoleGroup(b, false); break; case RatingRole: - groupA = ratingRoleGroup(a, false).comparable; - groupB = ratingRoleGroup(b, false).comparable; + groupA = ratingRoleGroup(a, false); + groupB = ratingRoleGroup(b, false); + break; + case TypeRole: + groupA = typeRoleGroup(a); + groupB = typeRoleGroup(b); break; default: { - QString strGroupA = genericStringRoleGroup(groupRole(), a); - QString strGroupB = genericStringRoleGroup(groupRole(), b); - result = stringCompare(strGroupA, strGroupB, collator); + groupA = genericStringRoleGroup(groupRole(), a); + groupB = genericStringRoleGroup(groupRole(), b); break; } } - if (result == 0) { - if (groupA < groupB) { - result = -1; - } else if (groupA > groupB) { - result = 1; - } + if (groupA.comparable < groupB.comparable) { + result = -1; + } else if (groupA.comparable > groupB.comparable) { + result = 1; + } else { + result = stringCompare(groupA.text, groupB.text, collator); } return result; } @@ -2866,9 +2861,9 @@ KFileItemModel::ItemGroupInfo KFileItemModel::ratingRoleGroup(const ItemData *it return groupInfo; } -QString KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const +KFileItemModel::ItemGroupInfo KFileItemModel::genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const { - return itemData->values.value(role).toString(); + return {0, itemData->values.value(role).toString()}; } QList> KFileItemModel::nameRoleGroups() const @@ -2917,6 +2912,20 @@ QList> KFileItemModel::sizeRoleGroups() const return groups; } +KFileItemModel::ItemGroupInfo KFileItemModel::typeRoleGroup(const ItemData *itemData) const +{ + int priority = 0; + if (itemData->item.isDir() && m_sortDirsFirst) { + // Ensure folders stay first regardless of grouping order + if (groupOrder() == Qt::AscendingOrder) { + priority = -1; + } else { + priority = 1; + } + } + return {priority, itemData->values.value("type").toString()}; +} + QList> KFileItemModel::timeRoleGroups(const std::function &fileTimeCb) const { Q_ASSERT(!m_itemData.isEmpty()); @@ -2995,17 +3004,17 @@ QList> KFileItemModel::genericStringRoleGroups(const QByteA const int maxIndex = count() - 1; QList> groups; - QString groupText; + ItemGroupInfo groupInfo; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } - QString newGroupText = genericStringRoleGroup(role, m_itemData.at(i)); + ItemGroupInfo newGroupInfo = genericStringRoleGroup(role, m_itemData.at(i)); - if (newGroupText != groupText) { - groupText = newGroupText; - groups.append(QPair(i, newGroupText)); + if (newGroupInfo != groupInfo) { + groupInfo = newGroupInfo; + groups.append(QPair(i, newGroupInfo.text)); } } return groups; diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index ea72a48a46..f89eae9469 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -477,13 +477,15 @@ private: ItemGroupInfo timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString = true) const; ItemGroupInfo permissionRoleGroup(const ItemData *itemData, bool withString = true) const; ItemGroupInfo ratingRoleGroup(const ItemData *itemData, bool withString = true) const; - QString genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const; + ItemGroupInfo typeRoleGroup(const ItemData *itemData) const; + ItemGroupInfo genericStringRoleGroup(const QByteArray &role, const ItemData *itemData) const; QList> nameRoleGroups() const; QList> sizeRoleGroups() const; QList> timeRoleGroups(const std::function &fileTimeCb) const; QList> permissionRoleGroups() const; QList> ratingRoleGroups() const; + QList> typeRoleGroups() const; QList> genericStringRoleGroups(const QByteArray &typeForRole) const; /** @@ -646,13 +648,4 @@ inline bool KFileItemModel::ItemGroupInfo::operator!=(const ItemGroupInfo &other return comparable != other.comparable || text != other.text; } -inline bool KFileItemModel::ItemGroupInfo::operator<(const ItemGroupInfo &other) const -{ - if (comparable == other.comparable) { - return text < other.text; - } else { - return comparable < other.comparable; - } -} - #endif From e7804cff520d502dbc08e342d003062b92db8c4c Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Tue, 18 Jun 2024 00:03:51 +0300 Subject: [PATCH 12/21] Fixed crashing when initial grouping is by size. Fixed size grouping ignoring directory size. --- src/kitemviews/kfileitemmodel.cpp | 11 ++++++----- src/kitemviews/kfileitemmodel.h | 1 + src/kitemviews/kfileitemmodelrolesupdater.cpp | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 7d5b0f5184..63686992f7 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1040,6 +1040,7 @@ void KFileItemModel::loadSortingSettings() // Workaround for bug https://bugreports.qt.io/browse/QTBUG-69361 // Force the clean state of QCollator in single thread to avoid thread safety problems in sort m_collator.compare(QString(), QString()); + m_dirSizeMode = ContentDisplaySettings::directorySizeMode(); } void KFileItemModel::resortAllItems() @@ -1105,7 +1106,8 @@ void KFileItemModel::resortAllItems() } Q_EMIT itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes); - } else if (groupedSorting()) { + } + if (groupedSorting()) { // The groups might have changed even if the order of the items has not. const QList> oldGroups = m_groups; m_groups.clear(); @@ -2129,8 +2131,7 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla return true; } } - if (m_sortDirsFirst - || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { + if (m_sortDirsFirst || (m_dirSizeMode == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { const bool isDirA = a->item.isDir(); const bool isDirB = b->item.isDir(); if (isDirA && !isDirB) { @@ -2184,7 +2185,7 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const break; case SizeRole: { - if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) { + if (m_dirSizeMode == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) { // folders first then // items A and B are folders thanks to lessThan checks auto valueA = a->values.value("count"); @@ -2542,7 +2543,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item groupInfo.comparable = -1; // None if (!item.isNull() && item.isDir()) { - if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount || m_sortDirsFirst) { + if (m_dirSizeMode == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount) { groupInfo.comparable = 0; // Folders } else { fileSize = itemData->values.value("size").toULongLong(); diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index f89eae9469..fc2f1b6c8c 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -574,6 +574,7 @@ private: bool m_naturalSorting; bool m_sortDirsFirst; bool m_sortHiddenLast; + int m_dirSizeMode; RoleType m_sortRole; RoleType m_groupRole; diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 0ff431ac99..0012e99c1c 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -368,7 +368,7 @@ void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList &itemRan timer.start(); // Determine the sort role synchronously for as many items as possible. - if (m_resolvableRoles.contains(m_model->sortRole())) { + if (m_resolvableRoles.contains(m_model->sortRole()) || m_resolvableRoles.contains(m_model->groupRole())) { int insertedCount = 0; for (const KItemRange &range : itemRanges) { const int lastIndex = insertedCount + range.index + range.count - 1; @@ -1218,13 +1218,14 @@ void KFileItemModelRolesUpdater::applySortRole(int index) QHash data; const KFileItem item = m_model->fileItem(index); - if (m_model->sortRole() == "type") { + // Despite the name, this handles both sorting and grouping, as they happen at the same time (resorting items). + if (m_model->sortRole() == "type" || m_model->groupRole() == "type") { if (!item.isMimeTypeKnown()) { item.determineMimeType(); } data.insert("type", item.mimeComment()); - } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { + } else if ((m_model->sortRole() == "size" || m_model->groupRole() == "size") && item.isLocalFile() && item.isDir()) { startDirectorySizeCounting(item, index); return; } else { From 27ca9bd64e6d4ce349d80696f1c4103ce3b13a36 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Tue, 18 Jun 2024 00:07:47 +0300 Subject: [PATCH 13/21] Minor fix on sizeRoleGroup and directories --- src/kitemviews/kfileitemmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 63686992f7..b77592b0ec 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2543,7 +2543,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item groupInfo.comparable = -1; // None if (!item.isNull() && item.isDir()) { - if (m_dirSizeMode == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount) { + if (m_dirSizeMode != ContentDisplaySettings::EnumDirectorySizeMode::ContentSize) { groupInfo.comparable = 0; // Folders } else { fileSize = itemData->values.value("size").toULongLong(); From 9aaf3054106311d8ef49174e32f0ad03dcbe3fca Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 22 Jun 2024 12:31:29 +0300 Subject: [PATCH 14/21] Some code cleanup as per suggestions --- src/kitemviews/kfileitemlistview.cpp | 3 +- src/kitemviews/kfileitemmodel.cpp | 49 +++++----------------------- src/kitemviews/kfileitemmodel.h | 10 ++++-- src/kitemviews/kitemmodelbase.cpp | 14 ++++---- src/kitemviews/kitemmodelbase.h | 10 +++--- 5 files changed, 30 insertions(+), 56 deletions(-) diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index d763fe2879..d58a1a8baf 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -452,8 +452,9 @@ void KFileItemListView::applyRolesToModel() roles.insert("expandedParentsCount"); } - // Assure that the role that is used for sorting will be determined + // Assure that the roles used for sorting and grouping will be determined roles.insert(fileItemModel->sortRole()); + roles.insert(fileItemModel->groupRole()); fileItemModel->setRoles(roles); m_modelRolesUpdater->setRoles(roles); diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index b77592b0ec..9464c7e09b 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -975,14 +975,10 @@ void KFileItemModel::onSortRoleChanged(const QByteArray ¤t, const QByteArr } } -void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current) Q_UNUSED(previous) - - if (resortItems) { - resortAllItems(); - } } void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) @@ -1008,14 +1004,10 @@ void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteAr } } -void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current) Q_UNUSED(previous) - - if (resortItems) { - resortAllItems(); - } } void KFileItemModel::loadSortingSettings() @@ -1716,9 +1708,9 @@ QList KFileItemModel::createItemDataList(const QUrl return itemDataList; } -void KFileItemModel::prepareItemsForSorting(QList &itemDataList) +void KFileItemModel::prepareItemsWithRole(QList &itemDataList, RoleType roleType) { - switch (m_sortRole) { + switch (roleType) { case ExtensionRole: case PermissionsRole: case OwnerRole: @@ -1755,35 +1747,12 @@ void KFileItemModel::prepareItemsForSorting(QList &itemDataList) // DateRole). break; } - switch (m_groupRole) { - case ExtensionRole: - case PermissionsRole: - case OwnerRole: - case GroupRole: - case DestinationRole: - case PathRole: - case DeletionTimeRole: - for (ItemData *itemData : std::as_const(itemDataList)) { - if (itemData->values.isEmpty()) { - itemData->values = retrieveData(itemData->item, itemData->parent); - } - } - break; +} - case 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); - } - } - } - break; - - default: - break; - } +void KFileItemModel::prepareItemsForSorting(QList &itemDataList) +{ + prepareItemsWithRole(itemDataList, m_sortRole); + prepareItemsWithRole(itemDataList, m_groupRole); } int KFileItemModel::expandedParentsCount(const ItemData *data) diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index fc2f1b6c8c..edc77457ba 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -293,9 +293,9 @@ 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, 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; - void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems = true) override; + void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) override; private Q_SLOTS: /** @@ -396,6 +396,12 @@ private: */ QList createItemDataList(const QUrl &parentUrl, const KFileItemList &items) const; + /** + * Helper method for prepareItemsForSorting(). + * For a set role, fills 'values' of ItemData non-lazily. + */ + void prepareItemsWithRole(QList &itemDataList, RoleType roleType); + /** * Prepares the items for sorting. Normally, the hash 'values' in ItemData is filled * lazily to save time and memory, but for some sort roles, it is expected that the diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index b846318ee7..2f8e1e4a30 100644 --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -68,12 +68,12 @@ QByteArray KItemModelBase::sortRole() const return m_sortRole; } -void KItemModelBase::setSortOrder(Qt::SortOrder order, bool resortItems) +void KItemModelBase::setSortOrder(Qt::SortOrder order) { if (order != m_sortOrder) { const Qt::SortOrder previous = m_sortOrder; m_sortOrder = order; - onSortOrderChanged(order, previous, resortItems); + onSortOrderChanged(order, previous); Q_EMIT sortOrderChanged(order, previous); } } @@ -93,12 +93,12 @@ QByteArray KItemModelBase::groupRole() const return m_groupRole; } -void KItemModelBase::setGroupOrder(Qt::SortOrder order, bool resortItems) +void KItemModelBase::setGroupOrder(Qt::SortOrder order) { if (order != m_groupOrder) { const Qt::SortOrder previous = m_groupOrder; m_groupOrder = order; - onGroupOrderChanged(order, previous, resortItems); + onGroupOrderChanged(order, previous); Q_EMIT groupOrderChanged(order, previous); } } @@ -180,11 +180,10 @@ void KItemModelBase::onSortRoleChanged(const QByteArray ¤t, const QByteArr Q_UNUSED(resortItems) } -void KItemModelBase::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +void KItemModelBase::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current) Q_UNUSED(previous) - Q_UNUSED(resortItems) } void KItemModelBase::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) @@ -194,11 +193,10 @@ void KItemModelBase::onGroupRoleChanged(const QByteArray ¤t, const QByteAr Q_UNUSED(resortItems) } -void KItemModelBase::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous, bool resortItems) +void KItemModelBase::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { 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 c336a07265..bc8ab64427 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -81,7 +81,7 @@ 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, bool resortItems = true); + void setSortOrder(Qt::SortOrder order); Qt::SortOrder sortOrder() const; /** @@ -98,7 +98,7 @@ public: * 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); + void setGroupOrder(Qt::SortOrder order); Qt::SortOrder groupOrder() const; /** @@ -293,7 +293,7 @@ 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, bool resortItems = true); + virtual void onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); /** * Is invoked if the sort role has been changed by KItemModelBase::setSortRole(). Allows @@ -304,7 +304,7 @@ protected: * 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); + virtual void onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool regroupItems = true); /** * Is invoked if the sort order has been changed by KItemModelBase::setSortOrder(). Allows @@ -314,7 +314,7 @@ protected: * 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); + virtual void onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); private: bool m_groupedSorting; From 9167b30a358cf3232a97b5e9ab297804470086c7 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 22 Jun 2024 13:13:03 +0300 Subject: [PATCH 15/21] Reverted an incorrect change --- src/kitemviews/kfileitemmodel.cpp | 10 ++++++---- src/kitemviews/kfileitemmodel.h | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 9464c7e09b..960847ea7f 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -979,6 +979,7 @@ void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder pre { Q_UNUSED(current) Q_UNUSED(previous) + resortAllItems(); } void KFileItemModel::onGroupRoleChanged(const QByteArray ¤t, const QByteArray &previous, bool resortItems) @@ -1008,6 +1009,7 @@ void KFileItemModel::onGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder pr { Q_UNUSED(current) Q_UNUSED(previous) + resortAllItems(); } void KFileItemModel::loadSortingSettings() @@ -1032,7 +1034,7 @@ void KFileItemModel::loadSortingSettings() // Workaround for bug https://bugreports.qt.io/browse/QTBUG-69361 // Force the clean state of QCollator in single thread to avoid thread safety problems in sort m_collator.compare(QString(), QString()); - m_dirSizeMode = ContentDisplaySettings::directorySizeMode(); + ContentDisplaySettings::self(); } void KFileItemModel::resortAllItems() @@ -2100,7 +2102,7 @@ bool KFileItemModel::lessThan(const ItemData *a, const ItemData *b, const QColla return true; } } - if (m_sortDirsFirst || (m_dirSizeMode == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { + if (m_sortDirsFirst || (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && m_sortRole == SizeRole)) { const bool isDirA = a->item.isDir(); const bool isDirB = b->item.isDir(); if (isDirA && !isDirB) { @@ -2154,7 +2156,7 @@ int KFileItemModel::sortRoleCompare(const ItemData *a, const ItemData *b, const break; case SizeRole: { - if (m_dirSizeMode == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) { + if (ContentDisplaySettings::directorySizeMode() == ContentDisplaySettings::EnumDirectorySizeMode::ContentCount && itemA.isDir()) { // folders first then // items A and B are folders thanks to lessThan checks auto valueA = a->values.value("count"); @@ -2512,7 +2514,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item groupInfo.comparable = -1; // None if (!item.isNull() && item.isDir()) { - if (m_dirSizeMode != ContentDisplaySettings::EnumDirectorySizeMode::ContentSize) { + if (ContentDisplaySettings::directorySizeMode() != ContentDisplaySettings::EnumDirectorySizeMode::ContentSize) { groupInfo.comparable = 0; // Folders } else { fileSize = itemData->values.value("size").toULongLong(); diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index edc77457ba..feb90d4a89 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -580,7 +580,6 @@ private: bool m_naturalSorting; bool m_sortDirsFirst; bool m_sortHiddenLast; - int m_dirSizeMode; RoleType m_sortRole; RoleType m_groupRole; From f631747a0cb04e47c677d344df06fa7011a16860 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 22 Jun 2024 13:39:24 +0300 Subject: [PATCH 16/21] Corrected description of applySortRole() --- src/kitemviews/kfileitemmodelrolesupdater.cpp | 1 - src/kitemviews/kfileitemmodelrolesupdater.h | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 0012e99c1c..3828f0979c 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -1218,7 +1218,6 @@ void KFileItemModelRolesUpdater::applySortRole(int index) QHash data; const KFileItem item = m_model->fileItem(index); - // Despite the name, this handles both sorting and grouping, as they happen at the same time (resorting items). if (m_model->sortRole() == "type" || m_model->groupRole() == "type") { if (!item.isMimeTypeKnown()) { item.determineMimeType(); diff --git a/src/kitemviews/kfileitemmodelrolesupdater.h b/src/kitemviews/kfileitemmodelrolesupdater.h index aa9ca5fc0e..df3a942262 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.h +++ b/src/kitemviews/kfileitemmodelrolesupdater.h @@ -315,6 +315,8 @@ private: /** * Resolves the sort role of the item and applies it to the model. + * Despite the name, this handles both sorting and grouping, as + * regrouping never happens without resorting at the same time. */ void applySortRole(int index); From 040122c1f5ba2ae38b5180c4683cc13f4376d266 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 22 Jun 2024 10:39:50 +0000 Subject: [PATCH 17/21] Apply 1 suggestion(s) to 1 file(s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Méven Car --- src/kitemviews/kfileitemmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 960847ea7f..071e60df72 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2828,7 +2828,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::ratingRoleGroup(const ItemData *it if (withString) { // Dolphin does not currently use string representation of star rating // as stars are rendered as graphics in group headers. - groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated ") + QString::number(groupInfo.comparable); + groupInfo.text = i18nc("@item:intext Rated N (stars)", "Rated %i", QString::number(groupInfo.comparable)); } return groupInfo; } From f1da070fd5845dbbdd5e20addffafe8c9929b3c9 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 22 Jun 2024 15:12:38 +0300 Subject: [PATCH 18/21] Implemented grouping-related slots to match sorting in some obscure places. --- src/kitemviews/kfileitemmodel.cpp | 9 +++++ src/kitemviews/kfileitemmodel.h | 7 ++++ src/kitemviews/kitemlistview.cpp | 22 +++++++++++ src/kitemviews/kitemlistview.h | 7 +++- src/settings/viewpropertiesdialog.cpp | 52 +++++++++++++++++++++++++- src/settings/viewpropertiesdialog.h | 4 ++ src/views/dolphinviewactionhandler.cpp | 15 ++------ 7 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 960847ea7f..b7598401b5 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -946,6 +946,15 @@ QList KFileItemModel::rolesInformation() return rolesInfo; } +QList KFileItemModel::extraGroupingInformation() +{ + static QList rolesInfo{ + {QByteArray("none"), kli18nc("@label", "No grouping").toString(), nullptr, nullptr, false, false}, + {QByteArray("followSort"), kli18nc("@label", "Follow sorting").toString(), nullptr, nullptr, false, false} + }; + return rolesInfo; +} + void KFileItemModel::onGroupedSortingChanged(bool current) { Q_UNUSED(current) diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index feb90d4a89..001c847012 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -211,6 +211,13 @@ public: */ static QList rolesInformation(); + /** + * @return Provides static information for all available grouping + * behaviors supported by KFileItemModel but not directly + * mapped to roles of KFileItemModel. + */ + static QList extraGroupingInformation(); + /** set to true to hide application/x-trash files */ void setShowTrashMime(bool show); diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 52cd49bfba..45f5851bf5 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1481,6 +1481,26 @@ void KItemListView::slotSortRoleChanged(const QByteArray ¤t, const QByteAr } } +void KItemListView::slotGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) +{ + Q_UNUSED(current) + Q_UNUSED(previous) + if (m_grouped) { + updateVisibleGroupHeaders(); + doLayout(NoAnimation); + } +} + +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) { Q_UNUSED(previous) @@ -1743,6 +1763,8 @@ 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::groupOrderChanged, this, &KItemListView::slotGroupOrderChanged); + disconnect(m_model, &KItemModelBase::groupRoleChanged, this, &KItemListView::slotGroupRoleChanged); m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count())); } diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 8812eb8cc4..04d48bd477 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -430,6 +430,8 @@ 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 slotGroupOrderChanged(Qt::SortOrder current, Qt::SortOrder 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); @@ -552,8 +554,9 @@ private: void recycleGroupHeaderForWidget(KItemListWidget *widget); /** - * Helper method for slotGroupedSortingChanged(), slotSortOrderChanged() - * and slotSortRoleChanged(): Iterates through all visible items and updates + * Helper method for slotGroupedSortingChanged(), slotSortOrderChanged(), + * slotSortRoleChanged(), slotGroupOrderChanged() and slotGroupRoleChanged(): + * Iterates through all visible items and updates * the group-header widgets. */ void updateVisibleGroupHeaders(); diff --git a/src/settings/viewpropertiesdialog.cpp b/src/settings/viewpropertiesdialog.cpp index 37c3d539b3..a13b0b117b 100644 --- a/src/settings/viewpropertiesdialog.cpp +++ b/src/settings/viewpropertiesdialog.cpp @@ -44,6 +44,8 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView *dolphinView) , m_viewMode(nullptr) , m_sortOrder(nullptr) , m_sorting(nullptr) + , m_groupOrder(nullptr) + , m_grouping(nullptr) , m_sortFoldersFirst(nullptr) , m_sortHiddenLast(nullptr) , m_previewsShown(nullptr) @@ -67,7 +69,7 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView *dolphinView) // Otherwise the dialog won't resize when we collapse the KCollapsibleGroupBox. layout->setSizeConstraint(QLayout::SetFixedSize); - // create 'Properties' group containing view mode, sorting, sort order and show hidden files + // create 'Properties' group containing view mode, sorting/grouping, sort/group order and show hidden files m_viewMode = new QComboBox(); m_viewMode->addItem(QIcon::fromTheme(QStringLiteral("view-list-icons")), i18nc("@item:inlistbox", "Icons"), DolphinView::IconsView); m_viewMode->addItem(QIcon::fromTheme(QStringLiteral("view-list-details")), i18nc("@item:inlistbox", "Compact"), DolphinView::CompactView); @@ -83,6 +85,16 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView *dolphinView) m_sorting->addItem(info.translation, info.role); } + m_groupOrder = new QComboBox(); + m_groupOrder->addItem(i18nc("@item:inlistbox Group", "Ascending")); + m_groupOrder->addItem(i18nc("@item:inlistbox Group", "Descending")); + + m_grouping = new QComboBox(); + const QList combinedGroupingInfo = rolesInfo + KFileItemModel::extraGroupingInformation(); + for (const KFileItemModel::RoleInfo &info : combinedGroupingInfo) { + m_grouping->addItem(info.translation, info.role); + } + m_sortFoldersFirst = new QCheckBox(i18nc("@option:check", "Show folders first")); m_sortHiddenLast = new QCheckBox(i18nc("@option:check", "Show hidden files last")); m_previewsShown = new QCheckBox(i18nc("@option:check", "Show preview")); @@ -139,8 +151,14 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView *dolphinView) sortingLayout->addWidget(m_sortOrder); sortingLayout->addWidget(m_sorting); + QHBoxLayout *groupingLayout = new QHBoxLayout(); + groupingLayout->setContentsMargins(0, 0, 0, 0); + groupingLayout->addWidget(m_groupOrder); + groupingLayout->addWidget(m_grouping); + layout->addRow(i18nc("@label:listbox", "View mode:"), m_viewMode); layout->addRow(i18nc("@label:listbox", "Sorting:"), sortingLayout); + layout->addRow(i18nc("@label:listbox", "Grouping:"), groupingLayout); layout->addItem(new QSpacerItem(0, Dolphin::VERTICAL_SPACER_HEIGHT, QSizePolicy::Fixed, QSizePolicy::Fixed)); @@ -153,6 +171,8 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView *dolphinView) connect(m_viewMode, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotViewModeChanged); connect(m_sorting, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotSortingChanged); connect(m_sortOrder, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotSortOrderChanged); + connect(m_grouping, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotGroupingChanged); + connect(m_groupOrder, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotGroupOrderChanged); connect(m_sortFoldersFirst, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotSortFoldersFirstChanged); connect(m_sortHiddenLast, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotSortHiddenLastChanged); connect(m_previewsShown, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotShowPreviewChanged); @@ -259,6 +279,20 @@ void ViewPropertiesDialog::slotSortOrderChanged(int index) markAsDirty(true); } +void ViewPropertiesDialog::slotGroupingChanged(int index) +{ + const QByteArray role = m_grouping->itemData(index).toByteArray(); + m_viewProps->setGroupRole(role); + markAsDirty(true); +} + +void ViewPropertiesDialog::slotGroupOrderChanged(int index) +{ + const Qt::SortOrder groupOrder = (index == 0) ? Qt::AscendingOrder : Qt::DescendingOrder; + m_viewProps->setGroupOrder(groupOrder); + markAsDirty(true); +} + void ViewPropertiesDialog::slotGroupedSortingChanged() { m_viewProps->setGroupedSorting(m_showInGroups->isChecked()); @@ -377,6 +411,8 @@ void ViewPropertiesDialog::applyViewProperties() m_dolphinView->setViewMode(m_viewProps->viewMode()); m_dolphinView->setSortRole(m_viewProps->sortRole()); m_dolphinView->setSortOrder(m_viewProps->sortOrder()); + m_dolphinView->setGroupRole(m_viewProps->groupRole()); + m_dolphinView->setGroupOrder(m_viewProps->groupOrder()); m_dolphinView->setSortFoldersFirst(m_viewProps->sortFoldersFirst()); m_dolphinView->setSortHiddenLast(m_viewProps->sortHiddenLast()); m_dolphinView->setGroupedSorting(m_viewProps->groupedSorting()); @@ -423,6 +459,20 @@ void ViewPropertiesDialog::loadSettings() m_sortFoldersFirst->setChecked(m_viewProps->sortFoldersFirst()); m_sortHiddenLast->setChecked(m_viewProps->sortHiddenLast()); + // Load group order and sorting + const int groupOrderIndex = (m_viewProps->groupOrder() == Qt::AscendingOrder) ? 0 : 1; + m_groupOrder->setCurrentIndex(groupOrderIndex); + + const QList combinedGroupingInfo = rolesInfo + KFileItemModel::extraGroupingInformation(); + int groupRoleIndex = 0; + for (int i = 0; i < combinedGroupingInfo.count(); ++i) { + if (combinedGroupingInfo[i].role == m_viewProps->groupRole()) { + groupRoleIndex = i; + break; + } + } + m_grouping->setCurrentIndex(groupRoleIndex); + // Load show preview, show in groups and show hidden files settings m_previewsShown->setChecked(m_viewProps->previewsShown()); m_showInGroups->setChecked(m_viewProps->groupedSorting()); diff --git a/src/settings/viewpropertiesdialog.h b/src/settings/viewpropertiesdialog.h index d1f056fbbf..49536dcc7b 100644 --- a/src/settings/viewpropertiesdialog.h +++ b/src/settings/viewpropertiesdialog.h @@ -44,6 +44,8 @@ private Q_SLOTS: void slotViewModeChanged(int index); void slotSortingChanged(int index); void slotSortOrderChanged(int index); + void slotGroupingChanged(int index); + void slotGroupOrderChanged(int index); void slotGroupedSortingChanged(); void slotSortFoldersFirstChanged(); void slotSortHiddenLastChanged(); @@ -67,6 +69,8 @@ private: QComboBox *m_viewMode; QComboBox *m_sortOrder; QComboBox *m_sorting; + QComboBox *m_groupOrder; + QComboBox *m_grouping; QCheckBox *m_sortFoldersFirst; QCheckBox *m_sortHiddenLast; QCheckBox *m_previewsShown; diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index ccf4ca1194..815b0f63e6 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -302,18 +302,6 @@ void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *ac // View -> Group By QActionGroup *groupByActionGroup = createFileItemRolesActionGroup(QStringLiteral("group_by_")); - KToggleAction *groupAsNone = m_actionCollection->add(QStringLiteral("group_none")); - groupAsNone->setData("none"); - groupAsNone->setActionGroup(groupByActionGroup); - groupAsNone->setText(i18nc("@label", "No grouping")); - m_groupByActions.insert("none", groupAsNone); - - KToggleAction *groupAsFollowSort = m_actionCollection->add(QStringLiteral("group_followSort")); - groupAsFollowSort->setData("followSort"); - groupAsFollowSort->setActionGroup(groupByActionGroup); - groupAsFollowSort->setText(i18nc("@label", "Follow sorting")); - m_groupByActions.insert("followSort", groupAsFollowSort); - KActionMenu *groupByActionMenu = m_actionCollection->add(QStringLiteral("group")); groupByActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); groupByActionMenu->setText(i18nc("@action:inmenu View", "Group By")); @@ -416,6 +404,9 @@ QActionGroup *DolphinViewActionHandler::createFileItemRolesActionGroup(const QSt #endif QList rolesInfo = KFileItemModel::rolesInformation(); + if (isGroupGroup) { + rolesInfo += KFileItemModel::extraGroupingInformation(); + } for (const KFileItemModel::RoleInfo &info : rolesInfo) { if (!isSortGroup && !isGroupGroup && info.role == "text") { From b500c27e11acd6a6de4aab507f6df438b6d2f580 Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sat, 22 Jun 2024 15:49:07 +0300 Subject: [PATCH 19/21] Fixed empty files being erroneously grouped together with folders in "By size" grouping --- src/kitemviews/kfileitemmodel.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 2133eef79e..e7afa5288d 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -2513,8 +2513,6 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *itemData, bool withString) const { - static ItemGroupInfo oldGroupInfo; - static KIO::filesize_t oldFileSize; ItemGroupInfo groupInfo; KIO::filesize_t fileSize; @@ -2529,9 +2527,6 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item fileSize = itemData->values.value("size").toULongLong(); } } - if (fileSize == oldFileSize) { - return oldGroupInfo; - } if (groupInfo.comparable < 0) { if (fileSize < 5 * 1024 * 1024) { // < 5 MB groupInfo.comparable = 1; // Small @@ -2546,8 +2541,6 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item char const *groupNames[] = {"Folders", "Small", "Medium", "Big"}; groupInfo.text = i18nc("@title:group Size", groupNames[groupInfo.comparable]); } - oldFileSize = fileSize; - oldGroupInfo = groupInfo; return groupInfo; } From 9663bfbc23b8a73dade97a520783f101664fcb1d Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Sun, 23 Jun 2024 10:24:09 +0300 Subject: [PATCH 20/21] "No grouping" chosen as the default grouping setting for first startup. --- src/settings/dolphin_directoryviewpropertysettings.kcfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/dolphin_directoryviewpropertysettings.kcfg b/src/settings/dolphin_directoryviewpropertysettings.kcfg index 4cc522b562..7f34f70932 100644 --- a/src/settings/dolphin_directoryviewpropertysettings.kcfg +++ b/src/settings/dolphin_directoryviewpropertysettings.kcfg @@ -55,7 +55,7 @@ This option defines which attribute (text, size, date, etc.) grouping is performed on. - + none From 2b6d8aac0b889fd7740dcb36f3c05b87dd763c8d Mon Sep 17 00:00:00 2001 From: Zakhar Afonin Date: Mon, 24 Jun 2024 01:19:31 +0300 Subject: [PATCH 21/21] "Grouped sorting" defaults to true. Grouping is still disabled by default because of "none" as default group role. --- src/kitemviews/kfileitemmodel.cpp | 16 +++++++++++++--- src/kitemviews/kitemmodelbase.cpp | 4 ++-- src/tests/kfileitemmodeltest.cpp | 6 +++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index e7afa5288d..5b7b781a8f 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -39,6 +39,7 @@ KFileItemModel::KFileItemModel(QObject *parent) , m_sortDirsFirst(true) , m_sortHiddenLast(false) , m_sortRole(NameRole) + , m_groupRole(NoRole) , m_sortingProgressPercent(-1) , m_roles() , m_itemData() @@ -2439,6 +2440,7 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *itemData, bool withString) const { + static bool oldWithString; static ItemGroupInfo oldGroupInfo; static QChar oldFirstChar; ItemGroupInfo groupInfo; @@ -2450,8 +2452,8 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item // Use the first character of the name as group indication firstChar = name.at(0).toUpper(); - - if (firstChar == oldFirstChar) { + + if (firstChar == oldFirstChar && withString == oldWithString) { return oldGroupInfo; } if (firstChar == QLatin1Char('~') && name.length() > 1) { @@ -2506,6 +2508,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::nameRoleGroup(const ItemData *item } groupInfo.comparable = (int)'.'; } + oldWithString = withString; oldFirstChar = firstChar; oldGroupInfo = groupInfo; return groupInfo; @@ -2547,6 +2550,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::sizeRoleGroup(const ItemData *item KFileItemModel::ItemGroupInfo KFileItemModel::timeRoleGroup(const std::function &fileTimeCb, const ItemData *itemData, bool withString) const { + static bool oldWithString; static ItemGroupInfo oldGroupInfo; static QDate oldFileDate; ItemGroupInfo groupInfo; @@ -2556,6 +2560,9 @@ KFileItemModel::timeRoleGroup(const std::function & const QDate fileDate = fileTime.date(); const int daysDistance = fileDate.daysTo(currentDate); + if (fileDate == oldFileDate && withString == oldWithString) { + return oldGroupInfo; + } // Simplified grouping algorithm, preserving dates // but not taking "pretty printing" into account if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { @@ -2759,6 +2766,7 @@ KFileItemModel::timeRoleGroup(const std::function & } } } + oldWithString = withString; oldFileDate = fileDate; oldGroupInfo = groupInfo; return groupInfo; @@ -2766,13 +2774,14 @@ KFileItemModel::timeRoleGroup(const std::function & KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData *itemData, bool withString) const { + static bool oldWithString; static ItemGroupInfo oldGroupInfo; static QFileDevice::Permissions oldPermissions; ItemGroupInfo groupInfo; const QFileInfo info(itemData->item.url().toLocalFile()); const QFileDevice::Permissions permissions = info.permissions(); - if (permissions == oldPermissions) { + if (permissions == oldPermissions && withString == oldWithString) { return oldGroupInfo; } groupInfo.comparable = (int)permissions; @@ -2818,6 +2827,7 @@ KFileItemModel::ItemGroupInfo KFileItemModel::permissionRoleGroup(const ItemData others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.length() - 2); groupInfo.text = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others); } + oldWithString = withString; oldPermissions = permissions; oldGroupInfo = groupInfo; return groupInfo; diff --git a/src/kitemviews/kitemmodelbase.cpp b/src/kitemviews/kitemmodelbase.cpp index 2f8e1e4a30..17716f57f5 100644 --- a/src/kitemviews/kitemmodelbase.cpp +++ b/src/kitemviews/kitemmodelbase.cpp @@ -10,7 +10,7 @@ KItemModelBase::KItemModelBase(QObject *parent) : QObject(parent) - , m_groupedSorting(false) + , m_groupedSorting(true) , m_sortRole() , m_sortOrder(Qt::AscendingOrder) , m_groupRole() @@ -20,7 +20,7 @@ KItemModelBase::KItemModelBase(QObject *parent) KItemModelBase::KItemModelBase(const QByteArray &sortRole, const QByteArray &groupRole, QObject *parent) : QObject(parent) - , m_groupedSorting(false) + , m_groupedSorting(true) , m_sortRole(sortRole) , m_sortOrder(Qt::AscendingOrder) , m_groupRole(groupRole) diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index fad825dc01..ea05f840b7 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -164,7 +164,7 @@ void KFileItemModelTest::testDefaultSortRole() void KFileItemModelTest::testDefaultGroupedSorting() { - QCOMPARE(m_model->groupedSorting(), false); + QCOMPARE(m_model->groupedSorting(), true); } void KFileItemModelTest::testNewItems() @@ -2007,6 +2007,7 @@ void KFileItemModelTest::testNameRoleGroups() m_testDir->createFiles({"b.txt", "c.txt", "d.txt", "e.txt"}); m_model->setGroupedSorting(true); + m_model->setGroupRole("text"); m_model->loadDirectory(m_testDir->url()); QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(itemsInModel(), @@ -2093,6 +2094,8 @@ void KFileItemModelTest::testNameRoleGroupsWithExpandedItems() m_testDir->createFiles({"a/b.txt", "a/c.txt", "d/e.txt", "d/f.txt"}); m_model->setGroupedSorting(true); + m_model->setGroupRole("text"); + m_model->loadDirectory(m_testDir->url()); QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(itemsInModel(), @@ -2102,6 +2105,7 @@ void KFileItemModelTest::testNameRoleGroupsWithExpandedItems() QList> expectedGroups; expectedGroups << QPair(0, QLatin1String("A")); expectedGroups << QPair(1, QLatin1String("D")); + QCOMPARE(m_model->groups(), expectedGroups); // Verify that expanding "a" and "d" will not change the groups (except for the index of "D").