1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qcombobox.h"
5
6#include <qstylepainter.h>
7#include <qpa/qplatformtheme.h>
8#include <qpa/qplatformmenu.h>
9#include <qlineedit.h>
10#include <qapplication.h>
11#include <qlistview.h>
12#if QT_CONFIG(tableview)
13#include <qtableview.h>
14#endif
15#include <qabstractitemdelegate.h>
16#include <qmap.h>
17#if QT_CONFIG(menu)
18#include <qmenu.h>
19#endif
20#include <qevent.h>
21#include <qlayout.h>
22#include <qscrollbar.h>
23#if QT_CONFIG(treeview)
24#include <qtreeview.h>
25#endif
26#include <qheaderview.h>
27#include <qmath.h>
28#include <qmetaobject.h>
29#if QT_CONFIG(proxymodel)
30#include <qabstractproxymodel.h>
31#endif
32#include <qstylehints.h>
33#include <private/qguiapplication_p.h>
34#include <private/qhighdpiscaling_p.h>
35#include <private/qapplication_p.h>
36#include <private/qcombobox_p.h>
37#include <private/qabstractitemmodel_p.h>
38#include <private/qabstractscrollarea_p.h>
39#include <private/qlineedit_p.h>
40#if QT_CONFIG(completer)
41#include <private/qcompleter_p.h>
42#endif
43#include <qdebug.h>
44#if QT_CONFIG(effects)
45# include <private/qeffects_p.h>
46#endif
47#include <private/qstyle_p.h>
48#if QT_CONFIG(accessibility)
49#include "qaccessible.h"
50#endif
51#include <array>
52
53#include <QtCore/qpointer.h>
54
55QT_BEGIN_NAMESPACE
56
57using namespace Qt::StringLiterals;
58
59QComboBoxPrivate::QComboBoxPrivate()
60 : QWidgetPrivate(),
61 shownOnce(false),
62 duplicatesEnabled(false),
63 frame(true),
64 inserting(false),
65 hidingPopup(false)
66{
67}
68
69QComboBoxPrivate::~QComboBoxPrivate()
70{
71 disconnectModel();
72#ifdef Q_OS_MAC
73 cleanupNativePopup();
74#endif
75}
76
77QStyleOptionMenuItem QComboMenuDelegate::getStyleOption(const QStyleOptionViewItem &option,
78 const QModelIndex &index) const
79{
80 QStyleOptionMenuItem menuOption;
81
82 QPalette resolvedpalette = option.palette.resolve(other: QApplication::palette(className: "QMenu"));
83 QVariant value = index.data(arole: Qt::ForegroundRole);
84 if (value.canConvert<QBrush>()) {
85 resolvedpalette.setBrush(acr: QPalette::WindowText, abrush: qvariant_cast<QBrush>(v: value));
86 resolvedpalette.setBrush(acr: QPalette::ButtonText, abrush: qvariant_cast<QBrush>(v: value));
87 resolvedpalette.setBrush(acr: QPalette::Text, abrush: qvariant_cast<QBrush>(v: value));
88 }
89 menuOption.palette = resolvedpalette;
90 menuOption.state = QStyle::State_None;
91 if (mCombo->window()->isActiveWindow())
92 menuOption.state = QStyle::State_Active;
93 if ((option.state & QStyle::State_Enabled) && (index.model()->flags(index) & Qt::ItemIsEnabled))
94 menuOption.state |= QStyle::State_Enabled;
95 else
96 menuOption.palette.setCurrentColorGroup(QPalette::Disabled);
97 if (option.state & QStyle::State_Selected)
98 menuOption.state |= QStyle::State_Selected;
99 menuOption.checkType = QStyleOptionMenuItem::NonExclusive;
100 // a valid checkstate means that the model has checkable items
101 const QVariant checkState = index.data(arole: Qt::CheckStateRole);
102 if (!checkState.isValid()) {
103 menuOption.checked = mCombo->currentIndex() == index.row();
104 } else {
105 menuOption.checked = qvariant_cast<int>(v: checkState) == Qt::Checked;
106 menuOption.state |= qvariant_cast<int>(v: checkState) == Qt::Checked
107 ? QStyle::State_On : QStyle::State_Off;
108 }
109 if (QComboBoxDelegate::isSeparator(index))
110 menuOption.menuItemType = QStyleOptionMenuItem::Separator;
111 else
112 menuOption.menuItemType = QStyleOptionMenuItem::Normal;
113
114 const QVariant variant = index.data(arole: Qt::DecorationRole);
115 switch (variant.userType()) {
116 case QMetaType::QIcon:
117 menuOption.icon = qvariant_cast<QIcon>(v: variant);
118 break;
119 case QMetaType::QColor: {
120 static QPixmap pixmap(option.decorationSize);
121 pixmap.fill(fillColor: qvariant_cast<QColor>(v: variant));
122 menuOption.icon = pixmap;
123 break; }
124 default:
125 menuOption.icon = qvariant_cast<QPixmap>(v: variant);
126 break;
127 }
128 if (index.data(arole: Qt::BackgroundRole).canConvert<QBrush>()) {
129 menuOption.palette.setBrush(cg: QPalette::All, cr: QPalette::Window,
130 brush: qvariant_cast<QBrush>(v: index.data(arole: Qt::BackgroundRole)));
131 }
132 menuOption.text = index.data(arole: Qt::DisplayRole).toString().replace(c: u'&', after: "&&"_L1);
133 menuOption.reservedShortcutWidth = 0;
134 menuOption.maxIconWidth = option.decorationSize.width() + 4;
135 menuOption.menuRect = option.rect;
136 menuOption.rect = option.rect;
137
138 // Make sure fonts set on the model or on the combo box, in
139 // that order, also override the font for the popup menu.
140 QVariant fontRoleData = index.data(arole: Qt::FontRole);
141 if (fontRoleData.isValid()) {
142 menuOption.font = qvariant_cast<QFont>(v: fontRoleData);
143 } else if (mCombo->testAttribute(attribute: Qt::WA_SetFont)
144 || mCombo->testAttribute(attribute: Qt::WA_MacSmallSize)
145 || mCombo->testAttribute(attribute: Qt::WA_MacMiniSize)
146 || mCombo->font() != qt_app_fonts_hash()->value(key: "QComboBox", defaultValue: QFont())) {
147 menuOption.font = mCombo->font();
148 } else {
149 menuOption.font = qt_app_fonts_hash()->value(key: "QComboMenuItem", defaultValue: mCombo->font());
150 }
151
152 menuOption.fontMetrics = QFontMetrics(menuOption.font);
153
154 return menuOption;
155}
156
157bool QComboMenuDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
158 const QStyleOptionViewItem &option, const QModelIndex &index)
159{
160 Q_ASSERT(event);
161 Q_ASSERT(model);
162
163 // make sure that the item is checkable
164 Qt::ItemFlags flags = model->flags(index);
165 if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled)
166 || !(flags & Qt::ItemIsEnabled))
167 return false;
168
169 // make sure that we have a check state
170 const QVariant checkState = index.data(arole: Qt::CheckStateRole);
171 if (!checkState.isValid())
172 return false;
173
174 // make sure that we have the right event type
175 if ((event->type() == QEvent::MouseButtonRelease)
176 || (event->type() == QEvent::MouseButtonDblClick)
177 || (event->type() == QEvent::MouseButtonPress)) {
178 QMouseEvent *me = static_cast<QMouseEvent*>(event);
179 if (me->button() != Qt::LeftButton)
180 return false;
181
182 if ((event->type() == QEvent::MouseButtonPress)
183 || (event->type() == QEvent::MouseButtonDblClick)) {
184 pressedIndex = index.row();
185 return false;
186 }
187
188 if (index.row() != pressedIndex)
189 return false;
190 pressedIndex = -1;
191
192 } else if (event->type() == QEvent::KeyPress) {
193 if (static_cast<QKeyEvent*>(event)->key() != Qt::Key_Space
194 && static_cast<QKeyEvent*>(event)->key() != Qt::Key_Select)
195 return false;
196 } else {
197 return false;
198 }
199
200 // we don't support user-tristate items in QComboBox (not implemented in any style)
201 Qt::CheckState newState = (static_cast<Qt::CheckState>(checkState.toInt()) == Qt::Checked)
202 ? Qt::Unchecked : Qt::Checked;
203 return model->setData(index, value: newState, role: Qt::CheckStateRole);
204}
205
206#if QT_CONFIG(completer)
207void QComboBoxPrivate::completerActivated(const QModelIndex &index)
208{
209 Q_Q(QComboBox);
210#if QT_CONFIG(proxymodel)
211 if (index.isValid() && q->completer()) {
212 QAbstractProxyModel *proxy = qobject_cast<QAbstractProxyModel *>(object: q->completer()->completionModel());
213 if (proxy) {
214 const QModelIndex &completerIndex = proxy->mapToSource(proxyIndex: index);
215 int row = -1;
216 if (completerIndex.model() == model) {
217 row = completerIndex.row();
218 } else {
219 // if QCompleter uses a proxy model to host widget's one - map again
220 QAbstractProxyModel *completerProxy = qobject_cast<QAbstractProxyModel *>(object: q->completer()->model());
221 if (completerProxy && completerProxy->sourceModel() == model) {
222 row = completerProxy->mapToSource(proxyIndex: completerIndex).row();
223 } else {
224 QString match = q->completer()->model()->data(index: completerIndex).toString();
225 row = q->findText(text: match, flags: matchFlags());
226 }
227 }
228 q->setCurrentIndex(row);
229 emitActivated(index: currentIndex);
230 }
231 }
232#endif
233}
234#endif // QT_CONFIG(completer)
235
236void QComboBoxPrivate::updateArrow(QStyle::StateFlag state)
237{
238 Q_Q(QComboBox);
239 if (arrowState == state)
240 return;
241 arrowState = state;
242 QStyleOptionComboBox opt;
243 q->initStyleOption(option: &opt);
244 q->update(q->rect());
245}
246
247void QComboBoxPrivate::modelReset()
248{
249 Q_Q(QComboBox);
250 if (lineEdit) {
251 lineEdit->setText(QString());
252 updateLineEditGeometry();
253 }
254 trySetValidIndex();
255 modelChanged();
256 q->update();
257}
258
259void QComboBoxPrivate::modelDestroyed()
260{
261 model = QAbstractItemModelPrivate::staticEmptyModel();
262}
263
264void QComboBoxPrivate::trySetValidIndex()
265{
266 Q_Q(QComboBox);
267 bool currentReset = false;
268
269 const int rowCount = q->count();
270 for (int pos = 0; pos < rowCount; ++pos) {
271 const QModelIndex idx(model->index(row: pos, column: modelColumn, parent: root));
272 if (idx.flags() & Qt::ItemIsEnabled) {
273 setCurrentIndex(idx);
274 currentReset = true;
275 break;
276 }
277 }
278
279 if (!currentReset)
280 setCurrentIndex(QModelIndex());
281}
282
283QRect QComboBoxPrivate::popupGeometry(const QPoint &globalPosition) const
284{
285 Q_Q(const QComboBox);
286 return QStylePrivate::useFullScreenForPopup()
287 ? QWidgetPrivate::screenGeometry(widget: q, globalPosition)
288 : QWidgetPrivate::availableScreenGeometry(widget: q, globalPosition);
289}
290
291bool QComboBoxPrivate::updateHoverControl(const QPoint &pos)
292{
293
294 Q_Q(QComboBox);
295 QRect lastHoverRect = hoverRect;
296 QStyle::SubControl lastHoverControl = hoverControl;
297 bool doesHover = q->testAttribute(attribute: Qt::WA_Hover);
298 if (lastHoverControl != newHoverControl(pos) && doesHover) {
299 q->update(lastHoverRect);
300 q->update(hoverRect);
301 return true;
302 }
303 return !doesHover;
304}
305
306QStyle::SubControl QComboBoxPrivate::newHoverControl(const QPoint &pos)
307{
308 Q_Q(QComboBox);
309 QStyleOptionComboBox opt;
310 q->initStyleOption(option: &opt);
311 opt.subControls = QStyle::SC_All;
312 hoverControl = q->style()->hitTestComplexControl(cc: QStyle::CC_ComboBox, opt: &opt, pt: pos, widget: q);
313 hoverRect = (hoverControl != QStyle::SC_None)
314 ? q->style()->subControlRect(cc: QStyle::CC_ComboBox, opt: &opt, sc: hoverControl, widget: q)
315 : QRect();
316 return hoverControl;
317}
318
319/*
320 Computes a size hint based on the maximum width
321 for the items in the combobox.
322*/
323int QComboBoxPrivate::computeWidthHint() const
324{
325 Q_Q(const QComboBox);
326
327 int width = 0;
328 const int count = q->count();
329 const int iconWidth = q->iconSize().width() + 4;
330 const QFontMetrics &fontMetrics = q->fontMetrics();
331
332 for (int i = 0; i < count; ++i) {
333 const int textWidth = fontMetrics.horizontalAdvance(q->itemText(index: i));
334 if (q->itemIcon(index: i).isNull())
335 width = (qMax(a: width, b: textWidth));
336 else
337 width = (qMax(a: width, b: textWidth + iconWidth));
338 }
339
340 QStyleOptionComboBox opt;
341 q->initStyleOption(option: &opt);
342 QSize tmp(width, 0);
343 tmp = q->style()->sizeFromContents(ct: QStyle::CT_ComboBox, opt: &opt, contentsSize: tmp, w: q);
344 return tmp.width();
345}
346
347QSize QComboBoxPrivate::recomputeSizeHint(QSize &sh) const
348{
349 Q_Q(const QComboBox);
350 if (!sh.isValid()) {
351 bool hasIcon = sizeAdjustPolicy == QComboBox::AdjustToMinimumContentsLengthWithIcon;
352 int count = q->count();
353 QSize iconSize = q->iconSize();
354 const QFontMetrics &fm = q->fontMetrics();
355
356 // text width
357 if (&sh == &sizeHint || minimumContentsLength == 0) {
358 switch (sizeAdjustPolicy) {
359 case QComboBox::AdjustToContents:
360 case QComboBox::AdjustToContentsOnFirstShow:
361 if (count == 0) {
362 sh.rwidth() = 7 * fm.horizontalAdvance(u'x');
363 } else {
364 for (int i = 0; i < count; ++i) {
365 if (!q->itemIcon(index: i).isNull()) {
366 hasIcon = true;
367 sh.setWidth(qMax(a: sh.width(), b: fm.boundingRect(text: q->itemText(index: i)).width() + iconSize.width() + 4));
368 } else {
369 sh.setWidth(qMax(a: sh.width(), b: fm.boundingRect(text: q->itemText(index: i)).width()));
370 }
371 }
372 }
373 break;
374 case QComboBox::AdjustToMinimumContentsLengthWithIcon:
375 ;
376 }
377 } else {
378 for (int i = 0; i < count && !hasIcon; ++i)
379 hasIcon = !q->itemIcon(index: i).isNull();
380 }
381 if (minimumContentsLength > 0)
382 sh.setWidth(qMax(a: sh.width(), b: minimumContentsLength * fm.horizontalAdvance(u'X') + (hasIcon ? iconSize.width() + 4 : 0)));
383 if (!placeholderText.isEmpty())
384 sh.setWidth(qMax(a: sh.width(), b: fm.boundingRect(text: placeholderText).width()));
385
386
387 // height
388 sh.setHeight(qMax(a: qCeil(v: QFontMetricsF(fm).height()), b: 14) + 2);
389 if (hasIcon) {
390 sh.setHeight(qMax(a: sh.height(), b: iconSize.height() + 2));
391 }
392
393 // add style and strut values
394 QStyleOptionComboBox opt;
395 q->initStyleOption(option: &opt);
396 sh = q->style()->sizeFromContents(ct: QStyle::CT_ComboBox, opt: &opt, contentsSize: sh, w: q);
397 }
398 return sh;
399}
400
401void QComboBoxPrivate::adjustComboBoxSize()
402{
403 viewContainer()->adjustSizeTimer.start(msec: 20, obj: container);
404}
405
406void QComboBoxPrivate::updateLayoutDirection()
407{
408 Q_Q(const QComboBox);
409 QStyleOptionComboBox opt;
410 q->initStyleOption(option: &opt);
411 Qt::LayoutDirection dir = Qt::LayoutDirection(
412 q->style()->styleHint(stylehint: QStyle::SH_ComboBox_LayoutDirection, opt: &opt, widget: q));
413 if (lineEdit)
414 lineEdit->setLayoutDirection(dir);
415 if (container)
416 container->setLayoutDirection(dir);
417}
418
419
420void QComboBoxPrivateContainer::timerEvent(QTimerEvent *timerEvent)
421{
422 if (timerEvent->timerId() == adjustSizeTimer.timerId()) {
423 adjustSizeTimer.stop();
424 if (combo->sizeAdjustPolicy() == QComboBox::AdjustToContents) {
425 combo->updateGeometry();
426 combo->adjustSize();
427 combo->update();
428 }
429 }
430}
431
432void QComboBoxPrivateContainer::resizeEvent(QResizeEvent *e)
433{
434 QStyleOptionComboBox opt = comboStyleOption();
435 if (combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo)) {
436 QStyleOption myOpt;
437 myOpt.initFrom(w: this);
438 QStyleHintReturnMask mask;
439 if (combo->style()->styleHint(stylehint: QStyle::SH_Menu_Mask, opt: &myOpt, widget: this, returnData: &mask)) {
440 setMask(mask.region);
441 }
442 } else {
443 clearMask();
444 }
445 QFrame::resizeEvent(event: e);
446}
447
448void QComboBoxPrivateContainer::paintEvent(QPaintEvent *e)
449{
450 QStyleOptionComboBox cbOpt = comboStyleOption();
451 if (combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &cbOpt, widget: combo)
452 && mask().isEmpty()) {
453 QStyleOption opt;
454 opt.initFrom(w: this);
455 QPainter p(this);
456 style()->drawPrimitive(pe: QStyle::PE_PanelMenu, opt: &opt, p: &p, w: this);
457 }
458
459 QFrame::paintEvent(e);
460}
461
462QComboBoxPrivateContainer::QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent)
463 : QFrame(parent, Qt::Popup), combo(parent)
464{
465 // we need the combobox and itemview
466 Q_ASSERT(parent);
467 Q_ASSERT(itemView);
468
469 setAttribute(Qt::WA_WindowPropagation);
470 setAttribute(Qt::WA_X11NetWmWindowTypeCombo);
471
472 // setup container
473 blockMouseReleaseTimer.setSingleShot(true);
474
475 // we need a vertical layout
476 QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
477 layout->setSpacing(0);
478 layout->setContentsMargins(QMargins());
479
480 // set item view
481 setItemView(itemView);
482
483 // add scroller arrows if style needs them
484 QStyleOptionComboBox opt = comboStyleOption();
485 const bool usePopup = combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo);
486 if (usePopup) {
487 top = new QComboBoxPrivateScroller(QAbstractSlider::SliderSingleStepSub, this);
488 bottom = new QComboBoxPrivateScroller(QAbstractSlider::SliderSingleStepAdd, this);
489 top->hide();
490 bottom->hide();
491 } else {
492 setLineWidth(1);
493 }
494
495 if (top) {
496 layout->insertWidget(index: 0, widget: top);
497 connect(sender: top, signal: &QComboBoxPrivateScroller::doScroll,
498 context: this, slot: &QComboBoxPrivateContainer::scrollItemView);
499 }
500 if (bottom) {
501 layout->addWidget(bottom);
502 connect(sender: bottom, signal: &QComboBoxPrivateScroller::doScroll,
503 context: this, slot: &QComboBoxPrivateContainer::scrollItemView);
504 }
505
506 // Some styles (Mac) have a margin at the top and bottom of the popup.
507 layout->insertSpacing(index: 0, size: 0);
508 layout->addSpacing(size: 0);
509 updateStyleSettings();
510}
511
512QComboBoxPrivateContainer::~QComboBoxPrivateContainer()
513{
514 disconnect(sender: view, signal: &QAbstractItemView::destroyed,
515 receiver: this, slot: &QComboBoxPrivateContainer::viewDestroyed);
516}
517
518void QComboBoxPrivateContainer::scrollItemView(int action)
519{
520#if QT_CONFIG(scrollbar)
521 if (view->verticalScrollBar())
522 view->verticalScrollBar()->triggerAction(action: static_cast<QAbstractSlider::SliderAction>(action));
523#endif
524}
525
526void QComboBoxPrivateContainer::hideScrollers()
527{
528 if (top)
529 top->hide();
530 if (bottom)
531 bottom->hide();
532}
533
534/*
535 Hides or shows the scrollers when we emulate a popupmenu
536*/
537void QComboBoxPrivateContainer::updateScrollers()
538{
539#if QT_CONFIG(scrollbar)
540 if (!top || !bottom)
541 return;
542
543 if (isVisible() == false)
544 return;
545
546 QStyleOptionComboBox opt = comboStyleOption();
547 if (combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo) &&
548 view->verticalScrollBar()->minimum() < view->verticalScrollBar()->maximum()) {
549
550 bool needTop = view->verticalScrollBar()->value()
551 > (view->verticalScrollBar()->minimum() + topMargin());
552 bool needBottom = view->verticalScrollBar()->value()
553 < (view->verticalScrollBar()->maximum() - bottomMargin() - topMargin());
554 if (needTop)
555 top->show();
556 else
557 top->hide();
558 if (needBottom)
559 bottom->show();
560 else
561 bottom->hide();
562 } else {
563 top->hide();
564 bottom->hide();
565 }
566#endif // QT_CONFIG(scrollbar)
567}
568
569/*
570 Cleans up when the view is destroyed.
571*/
572void QComboBoxPrivateContainer::viewDestroyed()
573{
574 view = nullptr;
575 setItemView(new QComboBoxListView());
576}
577
578/*
579 Returns the item view used for the combobox popup.
580*/
581QAbstractItemView *QComboBoxPrivateContainer::itemView() const
582{
583 return view;
584}
585
586/*!
587 Sets the item view to be used for the combobox popup.
588*/
589void QComboBoxPrivateContainer::setItemView(QAbstractItemView *itemView)
590{
591 Q_ASSERT(itemView);
592
593 // clean up old one
594 if (view) {
595 view->removeEventFilter(obj: this);
596 view->viewport()->removeEventFilter(obj: this);
597#if QT_CONFIG(scrollbar)
598 disconnect(sender: view->verticalScrollBar(), signal: &QScrollBar::valueChanged,
599 receiver: this, slot: &QComboBoxPrivateContainer::updateScrollers);
600 disconnect(sender: view->verticalScrollBar(), signal: &QScrollBar::rangeChanged,
601 receiver: this, slot: &QComboBoxPrivateContainer::updateScrollers);
602#endif
603 disconnect(sender: view, signal: &QAbstractItemView::destroyed,
604 receiver: this, slot: &QComboBoxPrivateContainer::viewDestroyed);
605
606 if (isAncestorOf(child: view))
607 delete view;
608 view = nullptr;
609 }
610
611 // setup the item view
612 view = itemView;
613 view->setParent(this);
614 view->setAttribute(Qt::WA_MacShowFocusRect, on: false);
615 qobject_cast<QBoxLayout*>(object: layout())->insertWidget(index: top ? 2 : 0, widget: view);
616 view->setSizePolicy(hor: QSizePolicy::Ignored, ver: QSizePolicy::Ignored);
617 view->installEventFilter(filterObj: this);
618 view->viewport()->installEventFilter(filterObj: this);
619 view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
620 QStyleOptionComboBox opt = comboStyleOption();
621 const bool usePopup = combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo);
622#if QT_CONFIG(scrollbar)
623 if (usePopup)
624 view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
625#endif
626 if (combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_ListMouseTracking, opt: &opt, widget: combo) ||
627 usePopup) {
628 view->setMouseTracking(true);
629 }
630 view->setSelectionMode(QAbstractItemView::SingleSelection);
631 view->setFrameStyle(QFrame::NoFrame);
632 view->setLineWidth(0);
633 view->setEditTriggers(QAbstractItemView::NoEditTriggers);
634#if QT_CONFIG(scrollbar)
635 connect(sender: view->verticalScrollBar(), signal: &QScrollBar::valueChanged,
636 context: this, slot: &QComboBoxPrivateContainer::updateScrollers);
637 connect(sender: view->verticalScrollBar(), signal: &QScrollBar::rangeChanged,
638 context: this, slot: &QComboBoxPrivateContainer::updateScrollers);
639#endif
640 connect(sender: view, signal: &QAbstractItemView::destroyed,
641 context: this, slot: &QComboBoxPrivateContainer::viewDestroyed);
642}
643
644/*!
645 Returns the top/bottom vertical margin of the view.
646*/
647int QComboBoxPrivateContainer::topMargin() const
648{
649 if (const QListView *lview = qobject_cast<const QListView*>(object: view))
650 return lview->spacing();
651#if QT_CONFIG(tableview)
652 if (const QTableView *tview = qobject_cast<const QTableView*>(object: view))
653 return tview->showGrid() ? 1 : 0;
654#endif
655 return 0;
656}
657
658/*!
659 Returns the spacing between the items in the view.
660*/
661int QComboBoxPrivateContainer::spacing() const
662{
663 QListView *lview = qobject_cast<QListView*>(object: view);
664 if (lview)
665 return 2 * lview->spacing(); // QListView::spacing is the padding around the item.
666#if QT_CONFIG(tableview)
667 QTableView *tview = qobject_cast<QTableView*>(object: view);
668 if (tview)
669 return tview->showGrid() ? 1 : 0;
670#endif
671 return 0;
672}
673
674void QComboBoxPrivateContainer::updateTopBottomMargin()
675{
676 if (!layout() || layout()->count() < 1)
677 return;
678
679 QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(object: layout());
680 if (!boxLayout)
681 return;
682
683 const QStyleOptionComboBox opt = comboStyleOption();
684 const bool usePopup = combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo);
685 const int margin = usePopup ? combo->style()->pixelMetric(metric: QStyle::PM_MenuVMargin, option: &opt, widget: combo) : 0;
686
687 QSpacerItem *topSpacer = boxLayout->itemAt(0)->spacerItem();
688 if (topSpacer)
689 topSpacer->changeSize(w: 0, h: margin, hData: QSizePolicy::Minimum, vData: QSizePolicy::Fixed);
690
691 QSpacerItem *bottomSpacer = boxLayout->itemAt(boxLayout->count() - 1)->spacerItem();
692 if (bottomSpacer && bottomSpacer != topSpacer)
693 bottomSpacer->changeSize(w: 0, h: margin, hData: QSizePolicy::Minimum, vData: QSizePolicy::Fixed);
694
695 boxLayout->invalidate();
696}
697
698void QComboBoxPrivateContainer::updateStyleSettings()
699{
700 // add scroller arrows if style needs them
701 QStyleOptionComboBox opt = comboStyleOption();
702 view->setMouseTracking(combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_ListMouseTracking, opt: &opt, widget: combo) ||
703 combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo));
704 setFrameStyle(combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_PopupFrameStyle, opt: &opt, widget: combo));
705 updateTopBottomMargin();
706}
707
708void QComboBoxPrivateContainer::changeEvent(QEvent *e)
709{
710 if (e->type() == QEvent::StyleChange)
711 updateStyleSettings();
712
713 QFrame::changeEvent(e);
714}
715
716
717bool QComboBoxPrivateContainer::eventFilter(QObject *o, QEvent *e)
718{
719 switch (e->type()) {
720 case QEvent::ShortcutOverride: {
721 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(e);
722 switch (keyEvent->key()) {
723 case Qt::Key_Enter:
724 case Qt::Key_Return:
725#ifdef QT_KEYPAD_NAVIGATION
726 case Qt::Key_Select:
727#endif
728 if (view->currentIndex().isValid() && view->currentIndex().flags().testFlag(flag: Qt::ItemIsEnabled)) {
729 combo->hidePopup();
730 keyEvent->accept();
731 emit itemSelected(view->currentIndex());
732 }
733 return true;
734 case Qt::Key_Down:
735 if (!(keyEvent->modifiers() & Qt::AltModifier))
736 break;
737 Q_FALLTHROUGH();
738 case Qt::Key_F4:
739 combo->hidePopup();
740 keyEvent->accept();
741 emit itemSelected(view->currentIndex());
742 return true;
743 default:
744#if QT_CONFIG(shortcut)
745 if (keyEvent->matches(key: QKeySequence::Cancel) && isVisible()) {
746 keyEvent->accept();
747 return true;
748 }
749#endif
750 break;
751 }
752 break;
753 }
754 case QEvent::MouseMove:
755 if (isVisible()) {
756 QMouseEvent *m = static_cast<QMouseEvent *>(e);
757 QWidget *widget = static_cast<QWidget *>(o);
758 QPoint vector = widget->mapToGlobal(m->position().toPoint()) - initialClickPosition;
759 if (vector.manhattanLength() > 9 && blockMouseReleaseTimer.isActive())
760 blockMouseReleaseTimer.stop();
761 QModelIndex indexUnderMouse = view->indexAt(point: m->position().toPoint());
762 if (indexUnderMouse.isValid()
763 && !QComboBoxDelegate::isSeparator(index: indexUnderMouse)) {
764 view->setCurrentIndex(indexUnderMouse);
765 }
766 }
767 break;
768 case QEvent::MouseButtonPress:
769 maybeIgnoreMouseButtonRelease = false;
770 break;
771 case QEvent::MouseButtonRelease: {
772 bool ignoreEvent = maybeIgnoreMouseButtonRelease && popupTimer.elapsed() < QApplication::doubleClickInterval();
773
774 QMouseEvent *m = static_cast<QMouseEvent *>(e);
775 if (isVisible() && view->rect().contains(p: m->position().toPoint()) && view->currentIndex().isValid()
776 && !blockMouseReleaseTimer.isActive() && !ignoreEvent
777 && (view->currentIndex().flags().testFlag(flag: Qt::ItemIsEnabled))
778 && (view->currentIndex().flags().testFlag(flag: Qt::ItemIsSelectable))) {
779 combo->hidePopup();
780 emit itemSelected(view->currentIndex());
781 return true;
782 }
783 break;
784 }
785 default:
786 break;
787 }
788 return QFrame::eventFilter(watched: o, event: e);
789}
790
791void QComboBoxPrivateContainer::showEvent(QShowEvent *)
792{
793 combo->update();
794}
795
796void QComboBoxPrivateContainer::hideEvent(QHideEvent *)
797{
798 emit resetButton();
799 combo->update();
800#if QT_CONFIG(graphicsview)
801 // QGraphicsScenePrivate::removePopup closes the combo box popup, it hides it non-explicitly.
802 // Hiding/showing the QComboBox after this will unexpectedly show the popup as well.
803 // Re-hiding the popup container makes sure it is explicitly hidden.
804 if (QGraphicsProxyWidget *proxy = graphicsProxyWidget())
805 proxy->hide();
806#endif
807}
808
809void QComboBoxPrivateContainer::mousePressEvent(QMouseEvent *e)
810{
811
812 QStyleOptionComboBox opt = comboStyleOption();
813 opt.subControls = QStyle::SC_All;
814 opt.activeSubControls = QStyle::SC_ComboBoxArrow;
815 QStyle::SubControl sc = combo->style()->hitTestComplexControl(cc: QStyle::CC_ComboBox, opt: &opt,
816 pt: combo->mapFromGlobal(e->globalPosition().toPoint()),
817 widget: combo);
818 if ((combo->isEditable() && sc == QStyle::SC_ComboBoxArrow)
819 || (!combo->isEditable() && sc != QStyle::SC_None))
820 setAttribute(Qt::WA_NoMouseReplay);
821 combo->hidePopup();
822}
823
824void QComboBoxPrivateContainer::mouseReleaseEvent(QMouseEvent *e)
825{
826 Q_UNUSED(e);
827 if (!blockMouseReleaseTimer.isActive()) {
828 combo->hidePopup();
829 emit resetButton();
830 }
831}
832
833QStyleOptionComboBox QComboBoxPrivateContainer::comboStyleOption() const
834{
835 // ### This should use QComboBox's initStyleOption(), but it's protected
836 // perhaps, we could cheat by having the QCombo private instead?
837 QStyleOptionComboBox opt;
838 opt.initFrom(w: combo);
839 opt.subControls = QStyle::SC_All;
840 opt.activeSubControls = QStyle::SC_None;
841 opt.editable = combo->isEditable();
842 return opt;
843}
844
845/*!
846 \enum QComboBox::InsertPolicy
847
848 This enum specifies what the QComboBox should do when a new string is
849 entered by the user.
850
851 \value NoInsert The string will not be inserted into the combobox.
852 \value InsertAtTop The string will be inserted as the first item in the combobox.
853 \value InsertAtCurrent The current item will be \e replaced by the string.
854 \value InsertAtBottom The string will be inserted after the last item in the combobox.
855 \value InsertAfterCurrent The string is inserted after the current item in the combobox.
856 \value InsertBeforeCurrent The string is inserted before the current item in the combobox.
857 \value InsertAlphabetically The string is inserted in the alphabetic order in the combobox.
858*/
859
860/*!
861 \enum QComboBox::SizeAdjustPolicy
862
863 This enum specifies how the size hint of the QComboBox should
864 adjust when new content is added or content changes.
865
866 \value AdjustToContents The combobox will always adjust to the contents
867 \value AdjustToContentsOnFirstShow The combobox will adjust to its contents the first time it is shown.
868 \value AdjustToMinimumContentsLengthWithIcon The combobox will adjust to \l minimumContentsLength plus space for an icon.
869 For performance reasons use this policy on large models.
870*/
871
872/*!
873 \fn void QComboBox::activated(int index)
874
875 This signal is sent when the user chooses an item in the combobox.
876 The item's \a index is passed. Note that this signal is sent even
877 when the choice is not changed. If you need to know when the
878 choice actually changes, use signal currentIndexChanged() or
879 currentTextChanged().
880*/
881
882/*!
883 \fn void QComboBox::textActivated(const QString &text)
884 \since 5.14
885
886 This signal is sent when the user chooses an item in the combobox.
887 The item's \a text is passed. Note that this signal is sent even
888 when the choice is not changed. If you need to know when the
889 choice actually changes, use signal currentIndexChanged() or
890 currentTextChanged().
891*/
892
893/*!
894 \fn void QComboBox::highlighted(int index)
895
896 This signal is sent when an item in the combobox popup list is
897 highlighted by the user. The item's \a index is passed.
898*/
899
900/*!
901 \fn void QComboBox::textHighlighted(const QString &text)
902 \since 5.14
903
904 This signal is sent when an item in the combobox popup list is
905 highlighted by the user. The item's \a text is passed.
906*/
907
908/*!
909 \fn void QComboBox::currentIndexChanged(int index)
910 \since 4.1
911
912 This signal is sent whenever the currentIndex in the combobox
913 changes either through user interaction or programmatically. The
914 item's \a index is passed or -1 if the combobox becomes empty or the
915 currentIndex was reset.
916*/
917
918/*!
919 \fn void QComboBox::currentTextChanged(const QString &text)
920 \since 5.0
921
922 This signal is emitted whenever currentText changes.
923 The new value is passed as \a text.
924
925 \note It is not emitted, if currentText remains the same,
926 even if currentIndex changes.
927*/
928
929/*!
930 Constructs a combobox with the given \a parent, using the default
931 model QStandardItemModel.
932*/
933QComboBox::QComboBox(QWidget *parent)
934 : QWidget(*new QComboBoxPrivate(), parent, { })
935{
936 Q_D(QComboBox);
937 d->init();
938}
939
940/*!
941 \internal
942*/
943QComboBox::QComboBox(QComboBoxPrivate &dd, QWidget *parent)
944 : QWidget(dd, parent, { })
945{
946 Q_D(QComboBox);
947 d->init();
948}
949
950/*!
951 \class QComboBox
952 \brief The QComboBox widget combines a button with a dropdown list.
953
954 \ingroup basicwidgets
955 \inmodule QtWidgets
956
957 \table
958 \row
959 \li \inlineimage collapsed_combobox.png
960 \caption Collapsed QCombobox
961 \li
962 \inlineimage expanded_combobox.png
963 \caption Expanded QCombobox
964 \endtable
965
966 \section1 Display Features
967 A QComboBox is a compact way to present a list of options to the user.
968
969 A combobox is a selection widget that shows the current item,
970 and pops up a list of selectable items when clicked. Comboboxes can
971 contain pixmaps as well as strings if the insertItem() and setItemText()
972 functions are suitably overloaded.
973
974 \section1 Editing Features
975 A combobox may be editable, allowing the user to modify each item in the
976 list. For editable comboboxes, the function clearEditText() is provided,
977 to clear the displayed string without changing the combobox's
978 contents.
979
980 When the user enters a new string in an editable combobox, the
981 widget may or may not insert it, and it can insert it in several
982 locations. The default policy is \l InsertAtBottom but you can change
983 this using setInsertPolicy().
984
985 It is possible to constrain the input to an editable combobox
986 using QValidator; see setValidator(). By default, any input is
987 accepted.
988
989 A combobox can be populated using the insert functions,
990 insertItem() and insertItems() for example. Items can be
991 changed with setItemText(). An item can be removed with
992 removeItem() and all items can be removed with clear(). The text
993 of the current item is returned by currentText(), and the text of
994 a numbered item is returned with text(). The current item can be
995 set with setCurrentIndex(). The number of items in the combobox is
996 returned by count(); the maximum number of items can be set with
997 setMaxCount(). You can allow editing using setEditable(). For
998 editable comboboxes you can set auto-completion using
999 setCompleter() and whether or not the user can add duplicates
1000 is set with setDuplicatesEnabled().
1001
1002 \section1 Signals
1003 There are three signals emitted if the current item of a combobox
1004 changes: currentIndexChanged(), currentTextChanged(), and activated().
1005 currentIndexChanged() and currentTextChanged() are always emitted
1006 regardless if the change
1007 was done programmatically or by user interaction, while
1008 activated() is only emitted when the change is caused by user
1009 interaction. The highlighted() signal is emitted when the user
1010 highlights an item in the combobox popup list. All three signals
1011 exist in two versions, one with a QString argument and one with an
1012 \c int argument. If the user selects or highlights a pixmap, only
1013 the \c int signals are emitted. Whenever the text of an editable
1014 combobox is changed, the editTextChanged() signal is emitted.
1015
1016 \section1 Model/View Framework
1017
1018 QComboBox uses the \l{Model/View Programming}{model/view framework} for its
1019 popup list and to store its items. By default a QStandardItemModel stores
1020 the items and a QListView subclass displays the popuplist. You can access
1021 the model and view directly (with model() and view()), but QComboBox also
1022 provides functions to set and get item data, for example, setItemData() and
1023 itemText(). You can also set a new model and view (with setModel()
1024 and setView()). For the text and icon in the combobox label, the data in
1025 the model that has the Qt::DisplayRole and Qt::DecorationRole is used.
1026
1027 \note You cannot alter the \l{QAbstractItemView::}{SelectionMode}
1028 of the view(), for example, by using
1029 \l{QAbstractItemView::}{setSelectionMode()}.
1030
1031 \sa QLineEdit, QSpinBox, QRadioButton, QButtonGroup
1032*/
1033
1034void QComboBoxPrivate::init()
1035{
1036 Q_Q(QComboBox);
1037#ifdef Q_OS_MACOS
1038 // On OS X, only line edits and list views always get tab focus. It's only
1039 // when we enable full keyboard access that other controls can get tab focus.
1040 // When it's not editable, a combobox looks like a button, and it behaves as
1041 // such in this respect.
1042 if (!q->isEditable())
1043 q->setFocusPolicy(Qt::TabFocus);
1044 else
1045#endif
1046 q->setFocusPolicy(Qt::WheelFocus);
1047
1048 q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed,
1049 QSizePolicy::ComboBox));
1050 setLayoutItemMargins(element: QStyle::SE_ComboBoxLayoutItem);
1051 q->setModel(new QStandardItemModel(0, 1, q));
1052 if (!q->isEditable())
1053 q->setAttribute(Qt::WA_InputMethodEnabled, on: false);
1054 else
1055 q->setAttribute(Qt::WA_InputMethodEnabled);
1056}
1057
1058QComboBoxPrivateContainer* QComboBoxPrivate::viewContainer()
1059{
1060 if (container)
1061 return container;
1062
1063 Q_Q(QComboBox);
1064 container = new QComboBoxPrivateContainer(new QComboBoxListView(q), q);
1065 disconnectModel();
1066 container->itemView()->setModel(model);
1067 connectModel();
1068 container->itemView()->setTextElideMode(Qt::ElideMiddle);
1069 updateDelegate(force: true);
1070 updateLayoutDirection();
1071 updateViewContainerPaletteAndOpacity();
1072 QObjectPrivate::connect(sender: container, signal: &QComboBoxPrivateContainer::itemSelected,
1073 receiverPrivate: this, slot: &QComboBoxPrivate::itemSelected);
1074 QObjectPrivate::connect(sender: container->itemView()->selectionModel(),
1075 signal: &QItemSelectionModel::currentChanged,
1076 receiverPrivate: this, slot: &QComboBoxPrivate::emitHighlighted);
1077 QObjectPrivate::connect(sender: container, signal: &QComboBoxPrivateContainer::resetButton,
1078 receiverPrivate: this, slot: &QComboBoxPrivate::resetButton);
1079 return container;
1080}
1081
1082
1083void QComboBoxPrivate::resetButton()
1084{
1085 updateArrow(state: QStyle::State_None);
1086}
1087
1088void QComboBoxPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
1089{
1090 Q_Q(QComboBox);
1091 if (inserting || topLeft.parent() != root)
1092 return;
1093
1094 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
1095 sizeHint = QSize();
1096 adjustComboBoxSize();
1097 q->updateGeometry();
1098 }
1099
1100 if (currentIndex.row() >= topLeft.row() && currentIndex.row() <= bottomRight.row()) {
1101 const QString text = q->itemText(index: currentIndex.row());
1102 if (lineEdit) {
1103 lineEdit->setText(text);
1104 updateLineEditGeometry();
1105 } else {
1106 updateCurrentText(text);
1107 }
1108 q->update();
1109#if QT_CONFIG(accessibility)
1110 QAccessibleValueChangeEvent event(q, text);
1111 QAccessible::updateAccessibility(event: &event);
1112#endif
1113 }
1114}
1115
1116void QComboBoxPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
1117{
1118 Q_Q(QComboBox);
1119 if (inserting || parent != root)
1120 return;
1121
1122 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
1123 sizeHint = QSize();
1124 adjustComboBoxSize();
1125 q->updateGeometry();
1126 }
1127
1128 // set current index if combo was previously empty and there is no placeholderText
1129 if (start == 0 && (end - start + 1) == q->count() && !currentIndex.isValid() &&
1130 placeholderText.isEmpty()) {
1131#if QT_CONFIG(accessibility)
1132 // This might have been called by the model emitting rowInserted(), at which
1133 // point the view won't have updated the accessibility bridge yet about its new
1134 // dimensions. Do it now so that the change of the selection matches the row
1135 // indexes of the accessibility bridge's representation.
1136 if (container && container->itemView()) {
1137 QAccessibleTableModelChangeEvent event(container->itemView(),
1138 QAccessibleTableModelChangeEvent::ModelReset);
1139 QAccessible::updateAccessibility(event: &event);
1140 }
1141#endif
1142 q->setCurrentIndex(0);
1143 // need to emit changed if model updated index "silently"
1144 } else if (currentIndex.row() != indexBeforeChange) {
1145 q->update();
1146 emitCurrentIndexChanged(index: currentIndex);
1147 }
1148}
1149
1150void QComboBoxPrivate::updateIndexBeforeChange()
1151{
1152 indexBeforeChange = currentIndex.row();
1153}
1154
1155void QComboBoxPrivate::rowsRemoved(const QModelIndex &parent, int /*start*/, int /*end*/)
1156{
1157 Q_Q(QComboBox);
1158 if (parent != root)
1159 return;
1160
1161 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
1162 sizeHint = QSize();
1163 adjustComboBoxSize();
1164 q->updateGeometry();
1165 }
1166
1167 // model has removed the last row
1168 if (model->rowCount(parent: root) == 0) {
1169 setCurrentIndex(QModelIndex());
1170 return;
1171 }
1172
1173 // model has changed the currentIndex
1174 if (currentIndex.row() != indexBeforeChange) {
1175 if (!currentIndex.isValid() && q->count()) {
1176 q->setCurrentIndex(qMin(a: q->count() - 1, b: qMax(a: indexBeforeChange, b: 0)));
1177 return;
1178 }
1179 if (lineEdit) {
1180 lineEdit->setText(q->itemText(index: currentIndex.row()));
1181 updateLineEditGeometry();
1182 }
1183 q->update();
1184 emitCurrentIndexChanged(index: currentIndex);
1185 }
1186}
1187
1188
1189void QComboBoxPrivate::updateViewContainerPaletteAndOpacity()
1190{
1191 if (!container)
1192 return;
1193 Q_Q(QComboBox);
1194 QStyleOptionComboBox opt;
1195 q->initStyleOption(option: &opt);
1196#if QT_CONFIG(menu)
1197 if (q->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: q)) {
1198 QMenu menu;
1199 menu.ensurePolished();
1200 container->setPalette(menu.palette());
1201 container->setWindowOpacity(menu.windowOpacity());
1202 } else
1203#endif
1204 {
1205 container->setPalette(q->palette());
1206 container->setWindowOpacity(1.0);
1207 }
1208 if (lineEdit)
1209 lineEdit->setPalette(q->palette());
1210}
1211
1212void QComboBoxPrivate::updateFocusPolicy()
1213{
1214#ifdef Q_OS_MACOS
1215 Q_Q(QComboBox);
1216
1217 // See comment in QComboBoxPrivate::init()
1218 if (q->isEditable())
1219 q->setFocusPolicy(Qt::WheelFocus);
1220 else
1221 q->setFocusPolicy(Qt::TabFocus);
1222#endif
1223}
1224
1225/*!
1226 Initialize \a option with the values from this QComboBox. This method
1227 is useful for subclasses when they need a QStyleOptionComboBox, but don't want
1228 to fill in all the information themselves.
1229
1230 \sa QStyleOption::initFrom()
1231*/
1232void QComboBox::initStyleOption(QStyleOptionComboBox *option) const
1233{
1234 if (!option)
1235 return;
1236
1237 Q_D(const QComboBox);
1238 option->initFrom(w: this);
1239 option->editable = isEditable();
1240 option->frame = d->frame;
1241 if (hasFocus() && !option->editable)
1242 option->state |= QStyle::State_Selected;
1243 option->subControls = QStyle::SC_All;
1244 if (d->arrowState == QStyle::State_Sunken) {
1245 option->activeSubControls = QStyle::SC_ComboBoxArrow;
1246 option->state |= d->arrowState;
1247 } else {
1248 option->activeSubControls = d->hoverControl;
1249 }
1250 option->currentText = currentText();
1251 if (d->currentIndex.isValid()) {
1252 option->currentIcon = d->itemIcon(index: d->currentIndex);
1253 QVariant alignment = d->model->data(index: d->currentIndex, role: Qt::TextAlignmentRole);
1254 if (alignment.isValid())
1255 option->textAlignment = static_cast<Qt::Alignment>(alignment.toUInt());
1256 }
1257 option->iconSize = iconSize();
1258 if (d->container && d->container->isVisible())
1259 option->state |= QStyle::State_On;
1260}
1261
1262void QComboBoxPrivate::updateLineEditGeometry()
1263{
1264 if (!lineEdit)
1265 return;
1266
1267 Q_Q(QComboBox);
1268 QStyleOptionComboBox opt;
1269 q->initStyleOption(option: &opt);
1270 QRect editRect = q->style()->subControlRect(cc: QStyle::CC_ComboBox, opt: &opt,
1271 sc: QStyle::SC_ComboBoxEditField, widget: q);
1272 if (currentIndex.isValid() && !q->itemIcon(index: q->currentIndex()).isNull()) {
1273 QRect comboRect(editRect);
1274 editRect.setWidth(editRect.width() - q->iconSize().width() - 4);
1275 editRect = QStyle::alignedRect(direction: q->layoutDirection(), alignment: Qt::AlignRight,
1276 size: editRect.size(), rectangle: comboRect);
1277 }
1278 lineEdit->setGeometry(editRect);
1279}
1280
1281Qt::MatchFlags QComboBoxPrivate::matchFlags() const
1282{
1283 // Base how duplicates are determined on the autocompletion case sensitivity
1284 Qt::MatchFlags flags = Qt::MatchFixedString;
1285#if QT_CONFIG(completer)
1286 if (!lineEdit->completer() || lineEdit->completer()->caseSensitivity() == Qt::CaseSensitive)
1287#endif
1288 flags |= Qt::MatchCaseSensitive;
1289 return flags;
1290}
1291
1292
1293void QComboBoxPrivate::editingFinished()
1294{
1295 Q_Q(QComboBox);
1296 if (!lineEdit)
1297 return;
1298 const auto leText = lineEdit->text();
1299 if (!leText.isEmpty() && itemText(index: currentIndex) != leText) {
1300#if QT_CONFIG(completer)
1301 const auto *leCompleter = lineEdit->completer();
1302 const auto *popup = leCompleter ? QCompleterPrivate::get(o: leCompleter)->popup : nullptr;
1303 if (popup && popup->isVisible()) {
1304 // QLineEdit::editingFinished() will be emitted before the code flow returns
1305 // to QCompleter::eventFilter(), where QCompleter::activated() may be emitted.
1306 // We know that the completer popup will still be visible at this point, and
1307 // that any selection should be valid.
1308 const QItemSelectionModel *selModel = popup->selectionModel();
1309 const QModelIndex curIndex = popup->currentIndex();
1310 const bool completerIsActive = selModel && selModel->selectedIndexes().contains(t: curIndex);
1311
1312 if (completerIsActive)
1313 return;
1314 }
1315#endif
1316 const int index = q_func()->findText(text: leText, flags: matchFlags());
1317 if (index != -1) {
1318 q->setCurrentIndex(index);
1319 emitActivated(index: currentIndex);
1320 }
1321 }
1322
1323}
1324
1325void QComboBoxPrivate::returnPressed()
1326{
1327 Q_Q(QComboBox);
1328
1329 // The insertion code below does not apply when the policy is QComboBox::NoInsert.
1330 // In case a completer is installed, item activation via the completer is handled
1331 // in completerActivated(). Otherwise editingFinished() updates the current
1332 // index as appropriate.
1333 if (insertPolicy == QComboBox::NoInsert)
1334 return;
1335
1336 if (lineEdit && !lineEdit->text().isEmpty()) {
1337 if (q->count() >= maxCount && !(this->insertPolicy == QComboBox::InsertAtCurrent))
1338 return;
1339 lineEdit->deselect();
1340 lineEdit->end(mark: false);
1341 QString text = lineEdit->text();
1342 // check for duplicates (if not enabled) and quit
1343 int index = -1;
1344 if (!duplicatesEnabled) {
1345 index = q->findText(text, flags: matchFlags());
1346 if (index != -1) {
1347 q->setCurrentIndex(index);
1348 emitActivated(index: currentIndex);
1349 return;
1350 }
1351 }
1352 switch (insertPolicy) {
1353 case QComboBox::InsertAtTop:
1354 index = 0;
1355 break;
1356 case QComboBox::InsertAtBottom:
1357 index = q->count();
1358 break;
1359 case QComboBox::InsertAtCurrent:
1360 case QComboBox::InsertAfterCurrent:
1361 case QComboBox::InsertBeforeCurrent:
1362 if (!q->count() || !currentIndex.isValid())
1363 index = 0;
1364 else if (insertPolicy == QComboBox::InsertAtCurrent)
1365 q->setItemText(index: q->currentIndex(), text);
1366 else if (insertPolicy == QComboBox::InsertAfterCurrent)
1367 index = q->currentIndex() + 1;
1368 else if (insertPolicy == QComboBox::InsertBeforeCurrent)
1369 index = q->currentIndex();
1370 break;
1371 case QComboBox::InsertAlphabetically:
1372 index = 0;
1373 for (int i = 0; i < q->count(); ++i, ++index) {
1374 if (text.toLower() < q->itemText(index: i).toLower())
1375 break;
1376 }
1377 break;
1378 default:
1379 break;
1380 }
1381 if (index >= 0) {
1382 q->insertItem(aindex: index, atext: text);
1383 q->setCurrentIndex(index);
1384 emitActivated(index: currentIndex);
1385 }
1386 }
1387}
1388
1389void QComboBoxPrivate::itemSelected(const QModelIndex &item)
1390{
1391 Q_Q(QComboBox);
1392 if (item != currentIndex) {
1393 setCurrentIndex(item);
1394 } else if (lineEdit) {
1395 lineEdit->selectAll();
1396 lineEdit->setText(q->itemText(index: currentIndex.row()));
1397 }
1398 emitActivated(index: currentIndex);
1399}
1400
1401void QComboBoxPrivate::emitActivated(const QModelIndex &index)
1402{
1403 Q_Q(QComboBox);
1404 if (!index.isValid())
1405 return;
1406 QString text(itemText(index));
1407 emit q->activated(index: index.row());
1408 emit q->textActivated(text);
1409}
1410
1411void QComboBoxPrivate::emitHighlighted(const QModelIndex &index)
1412{
1413 Q_Q(QComboBox);
1414 if (!index.isValid())
1415 return;
1416 QString text(itemText(index));
1417 emit q->highlighted(index: index.row());
1418 emit q->textHighlighted(text);
1419}
1420
1421void QComboBoxPrivate::emitCurrentIndexChanged(const QModelIndex &index)
1422{
1423 Q_Q(QComboBox);
1424 const QString text = itemText(index);
1425 emit q->currentIndexChanged(index: index.row());
1426 // signal lineEdit.textChanged already connected to signal currentTextChanged, so don't emit double here
1427 if (!lineEdit)
1428 updateCurrentText(text);
1429#if QT_CONFIG(accessibility)
1430 QAccessibleValueChangeEvent event(q, text);
1431 QAccessible::updateAccessibility(event: &event);
1432#endif
1433}
1434
1435QString QComboBoxPrivate::itemText(const QModelIndex &index) const
1436{
1437 return index.isValid() ? model->data(index, role: itemRole()).toString() : QString();
1438}
1439
1440int QComboBoxPrivate::itemRole() const
1441{
1442 return q_func()->isEditable() ? Qt::EditRole : Qt::DisplayRole;
1443}
1444
1445/*!
1446 Destroys the combobox.
1447*/
1448QComboBox::~QComboBox()
1449{
1450 // ### check delegateparent and delete delegate if us?
1451 Q_D(QComboBox);
1452
1453 QT_TRY {
1454 d->disconnectModel();
1455 } QT_CATCH(...) {
1456 ; // objects can't throw in destructor
1457 }
1458
1459 // Dispose of container before QComboBox goes away
1460 delete d->container;
1461}
1462
1463/*!
1464 \property QComboBox::maxVisibleItems
1465 \brief the maximum allowed size on screen of the combo box, measured in items
1466
1467 By default, this property has a value of 10.
1468
1469 \note This property is ignored for non-editable comboboxes in styles that returns
1470 true for QStyle::SH_ComboBox_Popup such as the Mac style or the Gtk+ Style.
1471*/
1472int QComboBox::maxVisibleItems() const
1473{
1474 Q_D(const QComboBox);
1475 return d->maxVisibleItems;
1476}
1477
1478void QComboBox::setMaxVisibleItems(int maxItems)
1479{
1480 Q_D(QComboBox);
1481 if (Q_UNLIKELY(maxItems < 0)) {
1482 qWarning(msg: "QComboBox::setMaxVisibleItems: "
1483 "Invalid max visible items (%d) must be >= 0", maxItems);
1484 return;
1485 }
1486 d->maxVisibleItems = maxItems;
1487}
1488
1489/*!
1490 \property QComboBox::count
1491 \brief the number of items in the combobox.
1492
1493 By default, for an empty combo box, this property has a value of 0.
1494*/
1495int QComboBox::count() const
1496{
1497 Q_D(const QComboBox);
1498 return d->model->rowCount(parent: d->root);
1499}
1500
1501/*!
1502 \property QComboBox::maxCount
1503 \brief the maximum number of items allowed in the combobox.
1504
1505 \note If you set the maximum number to be less then the current
1506 amount of items in the combobox, the extra items will be
1507 truncated. This also applies if you have set an external model on
1508 the combobox.
1509
1510 By default, this property's value is derived from the highest
1511 signed integer available (typically 2147483647).
1512*/
1513void QComboBox::setMaxCount(int max)
1514{
1515 Q_D(QComboBox);
1516 if (Q_UNLIKELY(max < 0)) {
1517 qWarning(msg: "QComboBox::setMaxCount: Invalid count (%d) must be >= 0", max);
1518 return;
1519 }
1520
1521 const int rowCount = count();
1522 if (rowCount > max)
1523 d->model->removeRows(row: max, count: rowCount - max, parent: d->root);
1524
1525 d->maxCount = max;
1526}
1527
1528int QComboBox::maxCount() const
1529{
1530 Q_D(const QComboBox);
1531 return d->maxCount;
1532}
1533
1534/*!
1535 \property QComboBox::duplicatesEnabled
1536 \brief whether the user can enter duplicate items into the combobox.
1537
1538 Note that it is always possible to programmatically insert duplicate items into the
1539 combobox.
1540
1541 By default, this property is \c false (duplicates are not allowed).
1542*/
1543bool QComboBox::duplicatesEnabled() const
1544{
1545 Q_D(const QComboBox);
1546 return d->duplicatesEnabled;
1547}
1548
1549void QComboBox::setDuplicatesEnabled(bool enable)
1550{
1551 Q_D(QComboBox);
1552 d->duplicatesEnabled = enable;
1553}
1554
1555/*! \fn int QComboBox::findText(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly|Qt::MatchCaseSensitive) const
1556
1557 Returns the index of the item containing the given \a text; otherwise
1558 returns -1.
1559
1560 The \a flags specify how the items in the combobox are searched.
1561*/
1562
1563/*!
1564 Returns the index of the item containing the given \a data for the
1565 given \a role; otherwise returns -1.
1566
1567 The \a flags specify how the items in the combobox are searched.
1568*/
1569int QComboBox::findData(const QVariant &data, int role, Qt::MatchFlags flags) const
1570{
1571 Q_D(const QComboBox);
1572 QModelIndex start = d->model->index(row: 0, column: d->modelColumn, parent: d->root);
1573 const QModelIndexList result = d->model->match(start, role, value: data, hits: 1, flags);
1574 if (result.isEmpty())
1575 return -1;
1576 return result.first().row();
1577}
1578
1579/*!
1580 \property QComboBox::insertPolicy
1581 \brief the policy used to determine where user-inserted items should
1582 appear in the combobox.
1583
1584 The default value is \l InsertAtBottom, indicating that new items will appear
1585 at the bottom of the list of items.
1586
1587 \sa InsertPolicy
1588*/
1589
1590QComboBox::InsertPolicy QComboBox::insertPolicy() const
1591{
1592 Q_D(const QComboBox);
1593 return d->insertPolicy;
1594}
1595
1596void QComboBox::setInsertPolicy(InsertPolicy policy)
1597{
1598 Q_D(QComboBox);
1599 d->insertPolicy = policy;
1600}
1601
1602/*!
1603 \property QComboBox::sizeAdjustPolicy
1604 \brief the policy describing how the size of the combobox changes
1605 when the content changes.
1606
1607 The default value is \l AdjustToContentsOnFirstShow.
1608
1609 \sa SizeAdjustPolicy
1610*/
1611
1612QComboBox::SizeAdjustPolicy QComboBox::sizeAdjustPolicy() const
1613{
1614 Q_D(const QComboBox);
1615 return d->sizeAdjustPolicy;
1616}
1617
1618void QComboBox::setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy policy)
1619{
1620 Q_D(QComboBox);
1621 if (policy == d->sizeAdjustPolicy)
1622 return;
1623
1624 d->sizeAdjustPolicy = policy;
1625 d->sizeHint = QSize();
1626 d->adjustComboBoxSize();
1627 updateGeometry();
1628}
1629
1630/*!
1631 \property QComboBox::minimumContentsLength
1632 \brief the minimum number of characters that should fit into the combobox.
1633
1634 The default value is 0.
1635
1636 If this property is set to a positive value, the
1637 minimumSizeHint() and sizeHint() take it into account.
1638
1639 \sa sizeAdjustPolicy
1640*/
1641int QComboBox::minimumContentsLength() const
1642{
1643 Q_D(const QComboBox);
1644 return d->minimumContentsLength;
1645}
1646
1647void QComboBox::setMinimumContentsLength(int characters)
1648{
1649 Q_D(QComboBox);
1650 if (characters == d->minimumContentsLength || characters < 0)
1651 return;
1652
1653 d->minimumContentsLength = characters;
1654
1655 if (d->sizeAdjustPolicy == AdjustToContents
1656 || d->sizeAdjustPolicy == AdjustToMinimumContentsLengthWithIcon) {
1657 d->sizeHint = QSize();
1658 d->adjustComboBoxSize();
1659 updateGeometry();
1660 }
1661}
1662
1663/*!
1664 \property QComboBox::iconSize
1665 \brief the size of the icons shown in the combobox.
1666
1667 Unless explicitly set this returns the default value of the
1668 current style. This size is the maximum size that icons can have;
1669 icons of smaller size are not scaled up.
1670*/
1671
1672QSize QComboBox::iconSize() const
1673{
1674 Q_D(const QComboBox);
1675 if (d->iconSize.isValid())
1676 return d->iconSize;
1677
1678 int iconWidth = style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: this);
1679 return QSize(iconWidth, iconWidth);
1680}
1681
1682void QComboBox::setIconSize(const QSize &size)
1683{
1684 Q_D(QComboBox);
1685 if (size == d->iconSize)
1686 return;
1687
1688 view()->setIconSize(size);
1689 d->iconSize = size;
1690 d->sizeHint = QSize();
1691 updateGeometry();
1692}
1693
1694/*!
1695 \property QComboBox::placeholderText
1696 \brief Sets a \a placeholderText text shown when no valid index is set.
1697
1698 The \a placeholderText will be shown when an invalid index is set. The
1699 text is not accessible in the dropdown list. When this function is called
1700 before items are added the placeholder text will be shown, otherwise you
1701 have to call setCurrentIndex(-1) programmatically if you want to show the
1702 placeholder text.
1703 Set an empty placeholder text to reset the setting.
1704
1705 When the QComboBox is editable, use QLineEdit::setPlaceholderText()
1706 instead.
1707
1708 \since 5.15
1709*/
1710void QComboBox::setPlaceholderText(const QString &placeholderText)
1711{
1712 Q_D(QComboBox);
1713 if (placeholderText == d->placeholderText)
1714 return;
1715
1716 d->placeholderText = placeholderText;
1717 if (currentIndex() == -1) {
1718 if (d->placeholderText.isEmpty())
1719 setCurrentIndex(0);
1720 else
1721 update();
1722 } else {
1723 updateGeometry();
1724 }
1725}
1726
1727QString QComboBox::placeholderText() const
1728{
1729 Q_D(const QComboBox);
1730 return d->placeholderText;
1731}
1732
1733/*!
1734 \property QComboBox::editable
1735 \brief whether the combo box can be edited by the user.
1736
1737 By default, this property is \c false. The effect of editing depends
1738 on the insert policy.
1739
1740 \note When disabling the \a editable state, the validator and
1741 completer are removed.
1742
1743 \sa InsertPolicy
1744*/
1745bool QComboBox::isEditable() const
1746{
1747 Q_D(const QComboBox);
1748 return d->lineEdit != nullptr;
1749}
1750
1751/*! \internal
1752 update the default delegate
1753 depending on the style's SH_ComboBox_Popup hint, we use a different default delegate.
1754
1755 but we do not change the delegate is the combobox use a custom delegate,
1756 unless \a force is set to true.
1757 */
1758void QComboBoxPrivate::updateDelegate(bool force)
1759{
1760 Q_Q(QComboBox);
1761 QStyleOptionComboBox opt;
1762 q->initStyleOption(option: &opt);
1763 if (q->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: q)) {
1764 if (force || qobject_cast<QComboBoxDelegate *>(object: q->itemDelegate()))
1765 q->setItemDelegate(new QComboMenuDelegate(q->view(), q));
1766 } else {
1767 if (force || qobject_cast<QComboMenuDelegate *>(object: q->itemDelegate()))
1768 q->setItemDelegate(new QComboBoxDelegate(q->view(), q));
1769 }
1770}
1771
1772QIcon QComboBoxPrivate::itemIcon(const QModelIndex &index) const
1773{
1774 if (!index.isValid())
1775 return {};
1776 QVariant decoration = model->data(index, role: Qt::DecorationRole);
1777 if (decoration.userType() == QMetaType::QPixmap)
1778 return QIcon(qvariant_cast<QPixmap>(v: decoration));
1779 else
1780 return qvariant_cast<QIcon>(v: decoration);
1781}
1782
1783void QComboBox::setEditable(bool editable)
1784{
1785 Q_D(QComboBox);
1786 if (isEditable() == editable)
1787 return;
1788
1789 QStyleOptionComboBox opt;
1790 initStyleOption(option: &opt);
1791 if (editable) {
1792 if (style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: this)) {
1793 d->viewContainer()->updateScrollers();
1794 view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
1795 }
1796 QLineEdit *le = new QLineEdit(this);
1797 le->setPalette(palette());
1798 setLineEdit(le);
1799 } else {
1800 if (style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: this)) {
1801 d->viewContainer()->updateScrollers();
1802 view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1803 }
1804 setAttribute(Qt::WA_InputMethodEnabled, on: false);
1805 d->lineEdit->hide();
1806 d->lineEdit->deleteLater();
1807 d->lineEdit = nullptr;
1808 }
1809
1810 d->updateDelegate();
1811 d->updateFocusPolicy();
1812
1813 d->viewContainer()->updateTopBottomMargin();
1814 if (!testAttribute(attribute: Qt::WA_Resized))
1815 adjustSize();
1816}
1817
1818/*!
1819 Sets the line \a edit to use instead of the current line edit widget.
1820
1821 The combo box takes ownership of the line edit.
1822
1823 \note Since the combobox's line edit owns the QCompleter, any previous
1824 call to setCompleter() will no longer have any effect.
1825*/
1826void QComboBox::setLineEdit(QLineEdit *edit)
1827{
1828 Q_D(QComboBox);
1829 if (Q_UNLIKELY(!edit)) {
1830 qWarning(msg: "QComboBox::setLineEdit: cannot set a 0 line edit");
1831 return;
1832 }
1833
1834 if (edit == d->lineEdit)
1835 return;
1836
1837 edit->setText(currentText());
1838 delete d->lineEdit;
1839
1840 d->lineEdit = edit;
1841#ifndef QT_NO_IM
1842 qt_widget_private(widget: d->lineEdit)->inheritsInputMethodHints = 1;
1843#endif
1844 if (d->lineEdit->parent() != this)
1845 d->lineEdit->setParent(this);
1846 QObjectPrivate::connect(sender: d->lineEdit, signal: &QLineEdit::returnPressed,
1847 receiverPrivate: d, slot: &QComboBoxPrivate::returnPressed);
1848 QObjectPrivate::connect(sender: d->lineEdit, signal: &QLineEdit::editingFinished,
1849 receiverPrivate: d, slot: &QComboBoxPrivate::editingFinished);
1850 connect(sender: d->lineEdit, signal: &QLineEdit::textChanged, context: this, slot: &QComboBox::editTextChanged);
1851 connect(sender: d->lineEdit, signal: &QLineEdit::textChanged, context: this, slot: &QComboBox::currentTextChanged);
1852 QObjectPrivate::connect(sender: d->lineEdit, signal: &QLineEdit::cursorPositionChanged,
1853 receiverPrivate: d, slot: &QComboBoxPrivate::updateMicroFocus);
1854 QObjectPrivate::connect(sender: d->lineEdit, signal: &QLineEdit::selectionChanged,
1855 receiverPrivate: d, slot: &QComboBoxPrivate::updateMicroFocus);
1856 QObjectPrivate::connect(sender: d->lineEdit->d_func()->control, signal: &QWidgetLineControl::updateMicroFocus,
1857 receiverPrivate: d, slot: &QComboBoxPrivate::updateMicroFocus);
1858 d->lineEdit->setFrame(false);
1859 d->lineEdit->setContextMenuPolicy(Qt::NoContextMenu);
1860 d->updateFocusPolicy();
1861 d->lineEdit->setFocusProxy(this);
1862 d->lineEdit->setAttribute(Qt::WA_MacShowFocusRect, on: false);
1863
1864#if QT_CONFIG(completer)
1865 // create a default completer
1866 if (!d->lineEdit->completer()) {
1867 QCompleter *completer = new QCompleter(d->model, d->lineEdit);
1868 completer->setCaseSensitivity(Qt::CaseInsensitive);
1869 completer->setCompletionMode(QCompleter::InlineCompletion);
1870 completer->setCompletionColumn(d->modelColumn);
1871
1872#ifdef QT_KEYPAD_NAVIGATION
1873 // Editable combo boxes will have a completer that is set to UnfilteredPopupCompletion.
1874 // This means that when the user enters edit mode they are immediately presented with a
1875 // list of possible completions.
1876 if (QApplicationPrivate::keypadNavigationEnabled())
1877 completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
1878#endif
1879 // sets up connections
1880 setCompleter(completer);
1881 }
1882#endif
1883
1884 setAttribute(Qt::WA_InputMethodEnabled);
1885 d->updateLayoutDirection();
1886 d->updateLineEditGeometry();
1887 if (isVisible())
1888 d->lineEdit->show();
1889
1890 update();
1891}
1892
1893/*!
1894 Returns the line edit used to edit items in the combobox, or
1895 \nullptr if there is no line edit.
1896
1897 Only editable combo boxes have a line edit.
1898*/
1899QLineEdit *QComboBox::lineEdit() const
1900{
1901 Q_D(const QComboBox);
1902 return d->lineEdit;
1903}
1904
1905#ifndef QT_NO_VALIDATOR
1906/*!
1907 \fn void QComboBox::setValidator(const QValidator *validator)
1908
1909 Sets the \a validator to use instead of the current validator.
1910
1911 \note The validator is removed when the \l editable property becomes \c false.
1912*/
1913
1914void QComboBox::setValidator(const QValidator *v)
1915{
1916 Q_D(QComboBox);
1917 if (d->lineEdit)
1918 d->lineEdit->setValidator(v);
1919}
1920
1921/*!
1922 Returns the validator that is used to constrain text input for the
1923 combobox.
1924
1925 \sa editable
1926*/
1927const QValidator *QComboBox::validator() const
1928{
1929 Q_D(const QComboBox);
1930 return d->lineEdit ? d->lineEdit->validator() : nullptr;
1931}
1932#endif // QT_NO_VALIDATOR
1933
1934#if QT_CONFIG(completer)
1935
1936/*!
1937 \fn void QComboBox::setCompleter(QCompleter *completer)
1938 \since 4.2
1939
1940 Sets the \a completer to use instead of the current completer.
1941 If \a completer is \nullptr, auto completion is disabled.
1942
1943 By default, for an editable combo box, a QCompleter that
1944 performs case insensitive inline completion is automatically created.
1945
1946 \note The completer is removed when the \l editable property becomes \c false,
1947 or when the line edit is replaced by a call to setLineEdit().
1948 Setting a completer on a QComboBox that is not editable will be ignored.
1949*/
1950void QComboBox::setCompleter(QCompleter *c)
1951{
1952 Q_D(QComboBox);
1953 if (!d->lineEdit) {
1954 qWarning(msg: "Setting a QCompleter on non-editable QComboBox is not allowed.");
1955 return;
1956 }
1957 d->lineEdit->setCompleter(c);
1958 if (c) {
1959 QObjectPrivate::connect(sender: c, signal: QOverload<const QModelIndex &>::of(ptr: &QCompleter::activated),
1960 receiverPrivate: d, slot: &QComboBoxPrivate::completerActivated);
1961 c->setWidget(this);
1962 }
1963}
1964
1965/*!
1966 \since 4.2
1967
1968 Returns the completer that is used to auto complete text input for the
1969 combobox.
1970
1971 \sa editable
1972*/
1973QCompleter *QComboBox::completer() const
1974{
1975 Q_D(const QComboBox);
1976 return d->lineEdit ? d->lineEdit->completer() : nullptr;
1977}
1978
1979#endif // QT_CONFIG(completer)
1980
1981/*!
1982 Returns the item delegate used by the popup list view.
1983
1984 \sa setItemDelegate()
1985*/
1986QAbstractItemDelegate *QComboBox::itemDelegate() const
1987{
1988 return view()->itemDelegate();
1989}
1990
1991/*!
1992 Sets the item \a delegate for the popup list view.
1993 The combobox takes ownership of the delegate.
1994
1995 Any existing delegate will be removed, but not deleted. QComboBox
1996 does not take ownership of \a delegate.
1997
1998 \warning You should not share the same instance of a delegate between comboboxes,
1999 widget mappers or views. Doing so can cause incorrect or unintuitive editing behavior
2000 since each view connected to a given delegate may receive the
2001 \l{QAbstractItemDelegate::}{closeEditor()} signal, and attempt to access, modify or
2002 close an editor that has already been closed.
2003
2004 \sa itemDelegate()
2005*/
2006void QComboBox::setItemDelegate(QAbstractItemDelegate *delegate)
2007{
2008 if (Q_UNLIKELY(!delegate)) {
2009 qWarning(msg: "QComboBox::setItemDelegate: cannot set a 0 delegate");
2010 return;
2011 }
2012 view()->setItemDelegate(delegate);
2013}
2014
2015/*!
2016 Returns the model used by the combobox.
2017*/
2018
2019QAbstractItemModel *QComboBox::model() const
2020{
2021 Q_D(const QComboBox);
2022 if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) {
2023 QComboBox *that = const_cast<QComboBox*>(this);
2024 that->setModel(new QStandardItemModel(0, 1, that));
2025 }
2026 return d->model;
2027}
2028
2029/*!
2030 Sets the model to be \a model. \a model must not be \nullptr.
2031 If you want to clear the contents of a model, call clear().
2032
2033 \note If the combobox is editable, then the \a model will also be
2034 set on the completer of the line edit.
2035
2036 \sa clear() setCompleter()
2037*/
2038void QComboBox::setModel(QAbstractItemModel *model)
2039{
2040 Q_D(QComboBox);
2041
2042 if (Q_UNLIKELY(!model)) {
2043 qWarning(msg: "QComboBox::setModel: cannot set a 0 model");
2044 return;
2045 }
2046
2047 if (model == d->model)
2048 return;
2049
2050#if QT_CONFIG(completer)
2051 if (d->lineEdit && d->lineEdit->completer())
2052 d->lineEdit->completer()->setModel(model);
2053#endif
2054 d->disconnectModel();
2055 if (d->model && d->model->QObject::parent() == this) {
2056 delete d->model;
2057 d->model = nullptr;
2058 }
2059
2060 d->model = model;
2061
2062 if (d->container) {
2063 d->container->itemView()->setModel(model);
2064 QObjectPrivate::connect(sender: d->container->itemView()->selectionModel(),
2065 signal: &QItemSelectionModel::currentChanged,
2066 receiverPrivate: d, slot: &QComboBoxPrivate::emitHighlighted, type: Qt::UniqueConnection);
2067 }
2068
2069 d->connectModel();
2070
2071 setRootModelIndex(QModelIndex());
2072
2073 d->trySetValidIndex();
2074 d->modelChanged();
2075}
2076
2077void QComboBoxPrivate::connectModel()
2078{
2079 if (!model)
2080 return;
2081
2082 modelConnections = {
2083 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::dataChanged,
2084 receiverPrivate: this, slot: &QComboBoxPrivate::dataChanged),
2085 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted,
2086 receiverPrivate: this, slot: &QComboBoxPrivate::updateIndexBeforeChange),
2087 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::rowsInserted,
2088 receiverPrivate: this, slot: &QComboBoxPrivate::rowsInserted),
2089 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved,
2090 receiverPrivate: this, slot: &QComboBoxPrivate::updateIndexBeforeChange),
2091 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::rowsRemoved,
2092 receiverPrivate: this, slot: &QComboBoxPrivate::rowsRemoved),
2093 QObjectPrivate::connect(sender: model, signal: &QObject::destroyed,
2094 receiverPrivate: this, slot: &QComboBoxPrivate::modelDestroyed),
2095 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::modelAboutToBeReset,
2096 receiverPrivate: this, slot: &QComboBoxPrivate::updateIndexBeforeChange),
2097 QObjectPrivate::connect(sender: model, signal: &QAbstractItemModel::modelReset,
2098 receiverPrivate: this, slot: &QComboBoxPrivate::modelReset)
2099 };
2100}
2101
2102void QComboBoxPrivate::disconnectModel()
2103{
2104 for (auto &connection : modelConnections)
2105 QObject::disconnect(connection);
2106}
2107
2108/*!
2109 Returns the root model item index for the items in the combobox.
2110
2111 \sa setRootModelIndex()
2112*/
2113
2114QModelIndex QComboBox::rootModelIndex() const
2115{
2116 Q_D(const QComboBox);
2117 return QModelIndex(d->root);
2118}
2119
2120/*!
2121 Sets the root model item \a index for the items in the combobox.
2122
2123 \sa rootModelIndex()
2124*/
2125void QComboBox::setRootModelIndex(const QModelIndex &index)
2126{
2127 Q_D(QComboBox);
2128 if (d->root == index)
2129 return;
2130 d->root = QPersistentModelIndex(index);
2131 view()->setRootIndex(index);
2132 update();
2133}
2134
2135/*!
2136 \property QComboBox::currentIndex
2137 \brief the index of the current item in the combobox.
2138
2139 The current index can change when inserting or removing items.
2140
2141 By default, for an empty combo box or a combo box in which no current
2142 item is set, this property has a value of -1.
2143*/
2144int QComboBox::currentIndex() const
2145{
2146 Q_D(const QComboBox);
2147 return d->currentIndex.row();
2148}
2149
2150void QComboBox::setCurrentIndex(int index)
2151{
2152 Q_D(QComboBox);
2153 QModelIndex mi = index >= 0 ? d->model->index(row: index, column: d->modelColumn, parent: d->root) : QModelIndex();
2154 d->setCurrentIndex(mi);
2155}
2156
2157void QComboBox::setCurrentText(const QString &text)
2158{
2159 if (isEditable()) {
2160 setEditText(text);
2161 } else {
2162 const int i = findText(text);
2163 if (i > -1)
2164 setCurrentIndex(i);
2165 }
2166}
2167
2168void QComboBoxPrivate::setCurrentIndex(const QModelIndex &mi)
2169{
2170 Q_Q(QComboBox);
2171
2172 QModelIndex normalized = mi.sibling(arow: mi.row(), acolumn: modelColumn); // no-op if mi.column() == modelColumn
2173 if (!normalized.isValid())
2174 normalized = mi; // Fallback to passed index.
2175
2176 bool indexChanged = (normalized != currentIndex);
2177 if (indexChanged)
2178 currentIndex = QPersistentModelIndex(normalized);
2179 if (lineEdit) {
2180 const QString newText = itemText(index: normalized);
2181 if (lineEdit->text() != newText) {
2182 lineEdit->setText(newText); // may cause lineEdit -> nullptr (QTBUG-54191)
2183#if QT_CONFIG(completer)
2184 if (lineEdit && lineEdit->completer())
2185 lineEdit->completer()->setCompletionPrefix(newText);
2186#endif
2187 }
2188 updateLineEditGeometry();
2189 }
2190 // If the model was reset to an empty one, currentIndex will be invalidated
2191 // (because it's a QPersistentModelIndex), but the index change will never
2192 // be advertised. So an explicit check for this condition is needed.
2193 // The variable used for that check has to be reset when a previously valid
2194 // index becomes invalid.
2195 const bool modelResetToEmpty = !normalized.isValid() && indexBeforeChange != -1;
2196 if (modelResetToEmpty)
2197 indexBeforeChange = -1;
2198
2199 if (indexChanged || modelResetToEmpty) {
2200 QItemSelectionModel::SelectionFlags selectionMode = QItemSelectionModel::ClearAndSelect;
2201 if (q->view()->selectionBehavior() == QAbstractItemView::SelectRows)
2202 selectionMode.setFlag(flag: QItemSelectionModel::Rows);
2203 if (auto *model = q->view()->selectionModel())
2204 model->setCurrentIndex(index: currentIndex, command: selectionMode);
2205
2206 q->update();
2207 emitCurrentIndexChanged(index: currentIndex);
2208 }
2209}
2210
2211/*!
2212 \property QComboBox::currentText
2213 \brief the current text
2214
2215 If the combo box is editable, the current text is the value displayed
2216 by the line edit. Otherwise, it is the value of the current item or
2217 an empty string if the combo box is empty or no current item is set.
2218
2219 The setter setCurrentText() simply calls setEditText() if the combo box is editable.
2220 Otherwise, if there is a matching text in the list, currentIndex is set to the
2221 corresponding index.
2222
2223 \sa editable, setEditText()
2224*/
2225QString QComboBox::currentText() const
2226{
2227 Q_D(const QComboBox);
2228 if (d->lineEdit)
2229 return d->lineEdit->text();
2230 if (d->currentIndex.isValid())
2231 return d->itemText(index: d->currentIndex);
2232 return {};
2233}
2234
2235/*!
2236 \property QComboBox::currentData
2237 \brief the data for the current item
2238 \since 5.2
2239
2240 By default, for an empty combo box or a combo box in which no current
2241 item is set, this property contains an invalid QVariant.
2242*/
2243QVariant QComboBox::currentData(int role) const
2244{
2245 Q_D(const QComboBox);
2246 return d->currentIndex.data(role);
2247}
2248
2249/*!
2250 Returns the text for the given \a index in the combobox.
2251*/
2252QString QComboBox::itemText(int index) const
2253{
2254 Q_D(const QComboBox);
2255 QModelIndex mi = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2256 return d->itemText(index: mi);
2257}
2258
2259/*!
2260 Returns the icon for the given \a index in the combobox.
2261*/
2262QIcon QComboBox::itemIcon(int index) const
2263{
2264 Q_D(const QComboBox);
2265 QModelIndex mi = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2266 return d->itemIcon(index: mi);
2267}
2268
2269/*!
2270 Returns the data for the given \a role in the given \a index in the
2271 combobox, or an invalid QVariant if there is no data for this role.
2272*/
2273QVariant QComboBox::itemData(int index, int role) const
2274{
2275 Q_D(const QComboBox);
2276 QModelIndex mi = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2277 return d->model->data(index: mi, role);
2278}
2279
2280/*!
2281 \fn void QComboBox::insertItem(int index, const QString &text, const QVariant &userData)
2282
2283 Inserts the \a text and \a userData (stored in the Qt::UserRole)
2284 into the combobox at the given \a index.
2285
2286 If the index is equal to or higher than the total number of items,
2287 the new item is appended to the list of existing items. If the
2288 index is zero or negative, the new item is prepended to the list
2289 of existing items.
2290
2291 \sa insertItems()
2292*/
2293
2294/*!
2295
2296 Inserts the \a icon, \a text and \a userData (stored in the
2297 Qt::UserRole) into the combobox at the given \a index.
2298
2299 If the index is equal to or higher than the total number of items,
2300 the new item is appended to the list of existing items. If the
2301 index is zero or negative, the new item is prepended to the list
2302 of existing items.
2303
2304 \sa insertItems()
2305*/
2306void QComboBox::insertItem(int index, const QIcon &icon, const QString &text, const QVariant &userData)
2307{
2308 Q_D(QComboBox);
2309 int itemCount = count();
2310 index = qBound(min: 0, val: index, max: itemCount);
2311 if (index >= d->maxCount)
2312 return;
2313
2314 // For the common case where we are using the built in QStandardItemModel
2315 // construct a QStandardItem, reducing the number of expensive signals from the model
2316 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(object: d->model)) {
2317 QStandardItem *item = new QStandardItem(text);
2318 if (!icon.isNull()) item->setData(value: icon, role: Qt::DecorationRole);
2319 if (userData.isValid()) item->setData(value: userData, role: Qt::UserRole);
2320 m->insertRow(arow: index, aitem: item);
2321 ++itemCount;
2322 } else {
2323 d->inserting = true;
2324 if (d->model->insertRows(row: index, count: 1, parent: d->root)) {
2325 QModelIndex item = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2326 if (icon.isNull() && !userData.isValid()) {
2327 d->model->setData(index: item, value: text, role: Qt::EditRole);
2328 } else {
2329 QMap<int, QVariant> values;
2330 if (!text.isNull()) values.insert(key: Qt::EditRole, value: text);
2331 if (!icon.isNull()) values.insert(key: Qt::DecorationRole, value: icon);
2332 if (userData.isValid()) values.insert(key: Qt::UserRole, value: userData);
2333 if (!values.isEmpty()) d->model->setItemData(index: item, roles: values);
2334 }
2335 d->inserting = false;
2336 d->rowsInserted(parent: d->root, start: index, end: index);
2337 ++itemCount;
2338 } else {
2339 d->inserting = false;
2340 }
2341 }
2342
2343 if (itemCount > d->maxCount)
2344 d->model->removeRows(row: itemCount - 1, count: itemCount - d->maxCount, parent: d->root);
2345}
2346
2347/*!
2348 Inserts the strings from the \a list into the combobox as separate items,
2349 starting at the \a index specified.
2350
2351 If the index is equal to or higher than the total number of items, the new items
2352 are appended to the list of existing items. If the index is zero or negative, the
2353 new items are prepended to the list of existing items.
2354
2355 \sa insertItem()
2356 */
2357void QComboBox::insertItems(int index, const QStringList &list)
2358{
2359 Q_D(QComboBox);
2360 if (list.isEmpty())
2361 return;
2362 index = qBound(min: 0, val: index, max: count());
2363 int insertCount = qMin(a: d->maxCount - index, b: list.size());
2364 if (insertCount <= 0)
2365 return;
2366 // For the common case where we are using the built in QStandardItemModel
2367 // construct a QStandardItem, reducing the number of expensive signals from the model
2368 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(object: d->model)) {
2369 QList<QStandardItem *> items;
2370 items.reserve(asize: insertCount);
2371 QStandardItem *hiddenRoot = m->invisibleRootItem();
2372 for (int i = 0; i < insertCount; ++i)
2373 items.append(t: new QStandardItem(list.at(i)));
2374 hiddenRoot->insertRows(row: index, items);
2375 } else {
2376 d->inserting = true;
2377 if (d->model->insertRows(row: index, count: insertCount, parent: d->root)) {
2378 QModelIndex item;
2379 for (int i = 0; i < insertCount; ++i) {
2380 item = d->model->index(row: i+index, column: d->modelColumn, parent: d->root);
2381 d->model->setData(index: item, value: list.at(i), role: Qt::EditRole);
2382 }
2383 d->inserting = false;
2384 d->rowsInserted(parent: d->root, start: index, end: index + insertCount - 1);
2385 } else {
2386 d->inserting = false;
2387 }
2388 }
2389
2390 int mc = count();
2391 if (mc > d->maxCount)
2392 d->model->removeRows(row: d->maxCount, count: mc - d->maxCount, parent: d->root);
2393}
2394
2395/*!
2396 \since 4.4
2397
2398 Inserts a separator item into the combobox at the given \a index.
2399
2400 If the index is equal to or higher than the total number of items, the new item
2401 is appended to the list of existing items. If the index is zero or negative, the
2402 new item is prepended to the list of existing items.
2403
2404 \sa insertItem()
2405*/
2406void QComboBox::insertSeparator(int index)
2407{
2408 Q_D(QComboBox);
2409 int itemCount = count();
2410 index = qBound(min: 0, val: index, max: itemCount);
2411 if (index >= d->maxCount)
2412 return;
2413 insertItem(index, icon: QIcon(), text: QString());
2414 QComboBoxDelegate::setSeparator(model: d->model, index: d->model->index(row: index, column: 0, parent: d->root));
2415}
2416
2417/*!
2418 Removes the item at the given \a index from the combobox.
2419 This will update the current index if the index is removed.
2420
2421 This function does nothing if \a index is out of range.
2422*/
2423void QComboBox::removeItem(int index)
2424{
2425 Q_D(QComboBox);
2426 if (index < 0 || index >= count())
2427 return;
2428 d->model->removeRows(row: index, count: 1, parent: d->root);
2429}
2430
2431/*!
2432 Sets the \a text for the item on the given \a index in the combobox.
2433*/
2434void QComboBox::setItemText(int index, const QString &text)
2435{
2436 Q_D(const QComboBox);
2437 QModelIndex item = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2438 if (item.isValid()) {
2439 d->model->setData(index: item, value: text, role: Qt::EditRole);
2440 }
2441}
2442
2443/*!
2444 Sets the \a icon for the item on the given \a index in the combobox.
2445*/
2446void QComboBox::setItemIcon(int index, const QIcon &icon)
2447{
2448 Q_D(const QComboBox);
2449 QModelIndex item = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2450 if (item.isValid()) {
2451 d->model->setData(index: item, value: icon, role: Qt::DecorationRole);
2452 }
2453}
2454
2455/*!
2456 Sets the data \a role for the item on the given \a index in the combobox
2457 to the specified \a value.
2458*/
2459void QComboBox::setItemData(int index, const QVariant &value, int role)
2460{
2461 Q_D(const QComboBox);
2462 QModelIndex item = d->model->index(row: index, column: d->modelColumn, parent: d->root);
2463 if (item.isValid()) {
2464 d->model->setData(index: item, value, role);
2465 }
2466}
2467
2468/*!
2469 Returns the list view used for the combobox popup.
2470*/
2471QAbstractItemView *QComboBox::view() const
2472{
2473 Q_D(const QComboBox);
2474 return const_cast<QComboBoxPrivate*>(d)->viewContainer()->itemView();
2475}
2476
2477/*!
2478 Sets the view to be used in the combobox popup to the given \a
2479 itemView. The combobox takes ownership of the view.
2480
2481 Note: If you want to use the convenience views (like QListWidget,
2482 QTableWidget or QTreeWidget), make sure to call setModel() on the
2483 combobox with the convenience widgets model before calling this
2484 function.
2485*/
2486void QComboBox::setView(QAbstractItemView *itemView)
2487{
2488 Q_D(QComboBox);
2489 if (Q_UNLIKELY(!itemView)) {
2490 qWarning(msg: "QComboBox::setView: cannot set a 0 view");
2491 return;
2492 }
2493
2494 if (itemView->model() != d->model) {
2495 d->disconnectModel();
2496 itemView->setModel(d->model);
2497 d->connectModel();
2498 }
2499 d->viewContainer()->setItemView(itemView);
2500}
2501
2502/*!
2503 \reimp
2504*/
2505QSize QComboBox::minimumSizeHint() const
2506{
2507 Q_D(const QComboBox);
2508 return d->recomputeSizeHint(sh&: d->minimumSizeHint);
2509}
2510
2511/*!
2512 \reimp
2513
2514 This implementation caches the size hint to avoid resizing when
2515 the contents change dynamically. To invalidate the cached value
2516 change the \l sizeAdjustPolicy.
2517*/
2518QSize QComboBox::sizeHint() const
2519{
2520 Q_D(const QComboBox);
2521 return d->recomputeSizeHint(sh&: d->sizeHint);
2522}
2523
2524#ifdef Q_OS_MAC
2525void QComboBoxPrivate::cleanupNativePopup()
2526{
2527 if (!m_platformMenu)
2528 return;
2529
2530 m_platformMenu->setVisible(false);
2531 int count = int(m_platformMenu->tag());
2532 for (int i = 0; i < count; ++i)
2533 m_platformMenu->menuItemAt(i)->deleteLater();
2534
2535 delete m_platformMenu;
2536 m_platformMenu = nullptr;
2537}
2538
2539/*!
2540 * \internal
2541 *
2542 * Tries to show a native popup. Returns true if it could, false otherwise.
2543 *
2544 */
2545bool QComboBoxPrivate::showNativePopup()
2546{
2547 Q_Q(QComboBox);
2548
2549 cleanupNativePopup();
2550
2551 QPlatformTheme *theme = QGuiApplicationPrivate::instance()->platformTheme();
2552 m_platformMenu = theme->createPlatformMenu();
2553 if (!m_platformMenu)
2554 return false;
2555
2556 int itemsCount = q->count();
2557 m_platformMenu->setTag(quintptr(itemsCount));
2558
2559 QPlatformMenuItem *currentItem = nullptr;
2560 int currentIndex = q->currentIndex();
2561
2562 for (int i = 0; i < itemsCount; ++i) {
2563 QPlatformMenuItem *item = theme->createPlatformMenuItem();
2564 QModelIndex rowIndex = model->index(i, modelColumn, root);
2565 QVariant textVariant = model->data(rowIndex, Qt::EditRole);
2566 item->setText(textVariant.toString());
2567 QVariant iconVariant = model->data(rowIndex, Qt::DecorationRole);
2568 const Qt::ItemFlags itemFlags = model->flags(rowIndex);
2569 if (iconVariant.canConvert<QIcon>())
2570 item->setIcon(iconVariant.value<QIcon>());
2571 item->setCheckable(true);
2572 item->setChecked(i == currentIndex);
2573 item->setEnabled(itemFlags & Qt::ItemIsEnabled);
2574 if (!currentItem || i == currentIndex)
2575 currentItem = item;
2576
2577 IndexSetter setter = { i, q };
2578 QObject::connect(item, &QPlatformMenuItem::activated, q, setter);
2579
2580 m_platformMenu->insertMenuItem(item, 0);
2581 m_platformMenu->syncMenuItem(item);
2582 }
2583
2584 QWindow *tlw = q->window()->windowHandle();
2585 m_platformMenu->setFont(q->font());
2586 m_platformMenu->setMinimumWidth(q->rect().width());
2587 QPoint offset = QPoint(0, 7);
2588 if (q->testAttribute(Qt::WA_MacSmallSize))
2589 offset = QPoint(-1, 7);
2590 else if (q->testAttribute(Qt::WA_MacMiniSize))
2591 offset = QPoint(-2, 6);
2592
2593 [[maybe_unused]] QPointer<QComboBox> guard(q);
2594 const QRect targetRect = QRect(tlw->mapFromGlobal(q->mapToGlobal(offset)), QSize());
2595 m_platformMenu->showPopup(tlw, QHighDpi::toNativePixels(targetRect, tlw), currentItem);
2596
2597#ifdef Q_OS_MACOS
2598 if (guard) {
2599 // The Cocoa popup will swallow any mouse release event.
2600 // We need to fake one here to un-press the button.
2601 QMouseEvent mouseReleased(QEvent::MouseButtonRelease, q->pos(), q->mapToGlobal(QPoint(0, 0)),
2602 Qt::LeftButton, Qt::MouseButtons(Qt::LeftButton), {});
2603 QCoreApplication::sendEvent(q, &mouseReleased);
2604 }
2605#endif
2606
2607 return true;
2608}
2609
2610#endif // Q_OS_MAC
2611
2612/*!
2613 Displays the list of items in the combobox. If the list is empty
2614 then no items will be shown.
2615
2616 If you reimplement this function to show a custom pop-up, make
2617 sure you call hidePopup() to reset the internal state.
2618
2619 \sa hidePopup()
2620*/
2621void QComboBox::showPopup()
2622{
2623 Q_D(QComboBox);
2624 if (count() <= 0)
2625 return;
2626
2627 QStyle * const style = this->style();
2628 QStyleOptionComboBox opt;
2629 initStyleOption(option: &opt);
2630 const bool usePopup = style->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: this);
2631
2632#ifdef Q_OS_MAC
2633 if (usePopup
2634 && (!d->container
2635 || (view()->metaObject()->className() == QByteArray("QComboBoxListView")
2636 && view()->itemDelegate()->metaObject()->className() == QByteArray("QComboMenuDelegate")))
2637 && style->styleHint(QStyle::SH_ComboBox_UseNativePopup, &opt, this)
2638 && d->showNativePopup())
2639 return;
2640#endif // Q_OS_MAC
2641
2642 QComboBoxPrivateContainer* container = d->viewContainer();
2643 QRect listRect(style->subControlRect(cc: QStyle::CC_ComboBox, opt: &opt,
2644 sc: QStyle::SC_ComboBoxListBoxPopup, widget: this));
2645 QRect screen = d->popupGeometry(globalPosition: mapToGlobal(listRect.topLeft()));
2646
2647 QPoint below = mapToGlobal(listRect.bottomLeft());
2648 int belowHeight = screen.bottom() - below.y();
2649 QPoint above = mapToGlobal(listRect.topLeft());
2650 int aboveHeight = above.y() - screen.y();
2651 bool boundToScreen = !window()->testAttribute(attribute: Qt::WA_DontShowOnScreen);
2652 const auto listView = qobject_cast<QListView *>(object: d->viewContainer()->itemView());
2653
2654 {
2655 int listHeight = 0;
2656 int count = 0;
2657 QStack<QModelIndex> toCheck;
2658 toCheck.push(t: view()->rootIndex());
2659#if QT_CONFIG(treeview)
2660 QTreeView *treeView = qobject_cast<QTreeView*>(object: view());
2661 if (treeView && treeView->header() && !treeView->header()->isHidden())
2662 listHeight += treeView->header()->height();
2663#endif
2664 while (!toCheck.isEmpty()) {
2665 QModelIndex parent = toCheck.pop();
2666 for (int i = 0, end = d->model->rowCount(parent); i < end; ++i) {
2667 if (listView && listView->isRowHidden(row: i))
2668 continue;
2669 QModelIndex idx = d->model->index(row: i, column: d->modelColumn, parent);
2670 if (!idx.isValid())
2671 continue;
2672 listHeight += view()->visualRect(index: idx).height();
2673#if QT_CONFIG(treeview)
2674 if (d->model->hasChildren(parent: idx) && treeView && treeView->isExpanded(index: idx))
2675 toCheck.push(t: idx);
2676#endif
2677 ++count;
2678 if (!usePopup && count >= d->maxVisibleItems) {
2679 toCheck.clear();
2680 break;
2681 }
2682 }
2683 }
2684 if (count > 1)
2685 listHeight += (count - 1) * container->spacing();
2686 listRect.setHeight(listHeight);
2687 }
2688
2689 {
2690 // add the spacing for the grid on the top and the bottom;
2691 int heightMargin = container->topMargin() + container->bottomMargin();
2692
2693 // add the frame of the container
2694 const QMargins cm = container->contentsMargins();
2695 heightMargin += cm.top() + cm.bottom();
2696
2697 //add the frame of the view
2698 const QMargins vm = view()->contentsMargins();
2699 heightMargin += vm.top() + vm.bottom();
2700 heightMargin += static_cast<QAbstractScrollAreaPrivate *>(QObjectPrivate::get(o: view()))->top;
2701 heightMargin += static_cast<QAbstractScrollAreaPrivate *>(QObjectPrivate::get(o: view()))->bottom;
2702
2703 listRect.setHeight(listRect.height() + heightMargin);
2704 }
2705
2706 // Add space for margin at top and bottom if the style wants it.
2707 if (usePopup)
2708 listRect.setHeight(listRect.height() + style->pixelMetric(metric: QStyle::PM_MenuVMargin, option: &opt, widget: this) * 2);
2709
2710 // Make sure the popup is wide enough to display its contents.
2711 if (usePopup) {
2712 const int diff = d->computeWidthHint() - width();
2713 if (diff > 0)
2714 listRect.setWidth(listRect.width() + diff);
2715 }
2716
2717 //we need to activate the layout to make sure the min/maximum size are set when the widget was not yet show
2718 container->layout()->activate();
2719 //takes account of the minimum/maximum size of the container
2720 listRect.setSize( listRect.size().expandedTo(otherSize: container->minimumSize())
2721 .boundedTo(otherSize: container->maximumSize()));
2722
2723 // make sure the widget fits on screen
2724 if (boundToScreen) {
2725 if (listRect.width() > screen.width() )
2726 listRect.setWidth(screen.width());
2727 if (mapToGlobal(listRect.bottomRight()).x() > screen.right()) {
2728 below.setX(screen.x() + screen.width() - listRect.width());
2729 above.setX(screen.x() + screen.width() - listRect.width());
2730 }
2731 if (mapToGlobal(listRect.topLeft()).x() < screen.x() ) {
2732 below.setX(screen.x());
2733 above.setX(screen.x());
2734 }
2735 }
2736
2737 if (usePopup) {
2738 // Position horizontally.
2739 listRect.moveLeft(pos: above.x());
2740
2741 // Position vertically so the currently selected item lines up
2742 // with the combo box. In order to do that, make sure that the item
2743 // view is scrolled to the top first, otherwise calls to view()->visualRect()
2744 // will return the geometry the selected item had the last time the popup
2745 // was visible (and perhaps scrolled). And this will not match the geometry
2746 // it will actually have when we resize the container to fit all the items
2747 // further down in this function.
2748 view()->scrollToTop();
2749 const QRect currentItemRect = view()->visualRect(index: view()->currentIndex());
2750 const int offset = listRect.top() - currentItemRect.top();
2751 listRect.moveTop(pos: above.y() + offset - listRect.top());
2752
2753 // Clamp the listRect height and vertical position so we don't expand outside the
2754 // available screen geometry.This may override the vertical position, but it is more
2755 // important to show as much as possible of the popup.
2756 const int height = !boundToScreen ? listRect.height() : qMin(a: listRect.height(), b: screen.height());
2757 listRect.setHeight(height);
2758
2759 if (boundToScreen) {
2760 if (listRect.top() < screen.top())
2761 listRect.moveTop(pos: screen.top());
2762 if (listRect.bottom() > screen.bottom())
2763 listRect.moveBottom(pos: screen.bottom());
2764 }
2765 } else if (!boundToScreen || listRect.height() <= belowHeight) {
2766 listRect.moveTopLeft(p: below);
2767 } else if (listRect.height() <= aboveHeight) {
2768 listRect.moveBottomLeft(p: above);
2769 } else if (belowHeight >= aboveHeight) {
2770 listRect.setHeight(belowHeight);
2771 listRect.moveTopLeft(p: below);
2772 } else {
2773 listRect.setHeight(aboveHeight);
2774 listRect.moveBottomLeft(p: above);
2775 }
2776
2777 if (qApp) {
2778 QGuiApplication::inputMethod()->reset();
2779 }
2780
2781 const QScrollBar *sb = view()->horizontalScrollBar();
2782 const auto needHorizontalScrollBar = [this, sb]{
2783 const Qt::ScrollBarPolicy policy = view()->horizontalScrollBarPolicy();
2784 return (policy == Qt::ScrollBarAsNeeded || policy == Qt::ScrollBarAlwaysOn)
2785 && sb->minimum() < sb->maximum();
2786 };
2787 const bool neededHorizontalScrollBar = needHorizontalScrollBar();
2788 if (neededHorizontalScrollBar)
2789 listRect.adjust(dx1: 0, dy1: 0, dx2: 0, dy2: sb->height());
2790
2791 // Hide the scrollers here, so that the listrect gets the full height of the container
2792 // If the scrollers are truly needed, the later call to container->updateScrollers()
2793 // will make them visible again.
2794 container->hideScrollers();
2795 container->setGeometry(listRect);
2796
2797#ifndef Q_OS_MAC
2798 const bool updatesEnabled = container->updatesEnabled();
2799#endif
2800
2801#if QT_CONFIG(effects)
2802 bool scrollDown = (listRect.topLeft() == below);
2803 if (QApplication::isEffectEnabled(Qt::UI_AnimateCombo)
2804 && !style->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: this) && !window()->testAttribute(attribute: Qt::WA_DontShowOnScreen))
2805 qScrollEffect(container, dir: scrollDown ? QEffects::DownScroll : QEffects::UpScroll, time: 150);
2806#endif
2807
2808// Don't disable updates on OS X. Windows are displayed immediately on this platform,
2809// which means that the window will be visible before the call to container->show() returns.
2810// If updates are disabled at this point we'll miss our chance at painting the popup
2811// menu before it's shown, causing flicker since the window then displays the standard gray
2812// background.
2813#ifndef Q_OS_MAC
2814 container->setUpdatesEnabled(false);
2815#endif
2816
2817 bool startTimer = !container->isVisible();
2818 container->raise();
2819 container->create();
2820 if (QWindow *containerWindow = qt_widget_private(widget: container)->windowHandle(mode: QWidgetPrivate::WindowHandleMode::TopLevel)) {
2821 QScreen *currentScreen = d->associatedScreen();
2822 if (currentScreen && !currentScreen->virtualSiblings().contains(t: containerWindow->screen())) {
2823 containerWindow->setScreen(currentScreen);
2824
2825 // This seems to workaround an issue in xcb+multi GPU+multiscreen
2826 // environment where the window might not always show up when screen
2827 // is changed.
2828 container->hide();
2829 }
2830 }
2831 container->show();
2832 if (!neededHorizontalScrollBar && needHorizontalScrollBar()) {
2833 listRect.adjust(dx1: 0, dy1: 0, dx2: 0, dy2: sb->height());
2834 container->setGeometry(listRect);
2835 }
2836
2837 container->updateScrollers();
2838 view()->setFocus();
2839
2840 view()->scrollTo(index: view()->currentIndex(),
2841 hint: style->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: this)
2842 ? QAbstractItemView::PositionAtCenter
2843 : QAbstractItemView::EnsureVisible);
2844
2845#ifndef Q_OS_MAC
2846 container->setUpdatesEnabled(updatesEnabled);
2847#endif
2848
2849 container->update();
2850 if (startTimer) {
2851 container->popupTimer.start();
2852 container->maybeIgnoreMouseButtonRelease = true;
2853 }
2854}
2855
2856/*!
2857 Hides the list of items in the combobox if it is currently visible
2858 and resets the internal state, so that if the custom pop-up was
2859 shown inside the reimplemented showPopup(), then you also need to
2860 reimplement the hidePopup() function to hide your custom pop-up
2861 and call the base class implementation to reset the internal state
2862 whenever your custom pop-up widget is hidden.
2863
2864 \sa showPopup()
2865*/
2866void QComboBox::hidePopup()
2867{
2868 Q_D(QComboBox);
2869 if (d->hidingPopup)
2870 return;
2871 d->hidingPopup = true;
2872 // can't use QBoolBlocker on a bitfield
2873 auto resetHidingPopup = qScopeGuard(f: [d]{
2874 d->hidingPopup = false;
2875 });
2876
2877 if (!d->container || !d->container->isVisible())
2878 return;
2879
2880#if QT_CONFIG(effects)
2881 QItemSelectionModel *selectionModel = d->container->itemView()
2882 ? d->container->itemView()->selectionModel() : nullptr;
2883 // Flash selected/triggered item (if any) before hiding the popup.
2884 if (style()->styleHint(stylehint: QStyle::SH_Menu_FlashTriggeredItem) &&
2885 selectionModel && selectionModel->hasSelection()) {
2886 const QItemSelection selection = selectionModel->selection();
2887
2888 QTimer::singleShot(interval: 0, receiver: d->container, slot: [d, selection, selectionModel]{
2889 QSignalBlocker modelBlocker(d->model);
2890 QSignalBlocker viewBlocker(d->container->itemView());
2891 QSignalBlocker containerBlocker(d->container);
2892
2893 // Deselect item and wait 60 ms.
2894 selectionModel->select(selection, command: QItemSelectionModel::Toggle);
2895 QTimer::singleShot(interval: 60, receiver: d->container, slot: [d, selection, selectionModel]{
2896 QSignalBlocker modelBlocker(d->model);
2897 QSignalBlocker viewBlocker(d->container->itemView());
2898 QSignalBlocker containerBlocker(d->container);
2899 selectionModel->select(selection, command: QItemSelectionModel::Toggle);
2900 QTimer::singleShot(interval: 20, receiver: d->container, slot: [d] {
2901 d->doHidePopup();
2902 });
2903 });
2904 });
2905 } else
2906#endif // QT_CONFIG(effects)
2907 {
2908 d->doHidePopup();
2909 }
2910}
2911
2912void QComboBoxPrivate::doHidePopup()
2913{
2914 if (container && container->isVisible())
2915 container->hide();
2916
2917 resetButton();
2918}
2919
2920void QComboBoxPrivate::updateCurrentText(const QString &text)
2921{
2922 if (text == currentText)
2923 return;
2924
2925 currentText = text;
2926 emit q_func()->currentTextChanged(text);
2927}
2928
2929/*!
2930 Clears the combobox, removing all items.
2931
2932 Note: If you have set an external model on the combobox this model
2933 will still be cleared when calling this function.
2934*/
2935void QComboBox::clear()
2936{
2937 Q_D(QComboBox);
2938 d->model->removeRows(row: 0, count: d->model->rowCount(parent: d->root), parent: d->root);
2939#if QT_CONFIG(accessibility)
2940 QAccessibleValueChangeEvent event(this, QString());
2941 QAccessible::updateAccessibility(event: &event);
2942#endif
2943}
2944
2945/*!
2946 Clears the contents of the line edit used for editing in the combobox.
2947*/
2948void QComboBox::clearEditText()
2949{
2950 Q_D(QComboBox);
2951 if (d->lineEdit)
2952 d->lineEdit->clear();
2953#if QT_CONFIG(accessibility)
2954 QAccessibleValueChangeEvent event(this, QString());
2955 QAccessible::updateAccessibility(event: &event);
2956#endif
2957}
2958
2959/*!
2960 Sets the \a text in the combobox's text edit.
2961*/
2962void QComboBox::setEditText(const QString &text)
2963{
2964 Q_D(QComboBox);
2965 if (d->lineEdit)
2966 d->lineEdit->setText(text);
2967#if QT_CONFIG(accessibility)
2968 QAccessibleValueChangeEvent event(this, text);
2969 QAccessible::updateAccessibility(event: &event);
2970#endif
2971}
2972
2973/*!
2974 \reimp
2975*/
2976void QComboBox::focusInEvent(QFocusEvent *e)
2977{
2978 Q_D(QComboBox);
2979 update();
2980 if (d->lineEdit) {
2981 d->lineEdit->event(e);
2982#if QT_CONFIG(completer)
2983 if (d->lineEdit->completer())
2984 d->lineEdit->completer()->setWidget(this);
2985#endif
2986 }
2987}
2988
2989/*!
2990 \reimp
2991*/
2992void QComboBox::focusOutEvent(QFocusEvent *e)
2993{
2994 Q_D(QComboBox);
2995 update();
2996 if (d->lineEdit)
2997 d->lineEdit->event(e);
2998}
2999
3000/*! \reimp */
3001void QComboBox::changeEvent(QEvent *e)
3002{
3003 Q_D(QComboBox);
3004 switch (e->type()) {
3005 case QEvent::StyleChange:
3006 if (d->container)
3007 d->container->updateStyleSettings();
3008 d->updateDelegate();
3009
3010#ifdef Q_OS_MAC
3011 case QEvent::MacSizeChange:
3012#endif
3013 d->sizeHint = QSize(); // invalidate size hint
3014 d->minimumSizeHint = QSize();
3015 d->updateLayoutDirection();
3016 if (d->lineEdit)
3017 d->updateLineEditGeometry();
3018 d->setLayoutItemMargins(element: QStyle::SE_ComboBoxLayoutItem);
3019
3020 if (e->type() == QEvent::MacSizeChange) {
3021 QPlatformTheme::Font f = QPlatformTheme::SystemFont;
3022 if (testAttribute(attribute: Qt::WA_MacSmallSize))
3023 f = QPlatformTheme::SmallFont;
3024 else if (testAttribute(attribute: Qt::WA_MacMiniSize))
3025 f = QPlatformTheme::MiniFont;
3026 if (const QFont *platformFont = QApplicationPrivate::platformTheme()->font(type: f)) {
3027 QFont f = font();
3028 f.setPointSizeF(platformFont->pointSizeF());
3029 setFont(f);
3030 }
3031 }
3032 // ### need to update scrollers etc. as well here
3033 break;
3034 case QEvent::EnabledChange:
3035 if (!isEnabled())
3036 hidePopup();
3037 break;
3038 case QEvent::PaletteChange: {
3039 d->updateViewContainerPaletteAndOpacity();
3040 break;
3041 }
3042 case QEvent::FontChange: {
3043 d->sizeHint = QSize(); // invalidate size hint
3044 d->viewContainer()->setFont(font());
3045 d->viewContainer()->itemView()->doItemsLayout();
3046 if (d->lineEdit)
3047 d->updateLineEditGeometry();
3048 break;
3049 }
3050 default:
3051 break;
3052 }
3053 QWidget::changeEvent(e);
3054}
3055
3056/*!
3057 \reimp
3058*/
3059void QComboBox::resizeEvent(QResizeEvent *)
3060{
3061 Q_D(QComboBox);
3062 d->updateLineEditGeometry();
3063}
3064
3065/*!
3066 \reimp
3067*/
3068void QComboBox::paintEvent(QPaintEvent *)
3069{
3070 QStylePainter painter(this);
3071 painter.setPen(palette().color(cr: QPalette::Text));
3072
3073 // draw the combobox frame, focusrect and selected etc.
3074 QStyleOptionComboBox opt;
3075 initStyleOption(option: &opt);
3076 painter.drawComplexControl(cc: QStyle::CC_ComboBox, opt);
3077
3078 if (currentIndex() < 0 && !placeholderText().isEmpty()) {
3079 opt.palette.setBrush(acr: QPalette::ButtonText, abrush: opt.palette.placeholderText());
3080 opt.currentText = placeholderText();
3081 }
3082
3083 // draw the icon and text
3084 painter.drawControl(ce: QStyle::CE_ComboBoxLabel, opt);
3085}
3086
3087/*!
3088 \reimp
3089*/
3090void QComboBox::showEvent(QShowEvent *e)
3091{
3092 Q_D(QComboBox);
3093 if (!d->shownOnce && d->sizeAdjustPolicy == QComboBox::AdjustToContentsOnFirstShow) {
3094 d->sizeHint = QSize();
3095 updateGeometry();
3096 }
3097 d->shownOnce = true;
3098 QWidget::showEvent(event: e);
3099}
3100
3101/*!
3102 \reimp
3103*/
3104void QComboBox::hideEvent(QHideEvent *)
3105{
3106 hidePopup();
3107}
3108
3109/*!
3110 \reimp
3111*/
3112bool QComboBox::event(QEvent *event)
3113{
3114 Q_D(QComboBox);
3115 switch(event->type()) {
3116 case QEvent::LayoutDirectionChange:
3117 case QEvent::ApplicationLayoutDirectionChange:
3118 d->updateLayoutDirection();
3119 d->updateLineEditGeometry();
3120 break;
3121 case QEvent::HoverEnter:
3122 case QEvent::HoverLeave:
3123 case QEvent::HoverMove:
3124 if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
3125 d->updateHoverControl(pos: he->position().toPoint());
3126 break;
3127 case QEvent::ShortcutOverride:
3128 if (d->lineEdit)
3129 return d->lineEdit->event(event);
3130 break;
3131#ifdef QT_KEYPAD_NAVIGATION
3132 case QEvent::EnterEditFocus:
3133 if (d->lineEdit)
3134 d->lineEdit->event(event); //so cursor starts
3135 break;
3136 case QEvent::LeaveEditFocus:
3137 if (d->lineEdit)
3138 d->lineEdit->event(event); //so cursor stops
3139 break;
3140#endif
3141 default:
3142 break;
3143 }
3144 return QWidget::event(event);
3145}
3146
3147/*!
3148 \reimp
3149*/
3150void QComboBox::mousePressEvent(QMouseEvent *e)
3151{
3152 Q_D(QComboBox);
3153 if (!QGuiApplication::styleHints()->setFocusOnTouchRelease())
3154 d->showPopupFromMouseEvent(e);
3155}
3156
3157void QComboBoxPrivate::showPopupFromMouseEvent(QMouseEvent *e)
3158{
3159 Q_Q(QComboBox);
3160 QStyleOptionComboBox opt;
3161 q->initStyleOption(option: &opt);
3162 QStyle::SubControl sc = q->style()->hitTestComplexControl(cc: QStyle::CC_ComboBox, opt: &opt, pt: e->position().toPoint(), widget: q);
3163
3164 if (e->button() == Qt::LeftButton
3165 && !(sc == QStyle::SC_None && e->type() == QEvent::MouseButtonRelease)
3166 && (sc == QStyle::SC_ComboBoxArrow || !q->isEditable())
3167 && !viewContainer()->isVisible()) {
3168 if (sc == QStyle::SC_ComboBoxArrow)
3169 updateArrow(state: QStyle::State_Sunken);
3170#ifdef QT_KEYPAD_NAVIGATION
3171 //if the container already exists, then d->viewContainer() is safe to call
3172 if (container) {
3173#else
3174 if (true) {
3175#endif
3176 // We've restricted the next couple of lines, because by not calling
3177 // viewContainer(), we avoid creating the QComboBoxPrivateContainer.
3178 viewContainer()->initialClickPosition = q->mapToGlobal(e->position().toPoint());
3179 }
3180 QPointer<QComboBox> guard = q;
3181 q->showPopup();
3182 if (!guard)
3183 return;
3184 // The code below ensures that regular mousepress and pick item still works
3185 // If it was not called the viewContainer would ignore event since it didn't have
3186 // a mousePressEvent first.
3187 if (viewContainer()) {
3188 viewContainer()->blockMouseReleaseTimer.start(msec: QApplication::doubleClickInterval());
3189 viewContainer()->maybeIgnoreMouseButtonRelease = false;
3190 }
3191 } else {
3192#ifdef QT_KEYPAD_NAVIGATION
3193 if (QApplicationPrivate::keypadNavigationEnabled() && sc == QStyle::SC_ComboBoxEditField && lineEdit) {
3194 lineEdit->event(e); //so lineedit can move cursor, etc
3195 return;
3196 }
3197#endif
3198 e->ignore();
3199 }
3200}
3201
3202/*!
3203 \reimp
3204*/
3205void QComboBox::mouseReleaseEvent(QMouseEvent *e)
3206{
3207 Q_D(QComboBox);
3208 d->updateArrow(state: QStyle::State_None);
3209 if (QGuiApplication::styleHints()->setFocusOnTouchRelease() && hasFocus())
3210 d->showPopupFromMouseEvent(e);
3211}
3212
3213/*!
3214 \reimp
3215*/
3216void QComboBox::keyPressEvent(QKeyEvent *e)
3217{
3218 Q_D(QComboBox);
3219
3220#if QT_CONFIG(completer)
3221 if (const auto *cmpltr = completer()) {
3222 const auto *popup = QCompleterPrivate::get(o: cmpltr)->popup;
3223 if (popup && popup->isVisible()) {
3224 // provide same autocompletion support as line edit
3225 d->lineEdit->event(e);
3226 return;
3227 }
3228 }
3229#endif
3230
3231 enum Move { NoMove=0 , MoveUp , MoveDown , MoveFirst , MoveLast};
3232
3233 Move move = NoMove;
3234 int newIndex = currentIndex();
3235
3236 bool pressLikeButton = !d->lineEdit;
3237#ifdef QT_KEYPAD_NAVIGATION
3238 pressLikeButton |= QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus();
3239#endif
3240 auto key = e->key();
3241 if (pressLikeButton) {
3242 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()
3243 ->themeHint(hint: QPlatformTheme::ButtonPressKeys)
3244 .value<QList<Qt::Key>>();
3245 if (buttonPressKeys.contains(t: key)) {
3246 showPopup();
3247 return;
3248 }
3249 }
3250
3251 switch (key) {
3252 case Qt::Key_Up:
3253 if (e->modifiers() & Qt::ControlModifier)
3254 break; // pass to line edit for auto completion
3255 Q_FALLTHROUGH();
3256 case Qt::Key_PageUp:
3257#ifdef QT_KEYPAD_NAVIGATION
3258 if (QApplicationPrivate::keypadNavigationEnabled())
3259 e->ignore();
3260 else
3261#endif
3262 move = MoveUp;
3263 break;
3264 case Qt::Key_Down:
3265 if (e->modifiers() & Qt::AltModifier) {
3266 showPopup();
3267 return;
3268 } else if (e->modifiers() & Qt::ControlModifier)
3269 break; // pass to line edit for auto completion
3270 Q_FALLTHROUGH();
3271 case Qt::Key_PageDown:
3272#ifdef QT_KEYPAD_NAVIGATION
3273 if (QApplicationPrivate::keypadNavigationEnabled())
3274 e->ignore();
3275 else
3276#endif
3277 move = MoveDown;
3278 break;
3279 case Qt::Key_Home:
3280 if (!d->lineEdit)
3281 move = MoveFirst;
3282 break;
3283 case Qt::Key_End:
3284 if (!d->lineEdit)
3285 move = MoveLast;
3286 break;
3287 case Qt::Key_F4:
3288 if (!e->modifiers()) {
3289 showPopup();
3290 return;
3291 }
3292 break;
3293 case Qt::Key_Enter:
3294 case Qt::Key_Return:
3295 case Qt::Key_Escape:
3296 if (!d->lineEdit)
3297 e->ignore();
3298 break;
3299#ifdef QT_KEYPAD_NAVIGATION
3300 case Qt::Key_Left:
3301 case Qt::Key_Right:
3302 if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus())
3303 e->ignore();
3304 break;
3305 case Qt::Key_Back:
3306 if (QApplicationPrivate::keypadNavigationEnabled()) {
3307 if (!hasEditFocus() || !d->lineEdit)
3308 e->ignore();
3309 } else {
3310 e->ignore(); // let the surrounding dialog have it
3311 }
3312 break;
3313#endif
3314 default:
3315#if QT_CONFIG(shortcut)
3316 if (d->container && d->container->isVisible() && e->matches(key: QKeySequence::Cancel)) {
3317 hidePopup();
3318 e->accept();
3319 }
3320#endif
3321
3322 if (!d->lineEdit) {
3323 const auto text = e->text();
3324 if (!text.isEmpty() && text.at(i: 0).isPrint())
3325 d->keyboardSearchString(text);
3326 else
3327 e->ignore();
3328 }
3329 }
3330
3331 const int rowCount = count();
3332
3333 if (move != NoMove) {
3334 e->accept();
3335 switch (move) {
3336 case MoveFirst:
3337 newIndex = -1;
3338 Q_FALLTHROUGH();
3339 case MoveDown:
3340 newIndex++;
3341 while (newIndex < rowCount && !(d->model->index(row: newIndex, column: d->modelColumn, parent: d->root).flags() & Qt::ItemIsEnabled))
3342 newIndex++;
3343 break;
3344 case MoveLast:
3345 newIndex = rowCount;
3346 Q_FALLTHROUGH();
3347 case MoveUp:
3348 newIndex--;
3349 while ((newIndex >= 0) && !(d->model->flags(index: d->model->index(row: newIndex,column: d->modelColumn,parent: d->root)) & Qt::ItemIsEnabled))
3350 newIndex--;
3351 break;
3352 default:
3353 e->ignore();
3354 break;
3355 }
3356
3357 if (newIndex >= 0 && newIndex < rowCount && newIndex != currentIndex()) {
3358 setCurrentIndex(newIndex);
3359 d->emitActivated(index: d->currentIndex);
3360 }
3361 } else if (d->lineEdit) {
3362 d->lineEdit->event(e);
3363 }
3364}
3365
3366
3367/*!
3368 \reimp
3369*/
3370void QComboBox::keyReleaseEvent(QKeyEvent *e)
3371{
3372 Q_D(QComboBox);
3373 if (d->lineEdit)
3374 d->lineEdit->event(e);
3375 else
3376 QWidget::keyReleaseEvent(event: e);
3377}
3378
3379/*!
3380 \reimp
3381*/
3382#if QT_CONFIG(wheelevent)
3383void QComboBox::wheelEvent(QWheelEvent *e)
3384{
3385 Q_D(QComboBox);
3386 QStyleOptionComboBox opt;
3387 initStyleOption(option: &opt);
3388 if (style()->styleHint(stylehint: QStyle::SH_ComboBox_AllowWheelScrolling, opt: &opt, widget: this) &&
3389 !d->viewContainer()->isVisible()) {
3390 const int rowCount = count();
3391 int newIndex = currentIndex();
3392 int delta = e->angleDelta().y();
3393
3394 if (delta > 0) {
3395 newIndex--;
3396 while ((newIndex >= 0) && !(d->model->flags(index: d->model->index(row: newIndex,column: d->modelColumn,parent: d->root)) & Qt::ItemIsEnabled))
3397 newIndex--;
3398 } else if (delta < 0) {
3399 newIndex++;
3400 while (newIndex < rowCount && !(d->model->index(row: newIndex, column: d->modelColumn, parent: d->root).flags() & Qt::ItemIsEnabled))
3401 newIndex++;
3402 }
3403
3404 if (newIndex >= 0 && newIndex < rowCount && newIndex != currentIndex()) {
3405 setCurrentIndex(newIndex);
3406 d->emitActivated(index: d->currentIndex);
3407 }
3408 e->accept();
3409 }
3410}
3411#endif
3412
3413#ifndef QT_NO_CONTEXTMENU
3414/*!
3415 \reimp
3416*/
3417void QComboBox::contextMenuEvent(QContextMenuEvent *e)
3418{
3419 Q_D(QComboBox);
3420 if (d->lineEdit) {
3421 Qt::ContextMenuPolicy p = d->lineEdit->contextMenuPolicy();
3422 d->lineEdit->setContextMenuPolicy(Qt::DefaultContextMenu);
3423 d->lineEdit->event(e);
3424 d->lineEdit->setContextMenuPolicy(p);
3425 }
3426}
3427#endif // QT_NO_CONTEXTMENU
3428
3429void QComboBoxPrivate::keyboardSearchString(const QString &text)
3430{
3431 // use keyboardSearch from the listView so we do not duplicate code
3432 QAbstractItemView *view = viewContainer()->itemView();
3433 view->setCurrentIndex(currentIndex);
3434 int currentRow = view->currentIndex().row();
3435 view->keyboardSearch(search: text);
3436 if (currentRow != view->currentIndex().row()) {
3437 setCurrentIndex(view->currentIndex());
3438 emitActivated(index: currentIndex);
3439 }
3440}
3441
3442void QComboBoxPrivate::modelChanged()
3443{
3444 Q_Q(QComboBox);
3445
3446 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
3447 sizeHint = QSize();
3448 adjustComboBoxSize();
3449 q->updateGeometry();
3450 }
3451}
3452
3453/*!
3454 \reimp
3455*/
3456void QComboBox::inputMethodEvent(QInputMethodEvent *e)
3457{
3458 Q_D(QComboBox);
3459 if (d->lineEdit) {
3460 d->lineEdit->event(e);
3461 } else {
3462 if (!e->commitString().isEmpty())
3463 d->keyboardSearchString(text: e->commitString());
3464 else
3465 e->ignore();
3466 }
3467}
3468
3469/*!
3470 \reimp
3471*/
3472QVariant QComboBox::inputMethodQuery(Qt::InputMethodQuery query) const
3473{
3474 Q_D(const QComboBox);
3475 if (d->lineEdit)
3476 return d->lineEdit->inputMethodQuery(query);
3477 return QWidget::inputMethodQuery(query);
3478}
3479
3480/*!\internal
3481*/
3482QVariant QComboBox::inputMethodQuery(Qt::InputMethodQuery query, const QVariant &argument) const
3483{
3484 Q_D(const QComboBox);
3485 if (d->lineEdit)
3486 return d->lineEdit->inputMethodQuery(property: query, argument);
3487 return QWidget::inputMethodQuery(query);
3488}
3489
3490/*!
3491 \fn void QComboBox::addItem(const QString &text, const QVariant &userData)
3492
3493 Adds an item to the combobox with the given \a text, and
3494 containing the specified \a userData (stored in the Qt::UserRole).
3495 The item is appended to the list of existing items.
3496*/
3497
3498/*!
3499 \fn void QComboBox::addItem(const QIcon &icon, const QString &text,
3500 const QVariant &userData)
3501
3502 Adds an item to the combobox with the given \a icon and \a text,
3503 and containing the specified \a userData (stored in the
3504 Qt::UserRole). The item is appended to the list of existing items.
3505*/
3506
3507/*!
3508 \fn void QComboBox::addItems(const QStringList &texts)
3509
3510 Adds each of the strings in the given \a texts to the combobox. Each item
3511 is appended to the list of existing items in turn.
3512*/
3513
3514/*!
3515 \fn void QComboBox::editTextChanged(const QString &text)
3516
3517 This signal is emitted when the text in the combobox's line edit
3518 widget is changed. The new text is specified by \a text.
3519*/
3520
3521/*!
3522 \property QComboBox::frame
3523 \brief whether the combo box draws itself with a frame.
3524
3525
3526 If enabled (the default) the combo box draws itself inside a
3527 frame, otherwise the combo box draws itself without any frame.
3528*/
3529bool QComboBox::hasFrame() const
3530{
3531 Q_D(const QComboBox);
3532 return d->frame;
3533}
3534
3535
3536void QComboBox::setFrame(bool enable)
3537{
3538 Q_D(QComboBox);
3539 d->frame = enable;
3540 update();
3541 updateGeometry();
3542}
3543
3544/*!
3545 \property QComboBox::modelColumn
3546 \brief the column in the model that is visible.
3547
3548 If set prior to populating the combo box, the pop-up view will
3549 not be affected and will show the first column (using this property's
3550 default value).
3551
3552 By default, this property has a value of 0.
3553
3554 \note In an editable combobox, the visible column will also become
3555 the \l{QCompleter::completionColumn}{completion column}.
3556*/
3557int QComboBox::modelColumn() const
3558{
3559 Q_D(const QComboBox);
3560 return d->modelColumn;
3561}
3562
3563void QComboBox::setModelColumn(int visibleColumn)
3564{
3565 Q_D(QComboBox);
3566 d->modelColumn = visibleColumn;
3567 QListView *lv = qobject_cast<QListView *>(object: d->viewContainer()->itemView());
3568 if (lv)
3569 lv->setModelColumn(visibleColumn);
3570#if QT_CONFIG(completer)
3571 if (d->lineEdit && d->lineEdit->completer())
3572 d->lineEdit->completer()->setCompletionColumn(visibleColumn);
3573#endif
3574 setCurrentIndex(currentIndex()); //update the text to the text of the new column;
3575}
3576
3577QT_END_NAMESPACE
3578
3579#include "moc_qcombobox.cpp"
3580#include "moc_qcombobox_p.cpp"
3581

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/widgets/widgets/qcombobox.cpp