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

KDE's Doxygen guidelines are available online.