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 | |
42 | QT_REQUIRE_CONFIG(combobox); |
43 | |
44 | QT_BEGIN_NAMESPACE |
45 | |
46 | class ; |
47 | |
48 | class QComboBoxListView : public QListView |
49 | { |
50 | Q_OBJECT |
51 | public: |
52 | QComboBoxListView(QComboBox *cmb = nullptr) : combo(cmb) |
53 | { |
54 | if (cmb) |
55 | setScreen(cmb->screen()); |
56 | } |
57 | |
58 | protected: |
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 ; |
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 | |
96 | private: |
97 | QComboBox *combo; |
98 | }; |
99 | |
100 | class Q_AUTOTEST_EXPORT QComboBoxPrivateScroller : public QWidget |
101 | { |
102 | Q_OBJECT |
103 | |
104 | public: |
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 | |
115 | protected: |
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 ; |
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 | |
172 | Q_SIGNALS: |
173 | void doScroll(int action); |
174 | |
175 | private: |
176 | QAbstractSlider::SliderAction sliderAction; |
177 | QBasicTimer timer; |
178 | bool fast = false; |
179 | }; |
180 | |
181 | class Q_WIDGETS_EXPORT QComboBoxPrivateContainer : public QFrame |
182 | { |
183 | Q_OBJECT |
184 | |
185 | public: |
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 | |
199 | public Q_SLOTS: |
200 | void scrollItemView(int action); |
201 | void hideScrollers(); |
202 | void updateScrollers(); |
203 | void viewDestroyed(); |
204 | |
205 | protected: |
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 | |
217 | Q_SIGNALS: |
218 | void itemSelected(const QModelIndex &); |
219 | void resetButton(); |
220 | |
221 | private: |
222 | QComboBox *combo; |
223 | QAbstractItemView *view = nullptr; |
224 | QComboBoxPrivateScroller *top = nullptr; |
225 | QComboBoxPrivateScroller *bottom = nullptr; |
226 | QElapsedTimer ; |
227 | bool maybeIgnoreMouseButtonRelease = false; |
228 | |
229 | friend class QComboBox; |
230 | friend class QComboBoxPrivate; |
231 | }; |
232 | |
233 | class Q_AUTOTEST_EXPORT : public QAbstractItemDelegate |
234 | { |
235 | Q_OBJECT |
236 | public: |
237 | (QObject *parent, QComboBox *cmb) |
238 | : QAbstractItemDelegate(parent), mCombo(cmb), pressedIndex(-1) |
239 | {} |
240 | |
241 | protected: |
242 | void (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 (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 (QEvent *event, QAbstractItemModel *model, |
256 | const QStyleOptionViewItem &option, const QModelIndex &index) override; |
257 | |
258 | private: |
259 | QStyleOptionMenuItem (const QStyleOptionViewItem &option, |
260 | const QModelIndex &index) const; |
261 | QComboBox *; |
262 | int ; |
263 | }; |
264 | |
265 | class Q_AUTOTEST_EXPORT QComboBoxDelegate : public QStyledItemDelegate |
266 | { |
267 | Q_OBJECT |
268 | public: |
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 | |
284 | protected: |
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 | } |
308 | private: |
309 | QComboBox *mCombo; |
310 | }; |
311 | |
312 | class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate |
313 | { |
314 | Q_DECLARE_PUBLIC(QComboBox) |
315 | public: |
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 (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 (QMouseEvent *e); |
359 | void (); |
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 : 1; |
406 | }; |
407 | |
408 | QT_END_NAMESPACE |
409 | |
410 | #endif // QCOMBOBOX_P_H |
411 | |