┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/kitemviews
diff options
context:
space:
mode:
Diffstat (limited to 'src/kitemviews')
-rw-r--r--src/kitemviews/kfileitemmodel.cpp273
-rw-r--r--src/kitemviews/kfileitemmodel.h6
-rw-r--r--src/kitemviews/kfileitemmodelrolesupdater.cpp2
-rw-r--r--src/kitemviews/kitemlistcontroller.cpp5
-rw-r--r--src/kitemviews/kitemlistcontroller.h7
-rw-r--r--src/kitemviews/kitemlistview.cpp38
-rw-r--r--src/kitemviews/kitemlistview.h1
-rw-r--r--src/kitemviews/kitemlistwidget.cpp94
-rw-r--r--src/kitemviews/kstandarditemlistwidget.cpp50
-rw-r--r--src/kitemviews/kstandarditemlistwidget.h2
-rw-r--r--src/kitemviews/private/kfileitemmodelfilter.cpp62
-rw-r--r--src/kitemviews/private/kfileitemmodelfilter.h36
-rw-r--r--src/kitemviews/private/kitemlistroleeditor.cpp16
-rw-r--r--src/kitemviews/private/kitemlistroleeditor.h1
-rw-r--r--src/kitemviews/private/kitemlistsmoothscroller.cpp1
-rw-r--r--src/kitemviews/private/kitemlistviewanimation.cpp2
-rw-r--r--src/kitemviews/private/kitemlistviewanimation.h1
17 files changed, 497 insertions, 100 deletions
diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp
index c8bac0b9d..699ce289c 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) {
@@ -2309,24 +2562,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;
-
- // Sort by baseName first
- const QString aBaseName = a.section('.', 0, 0, flags);
- const QString bBaseName = b.section('.', 0, 0, flags);
+ 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;
- 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 &current, 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..32118c569 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::Highlight)};
+ backgroundColor.setAlphaF(0.0);
+ 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);
+ }
}
- }
+ 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::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 &current, 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 &current, 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();