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

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