diff options
Diffstat (limited to 'src/kitemviews')
| -rw-r--r-- | src/kitemviews/kfileitemmodel.cpp | 282 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodel.h | 6 | ||||
| -rw-r--r-- | src/kitemviews/kfileitemmodelrolesupdater.cpp | 2 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistcontroller.cpp | 5 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistcontroller.h | 7 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.cpp | 38 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistview.h | 1 | ||||
| -rw-r--r-- | src/kitemviews/kitemlistwidget.cpp | 94 | ||||
| -rw-r--r-- | src/kitemviews/kstandarditemlistwidget.cpp | 50 | ||||
| -rw-r--r-- | src/kitemviews/kstandarditemlistwidget.h | 2 | ||||
| -rw-r--r-- | src/kitemviews/private/kfileitemmodelfilter.cpp | 62 | ||||
| -rw-r--r-- | src/kitemviews/private/kfileitemmodelfilter.h | 36 | ||||
| -rw-r--r-- | src/kitemviews/private/kitemlistroleeditor.cpp | 16 | ||||
| -rw-r--r-- | src/kitemviews/private/kitemlistroleeditor.h | 1 | ||||
| -rw-r--r-- | src/kitemviews/private/kitemlistsmoothscroller.cpp | 1 | ||||
| -rw-r--r-- | src/kitemviews/private/kitemlistviewanimation.cpp | 2 | ||||
| -rw-r--r-- | src/kitemviews/private/kitemlistviewanimation.h | 1 | ||||
| -rw-r--r-- | src/kitemviews/private/kpixmapmodifier.cpp | 7 |
18 files changed, 511 insertions, 102 deletions
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index c8bac0b9d..72c3d1410 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -31,12 +31,237 @@ #include <QRecursiveMutex> #include <QTimer> #include <QWidget> +#include <QtCore/qcompare.h> +#include <algorithm> #include <klazylocalizedstring.h> Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex) // #define KFILEITEMMODEL_DEBUG +namespace +{ +bool isAsciiDigit(QChar c) +{ + return c >= QLatin1Char('0') && c <= QLatin1Char('9'); +} + +Qt::strong_ordering orderingFromInt(int result) +{ + if (result < 0) { + return Qt::strong_ordering::less; + } + + if (result > 0) { + return Qt::strong_ordering::greater; + } + + return Qt::strong_ordering::equivalent; +} + +int orderingToInt(Qt::strong_ordering ordering) +{ + if (ordering < 0) { + return -1; + } + + if (ordering > 0) { + return 1; + } + + return 0; +} + +Qt::strong_ordering compareDigitStrings(const QString &a, const QString &b) +{ + int firstSignificantA = 0; + while (firstSignificantA < a.length() && a.at(firstSignificantA) == QLatin1Char('0')) { + ++firstSignificantA; + } + + int firstSignificantB = 0; + while (firstSignificantB < b.length() && b.at(firstSignificantB) == QLatin1Char('0')) { + ++firstSignificantB; + } + + const int significantLengthA = a.length() - firstSignificantA; + const int significantLengthB = b.length() - firstSignificantB; + if (significantLengthA != significantLengthB) { + return significantLengthA < significantLengthB ? Qt::strong_ordering::less : Qt::strong_ordering::greater; + } + + for (int i = 0; i < significantLengthA; ++i) { + const QChar digitA = a.at(firstSignificantA + i); + const QChar digitB = b.at(firstSignificantB + i); + if (digitA != digitB) { + return digitA < digitB ? Qt::strong_ordering::less : Qt::strong_ordering::greater; + } + } + + return Qt::strong_ordering::equivalent; +} + +Qt::strong_ordering compareFractionalDigitStrings(const QString &a, const QString &b) +{ + const int length = std::max(a.length(), b.length()); + for (int i = 0; i < length; ++i) { + const QChar digitA = i < a.length() ? a.at(i) : QLatin1Char('0'); + const QChar digitB = i < b.length() ? b.at(i) : QLatin1Char('0'); + if (digitA != digitB) { + return digitA < digitB ? Qt::strong_ordering::less : Qt::strong_ordering::greater; + } + } + + return Qt::strong_ordering::equivalent; +} + +int findDigitRunEnd(const QString &text, int start) +{ + int end = start; + while (end < text.length() && isAsciiDigit(text.at(end))) { + ++end; + } + + return end; +} + +int countNumericChainSegments(const QString &text, int start, int *chainEnd) +{ + int end = findDigitRunEnd(text, start); + int segmentCount = 1; + + while (end + 1 < text.length() && text.at(end) == QLatin1Char('.') && isAsciiDigit(text.at(end + 1))) { + end = findDigitRunEnd(text, end + 1); + ++segmentCount; + } + + if (chainEnd) { + *chainEnd = end; + } + + return segmentCount; +} + +Qt::strong_ordering compareNumericChains(const QString &a, int startA, int endA, int segmentCountA, const QString &b, int startB, int endB, int segmentCountB) +{ + if (segmentCountA == 2 && segmentCountB == 2) { + const int dotA = findDigitRunEnd(a, startA); + const int dotB = findDigitRunEnd(b, startB); + + const Qt::strong_ordering integerResult = compareDigitStrings(a.mid(startA, dotA - startA), b.mid(startB, dotB - startB)); + if (integerResult != 0) { + return integerResult; + } + + return compareFractionalDigitStrings(a.mid(dotA + 1, endA - dotA - 1), b.mid(dotB + 1, endB - dotB - 1)); + } + + int segmentStartA = startA; + int segmentStartB = startB; + + while (true) { + const int segmentEndA = findDigitRunEnd(a, segmentStartA); + const int segmentEndB = findDigitRunEnd(b, segmentStartB); + + const Qt::strong_ordering segmentResult = + compareDigitStrings(a.mid(segmentStartA, segmentEndA - segmentStartA), b.mid(segmentStartB, segmentEndB - segmentStartB)); + if (segmentResult != 0) { + return segmentResult; + } + + const bool hasNextSegmentA = segmentEndA < endA; + const bool hasNextSegmentB = segmentEndB < endB; + if (!hasNextSegmentA || !hasNextSegmentB) { + if (hasNextSegmentA != hasNextSegmentB) { + return hasNextSegmentA ? Qt::strong_ordering::greater : Qt::strong_ordering::less; + } + + return Qt::strong_ordering::equivalent; + } + + segmentStartA = segmentEndA + 1; + segmentStartB = segmentEndB + 1; + } +} + +int findExtensionSeparator(const QString &text) +{ + for (int i = text.length() - 1; i > 0; --i) { + if (text.at(i) != QLatin1Char('.')) { + continue; + } + + if (isAsciiDigit(text.at(i - 1)) && i + 1 < text.length() && isAsciiDigit(text.at(i + 1))) { + continue; + } + + return i; + } + + return -1; +} + +Qt::strong_ordering decimalAwareNaturalCompare(const QString &a, const QString &b, const QCollator &collator) +{ + bool comparedNumericTokens = false; + int indexA = 0; + int indexB = 0; + + while (indexA < a.length() && indexB < b.length()) { + if (isAsciiDigit(a.at(indexA)) && isAsciiDigit(b.at(indexB))) { + comparedNumericTokens = true; + int chainEndA = indexA; + const int segmentCountA = countNumericChainSegments(a, indexA, &chainEndA); + int chainEndB = indexB; + const int segmentCountB = countNumericChainSegments(b, indexB, &chainEndB); + + const Qt::strong_ordering numericResult = compareNumericChains(a, indexA, chainEndA, segmentCountA, b, indexB, chainEndB, segmentCountB); + indexA = chainEndA; + indexB = chainEndB; + if (numericResult != 0) { + return numericResult; + } + + continue; + } + + int textEndA = indexA; + while (textEndA < a.length() && !isAsciiDigit(a.at(textEndA))) { + ++textEndA; + } + + int textEndB = indexB; + while (textEndB < b.length() && !isAsciiDigit(b.at(textEndB))) { + ++textEndB; + } + + const Qt::strong_ordering textResult = orderingFromInt(collator.compare(a.mid(indexA, textEndA - indexA), b.mid(indexB, textEndB - indexB))); + if (textResult != 0) { + return orderingFromInt(collator.compare(a.mid(indexA), b.mid(indexB))); + } + + indexA = textEndA; + indexB = textEndB; + } + + const Qt::strong_ordering remainderResult = orderingFromInt(collator.compare(a.mid(indexA), b.mid(indexB))); + if (remainderResult != 0) { + return remainderResult; + } + + if (!comparedNumericTokens) { + return Qt::strong_ordering::equivalent; + } + + const Qt::strong_ordering result = orderingFromInt(QString::compare(a, b, collator.caseSensitivity())); + if (result != 0 || collator.caseSensitivity() == Qt::CaseSensitive) { + return result; + } + + return orderingFromInt(QString::compare(a, b, Qt::CaseSensitive)); +} +} + KFileItemModel::KFileItemModel(QObject *parent) : KItemModelBase("text", parent) , m_dirLister(nullptr) @@ -817,6 +1042,34 @@ QStringList KFileItemModel::mimeTypeFilters() const return m_filter.mimeTypes(); } +void KFileItemModel::setFilterMode(KFileItemModelFilter::FilterMode mode) +{ + if (m_filter.filterMode() != mode) { + dispatchPendingItemsToInsert(); + m_filter.setFilterMode(mode); + applyFilters(); + } +} + +KFileItemModelFilter::FilterMode KFileItemModel::filterMode() const +{ + return m_filter.filterMode(); +} + +void KFileItemModel::setFilterCaseSensitive(bool caseSensitive) +{ + if (m_filter.isCaseSensitive() != caseSensitive) { + dispatchPendingItemsToInsert(); + m_filter.setCaseSensitive(caseSensitive); + applyFilters(); + } +} + +bool KFileItemModel::isFilterCaseSensitive() const +{ + return m_filter.isCaseSensitive(); +} + void KFileItemModel::setExcludeMimeTypeFilter(const QStringList &filters) { if (m_filter.excludeMimeTypes() != filters) { @@ -1921,7 +2174,14 @@ QHash<QByteArray, QVariant> KFileItemModel::retrieveData(const KFileItem &item, } if (m_requestRole[NameRole]) { - data.insert(sharedValue("text"), item.text()); + QString displayName = item.text(); + if (ContentDisplaySettings::hideFileExtensions() && !isDir) { + const int dotIndex = displayName.lastIndexOf(QLatin1Char('.')); + if (dotIndex > 0) { + displayName = displayName.left(dotIndex); + } + } + data.insert(sharedValue("text"), displayName); } if (m_requestRole[ExtensionRole] && !isDir) { @@ -2309,24 +2569,18 @@ int KFileItemModel::stringCompare(const QString &a, const QString &b, const QCol QMutexLocker collatorLock(s_collatorMutex()); if (m_naturalSorting) { - // Split extension, taking into account it can be empty - constexpr QString::SectionFlags flags = QString::SectionSkipEmpty | QString::SectionIncludeLeadingSep; + const int aExtensionSeparator = findExtensionSeparator(a); + const int bExtensionSeparator = findExtensionSeparator(b); + const int aBaseNameLength = aExtensionSeparator < 0 ? a.length() : aExtensionSeparator; + const int bBaseNameLength = bExtensionSeparator < 0 ? b.length() : bExtensionSeparator; - // Sort by baseName first - const QString aBaseName = a.section('.', 0, 0, flags); - const QString bBaseName = b.section('.', 0, 0, flags); - - const int res = collator.compare(aBaseName, bBaseName); - if (res != 0 || (aBaseName.length() == a.length() && bBaseName.length() == b.length())) { + const int res = orderingToInt(decimalAwareNaturalCompare(a.left(aBaseNameLength), b.left(bBaseNameLength), collator)); + if (res != 0 || (aExtensionSeparator < 0 && bExtensionSeparator < 0)) { return res; } - // sliced() has undefined behavior when pos < 0 or pos > size(). - Q_ASSERT(aBaseName.length() <= a.length() && aBaseName.length() >= 0); - Q_ASSERT(bBaseName.length() <= b.length() && bBaseName.length() >= 0); - // baseNames were equal, sort by extension - return collator.compare(a.sliced(aBaseName.length()), b.sliced(bBaseName.length())); + return orderingToInt(decimalAwareNaturalCompare(a.mid(aBaseNameLength), b.mid(bBaseNameLength), collator)); } const int result = QString::compare(a, b, collator.caseSensitivity()); diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 3749b0c1b..2a6e710c3 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -184,6 +184,12 @@ public: void setMimeTypeFilters(const QStringList &filters); QStringList mimeTypeFilters() const; + void setFilterMode(KFileItemModelFilter::FilterMode mode); + KFileItemModelFilter::FilterMode filterMode() const; + + void setFilterCaseSensitive(bool caseSensitive); + bool isFilterCaseSensitive() const; + void setExcludeMimeTypeFilter(const QStringList &filters); QStringList excludeMimeTypeFilter() const; diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 31459d8d1..0f4816424 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -973,8 +973,6 @@ void KFileItemModelRolesUpdater::startPreviewJob() const KFileItemList items = m_pendingPreviewItems; m_pendingPreviewItems.clear(); - const KFileItem &referenceItem = items.first(); - KIO::PreviewJob *job = new KIO::PreviewJob(items, cacheSize(), &m_enabledPlugins); job->setDevicePixelRatio(m_devicePixelRatio); if (job->uiDelegate()) { diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 54e95e1d1..e87ed3c18 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -547,6 +547,7 @@ void KItemListController::slotChangeCurrentItem(const QString &text, bool search m_view->scrollToItem(index, KItemListView::ViewItemPosition::Beginning); *found = true; } + Q_EMIT typeAheadUsed(text, index >= 0 ? std::make_optional<int>(index) : std::nullopt); } void KItemListController::slotAutoActivationTimeout() @@ -1493,8 +1494,7 @@ KItemListWidget *KItemListController::widgetForDropPos(const QPointF &pos) const const auto widgets = m_view->visibleItemListWidgets(); for (KItemListWidget *widget : widgets) { const QPointF mappedPos = widget->mapFromItem(m_view, pos); - const QRectF highlightRect = m_view->highlightEntireRow() ? widget->selectionRectFull() : widget->selectionRectCore(); - if (highlightRect.contains(mappedPos)) { + if (widget->selectionRectCore().contains(mappedPos)) { return widget; } } @@ -1763,6 +1763,7 @@ bool KItemListController::onPress(const QPointF &pos, const Qt::KeyboardModifier case SingleSelection: m_selectionManager->setSelected(m_pressedIndex.value()); + Q_FALLTHROUGH(); case MultiSelection: if (controlPressed && !shiftPressed && leftClick) { diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index 48a518610..6379acbd8 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -190,6 +190,13 @@ Q_SIGNALS: void aboveItemDropEvent(int index, QGraphicsSceneDragDropEvent *event); /** + * Emits the keys the user typed for searching so they can be displayed back to the user. + * @param typedString A string of basic interpretation of key presses e.g. "qwert". + * @param foundIndex The index of the item that was marked as current in response to this search. + */ + void typeAheadUsed(const QString &typedString, std::optional<int> foundIndex); + + /** * Is emitted if the Escape key is pressed. */ void escapePressed(); diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index b780e3ff4..452567f05 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -117,6 +117,7 @@ KItemListView::KItemListView(QGraphicsWidget *parent) m_animation = new KItemListViewAnimation(this); connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished); + connect(m_animation, &KItemListViewAnimation::started, this, &KItemListView::slotAnimationStarted); m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); @@ -132,7 +133,7 @@ KItemListView::KItemListView(QGraphicsWidget *parent) } update(); }); - connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() { + connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this](const QPointF &, const QPointF &) { if (m_tapAndHoldIndicator->isActive()) { update(); } @@ -758,8 +759,7 @@ void KItemListView::editRole(int index, const QByteArray &role) if (!widget) { return; } - if (m_editingRole || m_animation->isStarted(widget)) { - Q_EMIT widget->roleEditingCanceled(index, role, QVariant()); + if (widget->editedRole() == role) { return; } @@ -771,7 +771,7 @@ void KItemListView::editRole(int index, const QByteArray &role) connect(widget, &KItemListWidget::roleEditingCanceled, this, &KItemListView::slotRoleEditingCanceled); connect(widget, &KItemListWidget::roleEditingFinished, this, &KItemListView::slotRoleEditingFinished); - connect(this, &KItemListView::scrollOffsetChanged, widget, &KStandardItemListWidget::finishRoleEditing); + connect(this, &KItemListView::scrollOffsetChanged, widget, &KStandardItemListWidget::updateRoleEditorGeometry); } void KItemListView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) @@ -1092,6 +1092,20 @@ bool KItemListView::event(QEvent *event) return true; break; + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseDoubleClick: + case QEvent::GraphicsSceneContextMenu: + if (m_editingRole) { + for (KItemListWidget *widget : std::as_const(m_visibleItems)) { + auto *standardWidget = qobject_cast<KStandardItemListWidget *>(widget); + if (standardWidget && !standardWidget->isVisible() && !standardWidget->editedRole().isEmpty()) { + standardWidget->finishRoleEditing(); + break; + } + } + } + [[fallthrough]]; + default: // Forward all other events to the controller and handle them there if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) { @@ -1577,6 +1591,13 @@ void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet #endif } +void KItemListView::slotAnimationStarted(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType /* type */, const QVariant & /* endValue */) +{ + KStandardItemListWidget *listWidget = qobject_cast<KStandardItemListWidget *>(widget); + Q_ASSERT(widget); + listWidget->cancelRoleEditing(); +} + void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type) { KItemListWidget *itemListWidget = qobject_cast<KItemListWidget *>(widget); @@ -1926,9 +1947,6 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha if (animate) { if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) { - if (m_editingRole) { - Q_EMIT widget->roleEditingCanceled(widget->index(), QByteArray(), QVariant()); - } m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); applyNewPos = false; } @@ -2022,6 +2040,10 @@ QList<int> KItemListView::recycleInvisibleItems(int firstVisibleIndex, int lastV const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); if (invisible) { + if (!widget->editedRole().isEmpty()) { + widget->setVisible(false); + continue; + } if (m_animation->isStarted(widget)) { if (hint == NoAnimation) { // Stopping the animation will call KItemListView::slotAnimationFinished() @@ -2804,7 +2826,7 @@ bool KItemListView::hasSiblingSuccessor(int index) const void KItemListView::disconnectRoleEditingSignals(int index) { - KStandardItemListWidget *widget = qobject_cast<KStandardItemListWidget *>(m_visibleItems.value(index)); + KItemListWidget *widget = m_visibleItems.value(index); if (!widget) { return; } diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 415710e02..c8ab796a9 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -446,6 +446,7 @@ protected Q_SLOTS: private Q_SLOTS: void slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type); + void slotAnimationStarted(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type, const QVariant &endValue); void slotRubberBandPosChanged(); void slotRubberBandActivationChanged(bool active); diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp index 089211716..2d94c4303 100644 --- a/src/kitemviews/kitemlistwidget.cpp +++ b/src/kitemviews/kitemlistwidget.cpp @@ -121,9 +121,13 @@ void KItemListWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *o painter->fillRect(backgroundRect, backgroundColor); } + const QStyle::State selectedState(m_selected ? QStyle::State_Selected : QStyle::State(0)); if ((m_selected || m_current) && m_editedRole.isEmpty()) { const QStyle::State activeState(isActiveWindow() && widget->hasFocus() ? QStyle::State_Active : 0); - drawItemStyleOption(painter, widget, activeState | QStyle::State_Enabled | QStyle::State_Selected | QStyle::State_Item); + // Only pass State_Selected when the item is actually selected, not just when + // it has keyboard focus (m_current), to avoid styles drawing a persistent + // selection highlight on focused but unselected items. + drawItemStyleOption(painter, widget, activeState | selectedState | QStyle::State_Enabled | QStyle::State_Item); } if (m_hoverOpacity > 0.0) { @@ -135,7 +139,7 @@ void KItemListWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *o QPainter pixmapPainter(m_hoverCache); const QStyle::State activeState(isActiveWindow() && widget->hasFocus() ? QStyle::State_Active | QStyle::State_Enabled : 0); - drawItemStyleOption(&pixmapPainter, widget, activeState | QStyle::State_MouseOver | QStyle::State_Item); + drawItemStyleOption(&pixmapPainter, widget, activeState | selectedState | QStyle::State_MouseOver | QStyle::State_Item); } const qreal opacity = painter->opacity(); @@ -231,6 +235,7 @@ void KItemListWidget::setSelected(bool selected) m_selectionToggle->setChecked(selected); } selectedChanged(selected); + clearHoverCache(); update(); } } @@ -617,50 +622,69 @@ void KItemListWidget::setPressed(bool enabled) void KItemListWidget::drawItemStyleOption(QPainter *painter, QWidget *widget, QStyle::State styleState) { + painter->save(); QStyleOptionViewItem viewItemOption; - constexpr int roundness = 5; // From Breeze style. - constexpr qreal penWidth = 1.25; initStyleOption(&viewItemOption); viewItemOption.state = styleState; viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; viewItemOption.showDecorationSelected = true; viewItemOption.rect = selectionRectFull().toRect(); - QPainterPath path; - const qreal adjustment = 0.5 * penWidth; // Use same adjustments as Breeze strokedRect uses, to snap to pixelGrid. - path.addRoundedRect(selectionRectFull().adjusted(adjustment, adjustment, -adjustment, -adjustment), roundness, roundness); - QColor backgroundColor{widget->palette().color(QPalette::Accent)}; - painter->setRenderHint(QPainter::Antialiasing); - bool current = m_current && styleState & QStyle::State_Active; - - // Background item, alpha values are from - // https://invent.kde.org/plasma/libplasma/-/blob/master/src/desktoptheme/breeze/widgets/viewitem.svg - backgroundColor.setAlphaF(0.0); + const bool current = m_current && styleState & QStyle::State_Active; - if (m_clickHighlighted) { - backgroundColor.setAlphaF(1.0); - } else { - if (m_selected && m_hovered) { - backgroundColor.setAlphaF(0.40); - } else if (m_selected) { - backgroundColor.setAlphaF(0.32); - } else if (m_hovered) { - backgroundColor = widget->palette().color(QPalette::Text); - backgroundColor.setAlphaF(0.06); + // TODO: Remove this check after Plasma 6.8 release + // See: https://invent.kde.org/plasma/breeze/-/merge_requests/595 + if (style()->name() == QStringLiteral("breeze")) { + QColor backgroundColor{widget->palette().color(QPalette::Active, QPalette::Highlight)}; + backgroundColor.setAlphaF(0.0); + if (m_clickHighlighted) { + backgroundColor.setAlphaF(1.0); + } else { + if (m_selected && m_hovered) { + backgroundColor.setAlphaF(0.85); + } else if (m_selected) { + backgroundColor.setAlphaF(0.70); + } else if (m_hovered) { + backgroundColor = widget->palette().color(QPalette::Text); + backgroundColor.setAlphaF(0.12); + } } - } + painter->setRenderHint(QPainter::Antialiasing); + constexpr int roundness = 5; // From Breeze style. + constexpr qreal penWidth = 1.25; + QPainterPath path; + const qreal adjustment = 0.5 * penWidth; // Use same adjustments as Breeze strokedRect uses, to snap to pixelGrid. + path.addRoundedRect(selectionRectFull().adjusted(adjustment, adjustment, -adjustment, -adjustment), roundness, roundness); + painter->fillPath(path, backgroundColor); - painter->fillPath(path, backgroundColor); + // Focus decoration + if (current) { + QColor focusColor{widget->palette().color(QPalette::Active, QPalette::Highlight)}; + // Set the pen color lighter or darker depending on background color + focusColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? focusColor.darker(110) : focusColor.lighter(110); + focusColor.setAlphaF(m_selected || m_hovered ? 1.0 : 0.8); + QPen pen{focusColor, penWidth}; + pen.setCosmetic(true); + painter->strokePath(path, pen); + } + } else { + style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &viewItemOption, painter, widget); - // Focus decoration - if (current) { - QColor focusColor{widget->palette().color(QPalette::Accent)}; - focusColor = m_styleOption.palette.color(QPalette::Base).lightnessF() > 0.5 ? focusColor.darker(110) : focusColor.lighter(110); - focusColor.setAlphaF(m_selected || m_hovered ? 1.0 : 0.8); - // Set the pen color lighter or darker depending on background color - QPen pen{focusColor, penWidth}; - pen.setCosmetic(true); - painter->strokePath(path, pen); + // Focus decoration + if (current) { + QStyleOptionFocusRect focusRectOption; + initStyleOption(&focusRectOption); + focusRectOption.state = QStyle::State_HasFocus; + if (m_selected && widget->hasFocus()) { + focusRectOption.state = QStyle::State_HasFocus | QStyle::State_Selected; + } + if (m_hovered) { + focusRectOption.state |= QStyle::State_MouseOver; + } + focusRectOption.rect = viewItemOption.rect; + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusRectOption, painter, widget); + } } + painter->restore(); } #include "moc_kitemlistwidget.cpp" diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index 494473c62..ddbb8b6a1 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -854,7 +854,7 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray ¤t, const { Q_UNUSED(previous) - QGraphicsView *parent = scene()->views()[0]; + QGraphicsView *parent = !scene() || scene()->views().isEmpty() ? nullptr : scene()->views()[0]; if (current.isEmpty() || !parent || current != "text") { if (m_roleEditor) { Q_EMIT roleEditingCanceled(index(), current, data().value(current)); @@ -891,16 +891,7 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray ¤t, const connect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, this, &KStandardItemListWidget::slotRoleEditingCanceled); connect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, this, &KStandardItemListWidget::slotRoleEditingFinished); - // Adjust the geometry of the editor - QRectF rect = roleEditingRect(current); - const int frameWidth = m_roleEditor->frameWidth(); - rect.adjust(-frameWidth, -frameWidth, frameWidth, frameWidth); - rect.translate(pos()); - if (rect.right() > parent->width()) { - rect.setWidth(parent->width() - rect.left()); - } - m_roleEditor->setGeometry(rect.toRect()); - m_roleEditor->autoAdjustSize(); + updateRoleEditorGeometry(); m_roleEditor->show(); m_roleEditor->setFocus(); setHovered(false); @@ -932,6 +923,13 @@ void KStandardItemListWidget::showEvent(QShowEvent *event) { KItemListWidget::showEvent(event); + if (m_roleEditor) { + m_roleEditor->setFinishedSignalBlocked(false); + updateRoleEditorGeometry(); + m_roleEditor->show(); + m_roleEditor->setFocus(); + } + // Listen to changes of the clipboard to mark the item as cut/uncut KFileItemClipboard *clipboard = KFileItemClipboard::instance(); @@ -943,6 +941,11 @@ void KStandardItemListWidget::showEvent(QShowEvent *event) void KStandardItemListWidget::hideEvent(QHideEvent *event) { + if (m_roleEditor) { + m_roleEditor->setFinishedSignalBlocked(true); + m_roleEditor->hide(); + } + disconnect(KFileItemClipboard::instance(), &KFileItemClipboard::cutItemsChanged, this, &KStandardItemListWidget::slotCutItemsChanged); KItemListWidget::hideEvent(event); @@ -964,6 +967,31 @@ void KStandardItemListWidget::finishRoleEditing() } } +void KStandardItemListWidget::cancelRoleEditing() +{ + if (!editedRole().isEmpty() && m_roleEditor) { + slotRoleEditingCanceled(editedRole(), KIO::encodeFileName(m_roleEditor->toPlainText())); + } +} + +void KStandardItemListWidget::updateRoleEditorGeometry() +{ + if (!m_roleEditor || editedRole().isEmpty() || !scene() || scene()->views().isEmpty()) { + return; + } + + auto *parent = scene()->views()[0]; + QRectF rect = roleEditingRect(editedRole()); + const int frameWidth = m_roleEditor->frameWidth(); + rect.adjust(-frameWidth, -frameWidth, frameWidth, frameWidth); + rect.translate(pos()); + if (rect.right() > parent->width()) { + rect.setWidth(parent->width() - rect.left()); + } + m_roleEditor->setGeometry(rect.toRect()); + m_roleEditor->autoAdjustSize(); +} + void KStandardItemListWidget::slotCutItemsChanged() { const QUrl itemUrl = data().value("url").toUrl(); diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h index 9e6fff935..e0a32b745 100644 --- a/src/kitemviews/kstandarditemlistwidget.h +++ b/src/kitemviews/kstandarditemlistwidget.h @@ -196,6 +196,8 @@ protected: public Q_SLOTS: void finishRoleEditing(); + void cancelRoleEditing(); + void updateRoleEditorGeometry(); private Q_SLOTS: void slotCutItemsChanged(); diff --git a/src/kitemviews/private/kfileitemmodelfilter.cpp b/src/kitemviews/private/kfileitemmodelfilter.cpp index 45c62e7ca..48d2f6276 100644 --- a/src/kitemviews/private/kfileitemmodelfilter.cpp +++ b/src/kitemviews/private/kfileitemmodelfilter.cpp @@ -13,7 +13,8 @@ #include <KFileItem> KFileItemModelFilter::KFileItemModelFilter() - : m_useRegExp(false) + : m_filterMode(Glob) + , m_caseSensitive(false) , m_regExp(nullptr) , m_lowerCasePattern() , m_pattern() @@ -31,16 +32,29 @@ void KFileItemModelFilter::setPattern(const QString &filter) m_pattern = filter; m_lowerCasePattern = filter.toLower(); - if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) { - if (!m_regExp) { - m_regExp = new QRegularExpression(); - m_regExp->setPatternOptions(QRegularExpression::CaseInsensitiveOption); - } - m_regExp->setPattern(QRegularExpression::wildcardToRegularExpression(filter)); - m_useRegExp = m_regExp->isValid(); - } else { - m_useRegExp = false; - } + updateFilter(); +} + +void KFileItemModelFilter::setFilterMode(FilterMode mode) +{ + m_filterMode = mode; + updateFilter(); +} + +KFileItemModelFilter::FilterMode KFileItemModelFilter::filterMode() const +{ + return m_filterMode; +} + +void KFileItemModelFilter::setCaseSensitive(bool caseSensitive) +{ + m_caseSensitive = caseSensitive; + updateFilter(); +} + +bool KFileItemModelFilter::isCaseSensitive() const +{ + return m_caseSensitive; } QString KFileItemModelFilter::pattern() const @@ -48,6 +62,26 @@ QString KFileItemModelFilter::pattern() const return m_pattern; } +void KFileItemModelFilter::updateFilter() +{ + if (m_filterMode == PlainText) { + return; + } + + if (!m_regExp) { + m_regExp = new QRegularExpression(); + } + + QRegularExpression::PatternOptions options = m_caseSensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption; + if (m_filterMode == Regex) { + m_regExp->setPattern(m_pattern); + m_regExp->setPatternOptions(options); + } else if (m_filterMode == Glob) { + m_regExp->setPattern(QRegularExpression::wildcardToRegularExpression(m_pattern, QRegularExpression::UnanchoredWildcardConversion)); + m_regExp->setPatternOptions(options); + } +} + void KFileItemModelFilter::setMimeTypes(const QStringList &types) { m_mimeTypes = types; @@ -98,8 +132,10 @@ bool KFileItemModelFilter::matches(const KFileItem &item) const bool KFileItemModelFilter::matchesPattern(const KFileItem &item) const { - if (m_useRegExp) { - return m_regExp->match(item.text()).hasMatch(); + if (m_filterMode == Glob || m_filterMode == Regex) { + return m_regExp->isValid() && m_regExp->match(item.text()).hasMatch(); + } else if (m_caseSensitive) { + return item.text().contains(m_pattern); } else { return item.text().toLower().contains(m_lowerCasePattern); } diff --git a/src/kitemviews/private/kfileitemmodelfilter.h b/src/kitemviews/private/kfileitemmodelfilter.h index ce6cbeebb..9d93d42cc 100644 --- a/src/kitemviews/private/kfileitemmodelfilter.h +++ b/src/kitemviews/private/kfileitemmodelfilter.h @@ -28,16 +28,36 @@ public: KFileItemModelFilter(); virtual ~KFileItemModelFilter(); + /** Filtering modes of KFileItemModelFilter */ + enum FilterMode { + /** Substring matching. */ + PlainText = 0, + /** Matching with glob, default. */ + Glob, + /** Matching with regex. */ + Regex + }; + /** * Sets the pattern that is used for a comparison with the item - * in KFileItemModelFilter::matches(). Per default the pattern - * defines a sub-string. As soon as the pattern contains at least - * a '*', '?' or '[' the pattern represents a regular expression. + * in KFileItemModelFilter::matches(). */ void setPattern(const QString &pattern); QString pattern() const; /** + * Sets the filtering mode used in KFileItemModelFilter::matches(). + */ + void setFilterMode(FilterMode mode); + FilterMode filterMode() const; + + /** + * Enable or disable the case sensitive filtering. + */ + void setCaseSensitive(bool caseSensitive); + bool isCaseSensitive() const; + + /** * Set the list of mimetypes that are used for comparison with the * item in KFileItemModelFilter::matchesMimeType. */ @@ -73,8 +93,14 @@ private: */ bool matchesType(const KFileItem &item) const; - bool m_useRegExp; // If true, m_regExp is used for filtering, - // otherwise m_lowerCaseFilter is used. + /** + * Instantiate and configure m_regExp according to m_filterMode and m_caseSensitive. + */ + void updateFilter(); + + FilterMode m_filterMode; // The current filtering mode. + bool m_caseSensitive; // If true the matching will be case sensitive. + QRegularExpression *m_regExp; QString m_lowerCasePattern; // Lowercase version of m_filter for // faster comparison in matches(). diff --git a/src/kitemviews/private/kitemlistroleeditor.cpp b/src/kitemviews/private/kitemlistroleeditor.cpp index 6105d604f..cec9bb98b 100644 --- a/src/kitemviews/private/kitemlistroleeditor.cpp +++ b/src/kitemviews/private/kitemlistroleeditor.cpp @@ -64,6 +64,11 @@ bool KItemListRoleEditor::event(QEvent *event) return KTextEdit::event(event); } +void KItemListRoleEditor::setFinishedSignalBlocked(bool blocked) +{ + m_blockFinishedSignal = blocked; +} + void KItemListRoleEditor::keyPressEvent(QKeyEvent *event) { switch (event->key()) { @@ -141,17 +146,6 @@ void KItemListRoleEditor::autoAdjustSize() const auto originalSize = size(); auto newSize = originalSize; - document()->adjustSize(); - const qreal requiredWidth = document()->size().width(); - const qreal availableWidth = size().width() - frameBorder; - if (requiredWidth > availableWidth) { - qreal newWidth = requiredWidth + frameBorder; - if (parentWidget() && pos().x() + newWidth > parentWidget()->width()) { - newWidth = parentWidget()->width() - pos().x(); - } - newSize.setWidth(newWidth); - } - const qreal requiredHeight = document()->size().height(); const qreal availableHeight = size().height() - frameBorder; if (requiredHeight > availableHeight) { diff --git a/src/kitemviews/private/kitemlistroleeditor.h b/src/kitemviews/private/kitemlistroleeditor.h index eb8a9cb5e..3956380cd 100644 --- a/src/kitemviews/private/kitemlistroleeditor.h +++ b/src/kitemviews/private/kitemlistroleeditor.h @@ -45,6 +45,7 @@ public: QByteArray role() const; void setAllowUpDownKeyChainEdit(bool allowChainEdit); + void setFinishedSignalBlocked(bool blocked); bool eventFilter(QObject *watched, QEvent *event) override; Q_SIGNALS: diff --git a/src/kitemviews/private/kitemlistsmoothscroller.cpp b/src/kitemviews/private/kitemlistsmoothscroller.cpp index b7d0a8cd4..915b6b3d5 100644 --- a/src/kitemviews/private/kitemlistsmoothscroller.cpp +++ b/src/kitemviews/private/kitemlistsmoothscroller.cpp @@ -214,6 +214,7 @@ void KItemListSmoothScroller::handleWheelEvent(QWheelEvent *event) QWheelEvent *copy = event->clone(); QApplication::sendEvent(m_scrollBar, copy); event->setAccepted(copy->isAccepted()); + delete copy; m_smoothScrolling = previous; } diff --git a/src/kitemviews/private/kitemlistviewanimation.cpp b/src/kitemviews/private/kitemlistviewanimation.cpp index 2ea884461..db98713f7 100644 --- a/src/kitemviews/private/kitemlistviewanimation.cpp +++ b/src/kitemviews/private/kitemlistviewanimation.cpp @@ -143,6 +143,8 @@ void KItemListViewAnimation::start(QGraphicsWidget *widget, AnimationType type, m_animation[type].insert(widget, propertyAnim); propertyAnim->start(); + + Q_EMIT started(widget, type, endValue); } void KItemListViewAnimation::stop(QGraphicsWidget *widget, AnimationType type) diff --git a/src/kitemviews/private/kitemlistviewanimation.h b/src/kitemviews/private/kitemlistviewanimation.h index 821566161..a23715a8a 100644 --- a/src/kitemviews/private/kitemlistviewanimation.h +++ b/src/kitemviews/private/kitemlistviewanimation.h @@ -74,6 +74,7 @@ public: Q_SIGNALS: void finished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type); + void started(QGraphicsWidget *widget, AnimationType type, const QVariant &endValue); private Q_SLOTS: void slotFinished(); diff --git a/src/kitemviews/private/kpixmapmodifier.cpp b/src/kitemviews/private/kpixmapmodifier.cpp index bf316b880..96aea26d4 100644 --- a/src/kitemviews/private/kpixmapmodifier.cpp +++ b/src/kitemviews/private/kpixmapmodifier.cpp @@ -15,6 +15,8 @@ #include "kpixmapmodifier.h" +#include "dolphin_iconsmodesettings.h" + #include <QGuiApplication> #include <QImage> #include <QPainter> @@ -281,7 +283,10 @@ void KPixmapModifier::scale(QPixmap &pixmap, const QSize &scaledSize) return; } qreal dpr = pixmap.devicePixelRatio(); - pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + const Qt::TransformationMode mode = IconsModeSettings::usePixelatedScaling() + ? Qt::FastTransformation + : Qt::SmoothTransformation; + pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, mode); pixmap.setDevicePixelRatio(dpr); } |
