KCompletion

khistorycombobox.cpp
1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2000, 2001 Dawit Alemayehu <adawit@kde.org>
5 SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
6 SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
7
8 SPDX-License-Identifier: LGPL-2.1-or-later
9*/
10
11#include "khistorycombobox.h"
12#include "kcombobox_p.h"
13
14#include <KStandardShortcut>
15
16#include <QAbstractItemView>
17#include <QApplication>
18#include <QComboBox>
19#include <QMenu>
20#include <QWheelEvent>
21
22class KHistoryComboBoxPrivate : public KComboBoxPrivate
23{
24 Q_DECLARE_PUBLIC(KHistoryComboBox)
25
26public:
27 KHistoryComboBoxPrivate(KHistoryComboBox *q)
28 : KComboBoxPrivate(q)
29 {
30 }
31
32 void init(bool useCompletion);
33 void rotateUp();
34 void rotateDown();
35
36 /**
37 * Called from the popupmenu,
38 * calls clearHistory() and emits cleared()
39 */
40 void _k_clear();
41
42 /**
43 * Appends our own context menu entry.
44 */
45 void _k_addContextMenuItems(QMenu *);
46
47 /**
48 * Used to emit the activated(QString) signal when enter is pressed
49 */
50 void _k_simulateActivated(const QString &);
51
52 /**
53 * The text typed before Up or Down was pressed.
54 */
55 QString typedText;
56
57 /**
58 * The current index in the combobox, used for Up and Down
59 */
60 int currentIndex;
61
62 /**
63 * Indicates that the user at least once rotated Up through the entire list
64 * Needed to allow going back after rotation.
65 */
66 bool rotated = false;
67
68 std::function<QIcon(QString)> iconProvider;
69};
70
71void KHistoryComboBoxPrivate::init(bool useCompletion)
72{
74 // Set a default history size to something reasonable, Qt sets it to INT_MAX by default
75 q->setMaxCount(50);
76
77 if (useCompletion) {
78 q->completionObject()->setOrder(KCompletion::Weighted);
79 }
80
81 q->setInsertPolicy(KHistoryComboBox::NoInsert);
82 currentIndex = -1;
83 rotated = false;
84
85 // obey HISTCONTROL setting
86 QByteArray histControl = qgetenv("HISTCONTROL");
87 if (histControl == "ignoredups" || histControl == "ignoreboth") {
88 q->setDuplicatesEnabled(false);
89 }
90
91 q->connect(q, &KComboBox::aboutToShowContextMenu, q, [this](QMenu *menu) {
92 _k_addContextMenuItems(menu);
93 });
95 QObject::connect(q, qOverload<const QString &>(&KComboBox::returnPressed), q, [q]() {
96 q->reset();
97 });
98 // We want _k_simulateActivated to be called _after_ QComboBoxPrivate::_q_returnPressed
99 // otherwise there's a risk of emitting activated twice (_k_simulateActivated will find
100 // the item, after some app's slotActivated inserted the item into the combo).
101 q->connect(
102 q,
103 qOverload<const QString &>(&KComboBox::returnPressed),
104 q,
105 [this](const QString &text) {
106 _k_simulateActivated(text);
107 },
109}
110
111// we are always read-write
113 : KComboBox(*new KHistoryComboBoxPrivate(this), parent)
114{
116 d->init(true); // using completion
117 setEditable(true);
118}
119
120// we are always read-write
121KHistoryComboBox::KHistoryComboBox(bool useCompletion, QWidget *parent)
122 : KComboBox(*new KHistoryComboBoxPrivate(this), parent)
123{
125 d->init(useCompletion);
126 setEditable(true);
127}
128
132
134{
135 setHistoryItems(items, false);
136}
137
138void KHistoryComboBox::setHistoryItems(const QStringList &items, bool setCompletionList)
139{
142
143 // limit to maxCount()
144 const int itemCount = insertingItems.count();
145 const int toRemove = itemCount - maxCount();
146
147 if (toRemove >= itemCount) {
148 insertingItems.clear();
149 } else {
150 for (int i = 0; i < toRemove; ++i) {
151 insertingItems.pop_front();
152 }
153 }
154
156
158 // we don't have any weighting information here ;(
160 comp->setOrder(KCompletion::Insertion);
161 comp->setItems(insertingItems);
162 comp->setOrder(KCompletion::Weighted);
163 }
164
166}
167
168QStringList KHistoryComboBox::historyItems() const
169{
170 QStringList list;
171 const int itemCount = count();
172 list.reserve(itemCount);
173 for (int i = 0; i < itemCount; ++i) {
174 list.append(itemText(i));
175 }
176
177 return list;
178}
179
181{
182 return compObj();
183}
184
186{
187 const QString temp = currentText();
189 if (useCompletion()) {
191 }
192 setEditText(temp);
193}
194
195void KHistoryComboBoxPrivate::_k_addContextMenuItems(QMenu *menu)
196{
197 Q_Q(KHistoryComboBox);
198 if (menu) {
199 menu->addSeparator();
200 QAction *clearHistory =
201 menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), KHistoryComboBox::tr("Clear &History", "@action:inmenu"), q, [this]() {
202 _k_clear();
203 });
204 if (!q->count()) {
205 clearHistory->setEnabled(false);
206 }
207 }
208}
209
211{
213 if (item.isEmpty() || (count() > 0 && item == itemText(0))) {
214 return;
215 }
216
217 bool wasCurrent = false;
218 // remove all existing items before adding
219 if (!duplicatesEnabled()) {
220 int i = 0;
221 int itemCount = count();
222 while (i < itemCount) {
223 if (itemText(i) == item) {
224 if (!wasCurrent) {
225 wasCurrent = (i == currentIndex());
226 }
227 removeItem(i);
228 --itemCount;
229 } else {
230 ++i;
231 }
232 }
233 }
234
235 // now add the item
236 if (d->iconProvider) {
237 insertItem(0, d->iconProvider(item), item);
238 } else {
239 insertItem(0, item);
240 }
241
242 if (wasCurrent) {
244 }
245
246 const bool useComp = useCompletion();
247
248 const int last = count() - 1; // last valid index
249 const int mc = maxCount();
250 const int stopAt = qMax(mc, 0);
251
252 for (int rmIndex = last; rmIndex >= stopAt; --rmIndex) {
253 // remove the last item, as long as we are longer than maxCount()
254 // remove the removed item from the completionObject if it isn't
255 // anymore available at all in the combobox.
258 if (useComp && !contains(rmItem)) {
260 }
261 }
262
263 if (useComp) {
264 completionObject()->addItem(item);
265 }
266}
267
269{
270 if (item.isEmpty()) {
271 return false;
272 }
273
274 bool removed = false;
275 const QString temp = currentText();
276 int i = 0;
277 int itemCount = count();
278 while (i < itemCount) {
279 if (item == itemText(i)) {
280 removed = true;
281 removeItem(i);
282 --itemCount;
283 } else {
284 ++i;
285 }
286 }
287
288 if (removed && useCompletion()) {
290 }
291
292 setEditText(temp);
293 return removed;
294}
295
296// going up in the history, rotating when reaching QListBox::count()
297//
298// Note: this differs from QComboBox because "up" means ++index here,
299// to simulate the way shell history works (up goes to the most
300// recent item). In QComboBox "down" means ++index, to match the popup...
301//
302void KHistoryComboBoxPrivate::rotateUp()
303{
304 Q_Q(KHistoryComboBox);
305 // save the current text in the lineedit
306 // (This is also where this differs from standard up/down in QComboBox,
307 // where a single keypress can make you lose your typed text)
308 if (currentIndex == -1) {
309 typedText = q->currentText();
310 }
311
312 ++currentIndex;
313
314 // skip duplicates/empty items
315 const int last = q->count() - 1; // last valid index
316 const QString currText = q->currentText();
317
318 while (currentIndex < last && (currText == q->itemText(currentIndex) || q->itemText(currentIndex).isEmpty())) {
319 ++currentIndex;
320 }
321
322 if (currentIndex >= q->count()) {
323 rotated = true;
324 currentIndex = -1;
325
326 // if the typed text is the same as the first item, skip the first
327 if (q->count() > 0 && typedText == q->itemText(0)) {
328 currentIndex = 0;
329 }
330
331 q->setEditText(typedText);
332 } else {
333 q->setCurrentIndex(currentIndex);
334 }
335}
336
337// going down in the history, no rotation possible. Last item will be
338// the text that was in the lineedit before Up was called.
339void KHistoryComboBoxPrivate::rotateDown()
340{
341 Q_Q(KHistoryComboBox);
342 // save the current text in the lineedit
343 if (currentIndex == -1) {
344 typedText = q->currentText();
345 }
346
347 --currentIndex;
348
349 const QString currText = q->currentText();
350 // skip duplicates/empty items
351 while (currentIndex >= 0 //
352 && (currText == q->itemText(currentIndex) || q->itemText(currentIndex).isEmpty())) {
353 --currentIndex;
354 }
355
356 if (currentIndex < 0) {
357 if (rotated && currentIndex == -2) {
358 rotated = false;
359 currentIndex = q->count() - 1;
360 q->setEditText(q->itemText(currentIndex));
361 } else { // bottom of history
362 currentIndex = -1;
363 if (q->currentText() != typedText) {
364 q->setEditText(typedText);
365 }
366 }
367 } else {
368 q->setCurrentIndex(currentIndex);
369 }
370}
371
373{
375 int event_key = e->key() | e->modifiers();
376
378 d->rotateUp();
380 d->rotateDown();
381 } else {
383 }
384}
385
387{
389 // Pass to poppable listbox if it's up
390 QAbstractItemView *const iv = view();
391 if (iv && iv->isVisible()) {
393 return;
394 }
395 // Otherwise make it change the text without emitting activated
396 if (ev->angleDelta().y() > 0) {
397 d->rotateUp();
398 } else {
399 d->rotateDown();
400 }
401 ev->accept();
402}
403
404void KHistoryComboBox::setIconProvider(std::function<QIcon(const QString &)> providerFunction)
405{
407 d->iconProvider = providerFunction;
408}
409
411{
413
414 for (const QString &item : items) {
415 if (item.isEmpty()) {
416 continue;
417 }
418
419 if (d->iconProvider) {
420 addItem(d->iconProvider(item), item);
421 } else {
422 addItem(item);
423 }
424 }
425}
426
427void KHistoryComboBoxPrivate::_k_clear()
428{
429 Q_Q(KHistoryComboBox);
430 q->clearHistory();
431 Q_EMIT q->cleared();
432}
433
434void KHistoryComboBoxPrivate::_k_simulateActivated(const QString &text)
435{
436 Q_Q(KHistoryComboBox);
437 /* With the insertion policy NoInsert, which we use by default,
438 Qt doesn't emit activated on typed text if the item is not already there,
439 which is perhaps reasonable. Generate the signal ourselves if that's the case.
440 */
441 if ((q->insertPolicy() == q->NoInsert && q->findText(text, Qt::MatchFixedString | Qt::MatchCaseSensitive) == -1)) {
442 Q_EMIT q->textActivated(text);
443 }
444
445 /*
446 Qt also doesn't emit it if the box is full, and policy is not
447 InsertAtCurrent
448 */
449 else if (q->insertPolicy() != q->InsertAtCurrent && q->count() >= q->maxCount()) {
450 Q_EMIT q->textActivated(text);
451 }
452}
453
455{
457 d->currentIndex = -1;
458 d->rotated = false;
459}
460
461#include "moc_khistorycombobox.cpp"
A combo box with completion support.
Definition kcombobox.h:136
void aboutToShowContextMenu(QMenu *contextMenu)
Emitted before the context menu is displayed.
bool contains(const QString &text) const
Convenience method which iterates over all items and checks if any of them is equal to text.
Definition kcombobox.cpp:64
void returnPressed(const QString &text)
Emitted when the user presses the Return or Enter key.
void setEditable(bool editable)
Reimplemented so that setEditable(true) creates a KLineEdit instead of QLineEdit.
KCompletion * compObj() const
Returns a pointer to the completion object.
KCompletion * completionObject(bool handleSignals=true)
Returns a pointer to the current completion object.
A generic class for completing QStrings.
void removeItem(const QString &item)
Removes an item from the list of available completions.
@ Insertion
Use order of insertion.
@ Weighted
Use weighted order.
virtual void clear()
Removes all inserted items.
void addItem(const QString &item)
Adds an item to the list of available completions.
A combobox for offering a history and completion.
KHistoryComboBox(QWidget *parent=nullptr)
Constructs a "read-write" combobox.
void addToHistory(const QString &item)
Adds an item to the end of the history list and to the completion list.
~KHistoryComboBox() override
Destructs the combo and the completion-object.
bool removeFromHistory(const QString &item)
Removes all items named item.
void keyPressEvent(QKeyEvent *) override
Handling key-events, the shortcuts to rotate the items.
void clearHistory()
Clears the history and the completion list.
void insertItems(const QStringList &items)
Inserts items into the combo, honoring setIconProvider() Does not update the completionObject.
void setHistoryItems(const QStringList &items)
Inserts items into the combobox.
void setIconProvider(std::function< QIcon(const QString &)> providerFunction)
Sets an icon provider, so that items in the combobox can have an icon.
void wheelEvent(QWheelEvent *ev) override
Handling wheel-events, to rotate the items.
void reset()
Resets the current position of the up/down history.
bool useCompletion() const
const QList< QKeySequence > & rotateDown()
const QList< QKeySequence > & rotateUp()
void setEnabled(bool)
void activated(int index)
void addItem(const QIcon &icon, const QString &text, const QVariant &userData)
void clear()
void clearEditText()
void insertItem(int index, const QIcon &icon, const QString &text, const QVariant &userData)
QString itemText(int index) const const
virtual void keyPressEvent(QKeyEvent *e) override
void removeItem(int index)
void setEditText(const QString &text)
QAbstractItemView * view() const const
bool sendEvent(QObject *receiver, QEvent *event)
QIcon fromTheme(const QString &name)
int key() const const
Qt::KeyboardModifiers modifiers() const const
void append(QList< T > &&value)
void reserve(qsizetype size)
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSeparator()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isEmpty() const const
QueuedConnection
MatchFixedString
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:24 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.