[utilities/krusader] krusader/Panel/PanelView: Added cycling through file name part selections when renaming files

Nikita Melnichenko null at kde.org
Tue Jun 30 07:00:02 BST 2020


Git commit d2d1affa75d0518efd062c4b53be4aabcd5733b6 by Nikita Melnichenko.
Committed on 30/06/2020 at 05:44.
Pushed by melnichenko into branch 'master'.

Added cycling through file name part selections when renaming files

Implemented a feature that allows cycling through various name
and extension selections in the file-renaming editor by consecutively
hitting the rename shortcut (F2 by default).

For simple names like "notes.txt", it will cycle through "notes.txt",
"notes" and "txt". For more complex names like "archive.tar.xz", it will
cycle through "archive.tar.xz", "archive", "archive.tar", "xz" and "tar.xz".
The feature will help users to quickly select part of the name they need
to rename.

In addition, fixed the warning appeared when consecutively hitting
the rename shortcut:
warning default unknown at 0 # edit: editing failed

ADDED: Cycling through file name part selections when renaming files
GUI: Added cycling through file name part selections when renaming files
CCBUG: 328923

Discussion: https://invent.kde.org/utilities/krusader/-/merge_requests/13

M  +16   -2    krusader/Panel/PanelView/krinterbriefview.cpp
M  +16   -2    krusader/Panel/PanelView/krinterdetailedview.cpp
M  +99   -6    krusader/Panel/PanelView/krviewitemdelegate.cpp
M  +11   -0    krusader/Panel/PanelView/krviewitemdelegate.h

https://invent.kde.org/utilities/krusader/commit/d2d1affa75d0518efd062c4b53be4aabcd5733b6

diff --git a/krusader/Panel/PanelView/krinterbriefview.cpp b/krusader/Panel/PanelView/krinterbriefview.cpp
index 88098cd6..5d4af05d 100644
--- a/krusader/Panel/PanelView/krinterbriefview.cpp
+++ b/krusader/Panel/PanelView/krinterbriefview.cpp
@@ -21,6 +21,7 @@
 #include "krinterbriefview.h"
 
 // QtCore
+#include <QDebug>
 #include <QDir>
 #include <QHashIterator>
 #include <QItemSelection>
