┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews
diff options
context:
space:
mode:
Diffstat (limited to 'src/kitemviews')
-rw-r--r--src/kitemviews/kfileitemlistwidget.cpp6
-rw-r--r--src/kitemviews/kfileitemmodel.cpp262
-rw-r--r--src/kitemviews/kfileitemmodel.h21
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.cpp23
-rw-r--r--src/kitemviews/kitemlistcontroller.cpp299
-rw-r--r--src/kitemviews/kitemlistcontroller.h6
-rw-r--r--src/kitemviews/kitemlistgroupheader.cpp10
-rw-r--r--src/kitemviews/kitemlistgroupheader.h1
-rw-r--r--src/kitemviews/kitemlistheader.cpp16
-rw-r--r--src/kitemviews/kitemlistheader.h9
-rw-r--r--src/kitemviews/kitemlistview.cpp71
-rw-r--r--src/kitemviews/kitemlistview.h40
-rw-r--r--src/kitemviews/kitemlistviewaccessible.cpp5
-rw-r--r--src/kitemviews/kitemlistwidget.cpp32
-rw-r--r--src/kitemviews/kitemlistwidget.h13
-rw-r--r--src/kitemviews/kstandarditem.cpp129
-rw-r--r--src/kitemviews/kstandarditem.h81
-rw-r--r--src/kitemviews/kstandarditemlistgroupheader.cpp17
-rw-r--r--src/kitemviews/kstandarditemlistgroupheader.h2
-rw-r--r--src/kitemviews/kstandarditemlistview.cpp4
-rw-r--r--src/kitemviews/kstandarditemlistwidget.cpp73
-rw-r--r--src/kitemviews/kstandarditemlistwidget.h17
-rw-r--r--src/kitemviews/kstandarditemmodel.cpp225
-rw-r--r--src/kitemviews/kstandarditemmodel.h101
-rw-r--r--src/kitemviews/private/kbaloorolesprovider.cpp14
-rw-r--r--src/kitemviews/private/kitemlistheaderwidget.cpp134
-rw-r--r--src/kitemviews/private/kitemlistheaderwidget.h15
-rw-r--r--src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp2
-rw-r--r--src/kitemviews/private/kitemlistsizehintresolver.cpp25
-rw-r--r--src/kitemviews/private/kitemlistsizehintresolver.h3
-rw-r--r--src/kitemviews/private/kitemlistviewlayouter.cpp270
31 files changed, 983 insertions, 943 deletions
diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp
index 587603ab3..4198df027 100644
--- a/src/kitemviews/kfileitemlistwidget.cpp
+++ b/src/kitemviews/kfileitemlistwidget.cpp
@@ -94,6 +94,11 @@ QString KFileItemListWidgetInformant::roleText(const QByteArray& role,
if (dateTime.isValid()) {
text = formatDate(dateTime);
}
+ } else if (role == "dimensions") {
+ const auto dimensions = roleValue.toSize();
+ if (dimensions.isValid()) {
+ text = i18nc("width x height", "%1 x %2", dimensions.width(), dimensions.height());
+ }
} else {
text = KStandardItemListWidgetInformant::roleText(role, values);
}
@@ -109,7 +114,6 @@ QFont KFileItemListWidgetInformant::customizedFontForLinks(const QFont& baseFont
return font;
}
-
KFileItemListWidget::KFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) :
KStandardItemListWidget(informant, parent)
{
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp
index d12355100..23afb087a 100644
--- a/src/kitemviews/kfileitemmodel.cpp
+++ b/src/kitemviews/kfileitemmodel.cpp
@@ -16,6 +16,7 @@
#include <KDirLister>
#include <KIO/Job>
#include <KLocalizedString>
+#include <KLazyLocalizedString>
#include <KUrlMimeData>
#include <QElapsedTimer>
@@ -25,6 +26,8 @@
#include <QWidget>
#include <QRecursiveMutex>
#include <QIcon>
+#include <algorithm>
+#include <klazylocalizedstring.h>
Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
@@ -64,15 +67,15 @@ KFileItemModel::KFileItemModel(QObject* parent) :
}
connect(m_dirLister, &KCoreDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
- connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled);
+ connect(m_dirLister, &KCoreDirLister::canceled, this, &KFileItemModel::slotCanceled);
connect(m_dirLister, &KCoreDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded);
connect(m_dirLister, &KCoreDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted);
connect(m_dirLister, &KCoreDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems);
- connect(m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, &KFileItemModel::slotClear);
+ connect(m_dirLister, &KCoreDirLister::clear, this, &KFileItemModel::slotClear);
connect(m_dirLister, &KCoreDirLister::infoMessage, this, &KFileItemModel::infoMessage);
connect(m_dirLister, &KCoreDirLister::jobError, this, &KFileItemModel::slotListerError);
connect(m_dirLister, &KCoreDirLister::percent, this, &KFileItemModel::directoryLoadingProgress);
- connect(m_dirLister, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection);
+ connect(m_dirLister, &KCoreDirLister::redirection, this, &KFileItemModel::directoryRedirection);
connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted);
// Apply default roles that should be determined
@@ -149,6 +152,18 @@ QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
ItemData* data = m_itemData.at(index);
if (data->values.isEmpty()) {
data->values = retrieveData(data->item, data->parent);
+ } else if (data->values.count() <= 2 && data->values.value("isExpanded").toBool()) {
+ // Special case dealt by slotRefreshItems(), avoid losing the "isExpanded" and "expandedParentsCount" state when refreshing
+ // slotRefreshItems() makes sure folders keep the "isExpanded" and "expandedParentsCount" while clearing the remaining values
+ // so this special request of different behavior can be identified here.
+ bool hasExpandedParentsCount = false;
+ const int expandedParentsCount = data->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount);
+
+ data->values = retrieveData(data->item, data->parent);
+ data->values.insert("isExpanded", true);
+ if (hasExpandedParentsCount) {
+ data->values.insert("expandedParentsCount", expandedParentsCount);
+ }
}
return data->values;
@@ -312,10 +327,10 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const
int count = 0;
const RoleInfoMap* map = rolesInfoMap(count);
for (int i = 0; i < count; ++i) {
- if (!map[i].roleTranslation) {
+ if (map[i].roleTranslation.isEmpty()) {
continue;
}
- description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation));
+ description.insert(map[i].role, map[i].roleTranslation.toString());
}
}
@@ -712,7 +727,7 @@ void KFileItemModel::applyFilters()
ItemData *itemData = m_itemData.at(index);
if (m_filter.matches(itemData->item)
- || (itemShownBelow && itemShownBelow->parent == itemData && itemData->values.value("isExpanded").toBool())) {
+ || (itemShownBelow && itemShownBelow->parent == itemData)) {
// We could've entered here for two reasons:
// 1. This item passes the filter itself
// 2. This is an expanded folder that doesn't pass the filter but sees a filter-passing child just below
@@ -815,9 +830,9 @@ QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
if (map[i].roleType != NoRole) {
RoleInfo info;
info.role = map[i].role;
- info.translation = i18nc(map[i].roleTranslationContext, map[i].roleTranslation);
- if (map[i].groupTranslation) {
- info.group = i18nc(map[i].groupTranslationContext, map[i].groupTranslation);
+ 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
@@ -1010,12 +1025,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
{
Q_ASSERT(!items.isEmpty());
- QUrl parentUrl;
- if (m_expandedDirs.contains(directoryUrl)) {
- parentUrl = m_expandedDirs.value(directoryUrl);
- } else {
- parentUrl = directoryUrl.adjusted(QUrl::StripTrailingSlash);
- }
+ const QUrl parentUrl = m_expandedDirs.value(directoryUrl, directoryUrl.adjusted(QUrl::StripTrailingSlash));
if (m_requestRole[ExpandedParentsCountRole]) {
// If the expanding of items is enabled, the call
@@ -1049,16 +1059,28 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
if (!m_filter.hasSetFilters()) {
m_pendingItemsToInsert.append(itemDataList);
} else {
+ QSet<ItemData *> parentsToEnsureVisible;
+
// The name or type filter is active. Hide filtered items
// before inserting them into the model and remember
// the filtered items in m_filteredItems.
for (ItemData* itemData : itemDataList) {
if (m_filter.matches(itemData->item)) {
m_pendingItemsToInsert.append(itemData);
+ if (itemData->parent) {
+ parentsToEnsureVisible.insert(itemData->parent);
+ }
} else {
m_filteredItems.insert(itemData->item, itemData);
}
}
+
+ // Entire parental chains must be shown
+ for (ItemData *parent : parentsToEnsureVisible) {
+ for (; parent && m_filteredItems.remove(parent->item); parent = parent->parent) {
+ m_pendingItemsToInsert.append(parent);
+ }
+ }
}
if (!m_maximumUpdateIntervalTimer->isActive()) {
@@ -1070,6 +1092,42 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
Q_EMIT fileItemsChanged({KFileItem(directoryUrl)});
}
+int KFileItemModel::filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible)
+{
+ int filteredParentsCount = 0;
+ // The childless parents not yet removed will always be right above the start of a removed range.
+ // We iterate backwards to ensure the deepest folders are processed before their parents
+ for (int i = removedItemRanges.size() - 1; i >= 0; i--) {
+ KItemRange itemRange = removedItemRanges.at(i);
+ const ItemData *const firstInRange = m_itemData.at(itemRange.index);
+ ItemData *itemAbove = itemRange.index - 1 >= 0 ? m_itemData.at(itemRange.index - 1) : nullptr;
+ const ItemData *const itemBelow = itemRange.index + itemRange.count < m_itemData.count() ? m_itemData.at(itemRange.index + itemRange.count) : nullptr;
+
+ if (itemAbove && firstInRange->parent == itemAbove && !m_filter.matches(itemAbove->item) && (!itemBelow || itemBelow->parent != itemAbove)
+ && !parentsToEnsureVisible.contains(itemAbove)) {
+ // The item above exists, is the parent, doesn't pass the filter, does not belong to parentsToEnsureVisible
+ // and this deleted range covers all of its descendents, so none will be left.
+ m_filteredItems.insert(itemAbove->item, itemAbove);
+ // This range's starting index will be extended to include the parent above:
+ --itemRange.index;
+ ++itemRange.count;
+ ++filteredParentsCount;
+ KItemRange previousRange = i > 0 ? removedItemRanges.at(i - 1) : KItemRange();
+ // We must check if this caused the range to touch the previous range, if that's the case they shall be merged
+ if (i > 0 && previousRange.index + previousRange.count == itemRange.index) {
+ previousRange.count += itemRange.count;
+ removedItemRanges.replace(i - 1, previousRange);
+ removedItemRanges.removeAt(i);
+ } else {
+ removedItemRanges.replace(i, itemRange);
+ // We must revisit this range in the next iteration since its starting index changed
+ ++i;
+ }
+ }
+ }
+ return filteredParentsCount;
+}
+
void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
{
dispatchPendingItemsToInsert();
@@ -1119,9 +1177,17 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
indexesToRemove = indexesToRemoveWithChildren;
}
- const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
+ KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
removeFilteredChildren(itemRanges);
- removeItems(itemRanges, DeleteItemData);
+
+ // This call will update itemRanges to include the childless parents that have been filtered.
+ const int filteredParentsCount = filterChildlessParents(itemRanges);
+
+ // If any childless parents were filtered, then itemRanges got updated and now contains items that were really deleted
+ // mixed with expanded folders that are just being filtered out.
+ // If that's the case, we pass 'DeleteItemDataIfUnfiltered' as a hint
+ // so removeItems() will check m_filteredItems to differentiate which is which.
+ removeItems(itemRanges, filteredParentsCount > 0 ? DeleteItemDataIfUnfiltered : DeleteItemData);
Q_EMIT fileItemsChanged(dirsChanged);
}
@@ -1149,6 +1215,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
QList<ItemData*> newVisibleItems;
QListIterator<QPair<KFileItem, KFileItem> > it(items);
+
while (it.hasNext()) {
const QPair<KFileItem, KFileItem>& itemPair = it.next();
const KFileItem& oldItem = itemPair.first;
@@ -1172,8 +1239,14 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
}
m_items.remove(oldItem.url());
- if (newItemMatchesFilter) {
- m_items.insert(newItem.url(), indexForItem);
+ // We must maintain m_items consistent with m_itemData for now, this very loop is using it.
+ // We leave it to be cleared by removeItems() later, when m_itemData actually gets updated.
+ m_items.insert(newItem.url(), indexForItem);
+ if (newItemMatchesFilter
+ || (itemData->values.value("isExpanded").toBool()
+ && (indexForItem + 1 < m_itemData.count() && m_itemData.at(indexForItem + 1)->parent == itemData))) {
+ // We are lenient with expanded folders that originally had visible children.
+ // If they become childless now they will be caught by filterChildlessParents()
changedFiles.append(newItem);
indexes.append(indexForItem);
} else {
@@ -1184,12 +1257,23 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
// Check if 'oldItem' is one of the filtered items.
QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
if (it != m_filteredItems.end()) {
- ItemData* itemData = it.value();
+ ItemData *const itemData = it.value();
itemData->item = newItem;
// The data stored in 'values' might have changed. Therefore, we clear
// 'values' and re-populate it the next time it is requested via data(int).
+ // Before clearing, we must remember if it was expanded and the expanded parents count,
+ // otherwise these states would be lost. The data() method will deal with this special case.
+ const bool isExpanded = itemData->values.value("isExpanded").toBool();
+ bool hasExpandedParentsCount = false;
+ const int expandedParentsCount = itemData->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount);
itemData->values.clear();
+ if (isExpanded) {
+ itemData->values.insert("isExpanded", true);
+ if (hasExpandedParentsCount) {
+ itemData->values.insert("expandedParentsCount", expandedParentsCount);
+ }
+ }
m_filteredItems.erase(it);
if (newItemMatchesFilter) {
@@ -1201,21 +1285,66 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
}
}
- // Hide items, previously visible that should get hidden
- const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+ std::sort(newFilteredIndexes.begin(), newFilteredIndexes.end());
+
+ // We must keep track of parents of new visible items since they must be shown no matter what
+ // They will be considered "immune" to filterChildlessParents()
+ QSet<ItemData *> parentsToEnsureVisible;
+
+ for (ItemData *item : newVisibleItems) {
+ for (ItemData *parent = item->parent; parent && !parentsToEnsureVisible.contains(parent); parent = parent->parent) {
+ parentsToEnsureVisible.insert(parent);
+ }
+ }
+ for (ItemData *parent : parentsToEnsureVisible) {
+ // We make sure they are all unfiltered.
+ if (m_filteredItems.remove(parent->item)) {
+ // If it is being unfiltered now, we mark it to be inserted by appending it to newVisibleItems
+ newVisibleItems.append(parent);
+ // It could be in newFilteredIndexes, we must remove it if it's there:
+ const int parentIndex = index(parent->item);
+ if (parentIndex >= 0) {
+ QVector<int>::iterator it = std::lower_bound(newFilteredIndexes.begin(), newFilteredIndexes.end(), parentIndex);
+ if (it != newFilteredIndexes.end() && *it == parentIndex) {
+ newFilteredIndexes.erase(it);
+ }
+ }
+ }
+ }
+
+ KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+
+ // This call will update itemRanges to include the childless parents that have been filtered.
+ filterChildlessParents(removedRanges, parentsToEnsureVisible);
+
removeItems(removedRanges, KeepItemData);
// Show previously hidden items that should get visible
insertItems(newVisibleItems);
+ // Final step: we will emit 'itemsChanged' and 'fileItemsChanged' signals and trigger the asynchronous re-sorting logic.
+
// If the changed items have been created recently, they might not be in m_items yet.
// In that case, the list 'indexes' might be empty.
if (indexes.isEmpty()) {
return;
}
+ if (newVisibleItems.count() > 0 || removedRanges.count() > 0) {
+ // The original indexes have changed and are now worthless since items were removed and/or inserted.
+ indexes.clear();
+ // m_items is not yet rebuilt at this point, so we use our own means to resolve the new indexes.
+ const QSet<const KFileItem> changedFilesSet(changedFiles.cbegin(), changedFiles.cend());
+ for (int i = 0; i < m_itemData.count(); i++) {
+ if (changedFilesSet.contains(m_itemData.at(i)->item)) {
+ indexes.append(i);
+ }
+ }
+ } else {
+ std::sort(indexes.begin(), indexes.end());
+ }
+
// Extract the item-ranges out of the changed indexes
- std::sort(indexes.begin(), indexes.end());
const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
@@ -1380,7 +1509,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
removedItemsCount += range.count;
for (int index = range.index; index < range.index + range.count; ++index) {
- if (behavior == DeleteItemData) {
+ if (behavior == DeleteItemData || (behavior == DeleteItemDataIfUnfiltered && !m_filteredItems.contains(m_itemData.at(index)->item))) {
delete m_itemData.at(index);
}
@@ -1424,8 +1553,9 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
determineMimeTypes(items, 200);
}
+ // We search for the parent in m_itemData and then in m_filteredItems if necessary
const int parentIndex = index(parentUrl);
- ItemData* parentItem = parentIndex < 0 ? nullptr : m_itemData.at(parentIndex);
+ ItemData *parentItem = parentIndex < 0 ? m_filteredItems.value(KFileItem(parentUrl), nullptr) : m_itemData.at(parentIndex);
QList<ItemData*> itemDataList;
itemDataList.reserve(items.count());
@@ -1959,6 +2089,19 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
break;
}
+ case DimensionsRole: {
+ const QByteArray role = roleForType(m_sortRole);
+ const QSize dimensionsA = a->values.value(role).toSize();
+ const QSize dimensionsB = b->values.value(role).toSize();
+
+ if (dimensionsA.width() == dimensionsB.width()) {
+ result = dimensionsA.height() - dimensionsB.height();
+ } else {
+ result = dimensionsA.width() - dimensionsB.width();
+ }
+ break;
+ }
+
default: {
const QByteArray role = roleForType(m_sortRole);
const QString roleValueA = a->values.value(role).toString();
@@ -2451,40 +2594,41 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
{
static const RoleInfoMap rolesInfoMap[] = {
- // | role | roleType | role translation | group translation | requires Baloo | requires indexer
- { nullptr, NoRole, nullptr, nullptr, nullptr, nullptr, false, false },
- { "text", NameRole, I18NC_NOOP("@label", "Name"), nullptr, nullptr, false, false },
- { "size", SizeRole, I18NC_NOOP("@label", "Size"), nullptr, nullptr, false, false },
- { "modificationtime", ModificationTimeRole, I18NC_NOOP("@label", "Modified"), nullptr, nullptr, false, false },
- { "creationtime", CreationTimeRole, I18NC_NOOP("@label", "Created"), nullptr, nullptr, false, false },
- { "accesstime", AccessTimeRole, I18NC_NOOP("@label", "Accessed"), nullptr, nullptr, false, false },
- { "type", TypeRole, I18NC_NOOP("@label", "Type"), nullptr, nullptr, false, false },
- { "rating", RatingRole, I18NC_NOOP("@label", "Rating"), nullptr, nullptr, true, false },
- { "tags", TagsRole, I18NC_NOOP("@label", "Tags"), nullptr, nullptr, true, false },
- { "comment", CommentRole, I18NC_NOOP("@label", "Comment"), nullptr, nullptr, true, false },
- { "title", TitleRole, I18NC_NOOP("@label", "Title"), I18NC_NOOP("@label", "Document"), true, true },
- { "wordCount", WordCountRole, I18NC_NOOP("@label", "Word Count"), I18NC_NOOP("@label", "Document"), true, true },
- { "lineCount", LineCountRole, I18NC_NOOP("@label", "Line Count"), I18NC_NOOP("@label", "Document"), true, true },
- { "imageDateTime", ImageDateTimeRole, I18NC_NOOP("@label", "Date Photographed"), I18NC_NOOP("@label", "Image"), true, true },
- { "width", WidthRole, I18NC_NOOP("@label", "Width"), I18NC_NOOP("@label", "Image"), true, true },
- { "height", HeightRole, I18NC_NOOP("@label", "Height"), I18NC_NOOP("@label", "Image"), true, true },
- { "orientation", OrientationRole, I18NC_NOOP("@label", "Orientation"), I18NC_NOOP("@label", "Image"), true, true },
- { "artist", ArtistRole, I18NC_NOOP("@label", "Artist"), I18NC_NOOP("@label", "Audio"), true, true },
- { "genre", GenreRole, I18NC_NOOP("@label", "Genre"), I18NC_NOOP("@label", "Audio"), true, true },
- { "album", AlbumRole, I18NC_NOOP("@label", "Album"), I18NC_NOOP("@label", "Audio"), true, true },
- { "duration", DurationRole, I18NC_NOOP("@label", "Duration"), I18NC_NOOP("@label", "Audio"), true, true },
- { "bitrate", BitrateRole, I18NC_NOOP("@label", "Bitrate"), I18NC_NOOP("@label", "Audio"), true, true },
- { "track", TrackRole, I18NC_NOOP("@label", "Track"), I18NC_NOOP("@label", "Audio"), true, true },
- { "releaseYear", ReleaseYearRole, I18NC_NOOP("@label", "Release Year"), I18NC_NOOP("@label", "Audio"), true, true },
- { "aspectRatio", AspectRatioRole, I18NC_NOOP("@label", "Aspect Ratio"), I18NC_NOOP("@label", "Video"), true, true },
- { "frameRate", FrameRateRole, I18NC_NOOP("@label", "Frame Rate"), I18NC_NOOP("@label", "Video"), true, true },
- { "path", PathRole, I18NC_NOOP("@label", "Path"), I18NC_NOOP("@label", "Other"), false, false },
- { "deletiontime", DeletionTimeRole, I18NC_NOOP("@label", "Deletion Time"), I18NC_NOOP("@label", "Other"), false, false },
- { "destination", DestinationRole, I18NC_NOOP("@label", "Link Destination"), I18NC_NOOP("@label", "Other"), false, false },
- { "originUrl", OriginUrlRole, I18NC_NOOP("@label", "Downloaded From"), I18NC_NOOP("@label", "Other"), true, false },
- { "permissions", PermissionsRole, I18NC_NOOP("@label", "Permissions"), I18NC_NOOP("@label", "Other"), false, false },
- { "owner", OwnerRole, I18NC_NOOP("@label", "Owner"), I18NC_NOOP("@label", "Other"), false, false },
- { "group", GroupRole, I18NC_NOOP("@label", "User Group"), I18NC_NOOP("@label", "Other"), false, false },
+ // | role | roleType | role translation | group translation | requires Baloo | requires indexer
+ { nullptr, NoRole, KLazyLocalizedString(), KLazyLocalizedString(), false, false },
+ { "text", NameRole, kli18nc("@label", "Name"), KLazyLocalizedString(), false, false },
+ { "size", SizeRole, kli18nc("@label", "Size"), KLazyLocalizedString(), false, false },
+ { "modificationtime", ModificationTimeRole, kli18nc("@label", "Modified"), KLazyLocalizedString(), false, false },
+ { "creationtime", CreationTimeRole, kli18nc("@label", "Created"), KLazyLocalizedString(), false, false },
+ { "accesstime", AccessTimeRole, kli18nc("@label", "Accessed"), KLazyLocalizedString(), false, false },
+ { "type", TypeRole, kli18nc("@label", "Type"), KLazyLocalizedString(), false, false },
+ { "rating", RatingRole, kli18nc("@label", "Rating"), KLazyLocalizedString(), true, false },
+ { "tags", TagsRole, kli18nc("@label", "Tags"), KLazyLocalizedString(), true, false },
+ { "comment", CommentRole, kli18nc("@label", "Comment"), KLazyLocalizedString(), true, false },
+ { "title", TitleRole, kli18nc("@label", "Title"), kli18nc("@label", "Document"), true, true },
+ { "wordCount", WordCountRole, kli18nc("@label", "Word Count"), kli18nc("@label", "Document"), true, true },
+ { "lineCount", LineCountRole, kli18nc("@label", "Line Count"), kli18nc("@label", "Document"), true, true },
+ { "imageDateTime", ImageDateTimeRole, kli18nc("@label", "Date Photographed"), kli18nc("@label", "Image"), true, true },
+ { "dimensions", DimensionsRole, kli18nc("@label width x height", "Dimensions"), kli18nc("@label", "Image"), true, true },
+ { "width", WidthRole, kli18nc("@label", "Width"), kli18nc("@label", "Image"), true, true },
+ { "height", HeightRole, kli18nc("@label", "Height"), kli18nc("@label", "Image"), true, true },
+ { "orientation", OrientationRole, kli18nc("@label", "Orientation"), kli18nc("@label", "Image"), true, true },
+ { "artist", ArtistRole, kli18nc("@label", "Artist"), kli18nc("@label", "Audio"), true, true },
+ { "genre", GenreRole, kli18nc("@label", "Genre"), kli18nc("@label", "Audio"), true, true },
+ { "album", AlbumRole, kli18nc("@label", "Album"), kli18nc("@label", "Audio"), true, true },
+ { "duration", DurationRole, kli18nc("@label", "Duration"), kli18nc("@label", "Audio"), true, true },
+ { "bitrate", BitrateRole, kli18nc("@label", "Bitrate"), kli18nc("@label", "Audio"), true, true },
+ { "track", TrackRole, kli18nc("@label", "Track"), kli18nc("@label", "Audio"), true, true },
+ { "releaseYear", ReleaseYearRole, kli18nc("@label", "Release Year"), kli18nc("@label", "Audio"), true, true },
+ { "aspectRatio", AspectRatioRole, kli18nc("@label", "Aspect Ratio"), kli18nc("@label", "Video"), true, true },
+ { "frameRate", FrameRateRole, kli18nc("@label", "Frame Rate"), kli18nc("@label", "Video"), true, true },
+ { "path", PathRole, kli18nc("@label", "Path"), kli18nc("@label", "Other"), false, false },
+ { "deletiontime", DeletionTimeRole, kli18nc("@label", "Deletion Time"), kli18nc("@label", "Other"), false, false },
+ { "destination", DestinationRole, kli18nc("@label", "Link Destination"), kli18nc("@label", "Other"), false, false },
+ { "originUrl", OriginUrlRole, kli18nc("@label", "Downloaded From"), kli18nc("@label", "Other"), true, false },
+ { "permissions", PermissionsRole, kli18nc("@label", "Permissions"), kli18nc("@label", "Other"), false, false },
+ { "owner", OwnerRole, kli18nc("@label", "Owner"), kli18nc("@label", "Other"), false, false },
+ { "group", GroupRole, kli18nc("@label", "User Group"), kli18nc("@label", "Other"), false, false },
};
count = sizeof(rolesInfoMap) / sizeof(RoleInfoMap);
diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h
index 161f6a0e2..cc39a0084 100644
--- a/src/kitemviews/kfileitemmodel.h
+++ b/src/kitemviews/kfileitemmodel.h
@@ -12,6 +12,7 @@
#include "kitemviews/private/kfileitemmodelfilter.h"
#include <KFileItem>
+#include <KLazyLocalizedString>
#include <QCollator>
#include <QHash>
@@ -291,7 +292,7 @@ private:
NoRole, NameRole, SizeRole, ModificationTimeRole, CreationTimeRole, AccessTimeRole, PermissionsRole, OwnerRole,
GroupRole, TypeRole, DestinationRole, PathRole, DeletionTimeRole,
// User visible roles available with Baloo:
- CommentRole, TagsRole, RatingRole, WidthRole, HeightRole, ImageDateTimeRole, OrientationRole,
+ CommentRole, TagsRole, RatingRole, DimensionsRole, WidthRole, HeightRole, ImageDateTimeRole, OrientationRole,
WordCountRole, TitleRole, LineCountRole, ArtistRole, GenreRole, AlbumRole, DurationRole, TrackRole, ReleaseYearRole,
BitrateRole, OriginUrlRole, AspectRatioRole, FrameRateRole,
// Non-visible roles:
@@ -309,7 +310,8 @@ private:
enum RemoveItemsBehavior {
KeepItemData,
- DeleteItemData
+ DeleteItemData,
+ DeleteItemDataIfUnfiltered
};
void insertItems(QList<ItemData*>& items);
@@ -439,10 +441,8 @@ private:
{
const char* const role;
const RoleType roleType;
- const char* const roleTranslationContext;
- const char* const roleTranslation;
- const char* const groupTranslationContext;
- const char* const groupTranslation;
+ const KLazyLocalizedString roleTranslation;
+ const KLazyLocalizedString groupTranslation;
const bool requiresBaloo;
const bool requiresIndexer;
};
@@ -469,6 +469,15 @@ private:
*/
bool isConsistent() const;
+ /**
+ * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
+ * Will update the removedItemRanges arguments to include the parents that have been filtered.
+ * @returns the number of parents that have been filtered.
+ * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
+ * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
+ */
+ int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
+
private:
KDirLister *m_dirLister = nullptr;
diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp
index 978f5df6e..49657a9b1 100644
--- a/src/kitemviews/kfileitemmodelrolesupdater.cpp
+++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp
@@ -18,7 +18,7 @@
#include <KIconLoader>
#include <KJobWidgets>
#include <KOverlayIconPlugin>
-#include <KPluginLoader>
+#include <KPluginMetaData>
#include <KSharedConfig>
#ifdef HAVE_BALOO
@@ -30,6 +30,7 @@
#include <QApplication>
#include <QIcon>
#include <QPainter>
+#include <QPluginLoader>
#include <QElapsedTimer>
#include <QTimer>
@@ -120,15 +121,16 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result,
this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived);
- const auto plugins = KPluginLoader::instantiatePlugins(QStringLiteral("kf5/overlayicon"), nullptr, qApp);
- for (QObject *it : plugins) {
- auto plugin = qobject_cast<KOverlayIconPlugin*>(it);
+ const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/overlayicon"));
+ for (const KPluginMetaData &data : plugins) {
+ auto instance = QPluginLoader(data.fileName()).instance();
+ auto plugin = qobject_cast<KOverlayIconPlugin *>(instance);
if (plugin) {
m_overlayIconsPlugin.append(plugin);
connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged);
} else {
// not our/valid plugin, so delete the created object
- it->deleteLater();
+ delete instance;
}
}
}
@@ -1405,10 +1407,19 @@ QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
(2 * m_maximumVisibleItems)));
// Add visible items.
+ // Resolve files first, their previews are quicker.
+ QList<int> visibleDirs;
for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
- result.append(i);
+ const KFileItem item = m_model->fileItem(i);
+ if (item.isDir()) {
+ visibleDirs.append(i);
+ } else {
+ result.append(i);
+ }
}
+ result.append(visibleDirs);
+
// We need a reasonable upper limit for number of items to resolve after
// and before the visible range. m_maximumVisibleItems can be quite large
// when using Compact View.
diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp
index 8687872ee..80c28f25c 100644
--- a/src/kitemviews/kitemlistcontroller.cpp
+++ b/src/kitemviews/kitemlistcontroller.cpp
@@ -46,7 +46,7 @@ KItemListController::KItemListController(KItemModelBase* model, KItemListView* v
m_view(nullptr),
m_selectionManager(new KItemListSelectionManager(this)),
m_keyboardManager(new KItemListKeyboardSearchManager(this)),
- m_pressedIndex(-1),
+ m_pressedIndex(std::nullopt),
m_pressedMousePos(),
m_autoActivationTimer(nullptr),
m_swipeGesture(Qt::CustomGesture),
@@ -574,16 +574,16 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
return false;
}
- if (m_pressedIndex >= 0) {
+ if (m_pressedIndex.has_value() && !m_view->rubberBand()->isActive()) {
// Check whether a dragging should be started
if (event->buttons() & Qt::LeftButton) {
const QPointF pos = transform.map(event->pos());
if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
- if (!m_selectionManager->isSelected(m_pressedIndex)) {
+ if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
// Always assure that the dragged item gets selected. Usually this is already
// done on the mouse-press event, but when using the selection-toggle on a
// selected item the dragged item is not selected yet.
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+ m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
} else {
// A selected item has been clicked to drag all selected items
// -> the selection should not be cleared when the mouse button is released.
@@ -599,12 +599,12 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
QPointF endPos = transform.map(event->pos());
// Update the current item.
- const int newCurrent = m_view->itemAt(endPos);
- if (newCurrent >= 0) {
+ const std::optional<int> newCurrent = m_view->itemAt(endPos);
+ if (newCurrent.has_value()) {
// It's expected that the new current index is also the new anchor (bug 163451).
m_selectionManager->endAnchoredSelection();
- m_selectionManager->setCurrentItem(newCurrent);
- m_selectionManager->beginAnchoredSelection(newCurrent);
+ m_selectionManager->setCurrentItem(newCurrent.value());
+ m_selectionManager->beginAnchoredSelection(newCurrent.value());
}
if (m_view->scrollOrientation() == Qt::Vertical) {
@@ -642,7 +642,7 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
return false;
}
- Q_EMIT mouseButtonReleased(m_pressedIndex, event->buttons());
+ Q_EMIT mouseButtonReleased(m_pressedIndex.value_or(-1), event->buttons());
return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
}
@@ -650,21 +650,21 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
{
const QPointF pos = transform.map(event->pos());
- const int index = m_view->itemAt(pos);
+ const std::optional<int> index = m_view->itemAt(pos);
// Expand item if desired - See Bug 295573
if (m_mouseDoubleClickAction != ActivateItemOnly) {
- if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
- const bool expanded = m_model->isExpanded(index);
- m_model->setExpanded(index, !expanded);
+ if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index.value_or(-1))) {
+ const bool expanded = m_model->isExpanded(index.value());
+ m_model->setExpanded(index.value(), !expanded);
}
}
if (event->button() & Qt::RightButton) {
m_selectionManager->clearSelection();
- if (index >= 0) {
- m_selectionManager->setSelected(index);
- Q_EMIT itemContextMenuRequested(index, event->screenPos());
+ if (index.has_value()) {
+ m_selectionManager->setSelected(index.value());
+ Q_EMIT itemContextMenuRequested(index.value(), event->screenPos());
} else {
const QRectF headerBounds = m_view->headerBoundaries();
if (headerBounds.contains(event->pos())) {
@@ -678,9 +678,9 @@ bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event,
bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) &&
(event->button() & Qt::LeftButton) &&
- index >= 0 && index < m_model->count();
+ index.has_value() && index.value() < m_model->count();
if (emitItemActivated) {
- Q_EMIT itemActivated(index);
+ Q_EMIT itemActivated(index.value());
}
return false;
}
@@ -805,7 +805,7 @@ bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QT
Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
} else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
// Something has been dropped on an item or on an empty part of the view.
- Q_EMIT itemDropEvent(m_view->itemAt(pos), event);
+ Q_EMIT itemDropEvent(m_view->itemAt(pos).value_or(-1), event);
}
QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
@@ -828,27 +828,76 @@ bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const
return false;
}
- KItemListWidget* oldHoveredWidget = hoveredWidget();
- const QPointF pos = transform.map(event->pos());
- KItemListWidget* newHoveredWidget = widgetForPos(pos);
+ // We identify the widget whose expansionArea had been hovered before this hoverMoveEvent() triggered.
+ // we can't use hoveredWidget() here (it handles the icon+text rect, not the expansion rect)
+ // like hoveredWidget(), we find the hovered widget for the expansion rect
+ const auto visibleItemListWidgets = m_view->visibleItemListWidgets();
+ const auto oldHoveredExpansionWidgetIterator = std::find_if(visibleItemListWidgets.begin(), visibleItemListWidgets.end(), [](auto &widget) {
+ return widget->expansionAreaHovered();
+ });
+ const auto oldHoveredExpansionWidget = oldHoveredExpansionWidgetIterator == visibleItemListWidgets.end() ?
+ std::nullopt : std::make_optional(*oldHoveredExpansionWidgetIterator);
- if (oldHoveredWidget != newHoveredWidget) {
- if (oldHoveredWidget) {
+ const auto unhoverOldHoveredWidget = [&]() {
+ if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget) {
+ // handle the text+icon one
oldHoveredWidget->setHovered(false);
Q_EMIT itemUnhovered(oldHoveredWidget->index());
}
+ };
- if (newHoveredWidget) {
- newHoveredWidget->setHovered(true);
- const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
- newHoveredWidget->setHoverPosition(mappedPos);
- Q_EMIT itemHovered(newHoveredWidget->index());
+ const auto unhoverOldExpansionWidget = [&]() {
+ if (oldHoveredExpansionWidget) {
+ // then the expansion toggle
+ (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
}
- } else if (oldHoveredWidget) {
- const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
- oldHoveredWidget->setHoverPosition(mappedPos);
- }
+ };
+
+ const QPointF pos = transform.map(event->pos());
+ if (KItemListWidget *newHoveredWidget = widgetForPos(pos); newHoveredWidget) {
+ // something got hovered, work out which part and set hover for the appropriate widget
+ const auto mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
+ const bool isOnExpansionToggle = newHoveredWidget->expansionToggleRect().contains(mappedPos);
+
+ if (isOnExpansionToggle) {
+ // make sure we unhover the old one first if old!=new
+ if (oldHoveredExpansionWidget && *oldHoveredExpansionWidget != newHoveredWidget) {
+ (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
+ }
+ // we also unhover any old icon+text hovers, in case the mouse movement from icon+text to expansion toggle is too fast (i.e. newHoveredWidget is never null between the transition)
+ unhoverOldHoveredWidget();
+
+ newHoveredWidget->setExpansionAreaHovered(true);
+ } else {
+ // make sure we unhover the old one first if old!=new
+ if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget && oldHoveredWidget != newHoveredWidget) {
+ oldHoveredWidget->setHovered(false);
+ Q_EMIT itemUnhovered(oldHoveredWidget->index());
+ }
+ // we also unhover any old expansion toggle hovers, in case the mouse movement from expansion toggle to icon+text is too fast (i.e. newHoveredWidget is never null between the transition)
+ unhoverOldExpansionWidget();
+
+ const bool isOverIconAndText = newHoveredWidget->iconRect().contains(mappedPos) || newHoveredWidget->textRect().contains(mappedPos);
+ const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1;
+
+ if (hasMultipleSelection && !isOverIconAndText) {
+ // In case we have multiple selections, clicking on any row will deselect the selection.
+ // So, as a visual cue for signalling that clicking anywhere won't select, but clear current highlights,
+ // we disable hover of the *row*(i.e. blank space to the right of the icon+text)
+
+ // (no-op in this branch for masked hover)
+ } else {
+ newHoveredWidget->setHovered(true);
+ newHoveredWidget->setHoverPosition(mappedPos);
+ Q_EMIT itemHovered(newHoveredWidget->index());
+ }
+ }
+ } else {
+ // unhover any currently hovered expansion and text+icon widgets
+ unhoverOldHoveredWidget();
+ unhoverOldExpansionWidget();
+ }
return false;
}
@@ -998,10 +1047,10 @@ void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTrans
m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
m_pressedIndex = m_view->itemAt(m_pressedMousePos);
- if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
+ if (m_pressedIndex.has_value() && !m_selectionManager->isSelected(m_pressedIndex.value())) {
m_selectionManager->clearSelection();
- m_selectionManager->setSelected(m_pressedIndex);
- } else if (m_pressedIndex == -1) {
+ m_selectionManager->setSelected(m_pressedIndex.value());
+ } else if (!m_pressedIndex.has_value()) {
m_selectionManager->clearSelection();
startRubberBand();
}
@@ -1064,9 +1113,9 @@ void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform&
Q_EMIT scrollerStop();
if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
- Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::BackButton);
+ Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::BackButton);
} else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
- Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
+ Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::ForwardButton);
} else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
Q_EMIT swipeUp();
}
@@ -1085,7 +1134,7 @@ void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTra
if (twoTap->state() == Qt::GestureStarted) {
m_pressedMousePos = transform.map(twoTap->pos());
m_pressedIndex = m_view->itemAt(m_pressedMousePos);
- if (m_pressedIndex >= 0) {
+ if (m_pressedIndex.has_value()) {
onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
}
@@ -1303,10 +1352,7 @@ KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
const auto widgets = m_view->visibleItemListWidgets();
for (KItemListWidget* widget : widgets) {
const QPointF mappedPos = widget->mapFromItem(m_view, pos);
-
- const bool hovered = widget->contains(mappedPos) &&
- !widget->expansionToggleRect().contains(mappedPos);
- if (hovered) {
+ if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
return widget;
}
}
@@ -1419,7 +1465,7 @@ void KItemListController::updateExtendedSelectionRegion()
bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
{
- Q_EMIT mouseButtonPressed(m_pressedIndex, buttons);
+ Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), buttons);
if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
// Do not select items when clicking the back/forward buttons, see
@@ -1427,26 +1473,27 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
return true;
}
- if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
+ if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos)) {
m_selectionManager->endAnchoredSelection();
- m_selectionManager->setCurrentItem(m_pressedIndex);
- m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+ m_selectionManager->setCurrentItem(m_pressedIndex.value());
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
return true;
}
- m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
+ m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos);
if (m_selectionTogglePressed) {
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+ m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
// The previous anchored selection has been finished already in
// KItemListSelectionManager::setSelected(). We can safely change
// the current item and start a new anchored selection now.
- m_selectionManager->setCurrentItem(m_pressedIndex);
- m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+ m_selectionManager->setCurrentItem(m_pressedIndex.value());
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
return true;
}
const bool shiftPressed = modifiers & Qt::ShiftModifier;
const bool controlPressed = modifiers & Qt::ControlModifier;
+ const bool rightClick = buttons & Qt::RightButton;
// The previous selection is cleared if either
// 1. The selection mode is SingleSelection, or
@@ -1456,11 +1503,35 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
// - start dragging multiple items, or
// - open the context menu and perform an action for all selected items.
const bool shiftOrControlPressed = shiftPressed || controlPressed;
- const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
+ const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
const bool clearSelection = m_selectionBehavior == SingleSelection ||
(!shiftOrControlPressed && !pressedItemAlreadySelected);
+
+
+ // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
if (clearSelection) {
+ const int selectedItemsCount = m_selectionManager->selectedItems().count();
m_selectionManager->clearSelection();
+ // clear and bail when we got an existing multi-selection
+ if (selectedItemsCount > 1 && m_pressedIndex.has_value()) {
+ const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
+ const auto mappedPos = row->mapFromItem(m_view, pos);
+ if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) {
+ // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
+ // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
+ // or we just keep going for double-click activation
+ if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
+ return true; // event handled, don't create rubber band
+ }
+ } else {
+ // we're not inside the text/icon rect, as we've already cleared the selection
+ // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
+ m_pressedIndex.reset();
+ // we don't stop event propagation and proceed to create a rubber band and let onRelease
+ // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
+ return false;
+ }
+ }
} else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
// The user might want to start dragging multiple items, but if he clicks the item
// in order to trigger it instead, the other selected items must be deselected.
@@ -1469,8 +1540,8 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
// clear the selection in mouseReleaseEvent(), unless the items are dragged.
m_clearSelectionIfItemsAreNotDragged = true;
- if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
- Q_EMIT selectedItemTextPressed(m_pressedIndex);
+ if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), m_pressedMousePos)) {
+ Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
}
}
@@ -1479,7 +1550,14 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
m_selectionManager->endAnchoredSelection();
}
- if (buttons & Qt::RightButton) {
+ if (rightClick) {
+
+ // Do header hit check and short circuit before commencing any state changing effects
+ if (m_view->headerBoundaries().contains(pos)) {
+ Q_EMIT headerContextMenuRequested(screenPos);
+ return true;
+ }
+
// Stop rubber band from persisting after right-clicks
KItemListRubberBand* rubberBand = m_view->rubberBand();
if (rubberBand->isActive()) {
@@ -1489,25 +1567,37 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
}
}
- if (m_pressedIndex >= 0) {
- m_selectionManager->setCurrentItem(m_pressedIndex);
+ if (m_pressedIndex.has_value()) {
+ m_selectionManager->setCurrentItem(m_pressedIndex.value());
+ const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect
+ const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
+ // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
+ // createRubberBand here tells us whether to return true or false.
+ bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
+
+ if (rightClick && hitTargetIsRowEmptyRegion) {
+ // we got a right click outside the text rect, default to action on the current url and not the pressed item
+ Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
+ return true;
+ }
switch (m_selectionBehavior) {
case NoSelection:
break;
case SingleSelection:
- m_selectionManager->setSelected(m_pressedIndex);
+ m_selectionManager->setSelected(m_pressedIndex.value());
break;
case MultiSelection:
if (controlPressed && !shiftPressed) {
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
- m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+ m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
+ createRubberBand = false; // multi selection, don't propagate any further
} else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
// Select the pressed item and start a new anchored selection
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
- m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+ m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
}
break;
@@ -1516,20 +1606,16 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
break;
}
- if (buttons & Qt::RightButton) {
- Q_EMIT itemContextMenuRequested(m_pressedIndex, screenPos);
+ if (rightClick) {
+ Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
}
-
- return true;
+ return !createRubberBand;
}
- if (buttons & Qt::RightButton) {
- const QRectF headerBounds = m_view->headerBoundaries();
- if (headerBounds.contains(pos)) {
- Q_EMIT headerContextMenuRequested(screenPos);
- } else {
- Q_EMIT viewContextMenuRequested(screenPos);
- }
+ if (rightClick) {
+ // header right click handling would have been done before this so just normal context
+ // menu here is fine
+ Q_EMIT viewContextMenuRequested(screenPos);
return true;
}
@@ -1538,14 +1624,14 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
{
- const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
+ const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos);
if (isAboveSelectionToggle) {
m_selectionTogglePressed = false;
return true;
}
if (!isAboveSelectionToggle && m_selectionTogglePressed) {
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+ m_selectionManager->setSelected(m_pressedIndex.value_or(-1), 1, KItemListSelectionManager::Toggle);
m_selectionTogglePressed = false;
return true;
}
@@ -1554,60 +1640,77 @@ bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifi
const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
controlPressed;
+ const std::optional<int> index = m_view->itemAt(pos);
+
KItemListRubberBand* rubberBand = m_view->rubberBand();
+ bool rubberBandRelease = false;
if (rubberBand->isActive()) {
disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
rubberBand->setActive(false);
m_oldSelection.clear();
m_view->setAutoScroll(false);
+ rubberBandRelease = true;
+ // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
+ // then we have a single click on one of the rows
+ if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
+ rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
+ // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
+ // in that case, we don't select anything
+ if (index.has_value() && m_pressedIndex.has_value()) {
+ if (controlPressed && m_selectionBehavior == MultiSelection) {
+ m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
+ } else {
+ m_selectionManager->setSelected(index.value());
+ }
+ if (!m_selectionManager->isAnchoredSelectionActive()) {
+ m_selectionManager->beginAnchoredSelection(index.value());
+ }
+ }
+ }
}
- const int index = m_view->itemAt(pos);
-
- if (index >= 0 && index == m_pressedIndex) {
+ if (index.has_value() && index == m_pressedIndex) {
// The release event is done above the same item as the press event
if (m_clearSelectionIfItemsAreNotDragged) {
// A selected item has been clicked, but no drag operation has been started
// -> clear the rest of the selection.
m_selectionManager->clearSelection();
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
- m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+ m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
}
if (buttons & Qt::LeftButton) {
bool emitItemActivated = true;
- if (m_view->isAboveExpansionToggle(index, pos)) {
- const bool expanded = m_model->isExpanded(index);
- m_model->setExpanded(index, !expanded);
+ if (m_view->isAboveExpansionToggle(index.value(), pos)) {
+ const bool expanded = m_model->isExpanded(index.value());
+ m_model->setExpanded(index.value(), !expanded);
- Q_EMIT itemExpansionToggleClicked(index);
+ Q_EMIT itemExpansionToggleClicked(index.value());
emitItemActivated = false;
- } else if (shiftOrControlPressed) {
- // The mouse click should only update the selection, not trigger the item
+ } else if (shiftOrControlPressed && m_selectionBehavior != SingleSelection) {
+ // The mouse click should only update the selection, not trigger the item, except when
+ // we are in single selection mode
emitItemActivated = false;
- // When Ctrl-clicking an item when in single selection mode
- // i.e. where Ctrl won't change the selection, pretend it was middle clicked
- if (controlPressed && m_selectionBehavior == SingleSelection) {
- Q_EMIT itemMiddleClicked(index);
- }
- } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
- if (touch) {
- emitItemActivated = true;
+ } else {
+ const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
+ if (!singleClickActivation) {
+ emitItemActivated = touch;
} else {
- emitItemActivated = false;
+ // activate on single click only if we didn't come from a rubber band release
+ emitItemActivated = !rubberBandRelease;
}
}
if (emitItemActivated) {
- Q_EMIT itemActivated(index);
+ Q_EMIT itemActivated(index.value());
}
} else if (buttons & Qt::MiddleButton) {
- Q_EMIT itemMiddleClicked(index);
+ Q_EMIT itemMiddleClicked(index.value());
}
}
m_pressedMousePos = QPointF();
- m_pressedIndex = -1;
+ m_pressedIndex = std::nullopt;
m_clearSelectionIfItemsAreNotDragged = false;
return false;
}
diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h
index 24339134e..5fe195e9f 100644
--- a/src/kitemviews/kitemlistcontroller.h
+++ b/src/kitemviews/kitemlistcontroller.h
@@ -9,6 +9,8 @@
#ifndef KITEMLISTCONTROLLER_H
#define KITEMLISTCONTROLLER_H
+#include <optional>
+
#include "dolphin_export.h"
#include "kitemset.h"
@@ -137,7 +139,7 @@ Q_SIGNALS:
* Is emitted if more than one item has been activated by pressing Return/Enter
* when having a selection.
*/
- void itemsActivated(const KItemSet& indexes);
+ void itemsActivated(const KItemSet &indexes);
void itemMiddleClicked(int index);
@@ -329,7 +331,7 @@ private:
KItemListView* m_view;
KItemListSelectionManager* m_selectionManager;
KItemListKeyboardSearchManager* m_keyboardManager;
- int m_pressedIndex;
+ std::optional<int> m_pressedIndex;
QPointF m_pressedMousePos;
QTimer* m_autoActivationTimer;
diff --git a/src/kitemviews/kitemlistgroupheader.cpp b/src/kitemviews/kitemlistgroupheader.cpp
index 80dd94149..f0ff52503 100644
--- a/src/kitemviews/kitemlistgroupheader.cpp
+++ b/src/kitemviews/kitemlistgroupheader.cpp
@@ -161,6 +161,7 @@ void KItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event)
if (event->oldSize().height() != event->newSize().height()) {
m_dirtyCache = true;
}
+ updateSize();
}
void KItemListGroupHeader::updateCache()
@@ -174,6 +175,13 @@ void KItemListGroupHeader::updateCache()
m_separatorColor = mixedColor(c1, c2, 10);
m_roleColor = mixedColor(c1, c2, 60);
+ updateSize();
+
+ m_dirtyCache = false;
+}
+
+void KItemListGroupHeader::updateSize()
+{
const int padding = qMax(1, m_styleOption.padding);
const int horizontalMargin = qMax(2, m_styleOption.horizontalMargin);
@@ -187,7 +195,7 @@ void KItemListGroupHeader::updateCache()
size().width() - 2 * padding - horizontalMargin,
roleHeight);
- m_dirtyCache = false;
+ update();
}
QColor KItemListGroupHeader::mixedColor(const QColor& c1, const QColor& c2, int c1Percent)
diff --git a/src/kitemviews/kitemlistgroupheader.h b/src/kitemviews/kitemlistgroupheader.h
index 4ba469056..48af1e9e0 100644
--- a/src/kitemviews/kitemlistgroupheader.h
+++ b/src/kitemviews/kitemlistgroupheader.h
@@ -94,6 +94,7 @@ protected:
private:
void updateCache();
+ void updateSize();
static QColor mixedColor(const QColor& c1, const QColor& c2, int c1Percent = 50);
diff --git a/src/kitemviews/kitemlistheader.cpp b/src/kitemviews/kitemlistheader.cpp
index dedeb57e3..22e70603b 100644
--- a/src/kitemviews/kitemlistheader.cpp
+++ b/src/kitemviews/kitemlistheader.cpp
@@ -61,6 +61,20 @@ qreal KItemListHeader::preferredColumnWidth(const QByteArray& role) const
return m_headerWidget->preferredColumnWidth(role);
}
+void KItemListHeader::setLeadingPadding(qreal width){
+ if (m_headerWidget->leadingPadding() != width) {
+ m_headerWidget->setLeadingPadding(width);
+ if (m_headerWidget->automaticColumnResizing()) {
+ m_view->applyAutomaticColumnWidths();
+ }
+ m_view->doLayout(KItemListView::NoAnimation);
+ }
+}
+
+qreal KItemListHeader::leadingPadding() const{
+ return m_headerWidget->leadingPadding();
+}
+
KItemListHeader::KItemListHeader(KItemListView* listView) :
QObject(listView),
m_view(listView)
@@ -72,5 +86,7 @@ KItemListHeader::KItemListHeader(KItemListView* listView) :
this, &KItemListHeader::columnWidthChanged);
connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChangeFinished,
this, &KItemListHeader::columnWidthChangeFinished);
+ connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+ this, &KItemListHeader::leadingPaddingChanged);
}
diff --git a/src/kitemviews/kitemlistheader.h b/src/kitemviews/kitemlistheader.h
index 6875a0f5e..e2653e41e 100644
--- a/src/kitemviews/kitemlistheader.h
+++ b/src/kitemviews/kitemlistheader.h
@@ -58,7 +58,16 @@ public:
*/
qreal preferredColumnWidth(const QByteArray& role) const;
+ /**
+ * Sets the width of the column *before* the first column.
+ * This is intended to facilitate an empty region for deselection in the main viewport.
+ */
+ void setLeadingPadding(qreal width);
+ qreal leadingPadding() const;
+
Q_SIGNALS:
+ void leadingPaddingChanged(qreal width);
+
/**
* Is emitted if the width of a column has been adjusted by the user with the mouse
* (no signal is emitted if KItemListHeader::setColumnWidth() is invoked).
diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp
index 5c8c712e8..86583db1e 100644
--- a/src/kitemviews/kitemlistview.cpp
+++ b/src/kitemviews/kitemlistview.cpp
@@ -389,7 +389,7 @@ qreal KItemListView::verticalPageStep() const
return size().height() - headerHeight;
}
-int KItemListView::itemAt(const QPointF& pos) const
+std::optional<int> KItemListView::itemAt(const QPointF& pos) const
{
QHashIterator<int, KItemListWidget*> it(m_visibleItems);
while (it.hasNext()) {
@@ -397,12 +397,12 @@ int KItemListView::itemAt(const QPointF& pos) const
const KItemListWidget* widget = it.value();
const QPointF mappedPos = widget->mapFromItem(this, pos);
- if (widget->contains(mappedPos)) {
+ if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
return it.key();
}
}
- return -1;
+ return std::nullopt;
}
bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
@@ -458,7 +458,7 @@ int KItemListView::lastVisibleIndex() const
return m_layouter->lastVisibleIndex();
}
-void KItemListView::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint) const
+void KItemListView::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint) const
{
widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this);
}
@@ -477,6 +477,32 @@ bool KItemListView::supportsItemExpanding() const
return m_supportsItemExpanding;
}
+void KItemListView::setHighlightEntireRow(bool highlightEntireRow)
+{
+ if (m_highlightEntireRow != highlightEntireRow) {
+ m_highlightEntireRow = highlightEntireRow;
+ onHighlightEntireRowChanged(highlightEntireRow);
+ }
+}
+
+bool KItemListView::highlightEntireRow() const
+{
+ return m_highlightEntireRow;
+}
+
+void KItemListView::setAlternateBackgrounds(bool alternate)
+{
+ if (m_alternateBackgrounds != alternate) {
+ m_alternateBackgrounds = alternate;
+ updateAlternateBackgrounds();
+ }
+}
+
+bool KItemListView::alternateBackgrounds() const
+{
+ return m_alternateBackgrounds;
+}
+
QRectF KItemListView::itemRect(int index) const
{
return m_layouter->itemRect(index);
@@ -495,6 +521,11 @@ QRectF KItemListView::itemContextRect(int index) const
return contextRect;
}
+bool KItemListView::isElided(int index) const
+{
+ return m_sizeHintResolver->isElided(index);
+}
+
void KItemListView::scrollToItem(int index)
{
QRectF viewGeometry = geometry();
@@ -576,6 +607,8 @@ void KItemListView::setHeaderVisible(bool visible)
connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
this, &KItemListView::slotHeaderColumnWidthChanged);
+ connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+ this, &KItemListView::slotLeadingPaddingChanged);
connect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
this, &KItemListView::slotHeaderColumnMoved);
connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
@@ -588,6 +621,8 @@ void KItemListView::setHeaderVisible(bool visible)
} else if (!visible && m_headerWidget->isVisible()) {
disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
this, &KItemListView::slotHeaderColumnWidthChanged);
+ disconnect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+ this, &KItemListView::slotLeadingPaddingChanged);
disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
this, &KItemListView::slotHeaderColumnMoved);
disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
@@ -748,7 +783,7 @@ void KItemListView::setItemSize(const QSizeF& size)
size,
m_layouter->itemMargin());
- const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) &&
+ const bool alternateBackgroundsChanged = m_alternateBackgrounds &&
(( m_itemSize.isEmpty() && !size.isEmpty()) ||
(!m_itemSize.isEmpty() && size.isEmpty()));
@@ -924,6 +959,11 @@ void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, co
Q_UNUSED(previous)
}
+void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow)
+{
+ Q_UNUSED(highlightEntireRow)
+}
+
void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
{
Q_UNUSED(supportsExpanding)
@@ -1516,6 +1556,16 @@ void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role,
doLayout(NoAnimation);
}
+void KItemListView::slotLeadingPaddingChanged(qreal width)
+{
+ Q_UNUSED(width)
+ if (m_headerWidget->automaticColumnResizing()) {
+ applyAutomaticColumnWidths();
+ }
+ applyColumnWidthsFromHeader();
+ doLayout(NoAnimation);
+}
+
void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
int currentIndex,
int previousIndex)
@@ -2220,7 +2270,7 @@ void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget)
bool KItemListView::useAlternateBackgrounds() const
{
- return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
+ return m_alternateBackgrounds && m_itemSize.isEmpty();
}
QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
@@ -2237,11 +2287,11 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
const QString headerText = m_model->roleDescription(visibleRole);
- const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
+ const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2;
widths.insert(visibleRole, headerWidth);
}
- // Calculate the preferred column withs for each item and ignore values
+ // Calculate the preferred column widths for each item and ignore values
// smaller than the width for showing the headline unclipped.
const KItemListWidgetCreatorBase* creator = widgetCreator();
int calculatedItemCount = 0;
@@ -2278,7 +2328,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
void KItemListView::applyColumnWidthsFromHeader()
{
// Apply the new size to the layouter
- const qreal requiredWidth = columnWidthsSum();
+ const qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
m_itemSize.height());
m_layouter->setItemSize(dynamicItemSize);
@@ -2296,6 +2346,7 @@ void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget)
for (const QByteArray& role : qAsConst(m_visibleRoles)) {
widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
}
+ widget->setLeadingPadding(m_headerWidget->leadingPadding());
}
void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges)
@@ -2373,7 +2424,7 @@ void KItemListView::applyAutomaticColumnWidths()
qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
QSizeF dynamicItemSize = m_itemSize;
- qreal requiredWidth = columnWidthsSum();
+ qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
const qreal availableWidth = size().width();
if (requiredWidth < availableWidth) {
// Stretch the first column to use the whole remaining width
diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h
index 760e0a415..228989cc4 100644
--- a/src/kitemviews/kitemlistview.h
+++ b/src/kitemviews/kitemlistview.h
@@ -9,6 +9,8 @@
#ifndef KITEMLISTVIEW_H
#define KITEMLISTVIEW_H
+#include <optional>
+
#include "dolphin_export.h"
#include "kitemviews/kitemliststyleoption.h"
#include "kitemviews/kitemlistwidget.h"
@@ -160,10 +162,10 @@ public:
* @return Index of the item that is below the point \a pos.
* The position is relative to the upper right of
* the visible area. Only (at least partly) visible
- * items are considered. -1 is returned if no item is
- * below the position.
+ * items are considered. std::nullopt is returned if
+ * no item is below the position.
*/
- int itemAt(const QPointF& pos) const;
+ std::optional<int> itemAt(const QPointF& pos) const;
bool isAboveSelectionToggle(int index, const QPointF& pos) const;
bool isAboveExpansionToggle(int index, const QPointF& pos) const;
bool isAboveText(int index, const QPointF& pos) const;
@@ -189,7 +191,7 @@ public:
* @note the logical height (width) is actually the
* width (height) if the scroll orientation is Qt::Vertical!
*/
- void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint) const;
+ void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint) const;
/**
* If set to true, items having child-items can be expanded to show the child-items as
@@ -203,6 +205,12 @@ public:
void setSupportsItemExpanding(bool supportsExpanding);
bool supportsItemExpanding() const;
+ void setHighlightEntireRow(bool highlightEntireRow);
+ bool highlightEntireRow() const;
+
+ void setAlternateBackgrounds(bool alternate);
+ bool alternateBackgrounds() const;
+
/**
* @return The rectangle of the item relative to the top/left of
* the currently visible area (see KItemListView::offset()).
@@ -222,6 +230,12 @@ public:
QRectF itemContextRect(int index) const;
/**
+ * @return Whether or not the name of the file has been elided. At present this will
+ * only ever be true when in icons view.
+ */
+ bool isElided(int index) const;
+
+ /**
* Scrolls to the item with the index \a index so that the item
* will be fully visible.
*/
@@ -366,6 +380,7 @@ protected:
virtual void onScrollOffsetChanged(qreal current, qreal previous);
virtual void onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous);
virtual void onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+ virtual void onHighlightEntireRowChanged(bool highlightEntireRow);
virtual void onSupportsItemExpandingChanged(bool supportsExpanding);
virtual void onTransactionBegin();
@@ -384,6 +399,8 @@ protected:
virtual void updateFont();
virtual void updatePalette();
+ KItemListSizeHintResolver* m_sizeHintResolver;
+
protected Q_SLOTS:
virtual void slotItemsInserted(const KItemRangeList& itemRanges);
virtual void slotItemsRemoved(const KItemRangeList& itemRanges);
@@ -416,6 +433,8 @@ private Q_SLOTS:
qreal currentWidth,
qreal previousWidth);
+ void slotLeadingPaddingChanged(qreal width);
+
/**
* Is invoked if a column has been moved by the user. Applies
* the moved role to the view.
@@ -597,7 +616,7 @@ private:
/**
* Resizes the column-widths of m_headerWidget based on the preferred widths
- * and the vailable view-size.
+ * and the available view-size.
*/
void applyAutomaticColumnWidths();
@@ -697,6 +716,8 @@ private:
private:
bool m_enabledSelectionToggles;
bool m_grouped;
+ bool m_highlightEntireRow;
+ bool m_alternateBackgrounds;
bool m_supportsItemExpanding;
bool m_editingRole;
int m_activeTransactions; // Counter for beginTransaction()/endTransaction()
@@ -723,7 +744,6 @@ private:
QHash<int, Cell> m_visibleCells;
int m_scrollBarExtent;
- KItemListSizeHintResolver* m_sizeHintResolver;
KItemListViewLayouter* m_layouter;
KItemListViewAnimation* m_animation;
@@ -803,7 +823,7 @@ public:
virtual void recycle(KItemListWidget* widget);
- virtual void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
+ virtual void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
virtual qreal preferredRoleColumnWidth(const QByteArray& role,
int index,
@@ -822,7 +842,7 @@ public:
KItemListWidget* create(KItemListView* view) override;
- void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
+ void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
qreal preferredRoleColumnWidth(const QByteArray& role,
int index,
@@ -856,7 +876,7 @@ KItemListWidget* KItemListWidgetCreator<T>::create(KItemListView* view)
}
template<class T>
-void KItemListWidgetCreator<T>::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KItemListWidgetCreator<T>::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
return m_informant->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, view);
}
@@ -873,7 +893,7 @@ qreal KItemListWidgetCreator<T>::preferredRoleColumnWidth(const QByteArray& role
* @brief Base class for creating KItemListGroupHeaders.
*
* It is recommended that applications simply use the KItemListGroupHeaderCreator-template class.
- * For a custom implementation the methods create() and recyle() must be reimplemented.
+ * For a custom implementation the methods create() and recycle() must be reimplemented.
* The intention of the group-header creator is to prevent repetitive and expensive instantiations and
* deletions of KItemListGroupHeaders by recycling existing header instances.
*/
diff --git a/src/kitemviews/kitemlistviewaccessible.cpp b/src/kitemviews/kitemlistviewaccessible.cpp
index ffc088bdb..a1afadff4 100644
--- a/src/kitemviews/kitemlistviewaccessible.cpp
+++ b/src/kitemviews/kitemlistviewaccessible.cpp
@@ -5,7 +5,6 @@
*/
#ifndef QT_NO_ACCESSIBILITY
-
#include "kitemlistviewaccessible.h"
#include "kitemlistcontainer.h"
@@ -202,8 +201,8 @@ QAccessible::State KItemListViewAccessible::state() const
QAccessibleInterface* KItemListViewAccessible::childAt(int x, int y) const
{
const QPointF point = QPointF(x, y);
- int itemIndex = view()->itemAt(view()->mapFromScene(point));
- return child(itemIndex);
+ const std::optional<int> itemIndex = view()->itemAt(view()->mapFromScene(point));
+ return child(itemIndex.value_or(-1));
}
QAccessibleInterface* KItemListViewAccessible::parent() const
diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp
index 69a38432a..79ffee210 100644
--- a/src/kitemviews/kitemlistwidget.cpp
+++ b/src/kitemviews/kitemlistwidget.cpp
@@ -34,6 +34,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant* informant, QGraphicsI
m_selected(false),
m_current(false),
m_hovered(false),
+ m_expansionAreaHovered(false),
m_alternateBackground(false),
m_enabledSelectionToggle(false),
m_data(),
@@ -180,6 +181,18 @@ qreal KItemListWidget::columnWidth(const QByteArray& role) const
return m_columnWidths.value(role);
}
+qreal KItemListWidget::leadingPadding() const {
+ return m_leadingPadding;
+}
+
+void KItemListWidget::setLeadingPadding(qreal width) {
+ if (m_leadingPadding != width){
+ m_leadingPadding = width;
+ leadingPaddingChanged(width);
+ update();
+ }
+}
+
void KItemListWidget::setStyleOption(const KItemListStyleOption& option)
{
if (m_styleOption == option) {
@@ -280,6 +293,20 @@ bool KItemListWidget::isHovered() const
return m_hovered;
}
+void KItemListWidget::setExpansionAreaHovered(bool hovered)
+{
+ if (hovered == m_expansionAreaHovered) {
+ return;
+ }
+ m_expansionAreaHovered = hovered;
+ update();
+}
+
+bool KItemListWidget::expansionAreaHovered() const
+{
+ return m_expansionAreaHovered;
+}
+
void KItemListWidget::setHoverPosition(const QPointF& pos)
{
if (m_selectionToggle) {
@@ -416,6 +443,11 @@ void KItemListWidget::columnWidthChanged(const QByteArray& role,
Q_UNUSED(previous)
}
+void KItemListWidget::leadingPaddingChanged(qreal width)
+{
+ Q_UNUSED(width)
+}
+
void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
const KItemListStyleOption& previous)
{
diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h
index 2de82c6fb..0b82266c4 100644
--- a/src/kitemviews/kitemlistwidget.h
+++ b/src/kitemviews/kitemlistwidget.h
@@ -35,7 +35,7 @@ public:
KItemListWidgetInformant();
virtual ~KItemListWidgetInformant();
- virtual void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
+ virtual void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
virtual qreal preferredRoleColumnWidth(const QByteArray& role,
int index,
@@ -80,6 +80,9 @@ public:
void setColumnWidth(const QByteArray& role, qreal width);
qreal columnWidth(const QByteArray& role) const;
+ void setLeadingPadding(qreal width);
+ qreal leadingPadding() const;
+
void setStyleOption(const KItemListStyleOption& option);
const KItemListStyleOption& styleOption() const;
@@ -94,6 +97,9 @@ public:
void setHovered(bool hovered);
bool isHovered() const;
+ void setExpansionAreaHovered(bool hover);
+ bool expansionAreaHovered() const;
+
void setHoverPosition(const QPointF& pos);
void setAlternateBackground(bool enable);
@@ -182,6 +188,7 @@ protected:
virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>());
virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous);
virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous);
+ virtual void leadingPaddingChanged(qreal width);
virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
virtual void currentChanged(bool current);
virtual void selectedChanged(bool selected);
@@ -190,6 +197,7 @@ protected:
virtual void siblingsInformationChanged(const QBitArray& current, const QBitArray& previous);
virtual void editedRoleChanged(const QByteArray& current, const QByteArray& previous);
void resizeEvent(QGraphicsSceneResizeEvent* event) override;
+ void clearHoverCache();
/**
* Called when the user starts hovering this item.
@@ -225,7 +233,6 @@ private Q_SLOTS:
private:
void initializeSelectionToggle();
void setHoverOpacity(qreal opacity);
- void clearHoverCache();
void drawItemStyleOption(QPainter* painter, QWidget* widget, QStyle::State styleState);
private:
@@ -236,11 +243,13 @@ private:
bool m_selected;
bool m_current;
bool m_hovered;
+ bool m_expansionAreaHovered;
bool m_alternateBackground;
bool m_enabledSelectionToggle;
QHash<QByteArray, QVariant> m_data;
QList<QByteArray> m_visibleRoles;
QHash<QByteArray, qreal> m_columnWidths;
+ qreal m_leadingPadding;
KItemListStyleOption m_styleOption;
QBitArray m_siblingsInfo;
diff --git a/src/kitemviews/kstandarditem.cpp b/src/kitemviews/kstandarditem.cpp
deleted file mode 100644
index fcaa50b9d..000000000
--- a/src/kitemviews/kstandarditem.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <[email protected]>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "kstandarditem.h"
-#include "kstandarditemmodel.h"
-
-KStandardItem::KStandardItem(KStandardItem* parent) :
- QObject(parent),
- m_model(nullptr),
- m_data()
-{
-}
-
-KStandardItem::KStandardItem(const QString& text, KStandardItem* parent) :
- QObject(parent),
- m_model(nullptr),
- m_data()
-{
- setText(text);
-}
-
-KStandardItem::KStandardItem(const QString& icon, const QString& text, KStandardItem* parent) :
- QObject(parent),
- m_model(nullptr),
- m_data()
-{
- setIcon(icon);
- setText(text);
-}
-
-KStandardItem::~KStandardItem()
-{
-}
-
-void KStandardItem::setText(const QString& text)
-{
- setDataValue("text", text);
-}
-
-QString KStandardItem::text() const
-{
- return m_data["text"].toString();
-}
-
-void KStandardItem::setIcon(const QString& icon)
-{
- setDataValue("iconName", icon);
-}
-
-QString KStandardItem::icon() const
-{
- return m_data["iconName"].toString();
-}
-
-void KStandardItem::setIconOverlays(const QStringList& overlays)
-{
- setDataValue("iconOverlays", overlays);
-}
-
-QStringList KStandardItem::iconOverlays() const
-{
- return m_data["iconOverlays"].toStringList();
-}
-
-void KStandardItem::setGroup(const QString& group)
-{
- setDataValue("group", group);
-}
-
-QString KStandardItem::group() const
-{
- return m_data["group"].toString();
-}
-
-void KStandardItem::setDataValue(const QByteArray& role, const QVariant& value)
-{
- const QVariant previous = m_data.value(role);
- if (previous == value) {
- return;
- }
-
- m_data.insert(role, value);
- onDataValueChanged(role, value, previous);
-
- if (m_model) {
- const int index = m_model->index(this);
- QSet<QByteArray> changedRoles;
- changedRoles.insert(role);
- m_model->onItemChanged(index, changedRoles);
- Q_EMIT m_model->itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
- }
-}
-
-QVariant KStandardItem::dataValue(const QByteArray& role) const
-{
- return m_data[role];
-}
-
-void KStandardItem::setData(const QHash<QByteArray, QVariant>& values)
-{
- const QHash<QByteArray, QVariant> previous = m_data;
- m_data = values;
- onDataChanged(values, previous);
-}
-
-QHash<QByteArray, QVariant> KStandardItem::data() const
-{
- return m_data;
-}
-
-void KStandardItem::onDataValueChanged(const QByteArray& role,
- const QVariant& current,
- const QVariant& previous)
-{
- Q_UNUSED(role)
- Q_UNUSED(current)
- Q_UNUSED(previous)
-}
-
-void KStandardItem::onDataChanged(const QHash<QByteArray, QVariant>& current,
- const QHash<QByteArray, QVariant>& previous)
-{
- Q_UNUSED(current)
- Q_UNUSED(previous)
-}
-
diff --git a/src/kitemviews/kstandarditem.h b/src/kitemviews/kstandarditem.h
deleted file mode 100644
index fb64b334d..000000000
--- a/src/kitemviews/kstandarditem.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <[email protected]>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef KSTANDARDITEM_H
-#define KSTANDARDITEM_H
-
-#include "dolphin_export.h"
-
-#include <QByteArray>
-#include <QHash>
-#include <QObject>
-#include <QSet>
-#include <QVariant>
-
-class KStandardItemModel;
-
-/**
- * @brief Represents and item of KStandardItemModel.
- *
- * Provides setter- and getter-methods for the most commonly
- * used roles. It is possible to assign values for custom
- * roles by using setDataValue().
- */
-class DOLPHIN_EXPORT KStandardItem : public QObject
-{
- Q_OBJECT
-public:
- explicit KStandardItem(KStandardItem* parent = nullptr);
- explicit KStandardItem(const QString& text, KStandardItem* parent = nullptr);
- KStandardItem(const QString& icon, const QString& text, KStandardItem* parent = nullptr);
- ~KStandardItem() override;
-
- /**
- * Sets the text for the "text"-role.
- */
- void setText(const QString& text);
- QString text() const;
-
- /**
- * Sets the icon for the "iconName"-role.
- */
- void setIcon(const QString& icon);
- QString icon() const;
-
- void setIconOverlays(const QStringList& overlays);
- QStringList iconOverlays() const;
-
- /**
- * Sets the group for the "group"-role.
- */
- void setGroup(const QString& group);
- QString group() const;
-
- void setDataValue(const QByteArray& role, const QVariant& value);
- QVariant dataValue(const QByteArray& role) const;
-
- void setData(const QHash<QByteArray, QVariant>& values);
- QHash<QByteArray, QVariant> data() const;
-
-protected:
- virtual void onDataValueChanged(const QByteArray& role,
- const QVariant& current,
- const QVariant& previous);
-
- virtual void onDataChanged(const QHash<QByteArray, QVariant>& current,
- const QHash<QByteArray, QVariant>& previous);
-
-private:
- KStandardItemModel* m_model;
-
- QHash<QByteArray, QVariant> m_data;
-
- friend class KStandardItemModel;
-};
-
-#endif
-
-
diff --git a/src/kitemviews/kstandarditemlistgroupheader.cpp b/src/kitemviews/kstandarditemlistgroupheader.cpp
index 22eefe5ea..28497ddfc 100644
--- a/src/kitemviews/kstandarditemlistgroupheader.cpp
+++ b/src/kitemviews/kstandarditemlistgroupheader.cpp
@@ -17,8 +17,7 @@ KStandardItemListGroupHeader::KStandardItemListGroupHeader(QGraphicsWidget* pare
m_text(),
m_pixmap()
{
- m_text.setTextFormat(Qt::PlainText);
- m_text.setPerformanceHint(QStaticText::AggressiveCaching);
+
}
KStandardItemListGroupHeader::~KStandardItemListGroupHeader()
@@ -37,7 +36,7 @@ void KStandardItemListGroupHeader::paintRole(QPainter* painter, const QRectF& ro
{
if (m_pixmap.isNull()) {
painter->setPen(color);
- painter->drawStaticText(roleBounds.topLeft(), m_text);
+ painter->drawText(roleBounds, 0, m_text);
} else {
painter->drawPixmap(roleBounds.topLeft(), m_pixmap);
}
@@ -55,7 +54,11 @@ void KStandardItemListGroupHeader::paintSeparator(QPainter* painter, const QColo
if (scrollOrientation() == Qt::Horizontal) {
painter->drawLine(0, 0, 0, size().height() - 1);
} else {
- painter->drawLine(0, 0, size().width() - 1, 0);
+ if (layoutDirection() == Qt::LeftToRight) {
+ painter->drawLine(0, 0, size().width() - 1, 0);
+ } else {
+ painter->drawLine(1, 0, size().width(), 0);
+ }
}
}
@@ -75,7 +78,7 @@ void KStandardItemListGroupHeader::dataChanged(const QVariant& current, const QV
void KStandardItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event)
{
- QGraphicsWidget::resizeEvent(event);
+ KItemListGroupHeader::resizeEvent(event);
m_dirtyCache = true;
}
@@ -87,7 +90,7 @@ void KStandardItemListGroupHeader::updateCache()
const qreal maxWidth = size().width() - 4 * styleOption().padding;
if (role() == "rating") {
- m_text.setText(QString());
+ m_text = QString();
const qreal height = styleOption().fontMetrics.ascent();
const QSizeF pixmapSize(qMin(height * 5, maxWidth), height);
@@ -104,7 +107,7 @@ void KStandardItemListGroupHeader::updateCache()
QFontMetricsF fontMetrics(font());
const QString text = fontMetrics.elidedText(data().toString(), Qt::ElideRight, maxWidth);
- m_text.setText(text);
+ m_text = text;
}
}
diff --git a/src/kitemviews/kstandarditemlistgroupheader.h b/src/kitemviews/kstandarditemlistgroupheader.h
index ff428c4ea..965bf995c 100644
--- a/src/kitemviews/kstandarditemlistgroupheader.h
+++ b/src/kitemviews/kstandarditemlistgroupheader.h
@@ -35,7 +35,7 @@ private:
private:
bool m_dirtyCache;
- QStaticText m_text;
+ QString m_text;
QPixmap m_pixmap;
};
#endif
diff --git a/src/kitemviews/kstandarditemlistview.cpp b/src/kitemviews/kstandarditemlistview.cpp
index 6edbefad8..4b7c2d9a4 100644
--- a/src/kitemviews/kstandarditemlistview.cpp
+++ b/src/kitemviews/kstandarditemlistview.cpp
@@ -17,6 +17,7 @@ KStandardItemListView::KStandardItemListView(QGraphicsWidget* parent) :
setAcceptDrops(true);
setScrollOrientation(Qt::Vertical);
setVisibleRoles({"text"});
+ setAlternateBackgrounds(true);
}
KStandardItemListView::~KStandardItemListView()
@@ -34,6 +35,8 @@ void KStandardItemListView::setItemLayout(ItemLayout layout)
const ItemLayout previous = m_itemLayout;
m_itemLayout = layout;
+ // keep the leading padding option unchanged here
+ setHighlightEntireRow(layout == DetailsLayout);
setSupportsItemExpanding(itemLayoutSupportsItemExpanding(layout));
setScrollOrientation(layout == CompactLayout ? Qt::Horizontal : Qt::Vertical);
@@ -69,6 +72,7 @@ void KStandardItemListView::initializeItemListWidget(KItemListWidget* item)
default: Q_ASSERT(false); break;
}
+ standardItemListWidget->setHighlightEntireRow(highlightEntireRow());
standardItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
}
diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp
index 9c527fa17..04d8e0f7e 100644
--- a/src/kitemviews/kstandarditemlistwidget.cpp
+++ b/src/kitemviews/kstandarditemlistwidget.cpp
@@ -36,7 +36,7 @@ KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant()
{
}
-void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
switch (static_cast<const KStandardItemListView*>(view)->itemLayout()) {
case KStandardItemListView::IconsLayout:
@@ -121,7 +121,7 @@ QFont KStandardItemListWidgetInformant::customizedFontForLinks(const QFont& base
return baseFont;
}
-void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
const KItemListStyleOption& option = view->styleOption();
const QFont& normalFont = option.font;
@@ -138,7 +138,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
for (int index = 0; index < logicalHeightHints.count(); ++index) {
- if (logicalHeightHints.at(index) > 0.0) {
+ if (logicalHeightHints.at(index).first > 0.0) {
continue;
}
@@ -146,7 +146,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
const QFont& font = itemIsLink(index, view) ? linkFont : normalFont;
const QString& text = KStringHandler::preProcessWrap(itemText(index, view));
-
+
// Calculate the number of lines required for wrapping the name
qreal textHeight = 0;
QTextLayout layout(text, font);
@@ -154,6 +154,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
layout.beginLayout();
QTextLine line;
int lineCount = 0;
+ bool isElided = false;
while ((line = layout.createLine()).isValid()) {
line.setLineWidth(maxWidth);
line.naturalTextWidth();
@@ -161,6 +162,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
++lineCount;
if (lineCount == option.maxTextLines) {
+ isElided = layout.createLine().isValid();
break;
}
}
@@ -169,13 +171,14 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
// Add one line for each additional information
textHeight += additionalRolesSpacing;
- logicalHeightHints[index] = textHeight + spacingAndIconHeight;
+ logicalHeightHints[index].first = textHeight + spacingAndIconHeight;
+ logicalHeightHints[index].second = isElided;
}
logicalWidthHint = itemWidth;
}
-void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
const KItemListStyleOption& option = view->styleOption();
const QFontMetrics& normalFontMetrics = option.fontMetrics;
@@ -190,7 +193,7 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect
const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font));
for (int index = 0; index < logicalHeightHints.count(); ++index) {
- if (logicalHeightHints.at(index) > 0.0) {
+ if (logicalHeightHints.at(index).first > 0.0) {
continue;
}
@@ -217,13 +220,13 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect
width = maxWidth;
}
- logicalHeightHints[index] = width;
+ logicalHeightHints[index].first = width;
}
logicalWidthHint = height;
}
-void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
{
const KItemListStyleOption& option = view->styleOption();
const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height());
@@ -247,6 +250,7 @@ KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* infor
m_pixmapPos(),
m_pixmap(),
m_scaledPixmapSize(),
+ m_columnWidthSum(),
m_iconRect(),
m_hoverPixmap(),
m_textRect(),
@@ -290,6 +294,18 @@ KStandardItemListWidget::Layout KStandardItemListWidget::layout() const
return m_layout;
}
+void KStandardItemListWidget::setHighlightEntireRow(bool highlightEntireRow) {
+ if (m_highlightEntireRow != highlightEntireRow) {
+ m_highlightEntireRow = highlightEntireRow;
+ m_dirtyLayout = true;
+ update();
+ }
+}
+
+bool KStandardItemListWidget::highlightEntireRow() const {
+ return m_highlightEntireRow;
+}
+
void KStandardItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding)
{
if (m_supportsItemExpanding != supportsItemExpanding) {
@@ -491,7 +507,11 @@ QRectF KStandardItemListWidget::selectionRect() const
case DetailsLayout: {
const int padding = styleOption().padding;
QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding);
- return adjustedIconRect | m_textRect;
+ QRectF result = adjustedIconRect | m_textRect;
+ if (m_highlightEntireRow) {
+ result.setRight(m_columnWidthSum + leadingPadding());
+ }
+ return result;
}
default:
@@ -708,6 +728,11 @@ void KStandardItemListWidget::columnWidthChanged(const QByteArray& role,
m_dirtyLayout = true;
}
+void KStandardItemListWidget::leadingPaddingChanged(qreal padding) {
+ Q_UNUSED(padding)
+ m_dirtyLayout = true;
+}
+
void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
const KItemListStyleOption& previous)
{
@@ -900,10 +925,13 @@ void KStandardItemListWidget::triggerCacheRefreshing()
m_isHidden = isHidden();
m_customizedFont = customizedFont(styleOption().font);
m_customizedFontMetrics = QFontMetrics(m_customizedFont);
+ m_columnWidthSum = std::accumulate(m_sortedVisibleRoles.begin(), m_sortedVisibleRoles.end(),
+ qreal(), [this](qreal sum, const auto &role){ return sum + columnWidth(role); });
updateExpansionArea();
updateTextsCache();
updatePixmapCache();
+ clearHoverCache();
m_dirtyLayout = false;
m_dirtyContent = false;
@@ -921,7 +949,8 @@ void KStandardItemListWidget::updateExpansionArea()
const qreal inc = (widgetHeight - option.iconSize) / 2;
const qreal x = expandedParentsCount * widgetHeight + inc;
const qreal y = inc;
- m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize);
+ const qreal xPadding = m_highlightEntireRow ? leadingPadding() : 0;
+ m_expansionArea = QRectF(xPadding + x, y, option.iconSize, option.iconSize);
return;
}
}
@@ -1358,7 +1387,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
if (m_supportsItemExpanding) {
firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
} else {
- firstColumnInc += option.padding;
+ firstColumnInc += option.padding + leadingPadding();
}
qreal x = firstColumnInc;
@@ -1374,7 +1403,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
const bool isTextRole = (role == "text");
if (isTextRole) {
- availableTextWidth -= firstColumnInc;
+ availableTextWidth -= firstColumnInc - leadingPadding();
}
if (requiredWidth > availableTextWidth) {
@@ -1396,7 +1425,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
// The column after the name should always be aligned on the same x-position independent
// from the expansion-level shown in the name column
- x -= firstColumnInc;
+ x -= firstColumnInc - leadingPadding();
} else if (isRoleRightAligned(role)) {
textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc;
}
@@ -1408,8 +1437,11 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor()
QColor c1;
if (m_customTextColor.isValid()) {
c1 = m_customTextColor;
- } else if (isSelected() && m_layout != DetailsLayout) {
- c1 = styleOption().palette.highlightedText().color();
+ } else if (isSelected()) {
+ // The detail text colour needs to match the main text (HighlightedText) for the same level
+ // of readability. We short circuit early here to avoid interpolating with another colour.
+ m_additionalInfoTextColor = styleOption().palette.color(QPalette::HighlightedText);
+ return;
} else {
c1 = styleOption().palette.text().color();
}
@@ -1448,15 +1480,15 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter)
const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2;
QRect siblingRect(x, 0, siblingSize, siblingSize);
- QStyleOption option;
- option.palette.setColor(QPalette::Text, option.palette.color(normalTextColorRole()));
bool isItemSibling = true;
const QBitArray siblings = siblingsInformation();
+ QStyleOption option;
+ const auto normalColor = option.palette.color(normalTextColorRole());
+ const auto highlightColor = option.palette.color(expansionAreaHovered() ? QPalette::Highlight : normalTextColorRole());
for (int i = siblings.count() - 1; i >= 0; --i) {
option.rect = siblingRect;
option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None;
-
if (isItemSibling) {
option.state |= QStyle::State_Item;
if (m_isExpandable) {
@@ -1465,7 +1497,10 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter)
if (data().value("isExpanded").toBool()) {
option.state |= QStyle::State_Open;
}
+ option.palette.setColor(QPalette::Text, highlightColor);
isItemSibling = false;
+ } else {
+ option.palette.setColor(QPalette::Text, normalColor);
}
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h
index 7d33419bc..3edec0db5 100644
--- a/src/kitemviews/kstandarditemlistwidget.h
+++ b/src/kitemviews/kstandarditemlistwidget.h
@@ -23,12 +23,13 @@ class DOLPHIN_EXPORT KStandardItemListWidgetInformant : public KItemListWidgetIn
public:
KStandardItemListWidgetInformant();
~KStandardItemListWidgetInformant() override;
-
- void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
+
+ void calculateItemSizeHints(QVector<std::pair<qreal /* height */, bool /* isElided */>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
qreal preferredRoleColumnWidth(const QByteArray& role,
int index,
const KItemListView* view) const override;
+
protected:
/**
* @return The value of the "text" role. The default implementation returns
@@ -59,9 +60,9 @@ protected:
*/
virtual QFont customizedFontForLinks(const QFont& baseFont) const;
- void calculateIconsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
- void calculateCompactLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
- void calculateDetailsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
+ void calculateIconsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
+ void calculateCompactLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
+ void calculateDetailsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
friend class KStandardItemListWidget; // Accesses roleText()
};
@@ -87,6 +88,9 @@ public:
void setLayout(Layout layout);
Layout layout() const;
+ void setHighlightEntireRow(bool highlightEntireRow);
+ bool highlightEntireRow() const;
+
void setSupportsItemExpanding(bool supportsItemExpanding);
bool supportsItemExpanding() const;
@@ -167,6 +171,7 @@ protected:
void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()) override;
void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous) override;
void columnWidthChanged(const QByteArray& role, qreal current, qreal previous) override;
+ void leadingPaddingChanged(qreal width) override;
void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) override;
void hoveredChanged(bool hovered) override;
void selectedChanged(bool selected) override;
@@ -240,6 +245,7 @@ private:
QFont m_customizedFont;
QFontMetrics m_customizedFontMetrics;
bool m_isExpandable;
+ bool m_highlightEntireRow;
bool m_supportsItemExpanding;
bool m_dirtyLayout;
@@ -251,6 +257,7 @@ private:
QPixmap m_pixmap;
QSize m_scaledPixmapSize; //Size of the pixmap in device independent pixels
+ qreal m_columnWidthSum;
QRectF m_iconRect; // Cache for KItemListWidget::iconRect()
QPixmap m_hoverPixmap; // Cache for modified m_pixmap when hovering the item
diff --git a/src/kitemviews/kstandarditemmodel.cpp b/src/kitemviews/kstandarditemmodel.cpp
deleted file mode 100644
index 128841ca8..000000000
--- a/src/kitemviews/kstandarditemmodel.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <[email protected]>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "kstandarditemmodel.h"
-
-#include "kstandarditem.h"
-
-KStandardItemModel::KStandardItemModel(QObject* parent) :
- KItemModelBase(parent),
- m_items(),
- m_indexesForItems()
-{
-}
-
-KStandardItemModel::~KStandardItemModel()
-{
- qDeleteAll(m_items);
- m_items.clear();
- m_indexesForItems.clear();
-}
-
-void KStandardItemModel::insertItem(int index, KStandardItem* item)
-{
- if (index < 0 || index > count() || !item) {
- delete item;
- return;
- }
-
- if (!m_indexesForItems.contains(item)) {
- item->m_model = this;
- m_items.insert(index, item);
- m_indexesForItems.insert(item, index);
-
- // Inserting an item requires to update the indexes
- // afterwards from m_indexesForItems.
- for (int i = index + 1; i < m_items.count(); ++i) {
- m_indexesForItems.insert(m_items[i], i);
- }
-
- // TODO: no hierarchical items are handled yet
-
- onItemInserted(index);
- Q_EMIT itemsInserted(KItemRangeList() << KItemRange(index, 1));
- }
-}
-
-void KStandardItemModel::changeItem(int index, KStandardItem* item)
-{
- if (index < 0 || index >= count() || !item) {
- delete item;
- return;
- }
-
- item->m_model = this;
-
- QSet<QByteArray> changedRoles;
-
- KStandardItem* oldItem = m_items[index];
- const QHash<QByteArray, QVariant> oldData = oldItem->data();
- const QHash<QByteArray, QVariant> newData = item->data();
-
- // Determine which roles have been changed
- QHashIterator<QByteArray, QVariant> it(oldData);
- while (it.hasNext()) {
- it.next();
- const QByteArray role = it.key();
- const QVariant oldValue = it.value();
- if (newData.contains(role) && newData.value(role) != oldValue) {
- changedRoles.insert(role);
- }
- }
-
- m_indexesForItems.remove(oldItem);
- delete oldItem;
- oldItem = nullptr;
-
- m_items[index] = item;
- m_indexesForItems.insert(item, index);
-
- onItemChanged(index, changedRoles);
- Q_EMIT itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
-}
-
-void KStandardItemModel::removeItem(int index)
-{
- if (index >= 0 && index < count()) {
- KStandardItem* item = m_items[index];
- m_indexesForItems.remove(item);
- m_items.removeAt(index);
-
- // Removing an item requires to update the indexes
- // afterwards from m_indexesForItems.
- for (int i = index; i < m_items.count(); ++i) {
- m_indexesForItems.insert(m_items[i], i);
- }
-
- onItemRemoved(index, item);
-
- item->deleteLater();
- item = nullptr;
-
- Q_EMIT itemsRemoved(KItemRangeList() << KItemRange(index, 1));
-
- // TODO: no hierarchical items are handled yet
- }
-}
-
-void KStandardItemModel::clear()
-{
- int size = m_items.size();
- m_items.clear();
- m_indexesForItems.clear();
-
- Q_EMIT itemsRemoved(KItemRangeList() << KItemRange(0, size));
-}
-
-KStandardItem* KStandardItemModel::item(int index) const
-{
- if (index < 0 || index >= m_items.count()) {
- return nullptr;
- }
- return m_items[index];
-}
-
-int KStandardItemModel::index(const KStandardItem* item) const
-{
- return m_indexesForItems.value(item, -1);
-}
-
-void KStandardItemModel::appendItem(KStandardItem *item)
-{
- insertItem(m_items.count(), item);
-}
-
-int KStandardItemModel::count() const
-{
- return m_items.count();
-}
-
-QHash<QByteArray, QVariant> KStandardItemModel::data(int index) const
-{
- if (index >= 0 && index < count()) {
- const KStandardItem* item = m_items[index];
- if (item) {
- return item->data();
- }
- }
- return QHash<QByteArray, QVariant>();
-}
-
-bool KStandardItemModel::setData(int index, const QHash<QByteArray, QVariant>& values)
-{
- Q_UNUSED(values)
- if (index < 0 || index >= count()) {
- return false;
- }
-
- return true;
-}
-
-QMimeData* KStandardItemModel::createMimeData(const KItemSet& indexes) const
-{
- Q_UNUSED(indexes)
- return nullptr;
-}
-
-int KStandardItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const
-{
- Q_UNUSED(text)
- Q_UNUSED(startFromIndex)
- return -1;
-}
-
-bool KStandardItemModel::supportsDropping(int index) const
-{
- Q_UNUSED(index)
- return false;
-}
-
-QString KStandardItemModel::roleDescription(const QByteArray& role) const
-{
- Q_UNUSED(role)
- return QString();
-}
-
-QList<QPair<int, QVariant> > KStandardItemModel::groups() const
-{
- QList<QPair<int, QVariant> > groups;
-
- const QByteArray role = sortRole().isEmpty() ? "group" : sortRole();
- bool isFirstGroupValue = true;
- QString groupValue;
- const int maxIndex = count() - 1;
- for (int i = 0; i <= maxIndex; ++i) {
- const QString newGroupValue = m_items.at(i)->dataValue(role).toString();
- if (newGroupValue != groupValue || isFirstGroupValue) {
- groupValue = newGroupValue;
- groups.append(QPair<int, QVariant>(i, newGroupValue));
- isFirstGroupValue = false;
- }
- }
-
- return groups;
-}
-
-void KStandardItemModel::onItemInserted(int index)
-{
- Q_UNUSED(index)
-}
-
-void KStandardItemModel::onItemChanged(int index, const QSet<QByteArray>& changedRoles)
-{
- Q_UNUSED(index)
- Q_UNUSED(changedRoles)
-}
-
-void KStandardItemModel::onItemRemoved(int index, KStandardItem* removedItem)
-{
- Q_UNUSED(index)
- Q_UNUSED(removedItem)
-}
-
diff --git a/src/kitemviews/kstandarditemmodel.h b/src/kitemviews/kstandarditemmodel.h
deleted file mode 100644
index d92ec5d46..000000000
--- a/src/kitemviews/kstandarditemmodel.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <[email protected]>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef KSTANDARDITEMMODEL_H
-#define KSTANDARDITEMMODEL_H
-
-#include "dolphin_export.h"
-#include "kitemviews/kitemmodelbase.h"
-
-#include <QHash>
-#include <QList>
-
-class KStandardItem;
-
-/**
- * @brief Model counterpart for KStandardItemListView.
- *
- * Allows to add items to the model in an easy way by the
- * class KStandardItem.
- *
- * @see KStandardItem
- */
-class DOLPHIN_EXPORT KStandardItemModel : public KItemModelBase
-{
- Q_OBJECT
-
-public:
- explicit KStandardItemModel(QObject* parent = nullptr);
- ~KStandardItemModel() override;
-
- /**
- * Inserts the item \a item at the index \a index. If the index
- * is equal to the number of items of the model, the item
- * gets appended as last element. KStandardItemModel takes
- * the ownership of the item. If the index is invalid, the item
- * gets deleted.
- */
- void insertItem(int index, KStandardItem* item);
-
- /**
- * Changes the item on the index \a index to \a item.
- * KStandardItemModel takes the ownership of the item. The
- * old item gets deleted. If the index is invalid, the item
- * gets deleted.
- */
- void changeItem(int index, KStandardItem* item);
-
- void removeItem(int index);
- KStandardItem* item(int index) const;
- int index(const KStandardItem* item) const;
-
- /**
- * Convenience method for insertItem(count(), item).
- */
- void appendItem(KStandardItem* item);
-
- int count() const override;
- QHash<QByteArray, QVariant> data(int index) const override;
- bool setData(int index, const QHash<QByteArray, QVariant>& values) override;
- QMimeData* createMimeData(const KItemSet& indexes) const override;
- int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const override;
- bool supportsDropping(int index) const override;
- QString roleDescription(const QByteArray& role) const override;
- QList<QPair<int, QVariant> > groups() const override;
-
- virtual void clear();
-protected:
- /**
- * Is invoked after an item has been inserted and before the signal
- * itemsInserted() gets emitted.
- */
- virtual void onItemInserted(int index);
-
- /**
- * Is invoked after an item or one of its roles has been changed and
- * before the signal itemsChanged() gets emitted.
- */
- virtual void onItemChanged(int index, const QSet<QByteArray>& changedRoles);
-
- /**
- * Is invoked after an item has been removed and before the signal
- * itemsRemoved() gets emitted. The item \a removedItem has already
- * been removed from the model and will get deleted after the
- * execution of onItemRemoved().
- */
- virtual void onItemRemoved(int index, KStandardItem* removedItem);
-
-private:
- QList<KStandardItem*> m_items;
- QHash<const KStandardItem*, int> m_indexesForItems;
-
- friend class KStandardItem;
- friend class KStandardItemModelTest; // For unit testing
-};
-
-#endif
-
-
diff --git a/src/kitemviews/private/kbaloorolesprovider.cpp b/src/kitemviews/private/kbaloorolesprovider.cpp
index 5c87de712..4c231e2ff 100644
--- a/src/kitemviews/private/kbaloorolesprovider.cpp
+++ b/src/kitemviews/private/kbaloorolesprovider.cpp
@@ -13,6 +13,7 @@
#include <QCollator>
#include <QDebug>
+#include <QSize>
#include <QTime>
namespace {
@@ -117,6 +118,18 @@ QHash<QByteArray, QVariant> KBalooRolesProvider::roleValues(const Baloo::File& f
rangeBegin = rangeEnd;
}
+ if (roles.contains("dimensions")) {
+ bool widthOk = false;
+ bool heightOk = false;
+
+ const int width = propMap.value(KFileMetaData::Property::Width).toInt(&widthOk);
+ const int height = propMap.value(KFileMetaData::Property::Height).toInt(&heightOk);
+
+ if (widthOk && heightOk && width >= 0 && height >= 0) {
+ values.insert("dimensions", QSize(width, height));
+ }
+ }
+
KFileMetaData::UserMetaData::Attributes attributes;
if (roles.contains("tags")) {
attributes |= KFileMetaData::UserMetaData::Tags;
@@ -160,6 +173,7 @@ KBalooRolesProvider::KBalooRolesProvider()
for (const auto& role : propertyRoleMap()) {
m_roles.insert(role);
}
+ m_roles.insert("dimensions");
// Display roles provided by UserMetaData
m_roles.insert(QByteArrayLiteral("tags"));
diff --git a/src/kitemviews/private/kitemlistheaderwidget.cpp b/src/kitemviews/private/kitemlistheaderwidget.cpp
index e5cbc602f..5fb929e52 100644
--- a/src/kitemviews/private/kitemlistheaderwidget.cpp
+++ b/src/kitemviews/private/kitemlistheaderwidget.cpp
@@ -18,6 +18,7 @@ KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) :
m_automaticColumnResizing(true),
m_model(nullptr),
m_offset(0),
+ m_leadingPadding(0),
m_columns(),
m_columnWidths(),
m_preferredColumnWidths(),
@@ -134,6 +135,20 @@ qreal KItemListHeaderWidget::offset() const
return m_offset;
}
+void KItemListHeaderWidget::setLeadingPadding(qreal width)
+{
+ if (m_leadingPadding != width) {
+ m_leadingPadding = width;
+ leadingPaddingChanged(width);
+ update();
+ }
+}
+
+qreal KItemListHeaderWidget::leadingPadding() const
+{
+ return m_leadingPadding;
+}
+
qreal KItemListHeaderWidget::minimumColumnWidth() const
{
QFontMetricsF fontMetrics(font());
@@ -153,7 +168,7 @@ void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsI
painter->setFont(font());
painter->setPen(palette().text().color());
- qreal x = -m_offset;
+ qreal x = -m_offset + m_leadingPadding;
int orderIndex = 0;
for (const QByteArray& role : qAsConst(m_columns)) {
const qreal roleWidth = m_columnWidths.value(role);
@@ -172,10 +187,14 @@ void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsI
void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
if (event->button() & Qt::LeftButton) {
- updatePressedRoleIndex(event->pos());
m_pressedMousePos = event->pos();
- m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
- ResizeRoleOperation : NoRoleOperation;
+ if (isAbovePaddingGrip(m_pressedMousePos, PaddingGrip::Leading)) {
+ m_roleOperation = ResizeLeadingColumnOperation;
+ } else {
+ updatePressedRoleIndex(event->pos());
+ m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
+ ResizeRoleOperation : NoRoleOperation;
+ }
event->accept();
} else {
event->ignore();
@@ -251,7 +270,7 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
case NoRoleOperation:
if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
// A role gets dragged by the user. Create a pixmap of the role that will get
- // synchronized on each furter mouse-move-event with the mouse-position.
+ // synchronized on each further mouse-move-event with the mouse-position.
m_roleOperation = MoveRoleOperation;
const int roleIndex = roleIndexAt(m_pressedMousePos);
m_movingRole.index = roleIndex;
@@ -263,7 +282,7 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
} else {
m_movingRole.pixmap = createRolePixmap(roleIndex);
- qreal roleX = -m_offset;
+ qreal roleX = -m_offset + m_leadingPadding;
for (int i = 0; i < roleIndex; ++i) {
const QByteArray role = m_columns[i];
roleX += m_columnWidths.value(role);
@@ -291,6 +310,20 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
break;
}
+ case ResizeLeadingColumnOperation: {
+ qreal currentWidth = m_leadingPadding;
+ currentWidth += event->pos().x() - event->lastPos().x();
+ currentWidth = qMax(0.0, currentWidth);
+
+ m_leadingPadding = currentWidth;
+
+ update();
+
+ Q_EMIT leadingPaddingChanged(currentWidth);
+
+ break;
+ }
+
case MoveRoleOperation: {
// TODO: It should be configurable whether moving the first role is allowed.
// In the context of Dolphin this is not required, however this should be
@@ -355,7 +388,9 @@ void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
const QPointF& pos = event->pos();
updateHoveredRoleIndex(pos);
- if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) {
+ if ((m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) ||
+ isAbovePaddingGrip(pos, PaddingGrip::Leading) ||
+ isAbovePaddingGrip(pos, PaddingGrip::Trailing)) {
setCursor(Qt::SplitHCursor);
} else {
unsetCursor();
@@ -385,6 +420,12 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
// The following code is based on the code from QHeaderView::paintSection().
// SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
QStyleOptionHeader option;
+ option.direction = widget->layoutDirection();
+ option.textAlignment =
+ widget->layoutDirection() == Qt::LeftToRight
+ ? Qt::AlignLeft
+ : Qt::AlignRight;
+
option.section = orderIndex;
option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
if (isEnabled()) {
@@ -404,19 +445,39 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
}
option.rect = rect.toRect();
+ option.orientation = Qt::Horizontal;
+ option.selectedPosition = QStyleOptionHeader::NotAdjacent;
+ option.text = m_model->roleDescription(role);
- bool paintBackgroundForEmptyArea = false;
+ // First we paint any potential empty (padding) space on left and/or right of this role's column.
+ const auto paintPadding = [&](int section, const QRectF &rect, const QStyleOptionHeader::SectionPosition &pos){
+ QStyleOptionHeader padding;
+ padding.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
+ padding.section = section;
+ padding.sortIndicator = QStyleOptionHeader::None;
+ padding.rect = rect.toRect();
+ padding.position = pos;
+ padding.text = QString();
+ style()->drawControl(QStyle::CE_Header, &padding, painter, widget);
+ };
if (m_columns.count() == 1) {
- option.position = QStyleOptionHeader::OnlyOneSection;
+ option.position = QStyleOptionHeader::Middle;
+ paintPadding(0, QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+ paintPadding(1, QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
} else if (orderIndex == 0) {
- option.position = QStyleOptionHeader::Beginning;
+ // Paint the header for the first column; check if there is some empty space to the left which needs to be filled.
+ if (rect.left() > 0) {
+ option.position = QStyleOptionHeader::Middle;
+ paintPadding(0,QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+ } else {
+ option.position = QStyleOptionHeader::Beginning;
+ }
} else if (orderIndex == m_columns.count() - 1) {
- // We are just painting the header for the last column. Check if there
- // is some empty space to the right which needs to be filled.
+ // Paint the header for the last column; check if there is some empty space to the right which needs to be filled.
if (rect.right() < size().width()) {
option.position = QStyleOptionHeader::Middle;
- paintBackgroundForEmptyArea = true;
+ paintPadding(m_columns.count(), QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
} else {
option.position = QStyleOptionHeader::End;
}
@@ -424,25 +485,7 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
option.position = QStyleOptionHeader::Middle;
}
- option.orientation = Qt::Horizontal;
- option.selectedPosition = QStyleOptionHeader::NotAdjacent;
- option.text = m_model->roleDescription(role);
-
style()->drawControl(QStyle::CE_Header, &option, painter, widget);
-
- if (paintBackgroundForEmptyArea) {
- option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
- option.section = m_columns.count();
- option.sortIndicator = QStyleOptionHeader::None;
-
- qreal backgroundRectX = rect.x() + rect.width();
- QRectF backgroundRect(backgroundRectX, 0.0, size().width() - backgroundRectX, rect.height());
- option.rect = backgroundRect.toRect();
- option.position = QStyleOptionHeader::End;
- option.text = QString();
-
- style()->drawControl(QStyle::CE_Header, &option, painter, widget);
- }
}
void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos)
@@ -467,7 +510,7 @@ int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
{
int index = -1;
- qreal x = -m_offset;
+ qreal x = -m_offset + m_leadingPadding;
for (const QByteArray& role : qAsConst(m_columns)) {
++index;
x += m_columnWidths.value(role);
@@ -481,7 +524,7 @@ int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const
{
- qreal x = -m_offset;
+ qreal x = -m_offset + m_leadingPadding;
for (int i = 0; i <= roleIndex; ++i) {
const QByteArray role = m_columns[i];
x += m_columnWidths.value(role);
@@ -491,6 +534,27 @@ bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) c
return pos.x() >= (x - grip) && pos.x() <= x;
}
+bool KItemListHeaderWidget::isAbovePaddingGrip(const QPointF& pos, PaddingGrip paddingGrip) const
+{
+ const qreal lx = -m_offset + m_leadingPadding;
+ const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+
+ switch (paddingGrip) {
+ case Leading:
+ return pos.x() >= (lx - grip) && pos.x() <= lx;
+ case Trailing:
+ {
+ qreal rx = lx;
+ for (const QByteArray& role : qAsConst(m_columns)) {
+ rx += m_columnWidths.value(role);
+ }
+ return pos.x() >= (rx - grip) && pos.x() <= rx;
+ }
+ default:
+ return false;
+ }
+}
+
QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
{
const QByteArray role = m_columns[roleIndex];
@@ -522,7 +586,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
const int movingRight = movingLeft + movingWidth - 1;
int targetIndex = 0;
- qreal targetLeft = -m_offset;
+ qreal targetLeft = -m_offset + m_leadingPadding;
while (targetIndex < m_columns.count()) {
const QByteArray role = m_columns[targetIndex];
const qreal targetWidth = m_columnWidths.value(role);
@@ -548,7 +612,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const
{
- qreal x = -m_offset;
+ qreal x = -m_offset + m_leadingPadding;
for (const QByteArray& visibleRole : qAsConst(m_columns)) {
if (visibleRole == role) {
return x;
diff --git a/src/kitemviews/private/kitemlistheaderwidget.h b/src/kitemviews/private/kitemlistheaderwidget.h
index 44adc23c5..58f0dc98e 100644
--- a/src/kitemviews/private/kitemlistheaderwidget.h
+++ b/src/kitemviews/private/kitemlistheaderwidget.h
@@ -50,6 +50,9 @@ public:
void setOffset(qreal offset);
qreal offset() const;
+ void setLeadingPadding(qreal width);
+ qreal leadingPadding() const;
+
qreal minimumColumnWidth() const;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
@@ -63,6 +66,8 @@ Q_SIGNALS:
qreal currentWidth,
qreal previousWidth);
+ void leadingPaddingChanged(qreal width);
+
/**
* Is emitted if the user has released the mouse button after adjusting the
* width of a visible role.
@@ -105,6 +110,13 @@ private Q_SLOTS:
void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
private:
+
+ enum PaddingGrip
+ {
+ Leading,
+ Trailing,
+ };
+
void paintRole(QPainter* painter,
const QByteArray& role,
const QRectF& rect,
@@ -115,6 +127,7 @@ private:
void updateHoveredRoleIndex(const QPointF& pos);
int roleIndexAt(const QPointF& pos) const;
bool isAboveRoleGrip(const QPointF& pos, int roleIndex) const;
+ bool isAbovePaddingGrip(const QPointF& pos, PaddingGrip paddingGrip) const;
/**
* Creates a pixmap of the role with the index \a roleIndex that is shown
@@ -138,12 +151,14 @@ private:
{
NoRoleOperation,
ResizeRoleOperation,
+ ResizeLeadingColumnOperation,
MoveRoleOperation
};
bool m_automaticColumnResizing;
KItemModelBase* m_model;
qreal m_offset;
+ qreal m_leadingPadding;
QList<QByteArray> m_columns;
QHash<QByteArray, qreal> m_columnWidths;
QHash<QByteArray, qreal> m_preferredColumnWidths;
diff --git a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
index 57a954adf..0e6280ede 100644
--- a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
+++ b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
@@ -55,7 +55,7 @@ void KItemListKeyboardSearchManager::addKeys(const QString& keys)
const bool searchFromNextItem = (!m_isSearchRestarted && newSearch) || sameKey;
// to remember not to searchFromNextItem if selection was deselected
- // loosing keyboard search context basically
+ // losing keyboard search context basically
m_isSearchRestarted = false;
Q_EMIT changeCurrentItem(sameKey ? firstKey : m_searchedString, searchFromNextItem);
diff --git a/src/kitemviews/private/kitemlistsizehintresolver.cpp b/src/kitemviews/private/kitemlistsizehintresolver.cpp
index 7deb5c621..0c2dd0b80 100644
--- a/src/kitemviews/private/kitemlistsizehintresolver.cpp
+++ b/src/kitemviews/private/kitemlistsizehintresolver.cpp
@@ -29,7 +29,12 @@ QSizeF KItemListSizeHintResolver::minSizeHint()
QSizeF KItemListSizeHintResolver::sizeHint(int index)
{
updateCache();
- return QSizeF(m_logicalWidthHint, m_logicalHeightHintCache.at(index));
+ return QSizeF(m_logicalWidthHint, m_logicalHeightHintCache.at(index).first);
+}
+
+bool KItemListSizeHintResolver::isElided(int index)
+{
+ return m_logicalHeightHintCache.at(index).second;
}
void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
@@ -44,7 +49,7 @@ void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
// We build the new list from the end to the beginning to mimize the
// number of moves.
- m_logicalHeightHintCache.insert(m_logicalHeightHintCache.end(), insertedCount, 0.0);
+ m_logicalHeightHintCache.insert(m_logicalHeightHintCache.end(), insertedCount, std::make_pair(0.0, false));
int sourceIndex = currentCount - 1;
int targetIndex = m_logicalHeightHintCache.count() - 1;
@@ -63,7 +68,7 @@ void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
// Then: insert QSizeF() for the items which are inserted into 'range'.
while (targetIndex >= itemsToInsertBeforeCurrentRange + range.index) {
- m_logicalHeightHintCache[targetIndex] = 0.0;
+ m_logicalHeightHintCache[targetIndex] = std::make_pair(0.0, false);
--targetIndex;
}
}
@@ -75,14 +80,14 @@ void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
void KItemListSizeHintResolver::itemsRemoved(const KItemRangeList& itemRanges)
{
- const QVector<qreal>::iterator begin = m_logicalHeightHintCache.begin();
- const QVector<qreal>::iterator end = m_logicalHeightHintCache.end();
+ const QVector<std::pair<qreal, bool>>::iterator begin = m_logicalHeightHintCache.begin();
+ const QVector<std::pair<qreal, bool>>::iterator end = m_logicalHeightHintCache.end();
KItemRangeList::const_iterator rangeIt = itemRanges.constBegin();
const KItemRangeList::const_iterator rangeEnd = itemRanges.constEnd();
- QVector<qreal>::iterator destIt = begin + rangeIt->index;
- QVector<qreal>::iterator srcIt = destIt + rangeIt->count;
+ QVector<std::pair<qreal, bool>>::iterator destIt = begin + rangeIt->index;
+ QVector<std::pair<qreal, bool>>::iterator srcIt = destIt + rangeIt->count;
++rangeIt;
@@ -109,7 +114,7 @@ void KItemListSizeHintResolver::itemsRemoved(const KItemRangeList& itemRanges)
void KItemListSizeHintResolver::itemsMoved(const KItemRange& range, const QList<int>& movedToIndexes)
{
- QVector<qreal> newLogicalHeightHintCache(m_logicalHeightHintCache);
+ QVector<std::pair<qreal, bool>> newLogicalHeightHintCache(m_logicalHeightHintCache);
const int movedRangeEnd = range.index + range.count;
for (int i = range.index; i < movedRangeEnd; ++i) {
@@ -124,7 +129,7 @@ void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QB
{
Q_UNUSED(roles)
while (count) {
- m_logicalHeightHintCache[index] = 0.0;
+ m_logicalHeightHintCache[index] = std::make_pair(0.0, false);
++index;
--count;
}
@@ -134,7 +139,7 @@ void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QB
void KItemListSizeHintResolver::clearCache()
{
- m_logicalHeightHintCache.fill(0.0);
+ m_logicalHeightHintCache.fill(std::make_pair(0.0, false));
m_needsResolving = true;
}
diff --git a/src/kitemviews/private/kitemlistsizehintresolver.h b/src/kitemviews/private/kitemlistsizehintresolver.h
index 9a0ab1f5b..a6cc56614 100644
--- a/src/kitemviews/private/kitemlistsizehintresolver.h
+++ b/src/kitemviews/private/kitemlistsizehintresolver.h
@@ -25,6 +25,7 @@ public:
virtual ~KItemListSizeHintResolver();
QSizeF minSizeHint();
QSizeF sizeHint(int index);
+ bool isElided(int index);
void itemsInserted(const KItemRangeList& itemRanges);
void itemsRemoved(const KItemRangeList& itemRanges);
@@ -36,7 +37,7 @@ public:
private:
const KItemListView* m_itemListView;
- mutable QVector<qreal> m_logicalHeightHintCache;
+ mutable QVector<std::pair<qreal /* height */, bool /* isElided */>> m_logicalHeightHintCache;
mutable qreal m_logicalWidthHint;
mutable qreal m_minHeightHint;
bool m_needsResolving;
diff --git a/src/kitemviews/private/kitemlistviewlayouter.cpp b/src/kitemviews/private/kitemlistviewlayouter.cpp
index 6de83ca87..4c22b4dbc 100644
--- a/src/kitemviews/private/kitemlistviewlayouter.cpp
+++ b/src/kitemviews/private/kitemlistviewlayouter.cpp
@@ -9,6 +9,9 @@
#include "kitemlistsizehintresolver.h"
#include "kitemviews/kitemmodelbase.h"
+#include <QGuiApplication>
+#include <QScopeGuard>
+
// #define KITEMLISTVIEWLAYOUTER_DEBUG
KItemListViewLayouter::KItemListViewLayouter(KItemListSizeHintResolver* sizeHintResolver, QObject* parent) :
@@ -343,170 +346,177 @@ void KItemListViewLayouter::markAsDirty()
void KItemListViewLayouter::doLayout()
{
- if (m_dirty) {
+ // we always want to update visible indexes after performing a layout
+ auto qsg = qScopeGuard([this] { updateVisibleIndexes(); });
+
+ if (!m_dirty) {
+ return;
+ }
+
#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
- QElapsedTimer timer;
- timer.start();
+ QElapsedTimer timer;
+ timer.start();
#endif
- m_visibleIndexesDirty = true;
+ m_visibleIndexesDirty = true;
- QSizeF itemSize = m_itemSize;
- QSizeF itemMargin = m_itemMargin;
- QSizeF size = m_size;
+ QSizeF itemSize = m_itemSize;
+ QSizeF itemMargin = m_itemMargin;
+ QSizeF size = m_size;
- const bool grouped = createGroupHeaders();
+ const bool grouped = createGroupHeaders();
- const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
- if (horizontalScrolling) {
- // Flip everything so that the layout logically can work like having
- // a vertical scrolling
- itemSize.transpose();
- itemMargin.transpose();
- size.transpose();
+ const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
+ if (horizontalScrolling) {
+ // Flip everything so that the layout logically can work like having
+ // a vertical scrolling
+ itemSize.transpose();
+ itemMargin.transpose();
+ size.transpose();
- if (grouped) {
- // In the horizontal scrolling case all groups are aligned
- // at the top, which decreases the available height. For the
- // flipped data this means that the width must be decreased.
- size.rwidth() -= m_groupHeaderHeight;
- }
+ if (grouped) {
+ // In the horizontal scrolling case all groups are aligned
+ // at the top, which decreases the available height. For the
+ // flipped data this means that the width must be decreased.
+ size.rwidth() -= m_groupHeaderHeight;
}
+ }
- m_columnWidth = itemSize.width() + itemMargin.width();
- const qreal widthForColumns = size.width() - itemMargin.width();
- m_columnCount = qMax(1, int(widthForColumns / m_columnWidth));
- m_xPosInc = itemMargin.width();
+ m_columnWidth = itemSize.width() + itemMargin.width();
+ const qreal widthForColumns = size.width() - itemMargin.width();
+ m_columnCount = qMax(1, int(widthForColumns / m_columnWidth));
+ m_xPosInc = itemMargin.width();
- const int itemCount = m_model->count();
- if (itemCount > m_columnCount && m_columnWidth >= 32) {
- // Apply the unused width equally to each column
- const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth;
- if (unusedWidth > 0) {
- const qreal columnInc = unusedWidth / (m_columnCount + 1);
- m_columnWidth += columnInc;
- m_xPosInc += columnInc;
- }
+ const int itemCount = m_model->count();
+ if (itemCount > m_columnCount && m_columnWidth >= 32) {
+ // Apply the unused width equally to each column
+ const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth;
+ if (unusedWidth > 0) {
+ const qreal columnInc = unusedWidth / (m_columnCount + 1);
+ m_columnWidth += columnInc;
+ m_xPosInc += columnInc;
}
+ }
- m_itemInfos.resize(itemCount);
+ m_itemInfos.resize(itemCount);
- // Calculate the offset of each column, i.e., the x-coordinate where the column starts.
- m_columnOffsets.resize(m_columnCount);
- qreal currentOffset = m_xPosInc;
+ // Calculate the offset of each column, i.e., the x-coordinate where the column starts.
+ m_columnOffsets.resize(m_columnCount);
+ qreal currentOffset = QGuiApplication::isRightToLeft() ? widthForColumns : m_xPosInc;
- if (grouped && horizontalScrolling) {
- // All group headers will always be aligned on the top and not
- // flipped like the other properties.
- currentOffset += m_groupHeaderHeight;
- }
+ if (grouped && horizontalScrolling) {
+ // All group headers will always be aligned on the top and not
+ // flipped like the other properties.
+ currentOffset += m_groupHeaderHeight;
+ }
- for (int column = 0; column < m_columnCount; ++column) {
- m_columnOffsets[column] = currentOffset;
- currentOffset += m_columnWidth;
- }
+ if (QGuiApplication::isLeftToRight()) for (int column = 0; column < m_columnCount; ++column) {
+ m_columnOffsets[column] = currentOffset;
+ currentOffset += m_columnWidth;
+ }
+ else for (int column = 0; column < m_columnCount; ++column) {
+ m_columnOffsets[column] = currentOffset - m_columnWidth;
+ currentOffset -= m_columnWidth;
+ }
- // Prepare the QVector which stores the y-coordinate for each new row.
- int numberOfRows = (itemCount + m_columnCount - 1) / m_columnCount;
- if (grouped && m_columnCount > 1) {
- // In the worst case, a new row will be started for every group.
- // We could calculate the exact number of rows now to prevent that we reserve
- // too much memory, but the code required to do that might need much more
- // memory than it would save in the average case.
- numberOfRows += m_groupItemIndexes.count();
- }
- m_rowOffsets.resize(numberOfRows);
+ // Prepare the QVector which stores the y-coordinate for each new row.
+ int numberOfRows = (itemCount + m_columnCount - 1) / m_columnCount;
+ if (grouped && m_columnCount > 1) {
+ // In the worst case, a new row will be started for every group.
+ // We could calculate the exact number of rows now to prevent that we reserve
+ // too much memory, but the code required to do that might need much more
+ // memory than it would save in the average case.
+ numberOfRows += m_groupItemIndexes.count();
+ }
+ m_rowOffsets.resize(numberOfRows);
- qreal y = m_headerHeight + itemMargin.height();
- int row = 0;
+ qreal y = m_headerHeight + itemMargin.height();
+ int row = 0;
- int index = 0;
- while (index < itemCount) {
- qreal maxItemHeight = itemSize.height();
+ int index = 0;
+ while (index < itemCount) {
+ qreal maxItemHeight = itemSize.height();
- if (grouped) {
- if (m_groupItemIndexes.contains(index)) {
- // The item is the first item of a group.
- // Increase the y-position to provide space
- // for the group header.
- if (index > 0) {
- // Only add a margin if there has been added another
- // group already before
- y += m_groupHeaderMargin;
- } else if (!horizontalScrolling) {
- // The first group header should be aligned on top
- y -= itemMargin.height();
- }
+ if (grouped) {
+ if (m_groupItemIndexes.contains(index)) {
+ // The item is the first item of a group.
+ // Increase the y-position to provide space
+ // for the group header.
+ if (index > 0) {
+ // Only add a margin if there has been added another
+ // group already before
+ y += m_groupHeaderMargin;
+ } else if (!horizontalScrolling) {
+ // The first group header should be aligned on top
+ y -= itemMargin.height();
+ }
- if (!horizontalScrolling) {
- y += m_groupHeaderHeight;
- }
+ if (!horizontalScrolling) {
+ y += m_groupHeaderHeight;
}
}
+ }
- m_rowOffsets[row] = y;
+ m_rowOffsets[row] = y;
- int column = 0;
- while (index < itemCount && column < m_columnCount) {
- qreal requiredItemHeight = itemSize.height();
- const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index);
- const qreal sizeHintHeight = sizeHint.height();
- if (sizeHintHeight > requiredItemHeight) {
- requiredItemHeight = sizeHintHeight;
- }
+ int column = 0;
+ while (index < itemCount && column < m_columnCount) {
+ qreal requiredItemHeight = itemSize.height();
+ const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index);
+ const qreal sizeHintHeight = sizeHint.height();
+ if (sizeHintHeight > requiredItemHeight) {
+ requiredItemHeight = sizeHintHeight;
+ }
- ItemInfo& itemInfo = m_itemInfos[index];
- itemInfo.column = column;
- itemInfo.row = row;
+ ItemInfo& itemInfo = m_itemInfos[index];
+ itemInfo.column = column;
+ itemInfo.row = row;
- if (grouped && horizontalScrolling) {
- // When grouping is enabled in the horizontal mode, the header alignment
- // looks like this:
- // Header-1 Header-2 Header-3
- // Item 1 Item 4 Item 7
- // Item 2 Item 5 Item 8
- // Item 3 Item 6 Item 9
- // In this case 'requiredItemHeight' represents the column-width. We don't
- // check the content of the header in the layouter to determine the required
- // width, hence assure that at least a minimal width of 15 characters is given
- // (in average a character requires the halve width of the font height).
- //
- // TODO: Let the group headers provide a minimum width and respect this width here
- const qreal headerWidth = minimumGroupHeaderWidth();
- if (requiredItemHeight < headerWidth) {
- requiredItemHeight = headerWidth;
- }
+ if (grouped && horizontalScrolling) {
+ // When grouping is enabled in the horizontal mode, the header alignment
+ // looks like this:
+ // Header-1 Header-2 Header-3
+ // Item 1 Item 4 Item 7
+ // Item 2 Item 5 Item 8
+ // Item 3 Item 6 Item 9
+ // In this case 'requiredItemHeight' represents the column-width. We don't
+ // check the content of the header in the layouter to determine the required
+ // width, hence assure that at least a minimal width of 15 characters is given
+ // (in average a character requires the halve width of the font height).
+ //
+ // TODO: Let the group headers provide a minimum width and respect this width here
+ const qreal headerWidth = minimumGroupHeaderWidth();
+ if (requiredItemHeight < headerWidth) {
+ requiredItemHeight = headerWidth;
}
+ }
- maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
- ++index;
- ++column;
+ maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
+ ++index;
+ ++column;
- if (grouped && m_groupItemIndexes.contains(index)) {
- // The item represents the first index of a group
- // and must aligned in the first column
- break;
- }
+ if (grouped && m_groupItemIndexes.contains(index)) {
+ // The item represents the first index of a group
+ // and must aligned in the first column
+ break;
}
-
- y += maxItemHeight + itemMargin.height();
- ++row;
}
- if (itemCount > 0) {
- m_maximumScrollOffset = y;
- m_maximumItemOffset = m_columnCount * m_columnWidth;
- } else {
- m_maximumScrollOffset = 0;
- m_maximumItemOffset = 0;
- }
+ y += maxItemHeight + itemMargin.height();
+ ++row;
+ }
-#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
- qCDebug(DolphinDebug) << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
-#endif
- m_dirty = false;
+ if (itemCount > 0) {
+ m_maximumScrollOffset = y;
+ m_maximumItemOffset = m_columnCount * m_columnWidth;
+ } else {
+ m_maximumScrollOffset = 0;
+ m_maximumItemOffset = 0;
}
- updateVisibleIndexes();
+#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
+ qCDebug(DolphinDebug) << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
+#endif
+ m_dirty = false;
}
void KItemListViewLayouter::updateVisibleIndexes()