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