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

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