File indexing completed on 2024-06-16 05:01:27

0001 /* Copyright (C) 2009, 2011, 2013 by Glad Deschrijver <glad.deschrijver@gmail.com>
0002    Copyright (C) 2013 Thomas Lübking <thomas.luebking@gmail.com>
0003    Copyright (C) 2006 - 2015 Jan Kundrát <jkt@kde.org>
0004 
0005    This file is part of the Trojita Qt IMAP e-mail client,
0006    http://trojita.flaska.net/
0007 
0008    This program is free software; you can redistribute it and/or
0009    modify it under the terms of the GNU General Public License as
0010    published by the Free Software Foundation; either version 2 of
0011    the License or (at your option) version 3 or any later version
0012    accepted by the membership of KDE e.V. (or its successor approved
0013    by the membership of KDE e.V.), which shall act as a proxy
0014    defined in Section 14 of version 3 of the license.
0015 
0016    This program is distributed in the hope that it will be useful,
0017    but WITHOUT ANY WARRANTY; without even the implied warranty of
0018    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0019    GNU General Public License for more details.
0020 
0021    You should have received a copy of the GNU General Public License
0022    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0023 */
0024 
0025 #include <QAbstractItemView>
0026 #include <QCompleter>
0027 #include <QKeyEvent>
0028 #include "Common/InvokeMethod.h"
0029 #include "Gui/LineEdit.h"
0030 #include "UiUtils/IconLoader.h"
0031 
0032 LineEdit::LineEdit(const QString &text, QWidget *parent)
0033     : QLineEdit(text, parent), m_historyEnabled(false), m_historyPosition(0)
0034 {
0035     setClearButtonEnabled(true);
0036     connect(this, &QLineEdit::editingFinished, this, [this](){
0037         emit textEditingFinished(this->text());
0038     });
0039 }
0040 
0041 LineEdit::LineEdit(QWidget *parent)
0042     : LineEdit(QString(), parent)
0043 {
0044 }
0045 
0046 bool LineEdit::isHistoryEnabled()
0047 {
0048     return m_historyEnabled;
0049 }
0050 
0051 void LineEdit::setHistoryEnabled(bool enabled)
0052 {
0053     if (enabled == m_historyEnabled)
0054         return;
0055     m_historyEnabled = enabled;
0056     if (m_historyEnabled) {
0057         // queued so we learn and update the history list *after* the user selected an item from popup
0058         connect(this, &QLineEdit::returnPressed, this, &LineEdit::learnEntry, Qt::QueuedConnection);
0059         QCompleter *completer = new QCompleter(QStringList(), this);
0060         completer->setCaseSensitivity(Qt::CaseInsensitive);
0061         completer->setCompletionMode(QCompleter::InlineCompletion);
0062         setCompleter(completer);
0063     } else {
0064         disconnect(this, &QLineEdit::returnPressed, this, &LineEdit::learnEntry);
0065         delete completer();
0066         setCompleter(nullptr);
0067     }
0068 }
0069 
0070 bool LineEdit::eventFilter(QObject *o, QEvent *e)
0071 {
0072     if (e->type() == QEvent::Hide && o == completer()->popup()) {
0073         // next event cycle, the popup is deleted by the change and "return true" does not help
0074         // connect queued to not manipulate the history *during* selection from popup, but *after*
0075         CALL_LATER_NOARG(this, restoreInlineCompletion);
0076     }
0077     return false;
0078 }
0079 
0080 void LineEdit::keyReleaseEvent(QKeyEvent *ke)
0081 {
0082     if (ke->key() == Qt::Key_Escape) {
0083         QLineEdit::keyReleaseEvent(ke);
0084         emit escapePressed();
0085         return;
0086     }
0087 
0088     if (!m_historyEnabled || completer()->completionMode() == QCompleter::UnfilteredPopupCompletion ||
0089         !(ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down)) {
0090         // irrelevant events -> ignore
0091         QLineEdit::keyReleaseEvent(ke);
0092         return;
0093     }
0094 
0095     // this manages the shell-a-like history and
0096     // triggers a popdown (when pressing "down" arrow from current entry)
0097 
0098     QAbstractItemModel *m = completer()->model();
0099     int historyCount = m->rowCount();
0100 
0101     if (ke->key() == Qt::Key_Up) {
0102         // shell-a-like history navigation
0103         if (m_historyPosition == historyCount) {
0104             m_currentText = text();
0105             if (historyCount && m->index(m_historyPosition - 1,0).data().toString() == m_currentText) {
0106                 // user still sees entry he entered (into history) last - skip that one.
0107                 --m_historyPosition;
0108             }
0109         }
0110         if (--m_historyPosition < 0) {
0111             m_historyPosition = historyCount;
0112             setText(m_currentText);
0113         } else {
0114             setText(m->index(m_historyPosition,0).data().toString());
0115         }
0116     } else if (ke->key() == Qt::Key_Down) {
0117         if (m_historyPosition + 1 < historyCount && m->index(m_historyPosition + 1,0).data().toString() == m_currentText) {
0118             // user still sees entry he entered (into history) last - skip that one.
0119             ++m_historyPosition;
0120         }
0121         if (++m_historyPosition == historyCount) {
0122             // returning from shell-a-like journey
0123             setText(m_currentText);
0124         } else if (m_historyPosition > historyCount) {
0125             // trigger pop...down ;-)
0126             completer()->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
0127             completer()->complete(); // make a popup
0128             completer()->popup()->removeEventFilter(this); // protected against accidental double filtering
0129             completer()->popup()->installEventFilter(this); // to restore inline completion when it closes
0130             m_historyPosition = historyCount; // fix value to "current" (it's usually historyCount + 1 now)
0131         } else {
0132             // shell-a-like history navigation
0133             setText(m->index(m_historyPosition,0).data().toString());
0134         }
0135     }
0136     QLineEdit::keyReleaseEvent(ke);
0137 }
0138 
0139 void LineEdit::learnEntry()
0140 {
0141     QAbstractItemModel *m = completer()->model();
0142     int rows = m->rowCount();
0143     for (int i = 0; i < rows; ++i) {
0144         if (m->index(i,0).data() == text()) {
0145             m->removeRow(i);
0146             --rows;
0147             break;
0148         }
0149     }
0150     m->insertRows(rows, 1);
0151     m->setData(m->index(rows, 0), text(), Qt::DisplayRole);
0152     m_historyPosition = rows + 1;
0153 }
0154 
0155 void LineEdit::restoreInlineCompletion()
0156 {
0157     m_currentText = text(); // this was probably just updated by seleting from combobox
0158     completer()->setCompletionMode(QCompleter::InlineCompletion);
0159     CALL_LATER_NOARG(this, setFocus); // can't get in the second event cycle either
0160 }