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