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