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(viewport()->width(), contentsSize().height());
94 QListView::resizeEvent(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(combo);
111 opt.editable = combo->isEditable();
112 if (combo->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, combo)) {
113 //we paint the empty menu area to avoid having blank space that can happen when scrolling
114 QStyleOptionMenuItem menuOpt;
115 menuOpt.initFrom(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(QStyle::CE_MenuEmptyArea, &menuOpt, &p, 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(QSizePolicy::Minimum, QSizePolicy::Fixed);
142 setAttribute(Qt::WA_NoMousePropagation);
143 }
144 QSize sizeHint() const override {
145 return QSize(20, style()->pixelMetric(QStyle::PM_MenuScrollerHeight, nullptr, this));
146 }
147
148protected:
149 inline void stopTimer() {
150 timer.stop();
151 }
152
153 inline void startTimer() {
154 timer.start(100, 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(sliderAction);
168 if (fast) {
169 emit doScroll(sliderAction);
170 emit doScroll(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(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());
202 style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &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
227 QTimer blockMouseReleaseTimer;
228 QBasicTimer adjustSizeTimer;
229 QPoint initialClickPosition;
230
231public Q_SLOTS:
232 void scrollItemView(int action);
233 void hideScrollers();
234 void updateScrollers();
235 void viewDestroyed();
236
237protected:
238 void changeEvent(QEvent *e) override;
239 bool eventFilter(QObject *o, QEvent *e) override;
240 void mousePressEvent(QMouseEvent *e) override;
241 void mouseReleaseEvent(QMouseEvent *e) override;
242 void showEvent(QShowEvent *e) override;
243 void hideEvent(QHideEvent *e) override;
244 void timerEvent(QTimerEvent *timerEvent) override;
245 void resizeEvent(QResizeEvent *e) override;
246 void paintEvent(QPaintEvent *e) override;
247 QStyleOptionComboBox comboStyleOption() const;
248
249Q_SIGNALS:
250 void itemSelected(const QModelIndex &);
251 void resetButton();
252
253private:
254 QComboBox *combo;
255 QAbstractItemView *view = nullptr;
256 QComboBoxPrivateScroller *top = nullptr;
257 QComboBoxPrivateScroller *bottom = nullptr;
258 QElapsedTimer popupTimer;
259 bool maybeIgnoreMouseButtonRelease = false;
260
261 friend class QComboBox;
262 friend class QComboBoxPrivate;
263};
264
265class Q_AUTOTEST_EXPORT QComboMenuDelegate : public QAbstractItemDelegate
266{
267 Q_OBJECT
268public:
269 QComboMenuDelegate(QObject *parent, QComboBox *cmb)
270 : QAbstractItemDelegate(parent), mCombo(cmb), pressedIndex(-1)
271 {}
272
273protected:
274 void paint(QPainter *painter,
275 const QStyleOptionViewItem &option,
276 const QModelIndex &index) const override {
277 QStyleOptionMenuItem opt = getStyleOption(option, index);
278 painter->fillRect(option.rect, opt.palette.window());
279 mCombo->style()->drawControl(QStyle::CE_MenuItem, &opt, painter, mCombo);
280 }
281 QSize sizeHint(const QStyleOptionViewItem &option,
282 const QModelIndex &index) const override {
283 QStyleOptionMenuItem opt = getStyleOption(option, index);
284 return mCombo->style()->sizeFromContents(
285 QStyle::CT_MenuItem, &opt, option.rect.size(), mCombo);
286 }
287 bool editorEvent(QEvent *event, QAbstractItemModel *model,
288 const QStyleOptionViewItem &option, const QModelIndex &index) override;
289
290private:
291 QStyleOptionMenuItem getStyleOption(const QStyleOptionViewItem &option,
292 const QModelIndex &index) const;
293 QComboBox *mCombo;
294 int pressedIndex;
295};
296
297// ### Qt6: QStyledItemDelegate ?
298// Note that this class is intentionally not using QStyledItemDelegate
299// Vista does not use the new theme for combo boxes and there might
300// be other side effects from using the new class
301class Q_AUTOTEST_EXPORT QComboBoxDelegate : public QItemDelegate
302{ Q_OBJECT
303public:
304 QComboBoxDelegate(QObject *parent, QComboBox *cmb) : QItemDelegate(parent), mCombo(cmb) {}
305
306 static bool isSeparator(const QModelIndex &index) {
307 return index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator");
308 }
309 static void setSeparator(QAbstractItemModel *model, const QModelIndex &index) {
310 model->setData(index, QString::fromLatin1("separator"), Qt::AccessibleDescriptionRole);
311 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(model))
312 if (QStandardItem *item = m->itemFromIndex(index))
313 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled));
314 }
315
316protected:
317 void paint(QPainter *painter,
318 const QStyleOptionViewItem &option,
319 const QModelIndex &index) const override {
320 if (isSeparator(index)) {
321 QRect rect = option.rect;
322 if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(option.widget))
323 rect.setWidth(view->viewport()->width());
324 QStyleOption opt;
325 opt.rect = rect;
326 mCombo->style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, painter, mCombo);
327 } else {
328 QItemDelegate::paint(painter, option, index);
329 }
330 }
331
332 QSize sizeHint(const QStyleOptionViewItem &option,
333 const QModelIndex &index) const override {
334 if (isSeparator(index)) {
335 int pm = mCombo->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, mCombo);
336 return QSize(pm, pm);
337 }
338 return QItemDelegate::sizeHint(option, index);
339 }
340private:
341 QComboBox *mCombo;
342};
343
344class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate
345{
346 Q_DECLARE_PUBLIC(QComboBox)
347public:
348 QComboBoxPrivate();
349 ~QComboBoxPrivate();
350 void init();
351 QComboBoxPrivateContainer* viewContainer();
352 void updateLineEditGeometry();
353 Qt::MatchFlags matchFlags() const;
354 void _q_editingFinished();
355 void _q_returnPressed();
356 void _q_complete();
357 void _q_itemSelected(const QModelIndex &item);
358 bool contains(const QString &text, int role);
359 void emitActivated(const QModelIndex &index);
360 void _q_emitHighlighted(const QModelIndex &index);
361 void _q_emitCurrentIndexChanged(const QModelIndex &index);
362 void _q_modelDestroyed();
363 void _q_modelReset();
364#if QT_CONFIG(completer)
365 void _q_completerActivated(const QModelIndex &index);
366#endif
367 void _q_resetButton();
368 void _q_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
369 void _q_updateIndexBeforeChange();
370 void _q_rowsInserted(const QModelIndex &parent, int start, int end);
371 void _q_rowsRemoved(const QModelIndex &parent, int start, int end);
372 void updateArrow(QStyle::StateFlag state);
373 bool updateHoverControl(const QPoint &pos);
374 void trySetValidIndex();
375 QRect popupGeometry(int screen = -1) const;
376 QStyle::SubControl newHoverControl(const QPoint &pos);
377 int computeWidthHint() const;
378 QSize recomputeSizeHint(QSize &sh) const;
379 void adjustComboBoxSize();
380 QString itemText(const QModelIndex &index) const;
381 QIcon itemIcon(const QModelIndex &index) const;
382 int itemRole() const;
383 void updateLayoutDirection();
384 void setCurrentIndex(const QModelIndex &index);
385 void updateDelegate(bool force = false);
386 void keyboardSearchString(const QString &text);
387 void modelChanged();
388 void updateViewContainerPaletteAndOpacity();
389 void updateFocusPolicy();
390 void showPopupFromMouseEvent(QMouseEvent *e);
391
392#ifdef Q_OS_MAC
393 void cleanupNativePopup();
394 bool showNativePopup();
395 struct IndexSetter {
396 int index;
397 QComboBox *cb;
398
399 void operator()(void)
400 {
401 cb->setCurrentIndex(index);
402 cb->d_func()->emitActivated(cb->d_func()->currentIndex);
403 }
404 };
405#endif
406
407 QAbstractItemModel *model = nullptr;
408 QLineEdit *lineEdit = nullptr;
409 QComboBoxPrivateContainer *container = nullptr;
410#ifdef Q_OS_MAC
411 QPlatformMenu *m_platformMenu = nullptr;
412#endif
413#if QT_CONFIG(completer)
414 QPointer<QCompleter> completer;
415#endif
416 QPersistentModelIndex currentIndex;
417 QPersistentModelIndex root;
418 QString placeholderText;
419 QRect hoverRect;
420 QSize iconSize;
421 mutable QSize minimumSizeHint;
422 mutable QSize sizeHint;
423 QComboBox::InsertPolicy insertPolicy = QComboBox::InsertAtBottom;
424 QComboBox::SizeAdjustPolicy sizeAdjustPolicy = QComboBox::AdjustToContentsOnFirstShow;
425 QStyle::StateFlag arrowState = QStyle::State_None;
426 QStyle::SubControl hoverControl = QStyle::SC_None;
427 Qt::CaseSensitivity autoCompletionCaseSensitivity = Qt::CaseInsensitive;
428 int minimumContentsLength = 0;
429 int indexBeforeChange = -1;
430 int maxVisibleItems = 10;
431 int maxCount = std::numeric_limits<int>::max();
432 int modelColumn = 0;
433 int placeholderIndex = -1;
434 bool shownOnce : 1;
435 bool autoCompletion : 1;
436 bool duplicatesEnabled : 1;
437 bool frame : 1;
438 bool inserting : 1;
439};
440
441QT_END_NAMESPACE
442
443#endif // QCOMBOBOX_P_H
444

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