┌   ┐
54
└   ┘

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