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 auto *style = combo->style();
873 const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo);
874#if QT_CONFIG(scrollbar)
875 if (usePopup)
876 view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
877#endif
878 if (usePopup ||
879 style->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Current, &opt, combo) ||
880 style->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Active, &opt, combo)
881 ) {
882 view->setMouseTracking(true);
883 }
884 view->setSelectionMode(QAbstractItemView::SingleSelection);
885 view->setFrameStyle(QFrame::NoFrame);
886 view->setLineWidth(0);
887 view->setEditTriggers(QAbstractItemView::NoEditTriggers);
888#if QT_CONFIG(scrollbar)
889 connect(view->verticalScrollBar(), &QScrollBar::valueChanged,
890 this, &QComboBoxPrivateContainer::updateScrollers);
891 connect(view->verticalScrollBar(), &QScrollBar::rangeChanged,
892 this, &QComboBoxPrivateContainer::updateScrollers);
893#endif
894 connect(view, &QAbstractItemView::destroyed,
895 this, &QComboBoxPrivateContainer::viewDestroyed);
896}
897
898/*!
899 Returns the top/bottom vertical margin of the view.
900*/
901int QComboBoxPrivateContainer::topMargin() const
902{
903 if (const QListView *lview = qobject_cast<const QListView*>(view))
904 return lview->spacing();
905#if QT_CONFIG(tableview)
906 if (const QTableView *tview = qobject_cast<const QTableView*>(view))
907 return tview->showGrid() ? 1 : 0;
908#endif
909 return 0;
910}
911
912/*!
913 Returns the spacing between the items in the view.
914*/
915int QComboBoxPrivateContainer::spacing() const
916{
917 QListView *lview = qobject_cast<QListView*>(view);
918 if (lview)
919 return 2 * lview->spacing(); // QListView::spacing is the padding around the item.
920#if QT_CONFIG(tableview)
921 QTableView *tview = qobject_cast<QTableView*>(view);
922 if (tview)
923 return tview->showGrid() ? 1 : 0;
924#endif
925 return 0;
926}
927
928void QComboBoxPrivateContainer::updateTopBottomMargin()
929{
930 if (!layout() || layout()->count() < 1)
931 return;
932
933 QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(layout());
934 if (!boxLayout)
935 return;
936
937 const QStyleOptionComboBox opt = comboStyleOption();
938 const auto *style = combo->style();
939 const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo);
940 const int margin = usePopup ? style->pixelMetric(QStyle::PM_MenuVMargin, &opt, combo) : 0;
941
942 QSpacerItem *topSpacer = boxLayout->itemAt(0)->spacerItem();
943 if (topSpacer)
944 topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed);
945
946 QSpacerItem *bottomSpacer = boxLayout->itemAt(boxLayout->count() - 1)->spacerItem();
947 if (bottomSpacer && bottomSpacer != topSpacer)
948 bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed);
949
950 boxLayout->invalidate();
951}
952
953void QComboBoxPrivateContainer::updateStyleSettings()
954{
955 // add scroller arrows if style needs them
956 QStyleOptionComboBox opt = comboStyleOption();
957 const auto *style = combo->style();
958 view->setMouseTracking(style->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Current, &opt, combo) ||
959 style->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Active, &opt, combo) ||
960 style->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo));
961 setFrameStyle(style->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, combo));
962 updateTopBottomMargin();
963}
964
965void QComboBoxPrivateContainer::changeEvent(QEvent *e)
966{
967 if (e->type() == QEvent::StyleChange)
968 updateStyleSettings();
969
970 QFrame::changeEvent(e);
971}
972
973
974bool QComboBoxPrivateContainer::eventFilter(QObject *o, QEvent *e)
975{
976 switch (e->type()) {
977 case QEvent::ShortcutOverride: {
978 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(e);
979 switch (keyEvent->key()) {
980 case Qt::Key_Enter:
981 case Qt::Key_Return:
982 if (view->currentIndex().isValid() && view->currentIndex().flags().testFlag(Qt::ItemIsEnabled)) {
983 combo->hidePopup();
984 keyEvent->accept();
985 emit itemSelected(view->currentIndex());
986 }
987 return true;
988 case Qt::Key_Down:
989 if (!(keyEvent->modifiers() & Qt::AltModifier))
990 break;
991 Q_FALLTHROUGH();
992 case Qt::Key_F4:
993 combo->hidePopup();
994 keyEvent->accept();
995 emit itemSelected(view->currentIndex());
996 return true;
997 default:
998#if QT_CONFIG(shortcut)
999 if (keyEvent->matches(QKeySequence::Cancel) && isVisible()) {
1000 keyEvent->accept();
1001 return true;
1002 }
1003#endif
1004 break;
1005 }
1006 break;
1007 }
1008 case QEvent::MouseMove:
1009 if (isVisible()) {
1010 QMouseEvent *m = static_cast<QMouseEvent *>(e);
1011 QWidget *widget = static_cast<QWidget *>(o);
1012 const QPointF vector = widget->mapToGlobal(m->position()) - initialClickPosition;
1013 if (vector.manhattanLength() > 9 && blockMouseReleaseTimer.isActive())
1014 blockMouseReleaseTimer.stop();
1015 if (combo->style()->styleHint(QStyle::SH_ComboBox_ListMouseTracking_Current, nullptr, combo)) {
1016 QModelIndex indexUnderMouse = view->indexAt(m->position().toPoint());
1017 if (indexUnderMouse.isValid()
1018 && !QComboBoxDelegate::isSeparator(indexUnderMouse)) {
1019 view->setCurrentIndex(indexUnderMouse);
1020 }
1021 }
1022 }
1023 break;
1024 case QEvent::MouseButtonPress:
1025 maybeIgnoreMouseButtonRelease = false;
1026 break;
1027 case QEvent::MouseButtonRelease: {
1028 bool ignoreEvent = maybeIgnoreMouseButtonRelease && popupTimer.elapsed() < QApplication::doubleClickInterval();
1029
1030 QMouseEvent *m = static_cast<QMouseEvent *>(e);
1031 if (isVisible() && view->rect().contains(m->position().toPoint()) && view->currentIndex().isValid()
1032 && !blockMouseReleaseTimer.isActive() && !ignoreEvent
1033 && (view->currentIndex().flags().testFlag(Qt::ItemIsEnabled))
1034 && (view->currentIndex().flags().testFlag(Qt::ItemIsSelectable))) {
1035 combo->hidePopup();
1036 emit itemSelected(view->currentIndex());
1037 return true;
1038 }
1039 break;
1040 }
1041 default:
1042 break;
1043 }
1044 return QFrame::eventFilter(o, e);
1045}
1046
1047void QComboBoxPrivateContainer::showEvent(QShowEvent *)
1048{
1049 combo->update();
1050}
1051
1052void QComboBoxPrivateContainer::hideEvent(QHideEvent *)
1053{
1054 emit resetButton();
1055 combo->update();
1056#if QT_CONFIG(graphicsview)
1057 // QGraphicsScenePrivate::removePopup closes the combo box popup, it hides it non-explicitly.
1058 // Hiding/showing the QComboBox after this will unexpectedly show the popup as well.
1059 // Re-hiding the popup container makes sure it is explicitly hidden.
1060 if (QGraphicsProxyWidget *proxy = graphicsProxyWidget())
1061 proxy->hide();
1062#endif
1063}
1064
1065void QComboBoxPrivateContainer::mousePressEvent(QMouseEvent *e)
1066{
1067
1068 QStyleOptionComboBox opt = comboStyleOption();
1069 opt.subControls = QStyle::SC_All;
1070 opt.activeSubControls = QStyle::SC_ComboBoxArrow;
1071 QStyle::SubControl sc = combo->style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt,
1072 combo->mapFromGlobal(e->globalPosition()).toPoint(),
1073 combo);
1074 if ((combo->isEditable() && sc == QStyle::SC_ComboBoxArrow)
1075 || (!combo->isEditable() && sc != QStyle::SC_None))
1076 setAttribute(Qt::WA_NoMouseReplay);
1077 combo->hidePopup();
1078}
1079
1080void QComboBoxPrivateContainer::mouseReleaseEvent(QMouseEvent *e)
1081{
1082 Q_UNUSED(e);
1083 if (!blockMouseReleaseTimer.isActive()) {
1084 combo->hidePopup();
1085 emit resetButton();
1086 }
1087}
1088
1089QStyleOptionComboBox QComboBoxPrivateContainer::comboStyleOption() const
1090{
1091 // ### This should use QComboBox's initStyleOption(), but it's protected
1092 // perhaps, we could cheat by having the QCombo private instead?
1093 QStyleOptionComboBox opt;
1094 opt.initFrom(combo);
1095 opt.subControls = QStyle::SC_All;
1096 opt.activeSubControls = QStyle::SC_None;
1097 opt.editable = combo->isEditable();
1098 return opt;
1099}
1100
1101/*!
1102 \enum QComboBox::InsertPolicy
1103
1104 This enum specifies what the QComboBox should do when a new string is
1105 entered by the user.
1106
1107 \value NoInsert The string will not be inserted into the combobox.
1108 \value InsertAtTop The string will be inserted as the first item in the combobox.
1109 \value InsertAtCurrent The current item will be \e replaced by the string.
1110 \value InsertAtBottom The string will be inserted after the last item in the combobox.
1111 \value InsertAfterCurrent The string is inserted after the current item in the combobox.
1112 \value InsertBeforeCurrent The string is inserted before the current item in the combobox.
1113 \value InsertAlphabetically The string is inserted in the alphabetic order in the combobox.
1114*/
1115
1116/*!
1117 \enum QComboBox::SizeAdjustPolicy
1118
1119 This enum specifies how the size hint of the QComboBox should
1120 adjust when new content is added or content changes.
1121
1122 \value AdjustToContents The combobox will always adjust to the contents
1123 \value AdjustToContentsOnFirstShow The combobox will adjust to its contents the first time it is shown.
1124 \value AdjustToMinimumContentsLengthWithIcon The combobox will adjust to \l minimumContentsLength plus space for an icon.
1125 For performance reasons use this policy on large models.
1126*/
1127
1128/*!
1129 \fn void QComboBox::activated(int index)
1130
1131 This signal is sent when the user chooses an item in the combobox.
1132 The item's \a index is passed. Note that this signal is sent even
1133 when the choice is not changed. If you need to know when the
1134 choice actually changes, use signal currentIndexChanged() or
1135 currentTextChanged().
1136*/
1137
1138/*!
1139 \fn void QComboBox::textActivated(const QString &text)
1140 \since 5.14
1141
1142 This signal is sent when the user chooses an item in the combobox.
1143 The item's \a text is passed. Note that this signal is sent even
1144 when the choice is not changed. If you need to know when the
1145 choice actually changes, use signal currentIndexChanged() or
1146 currentTextChanged().
1147*/
1148
1149/*!
1150 \fn void QComboBox::highlighted(int index)
1151
1152 This signal is sent when an item in the combobox popup list is
1153 highlighted by the user. The item's \a index is passed.
1154*/
1155
1156/*!
1157 \fn void QComboBox::textHighlighted(const QString &text)
1158 \since 5.14
1159
1160 This signal is sent when an item in the combobox popup list is
1161 highlighted by the user. The item's \a text is passed.
1162*/
1163
1164/*!
1165 \fn void QComboBox::currentIndexChanged(int index)
1166 \since 4.1
1167
1168 This signal is sent whenever the currentIndex in the combobox
1169 changes either through user interaction or programmatically. The
1170 item's \a index is passed or -1 if the combobox becomes empty or the
1171 currentIndex was reset.
1172*/
1173
1174/*!
1175 \fn void QComboBox::currentTextChanged(const QString &text)
1176 \since 5.0
1177
1178 This signal is emitted whenever currentText changes.
1179 The new value is passed as \a text.
1180
1181 \note It is not emitted, if currentText remains the same,
1182 even if currentIndex changes.
1183*/
1184
1185/*!
1186 Constructs a combobox with the given \a parent, using the default
1187 model QStandardItemModel.
1188*/
1189QComboBox::QComboBox(QWidget *parent)
1190 : QWidget(*new QComboBoxPrivate(), parent, { })
1191{
1192 Q_D(QComboBox);
1193 d->init();
1194}
1195
1196/*!
1197 \internal
1198*/
1199QComboBox::QComboBox(QComboBoxPrivate &dd, QWidget *parent)
1200 : QWidget(dd, parent, { })
1201{
1202 Q_D(QComboBox);
1203 d->init();
1204}
1205
1206/*!
1207 \class QComboBox
1208 \brief The QComboBox widget combines a button with a dropdown list.
1209
1210 \ingroup basicwidgets
1211 \inmodule QtWidgets
1212
1213 \table
1214 \row
1215 \li \image collapsed_combobox.png
1216 {Combo box with collapsed options list}
1217 \caption Collapsed QCombobox
1218 \li
1219 \image expanded_combobox.png
1220 {Combo box with expanded options list}
1221 \caption Expanded QCombobox
1222 \endtable
1223
1224 \section1 Display Features
1225 A QComboBox is a compact way to present a list of options to the user.
1226
1227 A combobox is a selection widget that shows the current item,
1228 and pops up a list of selectable items when clicked. Comboboxes can
1229 contain pixmaps as well as strings if the insertItem() and setItemText()
1230 functions are suitably overloaded.
1231
1232 \section1 Editing Features
1233 A combobox may be editable, allowing the user to modify each item in the
1234 list. For editable comboboxes, the function clearEditText() is provided,
1235 to clear the displayed string without changing the combobox's
1236 contents.
1237
1238 When the user enters a new string in an editable combobox, the
1239 widget may or may not insert it, and it can insert it in several
1240 locations. The default policy is \l InsertAtBottom but you can change
1241 this using setInsertPolicy().
1242
1243 It is possible to constrain the input to an editable combobox
1244 using QValidator; see setValidator(). By default, any input is
1245 accepted.
1246
1247 A combobox can be populated using the insert functions,
1248 insertItem() and insertItems() for example. Items can be
1249 changed with setItemText(). An item can be removed with
1250 removeItem() and all items can be removed with clear(). The text
1251 of the current item is returned by currentText(), and the text of
1252 a numbered item is returned with text(). The current item can be
1253 set with setCurrentIndex(). The number of items in the combobox is
1254 returned by count(); the maximum number of items can be set with
1255 setMaxCount(). You can allow editing using setEditable(). For
1256 editable comboboxes you can set auto-completion using
1257 setCompleter() and whether or not the user can add duplicates
1258 is set with setDuplicatesEnabled().
1259
1260 \section1 Signals
1261 There are three signals emitted if the current item of a combobox
1262 changes: currentIndexChanged(), currentTextChanged(), and activated().
1263 currentIndexChanged() and currentTextChanged() are always emitted
1264 regardless if the change
1265 was done programmatically or by user interaction, while
1266 activated() is only emitted when the change is caused by user
1267 interaction. The highlighted() signal is emitted when the user
1268 highlights an item in the combobox popup list. All three signals
1269 exist in two versions, one with a QString argument and one with an
1270 \c int argument. If the user selects or highlights a pixmap, only
1271 the \c int signals are emitted. Whenever the text of an editable
1272 combobox is changed, the editTextChanged() signal is emitted.
1273
1274 \section1 Model/View Framework
1275
1276 QComboBox uses the \l{Model/View Programming}{model/view framework} for its
1277 popup list and to store its items. By default a QStandardItemModel stores
1278 the items and a QListView subclass displays the popuplist. You can access
1279 the model and view directly (with model() and view()), but QComboBox also
1280 provides functions to set and get item data, for example, setItemData() and
1281 itemText(). You can also set a new model and view (with setModel()
1282 and setView()). For the text and icon in the combobox label, the data in
1283 the model that has the Qt::DisplayRole and Qt::DecorationRole is used.
1284
1285 \note You cannot alter the \l{QAbstractItemView::}{SelectionMode}
1286 of the view(), for example, by using
1287 \l{QAbstractItemView::}{setSelectionMode()}.
1288
1289 \sa QLineEdit, QSpinBox, QRadioButton, QButtonGroup
1290*/
1291
1292void QComboBoxPrivate::init()
1293{
1294 Q_Q(QComboBox);
1295#ifdef Q_OS_MACOS
1296 // On OS X, only line edits and list views always get tab focus. It's only
1297 // when we enable full keyboard access that other controls can get tab focus.
1298 // When it's not editable, a combobox looks like a button, and it behaves as
1299 // such in this respect.
1300 if (!q->isEditable())
1301 q->setFocusPolicy(Qt::TabFocus);
1302 else
1303#endif
1304 q->setFocusPolicy(Qt::WheelFocus);
1305
1306 q->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed,
1307 QSizePolicy::ComboBox));
1308 setLayoutItemMargins(QStyle::SE_ComboBoxLayoutItem);
1309 q->setModel(new QStandardItemModel(0, 1, q));
1310 if (!q->isEditable())
1311 q->setAttribute(Qt::WA_InputMethodEnabled, false);
1312 else
1313 q->setAttribute(Qt::WA_InputMethodEnabled);
1314}
1315
1316QComboBoxPrivateContainer* QComboBoxPrivate::viewContainer()
1317{
1318 if (container)
1319 return container;
1320
1321 Q_Q(QComboBox);
1322 container = new QComboBoxPrivateContainer(new QComboBoxListView(q), q);
1323 disconnectModel();
1324 container->itemView()->setModel(model);
1325 connectModel();
1326 container->itemView()->setTextElideMode(Qt::ElideMiddle);
1327 updateDelegate(true);
1328 updateLayoutDirection();
1329 updateViewContainerPaletteAndOpacity();
1330 QObjectPrivate::connect(container, &QComboBoxPrivateContainer::itemSelected,
1331 this, &QComboBoxPrivate::itemSelected);
1332 QObjectPrivate::connect(container->itemView()->selectionModel(),
1333 &QItemSelectionModel::currentChanged,
1334 this, &QComboBoxPrivate::emitHighlighted);
1335 QObjectPrivate::connect(container, &QComboBoxPrivateContainer::resetButton,
1336 this, &QComboBoxPrivate::resetButton);
1337 return container;
1338}
1339
1340
1341void QComboBoxPrivate::resetButton()
1342{
1343 updateArrow(QStyle::State_None);
1344}
1345
1346void QComboBoxPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
1347{
1348 Q_Q(QComboBox);
1349 if (inserting || topLeft.parent() != root)
1350 return;
1351
1352 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
1353 sizeHint = QSize();
1354 adjustComboBoxSize();
1355 q->updateGeometry();
1356 }
1357
1358 if (currentIndex.row() >= topLeft.row() && currentIndex.row() <= bottomRight.row()) {
1359 const QString text = q->itemText(currentIndex.row());
1360 if (lineEdit) {
1361 lineEdit->setText(text);
1362 updateLineEditGeometry();
1363 } else {
1364 updateCurrentText(text);
1365 }
1366 q->update();
1367#if QT_CONFIG(accessibility)
1368 QAccessibleValueChangeEvent event(q, text);
1369 QAccessible::updateAccessibility(&event);
1370#endif
1371 }
1372}
1373
1374void QComboBoxPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
1375{
1376 Q_Q(QComboBox);
1377 if (inserting || parent != root)
1378 return;
1379
1380 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
1381 sizeHint = QSize();
1382 adjustComboBoxSize();
1383 q->updateGeometry();
1384 }
1385
1386 // set current index if combo was previously empty and there is no placeholderText
1387 if (start == 0 && (end - start + 1) == q->count() && !currentIndex.isValid() &&
1388 placeholderText.isEmpty()) {
1389#if QT_CONFIG(accessibility)
1390 // This might have been called by the model emitting rowInserted(), at which
1391 // point the view won't have updated the accessibility bridge yet about its new
1392 // dimensions. Do it now so that the change of the selection matches the row
1393 // indexes of the accessibility bridge's representation.
1394 if (container && container->itemView()) {
1395 QAccessibleTableModelChangeEvent event(container->itemView(),
1396 QAccessibleTableModelChangeEvent::ModelReset);
1397 QAccessible::updateAccessibility(&event);
1398 }
1399#endif
1400 q->setCurrentIndex(0);
1401 // need to emit changed if model updated index "silently"
1402 } else if (currentIndex.row() != indexBeforeChange) {
1403 q->update();
1404 emitCurrentIndexChanged(currentIndex);
1405 }
1406}
1407
1408void QComboBoxPrivate::updateIndexBeforeChange()
1409{
1410 indexBeforeChange = currentIndex.row();
1411}
1412
1413void QComboBoxPrivate::rowsRemoved(const QModelIndex &parent, int /*start*/, int /*end*/)
1414{
1415 Q_Q(QComboBox);
1416 if (parent != root)
1417 return;
1418
1419 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
1420 sizeHint = QSize();
1421 adjustComboBoxSize();
1422 q->updateGeometry();
1423 }
1424
1425 // model has removed the last row
1426 if (model->rowCount(root) == 0) {
1427 setCurrentIndex(QModelIndex());
1428 return;
1429 }
1430
1431 // model has changed the currentIndex
1432 if (currentIndex.row() != indexBeforeChange) {
1433 if (!currentIndex.isValid() && q->count()) {
1434 q->setCurrentIndex(qMin(q->count() - 1, qMax(indexBeforeChange, 0)));
1435 return;
1436 }
1437 if (lineEdit) {
1438 lineEdit->setText(q->itemText(currentIndex.row()));
1439 updateLineEditGeometry();
1440 }
1441 q->update();
1442 emitCurrentIndexChanged(currentIndex);
1443 }
1444}
1445
1446
1447void QComboBoxPrivate::updateViewContainerPaletteAndOpacity()
1448{
1449 if (!container)
1450 return;
1451 Q_Q(QComboBox);
1452 QStyleOptionComboBox opt;
1453 q->initStyleOption(&opt);
1454#if QT_CONFIG(menu)
1455 if (q->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, q)) {
1456 QMenu menu;
1457 menu.ensurePolished();
1458 container->setPalette(menu.palette());
1459 container->setWindowOpacity(menu.windowOpacity());
1460 } else
1461#endif
1462 {
1463 container->setPalette(q->palette());
1464 container->setWindowOpacity(1.0);
1465 }
1466 if (lineEdit)
1467 lineEdit->setPalette(q->palette());
1468}
1469
1470void QComboBoxPrivate::updateFocusPolicy()
1471{
1472#ifdef Q_OS_MACOS
1473 Q_Q(QComboBox);
1474
1475 // See comment in QComboBoxPrivate::init()
1476 if (q->isEditable())
1477 q->setFocusPolicy(Qt::WheelFocus);
1478 else
1479 q->setFocusPolicy(Qt::TabFocus);
1480#endif
1481}
1482
1483/*!
1484 Initialize \a option with the values from this QComboBox. This method
1485 is useful for subclasses when they need a QStyleOptionComboBox, but don't want
1486 to fill in all the information themselves.
1487
1488 \sa QStyleOption::initFrom()
1489*/
1490void QComboBox::initStyleOption(QStyleOptionComboBox *option) const
1491{
1492 if (!option)
1493 return;
1494
1495 Q_D(const QComboBox);
1496 option->initFrom(this);
1497 option->editable = isEditable();
1498 option->frame = d->frame;
1499 if (hasFocus() && !option->editable)
1500 option->state |= QStyle::State_Selected;
1501 option->subControls = QStyle::SC_All;
1502 if (d->arrowState == QStyle::State_Sunken) {
1503 option->activeSubControls = QStyle::SC_ComboBoxArrow;
1504 option->state |= d->arrowState;
1505 } else {
1506 option->activeSubControls = d->hoverControl;
1507 }
1508 option->currentText = currentText();
1509 if (d->currentIndex.isValid()) {
1510 option->currentIcon = d->itemIcon(d->currentIndex);
1511 QVariant alignment = d->model->data(d->currentIndex, Qt::TextAlignmentRole);
1512 if (alignment.isValid())
1513 option->textAlignment = static_cast<Qt::Alignment>(alignment.toUInt());
1514 }
1515 option->iconSize = iconSize();
1516 if (d->container && d->container->isVisible())
1517 option->state |= QStyle::State_On;
1518}
1519
1520void QComboBoxPrivate::initViewItemOption(QStyleOptionViewItem *option) const
1521{
1522 Q_Q(const QComboBox);
1523 q->view()->initViewItemOption(option);
1524 option->widget = q;
1525 option->index = currentIndex;
1526 option->text = q->currentText();
1527 option->icon = itemIcon(currentIndex);
1528}
1529
1530void QComboBoxPrivate::updateLineEditGeometry()
1531{
1532 if (!lineEdit)
1533 return;
1534
1535 Q_Q(QComboBox);
1536 QStyleOptionComboBox opt;
1537 q->initStyleOption(&opt);
1538 QRect editRect = q->style()->subControlRect(QStyle::CC_ComboBox, &opt,
1539 QStyle::SC_ComboBoxEditField, q);
1540 if (currentIndex.isValid() && !q->itemIcon(q->currentIndex()).isNull()) {
1541 QRect comboRect(editRect);
1542 editRect.setWidth(editRect.width() - q->iconSize().width() - 4);
1543 editRect = QStyle::alignedRect(q->layoutDirection(), Qt::AlignRight,
1544 editRect.size(), comboRect);
1545 }
1546 lineEdit->setGeometry(editRect);
1547}
1548
1549Qt::MatchFlags QComboBoxPrivate::matchFlags() const
1550{
1551 // Base how duplicates are determined on the autocompletion case sensitivity
1552 Qt::MatchFlags flags = Qt::MatchFixedString;
1553#if QT_CONFIG(completer)
1554 if (!lineEdit->completer() || lineEdit->completer()->caseSensitivity() == Qt::CaseSensitive)
1555#endif
1556 flags |= Qt::MatchCaseSensitive;
1557 return flags;
1558}
1559
1560
1561void QComboBoxPrivate::editingFinished()
1562{
1563 Q_Q(QComboBox);
1564 if (!lineEdit)
1565 return;
1566 const auto leText = lineEdit->text();
1567 if (!leText.isEmpty() && itemText(currentIndex) != leText) {
1568#if QT_CONFIG(completer)
1569 const auto *leCompleter = lineEdit->completer();
1570 const auto *popup = leCompleter ? QCompleterPrivate::get(leCompleter)->popup : nullptr;
1571 if (popup && popup->isVisible()) {
1572 // QLineEdit::editingFinished() will be emitted before the code flow returns
1573 // to QCompleter::eventFilter(), where QCompleter::activated() may be emitted.
1574 // We know that the completer popup will still be visible at this point, and
1575 // that any selection should be valid.
1576 const QItemSelectionModel *selModel = popup->selectionModel();
1577 const QModelIndex curIndex = popup->currentIndex();
1578 const bool completerIsActive = selModel && selModel->selectedIndexes().contains(curIndex);
1579
1580 if (completerIsActive)
1581 return;
1582 }
1583#endif
1584 const int index = q_func()->findText(leText, matchFlags());
1585 if (index != -1) {
1586 q->setCurrentIndex(index);
1587 emitActivated(currentIndex);
1588 }
1589 }
1590
1591}
1592
1593void QComboBoxPrivate::returnPressed()
1594{
1595 Q_Q(QComboBox);
1596
1597 // The insertion code below does not apply when the policy is QComboBox::NoInsert.
1598 // In case a completer is installed, item activation via the completer is handled
1599 // in completerActivated(). Otherwise editingFinished() updates the current
1600 // index as appropriate.
1601 if (insertPolicy == QComboBox::NoInsert)
1602 return;
1603
1604 if (lineEdit && !lineEdit->text().isEmpty()) {
1605 if (q->count() >= maxCount && !(this->insertPolicy == QComboBox::InsertAtCurrent))
1606 return;
1607 lineEdit->deselect();
1608 lineEdit->end(false);
1609 QString text = lineEdit->text();
1610 // check for duplicates (if not enabled) and quit
1611 int index = -1;
1612 if (!duplicatesEnabled) {
1613 index = q->findText(text, matchFlags());
1614 if (index != -1) {
1615 q->setCurrentIndex(index);
1616 emitActivated(currentIndex);
1617 return;
1618 }
1619 }
1620 switch (insertPolicy) {
1621 case QComboBox::InsertAtTop:
1622 index = 0;
1623 break;
1624 case QComboBox::InsertAtBottom:
1625 index = q->count();
1626 break;
1627 case QComboBox::InsertAtCurrent:
1628 case QComboBox::InsertAfterCurrent:
1629 case QComboBox::InsertBeforeCurrent:
1630 if (!q->count() || !currentIndex.isValid())
1631 index = 0;
1632 else if (insertPolicy == QComboBox::InsertAtCurrent)
1633 q->setItemText(q->currentIndex(), text);
1634 else if (insertPolicy == QComboBox::InsertAfterCurrent)
1635 index = q->currentIndex() + 1;
1636 else if (insertPolicy == QComboBox::InsertBeforeCurrent)
1637 index = q->currentIndex();
1638 break;
1639 case QComboBox::InsertAlphabetically:
1640 index = 0;
1641 for (int i = 0; i < q->count(); ++i, ++index) {
1642 if (text.toLower() < q->itemText(i).toLower())
1643 break;
1644 }
1645 break;
1646 default:
1647 break;
1648 }
1649 if (index >= 0) {
1650 q->insertItem(index, text);
1651 q->setCurrentIndex(index);
1652 emitActivated(currentIndex);
1653 }
1654 }
1655}
1656
1657void QComboBoxPrivate::itemSelected(const QModelIndex &item)
1658{
1659 Q_Q(QComboBox);
1660 if (item != currentIndex) {
1661 setCurrentIndex(item);
1662 } else if (lineEdit) {
1663 lineEdit->selectAll();
1664 lineEdit->setText(q->itemText(currentIndex.row()));
1665 }
1666 emitActivated(currentIndex);
1667}
1668
1669void QComboBoxPrivate::emitActivated(const QModelIndex &index)
1670{
1671 Q_Q(QComboBox);
1672 if (!index.isValid())
1673 return;
1674 QString text(itemText(index));
1675 emit q->activated(index.row());
1676 emit q->textActivated(text);
1677}
1678
1679void QComboBoxPrivate::emitHighlighted(const QModelIndex &index)
1680{
1681 Q_Q(QComboBox);
1682 if (!index.isValid())
1683 return;
1684 QString text(itemText(index));
1685 emit q->highlighted(index.row());
1686 emit q->textHighlighted(text);
1687}
1688
1689void QComboBoxPrivate::emitCurrentIndexChanged(const QModelIndex &index)
1690{
1691 Q_Q(QComboBox);
1692 const QString text = itemText(index);
1693 emit q->currentIndexChanged(index.row());
1694 // signal lineEdit.textChanged already connected to signal currentTextChanged, so don't emit double here
1695 if (!lineEdit)
1696 updateCurrentText(text);
1697#if QT_CONFIG(accessibility)
1698 QAccessibleValueChangeEvent event(q, text);
1699 QAccessible::updateAccessibility(&event);
1700#endif
1701}
1702
1703QString QComboBoxPrivate::itemText(const QModelIndex &index) const
1704{
1705 return index.isValid() ? model->data(index, itemRole()).toString() : QString();
1706}
1707
1708int QComboBoxPrivate::itemRole() const
1709{
1710 return q_func()->isEditable() ? Qt::EditRole : Qt::DisplayRole;
1711}
1712
1713/*!
1714 Destroys the combobox.
1715*/
1716QComboBox::~QComboBox()
1717{
1718 // ### check delegateparent and delete delegate if us?
1719 Q_D(QComboBox);
1720
1721 QT_TRY {
1722 d->disconnectModel();
1723 } QT_CATCH(...) {
1724 ; // objects can't throw in destructor
1725 }
1726
1727 // Dispose of container before QComboBox goes away. Close explicitly so that
1728 // update cycles back into the combobox (e.g. from accessibility when the
1729 // active window changes) are completed first.
1730 if (d->container) {
1731 d->container->close();
1732 delete d->container;
1733 d->container = nullptr;
1734 }
1735}
1736
1737/*!
1738 \property QComboBox::maxVisibleItems
1739 \brief the maximum allowed size on screen of the combo box, measured in items
1740
1741 By default, this property has a value of 10.
1742
1743 \note This property is ignored for non-editable comboboxes in styles that returns
1744 true for QStyle::SH_ComboBox_Popup such as the Mac style or the Gtk+ Style.
1745*/
1746int QComboBox::maxVisibleItems() const
1747{
1748 Q_D(const QComboBox);
1749 return d->maxVisibleItems;
1750}
1751
1752void QComboBox::setMaxVisibleItems(int maxItems)
1753{
1754 Q_D(QComboBox);
1755 if (Q_UNLIKELY(maxItems < 0)) {
1756 qWarning("QComboBox::setMaxVisibleItems: "
1757 "Invalid max visible items (%d) must be >= 0", maxItems);
1758 return;
1759 }
1760 d->maxVisibleItems = maxItems;
1761}
1762
1763/*!
1764 \property QComboBox::count
1765 \brief the number of items in the combobox.
1766
1767 By default, for an empty combo box, this property has a value of 0.
1768*/
1769int QComboBox::count() const
1770{
1771 Q_D(const QComboBox);
1772 return d->model->rowCount(d->root);
1773}
1774
1775/*!
1776 \property QComboBox::maxCount
1777 \brief the maximum number of items allowed in the combobox.
1778
1779 \note If you set the maximum number to be less then the current
1780 amount of items in the combobox, the extra items will be
1781 truncated. This also applies if you have set an external model on
1782 the combobox.
1783
1784 By default, this property's value is derived from the highest
1785 signed integer available (typically 2147483647).
1786*/
1787void QComboBox::setMaxCount(int max)
1788{
1789 Q_D(QComboBox);
1790 if (Q_UNLIKELY(max < 0)) {
1791 qWarning("QComboBox::setMaxCount: Invalid count (%d) must be >= 0", max);
1792 return;
1793 }
1794
1795 const int rowCount = count();
1796 if (rowCount > max)
1797 d->model->removeRows(max, rowCount - max, d->root);
1798
1799 d->maxCount = max;
1800}
1801
1802int QComboBox::maxCount() const
1803{
1804 Q_D(const QComboBox);
1805 return d->maxCount;
1806}
1807
1808/*!
1809 \property QComboBox::duplicatesEnabled
1810 \brief whether the user can enter duplicate items into the combobox.
1811
1812 Note that it is always possible to programmatically insert duplicate items into the
1813 combobox.
1814
1815 By default, this property is \c false (duplicates are not allowed).
1816*/
1817bool QComboBox::duplicatesEnabled() const
1818{
1819 Q_D(const QComboBox);
1820 return d->duplicatesEnabled;
1821}
1822
1823void QComboBox::setDuplicatesEnabled(bool enable)
1824{
1825 Q_D(QComboBox);
1826 d->duplicatesEnabled = enable;
1827}
1828
1829/*! \fn int QComboBox::findText(const QString &text, Qt::MatchFlags flags = Qt::MatchExactly|Qt::MatchCaseSensitive) const
1830
1831 Returns the index of the item containing the given \a text; otherwise
1832 returns -1.
1833
1834 The \a flags specify how the items in the combobox are searched.
1835*/
1836
1837/*!
1838 Returns the index of the item containing the given \a data for the
1839 given \a role; otherwise returns -1.
1840
1841 The \a flags specify how the items in the combobox are searched.
1842*/
1843int QComboBox::findData(const QVariant &data, int role, Qt::MatchFlags flags) const
1844{
1845 Q_D(const QComboBox);
1846 QModelIndex start = d->model->index(0, d->modelColumn, d->root);
1847 const QModelIndexList result = d->model->match(start, role, data, 1, flags);
1848 if (result.isEmpty())
1849 return -1;
1850 return result.first().row();
1851}
1852
1853/*!
1854 \property QComboBox::insertPolicy
1855 \brief the policy used to determine where user-inserted items should
1856 appear in the combobox.
1857
1858 The default value is \l InsertAtBottom, indicating that new items will appear
1859 at the bottom of the list of items.
1860
1861 \sa InsertPolicy
1862*/
1863
1864QComboBox::InsertPolicy QComboBox::insertPolicy() const
1865{
1866 Q_D(const QComboBox);
1867 return d->insertPolicy;
1868}
1869
1870void QComboBox::setInsertPolicy(InsertPolicy policy)
1871{
1872 Q_D(QComboBox);
1873 d->insertPolicy = policy;
1874}
1875
1876/*!
1877 \property QComboBox::sizeAdjustPolicy
1878 \brief the policy describing how the size of the combobox changes
1879 when the content changes.
1880
1881 The default value is \l AdjustToContentsOnFirstShow.
1882
1883 \sa SizeAdjustPolicy
1884*/
1885
1886QComboBox::SizeAdjustPolicy QComboBox::sizeAdjustPolicy() const
1887{
1888 Q_D(const QComboBox);
1889 return d->sizeAdjustPolicy;
1890}
1891
1892void QComboBox::setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy policy)
1893{
1894 Q_D(QComboBox);
1895 if (policy == d->sizeAdjustPolicy)
1896 return;
1897
1898 d->sizeAdjustPolicy = policy;
1899 d->sizeHint = QSize();
1900 d->adjustComboBoxSize();
1901 updateGeometry();
1902}
1903
1904/*!
1905 \property QComboBox::minimumContentsLength
1906 \brief the minimum number of characters that should fit into the combobox.
1907
1908 The default value is 0.
1909
1910 If this property is set to a positive value, the
1911 minimumSizeHint() and sizeHint() take it into account.
1912
1913 \sa sizeAdjustPolicy
1914*/
1915int QComboBox::minimumContentsLength() const
1916{
1917 Q_D(const QComboBox);
1918 return d->minimumContentsLength;
1919}
1920
1921void QComboBox::setMinimumContentsLength(int characters)
1922{
1923 Q_D(QComboBox);
1924 if (characters == d->minimumContentsLength || characters < 0)
1925 return;
1926
1927 d->minimumContentsLength = characters;
1928
1929 if (d->sizeAdjustPolicy == AdjustToContents
1930 || d->sizeAdjustPolicy == AdjustToMinimumContentsLengthWithIcon) {
1931 d->sizeHint = QSize();
1932 d->adjustComboBoxSize();
1933 updateGeometry();
1934 }
1935}
1936
1937/*!
1938 \property QComboBox::iconSize
1939 \brief the size of the icons shown in the combobox.
1940
1941 Unless explicitly set this returns the default value of the
1942 current style. This size is the maximum size that icons can have;
1943 icons of smaller size are not scaled up.
1944*/
1945
1946QSize QComboBox::iconSize() const
1947{
1948 Q_D(const QComboBox);
1949 if (d->iconSize.isValid())
1950 return d->iconSize;
1951
1952 int iconWidth = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this);
1953 return QSize(iconWidth, iconWidth);
1954}
1955
1956void QComboBox::setIconSize(const QSize &size)
1957{
1958 Q_D(QComboBox);
1959 if (size == d->iconSize)
1960 return;
1961
1962 view()->setIconSize(size);
1963 d->iconSize = size;
1964 d->sizeHint = QSize();
1965 updateGeometry();
1966}
1967
1968/*!
1969 \property QComboBox::placeholderText
1970 \brief Sets a \a placeholderText text shown when no valid index is set.
1971
1972 The \a placeholderText will be shown when an invalid index is set. The
1973 text is not accessible in the dropdown list. When this function is called
1974 before items are added the placeholder text will be shown, otherwise you
1975 have to call setCurrentIndex(-1) programmatically if you want to show the
1976 placeholder text.
1977 Set an empty placeholder text to reset the setting.
1978
1979 When the QComboBox is editable, use QLineEdit::setPlaceholderText()
1980 instead.
1981
1982 \since 5.15
1983*/
1984void QComboBox::setPlaceholderText(const QString &placeholderText)
1985{
1986 Q_D(QComboBox);
1987 if (placeholderText == d->placeholderText)
1988 return;
1989
1990 d->placeholderText = placeholderText;
1991 if (currentIndex() == -1) {
1992 if (d->placeholderText.isEmpty())
1993 setCurrentIndex(0);
1994 else
1995 update();
1996 } else {
1997 updateGeometry();
1998 }
1999}
2000
2001QString QComboBox::placeholderText() const
2002{
2003 Q_D(const QComboBox);
2004 return d->placeholderText;
2005}
2006
2007/*!
2008 \property QComboBox::editable
2009 \brief whether the combo box can be edited by the user.
2010
2011 By default, this property is \c false. The effect of editing depends
2012 on the insert policy.
2013
2014 \note When disabling the \a editable state, the validator and
2015 completer are removed.
2016
2017 \sa InsertPolicy
2018*/
2019bool QComboBox::isEditable() const
2020{
2021 Q_D(const QComboBox);
2022 return d->lineEdit != nullptr;
2023}
2024
2025/*! \internal
2026 update the default delegate
2027 depending on the style's SH_ComboBox_Popup hint, we use a different default delegate.
2028
2029 but we do not change the delegate is the combobox use a custom delegate,
2030 unless \a force is set to true.
2031 */
2032void QComboBoxPrivate::updateDelegate(bool force)
2033{
2034 Q_Q(QComboBox);
2035 QStyleOptionComboBox opt;
2036 q->initStyleOption(&opt);
2037 if (q->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, q)) {
2038 if (force || qobject_cast<QComboBoxDelegate *>(q->itemDelegate()))
2039 q->setItemDelegate(new QComboMenuDelegate(q->view(), q));
2040 } else {
2041 if (force || qobject_cast<QComboMenuDelegate *>(q->itemDelegate()))
2042 q->setItemDelegate(new QComboBoxDelegate(q->view(), q));
2043 }
2044}
2045
2046QIcon QComboBoxPrivate::itemIcon(const QModelIndex &index) const
2047{
2048 if (!index.isValid())
2049 return {};
2050 QVariant decoration = model->data(index, Qt::DecorationRole);
2051 if (decoration.userType() == QMetaType::QPixmap)
2052 return QIcon(qvariant_cast<QPixmap>(decoration));
2053 else
2054 return qvariant_cast<QIcon>(decoration);
2055}
2056
2057void QComboBox::setEditable(bool editable)
2058{
2059 Q_D(QComboBox);
2060 if (isEditable() == editable)
2061 return;
2062
2063 QStyleOptionComboBox opt;
2064 initStyleOption(&opt);
2065 if (editable) {
2066 if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) {
2067 d->viewContainer()->updateScrollers();
2068 view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
2069 }
2070 QLineEdit *le = new QLineEdit(this);
2071 le->setPalette(palette());
2072 setLineEdit(le);
2073 } else {
2074 if (style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)) {
2075 d->viewContainer()->updateScrollers();
2076 view()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2077 }
2078 setAttribute(Qt::WA_InputMethodEnabled, false);
2079 d->lineEdit->hide();
2080 d->lineEdit->deleteLater();
2081 d->lineEdit = nullptr;
2082 }
2083
2084 d->updateDelegate();
2085 d->updateFocusPolicy();
2086
2087 d->viewContainer()->updateTopBottomMargin();
2088 if (!testAttribute(Qt::WA_Resized))
2089 adjustSize();
2090}
2091
2092/*!
2093 Sets the line \a edit to use instead of the current line edit widget.
2094
2095 The combo box takes ownership of the line edit.
2096
2097 \note Since the combobox's line edit owns the QCompleter, any previous
2098 call to setCompleter() will no longer have any effect.
2099*/
2100void QComboBox::setLineEdit(QLineEdit *edit)
2101{
2102 Q_D(QComboBox);
2103 if (Q_UNLIKELY(!edit)) {
2104 qWarning("QComboBox::setLineEdit: cannot set a 0 line edit");
2105 return;
2106 }
2107
2108 if (edit == d->lineEdit)
2109 return;
2110
2111 edit->setText(currentText());
2112 delete d->lineEdit;
2113
2114 d->lineEdit = edit;
2115#ifndef QT_NO_IM
2116 qt_widget_private(d->lineEdit)->inheritsInputMethodHints = 1;
2117#endif
2118 if (d->lineEdit->parent() != this)
2119 d->lineEdit->setParent(this);
2120 QObjectPrivate::connect(d->lineEdit, &QLineEdit::returnPressed,
2121 d, &QComboBoxPrivate::returnPressed);
2122 QObjectPrivate::connect(d->lineEdit, &QLineEdit::editingFinished,
2123 d, &QComboBoxPrivate::editingFinished);
2124 connect(d->lineEdit, &QLineEdit::textChanged, this, &QComboBox::editTextChanged);
2125 connect(d->lineEdit, &QLineEdit::textChanged, this, &QComboBox::currentTextChanged);
2126 QObjectPrivate::connect(d->lineEdit, &QLineEdit::cursorPositionChanged,
2127 d, &QComboBoxPrivate::updateMicroFocus);
2128 QObjectPrivate::connect(d->lineEdit, &QLineEdit::selectionChanged,
2129 d, &QComboBoxPrivate::updateMicroFocus);
2130 QObjectPrivate::connect(d->lineEdit->d_func()->control, &QWidgetLineControl::updateMicroFocus,
2131 d, &QComboBoxPrivate::updateMicroFocus);
2132 d->lineEdit->setFrame(false);
2133 d->lineEdit->setContextMenuPolicy(Qt::NoContextMenu);
2134 d->updateFocusPolicy();
2135 d->lineEdit->setFocusProxy(this);
2136 d->lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
2137
2138#if QT_CONFIG(completer)
2139 // create a default completer
2140 if (!d->lineEdit->completer()) {
2141 QCompleter *completer = new QCompleter(d->model, d->lineEdit);
2142 completer->setCaseSensitivity(Qt::CaseInsensitive);
2143 completer->setCompletionMode(QCompleter::InlineCompletion);
2144 completer->setCompletionColumn(d->modelColumn);
2145 // sets up connections
2146 setCompleter(completer);
2147 }
2148#endif
2149
2150 setAttribute(Qt::WA_InputMethodEnabled);
2151 d->updateLayoutDirection();
2152 d->updateLineEditGeometry();
2153 if (isVisible())
2154 d->lineEdit->show();
2155
2156 update();
2157}
2158
2159/*!
2160 Returns the line edit used to edit items in the combobox, or
2161 \nullptr if there is no line edit.
2162
2163 Only editable combo boxes have a line edit.
2164*/
2165QLineEdit *QComboBox::lineEdit() const
2166{
2167 Q_D(const QComboBox);
2168 return d->lineEdit;
2169}
2170
2171#ifndef QT_NO_VALIDATOR
2172/*!
2173 \fn void QComboBox::setValidator(const QValidator *validator)
2174
2175 Sets the \a validator to use instead of the current validator.
2176
2177 \note The validator is removed when the \l editable property becomes \c false.
2178*/
2179
2180void QComboBox::setValidator(const QValidator *v)
2181{
2182 Q_D(QComboBox);
2183 if (d->lineEdit)
2184 d->lineEdit->setValidator(v);
2185}
2186
2187/*!
2188 Returns the validator that is used to constrain text input for the
2189 combobox.
2190
2191 \sa editable
2192*/
2193const QValidator *QComboBox::validator() const
2194{
2195 Q_D(const QComboBox);
2196 return d->lineEdit ? d->lineEdit->validator() : nullptr;
2197}
2198#endif // QT_NO_VALIDATOR
2199
2200#if QT_CONFIG(completer)
2201
2202/*!
2203 \fn void QComboBox::setCompleter(QCompleter *completer)
2204 \since 4.2
2205
2206 Sets the \a completer to use instead of the current completer.
2207 If \a completer is \nullptr, auto completion is disabled.
2208
2209 By default, for an editable combo box, a QCompleter that
2210 performs case insensitive inline completion is automatically created.
2211
2212 \note The completer is removed when the \l editable property becomes \c false,
2213 or when the line edit is replaced by a call to setLineEdit().
2214 Setting a completer on a QComboBox that is not editable will be ignored.
2215*/
2216void QComboBox::setCompleter(QCompleter *c)
2217{
2218 Q_D(QComboBox);
2219 if (!d->lineEdit) {
2220 qWarning("Setting a QCompleter on non-editable QComboBox is not allowed.");
2221 return;
2222 }
2223 d->lineEdit->setCompleter(c);
2224 if (c) {
2225 QObjectPrivate::connect(c, QOverload<const QModelIndex &>::of(&QCompleter::activated),
2226 d, &QComboBoxPrivate::completerActivated);
2227 c->setWidget(this);
2228 }
2229}
2230
2231/*!
2232 \since 4.2
2233
2234 Returns the completer that is used to auto complete text input for the
2235 combobox.
2236
2237 \sa editable
2238*/
2239QCompleter *QComboBox::completer() const
2240{
2241 Q_D(const QComboBox);
2242 return d->lineEdit ? d->lineEdit->completer() : nullptr;
2243}
2244
2245#endif // QT_CONFIG(completer)
2246
2247/*!
2248 Returns the item delegate used by the popup list view.
2249
2250 \sa setItemDelegate()
2251*/
2252QAbstractItemDelegate *QComboBox::itemDelegate() const
2253{
2254 return view()->itemDelegate();
2255}
2256
2257/*!
2258 Sets the item \a delegate for the popup list view.
2259 The combobox takes ownership of the delegate.
2260
2261 Any existing delegate will be removed, but not deleted. QComboBox
2262 does not take ownership of \a delegate.
2263
2264 \warning You should not share the same instance of a delegate between comboboxes,
2265 widget mappers or views. Doing so can cause incorrect or unintuitive editing behavior
2266 since each view connected to a given delegate may receive the
2267 \l{QAbstractItemDelegate::}{closeEditor()} signal, and attempt to access, modify or
2268 close an editor that has already been closed.
2269
2270 \sa itemDelegate()
2271*/
2272void QComboBox::setItemDelegate(QAbstractItemDelegate *delegate)
2273{
2274 if (Q_UNLIKELY(!delegate)) {
2275 qWarning("QComboBox::setItemDelegate: cannot set a 0 delegate");
2276 return;
2277 }
2278 view()->setItemDelegate(delegate);
2279}
2280
2281/*!
2282 Returns the model used by the combobox.
2283*/
2284
2285QAbstractItemModel *QComboBox::model() const
2286{
2287 Q_D(const QComboBox);
2288 if (d->model == QAbstractItemModelPrivate::staticEmptyModel()) {
2289 QComboBox *that = const_cast<QComboBox*>(this);
2290 that->setModel(new QStandardItemModel(0, 1, that));
2291 }
2292 return d->model;
2293}
2294
2295/*!
2296 Sets the model to be \a model. \a model must not be \nullptr.
2297 If you want to clear the contents of a model, call clear().
2298
2299 \note If the combobox is editable, then the \a model will also be
2300 set on the completer of the line edit.
2301
2302 \sa clear() setCompleter()
2303*/
2304void QComboBox::setModel(QAbstractItemModel *model)
2305{
2306 Q_D(QComboBox);
2307
2308 if (Q_UNLIKELY(!model)) {
2309 qWarning("QComboBox::setModel: cannot set a 0 model");
2310 return;
2311 }
2312
2313 if (model == d->model)
2314 return;
2315
2316#if QT_CONFIG(completer)
2317 if (d->lineEdit && d->lineEdit->completer())
2318 d->lineEdit->completer()->setModel(model);
2319#endif
2320 d->disconnectModel();
2321 if (d->model && d->model->QObject::parent() == this) {
2322 delete d->model;
2323 d->model = nullptr;
2324 }
2325
2326 d->model = model;
2327
2328 if (d->container) {
2329 d->container->itemView()->setModel(model);
2330 QObjectPrivate::connect(d->container->itemView()->selectionModel(),
2331 &QItemSelectionModel::currentChanged,
2332 d, &QComboBoxPrivate::emitHighlighted, Qt::UniqueConnection);
2333 }
2334
2335 d->connectModel();
2336
2337 setRootModelIndex(QModelIndex());
2338
2339 d->trySetValidIndex();
2340 d->modelChanged();
2341}
2342
2343void QComboBoxPrivate::connectModel()
2344{
2345 if (!model)
2346 return;
2347
2348 modelConnections = {
2349 QObjectPrivate::connect(model, &QAbstractItemModel::dataChanged,
2350 this, &QComboBoxPrivate::dataChanged),
2351 QObjectPrivate::connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
2352 this, &QComboBoxPrivate::updateIndexBeforeChange),
2353 QObjectPrivate::connect(model, &QAbstractItemModel::rowsInserted,
2354 this, &QComboBoxPrivate::rowsInserted),
2355 QObjectPrivate::connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
2356 this, &QComboBoxPrivate::updateIndexBeforeChange),
2357 QObjectPrivate::connect(model, &QAbstractItemModel::rowsRemoved,
2358 this, &QComboBoxPrivate::rowsRemoved),
2359 QObjectPrivate::connect(model, &QObject::destroyed,
2360 this, &QComboBoxPrivate::modelDestroyed),
2361 QObjectPrivate::connect(model, &QAbstractItemModel::modelAboutToBeReset,
2362 this, &QComboBoxPrivate::updateIndexBeforeChange),
2363 QObjectPrivate::connect(model, &QAbstractItemModel::modelReset,
2364 this, &QComboBoxPrivate::modelReset)
2365 };
2366}
2367
2368void QComboBoxPrivate::disconnectModel()
2369{
2370 for (auto &connection : modelConnections)
2371 QObject::disconnect(connection);
2372}
2373
2374/*!
2375 Returns the root model item index for the items in the combobox.
2376
2377 \sa setRootModelIndex()
2378*/
2379
2380QModelIndex QComboBox::rootModelIndex() const
2381{
2382 Q_D(const QComboBox);
2383 return QModelIndex(d->root);
2384}
2385
2386/*!
2387 Sets the root model item \a index for the items in the combobox.
2388
2389 \sa rootModelIndex()
2390*/
2391void QComboBox::setRootModelIndex(const QModelIndex &index)
2392{
2393 Q_D(QComboBox);
2394 if (d->root == index)
2395 return;
2396 d->root = QPersistentModelIndex(index);
2397 view()->setRootIndex(index);
2398 update();
2399}
2400
2401/*!
2402 \property QComboBox::currentIndex
2403 \brief the index of the current item in the combobox.
2404
2405 The current index can change when inserting or removing items.
2406
2407 By default, for an empty combo box or a combo box in which no current
2408 item is set, this property has a value of -1.
2409*/
2410int QComboBox::currentIndex() const
2411{
2412 Q_D(const QComboBox);
2413 return d->currentIndex.row();
2414}
2415
2416void QComboBox::setCurrentIndex(int index)
2417{
2418 Q_D(QComboBox);
2419 QModelIndex mi = index >= 0 ? d->model->index(index, d->modelColumn, d->root) : QModelIndex();
2420 d->setCurrentIndex(mi);
2421}
2422
2423void QComboBox::setCurrentText(const QString &text)
2424{
2425 if (isEditable()) {
2426 setEditText(text);
2427 } else {
2428 const int i = findText(text);
2429 if (i > -1)
2430 setCurrentIndex(i);
2431 }
2432}
2433
2434void QComboBoxPrivate::setCurrentIndex(const QModelIndex &mi)
2435{
2436 Q_Q(QComboBox);
2437
2438 QModelIndex normalized = mi.sibling(mi.row(), modelColumn); // no-op if mi.column() == modelColumn
2439 if (!normalized.isValid())
2440 normalized = mi; // Fallback to passed index.
2441
2442 bool indexChanged = (normalized != currentIndex);
2443 if (indexChanged)
2444 currentIndex = QPersistentModelIndex(normalized);
2445 if (lineEdit) {
2446 const QString newText = itemText(normalized);
2447 if (lineEdit->text() != newText) {
2448 lineEdit->setText(newText); // may cause lineEdit -> nullptr (QTBUG-54191)
2449#if QT_CONFIG(completer)
2450 if (lineEdit && lineEdit->completer())
2451 lineEdit->completer()->setCompletionPrefix(newText);
2452#endif
2453 }
2454 updateLineEditGeometry();
2455 }
2456 // If the model was reset to an empty one, currentIndex will be invalidated
2457 // (because it's a QPersistentModelIndex), but the index change will never
2458 // be advertised. So an explicit check for this condition is needed.
2459 // The variable used for that check has to be reset when a previously valid
2460 // index becomes invalid.
2461 const bool modelResetToEmpty = !normalized.isValid() && indexBeforeChange != -1;
2462 if (modelResetToEmpty)
2463 indexBeforeChange = -1;
2464
2465 if (indexChanged || modelResetToEmpty) {
2466 QItemSelectionModel::SelectionFlags selectionMode = QItemSelectionModel::ClearAndSelect;
2467 if (q->view()->selectionBehavior() == QAbstractItemView::SelectRows)
2468 selectionMode.setFlag(QItemSelectionModel::Rows);
2469 if (auto *model = q->view()->selectionModel())
2470 model->setCurrentIndex(currentIndex, selectionMode);
2471
2472 q->update();
2473 emitCurrentIndexChanged(currentIndex);
2474 }
2475}
2476
2477/*!
2478 \property QComboBox::currentText
2479 \brief the current text
2480
2481 If the combo box is editable, the current text is the value displayed
2482 by the line edit. Otherwise, it is the value of the current item or
2483 an empty string if the combo box is empty or no current item is set.
2484
2485 The setter setCurrentText() simply calls setEditText() if the combo box is editable.
2486 Otherwise, if there is a matching text in the list, currentIndex is set to the
2487 corresponding index.
2488
2489 \sa editable, setEditText()
2490*/
2491QString QComboBox::currentText() const
2492{
2493 Q_D(const QComboBox);
2494 if (d->lineEdit)
2495 return d->lineEdit->text();
2496 if (d->currentIndex.isValid())
2497 return d->itemText(d->currentIndex);
2498 return {};
2499}
2500
2501/*!
2502 \property QComboBox::currentData
2503 \brief the data for the current item
2504 \since 5.2
2505
2506 By default, for an empty combo box or a combo box in which no current
2507 item is set, this property contains an invalid QVariant.
2508*/
2509QVariant QComboBox::currentData(int role) const
2510{
2511 Q_D(const QComboBox);
2512 return d->currentIndex.data(role);
2513}
2514
2515/*!
2516 Returns the text for the given \a index in the combobox.
2517*/
2518QString QComboBox::itemText(int index) const
2519{
2520 Q_D(const QComboBox);
2521 QModelIndex mi = d->model->index(index, d->modelColumn, d->root);
2522 return d->itemText(mi);
2523}
2524
2525/*!
2526 Returns the icon for the given \a index in the combobox.
2527*/
2528QIcon QComboBox::itemIcon(int index) const
2529{
2530 Q_D(const QComboBox);
2531 QModelIndex mi = d->model->index(index, d->modelColumn, d->root);
2532 return d->itemIcon(mi);
2533}
2534
2535/*!
2536 Returns the data for the given \a role in the given \a index in the
2537 combobox, or an invalid QVariant if there is no data for this role.
2538*/
2539QVariant QComboBox::itemData(int index, int role) const
2540{
2541 Q_D(const QComboBox);
2542 QModelIndex mi = d->model->index(index, d->modelColumn, d->root);
2543 return d->model->data(mi, role);
2544}
2545
2546/*!
2547 \fn void QComboBox::insertItem(int index, const QString &text, const QVariant &userData)
2548
2549 Inserts the \a text and \a userData (stored in the Qt::UserRole)
2550 into the combobox at the given \a index.
2551
2552 If the index is equal to or higher than the total number of items,
2553 the new item is appended to the list of existing items. If the
2554 index is zero or negative, the new item is prepended to the list
2555 of existing items.
2556
2557 \sa insertItems()
2558*/
2559
2560/*!
2561
2562 Inserts the \a icon, \a text and \a userData (stored in the
2563 Qt::UserRole) into the combobox at the given \a index.
2564
2565 If the index is equal to or higher than the total number of items,
2566 the new item is appended to the list of existing items. If the
2567 index is zero or negative, the new item is prepended to the list
2568 of existing items.
2569
2570 \sa insertItems()
2571*/
2572void QComboBox::insertItem(int index, const QIcon &icon, const QString &text, const QVariant &userData)
2573{
2574 Q_D(QComboBox);
2575 int itemCount = count();
2576 index = qBound(0, index, itemCount);
2577 if (index >= d->maxCount)
2578 return;
2579
2580 // For the common case where we are using the built in QStandardItemModel
2581 // construct a QStandardItem, reducing the number of expensive signals from the model
2582 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(d->model)) {
2583 QStandardItem *item = new QStandardItem(text);
2584 if (!icon.isNull()) item->setData(icon, Qt::DecorationRole);
2585 if (userData.isValid()) item->setData(userData, Qt::UserRole);
2586 m->insertRow(index, item);
2587 ++itemCount;
2588 } else {
2589 d->inserting = true;
2590 if (d->model->insertRows(index, 1, d->root)) {
2591 QModelIndex item = d->model->index(index, d->modelColumn, d->root);
2592 if (icon.isNull() && !userData.isValid()) {
2593 d->model->setData(item, text, Qt::EditRole);
2594 } else {
2595 QMap<int, QVariant> values;
2596 if (!text.isNull()) values.insert(Qt::EditRole, text);
2597 if (!icon.isNull()) values.insert(Qt::DecorationRole, icon);
2598 if (userData.isValid()) values.insert(Qt::UserRole, userData);
2599 if (!values.isEmpty()) d->model->setItemData(item, values);
2600 }
2601 d->inserting = false;
2602 d->rowsInserted(d->root, index, index);
2603 ++itemCount;
2604 } else {
2605 d->inserting = false;
2606 }
2607 }
2608
2609 if (itemCount > d->maxCount)
2610 d->model->removeRows(itemCount - 1, itemCount - d->maxCount, d->root);
2611}
2612
2613/*!
2614 Inserts the strings from the \a list into the combobox as separate items,
2615 starting at the \a index specified.
2616
2617 If the index is equal to or higher than the total number of items, the new items
2618 are appended to the list of existing items. If the index is zero or negative, the
2619 new items are prepended to the list of existing items.
2620
2621 \sa insertItem()
2622 */
2623void QComboBox::insertItems(int index, const QStringList &list)
2624{
2625 Q_D(QComboBox);
2626 if (list.isEmpty())
2627 return;
2628 index = qBound(0, index, count());
2629 int insertCount = qMin(d->maxCount - index, list.size());
2630 if (insertCount <= 0)
2631 return;
2632 // For the common case where we are using the built in QStandardItemModel
2633 // construct a QStandardItem, reducing the number of expensive signals from the model
2634 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(d->model)) {
2635 QList<QStandardItem *> items;
2636 items.reserve(insertCount);
2637 QStandardItem *hiddenRoot = m->invisibleRootItem();
2638 for (int i = 0; i < insertCount; ++i)
2639 items.append(new QStandardItem(list.at(i)));
2640 hiddenRoot->insertRows(index, items);
2641 } else {
2642 d->inserting = true;
2643 if (d->model->insertRows(index, insertCount, d->root)) {
2644 QModelIndex item;
2645 for (int i = 0; i < insertCount; ++i) {
2646 item = d->model->index(i+index, d->modelColumn, d->root);
2647 d->model->setData(item, list.at(i), Qt::EditRole);
2648 }
2649 d->inserting = false;
2650 d->rowsInserted(d->root, index, index + insertCount - 1);
2651 } else {
2652 d->inserting = false;
2653 }
2654 }
2655
2656 int mc = count();
2657 if (mc > d->maxCount)
2658 d->model->removeRows(d->maxCount, mc - d->maxCount, d->root);
2659}
2660
2661/*!
2662 \since 4.4
2663
2664 Inserts a separator item into the combobox at the given \a index.
2665
2666 If the index is equal to or higher than the total number of items, the new item
2667 is appended to the list of existing items. If the index is zero or negative, the
2668 new item is prepended to the list of existing items.
2669
2670 \sa insertItem()
2671*/
2672void QComboBox::insertSeparator(int index)
2673{
2674 Q_D(QComboBox);
2675 int itemCount = count();
2676 index = qBound(0, index, itemCount);
2677 if (index >= d->maxCount)
2678 return;
2679 insertItem(index, QIcon(), QString());
2680 QComboBoxDelegate::setSeparator(d->model, d->model->index(index, 0, d->root));
2681}
2682
2683/*!
2684 Removes the item at the given \a index from the combobox.
2685 This will update the current index if the index is removed.
2686
2687 This function does nothing if \a index is out of range.
2688*/
2689void QComboBox::removeItem(int index)
2690{
2691 Q_D(QComboBox);
2692 if (index < 0 || index >= count())
2693 return;
2694 d->model->removeRows(index, 1, d->root);
2695}
2696
2697/*!
2698 Sets the \a text for the item on the given \a index in the combobox.
2699*/
2700void QComboBox::setItemText(int index, const QString &text)
2701{
2702 Q_D(const QComboBox);
2703 QModelIndex item = d->model->index(index, d->modelColumn, d->root);
2704 if (item.isValid()) {
2705 d->model->setData(item, text, Qt::EditRole);
2706 }
2707}
2708
2709/*!
2710 Sets the \a icon for the item on the given \a index in the combobox.
2711*/
2712void QComboBox::setItemIcon(int index, const QIcon &icon)
2713{
2714 Q_D(const QComboBox);
2715 QModelIndex item = d->model->index(index, d->modelColumn, d->root);
2716 if (item.isValid()) {
2717 d->model->setData(item, icon, Qt::DecorationRole);
2718 }
2719}
2720
2721/*!
2722 Sets the data \a role for the item on the given \a index in the combobox
2723 to the specified \a value.
2724*/
2725void QComboBox::setItemData(int index, const QVariant &value, int role)
2726{
2727 Q_D(const QComboBox);
2728 QModelIndex item = d->model->index(index, d->modelColumn, d->root);
2729 if (item.isValid()) {
2730 d->model->setData(item, value, role);
2731 }
2732}
2733
2734/*!
2735 Returns the list view used for the combobox popup.
2736*/
2737QAbstractItemView *QComboBox::view() const
2738{
2739 Q_D(const QComboBox);
2740 return const_cast<QComboBoxPrivate*>(d)->viewContainer()->itemView();
2741}
2742
2743/*!
2744 Sets the view to be used in the combobox popup to the given \a
2745 itemView. The combobox takes ownership of the view.
2746
2747 Note: If you want to use the convenience views (like QListWidget,
2748 QTableWidget or QTreeWidget), make sure to call setModel() on the
2749 combobox with the convenience widgets model before calling this
2750 function.
2751*/
2752void QComboBox::setView(QAbstractItemView *itemView)
2753{
2754 Q_D(QComboBox);
2755 if (Q_UNLIKELY(!itemView)) {
2756 qWarning("QComboBox::setView: cannot set a 0 view");
2757 return;
2758 }
2759
2760 if (itemView->model() != d->model) {
2761 d->disconnectModel();
2762 itemView->setModel(d->model);
2763 d->connectModel();
2764 }
2765 d->viewContainer()->setItemView(itemView);
2766}
2767
2768/*!
2769 \reimp
2770*/
2771QSize QComboBox::minimumSizeHint() const
2772{
2773 Q_D(const QComboBox);
2774 return d->recomputeSizeHint(d->minimumSizeHint);
2775}
2776
2777/*!
2778 \reimp
2779
2780 This implementation caches the size hint to avoid resizing when
2781 the contents change dynamically. To invalidate the cached value
2782 change the \l sizeAdjustPolicy.
2783*/
2784QSize QComboBox::sizeHint() const
2785{
2786 Q_D(const QComboBox);
2787 return d->recomputeSizeHint(d->sizeHint);
2788}
2789
2790#ifdef Q_OS_MACOS
2791void QComboBoxPrivate::cleanupNativePopup()
2792{
2793 if (!m_platformMenu)
2794 return;
2795
2796 m_platformMenu->setVisible(false);
2797 int count = int(m_platformMenu->tag());
2798 for (int i = 0; i < count; ++i)
2799 m_platformMenu->menuItemAt(i)->deleteLater();
2800
2801 delete m_platformMenu;
2802 m_platformMenu = nullptr;
2803}
2804
2805/*!
2806 * \internal
2807 *
2808 * Tries to show a native popup. Returns true if it could, false otherwise.
2809 *
2810 */
2811bool QComboBoxPrivate::showNativePopup()
2812{
2813 Q_Q(QComboBox);
2814
2815 cleanupNativePopup();
2816
2817 QPlatformTheme *theme = QGuiApplicationPrivate::instance()->platformTheme();
2818 m_platformMenu = theme->createPlatformMenu();
2819 if (!m_platformMenu)
2820 return false;
2821
2822 int itemsCount = q->count();
2823 m_platformMenu->setTag(quintptr(itemsCount));
2824
2825 QPlatformMenuItem *currentItem = nullptr;
2826 int currentIndex = q->currentIndex();
2827
2828 for (int i = 0; i < itemsCount; ++i) {
2829 QPlatformMenuItem *item = theme->createPlatformMenuItem();
2830 QModelIndex rowIndex = model->index(i, modelColumn, root);
2831 QVariant textVariant = model->data(rowIndex, Qt::EditRole);
2832 item->setText(textVariant.toString());
2833 QVariant iconVariant = model->data(rowIndex, Qt::DecorationRole);
2834 const Qt::ItemFlags itemFlags = model->flags(rowIndex);
2835 if (iconVariant.canConvert<QIcon>())
2836 item->setIcon(iconVariant.value<QIcon>());
2837 item->setCheckable(true);
2838 item->setChecked(i == currentIndex);
2839 item->setEnabled(itemFlags & Qt::ItemIsEnabled);
2840 if (!currentItem || i == currentIndex)
2841 currentItem = item;
2842
2843 IndexSetter setter = { i, q };
2844 QObject::connect(item, &QPlatformMenuItem::activated, q, setter);
2845
2846 m_platformMenu->insertMenuItem(item, 0);
2847 m_platformMenu->syncMenuItem(item);
2848 }
2849
2850 QWindow *tlw = q->window()->windowHandle();
2851 m_platformMenu->setFont(q->font());
2852 m_platformMenu->setMinimumWidth(q->rect().width());
2853 QPoint offset = QPoint(0, 7);
2854 if (q->testAttribute(Qt::WA_MacSmallSize))
2855 offset = QPoint(-1, 7);
2856 else if (q->testAttribute(Qt::WA_MacMiniSize))
2857 offset = QPoint(-2, 6);
2858
2859 [[maybe_unused]] QPointer<QComboBox> guard(q);
2860 const QRect targetRect = QRect(tlw->mapFromGlobal(q->mapToGlobal(offset)), QSize());
2861 m_platformMenu->showPopup(tlw, QHighDpi::toNativePixels(targetRect, tlw), currentItem);
2862
2863#ifdef Q_OS_MACOS
2864 if (guard) {
2865 // The Cocoa popup will swallow any mouse release event.
2866 // We need to fake one here to un-press the button.
2867 QMouseEvent mouseReleased(QEvent::MouseButtonRelease, q->pos(), q->mapToGlobal(QPoint(0, 0)),
2868 Qt::LeftButton, Qt::MouseButtons(Qt::LeftButton), {});
2869 QCoreApplication::sendEvent(q, &mouseReleased);
2870 }
2871#endif
2872
2873 return true;
2874}
2875
2876#endif // Q_OS_MACOS
2877
2878/*!
2879 Displays the list of items in the combobox. If the list is empty
2880 then no items will be shown.
2881
2882 If you reimplement this function to show a custom pop-up, make
2883 sure you call hidePopup() to reset the internal state.
2884
2885 \sa hidePopup()
2886*/
2887void QComboBox::showPopup()
2888{
2889 Q_D(QComboBox);
2890 if (count() <= 0)
2891 return;
2892
2893 QStyle * const style = this->style();
2894 QStyleOptionComboBox opt;
2895 initStyleOption(&opt);
2896 const bool usePopup = style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this);
2897
2898#ifdef Q_OS_MACOS
2899 if (usePopup
2900 && (!d->container
2901 || (qobject_cast<QComboBoxListView*>(view())
2902 && qobject_cast<QComboMenuDelegate*>(view()->itemDelegate())))
2903 && style->styleHint(QStyle::SH_ComboBox_UseNativePopup, &opt, this)
2904 && d->showNativePopup())
2905 return;
2906#endif // Q_OS_MACOS
2907
2908 QComboBoxPrivateContainer* container = d->viewContainer();
2909 QRect listRect(style->subControlRect(QStyle::CC_ComboBox, &opt,
2910 QStyle::SC_ComboBoxListBoxPopup, this));
2911 QRect screen = d->popupGeometry(mapToGlobal(listRect.topLeft()));
2912
2913 QPoint below = mapToGlobal(listRect.bottomLeft());
2914 int belowHeight = screen.bottom() - below.y();
2915 QPoint above = mapToGlobal(listRect.topLeft());
2916 int aboveHeight = above.y() - screen.y();
2917 bool boundToScreen = !window()->testAttribute(Qt::WA_DontShowOnScreen);
2918 const auto listView = qobject_cast<QListView *>(d->viewContainer()->itemView());
2919
2920 {
2921 int listHeight = 0;
2922 int count = 0;
2923 QStack<QModelIndex> toCheck;
2924 toCheck.push(view()->rootIndex());
2925#if QT_CONFIG(treeview)
2926 QTreeView *treeView = qobject_cast<QTreeView*>(view());
2927 if (treeView && treeView->header() && !treeView->header()->isHidden())
2928 listHeight += treeView->header()->height();
2929#endif
2930 while (!toCheck.isEmpty()) {
2931 QModelIndex parent = toCheck.pop();
2932 for (int i = 0, end = d->model->rowCount(parent); i < end; ++i) {
2933 if (listView && listView->isRowHidden(i))
2934 continue;
2935 QModelIndex idx = d->model->index(i, d->modelColumn, parent);
2936 if (!idx.isValid())
2937 continue;
2938 listHeight += view()->visualRect(idx).height();
2939#if QT_CONFIG(treeview)
2940 if (d->model->hasChildren(idx) && treeView && treeView->isExpanded(idx))
2941 toCheck.push(idx);
2942#endif
2943 ++count;
2944 if (!usePopup && count >= d->maxVisibleItems) {
2945 toCheck.clear();
2946 break;
2947 }
2948 }
2949 }
2950 if (count > 1)
2951 listHeight += (count - 1) * container->spacing();
2952 listRect.setHeight(listHeight);
2953 }
2954
2955 {
2956 // add the spacing for the grid on the top and the bottom;
2957 int heightMargin = container->topMargin() + container->bottomMargin();
2958
2959 // add the frame of the container
2960 const QMargins cm = container->contentsMargins();
2961 heightMargin += cm.top() + cm.bottom();
2962
2963 //add the frame of the view
2964 const QMargins vm = view()->contentsMargins();
2965 heightMargin += vm.top() + vm.bottom();
2966 heightMargin += static_cast<QAbstractScrollAreaPrivate *>(QObjectPrivate::get(view()))->top;
2967 heightMargin += static_cast<QAbstractScrollAreaPrivate *>(QObjectPrivate::get(view()))->bottom;
2968
2969 listRect.setHeight(listRect.height() + heightMargin);
2970 }
2971
2972 // Add space for margin at top and bottom if the style wants it.
2973 if (usePopup)
2974 listRect.setHeight(listRect.height() + style->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) * 2);
2975
2976 // Make sure the popup is wide enough to display its contents.
2977 if (usePopup) {
2978 const int diff = d->computeWidthHint() - width();
2979 if (diff > 0)
2980 listRect.setWidth(listRect.width() + diff);
2981 }
2982
2983 //we need to activate the layout to make sure the min/maximum size are set when the widget was not yet show
2984 container->layout()->activate();
2985 //takes account of the minimum/maximum size of the container
2986 listRect.setSize( listRect.size().expandedTo(container->minimumSize())
2987 .boundedTo(container->maximumSize()));
2988
2989 // make sure the widget fits on screen
2990 if (boundToScreen) {
2991 if (listRect.width() > screen.width() )
2992 listRect.setWidth(screen.width());
2993 if (mapToGlobal(listRect.bottomRight()).x() > screen.right()) {
2994 below.setX(screen.x() + screen.width() - listRect.width());
2995 above.setX(screen.x() + screen.width() - listRect.width());
2996 }
2997 if (mapToGlobal(listRect.topLeft()).x() < screen.x() ) {
2998 below.setX(screen.x());
2999 above.setX(screen.x());
3000 }
3001 }
3002
3003 if (usePopup) {
3004 // Position horizontally.
3005 listRect.moveLeft(above.x());
3006
3007 // Position vertically so the currently selected item lines up
3008 // with the combo box. In order to do that, make sure that the item
3009 // view is scrolled to the top first, otherwise calls to view()->visualRect()
3010 // will return the geometry the selected item had the last time the popup
3011 // was visible (and perhaps scrolled). And this will not match the geometry
3012 // it will actually have when we resize the container to fit all the items
3013 // further down in this function.
3014 view()->scrollToTop();
3015 const QRect currentItemRect = view()->visualRect(view()->currentIndex());
3016 const int offset = listRect.top() - currentItemRect.top();
3017 listRect.moveTop(above.y() + offset - listRect.top());
3018
3019 // Clamp the listRect height and vertical position so we don't expand outside the
3020 // available screen geometry.This may override the vertical position, but it is more
3021 // important to show as much as possible of the popup.
3022 const int height = !boundToScreen ? listRect.height() : qMin(listRect.height(), screen.height());
3023 listRect.setHeight(height);
3024
3025 if (boundToScreen) {
3026 if (listRect.top() < screen.top())
3027 listRect.moveTop(screen.top());
3028 if (listRect.bottom() > screen.bottom())
3029 listRect.moveBottom(screen.bottom());
3030 }
3031 } else if (!boundToScreen || listRect.height() <= belowHeight) {
3032 listRect.moveTopLeft(below);
3033 } else if (listRect.height() <= aboveHeight) {
3034 listRect.moveBottomLeft(above);
3035 } else if (belowHeight >= aboveHeight) {
3036 listRect.setHeight(belowHeight);
3037 listRect.moveTopLeft(below);
3038 } else {
3039 listRect.setHeight(aboveHeight);
3040 listRect.moveBottomLeft(above);
3041 }
3042
3043 if (qApp) {
3044 QGuiApplication::inputMethod()->reset();
3045 }
3046
3047 const QScrollBar *sb = view()->horizontalScrollBar();
3048 const auto needHorizontalScrollBar = [this, sb]{
3049 const Qt::ScrollBarPolicy policy = view()->horizontalScrollBarPolicy();
3050 return (policy == Qt::ScrollBarAsNeeded || policy == Qt::ScrollBarAlwaysOn)
3051 && sb->minimum() < sb->maximum();
3052 };
3053 const bool neededHorizontalScrollBar = needHorizontalScrollBar();
3054 if (neededHorizontalScrollBar)
3055 listRect.adjust(0, 0, 0, sb->height());
3056
3057 // Hide the scrollers here, so that the listrect gets the full height of the container
3058 // If the scrollers are truly needed, the later call to container->updateScrollers()
3059 // will make them visible again.
3060 container->hideScrollers();
3061 container->setGeometry(listRect);
3062
3063#ifndef Q_OS_MACOS
3064 const bool updatesEnabled = container->updatesEnabled();
3065#endif
3066
3067#if QT_CONFIG(effects)
3068 bool scrollDown = (listRect.topLeft() == below);
3069 if (QApplication::isEffectEnabled(Qt::UI_AnimateCombo)
3070 && !style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) && !window()->testAttribute(Qt::WA_DontShowOnScreen))
3071 qScrollEffect(container, scrollDown ? QEffects::DownScroll : QEffects::UpScroll, 150);
3072#endif
3073
3074// Don't disable updates on OS X. Windows are displayed immediately on this platform,
3075// which means that the window will be visible before the call to container->show() returns.
3076// If updates are disabled at this point we'll miss our chance at painting the popup
3077// menu before it's shown, causing flicker since the window then displays the standard gray
3078// background.
3079#ifndef Q_OS_MACOS
3080 container->setUpdatesEnabled(false);
3081#endif
3082
3083 bool startTimer = !container->isVisible();
3084 container->raise();
3085 container->create();
3086 if (QWindow *containerWindow = qt_widget_private(container)->windowHandle(QWidgetPrivate::WindowHandleMode::TopLevel)) {
3087 QScreen *currentScreen = d->associatedScreen();
3088 if (currentScreen && !currentScreen->virtualSiblings().contains(containerWindow->screen())) {
3089 containerWindow->setScreen(currentScreen);
3090
3091 // This seems to workaround an issue in xcb+multi GPU+multiscreen
3092 // environment where the window might not always show up when screen
3093 // is changed.
3094 container->hide();
3095 }
3096 }
3097
3098#if QT_CONFIG(wayland)
3099 if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(container->windowHandle()->handle())) {
3100 const QRect popup(style->subControlRect(QStyle::CC_ComboBox, &opt,
3101 QStyle::SC_ComboBoxListBoxPopup, this));
3102 const QRect controlGeometry = QRect(mapTo(window(), popup.topLeft()), popup.size());
3103 waylandWindow->setParentControlGeometry(controlGeometry);
3104 waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ComboBox);
3105 }
3106#endif
3107
3108 container->show();
3109 if (!neededHorizontalScrollBar && needHorizontalScrollBar()) {
3110 listRect.adjust(0, 0, 0, sb->height());
3111 container->setGeometry(listRect);
3112 }
3113
3114 container->updateScrollers();
3115 view()->setFocus();
3116
3117 view()->scrollTo(view()->currentIndex(),
3118 style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this)
3119 ? QAbstractItemView::PositionAtCenter
3120 : QAbstractItemView::EnsureVisible);
3121
3122#ifndef Q_OS_MACOS
3123 container->setUpdatesEnabled(updatesEnabled);
3124#endif
3125
3126 container->update();
3127 if (startTimer) {
3128 container->popupTimer.start();
3129 container->maybeIgnoreMouseButtonRelease = true;
3130 }
3131}
3132
3133/*!
3134 Hides the list of items in the combobox if it is currently visible
3135 and resets the internal state, so that if the custom pop-up was
3136 shown inside the reimplemented showPopup(), then you also need to
3137 reimplement the hidePopup() function to hide your custom pop-up
3138 and call the base class implementation to reset the internal state
3139 whenever your custom pop-up widget is hidden.
3140
3141 \sa showPopup()
3142*/
3143void QComboBox::hidePopup()
3144{
3145 Q_D(QComboBox);
3146 if (d->hidingPopup)
3147 return;
3148 d->hidingPopup = true;
3149 // can't use QScopedValueRollback on a bitfield
3150 auto resetHidingPopup = qScopeGuard([d]{
3151 d->hidingPopup = false;
3152 });
3153
3154 if (!d->container || !d->container->isVisible())
3155 return;
3156
3157#if QT_CONFIG(effects)
3158 QItemSelectionModel *selectionModel = d->container->itemView()
3159 ? d->container->itemView()->selectionModel() : nullptr;
3160 // Flash selected/triggered item (if any) before hiding the popup.
3161 if (style()->styleHint(QStyle::SH_Menu_FlashTriggeredItem, nullptr, this) &&
3162 selectionModel && selectionModel->hasSelection()) {
3163 const QItemSelection selection = selectionModel->selection();
3164
3165 QTimer::singleShot(0, d->container, [d, selection, selectionModel]{
3166 QSignalBlocker modelBlocker(d->model);
3167 QSignalBlocker viewBlocker(d->container->itemView());
3168 QSignalBlocker containerBlocker(d->container);
3169
3170 // Deselect item and wait 60 ms.
3171 selectionModel->select(selection, QItemSelectionModel::Toggle);
3172 QTimer::singleShot(60, d->container, [d, selection, selectionModel]{
3173 QSignalBlocker modelBlocker(d->model);
3174 QSignalBlocker viewBlocker(d->container->itemView());
3175 QSignalBlocker containerBlocker(d->container);
3176 selectionModel->select(selection, QItemSelectionModel::Toggle);
3177 QTimer::singleShot(20, d->container, [d] {
3178 d->doHidePopup();
3179 });
3180 });
3181 });
3182 } else
3183#endif // QT_CONFIG(effects)
3184 {
3185 d->doHidePopup();
3186 }
3187}
3188
3189void QComboBoxPrivate::doHidePopup()
3190{
3191 if (container && container->isVisible())
3192 container->hide();
3193
3194 resetButton();
3195}
3196
3197void QComboBoxPrivate::updateCurrentText(const QString &text)
3198{
3199 if (text == currentText)
3200 return;
3201
3202 currentText = text;
3203 emit q_func()->currentTextChanged(text);
3204}
3205
3206/*!
3207 Clears the combobox, removing all items.
3208
3209 Note: If you have set an external model on the combobox this model
3210 will still be cleared when calling this function.
3211*/
3212void QComboBox::clear()
3213{
3214 Q_D(QComboBox);
3215 d->model->removeRows(0, d->model->rowCount(d->root), d->root);
3216#if QT_CONFIG(accessibility)
3217 QAccessibleValueChangeEvent event(this, QString());
3218 QAccessible::updateAccessibility(&event);
3219#endif
3220}
3221
3222/*!
3223 Clears the contents of the line edit used for editing in the combobox.
3224*/
3225void QComboBox::clearEditText()
3226{
3227 Q_D(QComboBox);
3228 if (d->lineEdit)
3229 d->lineEdit->clear();
3230#if QT_CONFIG(accessibility)
3231 QAccessibleValueChangeEvent event(this, QString());
3232 QAccessible::updateAccessibility(&event);
3233#endif
3234}
3235
3236/*!
3237 Sets the \a text in the combobox's text edit.
3238*/
3239void QComboBox::setEditText(const QString &text)
3240{
3241 Q_D(QComboBox);
3242 if (d->lineEdit)
3243 d->lineEdit->setText(text);
3244#if QT_CONFIG(accessibility)
3245 QAccessibleValueChangeEvent event(this, text);
3246 QAccessible::updateAccessibility(&event);
3247#endif
3248}
3249
3250/*!
3251 \reimp
3252*/
3253void QComboBox::focusInEvent(QFocusEvent *e)
3254{
3255 Q_D(QComboBox);
3256 update();
3257 if (d->lineEdit) {
3258 d->lineEdit->event(e);
3259#if QT_CONFIG(completer)
3260 if (d->lineEdit->completer())
3261 d->lineEdit->completer()->setWidget(this);
3262#endif
3263 }
3264}
3265
3266/*!
3267 \reimp
3268*/
3269void QComboBox::focusOutEvent(QFocusEvent *e)
3270{
3271 Q_D(QComboBox);
3272 update();
3273 if (d->lineEdit)
3274 d->lineEdit->event(e);
3275}
3276
3277/*! \reimp */
3278void QComboBox::changeEvent(QEvent *e)
3279{
3280 Q_D(QComboBox);
3281 switch (e->type()) {
3282 case QEvent::StyleChange:
3283 if (d->container)
3284 d->container->updateStyleSettings();
3285 d->updateDelegate();
3286
3287#ifdef Q_OS_MACOS
3288 case QEvent::MacSizeChange:
3289#endif
3290 d->sizeHint = QSize(); // invalidate size hint
3291 d->minimumSizeHint = QSize();
3292 d->updateLayoutDirection();
3293 if (d->lineEdit)
3294 d->updateLineEditGeometry();
3295 d->setLayoutItemMargins(QStyle::SE_ComboBoxLayoutItem);
3296
3297 if (e->type() == QEvent::MacSizeChange) {
3298 QPlatformTheme::Font f = QPlatformTheme::SystemFont;
3299 if (testAttribute(Qt::WA_MacSmallSize))
3300 f = QPlatformTheme::SmallFont;
3301 else if (testAttribute(Qt::WA_MacMiniSize))
3302 f = QPlatformTheme::MiniFont;
3303 if (const QFont *platformFont = QApplicationPrivate::platformTheme()->font(f)) {
3304 QFont f = font();
3305 f.setPointSizeF(platformFont->pointSizeF());
3306 setFont(f);
3307 }
3308 }
3309 // ### need to update scrollers etc. as well here
3310 break;
3311 case QEvent::EnabledChange:
3312 if (!isEnabled())
3313 hidePopup();
3314 break;
3315 case QEvent::PaletteChange: {
3316 d->updateViewContainerPaletteAndOpacity();
3317 break;
3318 }
3319 case QEvent::FontChange: {
3320 d->sizeHint = QSize(); // invalidate size hint
3321 d->viewContainer()->setFont(font());
3322 d->viewContainer()->itemView()->doItemsLayout();
3323 if (d->lineEdit)
3324 d->updateLineEditGeometry();
3325 break;
3326 }
3327 default:
3328 break;
3329 }
3330 QWidget::changeEvent(e);
3331}
3332
3333/*!
3334 \reimp
3335*/
3336void QComboBox::resizeEvent(QResizeEvent *)
3337{
3338 Q_D(QComboBox);
3339 d->updateLineEditGeometry();
3340}
3341
3342/*!
3343 \reimp
3344*/
3345void QComboBox::paintEvent(QPaintEvent *)
3346{
3347 Q_D(QComboBox);
3348 QStylePainter painter(this);
3349 painter.setPen(palette().color(QPalette::Text));
3350
3351 // draw the combobox frame, focusrect and selected etc.
3352 QStyleOptionComboBox opt;
3353 initStyleOption(&opt);
3354 painter.drawComplexControl(QStyle::CC_ComboBox, opt);
3355
3356 if (currentIndex() < 0 && !placeholderText().isEmpty()) {
3357 opt.palette.setBrush(QPalette::ButtonText, opt.palette.placeholderText());
3358 opt.currentText = placeholderText();
3359 }
3360
3361 // draw contents
3362 if (itemDelegate() && labelDrawingMode() == QComboBox::LabelDrawingMode::UseDelegate) {
3363 QStyleOptionViewItem itemOption;
3364 d->initViewItemOption(&itemOption);
3365 itemOption.rect = style()->subControlRect(QStyle::CC_ComboBox, &opt,
3366 QStyle::SC_ComboBoxEditField, this);
3367 itemDelegate()->paint(&painter, itemOption, d->currentIndex);
3368 } else {
3369 // draw the icon and text
3370 painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
3371 }
3372}
3373
3374/*!
3375 \reimp
3376*/
3377void QComboBox::showEvent(QShowEvent *e)
3378{
3379 Q_D(QComboBox);
3380 if (!d->shownOnce && d->sizeAdjustPolicy == QComboBox::AdjustToContentsOnFirstShow) {
3381 d->sizeHint = QSize();
3382 updateGeometry();
3383 }
3384 d->shownOnce = true;
3385 QWidget::showEvent(e);
3386}
3387
3388/*!
3389 \reimp
3390*/
3391void QComboBox::hideEvent(QHideEvent *)
3392{
3393 hidePopup();
3394}
3395
3396/*!
3397 \reimp
3398*/
3399bool QComboBox::event(QEvent *event)
3400{
3401 Q_D(QComboBox);
3402 switch(event->type()) {
3403 case QEvent::LayoutDirectionChange:
3404 case QEvent::ApplicationLayoutDirectionChange:
3405 d->updateLayoutDirection();
3406 d->updateLineEditGeometry();
3407 break;
3408 case QEvent::HoverEnter:
3409 case QEvent::HoverLeave:
3410 case QEvent::HoverMove:
3411 if (const QHoverEvent *he = static_cast<const QHoverEvent *>(event))
3412 d->updateHoverControl(he->position().toPoint());
3413 break;
3414 case QEvent::ShortcutOverride:
3415 if (d->lineEdit)
3416 return d->lineEdit->event(event);
3417 break;
3418 default:
3419 break;
3420 }
3421 return QWidget::event(event);
3422}
3423
3424/*!
3425 \reimp
3426*/
3427void QComboBox::mousePressEvent(QMouseEvent *e)
3428{
3429 Q_D(QComboBox);
3430 if (!QGuiApplication::styleHints()->setFocusOnTouchRelease())
3431 d->showPopupFromMouseEvent(e);
3432}
3433
3434void QComboBoxPrivate::showPopupFromMouseEvent(QMouseEvent *e)
3435{
3436 Q_Q(QComboBox);
3437 QStyleOptionComboBox opt;
3438 q->initStyleOption(&opt);
3439 QStyle::SubControl sc = q->style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->position().toPoint(), q);
3440
3441 if (e->button() == Qt::LeftButton
3442 && !(sc == QStyle::SC_None && e->type() == QEvent::MouseButtonRelease)
3443 && (sc == QStyle::SC_ComboBoxArrow || !q->isEditable())
3444 && !viewContainer()->isVisible()) {
3445 if (sc == QStyle::SC_ComboBoxArrow)
3446 updateArrow(QStyle::State_Sunken);
3447 // We've restricted the next couple of lines, because by not calling
3448 // viewContainer(), we avoid creating the QComboBoxPrivateContainer.
3449 viewContainer()->initialClickPosition = q->mapToGlobal(e->position());
3450 QPointer<QComboBox> guard = q;
3451 q->showPopup();
3452 if (!guard)
3453 return;
3454 // The code below ensures that regular mousepress and pick item still works
3455 // If it was not called the viewContainer would ignore event since it didn't have
3456 // a mousePressEvent first.
3457 if (viewContainer()) {
3458 viewContainer()->blockMouseReleaseTimer.start(QApplication::doubleClickInterval());
3459 viewContainer()->maybeIgnoreMouseButtonRelease = false;
3460 }
3461 } else {
3462 e->ignore();
3463 }
3464}
3465
3466/*!
3467 \reimp
3468*/
3469void QComboBox::mouseReleaseEvent(QMouseEvent *e)
3470{
3471 Q_D(QComboBox);
3472 d->updateArrow(QStyle::State_None);
3473 if (QGuiApplication::styleHints()->setFocusOnTouchRelease() && hasFocus())
3474 d->showPopupFromMouseEvent(e);
3475}
3476
3477/*!
3478 \reimp
3479*/
3480void QComboBox::keyPressEvent(QKeyEvent *e)
3481{
3482 Q_D(QComboBox);
3483
3484#if QT_CONFIG(completer)
3485 if (const auto *cmpltr = completer()) {
3486 const auto *popup = QCompleterPrivate::get(cmpltr)->popup;
3487 if (popup && popup->isVisible()) {
3488 // provide same autocompletion support as line edit
3489 d->lineEdit->event(e);
3490 return;
3491 }
3492 }
3493#endif
3494
3495 enum Move { NoMove=0 , MoveUp , MoveDown , MoveFirst , MoveLast};
3496
3497 Move move = NoMove;
3498 int newIndex = currentIndex();
3499
3500 bool pressLikeButton = !d->lineEdit;
3501 auto key = e->key();
3502 if (pressLikeButton) {
3503 const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()
3504 ->themeHint(QPlatformTheme::ButtonPressKeys)
3505 .value<QList<Qt::Key>>();
3506 if (buttonPressKeys.contains(key)) {
3507 showPopup();
3508 return;
3509 }
3510 }
3511
3512 switch (key) {
3513 case Qt::Key_Up:
3514 if (e->modifiers() & Qt::ControlModifier)
3515 break; // pass to line edit for auto completion
3516 Q_FALLTHROUGH();
3517 case Qt::Key_PageUp:
3518 move = MoveUp;
3519 break;
3520 case Qt::Key_Down:
3521 if (e->modifiers() & Qt::AltModifier) {
3522 showPopup();
3523 return;
3524 } else if (e->modifiers() & Qt::ControlModifier)
3525 break; // pass to line edit for auto completion
3526 Q_FALLTHROUGH();
3527 case Qt::Key_PageDown:
3528 move = MoveDown;
3529 break;
3530 case Qt::Key_Home:
3531 if (!d->lineEdit)
3532 move = MoveFirst;
3533 break;
3534 case Qt::Key_End:
3535 if (!d->lineEdit)
3536 move = MoveLast;
3537 break;
3538 case Qt::Key_F4:
3539 if (!e->modifiers()) {
3540 showPopup();
3541 return;
3542 }
3543 break;
3544 case Qt::Key_Enter:
3545 case Qt::Key_Return:
3546 case Qt::Key_Escape:
3547 if (!d->lineEdit)
3548 e->ignore();
3549 break;
3550 default:
3551#if QT_CONFIG(shortcut)
3552 if (d->container && d->container->isVisible() && e->matches(QKeySequence::Cancel)) {
3553 hidePopup();
3554 e->accept();
3555 }
3556#endif
3557
3558 if (!d->lineEdit) {
3559 const auto text = e->text();
3560 if (!text.isEmpty() && text.at(0).isPrint())
3561 d->keyboardSearchString(text);
3562 else
3563 e->ignore();
3564 }
3565 }
3566
3567 const int rowCount = count();
3568
3569 if (move != NoMove) {
3570 e->accept();
3571 switch (move) {
3572 case MoveFirst:
3573 newIndex = -1;
3574 Q_FALLTHROUGH();
3575 case MoveDown:
3576 newIndex++;
3577 while (newIndex < rowCount && !(d->model->index(newIndex, d->modelColumn, d->root).flags() & Qt::ItemIsEnabled))
3578 newIndex++;
3579 break;
3580 case MoveLast:
3581 newIndex = rowCount;
3582 Q_FALLTHROUGH();
3583 case MoveUp:
3584 newIndex--;
3585 while ((newIndex >= 0) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled))
3586 newIndex--;
3587 break;
3588 default:
3589 e->ignore();
3590 break;
3591 }
3592
3593 if (newIndex >= 0 && newIndex < rowCount && newIndex != currentIndex()) {
3594 setCurrentIndex(newIndex);
3595 d->emitActivated(d->currentIndex);
3596 }
3597 } else if (d->lineEdit) {
3598 d->lineEdit->event(e);
3599 }
3600}
3601
3602
3603/*!
3604 \reimp
3605*/
3606void QComboBox::keyReleaseEvent(QKeyEvent *e)
3607{
3608 Q_D(QComboBox);
3609 if (d->lineEdit)
3610 d->lineEdit->event(e);
3611 else
3612 QWidget::keyReleaseEvent(e);
3613}
3614
3615/*!
3616 \reimp
3617*/
3618#if QT_CONFIG(wheelevent)
3619void QComboBox::wheelEvent(QWheelEvent *e)
3620{
3621 Q_D(QComboBox);
3622 QStyleOptionComboBox opt;
3623 initStyleOption(&opt);
3624 if (style()->styleHint(QStyle::SH_ComboBox_AllowWheelScrolling, &opt, this) &&
3625 !d->viewContainer()->isVisible()) {
3626 const int rowCount = count();
3627 int newIndex = currentIndex();
3628 int delta = e->angleDelta().y();
3629
3630 if (delta > 0) {
3631 newIndex--;
3632 while ((newIndex >= 0) && !(d->model->flags(d->model->index(newIndex,d->modelColumn,d->root)) & Qt::ItemIsEnabled))
3633 newIndex--;
3634 } else if (delta < 0) {
3635 newIndex++;
3636 while (newIndex < rowCount && !(d->model->index(newIndex, d->modelColumn, d->root).flags() & Qt::ItemIsEnabled))
3637 newIndex++;
3638 }
3639
3640 if (newIndex >= 0 && newIndex < rowCount && newIndex != currentIndex()) {
3641 setCurrentIndex(newIndex);
3642 d->emitActivated(d->currentIndex);
3643 }
3644 e->accept();
3645 } else {
3646 e->ignore();
3647 }
3648}
3649#endif
3650
3651#ifndef QT_NO_CONTEXTMENU
3652/*!
3653 \reimp
3654*/
3655void QComboBox::contextMenuEvent(QContextMenuEvent *e)
3656{
3657 Q_D(QComboBox);
3658 if (d->lineEdit) {
3659 Qt::ContextMenuPolicy p = d->lineEdit->contextMenuPolicy();
3660 d->lineEdit->setContextMenuPolicy(Qt::DefaultContextMenu);
3661 d->lineEdit->event(e);
3662 d->lineEdit->setContextMenuPolicy(p);
3663 }
3664}
3665#endif // QT_NO_CONTEXTMENU
3666
3667void QComboBoxPrivate::keyboardSearchString(const QString &text)
3668{
3669 // use keyboardSearch from the listView so we do not duplicate code
3670 QAbstractItemView *view = viewContainer()->itemView();
3671 view->setCurrentIndex(currentIndex);
3672 int currentRow = view->currentIndex().row();
3673 view->keyboardSearch(text);
3674 if (currentRow != view->currentIndex().row()) {
3675 setCurrentIndex(view->currentIndex());
3676 emitActivated(currentIndex);
3677 }
3678}
3679
3680void QComboBoxPrivate::modelChanged()
3681{
3682 Q_Q(QComboBox);
3683
3684 if (sizeAdjustPolicy == QComboBox::AdjustToContents) {
3685 sizeHint = QSize();
3686 adjustComboBoxSize();
3687 q->updateGeometry();
3688 }
3689}
3690
3691/*!
3692 \reimp
3693*/
3694void QComboBox::inputMethodEvent(QInputMethodEvent *e)
3695{
3696 Q_D(QComboBox);
3697 if (d->lineEdit) {
3698 d->lineEdit->event(e);
3699 } else {
3700 if (!e->commitString().isEmpty())
3701 d->keyboardSearchString(e->commitString());
3702 else
3703 e->ignore();
3704 }
3705}
3706
3707/*!
3708 \reimp
3709*/
3710QVariant QComboBox::inputMethodQuery(Qt::InputMethodQuery query) const
3711{
3712 Q_D(const QComboBox);
3713 if (d->lineEdit)
3714 return d->lineEdit->inputMethodQuery(query);
3715 return QWidget::inputMethodQuery(query);
3716}
3717
3718/*!\internal
3719*/
3720QVariant QComboBox::inputMethodQuery(Qt::InputMethodQuery query, const QVariant &argument) const
3721{
3722 Q_D(const QComboBox);
3723 if (d->lineEdit)
3724 return d->lineEdit->inputMethodQuery(query, argument);
3725 return QWidget::inputMethodQuery(query);
3726}
3727
3728/*!
3729 \fn void QComboBox::addItem(const QString &text, const QVariant &userData)
3730
3731 Adds an item to the combobox with the given \a text, and
3732 containing the specified \a userData (stored in the Qt::UserRole).
3733 The item is appended to the list of existing items.
3734*/
3735
3736/*!
3737 \fn void QComboBox::addItem(const QIcon &icon, const QString &text,
3738 const QVariant &userData)
3739
3740 Adds an item to the combobox with the given \a icon and \a text,
3741 and containing the specified \a userData (stored in the
3742 Qt::UserRole). The item is appended to the list of existing items.
3743*/
3744
3745/*!
3746 \fn void QComboBox::addItems(const QStringList &texts)
3747
3748 Adds each of the strings in the given \a texts to the combobox. Each item
3749 is appended to the list of existing items in turn.
3750*/
3751
3752/*!
3753 \fn void QComboBox::editTextChanged(const QString &text)
3754
3755 This signal is emitted when the text in the combobox's line edit
3756 widget is changed. The new text is specified by \a text.
3757*/
3758
3759/*!
3760 \property QComboBox::frame
3761 \brief whether the combo box draws itself with a frame.
3762
3763
3764 If enabled (the default) the combo box draws itself inside a
3765 frame, otherwise the combo box draws itself without any frame.
3766*/
3767bool QComboBox::hasFrame() const
3768{
3769 Q_D(const QComboBox);
3770 return d->frame;
3771}
3772
3773
3774void QComboBox::setFrame(bool enable)
3775{
3776 Q_D(QComboBox);
3777 d->frame = enable;
3778 update();
3779 updateGeometry();
3780}
3781
3782/*!
3783 \property QComboBox::modelColumn
3784 \brief the column in the model that is visible.
3785
3786 If set prior to populating the combo box, the pop-up view will
3787 not be affected and will show the first column (using this property's
3788 default value).
3789
3790 By default, this property has a value of 0.
3791
3792 \note In an editable combobox, the visible column will also become
3793 the \l{QCompleter::completionColumn}{completion column}.
3794*/
3795int QComboBox::modelColumn() const
3796{
3797 Q_D(const QComboBox);
3798 return d->modelColumn;
3799}
3800
3801void QComboBox::setModelColumn(int visibleColumn)
3802{
3803 Q_D(QComboBox);
3804 d->modelColumn = visibleColumn;
3805 QListView *lv = qobject_cast<QListView *>(d->viewContainer()->itemView());
3806 if (lv)
3807 lv->setModelColumn(visibleColumn);
3808#if QT_CONFIG(completer)
3809 if (d->lineEdit && d->lineEdit->completer())
3810 d->lineEdit->completer()->setCompletionColumn(visibleColumn);
3811#endif
3812 setCurrentIndex(currentIndex()); //update the text to the text of the new column;
3813}
3814
3815/*!
3816 \enum QComboBox::LabelDrawingMode
3817 \since 6.9
3818
3819 This enum specifies how the combobox draws its label.
3820
3821 \value UseStyle The combobox uses the \l{QStyle}{style} to draw its label.
3822 \value UseDelegate The combobox uses the \l{itemDelegate()}{item delegate} to
3823 draw the label. Set a suitable item delegate when using this mode.
3824
3825 \sa labelDrawingMode, {Books}{Books example}
3826*/
3827
3828/*!
3829 \property QComboBox::labelDrawingMode
3830 \since 6.9
3831
3832 \brief the mode used by the combobox to draw its label.
3833
3834 The default value is \l{QComboBox::}{UseStyle}. When changing this property
3835 to UseDelegate, make sure to also set a suitable \l{itemDelegate()}{item delegate}.
3836 The default delegate depends on the style and might not be suitable for
3837 drawing the label.
3838
3839 \sa {Books}{Books example}
3840*/
3841QComboBox::LabelDrawingMode QComboBox::labelDrawingMode() const
3842{
3843 Q_D(const QComboBox);
3844 return d->labelDrawingMode;
3845}
3846
3847void QComboBox::setLabelDrawingMode(LabelDrawingMode drawingLabel)
3848{
3849 Q_D(QComboBox);
3850 if (d->labelDrawingMode != drawingLabel) {
3851 d->labelDrawingMode = drawingLabel;
3852 update();
3853 }
3854}
3855
3856QT_END_NAMESPACE
3857
3858#include "moc_qcombobox.cpp"
3859#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