File indexing completed on 2024-05-12 17:22:03

0001 /*
0002     SPDX-FileCopyrightText: 2009 Csaba Karai <cskarai@freemail.hu>
0003     SPDX-FileCopyrightText: 2009-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "krviewitemdelegate.h"
0009 
0010 #include "../../compat.h"
0011 #include "../krcolorcache.h"
0012 #include "../krglobal.h"
0013 #include "../listpanel.h"
0014 #include "krviewproperties.h"
0015 
0016 // QtCore
0017 #include <QDebug>
0018 // QtGui
0019 #include <QKeyEvent>
0020 #include <QPainter>
0021 // QtWidgets
0022 #include <QApplication>
0023 #include <QDialog>
0024 #include <QLineEdit>
0025 
0026 #include <KConfigCore/KSharedConfig>
0027 
0028 KrViewItemDelegate::KrViewItemDelegate(QObject *parent)
0029     : QItemDelegate(parent)
0030     , _currentlyEdited(-1)
0031     , _dontDraw(false)
0032     , _editor(nullptr)
0033 {
0034 }
0035 
0036 void KrViewItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0037 {
0038     QStyleOptionViewItem opt = option;
0039     opt.state &= ~QStyle::State_Selected;
0040     _dontDraw = (_currentlyEdited == index.row()) && (index.column() == KrViewProperties::Ext);
0041     QItemDelegate::paint(painter, opt, index);
0042 }
0043 
0044 void KrViewItemDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const
0045 {
0046     if (!_dontDraw)
0047         QItemDelegate::drawDisplay(painter, option, rect, text);
0048 }
0049 
0050 QWidget *KrViewItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &sovi, const QModelIndex &index) const
0051 {
0052     _currentlyEdited = index.row();
0053     _editor = QItemDelegate::createEditor(parent, sovi, index);
0054     return _editor;
0055 }
0056 
0057 void KrViewItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
0058 {
0059     QItemDelegate::setEditorData(editor, index);
0060     auto *lineEdit = qobject_cast<QLineEdit *>(editor);
0061     if (lineEdit) {
0062         KConfigGroup gl(krConfig, "Look&Feel");
0063         QFont font = index.data(Qt::FontRole).value<QFont>();
0064         lineEdit->setFont(font);
0065         if (gl.readEntry("Rename Selects Extension", true))
0066             lineEdit->selectAll();
0067         else {
0068             QString nameWithoutExt = index.data(Qt::UserRole).toString();
0069             lineEdit->deselect();
0070             lineEdit->setSelection(0, nameWithoutExt.length());
0071         }
0072 
0073         KrColorSettings colorSettings;
0074 
0075         if (!colorSettings.getBoolValue("KDE Default")) {
0076             QPalette renamePalette = lineEdit->palette();
0077 
0078             if (!colorSettings.getColorTextValue("Rename Foreground").isEmpty())
0079                 renamePalette.setColor(QPalette::Text, colorSettings.getColorValue("Rename Foreground"));
0080 
0081             if (!colorSettings.getColorTextValue("Rename Background").isEmpty())
0082                 renamePalette.setColor(QPalette::Base, colorSettings.getColorValue("Rename Background"));
0083 
0084             lineEdit->setPalette(renamePalette);
0085         }
0086     }
0087 }
0088 
0089 QSize KrViewItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0090 {
0091     QSize size = QItemDelegate::sizeHint(option, index);
0092     if (size.isEmpty()) {
0093         // prevent items without text from bloating the view vertically
0094         return QSize(0, 0);
0095     }
0096     return size;
0097 }
0098 
0099 bool KrViewItemDelegate::eventFilter(QObject *object, QEvent *event)
0100 {
0101     QWidget *editor = qobject_cast<QWidget *>(object);
0102     if (!editor)
0103         return false;
0104     if (event->type() == QEvent::KeyPress) {
0105         switch (dynamic_cast<QKeyEvent *>(event)->key()) {
0106         case Qt::Key_Tab:
0107         case Qt::Key_Backtab:
0108             onEditorClose();
0109             emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache);
0110             return true;
0111         case Qt::Key_Enter:
0112         case Qt::Key_Return:
0113             if (auto *e = qobject_cast<QLineEdit *>(editor)) {
0114                 if (!e->hasAcceptableInput())
0115                     return true;
0116                 event->accept();
0117                 emit commitData(editor);
0118                 emit closeEditor(editor, QAbstractItemDelegate::SubmitModelCache);
0119                 onEditorClose();
0120                 return true;
0121             }
0122             return false;
0123         case Qt::Key_Escape:
0124             event->accept();
0125             // don't commit data
0126             onEditorClose();
0127             emit closeEditor(editor, QAbstractItemDelegate::RevertModelCache);
0128             break;
0129         default:
0130             return false;
0131         }
0132 
0133         if (editor->parentWidget())
0134             editor->parentWidget()->setFocus();
0135         return true;
0136     } else if (event->type() == QEvent::FocusOut) {
0137         if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) {
0138             QWidget *w = QApplication::focusWidget();
0139             while (w) { // don't worry about focus changes internally in the editor
0140                 if (w == editor)
0141                     return false;
0142                 w = w->parentWidget();
0143             }
0144             // Opening a modal dialog will start a new eventloop
0145             // that will process the deleteLater event.
0146             if (QApplication::activeModalWidget() && !QApplication::activeModalWidget()->isAncestorOf(editor)
0147                 && qobject_cast<QDialog *>(QApplication::activeModalWidget()))
0148                 return false;
0149             onEditorClose();
0150             // manually set focus back to panel after rename canceled by focusing another window
0151             ACTIVE_PANEL->gui->slotFocusOnMe();
0152             emit closeEditor(editor, RevertModelCache);
0153         }
0154     } else if (event->type() == QEvent::ShortcutOverride) {
0155         const QKeyEvent *ke = dynamic_cast<QKeyEvent *>(event);
0156         if (ke->key() == Qt::Key_Escape || (ke->key() == Qt::Key_Backspace && ke->modifiers() == Qt::ControlModifier)) {
0157             event->accept();
0158             return true;
0159         }
0160     }
0161     return false;
0162 }
0163 
0164 //! Helper class to represent an editor selection
0165 class EditorSelection : public QPair<int, int>
0166 {
0167 public:
0168     EditorSelection(int start, int length)
0169         : QPair<int, int>(start, length)
0170     {
0171     }
0172 
0173     int start() const
0174     {
0175         return first;
0176     }
0177     int length() const
0178     {
0179         return second;
0180     }
0181 };
0182 
0183 //! Generate helpful file name selections: full name (always present), name candidates, extension candidates
0184 static QList<EditorSelection> generateFileNameSelections(const QString &text)
0185 {
0186     auto selections = QList<EditorSelection>();
0187     auto length = text.length();
0188     auto parts = text.split('.');
0189 
0190     // append full selection
0191     selections.append(EditorSelection(0, length));
0192 
0193     // append forward selections
0194     int selectionLength = 0;
0195     bool isFirstPart = true;
0196     for (auto part : parts) {
0197         // if the part is not the first one, we need to add one character to account for the dot
0198         selectionLength += part.length() + !isFirstPart;
0199         isFirstPart = false;
0200         // if we reached the full length, don't add the selection, since it's a full selection
0201         if (selectionLength == length)
0202             break;
0203 
0204         // don't add empty selections (could happen if the full name starts with a dot)
0205         if (selectionLength > 0)
0206             selections.append(EditorSelection(0, selectionLength));
0207     }
0208 
0209     // append backward selections
0210     std::reverse(parts.begin(), parts.end());
0211     selectionLength = 0;
0212     isFirstPart = true;
0213     for (auto part : parts) {
0214         // if the part is not the first one, we need to add one character to account for the dot
0215         selectionLength += part.length() + !isFirstPart;
0216         isFirstPart = false;
0217         // if we reached the full length, don't add the selection, since it's a full selection
0218         if (selectionLength == length)
0219             break;
0220 
0221         // don't add empty selections (could happen if the full name ends with a dot)
0222         if (selectionLength > 0)
0223             selections.append(EditorSelection(length - selectionLength, selectionLength));
0224     }
0225 
0226     return selections;
0227 }
0228 
0229 void KrViewItemDelegate::cycleEditorSelection()
0230 {
0231     auto editor = qobject_cast<QLineEdit *>(_editor);
0232     if (!editor) {
0233         qWarning() << "Unable to cycle through editor selections due to a missing or unsupported type of item editor" << _editor;
0234         return;
0235     }
0236 
0237     EditorSelection currentSelection(editor->selectionStart(), editor->selectionLength());
0238     auto text = editor->text();
0239     const auto selections = generateFileNameSelections(text);
0240 
0241     // try to find current selection in the list
0242     int currentIndex = 0;
0243     for (auto selection : selections) {
0244         if (selection == currentSelection)
0245             break;
0246         currentIndex++;
0247     }
0248 
0249     // if we found current selection, pick the next in the cycle
0250     auto selectionCount = selections.length();
0251     if (currentIndex < selections.length())
0252         currentIndex = (currentIndex + 1) % selectionCount;
0253     // otherwise pick the first one - the full selection
0254     else
0255         currentIndex = 0;
0256 
0257     // set the selection
0258     auto selection = selections[currentIndex];
0259     qDebug() << "setting selection" << selection << "index" << currentIndex;
0260     editor->setSelection(selection.start(), selection.length());
0261 }