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

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