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