| 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/qtimer.h" |
| 31 | #include "private/qwidget_p.h" |
| 32 | #include "QtCore/qpointer.h" |
| 33 | #if QT_CONFIG(completer) |
| 34 | #include "QtWidgets/qcompleter.h" |
| 35 | #endif |
| 36 | #include "QtGui/qevent.h" |
| 37 | |
| 38 | #include <limits.h> |
| 39 | |
| 40 | QT_REQUIRE_CONFIG(combobox); |
| 41 | |
| 42 | QT_BEGIN_NAMESPACE |
| 43 | |
| 44 | class ; |
| 45 | |
| 46 | class QComboBoxListView : public QListView |
| 47 | { |
| 48 | Q_OBJECT |
| 49 | public: |
| 50 | QComboBoxListView(QComboBox *cmb = nullptr) : combo(cmb) |
| 51 | { |
| 52 | if (cmb) |
| 53 | setScreen(cmb->screen()); |
| 54 | } |
| 55 | |
| 56 | protected: |
| 57 | void resizeEvent(QResizeEvent *event) override |
| 58 | { |
| 59 | resizeContents(width: viewport()->width(), height: contentsSize().height()); |
| 60 | QListView::resizeEvent(e: event); |
| 61 | } |
| 62 | |
| 63 | void initViewItemOption(QStyleOptionViewItem *option) const override |
| 64 | { |
| 65 | QListView::initViewItemOption(option); |
| 66 | option->showDecorationSelected = true; |
| 67 | if (combo) |
| 68 | option->font = combo->font(); |
| 69 | } |
| 70 | |
| 71 | void paintEvent(QPaintEvent *e) override |
| 72 | { |
| 73 | if (combo) { |
| 74 | QStyleOptionComboBox opt; |
| 75 | opt.initFrom(w: combo); |
| 76 | opt.editable = combo->isEditable(); |
| 77 | if (combo->style()->styleHint(stylehint: QStyle::SH_ComboBox_Popup, opt: &opt, widget: combo)) { |
| 78 | //we paint the empty menu area to avoid having blank space that can happen when scrolling |
| 79 | QStyleOptionMenuItem ; |
| 80 | menuOpt.initFrom(w: this); |
| 81 | menuOpt.palette = palette(); |
| 82 | menuOpt.state = QStyle::State_None; |
| 83 | menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; |
| 84 | menuOpt.menuRect = e->rect(); |
| 85 | menuOpt.maxIconWidth = 0; |
| 86 | menuOpt.reservedShortcutWidth = 0; |
| 87 | QPainter p(viewport()); |
| 88 | combo->style()->drawControl(element: QStyle::CE_MenuEmptyArea, opt: &menuOpt, p: &p, w: this); |
| 89 | } |
| 90 | } |
| 91 | QListView::paintEvent(e); |
| 92 | } |
| 93 | |
| 94 | private: |
| 95 | QComboBox *combo; |
| 96 | }; |
| 97 | |
| 98 | class Q_AUTOTEST_EXPORT QComboBoxPrivateScroller : public QWidget |
| 99 | { |
| 100 | Q_OBJECT |
| 101 | |
| 102 | public: |
| 103 | QComboBoxPrivateScroller(QAbstractSlider::SliderAction action, QWidget *parent) |
| 104 | : QWidget(parent), sliderAction(action) |
| 105 | { |
| 106 | setSizePolicy(hor: QSizePolicy::Minimum, ver: QSizePolicy::Fixed); |
| 107 | setAttribute(Qt::WA_NoMousePropagation); |
| 108 | } |
| 109 | QSize sizeHint() const override { |
| 110 | return QSize(20, style()->pixelMetric(metric: QStyle::PM_MenuScrollerHeight, option: nullptr, widget: this)); |
| 111 | } |
| 112 | |
| 113 | protected: |
| 114 | inline void stopTimer() { |
| 115 | timer.stop(); |
| 116 | } |
| 117 | |
| 118 | inline void startTimer() { |
| 119 | timer.start(msec: 100, obj: this); |
| 120 | fast = false; |
| 121 | } |
| 122 | |
| 123 | void enterEvent(QEnterEvent *) override { |
| 124 | startTimer(); |
| 125 | } |
| 126 | |
| 127 | void leaveEvent(QEvent *) override { |
| 128 | stopTimer(); |
| 129 | } |
| 130 | void timerEvent(QTimerEvent *e) override { |
| 131 | if (e->timerId() == timer.timerId()) { |
| 132 | emit doScroll(action: sliderAction); |
| 133 | if (fast) { |
| 134 | emit doScroll(action: sliderAction); |
| 135 | emit doScroll(action: sliderAction); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | void hideEvent(QHideEvent *) override { |
| 140 | stopTimer(); |
| 141 | } |
| 142 | |
| 143 | void mouseMoveEvent(QMouseEvent *e) override |
| 144 | { |
| 145 | // Enable fast scrolling if the cursor is directly above or below the popup. |
| 146 | const int mouseX = e->position().toPoint().x(); |
| 147 | const int mouseY = e->position().toPoint().y(); |
| 148 | const bool horizontallyInside = pos().x() < mouseX && mouseX < rect().right() + 1; |
| 149 | const bool verticallyOutside = (sliderAction == QAbstractSlider::SliderSingleStepAdd) ? |
| 150 | rect().bottom() + 1 < mouseY : mouseY < pos().y(); |
| 151 | |
| 152 | fast = horizontallyInside && verticallyOutside; |
| 153 | } |
| 154 | |
| 155 | void paintEvent(QPaintEvent *) override { |
| 156 | QPainter p(this); |
| 157 | QStyleOptionMenuItem ; |
| 158 | menuOpt.initFrom(w: this); |
| 159 | menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; |
| 160 | menuOpt.menuRect = rect(); |
| 161 | menuOpt.maxIconWidth = 0; |
| 162 | menuOpt.reservedShortcutWidth = 0; |
| 163 | menuOpt.menuItemType = QStyleOptionMenuItem::Scroller; |
| 164 | if (sliderAction == QAbstractSlider::SliderSingleStepAdd) |
| 165 | menuOpt.state |= QStyle::State_DownArrow; |
| 166 | p.eraseRect(rect: rect()); |
| 167 | style()->drawControl(element: QStyle::CE_MenuScroller, opt: &menuOpt, p: &p); |
| 168 | } |
| 169 | |
| 170 | Q_SIGNALS: |
| 171 | void doScroll(int action); |
| 172 | |
| 173 | private: |
| 174 | QAbstractSlider::SliderAction sliderAction; |
| 175 | QBasicTimer timer; |
| 176 | bool fast = false; |
| 177 | }; |
| 178 | |
| 179 | class Q_WIDGETS_EXPORT QComboBoxPrivateContainer : public QFrame |
| 180 | { |
| 181 | Q_OBJECT |
| 182 | |
| 183 | public: |
| 184 | QComboBoxPrivateContainer(QAbstractItemView *itemView, QComboBox *parent); |
| 185 | ~QComboBoxPrivateContainer(); |
| 186 | QAbstractItemView *itemView() const; |
| 187 | void setItemView(QAbstractItemView *itemView); |
| 188 | int spacing() const; |
| 189 | int topMargin() const; |
| 190 | int bottomMargin() const { return topMargin(); } |
| 191 | void updateTopBottomMargin(); |
| 192 | void updateStyleSettings(); |
| 193 | |
| 194 | QTimer blockMouseReleaseTimer; |
| 195 | QBasicTimer adjustSizeTimer; |
| 196 | QPoint initialClickPosition; |
| 197 | |
| 198 | public Q_SLOTS: |
| 199 | void scrollItemView(int action); |
| 200 | void hideScrollers(); |
| 201 | void updateScrollers(); |
| 202 | void viewDestroyed(); |
| 203 | |
| 204 | protected: |
| 205 | void changeEvent(QEvent *e) override; |
| 206 | bool eventFilter(QObject *o, QEvent *e) override; |
| 207 | void mousePressEvent(QMouseEvent *e) override; |
| 208 | void mouseReleaseEvent(QMouseEvent *e) override; |
| 209 | void showEvent(QShowEvent *e) override; |
| 210 | void hideEvent(QHideEvent *e) override; |
| 211 | void timerEvent(QTimerEvent *timerEvent) override; |
| 212 | void resizeEvent(QResizeEvent *e) override; |
| 213 | void paintEvent(QPaintEvent *e) override; |
| 214 | QStyleOptionComboBox comboStyleOption() const; |
| 215 | |
| 216 | Q_SIGNALS: |
| 217 | void itemSelected(const QModelIndex &); |
| 218 | void resetButton(); |
| 219 | |
| 220 | private: |
| 221 | QComboBox *combo; |
| 222 | QAbstractItemView *view = nullptr; |
| 223 | QComboBoxPrivateScroller *top = nullptr; |
| 224 | QComboBoxPrivateScroller *bottom = nullptr; |
| 225 | QElapsedTimer ; |
| 226 | bool maybeIgnoreMouseButtonRelease = false; |
| 227 | |
| 228 | friend class QComboBox; |
| 229 | friend class QComboBoxPrivate; |
| 230 | }; |
| 231 | |
| 232 | class Q_AUTOTEST_EXPORT : public QAbstractItemDelegate |
| 233 | { |
| 234 | Q_OBJECT |
| 235 | public: |
| 236 | (QObject *parent, QComboBox *cmb) |
| 237 | : QAbstractItemDelegate(parent), mCombo(cmb), pressedIndex(-1) |
| 238 | {} |
| 239 | |
| 240 | protected: |
| 241 | void (QPainter *painter, |
| 242 | const QStyleOptionViewItem &option, |
| 243 | const QModelIndex &index) const override { |
| 244 | QStyleOptionMenuItem opt = getStyleOption(option, index); |
| 245 | painter->fillRect(option.rect, opt.palette.window()); |
| 246 | mCombo->style()->drawControl(element: QStyle::CE_MenuItem, opt: &opt, p: painter, w: mCombo); |
| 247 | } |
| 248 | QSize (const QStyleOptionViewItem &option, |
| 249 | const QModelIndex &index) const override { |
| 250 | QStyleOptionMenuItem opt = getStyleOption(option, index); |
| 251 | return mCombo->style()->sizeFromContents( |
| 252 | ct: QStyle::CT_MenuItem, opt: &opt, contentsSize: option.rect.size(), w: mCombo); |
| 253 | } |
| 254 | bool (QEvent *event, QAbstractItemModel *model, |
| 255 | const QStyleOptionViewItem &option, const QModelIndex &index) override; |
| 256 | |
| 257 | private: |
| 258 | QStyleOptionMenuItem (const QStyleOptionViewItem &option, |
| 259 | const QModelIndex &index) const; |
| 260 | QComboBox *; |
| 261 | int ; |
| 262 | }; |
| 263 | |
| 264 | class Q_AUTOTEST_EXPORT QComboBoxDelegate : public QStyledItemDelegate |
| 265 | { |
| 266 | Q_OBJECT |
| 267 | public: |
| 268 | QComboBoxDelegate(QObject *parent, QComboBox *cmb) |
| 269 | : QStyledItemDelegate(parent), mCombo(cmb) |
| 270 | {} |
| 271 | |
| 272 | static bool isSeparator(const QModelIndex &index) { |
| 273 | return index.data(arole: Qt::AccessibleDescriptionRole).toString() |
| 274 | == QLatin1StringView("separator" ); |
| 275 | } |
| 276 | static void setSeparator(QAbstractItemModel *model, const QModelIndex &index) { |
| 277 | model->setData(index, value: QString::fromLatin1(ba: "separator" ), role: Qt::AccessibleDescriptionRole); |
| 278 | if (QStandardItemModel *m = qobject_cast<QStandardItemModel*>(object: model)) |
| 279 | if (QStandardItem *item = m->itemFromIndex(index)) |
| 280 | item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); |
| 281 | } |
| 282 | |
| 283 | protected: |
| 284 | void paint(QPainter *painter, |
| 285 | const QStyleOptionViewItem &option, |
| 286 | const QModelIndex &index) const override { |
| 287 | if (isSeparator(index)) { |
| 288 | QRect rect = option.rect; |
| 289 | if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*>(object: option.widget)) |
| 290 | rect.setWidth(view->viewport()->width()); |
| 291 | QStyleOption opt; |
| 292 | opt.rect = rect; |
| 293 | mCombo->style()->drawPrimitive(pe: QStyle::PE_IndicatorToolBarSeparator, opt: &opt, p: painter, w: mCombo); |
| 294 | } else { |
| 295 | QStyledItemDelegate::paint(painter, option, index); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | QSize sizeHint(const QStyleOptionViewItem &option, |
| 300 | const QModelIndex &index) const override { |
| 301 | if (isSeparator(index)) { |
| 302 | int pm = mCombo->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth, option: nullptr, widget: mCombo); |
| 303 | return QSize(pm, pm); |
| 304 | } |
| 305 | return QStyledItemDelegate::sizeHint(option, index); |
| 306 | } |
| 307 | private: |
| 308 | QComboBox *mCombo; |
| 309 | }; |
| 310 | |
| 311 | class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate |
| 312 | { |
| 313 | Q_DECLARE_PUBLIC(QComboBox) |
| 314 | public: |
| 315 | QComboBoxPrivate(); |
| 316 | ~QComboBoxPrivate(); |
| 317 | void init(); |
| 318 | QComboBoxPrivateContainer* viewContainer(); |
| 319 | void updateLineEditGeometry(); |
| 320 | Qt::MatchFlags matchFlags() const; |
| 321 | void editingFinished(); |
| 322 | void returnPressed(); |
| 323 | void complete(); |
| 324 | void itemSelected(const QModelIndex &item); |
| 325 | bool contains(const QString &text, int role); |
| 326 | void emitActivated(const QModelIndex &index); |
| 327 | void emitHighlighted(const QModelIndex &index); |
| 328 | void emitCurrentIndexChanged(const QModelIndex &index); |
| 329 | void modelDestroyed(); |
| 330 | void modelReset(); |
| 331 | void updateMicroFocus() { q_func()->updateMicroFocus(); } // PMF connect doesn't handle default args |
| 332 | #if QT_CONFIG(completer) |
| 333 | void completerActivated(const QModelIndex &index); |
| 334 | #endif |
| 335 | void resetButton(); |
| 336 | void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); |
| 337 | void updateIndexBeforeChange(); |
| 338 | void rowsInserted(const QModelIndex &parent, int start, int end); |
| 339 | void 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 initViewItemOption(QStyleOptionViewItem *option) const; |
| 355 | void keyboardSearchString(const QString &text); |
| 356 | void modelChanged(); |
| 357 | void updateViewContainerPaletteAndOpacity(); |
| 358 | void updateFocusPolicy(); |
| 359 | void (QMouseEvent *e); |
| 360 | void (); |
| 361 | void updateCurrentText(const QString &text); |
| 362 | void connectModel(); |
| 363 | void disconnectModel(); |
| 364 | |
| 365 | #ifdef Q_OS_MAC |
| 366 | void cleanupNativePopup(); |
| 367 | bool showNativePopup(); |
| 368 | struct IndexSetter { |
| 369 | int index; |
| 370 | QComboBox *cb; |
| 371 | |
| 372 | void operator()(void) |
| 373 | { |
| 374 | cb->setCurrentIndex(index); |
| 375 | cb->d_func()->emitActivated(cb->d_func()->currentIndex); |
| 376 | } |
| 377 | }; |
| 378 | #endif |
| 379 | |
| 380 | std::array<QMetaObject::Connection, 8> modelConnections; |
| 381 | QAbstractItemModel *model = nullptr; |
| 382 | QLineEdit *lineEdit = nullptr; |
| 383 | QPointer<QComboBoxPrivateContainer> container; |
| 384 | #ifdef Q_OS_MAC |
| 385 | QPlatformMenu *m_platformMenu = nullptr; |
| 386 | #endif |
| 387 | QPersistentModelIndex currentIndex; |
| 388 | QPersistentModelIndex root; |
| 389 | QString placeholderText; |
| 390 | QString currentText; |
| 391 | QRect hoverRect; |
| 392 | QSize iconSize; |
| 393 | mutable QSize minimumSizeHint; |
| 394 | mutable QSize sizeHint; |
| 395 | QComboBox::InsertPolicy insertPolicy = QComboBox::InsertAtBottom; |
| 396 | QComboBox::SizeAdjustPolicy sizeAdjustPolicy = QComboBox::AdjustToContentsOnFirstShow; |
| 397 | QStyle::StateFlag arrowState = QStyle::State_None; |
| 398 | QStyle::SubControl hoverControl = QStyle::SC_None; |
| 399 | QComboBox::LabelDrawingMode labelDrawingMode = QComboBox::LabelDrawingMode::UseStyle; |
| 400 | int minimumContentsLength = 0; |
| 401 | int indexBeforeChange = -1; |
| 402 | int maxVisibleItems = 10; |
| 403 | int maxCount = (std::numeric_limits<int>::max)(); |
| 404 | int modelColumn = 0; |
| 405 | int placeholderIndex = -1; |
| 406 | bool shownOnce : 1; |
| 407 | bool duplicatesEnabled : 1; |
| 408 | bool frame : 1; |
| 409 | bool inserting : 1; |
| 410 | bool : 1; |
| 411 | }; |
| 412 | |
| 413 | QT_END_NAMESPACE |
| 414 | |
| 415 | #endif // QCOMBOBOX_P_H |
| 416 | |