File indexing completed on 2024-04-28 15:39:43

0001 // SPDX-FileCopyrightText: 2003-2019 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2020 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0004 // SPDX-FileCopyrightText: 2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0005 //
0006 // SPDX-License-Identifier: GPL-2.0-or-later
0007 
0008 #include "CompletableLineEdit.h"
0009 
0010 #include "ListSelect.h"
0011 
0012 #include <QKeyEvent>
0013 #include <QRegExp>
0014 #include <QTreeWidgetItem>
0015 #include <QTreeWidgetItemIterator>
0016 #include <kcompletion_version.h>
0017 
0018 AnnotationDialog::CompletableLineEdit::CompletableLineEdit(ListSelect *parent)
0019     : KLineEdit(parent)
0020     , m_listSelect(parent)
0021 {
0022 }
0023 
0024 AnnotationDialog::CompletableLineEdit::CompletableLineEdit(AnnotationDialog::ListSelect *ls, QWidget *parent)
0025     : KLineEdit(parent)
0026     , m_listSelect(ls)
0027 {
0028 }
0029 
0030 void AnnotationDialog::CompletableLineEdit::setListView(QTreeWidget *listView)
0031 {
0032     m_listView = listView;
0033 }
0034 
0035 void AnnotationDialog::CompletableLineEdit::setMode(UsageMode mode)
0036 {
0037     m_mode = mode;
0038 }
0039 
0040 void AnnotationDialog::CompletableLineEdit::keyPressEvent(QKeyEvent *ev)
0041 {
0042     if (ev->key() == Qt::Key_Down || ev->key() == Qt::Key_Up) {
0043         selectPrevNextMatch(ev->key() == Qt::Key_Down);
0044         return;
0045     }
0046 
0047     if (m_mode == SearchMode && (ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Enter)) { // Confirm autocomplete, deselect all text
0048         handleSpecialKeysInSearch(ev);
0049         m_listSelect->showOnlyItemsMatching(QString()); // Show all again after confirming autocomplete suggestion.
0050         return;
0051     }
0052 
0053     if (m_mode != SearchMode && isSpecialKey(ev))
0054         return; // Don't insert the special character.
0055 
0056     if (ev->key() == Qt::Key_Space && ev->modifiers() & Qt::ControlModifier) {
0057         mergePreviousImageSelection();
0058         return;
0059     }
0060 
0061     QString prevContent = text();
0062 
0063     if (ev->text().isEmpty() || !ev->text()[0].isPrint()) {
0064         // If final Return is handled by the default implementation,
0065         // it can "leak" to other widgets. So we swallow it here:
0066         if (ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Enter)
0067 #if KCOMPLETION_VERSION >= QT_VERSION_CHECK(5, 81, 0)
0068             Q_EMIT KLineEdit::returnKeyPressed(text());
0069 #else
0070             Q_EMIT KLineEdit::returnPressed(text());
0071 #endif
0072         else
0073             KLineEdit::keyPressEvent(ev);
0074         if (prevContent != text())
0075             m_listSelect->showOnlyItemsMatching(text());
0076         return;
0077     }
0078 
0079     // &,|, or ! should result in the current item being inserted
0080     if (m_mode == SearchMode && isSpecialKey(ev)) {
0081         handleSpecialKeysInSearch(ev);
0082         m_listSelect->showOnlyItemsMatching(QString()); // Show all again after a special caracter.
0083         return;
0084     }
0085 
0086     int cursorPos = cursorPosition();
0087     int selStart = selectionStart();
0088 
0089     KLineEdit::keyPressEvent(ev);
0090 
0091     // Find the text of the current item
0092     int itemStart = 0;
0093     QString input = text();
0094     if (m_mode == SearchMode) {
0095         input = input.left(cursorPosition());
0096         itemStart = input.lastIndexOf(QRegExp(QString::fromLatin1("[!&|]"))) + 1;
0097 
0098         if (itemStart > 0) {
0099             itemStart++;
0100         }
0101 
0102         input = input.mid(itemStart);
0103     }
0104 
0105     // Find the text in the listView
0106     QTreeWidgetItem *item = findItemInListView(input);
0107     if (!item && m_mode == SearchMode) {
0108         // revert
0109         setText(prevContent);
0110         setCursorPosition(cursorPos);
0111         item = findItemInListView(input);
0112         if (selStart >= 0)
0113             setSelection(selStart, prevContent.length()); // Reset previous selection.
0114     }
0115 
0116     if (item) {
0117         selectItemAndUpdateLineEdit(item, itemStart, input);
0118         m_listSelect->showOnlyItemsMatching(input);
0119     } else if (m_mode != SearchMode)
0120         m_listSelect->showOnlyItemsMatching(input);
0121 }
0122 
0123 /**
0124  * Search for the first item in the appearance order, which matches text.
0125  */
0126 QTreeWidgetItem *AnnotationDialog::CompletableLineEdit::findItemInListView(const QString &text)
0127 {
0128     for (QTreeWidgetItemIterator itemIt(m_listView); *itemIt; ++itemIt) {
0129         // Hide the "untagged image" tag from the auto-completion
0130         if ((*itemIt)->isHidden()) {
0131             continue;
0132         }
0133 
0134         if (itemMatchesText(*itemIt, text)) {
0135             return *itemIt;
0136         }
0137     }
0138 
0139     return nullptr;
0140 }
0141 
0142 bool AnnotationDialog::CompletableLineEdit::itemMatchesText(QTreeWidgetItem *item, const QString &text)
0143 {
0144     return item->text(0).toLower().startsWith(text.toLower());
0145 }
0146 
0147 bool AnnotationDialog::CompletableLineEdit::isSpecialKey(QKeyEvent *ev)
0148 {
0149     return (ev->text() == QString::fromLatin1("&") || ev->text() == QString::fromLatin1("|") || ev->text() == QString::fromLatin1("!")
0150             /* || ev->text() == "(" */
0151     );
0152 }
0153 
0154 void AnnotationDialog::CompletableLineEdit::handleSpecialKeysInSearch(QKeyEvent *ev)
0155 {
0156     int cursorPos = cursorPosition();
0157     QString txt;
0158     int additionalLength;
0159 
0160     if (!isSpecialKey(ev)) {
0161         txt = text().left(cursorPos) + ev->text() + text().mid(cursorPos);
0162         additionalLength = 0;
0163     } else {
0164         txt = text() + QString::fromUtf8(" %1 ").arg(ev->text());
0165         cursorPos += 2;
0166         additionalLength = 2;
0167     }
0168     setText(txt);
0169 
0170     if (!isSpecialKey(ev)) {
0171         // Special handling for ENTER to position the cursor correctly
0172         setText(text().left(text().size() - 1));
0173         cursorPos--;
0174     }
0175 
0176     setCursorPosition(cursorPos + ev->text().length() + additionalLength);
0177     deselect();
0178 
0179     // Select the item in the listView - not perfect but acceptable for now.
0180     int start = txt.lastIndexOf(QRegExp(QString::fromLatin1("[!&|]")), cursorPosition() - 2) + 1;
0181     if (start > 0) {
0182         start++;
0183     }
0184     QString input = txt.mid(start, cursorPosition() - start);
0185 
0186     if (!input.isEmpty()) {
0187         QTreeWidgetItem *item = findItemInListView(input);
0188         if (item) {
0189             item->setCheckState(0, Qt::Checked);
0190         }
0191     }
0192 }
0193 
0194 void AnnotationDialog::CompletableLineEdit::selectPrevNextMatch(bool next)
0195 {
0196     QTreeWidgetItem *item { nullptr };
0197 
0198     // the current item is usually the selected one...
0199     QList<QTreeWidgetItem *> selectedItems = m_listView->selectedItems();
0200     if (!selectedItems.isEmpty())
0201         item = selectedItems.at(0);
0202 
0203     // ...except when the selected one is filtered out:
0204     if (!item || item->isHidden()) {
0205         // in that case, we select the first item in the viewport
0206         item = m_listView->itemAt(0, 0);
0207     }
0208     if (!item)
0209         return;
0210     QTreeWidgetItem *baseItem = item;
0211 
0212     // only go to the next item, if there was a "previous" selected item:
0213     if (!selectedItems.isEmpty()) {
0214         if (next)
0215             item = m_listView->itemBelow(item);
0216         else
0217             item = m_listView->itemAbove(item);
0218     }
0219 
0220     // select current item if there is no next/prev item:
0221     if (!item) {
0222         item = baseItem;
0223     }
0224 
0225     // extract last component of line edit
0226     int itemStart = text().lastIndexOf(QRegExp(QString::fromLatin1("[!&|]"))) + 1;
0227     selectItemAndUpdateLineEdit(item, itemStart, text().left(selectionStart()));
0228 }
0229 
0230 void AnnotationDialog::CompletableLineEdit::selectItemAndUpdateLineEdit(QTreeWidgetItem *item,
0231                                                                         const int itemStart,
0232                                                                         const QString &inputText)
0233 {
0234     m_listView->setCurrentItem(item);
0235     m_listView->scrollToItem(item);
0236 
0237     QString txt = text().left(itemStart) + item->text(0) + text().mid(cursorPosition());
0238 
0239     setText(txt);
0240     setSelection(itemStart + inputText.length(), item->text(0).length() - inputText.length());
0241 }
0242 
0243 void AnnotationDialog::CompletableLineEdit::mergePreviousImageSelection()
0244 {
0245 }
0246 
0247 // vi:expandtab:tabstop=4 shiftwidth=4: