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