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@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