@@ -621,8 +622,21 @@ void KrInterBriefView::currentChanged(const QModelIndex & current, const QModelI
 
 void KrInterBriefView::renameCurrentItem()
 {
-    QModelIndex cIndex = currentIndex();
-    QModelIndex nameIndex = _model->index(cIndex.row(), KrViewProperties::Name);
+    QModelIndex nameIndex = _model->index(currentIndex().row(), KrViewProperties::Name);
+
+    // cycle through various text selections if we are in the editing mode already
+    if (state() == QAbstractItemView::EditingState) {
+        auto delegate = dynamic_cast<KrViewItemDelegate *>(itemDelegate(nameIndex));
+        if (!delegate) {
+            qWarning() << "KrInterView item delegate is not KrViewItemDelegate, selection is not updated";
+            return;
+        }
+
+        delegate->cycleEditorSelection();
+        return;
+    }
+
+    // create and show file name editor
     edit(nameIndex);
     updateEditorData();
     update(nameIndex);
diff --git a/krusader/Panel/PanelView/krinterdetailedview.cpp b/krusader/Panel/PanelView/krinterdetailedview.cpp
index 5734576d..aabc34cd 100644
--- a/krusader/Panel/PanelView/krinterdetailedview.cpp
+++ b/krusader/Panel/PanelView/krinterdetailedview.cpp
@@ -24,6 +24,7 @@
 // QtCore
 #include <QDir>
 #include <QHashIterator>
+#include <QDebug>
 // QtWidgets
 #include <QApplication>
 #include <QDirModel>
@@ -260,8 +261,21 @@ bool KrInterDetailedView::event(QEvent * e)
 
 void KrInterDetailedView::renameCurrentItem()
 {
-    QModelIndex cIndex = currentIndex();
-    QModelIndex nameIndex = _model->index(cIndex.row(), KrViewProperties::Name);
+    QModelIndex nameIndex = _model->index(currentIndex().row(), KrViewProperties::Name);
+
+    // cycle through various text selections if we are in the editing mode already
+    if (state() == QAbstractItemView::EditingState) {
+        auto delegate = dynamic_cast<KrViewItemDelegate *>(itemDelegate(nameIndex));
+        if (!delegate) {
+            qWarning() << "KrInterView item delegate is not KrViewItemDelegate, selection is not updated";
+            return;
+        }
+
+        delegate->cycleEditorSelection();
+        return;
+    }
+
+    // create and show file name editor
     edit(nameIndex);
     updateEditorData();
     update(nameIndex);
diff --git a/krusader/Panel/PanelView/krviewitemdelegate.cpp b/krusader/Panel/PanelView/krviewitemdelegate.cpp
index ae8c0076..af561e5b 100644
--- a/krusader/Panel/PanelView/krviewitemdelegate.cpp
+++ b/krusader/Panel/PanelView/krviewitemdelegate.cpp
@@ -25,6 +25,8 @@
 #include "../listpanel.h"
 #include "../krcolorcache.h"
 
+// QtCore
+#include <QDebug>
 // QtGui
 #include <QKeyEvent>
 #include <QPainter>
@@ -36,7 +38,7 @@
 #include <KConfigCore/KSharedConfig>
 
 KrViewItemDelegate::KrViewItemDelegate(QObject *parent) :
-        QItemDelegate(parent), _currentlyEdited(-1), _dontDraw(false) {}
+    QItemDelegate(parent), _currentlyEdited(-1), _dontDraw(false), _editor(nullptr) {}
 
 void KrViewItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
 {
@@ -55,7 +57,8 @@ void KrViewItemDelegate::drawDisplay(QPainter * painter, const QStyleOptionViewI
 QWidget * KrViewItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &sovi, const QModelIndex &index) const
 {
     _currentlyEdited = index.row();
-    return QItemDelegate::createEditor(parent, sovi, index);
+    _editor = QItemDelegate::createEditor(parent, sovi, index);
+    return _editor;
 }
 
 void KrViewItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
@@ -109,7 +112,7 @@ bool KrViewItemDelegate::eventFilter(QObject *object, QEvent *event)
         switch (dynamic_cast<QKeyEvent *>(event)->key()) {
         case Qt::Key_Tab:
         case Qt::Key_Backtab:
-            _currentlyEdited = -1;
+            onEditorClose();
             emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache);
             return true;
         case Qt::Key_Enter:
@@ -120,14 +123,14 @@ bool KrViewItemDelegate::eventFilter(QObject *object, QEvent *event)
                 event->accept();
                 emit commitData(editor);
                 emit closeEditor(editor, QAbstractItemDelegate::SubmitModelCache);
-                _currentlyEdited = -1;
+                onEditorClose();
                 return true;
             }
             return false;
         case Qt::Key_Escape:
             event->accept();
             // don't commit data
-            _currentlyEdited = -1;
+            onEditorClose();
             emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache);
             break;
         default:
@@ -151,7 +154,7 @@ bool KrViewItemDelegate::eventFilter(QObject *object, QEvent *event)
                     && !QApplication::activeModalWidget()->isAncestorOf(editor)
                     && qobject_cast<QDialog*>(QApplication::activeModalWidget()))
                 return false;
-            _currentlyEdited = -1;
+            onEditorClose();
             // manually set focus back to panel after rename canceled by focusing another window
             ACTIVE_PANEL->gui->slotFocusOnMe();
             emit closeEditor(editor, RevertModelCache);
@@ -166,3 +169,93 @@ bool KrViewItemDelegate::eventFilter(QObject *object, QEvent *event)
     }
     return false;
 }
