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 
78 QT_REQUIRE_CONFIG(combobox);
79 
80 QT_BEGIN_NAMESPACE
81 
82 class QPlatformMenu;
83 
84 class QComboBoxListView : public QListView
85 {
86     Q_OBJECT
87 public:
combo(cmb)88     QComboBoxListView(QComboBox *cmb = nullptr) : combo(cmb) {}
89 
90 protected:
resizeEvent(QResizeEvent * event)91     void resizeEvent(QResizeEvent *event) override
92     {
93         resizeContents(viewport()->width(), contentsSize().height());
94         QListView::resizeEvent(event);
95     }
96 
viewOptions()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 
paintEvent(QPaintEvent * e)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 
129 private:
130     QComboBox *combo;
131 };
132 
133 class Q_AUTOTEST_EXPORT QComboBoxPrivateScroller : public QWidget
134 {
135     Q_OBJECT
136 
137 public:
QComboBoxPrivateScroller(QAbstractSlider::SliderAction action,QWidget * parent)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     }
sizeHint()144     QSize sizeHint() const override {
145         return QSize(20, style()->pixelMetric(QStyle::PM_MenuScrollerHeight, nullptr, this));
146     }
147 
148 protected:
stopTimer()149     inline void stopTimer() {
150         timer.stop();
151     }
152 
startTimer()153     inline void startTimer() {
154         timer.start(100, this);
155         fast = false;
156     }
157 
enterEvent(QEvent *)158     void enterEvent(QEvent *) override {
159         startTimer();
160     }
161 
leaveEvent(QEvent *)162     void leaveEvent(QEvent *) override {
163         stopTimer();
164     }
timerEvent(QTimerEvent * e)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     }
hideEvent(QHideEvent *)174     void hideEvent(QHideEvent *) override {
175         stopTimer();
176     }
177 
mouseMoveEvent(QMouseEvent * e)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 
paintEvent(QPaintEvent *)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 
205 Q_SIGNALS:
206     void doScroll(int action);
207 
208 private:
209     QAbstractSlider::SliderAction sliderAction;
210     QBasicTimer timer;
211     bool fast = false;
212 };
213 
214 class Q_WIDGETS_EXPORT QComboBoxPrivateContainer : public QFrame
215 {
216     Q_OBJECT
217 
218 public:
219     QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent);
220     QAbstractItemView *itemView() const;
221     void setItemView(QAbstractItemView *itemView);
222     int spacing() const;
223     int topMargin() const;
bottomMargin()224     int bottomMargin() const { return topMargin(); }
225     void updateTopBottomMargin();
226 
227     QTimer blockMouseReleaseTimer;
228     QBasicTimer adjustSizeTimer;
229     QPoint initialClickPosition;
230 
231 public Q_SLOTS:
232     void scrollItemView(int action);
233     void hideScrollers();
234     void updateScrollers();
235     void viewDestroyed();
236 
237 protected:
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 
249 Q_SIGNALS:
250     void itemSelected(const QModelIndex &);
251     void resetButton();
252 
253 private:
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 
265 class Q_AUTOTEST_EXPORT QComboMenuDelegate : public QAbstractItemDelegate
266 {
267     Q_OBJECT
268 public:
QComboMenuDelegate(QObject * parent,QComboBox * cmb)269     QComboMenuDelegate(QObject *parent, QComboBox *cmb)
270     : QAbstractItemDelegate(parent), mCombo(cmb), pressedIndex(-1)
271     {}
272 
273 protected:
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index)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     }
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index)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 
290 private:
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
301 class Q_AUTOTEST_EXPORT QComboBoxDelegate : public QItemDelegate
302 { Q_OBJECT
303 public:
QComboBoxDelegate(QObject * parent,QComboBox * cmb)304     QComboBoxDelegate(QObject *parent, QComboBox *cmb) : QItemDelegate(parent), mCombo(cmb) {}
305 
isSeparator(const QModelIndex & index)306     static bool isSeparator(const QModelIndex &index) {
307         return index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator");
308     }
setSeparator(QAbstractItemModel * model,const QModelIndex & index)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 
316 protected:
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index)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 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index)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     }
340 private:
341     QComboBox *mCombo;
342 };
343 
344 class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate
345 {
346     Q_DECLARE_PUBLIC(QComboBox)
347 public:
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 
operatorIndexSetter399         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 
441 QT_END_NAMESPACE
442 
443 #endif // QCOMBOBOX_P_H
444