KCompletion

kcompletionbox.cpp
1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2000, 2001, 2002 Carsten Pfeiffer <pfeiffer@kde.org>
5 SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
6 SPDX-FileCopyrightText: 2000, 2001, 2002, 2003, 2004 Dawit Alemayehu <adawit@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "kcompletionbox.h"
12#include "klineedit.h"
13
14#include <QApplication>
15#include <QKeyEvent>
16#include <QScreen>
17#include <QScrollBar>
18
19class KCompletionBoxPrivate
20{
21public:
22 QWidget *m_parent = nullptr; // necessary to set the focus back
23 QString cancelText;
24 bool tabHandling = true;
25 bool upwardBox = false;
26 bool emitSelected = true;
27};
28
30 : QListWidget(parent)
31 , d(new KCompletionBoxPrivate)
32{
33 d->m_parent = parent;
34
35 // we can't link to QXcbWindowFunctions::Combo
36 // also, q->setAttribute(Qt::WA_X11NetWmWindowTypeCombo); is broken in Qt xcb
37 setProperty("_q_xcb_wm_window_type", 0x001000);
39
40 // on wayland, we need an xdg-popup but we don't want it to grab
41 // calls setVisible, so must be done after initializations
42 if (qGuiApp->platformName() == QLatin1String("wayland")) {
44 } else {
46 }
48
49 setLineWidth(1);
51
54
57 if (item) {
58 hide();
61 }
62 });
63}
64
66{
67 d->m_parent = nullptr;
68}
69
71{
72 QStringList list;
73 list.reserve(count());
74 for (int i = 0; i < count(); i++) {
76
77 list.append(currItem->text());
78 }
79
80 return list;
81}
82
84{
85 if (item) {
86 hide();
88 }
89}
90
92{
93 int type = e->type();
95
96 if (o == this) {
97 return false;
98 }
99
100 if (wid && wid == d->m_parent //
101 && (type == QEvent::Move || type == QEvent::Resize)) {
103 return false;
104 }
105
106 if (wid && (wid->windowFlags() & Qt::Window) //
107 && type == QEvent::Move && wid == d->m_parent->window()) {
108 hide();
109 return false;
110 }
111
112 if (type == QEvent::MouseButtonPress && (wid && !isAncestorOf(wid))) {
113 if (!d->emitSelected && currentItem() && !qobject_cast<QScrollBar *>(o)) {
116 }
117 hide();
118 e->accept();
119 return true;
120 }
121
122 if (wid && wid->isAncestorOf(d->m_parent) && isVisible()) {
123 if (type == QEvent::KeyPress) {
124 QKeyEvent *ev = static_cast<QKeyEvent *>(e);
125 switch (ev->key()) {
126 case Qt::Key_Backtab:
127 if (d->tabHandling && (ev->modifiers() == Qt::NoButton || (ev->modifiers() & Qt::ShiftModifier))) {
128 up();
129 ev->accept();
130 return true;
131 }
132 break;
133 case Qt::Key_Tab:
134 if (d->tabHandling && (ev->modifiers() == Qt::NoButton)) {
135 down();
136 // #65877: Key_Tab should complete using the first
137 // (or selected) item, and then offer completions again
138 if (count() == 1) {
140 if (parent) {
141 parent->doCompletion(currentItem()->text());
142 } else {
143 hide();
144 }
145 }
146 ev->accept();
147 return true;
148 }
149 break;
150 case Qt::Key_Down:
151 down();
152 ev->accept();
153 return true;
154 case Qt::Key_Up:
155 // If there is no selected item and we've popped up above
156 // our parent, select the first item when they press up.
157 if (!selectedItems().isEmpty() //
158 || mapToGlobal(QPoint(0, 0)).y() > d->m_parent->mapToGlobal(QPoint(0, 0)).y()) {
159 up();
160 } else {
161 down();
162 }
163 ev->accept();
164 return true;
165 case Qt::Key_PageUp:
166 pageUp();
167 ev->accept();
168 return true;
169 case Qt::Key_PageDown:
170 pageDown();
171 ev->accept();
172 return true;
173 case Qt::Key_Escape:
174 if (!d->cancelText.isNull()) {
175 Q_EMIT userCancelled(d->cancelText);
176 }
177 if (isVisible()) {
178 hide();
179 }
180 ev->accept();
181 return true;
182 case Qt::Key_Enter:
183 case Qt::Key_Return:
184 if (ev->modifiers() & Qt::ShiftModifier) {
185 hide();
186 ev->accept(); // Consume the Enter event
187 return true;
188 }
189 break;
190 case Qt::Key_End:
191 if (ev->modifiers() & Qt::ControlModifier) {
192 end();
193 ev->accept();
194 return true;
195 }
196 break;
197 case Qt::Key_Home:
198 if (ev->modifiers() & Qt::ControlModifier) {
199 home();
200 ev->accept();
201 return true;
202 }
204 default:
205 break;
206 }
207 } else if (type == QEvent::ShortcutOverride) {
208 // Override any accelerators that match
209 // the key sequences we use here...
210 QKeyEvent *ev = static_cast<QKeyEvent *>(e);
211 switch (ev->key()) {
212 case Qt::Key_Down:
213 case Qt::Key_Up:
214 case Qt::Key_PageUp:
215 case Qt::Key_PageDown:
216 case Qt::Key_Escape:
217 case Qt::Key_Enter:
218 case Qt::Key_Return:
219 ev->accept();
220 return true;
221 case Qt::Key_Tab:
222 case Qt::Key_Backtab:
223 if (ev->modifiers() == Qt::NoButton || (ev->modifiers() & Qt::ShiftModifier)) {
224 ev->accept();
225 return true;
226 }
227 break;
228 case Qt::Key_Home:
229 case Qt::Key_End:
230 if (ev->modifiers() & Qt::ControlModifier) {
231 ev->accept();
232 return true;
233 }
234 break;
235 default:
236 break;
237 }
238 } else if (type == QEvent::FocusOut) {
239 QFocusEvent *event = static_cast<QFocusEvent *>(e);
240 if (event->reason() != Qt::PopupFocusReason
241#ifdef Q_OS_WIN
242 && (event->reason() != Qt::ActiveWindowFocusReason || QApplication::activeWindow() != this)
243#endif
244 ) {
245 hide();
246 }
247 }
248 }
249
250 return QListWidget::eventFilter(o, e);
251}
252
254{
255 if (count() == 0) {
256 hide();
257 } else {
258 bool block = signalsBlocked();
259 blockSignals(true);
260 setCurrentRow(-1);
261 blockSignals(block);
263 if (!isVisible()) {
264 show();
265 } else if (size().height() != sizeHint().height()) {
267 }
268 }
269}
270
272{
273 int currentGeom = height();
276 resize(geom.size());
277
278 int x = currentPos.x();
279 int y = currentPos.y();
280 if (d->m_parent) {
281 if (!isVisible()) {
284 if (screen) {
285 const QRect screenSize = screen->geometry();
286
287 x = orig.x() + geom.x();
288 y = orig.y() + geom.y();
289
290 if (x + width() > screenSize.right()) {
291 x = screenSize.right() - width();
292 }
293 if (y + height() > screenSize.bottom()) {
294 y = y - height() - d->m_parent->height();
295 d->upwardBox = true;
296 }
297 }
298 } else {
299 // Are we above our parent? If so we must keep bottom edge anchored.
300 if (d->upwardBox) {
301 y += (currentGeom - height());
302 }
303 }
304 move(x, y);
305 }
306}
307
309{
310 if (!d->m_parent) {
311 return QPoint();
312 }
313 return d->m_parent->mapToGlobal(QPoint(0, d->m_parent->height()));
314}
315
317{
318 if (visible) {
319 d->upwardBox = false;
320 if (d->m_parent) {
322 qApp->installEventFilter(this);
323 }
324
325 // FIXME: Is this comment still valid or can it be deleted? Is a patch already sent to Qt?
326 // Following lines are a workaround for a bug (not sure whose this is):
327 // If this KCompletionBox' parent is in a layout, that layout will detect the
328 // insertion of a new child (posting a ChildInserted event). Then it will trigger relayout
329 // (posting a LayoutHint event).
330 //
331 // QWidget::show() then sends also posted ChildInserted events for the parent,
332 // and later all LayoutHint events, which cause layout updating.
333 // The problem is that KCompletionBox::eventFilter() detects the resizing
334 // of the parent, calls hide() and this hide() happens in the middle
335 // of show(), causing inconsistent state. I'll try to submit a Qt patch too.
336 qApp->sendPostedEvents();
337 } else {
338 if (d->m_parent) {
339 qApp->removeEventFilter(this);
340 }
341 d->cancelText.clear();
342 }
343
345}
346
348{
350 if (count() == 0 || !(visualRect = visualItemRect(item(0))).isValid()) {
351 return QRect();
352 }
353
354 int x = 0;
355 int y = 0;
356 int ih = visualRect.height();
357 int h = qMin(15 * ih, count() * ih) + 2 * frameWidth();
358
359 int w = (d->m_parent) ? d->m_parent->width() : QListWidget::minimumSizeHint().width();
361 return QRect(x, y, w, h);
362}
363
364QSize KCompletionBox::sizeHint() const
365{
366 return calculateGeometry().size();
367}
368
370{
371 const int row = currentRow();
372 const int lastRow = count() - 1;
373 if (row < lastRow) {
374 setCurrentRow(row + 1);
375 return;
376 }
377
378 if (lastRow > -1) {
379 setCurrentRow(0);
380 }
381}
382
384{
385 const int row = currentRow();
386 if (row > 0) {
387 setCurrentRow(row - 1);
388 return;
389 }
390
391 const int lastRow = count() - 1;
392 if (lastRow > 0) {
393 setCurrentRow(lastRow);
394 }
395}
396
401
406
408{
409 setCurrentRow(0);
410}
411
413{
414 setCurrentRow(count() - 1);
415}
416
418{
419 d->tabHandling = enable;
420}
421
422bool KCompletionBox::isTabHandling() const
423{
424 return d->tabHandling;
425}
426
428{
429 d->cancelText = text;
430}
431
432QString KCompletionBox::cancelledText() const
433{
434 return d->cancelText;
435}
436
437void KCompletionBox::insertItems(const QStringList &items, int index)
438{
439 bool block = signalsBlocked();
440 blockSignals(true);
442 blockSignals(block);
443 setCurrentRow(-1);
444}
445
447{
448 bool block = signalsBlocked();
449 blockSignals(true);
450
451 int rowIndex = 0;
452
453 if (!count()) {
455 } else {
456 for (const auto &text : items) {
457 if (rowIndex < count()) {
458 auto item = this->item(rowIndex);
459 if (item->text() != text) {
460 item->setText(text);
461 }
462 } else {
463 addItem(text);
464 }
465 rowIndex++;
466 }
467
468 // remove unused items with an index >= rowIndex
469 for (; rowIndex < count();) {
470 QListWidgetItem *item = takeItem(rowIndex);
471 Q_ASSERT(item);
472 delete item;
473 }
474 }
475
476 if (isVisible() && size().height() != sizeHint().height()) {
478 }
479
480 blockSignals(block);
481}
482
484{
485 d->emitSelected = doEmit;
486}
487
488bool KCompletionBox::activateOnSelect() const
489{
490 return d->emitSelected;
491}
492
493#include "moc_kcompletionbox.cpp"
void up()
Moves the selection one line up or select the first item if nothing is selected yet.
void insertItems(const QStringList &items, int index=-1)
Inserts items into the box.
virtual void slotActivated(QListWidgetItem *)
Called when an item is activated.
void resizeAndReposition()
This properly resizes and repositions the listbox.
~KCompletionBox() override
Destroys the box.
void userCancelled(const QString &)
Emitted whenever the user chooses to ignore the available selections and closes this box.
void textActivated(const QString &text)
Emitted when an item is selected, text is the text of the selected item.
void end()
Moves the selection down to the last item.
void setItems(const QStringList &items)
Clears the box and inserts items.
void home()
Moves the selection up to the first item.
void setCancelledText(const QString &text)
Sets the text to be emitted if the user chooses not to pick from the available matches.
void setActivateOnSelect(bool doEmit)
Set whether or not the selected signal should be emitted when an item is selected.
void pageUp()
Moves the selection one page up.
KCompletionBox(QWidget *parent=nullptr)
Constructs a KCompletionBox.
void down()
Moves the selection one line down or select the first item if nothing is selected yet.
bool eventFilter(QObject *, QEvent *) override
Reimplemented from QListWidget to get events from the viewport (to hide this widget on mouse-click,...
virtual QPoint globalPositionHint() const
The preferred global coordinate at which the completion box's top left corner should be positioned.
QStringList items() const
Returns a list of all items currently in the box.
virtual void popup()
Adjusts the size of the box to fit the width of the parent given in the constructor and pops it up at...
void pageDown()
Moves the selection one page down.
void setTabHandling(bool enable)
Makes this widget (when visible) capture Tab-key events to traverse the items in the dropdown list (T...
QRect calculateGeometry() const
This calculates the size of the dropdown and the relative position of the top left corner with respec...
void setVisible(bool visible) override
Reimplemented for internal reasons.
An enhanced QLineEdit widget for inputting text.
Definition klineedit.h:139
virtual bool eventFilter(QObject *object, QEvent *event) override
QItemSelectionModel * selectionModel() const const
void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy)
void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy)
QWidget * activeWindow()
void accept()
Type type() const const
void setLineWidth(int)
void setFrameStyle(int style)
QScreen * screenAt(const QPoint &point)
virtual void setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
void append(QList< T > &&value)
void reserve(qsizetype size)
virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override
void setUniformItemSizes(bool enable)
virtual QRect visualRect(const QModelIndex &index) const const override
void addItem(QListWidgetItem *item)
void addItems(const QStringList &labels)
QListWidgetItem * currentItem() const const
void setCurrentRow(int row)
void currentTextChanged(const QString &currentText)
virtual bool event(QEvent *e) override
void insertItems(int row, const QStringList &labels)
QListWidgetItem * item(int row) const const
void itemClicked(QListWidgetItem *item)
void itemDoubleClicked(QListWidgetItem *item)
int row(const QListWidgetItem *item) const const
QList< QListWidgetItem * > selectedItems() const const
QListWidgetItem * takeItem(int row)
QRect visualItemRect(const QListWidgetItem *item) const const
void setText(const QString &text)
QString text() const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
T qobject_cast(QObject *object)
bool setProperty(const char *name, QVariant &&value)
bool signalsBlocked() const const
int bottom() const const
int height() const const
int right() const const
QSize size() const const
PopupFocusReason
Key_Backtab
ShiftModifier
NoButton
ScrollBarAsNeeded
WA_ShowWithoutActivating
void hide()
bool isAncestorOf(const QWidget *child) const const
QPoint mapToGlobal(const QPoint &pos) const const
QScreen * screen() const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void show()
bool isVisible() const const
void setWindowFlags(Qt::WindowFlags type)
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.