KCompletion

khistorycombobox.cpp
1 /*
2  This file is part of the KDE libraries
3 
4  SPDX-FileCopyrightText: 2000, 2001 Dawit Alemayehu <[email protected]>
5  SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <[email protected]>
6  SPDX-FileCopyrightText: 2000 Stefan Schimanski <[email protected]>
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 #include <kpixmapprovider.h>
16 
17 #include <QAbstractItemView>
18 #include <QApplication>
19 #include <QComboBox>
20 #include <QMenu>
21 #include <QWheelEvent>
22 
23 class KHistoryComboBoxPrivate : public KComboBoxPrivate
24 {
25  Q_DECLARE_PUBLIC(KHistoryComboBox)
26 
27 public:
28  KHistoryComboBoxPrivate(KHistoryComboBox *q)
29  : KComboBoxPrivate(q)
30  {
31  }
32 
33  void init(bool useCompletion);
34  void rotateUp();
35  void rotateDown();
36 
37  /**
38  * Called from the popupmenu,
39  * calls clearHistory() and emits cleared()
40  */
41  void _k_clear();
42 
43  /**
44  * Appends our own context menu entry.
45  */
46  void _k_addContextMenuItems(QMenu *);
47 
48  /**
49  * Used to emit the activated(QString) signal when enter is pressed
50  */
51  void _k_simulateActivated(const QString &);
52 
53  /**
54  * The text typed before Up or Down was pressed.
55  */
56  QString typedText;
57 
58 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
59  KPixmapProvider *pixmapProvider = nullptr;
60 #endif
61 
62  /**
63  * The current index in the combobox, used for Up and Down
64  */
65  int currentIndex;
66 
67  /**
68  * Indicates that the user at least once rotated Up through the entire list
69  * Needed to allow going back after rotation.
70  */
71  bool rotated = false;
72 
73  std::function<QIcon(QString)> iconProvider;
74 };
75 
76 void KHistoryComboBoxPrivate::init(bool useCompletion)
77 {
78  Q_Q(KHistoryComboBox);
79  // Set a default history size to something reasonable, Qt sets it to INT_MAX by default
80  q->setMaxCount(50);
81 
82  if (useCompletion) {
83  q->completionObject()->setOrder(KCompletion::Weighted);
84  }
85 
86  q->setInsertPolicy(KHistoryComboBox::NoInsert);
87  currentIndex = -1;
88  rotated = false;
89 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
90  pixmapProvider = nullptr;
91 #endif
92 
93  // obey HISTCONTROL setting
94  QByteArray histControl = qgetenv("HISTCONTROL");
95  if (histControl == "ignoredups" || histControl == "ignoreboth") {
96  q->setDuplicatesEnabled(false);
97  }
98 
99  q->connect(q, &KComboBox::aboutToShowContextMenu, q, [this](QMenu *menu) {
100  _k_addContextMenuItems(menu);
101  });
103  QObject::connect(q, qOverload<const QString &>(&KComboBox::returnPressed), q, [q]() {
104  q->reset();
105  });
106  // We want _k_simulateActivated to be called _after_ QComboBoxPrivate::_q_returnPressed
107  // otherwise there's a risk of emitting activated twice (_k_simulateActivated will find
108  // the item, after some app's slotActivated inserted the item into the combo).
109  q->connect(
110  q,
111  qOverload<const QString &>(&KComboBox::returnPressed),
112  q,
113  [this](const QString &text) {
114  _k_simulateActivated(text);
115  },
117 }
118 
119 // we are always read-write
121  : KComboBox(*new KHistoryComboBoxPrivate(this), parent)
122 {
124  d->init(true); // using completion
125  setEditable(true);
126 }
127 
128 // we are always read-write
129 KHistoryComboBox::KHistoryComboBox(bool useCompletion, QWidget *parent)
130  : KComboBox(*new KHistoryComboBoxPrivate(this), parent)
131 {
133  d->init(useCompletion);
134  setEditable(true);
135 }
136 
138 {
139 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
141  delete d->pixmapProvider;
142 #endif
143 }
144 
146 {
147  setHistoryItems(items, false);
148 }
149 
150 void KHistoryComboBox::setHistoryItems(const QStringList &items, bool setCompletionList)
151 {
152  QStringList insertingItems = items;
154 
155  // limit to maxCount()
156  const int itemCount = insertingItems.count();
157  const int toRemove = itemCount - maxCount();
158 
159  if (toRemove >= itemCount) {
160  insertingItems.clear();
161  } else {
162  for (int i = 0; i < toRemove; ++i) {
163  insertingItems.pop_front();
164  }
165  }
166 
167  insertItems(insertingItems);
168 
169  if (setCompletionList && useCompletion()) {
170  // we don't have any weighting information here ;(
171  KCompletion *comp = completionObject();
173  comp->setItems(insertingItems);
175  }
176 
177  clearEditText();
178 }
179 
180 QStringList KHistoryComboBox::historyItems() const
181 {
182  QStringList list;
183  const int itemCount = count();
184  list.reserve(itemCount);
185  for (int i = 0; i < itemCount; ++i) {
186  list.append(itemText(i));
187  }
188 
189  return list;
190 }
191 
193 {
194  return compObj();
195 }
196 
198 {
199  const QString temp = currentText();
201  if (useCompletion()) {
202  completionObject()->clear();
203  }
204  setEditText(temp);
205 }
206 
207 void KHistoryComboBoxPrivate::_k_addContextMenuItems(QMenu *menu)
208 {
209  Q_Q(KHistoryComboBox);
210  if (menu) {
211  menu->addSeparator();
212  QAction *clearHistory =
213  menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), KHistoryComboBox::tr("Clear &History", "@action:inmenu"), q, [this]() {
214  _k_clear();
215  });
216  if (!q->count()) {
217  clearHistory->setEnabled(false);
218  }
219  }
220 }
221 
223 {
225  if (item.isEmpty() || (count() > 0 && item == itemText(0))) {
226  return;
227  }
228 
229  bool wasCurrent = false;
230  // remove all existing items before adding
231  if (!duplicatesEnabled()) {
232  int i = 0;
233  int itemCount = count();
234  while (i < itemCount) {
235  if (itemText(i) == item) {
236  if (!wasCurrent) {
237  wasCurrent = (i == currentIndex());
238  }
239  removeItem(i);
240  --itemCount;
241  } else {
242  ++i;
243  }
244  }
245  }
246 
247  // now add the item
248  if (d->iconProvider) {
249  insertItem(0, d->iconProvider(item), item);
250 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
251  } else if (d->pixmapProvider) {
252  insertItem(0, d->pixmapProvider->pixmapFor(item, iconSize().height()), item);
253 #endif
254  } else {
255  insertItem(0, item);
256  }
257 
258  if (wasCurrent) {
259  setCurrentIndex(0);
260  }
261 
262  const bool useComp = useCompletion();
263 
264  const int last = count() - 1; // last valid index
265  const int mc = maxCount();
266  const int stopAt = qMax(mc, 0);
267 
268  for (int rmIndex = last; rmIndex >= stopAt; --rmIndex) {
269  // remove the last item, as long as we are longer than maxCount()
270  // remove the removed item from the completionObject if it isn't
271  // anymore available at all in the combobox.
272  const QString rmItem = itemText(rmIndex);
273  removeItem(rmIndex);
274  if (useComp && !contains(rmItem)) {
275  completionObject()->removeItem(rmItem);
276  }
277  }
278 
279  if (useComp) {
280  completionObject()->addItem(item);
281  }
282 }
283 
285 {
286  if (item.isEmpty()) {
287  return false;
288  }
289 
290  bool removed = false;
291  const QString temp = currentText();
292  int i = 0;
293  int itemCount = count();
294  while (i < itemCount) {
295  if (item == itemText(i)) {
296  removed = true;
297  removeItem(i);
298  --itemCount;
299  } else {
300  ++i;
301  }
302  }
303 
304  if (removed && useCompletion()) {
305  completionObject()->removeItem(item);
306  }
307 
308  setEditText(temp);
309  return removed;
310 }
311 
312 // going up in the history, rotating when reaching QListBox::count()
313 //
314 // Note: this differs from QComboBox because "up" means ++index here,
315 // to simulate the way shell history works (up goes to the most
316 // recent item). In QComboBox "down" means ++index, to match the popup...
317 //
318 void KHistoryComboBoxPrivate::rotateUp()
319 {
320  Q_Q(KHistoryComboBox);
321  // save the current text in the lineedit
322  // (This is also where this differs from standard up/down in QComboBox,
323  // where a single keypress can make you lose your typed text)
324  if (currentIndex == -1) {
325  typedText = q->currentText();
326  }
327 
328  ++currentIndex;
329 
330  // skip duplicates/empty items
331  const int last = q->count() - 1; // last valid index
332  const QString currText = q->currentText();
333 
334  while (currentIndex < last && (currText == q->itemText(currentIndex) || q->itemText(currentIndex).isEmpty())) {
335  ++currentIndex;
336  }
337 
338  if (currentIndex >= q->count()) {
339  rotated = true;
340  currentIndex = -1;
341 
342  // if the typed text is the same as the first item, skip the first
343  if (q->count() > 0 && typedText == q->itemText(0)) {
344  currentIndex = 0;
345  }
346 
347  q->setEditText(typedText);
348  } else {
349  q->setCurrentIndex(currentIndex);
350  }
351 }
352 
353 // going down in the history, no rotation possible. Last item will be
354 // the text that was in the lineedit before Up was called.
355 void KHistoryComboBoxPrivate::rotateDown()
356 {
357  Q_Q(KHistoryComboBox);
358  // save the current text in the lineedit
359  if (currentIndex == -1) {
360  typedText = q->currentText();
361  }
362 
363  --currentIndex;
364 
365  const QString currText = q->currentText();
366  // skip duplicates/empty items
367  while (currentIndex >= 0 //
368  && (currText == q->itemText(currentIndex) || q->itemText(currentIndex).isEmpty())) {
369  --currentIndex;
370  }
371 
372  if (currentIndex < 0) {
373  if (rotated && currentIndex == -2) {
374  rotated = false;
375  currentIndex = q->count() - 1;
376  q->setEditText(q->itemText(currentIndex));
377  } else { // bottom of history
378  currentIndex = -1;
379  if (q->currentText() != typedText) {
380  q->setEditText(typedText);
381  }
382  }
383  } else {
384  q->setCurrentIndex(currentIndex);
385  }
386 }
387 
389 {
391  int event_key = e->key() | e->modifiers();
392 
393  if (KStandardShortcut::rotateUp().contains(event_key)) {
394  d->rotateUp();
395  } else if (KStandardShortcut::rotateDown().contains(event_key)) {
396  d->rotateDown();
397  } else {
399  }
400 }
401 
403 {
405  // Pass to poppable listbox if it's up
406  QAbstractItemView *const iv = view();
407  if (iv && iv->isVisible()) {
408  QApplication::sendEvent(iv, ev);
409  return;
410  }
411  // Otherwise make it change the text without emitting activated
412  if (ev->angleDelta().y() > 0) {
413  d->rotateUp();
414  } else {
415  d->rotateDown();
416  }
417  ev->accept();
418 }
419 
420 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
422 {
424  if (d->pixmapProvider == provider) {
425  return;
426  }
427 
428  delete d->pixmapProvider;
429  d->pixmapProvider = provider;
430 
431  // re-insert all the items with/without pixmap
432  // I would prefer to use changeItem(), but that doesn't honor the pixmap
433  // when using an editable combobox (what we do)
434  if (count() > 0) {
435  QStringList items(historyItems());
436  clear();
437  insertItems(items);
438  }
439 }
440 #endif
441 
442 void KHistoryComboBox::setIconProvider(std::function<QIcon(const QString &)> providerFunction)
443 {
445  d->iconProvider = providerFunction;
446 }
447 
449 {
451 
452  for (const QString &item : items) {
453  if (item.isEmpty()) {
454  continue;
455  }
456 
457  if (d->iconProvider) {
458  addItem(d->iconProvider(item), item);
459 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
460  } else if (d->pixmapProvider) {
461  addItem(d->pixmapProvider->pixmapFor(item, iconSize().height()), item);
462 #endif
463  } else {
464  addItem(item);
465  }
466  }
467 }
468 
469 void KHistoryComboBoxPrivate::_k_clear()
470 {
471  Q_Q(KHistoryComboBox);
472  q->clearHistory();
473  Q_EMIT q->cleared();
474 }
475 
476 void KHistoryComboBoxPrivate::_k_simulateActivated(const QString &text)
477 {
478  Q_Q(KHistoryComboBox);
479  /* With the insertion policy NoInsert, which we use by default,
480  Qt doesn't emit activated on typed text if the item is not already there,
481  which is perhaps reasonable. Generate the signal ourselves if that's the case.
482  */
483  if ((q->insertPolicy() == q->NoInsert && q->findText(text, Qt::MatchFixedString | Qt::MatchCaseSensitive) == -1)) {
484 #if QT_DEPRECATED_SINCE(5, 15)
485  QT_WARNING_PUSH
486  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
487  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
488  Q_EMIT q->activated(text);
489  QT_WARNING_POP
490 #endif
491  Q_EMIT q->textActivated(text);
492  }
493 
494  /*
495  Qt also doesn't emit it if the box is full, and policy is not
496  InsertAtCurrent
497  */
498  else if (q->insertPolicy() != q->InsertAtCurrent && q->count() >= q->maxCount()) {
499 #if QT_DEPRECATED_SINCE(5, 15)
500  QT_WARNING_PUSH
501  QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
502  QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
503  Q_EMIT q->activated(text);
504  QT_WARNING_POP
505 #endif
506  Q_EMIT q->textActivated(text);
507  }
508 }
509 
510 #if KCOMPLETION_BUILD_DEPRECATED_SINCE(5, 66)
512 {
513  Q_D(const KHistoryComboBox);
514  return d->pixmapProvider;
515 }
516 #endif
517 
519 {
521  d->currentIndex = -1;
522  d->rotated = false;
523 }
524 
525 #include "moc_khistorycombobox.cpp"
void append(const T &value)
void addToHistory(const QString &item)
Adds an item to the end of the history list and to the completion list.
void clearEditText()
KPixmapProvider * pixmapProvider() const
virtual void setItems(const QStringList &itemList)
Sets the list of items available for completion.
KHistoryComboBox(QWidget *parent=nullptr)
Constructs a "read-write" combobox.
virtual void keyPressEvent(QKeyEvent *e) override
bool useCompletion() const
void reset()
Resets the current position of the up/down history.
QAbstractItemView * view() const const
QCA_EXPORT void init()
int count(const T &value) const const
A generic class for completing QStrings.
Definition: kcompletion.h:117
QPoint angleDelta() const const
QIcon fromTheme(const QString &name)
An abstract interface for looking up icons.
QAction * addSeparator()
void keyPressEvent(QKeyEvent *) override
Handling key-events, the shortcuts to rotate the items.
int y() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
@ Weighted
Use weighted order.
Definition: kcompletion.h:166
void setPixmapProvider(KPixmapProvider *provider)
Sets a pixmap provider, so that items in the combobox can have a pixmap.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString itemText(int index) const const
QAction * addAction(const QString &text)
void reserve(int alloc)
void setEditText(const QString &text)
@ Insertion
Use order of insertion.
Definition: kcompletion.h:165
bool isVisible() const const
Qt::KeyboardModifiers modifiers() const const
KCompletion * compObj() const
Returns a pointer to the completion object.
~KHistoryComboBox() override
Destructs the combo, the completion-object and the pixmap-provider.
bool sendEvent(QObject *receiver, QEvent *event)
void insertItem(int index, const QString &text, const QVariant &userData)
void pop_front()
bool isEmpty() const const
void setIconProvider(std::function< QIcon(const QString &)> providerFunction)
Sets an icon provider, so that items in the combobox can have an icon.
QueuedConnection
void returnPressed()
Emitted when the user presses the Enter key.
void insertItems(const QStringList &items)
Inserts items into the combo, honoring pixmapProvider() Does not update the completionObject.
void clearHistory()
Clears the history and the completion list.
void setHistoryItems(const QStringList &items)
Inserts items into the combobox.
A combo box with completion support.
Definition: kcombobox.h:135
void clear()
KCompletion * completionObject(bool handleSignals=true)
Returns a pointer to the current completion object.
int count() const const
void setEnabled(bool)
int key() const const
void wheelEvent(QWheelEvent *ev) override
Handling wheel-events, to rotate the items.
void removeItem(const QString &item)
Removes an item from the list of available completions.
void removeItem(int index)
void addItem(const QString &item)
Adds an item to the list of available completions.
const QList< QKeySequence > & rotateUp()
void clear()
void addItem(const QString &text, const QVariant &userData)
const QList< QKeySequence > & rotateDown()
A combobox for offering a history and completion.
MatchFixedString
virtual void clear()
Removes all inserted items.
bool removeFromHistory(const QString &item)
Removes all items named item.
QString tr(const char *sourceText, const char *disambiguation, int n)
void activated(int index)
void setEditable(bool editable)
Reimplemented so that setEditable(true) creates a KLineEdit instead of QLineEdit.
Definition: kcombobox.cpp:364
Q_D(Todo)
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 aboutToShowContextMenu(QMenu *contextMenu)
Emitted before the context menu is displayed.
void accept()
virtual void setOrder(CompOrder order)
KCompletion offers three different ways in which it offers its items:
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:07:52 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.