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