From 5e35194b00d84db6aea3370ee8bb0ad560428c62 Mon Sep 17 00:00:00 2001 From: Pan Zhang Date: Mon, 23 Mar 2026 10:12:10 +0800 Subject: kitemviews: Preserve inline rename when item scrolls out of view Inline rename was canceled when the edited item scrolled out of view. Scrolling could both finish the edit and recycle the item widget, causing the typed name to be lost. Keep the inline rename editor alive while the item is temporarily offscreen. Update the editor geometry on scroll, avoid recycling the widget while it is being edited, and suppress the temporary FocusOut triggered by hiding the editor. If the user interacts with another item while the edited one is offscreen, finish the hidden edit first so normal selection behavior is preserved. BUG: 506884 --- src/kitemviews/kitemlistview.cpp | 20 +++++++++++- src/kitemviews/kstandarditemlistwidget.cpp | 43 +++++++++++++++++++------- src/kitemviews/kstandarditemlistwidget.h | 1 + src/kitemviews/private/kitemlistroleeditor.cpp | 5 +++ src/kitemviews/private/kitemlistroleeditor.h | 1 + 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 1d02ee5c0..452567f05 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -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(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())) { @@ -2026,6 +2040,10 @@ QList 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() diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index 9195f4e77..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); @@ -971,6 +974,24 @@ void KStandardItemListWidget::cancelRoleEditing() } } +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 992d41aa1..e0a32b745 100644 --- a/src/kitemviews/kstandarditemlistwidget.h +++ b/src/kitemviews/kstandarditemlistwidget.h @@ -197,6 +197,7 @@ protected: public Q_SLOTS: void finishRoleEditing(); void cancelRoleEditing(); + void updateRoleEditorGeometry(); private Q_SLOTS: void slotCutItemsChanged(); diff --git a/src/kitemviews/private/kitemlistroleeditor.cpp b/src/kitemviews/private/kitemlistroleeditor.cpp index 6105d604f..9cb44a829 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()) { 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: -- cgit v1.3