From 3e9895f3de7f118beab3068b938ff6b9f2f5ef73 Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Mon, 2 Dec 2019 18:54:14 +0100 Subject: Support checkable QComboBox items with styles using a popup dropdown The dropdown of a combobox is rendered using menu items when the style request it to do so via the SH_ComboBox_Popup style hint. In that case, checkable items were not supported; the QComboBox didn't pass the checked state correctly to the style, and the delegate used for rendering the items into the list view did not implement modifying the checked state of the item. However, the QStyleOptionMenuItem's checked state and checkType members were set anyway, as on e.g. macOS style we use a checkmark to show which item is currently selected in the combobox. The QStyle::State enum defines State_On and State_Off for toggleable things, so in addition to setting QStyleOptionMenuItem::checked, we are now also adding State_On or State_Off if the model provides a valid checked/unchecked state. Otherwise, we only set the checked state if the item is currently selected. In addition, we implement the delegate to support toggling of checkable model data with mouse and keyboard, using a simplified version of the QItemDelegate implementation. To avoid spurious item toggles when the popup is opened, we only handle mouse releases when the press was on the same row. In the fusion style, we ignore the workaround to let QtQuickControls render comboboxes if State_On or State_Off are set. [ChangeLog][QtWidgets][QComboBox] Support checkable items in styles that use a popup for the dropdown. Change-Id: Ia01519694b0419d777dc66b1ef683482fb01754c Fixes: QTBUG-60310 Reviewed-by: Mitch Curtis Reviewed-by: Christian Ehrlicher --- src/widgets/widgets/qcombobox.cpp | 59 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) (limited to 'src/widgets/widgets/qcombobox.cpp') diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp index 19b442f477..4cfa5554e4 100644 --- a/src/widgets/widgets/qcombobox.cpp +++ b/src/widgets/widgets/qcombobox.cpp @@ -129,7 +129,15 @@ QStyleOptionMenuItem QComboMenuDelegate::getStyleOption(const QStyleOptionViewIt if (option.state & QStyle::State_Selected) menuOption.state |= QStyle::State_Selected; menuOption.checkType = QStyleOptionMenuItem::NonExclusive; - menuOption.checked = mCombo->currentIndex() == index.row(); + // a valid checkstate means that the model has checkable items + const QVariant checkState = index.data(Qt::CheckStateRole); + if (!checkState.isValid()) { + menuOption.checked = mCombo->currentIndex() == index.row(); + } else { + menuOption.checked = qvariant_cast(checkState) == Qt::Checked; + menuOption.state |= qvariant_cast(checkState) == Qt::Checked + ? QStyle::State_On : QStyle::State_Off; + } if (QComboBoxDelegate::isSeparator(index)) menuOption.menuItemType = QStyleOptionMenuItem::Separator; else @@ -179,6 +187,55 @@ QStyleOptionMenuItem QComboMenuDelegate::getStyleOption(const QStyleOptionViewIt return menuOption; } +bool QComboMenuDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + Q_ASSERT(event); + Q_ASSERT(model); + + // make sure that the item is checkable + Qt::ItemFlags flags = model->flags(index); + if (!(flags & Qt::ItemIsUserCheckable) || !(option.state & QStyle::State_Enabled) + || !(flags & Qt::ItemIsEnabled)) + return false; + + // make sure that we have a check state + const QVariant checkState = index.data(Qt::CheckStateRole); + if (!checkState.isValid()) + return false; + + // make sure that we have the right event type + if ((event->type() == QEvent::MouseButtonRelease) + || (event->type() == QEvent::MouseButtonDblClick) + || (event->type() == QEvent::MouseButtonPress)) { + QMouseEvent *me = static_cast(event); + if (me->button() != Qt::LeftButton) + return false; + + if ((event->type() == QEvent::MouseButtonPress) + || (event->type() == QEvent::MouseButtonDblClick)) { + pressedIndex = index.row(); + return false; + } + + if (index.row() != pressedIndex) + return false; + pressedIndex = -1; + + } else if (event->type() == QEvent::KeyPress) { + if (static_cast(event)->key() != Qt::Key_Space + && static_cast(event)->key() != Qt::Key_Select) + return false; + } else { + return false; + } + + // we don't support user-tristate items in QComboBox (not implemented in any style) + Qt::CheckState newState = (static_cast(checkState.toInt()) == Qt::Checked) + ? Qt::Unchecked : Qt::Checked; + return model->setData(index, newState, Qt::CheckStateRole); +} + #if QT_CONFIG(completer) void QComboBoxPrivate::_q_completerActivated(const QModelIndex &index) { -- cgit v1.2.3 From d6266c757d2f2ea4ff1e71dc8545f9bf97aa3bb1 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 6 Dec 2019 13:19:37 +0100 Subject: Replace usages of QVariant::value by qvariant_cast This is done automatically with a clazy check Change-Id: I3b59511d3d36d416c8eda74858ead611d327b116 Reviewed-by: Lars Knoll --- src/widgets/widgets/qcombobox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/widgets/widgets/qcombobox.cpp') diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp index 4cfa5554e4..d786c7ff83 100644 --- a/src/widgets/widgets/qcombobox.cpp +++ b/src/widgets/widgets/qcombobox.cpp @@ -172,7 +172,7 @@ QStyleOptionMenuItem QComboMenuDelegate::getStyleOption(const QStyleOptionViewIt // that order, also override the font for the popup menu. QVariant fontRoleData = index.data(Qt::FontRole); if (fontRoleData.isValid()) { - menuOption.font = fontRoleData.value(); + menuOption.font = qvariant_cast(fontRoleData); } else if (mCombo->testAttribute(Qt::WA_SetFont) || mCombo->testAttribute(Qt::WA_MacSmallSize) || mCombo->testAttribute(Qt::WA_MacMiniSize) -- cgit v1.2.3