+
+//! Helper class to represent an editor selection
+class EditorSelection : public QPair<int, int>
+{
+public:
+    EditorSelection(int start, int length) : QPair<int, int>(start, length) {}
+
+    int start() const { return first; }
+    int length() const { return second; }
+};
+
+//! Generate helpful file name selections: full name (always present), name candidates, extension candidates
+static QList<EditorSelection> generateFileNameSelections(const QString &text)
+{
+    auto selections = QList<EditorSelection>();
+    auto length = text.length();
+    auto parts = text.split('.');
+
+    // append full selection
+    selections.append(EditorSelection(0, length));
+
+    // append forward selections
+    int selectionLength = 0;
+    bool isFirstPart = true;
+    for (auto part : parts) {
+        // if the part is not the first one, we need to add one character to account for the dot
+        selectionLength += part.length() + !isFirstPart;
+        isFirstPart = false;
+        // if we reached the full length, don't add the selection, since it's a full selection
+        if (selectionLength == length)
+            break;
+
+        // don't add empty selections (could happen if the full name starts with a dot)
+        if (selectionLength > 0)
+            selections.append(EditorSelection(0, selectionLength));
+    }
+
+    // append backward selections
+    std::reverse(parts.begin(), parts.end());
+    selectionLength = 0;
+    isFirstPart = true;
+    for (auto part : parts) {
+        // if the part is not the first one, we need to add one character to account for the dot
+        selectionLength += part.length() + !isFirstPart;
+        isFirstPart = false;
+        // if we reached the full length, don't add the selection, since it's a full selection
+        if (selectionLength == length)
+            break;
+
+        // don't add empty selections (could happen if the full name ends with a dot)
+        if (selectionLength > 0)
+            selections.append(EditorSelection(length - selectionLength, selectionLength));
+    }
+
+    return selections;
+}
+
+void KrViewItemDelegate::cycleEditorSelection()
+{
+    auto editor = qobject_cast<QLineEdit *>(_editor);
+    if (!editor) {
+        qWarning() << "Unable to cycle through editor selections due to a missing or unsupported type of item editor" << _editor;
+        return;
+    }
+
+    EditorSelection currentSelection(editor->selectionStart(), editor->selectionLength());
+    auto text = editor->text();
+    auto selections = generateFileNameSelections(text);
+
+    // try to find current selection in the list
+    int currentIndex = 0;
+    for (auto selection : selections) {
+        if (selection == currentSelection)
+            break;
+        currentIndex++;
+    }
+
+    // if we found current selection, pick the next in the cycle
+    auto selectionCount = selections.length();
+    if (currentIndex < selections.length())
+        currentIndex = (currentIndex + 1) % selectionCount;
+    // otherwise pick the first one - the full selection
+    else
+        currentIndex = 0;
+
+    // set the selection
+    auto selection = selections[currentIndex];
+    qDebug() << "setting selection" << selection << "index" << currentIndex;
+    editor->setSelection(selection.start(), selection.length());
+}
diff --git a/krusader/Panel/PanelView/krviewitemdelegate.h b/krusader/Panel/PanelView/krviewitemdelegate.h
index f1a47742..d9de1269 100644
--- a/krusader/Panel/PanelView/krviewitemdelegate.h
+++ b/krusader/Panel/PanelView/krviewitemdelegate.h
@@ -40,9 +40,20 @@ public:
                    const QModelIndex &index) const override;
     bool eventFilter(QObject *object, QEvent *event) override;
 
+    /// Set the next file name selection in the editor.
+    void cycleEditorSelection();
+
 private:
     mutable int _currentlyEdited;
     mutable bool _dontDraw;
+    mutable QWidget *_editor;
+
+    /// Init editor-related members when editor is closed.
+    void onEditorClose()
+    {
+        _currentlyEdited = -1;
+        _editor = nullptr;
+    }
 };
 
 #endif


More information about the kde-doc-english mailing list