1 /****************************************************************************
3 ** Copyright (c) 2007 Trolltech ASA <info@trolltech.com>
4 ** Modified (c) 2009, 2011, 2013 by Glad Deschrijver <glad.deschrijver@gmail.com>
6 ** Use, modification and distribution is allowed without limitation,
7 ** warranty, liability or support of any kind.
9 ****************************************************************************/
13 #include <QAbstractItemModel>
14 #include <QAbstractItemView>
16 #include <QHBoxLayout>
18 #include <QToolButton>
20 #include "Common/InvokeMethod.h"
21 #include "UiUtils/IconLoader.h"
23 LineEdit::LineEdit(const QString
&text
, QWidget
*parent
)
24 : QLineEdit(parent
), m_historyEnabled(false), m_historyPosition(0)
30 LineEdit::LineEdit(QWidget
*parent
)
31 : QLineEdit(parent
), m_historyEnabled(false), m_historyPosition(0)
38 m_clearButton
= new QToolButton(this);
39 m_clearButton
->setIcon(UiUtils::loadIcon(QStringLiteral("edit-clear-locationbar")));
40 m_clearButton
->setCursor(Qt::ArrowCursor
);
41 m_clearButton
->setToolTip(tr("Clear input field"));
42 m_clearButton
->setFocusPolicy(Qt::NoFocus
);
43 m_clearButton
->hide();
44 connect(m_clearButton
, &QAbstractButton::clicked
, this, &QLineEdit::clear
);
45 connect(this, &QLineEdit::textChanged
, this, &LineEdit::updateClearButton
);
47 const int frameWidth
= style()->pixelMetric(QStyle::PM_DefaultFrameWidth
);
48 m_clearButton
->setStyleSheet(QString::fromUtf8("QToolButton { border: none; padding-left: 1px; padding-top: %1px; padding-bottom: %1px; padding-right: %1px; }")
49 .arg(frameWidth
+ 1));
50 setStyleSheet(QString::fromUtf8("QLineEdit { padding-right: %1px; }")
51 .arg(m_clearButton
->sizeHint().width()));
53 QHBoxLayout
*layout
= new QHBoxLayout(this);
54 layout
->setContentsMargins(0, 0, 0, 0);
56 layout
->addWidget(m_clearButton
);
58 connect(this, &QLineEdit::editingFinished
, this, &LineEdit::doEmitTextEditingFinished
);
61 QToolButton
*LineEdit::clearButton()
66 bool LineEdit::isHistoryEnabled()
68 return m_historyEnabled
;
71 void LineEdit::setHistoryEnabled(bool enabled
)
73 if (enabled
== m_historyEnabled
)
75 m_historyEnabled
= enabled
;
76 if (m_historyEnabled
) {
77 // queued so we learn and update the history list *after* the user selected an item from popup
78 connect(this, &QLineEdit::returnPressed
, this, &LineEdit::learnEntry
, Qt::QueuedConnection
);
79 QCompleter
*completer
= new QCompleter(QStringList(), this);
80 completer
->setCaseSensitivity(Qt::CaseInsensitive
);
81 completer
->setCompletionMode(QCompleter::InlineCompletion
);
82 setCompleter(completer
);
84 disconnect(this, &QLineEdit::returnPressed
, this, &LineEdit::learnEntry
);
90 QSize
LineEdit::sizeHint() const
92 const QSize defaultSize
= QLineEdit::sizeHint();
93 const int minimumWidth
= m_clearButton
->sizeHint().width() + 2;
94 return QSize(qMax(defaultSize
.width(), minimumWidth
), defaultSize
.height());
97 bool LineEdit::eventFilter(QObject
*o
, QEvent
*e
)
99 if (e
->type() == QEvent::Hide
&& o
== completer()->popup()) {
100 // next event cycle, the popup is deleted by the change and "return true" does not help
101 // connect queued to not manipulate the history *during* selection from popup, but *after*
102 CALL_LATER_NOARG(this, restoreInlineCompletion
);
107 void LineEdit::keyReleaseEvent(QKeyEvent
*ke
)
109 if (!m_historyEnabled
|| completer()->completionMode() == QCompleter::UnfilteredPopupCompletion
||
110 !(ke
->key() == Qt::Key_Up
|| ke
->key() == Qt::Key_Down
)) {
111 // irrelevant events -> ignore
112 QLineEdit::keyReleaseEvent(ke
);
116 // this manages the shell-a-like history and
117 // triggers a popdown (when pressing "down" arrow from current entry)
119 QAbstractItemModel
*m
= completer()->model();
120 int historyCount
= m
->rowCount();
122 if (ke
->key() == Qt::Key_Up
) {
123 // shell-a-like history navigation
124 if (m_historyPosition
== historyCount
) {
125 m_currentText
= text();
126 if (historyCount
&& m
->index(m_historyPosition
- 1,0).data().toString() == m_currentText
) {
127 // user still sees entry he entered (into history) last - skip that one.
131 if (--m_historyPosition
< 0) {
132 m_historyPosition
= historyCount
;
133 setText(m_currentText
);
135 setText(m
->index(m_historyPosition
,0).data().toString());
137 } else if (ke
->key() == Qt::Key_Down
) {
138 if (m_historyPosition
+ 1 < historyCount
&& m
->index(m_historyPosition
+ 1,0).data().toString() == m_currentText
) {
139 // user still sees entry he entered (into history) last - skip that one.
142 if (++m_historyPosition
== historyCount
) {
143 // returning from shell-a-like journey
144 setText(m_currentText
);
145 } else if (m_historyPosition
> historyCount
) {
146 // trigger pop...down ;-)
147 completer()->setCompletionMode(QCompleter::UnfilteredPopupCompletion
);
148 completer()->complete(); // make a popup
149 completer()->popup()->removeEventFilter(this); // protected against accidental double filtering
150 completer()->popup()->installEventFilter(this); // to restore inline completion when it closes
151 m_historyPosition
= historyCount
; // fix value to "current" (it's usually historyCount + 1 now)
153 // shell-a-like history navigation
154 setText(m
->index(m_historyPosition
,0).data().toString());
157 QLineEdit::keyReleaseEvent(ke
);
160 void LineEdit::updateClearButton(const QString
&text
)
162 m_clearButton
->setVisible(!text
.isEmpty());
165 void LineEdit::learnEntry()
167 QAbstractItemModel
*m
= completer()->model();
168 int rows
= m
->rowCount();
169 for (int i
= 0; i
< rows
; ++i
) {
170 if (m
->index(i
,0).data() == text()) {
176 m
->insertRows(rows
, 1);
177 m
->setData(m
->index(rows
, 0), text(), Qt::DisplayRole
);
178 m_historyPosition
= rows
+ 1;
181 void LineEdit::restoreInlineCompletion()
183 m_currentText
= text(); // this was probably just updated by seleting from combobox
184 completer()->setCompletionMode(QCompleter::InlineCompletion
);
185 CALL_LATER_NOARG(this, setFocus
); // can't get in the second event cycle either
188 void LineEdit::doEmitTextEditingFinished()
190 emit
textEditingFinished(text());