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