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