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

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