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