1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QCOMBOBOX_P_H
5#define QCOMBOBOX_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtWidgets/private/qtwidgetsglobal_p.h>
19#include "QtWidgets/qcombobox.h"
20
21#include "QtWidgets/qabstractslider.h"
22#include "QtWidgets/qapplication.h"
23#include "QtWidgets/qstyleditemdelegate.h"
24#include "QtGui/qstandarditemmodel.h"
25#include "QtWidgets/qlineedit.h"
26#include "QtWidgets/qlistview.h"
27#include "QtGui/qpainter.h"
28#include "QtWidgets/qstyle.h"
29#include "QtWidgets/qstyleoption.h"
30#include "QtCore/qtimer.h"
31#include "private/qwidget_p.h"
32#include "QtCore/qpointer.h"
33#if QT_CONFIG(completer)
34#include "QtWidgets/qcompleter.h"
35#endif
36#include "QtGui/qevent.h"
37
38#include <limits.h>
39
40QT_REQUIRE_CONFIG(combobox);
41
42QT_BEGIN_NAMESPACE
43
44class QPlatformMenu;
45
46class QComboBoxListView : public QListView
47{
48 Q_OBJECT
49public:
50 QComboBoxListView(QComboBox *cmb = nullptr) : combo(cmb)
51 {
52 if (cmb)
53 setScreen(cmb->screen());
54 }
55
56protected:
57 void resizeEvent(QResizeEvent *event) override
58 {
59 resizeContents(width: viewport()->width(), height: contentsSize().height());
60 QListView::resizeEvent(e: event);
61 }
62
63 void initViewItemOption(QStyleOptionViewItem *option) const override
64 {
65 QListView::initViewItemOption(option);
66 option->showDecorationSelected = true;
67 if (combo)
68 option->font = combo->font();
69 }
70
71 void paintEvent(QPaintEvent *e) override
72 {
73 if (combo) {
74 QStyleOptionComboBox opt;
75 opt.initFrom(w: combo);
76 opt.editable = combo->isEditable();
77 if (combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo)) {
78 //we paint the empty menu area to avoid having blank space that can happen when scrolling
79 QStyleOptionMenuItem menuOpt;
80 menuOpt.initFrom(w: this);
81 menuOpt.palette = palette();
82 menuOpt.state = QStyle::State_None;
83 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
84 menuOpt.menuRect = e->rect();
85 menuOpt.maxIconWidth = 0;
86 menuOpt.reservedShortcutWidth = 0;
87 QPainter p(viewport());
88 combo->style()->drawControl(element: QStyle::CE_MenuEmptyArea, opt: &menuOpt, p: &p, w: this);
89 }
90 }
91 QListView::paintEvent(e);
92 }
93
94private:
95 QComboBox *combo;
96};
97
98class Q_AUTOTEST_EXPORT QComboBoxPrivateScroller : public QWidget
99{
100 Q_OBJECT
101
102public:
103 QComboBoxPrivateScroller(QAbstractSlider::SliderAction action, QWidget *parent)
104 : QWidget(parent), sliderAction(action)
105 {
106 setSizePolicy(hor: QSizePolicy::Minimum, ver: QSizePolicy::Fixed);
107 setAttribute(Qt::WA_NoMousePropagation);
108 }
109 QSize sizeHint() const override {
110 return QSize(20, style()->pixelMetric(metric: QStyle::PM_MenuScrollerHeight, option: nullptr, widget: this));
111 }
112
113protected:
114 inline void stopTimer() {
115 timer.stop();
116 }
117
118 inline void startTimer() {
119 timer.start(msec: 100, obj: this);
120 fast = false;
121 }
122
123 void enterEvent(QEnterEvent *) override {
124 startTimer();
125 }
126
127 void leaveEvent(QEvent *) override {
128 stopTimer();
129 }
130 void timerEvent(QTimerEvent *e) override {
131 if (e->timerId() == timer.timerId()) {
132 emit doScroll(action: sliderAction);
133 if (fast) {
134 emit doScroll(action: sliderAction);
135 emit doScroll(action: sliderAction);
136 }
137 }
138 }
139 void hideEvent(QHideEvent *) override {
140 stopTimer();
141 }
142
143 void mouseMoveEvent(QMouseEvent *e) override
144 {
145 // Enable fast scrolling if the cursor is directly above or below the popup.
146 const int mouseX = e->position().toPoint().x();
147 const int mouseY = e->position().toPoint().y();
148 const bool horizontallyInside = pos().x() < mouseX && mouseX < rect().right() + 1;
149 const bool verticallyOutside = (sliderAction == QAbstractSlider::SliderSingleStepAdd) ?
150 rect().bottom() + 1 < mouseY : mouseY < pos().y();
151
152 fast = horizontallyInside && verticallyOutside;
153 }
154
155 void paintEvent(QPaintEvent *) override {
156 QPainter p(this);
157 QStyleOptionMenuItem menuOpt;
158 menuOpt.initFrom(w: this);
159 menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
160 menuOpt.menuRect = rect();
161 menuOpt.maxIconWidth = 0;
162 menuOpt.reservedShortcutWidth = 0;
163 menuOpt.menuItemType = QStyleOptionMenuItem::Scroller;
164 if (sliderAction == QAbstractSlider::SliderSingleStepAdd)
165 menuOpt.state |= QStyle::State_DownArrow;
166 p.eraseRect(rect: rect());
167 style()->drawControl(element: QStyle::CE_MenuScroller, opt: &menuOpt, p: &p);
168 }
169
170Q_SIGNALS:
171 void doScroll(int action);
172
173private:
174 QAbstractSlider::SliderAction sliderAction;
175 QBasicTimer timer;
176 bool fast = false;
177};
178
179class Q_WIDGETS_EXPORT QComboBoxPrivateContainer : public QFrame
180{
181 Q_OBJECT
182
183public:
184 QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent);
185 ~QComboBoxPrivateContainer();
186 QAbstractItemView *itemView() const;
187 void setItemView(QAbstractItemView *itemView);
188 int spacing() const;
189 int topMargin() const;
190 int bottomMargin() const { return topMargin(); }
191 void updateTopBottomMargin();
192 void updateStyleSettings();
193
194 QTimer blockMouseReleaseTimer;
195 QBasicTimer adjustSizeTimer;
196 QPoint initialClickPosition;
197
198public Q_SLOTS:
199 void scrollItemView(int action);
200 void hideScrollers();
201 void updateScrollers();
202 void viewDestroyed();
203
204protected:
205 void changeEvent(QEvent *e) override;
206 bool eventFilter(QObject *o, QEvent *e) override;
207 void mousePressEvent(QMouseEvent *e) override;
208 void mouseReleaseEvent(QMouseEvent *e) override;
209 void showEvent(QShowEvent *e) override;
210 void hideEvent(QHideEvent *e) override;
211 void timerEvent(QTimerEvent *timerEvent) override;
212 void resizeEvent(QResizeEvent *e) override;
213 void paintEvent(QPaintEvent *e) override;
214 QStyleOptionComboBox comboStyleOption() const;
215
216Q_SIGNALS:
217 void itemSelected(const QModelIndex &);
218 void resetButton();
219
220private:
221 QComboBox *combo;
222 QAbstractItemView *view = nullptr;
223 QComboBoxPrivateScroller *top = nullptr;
224 QComboBoxPrivateScroller *bottom = nullptr;
225 QElapsedTimer popupTimer;
226 bool maybeIgnoreMouseButtonRelease = false;
227
228 friend class QComboBox;
229 friend class QComboBoxPrivate;
230};
231
232class Q_AUTOTEST_EXPORT QComboMenuDelegate : public QAbstractItemDelegate
233{
234 Q_OBJECT
235public:
236 QComboMenuDelegate(QObject *parent, QComboBox *cmb)
237 : QAbstractItemDelegate(parent), mCombo(cmb), pressedIndex(-1)
238 {}
239
240protected:
241 void paint(QPainter *painter,
242 const QStyleOptionViewItem &option,
243 const QModelIndex &index) const override {
244 QStyleOptionMenuItem opt = getStyleOption(option, index);
245 painter->fillRect(option.rect, opt.palette.window());
246 mCombo->style()->drawControl(element: QStyle::CE_MenuItem, opt: &opt, p: painter, w: mCombo);
247 }
248 QSize sizeHint(const QStyleOptionViewItem &option,
249 const QModelIndex &index) const override {
250 QStyleOptionMenuItem opt = getStyleOption(option, index);
251 return mCombo->style()->sizeFromContents(
252 ct: QStyle::CT_MenuItem, opt: &opt, contentsSize: option.rect.size(), w: mCombo);
253 }
254 bool editorEvent(QEvent *event, QAbstractItemModel *model,
255 const QStyleOptionViewItem &option, const QModelIndex &index) override;
256
257private:
258 QStyleOptionMenuItem getStyleOption(const QStyleOptionViewItem &option,
259 const QModelIndex &index) const;
260 QComboBox *mCombo;
261 int pressedIndex;
262};
263
264class Q_AUTOTEST_EXPORT QComboBoxDelegate : public QStyledItemDelegate
265{
266 Q_OBJECT
267public:
268 QComboBoxDelegate(QObject *parent, QComboBox *cmb)
269 : QStyledItemDelegate(parent), mCombo(cmb)
270 {}
271
272 static bool isSeparator(const QModelIndex &index) {
273 return index.data(arole: Qt::AccessibleDescriptionRole).toString()
274 == QLatin1StringView("separator");
275 }
276 static void setSeparator(QAbstractItemModel *model, const QModelIndex &index) {
277 model->setData(index, value: QString::fromLatin1(ba: "separator"), role: Qt::AccessibleDescriptionRole);
278 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(object: model))
279 if (QStandardItem *item = m->itemFromIndex(index))
280 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
281 }
282
283protected:
284 void paint(QPainter *painter,
285 const QStyleOptionViewItem &option,
286 const QModelIndex &index) const override {
287 if (isSeparator(index)) {
288 QRect rect = option.rect;
289 if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(object: option.widget))
290 rect.setWidth(view->viewport()->width());
291 QStyleOption opt;
292 opt.rect = rect;
293 mCombo->style()->drawPrimitive(pe: QStyle::PE_IndicatorToolBarSeparator, opt: &opt, p: painter, w: mCombo);
294 } else {
295 QStyledItemDelegate::paint(painter, option, index);
296 }
297 }
298
299 QSize sizeHint(const QStyleOptionViewItem &option,
300 const QModelIndex &index) const override {
301 if (isSeparator(index)) {
302 int pm = mCombo->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth, option: nullptr, widget: mCombo);
303 return QSize(pm, pm);
304 }
305 return QStyledItemDelegate::sizeHint(option, index);
306 }
307private:
308 QComboBox *mCombo;
309};
310
311class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate
312{
313 Q_DECLARE_PUBLIC(QComboBox)
314public:
315 QComboBoxPrivate();
316 ~QComboBoxPrivate();
317 void init();
318 QComboBoxPrivateContainer* viewContainer();
319 void updateLineEditGeometry();
320 Qt::MatchFlags matchFlags() const;
321 void editingFinished();
322 void returnPressed();
323 void complete();
324 void itemSelected(const QModelIndex &item);
325 bool contains(const QString &text, int role);
326 void emitActivated(const QModelIndex &index);
327 void emitHighlighted(const QModelIndex &index);
328 void emitCurrentIndexChanged(const QModelIndex &index);
329 void modelDestroyed();
330 void modelReset();
331 void updateMicroFocus() { q_func()->updateMicroFocus(); } // PMF connect doesn't handle default args
332#if QT_CONFIG(completer)
333 void completerActivated(const QModelIndex &index);
334#endif
335 void resetButton();
336 void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
337 void updateIndexBeforeChange();
338 void rowsInserted(const QModelIndex &parent, int start, int end);
339 void rowsRemoved(const QModelIndex &parent, int start, int end);
340 void updateArrow(QStyle::StateFlag state);
341 bool updateHoverControl(const QPoint &pos);
342 void trySetValidIndex();
343 QRect popupGeometry(const QPoint &globalPos) const;
344 QStyle::SubControl newHoverControl(const QPoint &pos);
345 int computeWidthHint() const;
346 QSize recomputeSizeHint(QSize &sh) const;
347 void adjustComboBoxSize();
348 QString itemText(const QModelIndex &index) const;
349 QIcon itemIcon(const QModelIndex &index) const;
350 int itemRole() const;
351 void updateLayoutDirection();
352 void setCurrentIndex(const QModelIndex &index);
353 void updateDelegate(bool force = false);
354 void initViewItemOption(QStyleOptionViewItem *option) const;
355 void keyboardSearchString(const QString &text);
356 void modelChanged();
357 void updateViewContainerPaletteAndOpacity();
358 void updateFocusPolicy();
359 void showPopupFromMouseEvent(QMouseEvent *e);
360 void doHidePopup();
361 void updateCurrentText(const QString &text);
362 void connectModel();
363 void disconnectModel();
364
365#ifdef Q_OS_MAC
366 void cleanupNativePopup();
367 bool showNativePopup();
368 struct IndexSetter {
369 int index;
370 QComboBox *cb;
371
372 void operator()(void)
373 {
374 cb->setCurrentIndex(index);
375 cb->d_func()->emitActivated(cb->d_func()->currentIndex);
376 }
377 };
378#endif
379
380 std::array<QMetaObject::Connection, 8> modelConnections;
381 QAbstractItemModel *model = nullptr;
382 QLineEdit *lineEdit = nullptr;
383 QPointer<QComboBoxPrivateContainer> container;
384#ifdef Q_OS_MAC
385 QPlatformMenu *m_platformMenu = nullptr;
386#endif
387 QPersistentModelIndex currentIndex;
388 QPersistentModelIndex root;
389 QString placeholderText;
390 QString currentText;
391 QRect hoverRect;
392 QSize iconSize;
393 mutable QSize minimumSizeHint;
394 mutable QSize sizeHint;
395 QComboBox::InsertPolicy insertPolicy = QComboBox::InsertAtBottom;
396 QComboBox::SizeAdjustPolicy sizeAdjustPolicy = QComboBox::AdjustToContentsOnFirstShow;
397 QStyle::StateFlag arrowState = QStyle::State_None;
398 QStyle::SubControl hoverControl = QStyle::SC_None;
399 QComboBox::LabelDrawingMode labelDrawingMode = QComboBox::LabelDrawingMode::UseStyle;
400 int minimumContentsLength = 0;
401 int indexBeforeChange = -1;
402 int maxVisibleItems = 10;
403 int maxCount = (std::numeric_limits<int>::max)();
404 int modelColumn = 0;
405 int placeholderIndex = -1;
406 bool shownOnce : 1;
407 bool duplicatesEnabled : 1;
408 bool frame : 1;
409 bool inserting : 1;
410 bool hidingPopup : 1;
411};
412
413QT_END_NAMESPACE
414
415#endif // QCOMBOBOX_P_H
416

source code of qtbase/src/widgets/widgets/qcombobox_p.h