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

source code of qtbase/src/widgets/widgets/qcombobox.cpp