diff options
author | Magdalena Stojek <[email protected]> | 2024-07-02 16:02:54 +0200 |
---|---|---|
committer | Volker Hilsheimer <[email protected]> | 2024-10-29 15:17:52 +0200 |
commit | 0ac8ab0d2011e0696d6d794cbf78ac2ca87f9460 (patch) | |
tree | 7f391d001041953ab72f034b0a642b6d5cf878af | |
parent | 4ce7235fdd6183478eebfcf2f1d6058a172b285a (diff) |
Use delegate to draw ComboBox Label
Allows customization and dynamic rendering of items
in the labels.
Task-number: QTBUG-126696
Change-Id: I6261131808aa303660f991e2f19248e547b14566
Reviewed-by: Richard Moe Gustavsen <[email protected]>
Reviewed-by: Matthias Rauter <[email protected]>
-rw-r--r-- | examples/sql/books/bookdelegate.cpp | 37 | ||||
-rw-r--r-- | examples/sql/books/bookdelegate.h | 6 | ||||
-rw-r--r-- | examples/sql/books/bookwindow.cpp | 35 | ||||
-rw-r--r-- | examples/sql/books/bookwindow.h | 1 | ||||
-rw-r--r-- | src/widgets/itemviews/qabstractitemview.h | 1 | ||||
-rw-r--r-- | src/widgets/widgets/qcombobox.cpp | 71 | ||||
-rw-r--r-- | src/widgets/widgets/qcombobox.h | 10 | ||||
-rw-r--r-- | src/widgets/widgets/qcombobox_p.h | 2 | ||||
-rw-r--r-- | tests/baseline/widgets/tst_baseline_widgets.cpp | 79 |
9 files changed, 189 insertions, 53 deletions
diff --git a/examples/sql/books/bookdelegate.cpp b/examples/sql/books/bookdelegate.cpp index ead9f9e7dbf..e59907678c2 100644 --- a/examples/sql/books/bookdelegate.cpp +++ b/examples/sql/books/bookdelegate.cpp @@ -6,36 +6,31 @@ #include <QMouseEvent> #include <QPainter> #include <QSpinBox> +#include <QTableView> + +BookDelegate::BookDelegate(int ratingColumn, QObject *parent) + : QSqlRelationalDelegate(parent), ratingColumn(ratingColumn) +{} void BookDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - if (index.column() != 5) { + if (index.column() != ratingColumn) { QSqlRelationalDelegate::paint(painter, option, index); } else { - const QAbstractItemModel *model = index.model(); - QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? - (option.state & QStyle::State_Active) ? - QPalette::Normal : - QPalette::Inactive : - QPalette::Disabled; - - if (option.state & QStyle::State_Selected) - painter->fillRect( - option.rect, - option.palette.color(cg, QPalette::Highlight)); + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, + option.palette.color(QPalette::Highlight)); + } - const int rating = model->data(index, Qt::DisplayRole).toInt(); - const int width = iconDimension; - const int height = width; + const int rating = index.data(Qt::DisplayRole).toInt(); + const int height = qMin(option.rect.height(), iconDimension); + const int width = height; // add cellPadding / 2 to center the stars in the cell int x = option.rect.x() + cellPadding / 2; int y = option.rect.y() + (option.rect.height() / 2) - (height / 2); - QIcon starIcon(QStringLiteral(":images/star.svg")); - QIcon starFilledIcon(QStringLiteral(":images/star-filled.svg")); - for (int i = 0; i < 5; ++i) { if (i < rating) starFilledIcon.paint(painter, QRect(x, y, width, height)); @@ -49,7 +44,7 @@ void BookDelegate::paint(QPainter *painter, QSize BookDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - if (index.column() == 5) + if (index.column() == ratingColumn) return QSize(5 * iconDimension, iconDimension) + QSize(cellPadding, cellPadding); // Since we draw the grid ourselves: return QSqlRelationalDelegate::sizeHint(option, index) + QSize(cellPadding, cellPadding); @@ -59,7 +54,7 @@ bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { - if (index.column() != 5) + if (!ratingColumn || index.column() != ratingColumn) return QSqlRelationalDelegate::editorEvent(event, model, option, index); if (event->type() == QEvent::MouseButtonPress) { @@ -78,7 +73,7 @@ QWidget *BookDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { - if (index.column() != 4) + if (!ratingColumn || index.column() != 4) return QSqlRelationalDelegate::createEditor(parent, option, index); // For editing the year, return a spinbox with a range from -1000 to 2100. diff --git a/examples/sql/books/bookdelegate.h b/examples/sql/books/bookdelegate.h index b36cb371ace..741b461c12a 100644 --- a/examples/sql/books/bookdelegate.h +++ b/examples/sql/books/bookdelegate.h @@ -13,7 +13,7 @@ QT_FORWARD_DECLARE_CLASS(QPainter) class BookDelegate : public QSqlRelationalDelegate { public: - using QSqlRelationalDelegate::QSqlRelationalDelegate; + explicit BookDelegate(int ratingColumn, QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; @@ -29,8 +29,12 @@ public: const QModelIndex &index) const override; private: + const QIcon starIcon{QStringLiteral(":images/star.svg")}; + const QIcon starFilledIcon{QStringLiteral(":images/star-filled.svg")}; + const int cellPadding = 6; const int iconDimension = 24; + const int ratingColumn; // 0 in the combobox, otherwise 5 }; #endif diff --git a/examples/sql/books/bookwindow.cpp b/examples/sql/books/bookwindow.cpp index 6de2d5e80ae..81fd2cd286e 100644 --- a/examples/sql/books/bookwindow.cpp +++ b/examples/sql/books/bookwindow.cpp @@ -127,7 +127,7 @@ void BookWindow::createModel() void BookWindow::configureWidgets() { tableView->setModel(model); - tableView->setItemDelegate(new BookDelegate(tableView)); + tableView->setItemDelegate(new BookDelegate(model->fieldIndex("rating"), tableView)); tableView->setColumnHidden(model->fieldIndex("id"), true); tableView->verticalHeader()->setVisible(false); tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -152,41 +152,18 @@ void BookWindow::configureWidgets() yearSpinBox->setMaximum(9999); - const int width = 16; - const int height = width; - const int y = 2; - const int padding = 2; - - QSize iconSize = QSize(width * 5 + padding * 2, width + padding * 2); - QIcon starIcon(QStringLiteral(":images/star.svg")); - QIcon starFilledIcon(QStringLiteral(":images/star-filled.svg")); - - for (int row = 0; row < 6; ++row) { - QPixmap icon(iconSize); - icon.fill(Qt::transparent); - QPainter painter(&icon); - int x = 2; - - for (int col = 0; col < 5; ++col) { - if (col < row) { - starFilledIcon.paint(&painter, QRect(x, y, width, height)); - } else { - starIcon.paint(&painter, QRect(x, y, width, height)); - } - x += width; - } - ratingComboBox->addItem(icon, ""); - ratingComboBox->setItemData(row, QString::number(row + 1)); - } + ratingComboBox->setItemDelegate(new BookDelegate(0, this)); + ratingComboBox->setLabelDrawingMode(QComboBox::LabelDrawingMode::UseDelegate); + ratingComboBox->addItems({"0", "1", "2", "3", "4", "5"}); - ratingComboBox->setIconSize(iconSize); + ratingComboBox->setIconSize(iconSize()); } void BookWindow::createMappings() { QDataWidgetMapper *mapper = new QDataWidgetMapper(this); mapper->setModel(model); - mapper->setItemDelegate(new BookDelegate(this)); + mapper->setItemDelegate(new BookDelegate(model->fieldIndex("rating"), this)); mapper->addMapping(titleLineEdit, model->fieldIndex("title")); mapper->addMapping(yearSpinBox, model->fieldIndex("year")); mapper->addMapping(authorComboBox, authorIdx); diff --git a/examples/sql/books/bookwindow.h b/examples/sql/books/bookwindow.h index 46d3570df1c..45c6b292fa2 100644 --- a/examples/sql/books/bookwindow.h +++ b/examples/sql/books/bookwindow.h @@ -5,6 +5,7 @@ #define BOOKWINDOW_H #include <QMainWindow> + QT_FORWARD_DECLARE_CLASS(QComboBox) QT_FORWARD_DECLARE_CLASS(QGridLayout) QT_FORWARD_DECLARE_CLASS(QLabel) diff --git a/src/widgets/itemviews/qabstractitemview.h b/src/widgets/itemviews/qabstractitemview.h index 63f2eefe83a..db95b713a7c 100644 --- a/src/widgets/itemviews/qabstractitemview.h +++ b/src/widgets/itemviews/qabstractitemview.h @@ -338,6 +338,7 @@ private: friend class QListModeViewBase; friend class QListViewPrivate; friend class QAbstractSlider; + friend class QComboBoxPrivate; // needed to call initViewItemOption }; Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractItemView::EditTriggers) diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp index 4f051867d6c..9ec84627490 100644 --- a/src/widgets/widgets/qcombobox.cpp +++ b/src/widgets/widgets/qcombobox.cpp @@ -348,6 +348,12 @@ QSize QComboBoxPrivate::recomputeSizeHint(QSize &sh) const { Q_Q(const QComboBox); if (!sh.isValid()) { + if (q->itemDelegate() && q->labelDrawingMode() == QComboBox::LabelDrawingMode::UseDelegate) { + QStyleOptionViewItem option; + initViewItemOption(&option); + sh = q->itemDelegate()->sizeHint(option, currentIndex); + } + bool hasIcon = sizeAdjustPolicy == QComboBox::AdjustToMinimumContentsLengthWithIcon; int count = q->count(); QSize iconSize = q->iconSize(); @@ -1259,6 +1265,16 @@ void QComboBox::initStyleOption(QStyleOptionComboBox *option) const option->state |= QStyle::State_On; } +void QComboBoxPrivate::initViewItemOption(QStyleOptionViewItem *option) const +{ + Q_Q(const QComboBox); + q->view()->initViewItemOption(option); + option->widget = q; + option->index = currentIndex; + option->text = q->currentText(); + option->icon = itemIcon(currentIndex); +} + void QComboBoxPrivate::updateLineEditGeometry() { if (!lineEdit) @@ -3067,6 +3083,7 @@ void QComboBox::resizeEvent(QResizeEvent *) */ void QComboBox::paintEvent(QPaintEvent *) { + Q_D(QComboBox); QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); @@ -3080,8 +3097,17 @@ void QComboBox::paintEvent(QPaintEvent *) opt.currentText = placeholderText(); } - // draw the icon and text - painter.drawControl(QStyle::CE_ComboBoxLabel, opt); + // draw contents + if (itemDelegate() && labelDrawingMode() == QComboBox::LabelDrawingMode::UseDelegate) { + QStyleOptionViewItem itemOption; + d->initViewItemOption(&itemOption); + itemOption.rect = style()->subControlRect(QStyle::CC_ComboBox, &opt, + QStyle::SC_ComboBoxEditField, this); + itemDelegate()->paint(&painter, itemOption, d->currentIndex); + } else { + // draw the icon and text + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); + } } /*! @@ -3574,6 +3600,47 @@ void QComboBox::setModelColumn(int visibleColumn) setCurrentIndex(currentIndex()); //update the text to the text of the new column; } +/*! + \enum QComboBox::LabelDrawingMode + \since 6.9 + + This enum specifies how the combobox draws its label. + + \value UseStyle The combobox uses the \l{QStyle}{style} to draw its label. + \value UseDelegate The combobox uses the \l{itemDelegate()}{item delegate} to + draw the label. Set a suitable item delegate when using this mode. + + \sa labelDrawingMode, {Books}{Books example} +*/ + +/*! + \property QComboBox::labelDrawingMode + \since 6.9 + + \brief the mode used by the combobox to draw its label. + + The default value is \l{QComboBox::}{UseStyle}. When changing this property + to UseDelegate, make sure to also set a suitable \l{itemDelegate()}{item delegate}. + The default delegate depends on the style and might not be suitable for + drawing the label. + + \sa {Books}{Books example} +*/ +QComboBox::LabelDrawingMode QComboBox::labelDrawingMode() const +{ + Q_D(const QComboBox); + return d->labelDrawingMode; +} + +void QComboBox::setLabelDrawingMode(LabelDrawingMode drawingLabel) +{ + Q_D(QComboBox); + if (d->labelDrawingMode != drawingLabel) { + d->labelDrawingMode = drawingLabel; + update(); + } +} + QT_END_NAMESPACE #include "moc_qcombobox.cpp" diff --git a/src/widgets/widgets/qcombobox.h b/src/widgets/widgets/qcombobox.h index 1689d0fa1e9..ad425ec60e1 100644 --- a/src/widgets/widgets/qcombobox.h +++ b/src/widgets/widgets/qcombobox.h @@ -40,6 +40,7 @@ class Q_WIDGETS_EXPORT QComboBox : public QWidget Q_PROPERTY(bool duplicatesEnabled READ duplicatesEnabled WRITE setDuplicatesEnabled) Q_PROPERTY(bool frame READ hasFrame WRITE setFrame) Q_PROPERTY(int modelColumn READ modelColumn WRITE setModelColumn) + Q_PROPERTY(LabelDrawingMode labelDrawingMode READ labelDrawingMode WRITE setLabelDrawingMode) public: explicit QComboBox(QWidget *parent = nullptr); @@ -85,6 +86,12 @@ public: }; Q_ENUM(SizeAdjustPolicy) + enum class LabelDrawingMode { + UseStyle, + UseDelegate, + }; + Q_ENUM(LabelDrawingMode) + SizeAdjustPolicy sizeAdjustPolicy() const; void setSizeAdjustPolicy(SizeAdjustPolicy policy); int minimumContentsLength() const; @@ -121,6 +128,9 @@ public: int modelColumn() const; void setModelColumn(int visibleColumn); + LabelDrawingMode labelDrawingMode() const; + void setLabelDrawingMode(LabelDrawingMode labelDrawing); + int currentIndex() const; QString currentText() const; QVariant currentData(int role = Qt::UserRole) const; diff --git a/src/widgets/widgets/qcombobox_p.h b/src/widgets/widgets/qcombobox_p.h index 723d637ae2e..c4a17598c24 100644 --- a/src/widgets/widgets/qcombobox_p.h +++ b/src/widgets/widgets/qcombobox_p.h @@ -352,6 +352,7 @@ public: void updateLayoutDirection(); void setCurrentIndex(const QModelIndex &index); void updateDelegate(bool force = false); + void initViewItemOption(QStyleOptionViewItem *option) const; void keyboardSearchString(const QString &text); void modelChanged(); void updateViewContainerPaletteAndOpacity(); @@ -396,6 +397,7 @@ public: QComboBox::SizeAdjustPolicy sizeAdjustPolicy = QComboBox::AdjustToContentsOnFirstShow; QStyle::StateFlag arrowState = QStyle::State_None; QStyle::SubControl hoverControl = QStyle::SC_None; + QComboBox::LabelDrawingMode labelDrawingMode = QComboBox::LabelDrawingMode::UseStyle; int minimumContentsLength = 0; int indexBeforeChange = -1; int maxVisibleItems = 10; diff --git a/tests/baseline/widgets/tst_baseline_widgets.cpp b/tests/baseline/widgets/tst_baseline_widgets.cpp index ffabf9e07eb..1717b885897 100644 --- a/tests/baseline/widgets/tst_baseline_widgets.cpp +++ b/tests/baseline/widgets/tst_baseline_widgets.cpp @@ -76,6 +76,9 @@ private slots: void tst_QCombobox_data(); void tst_QCombobox(); + void tst_QComboboxDelegate_data(); + void tst_QComboboxDelegate(); + void tst_QCommandLinkButton_data(); void tst_QCommandLinkButton(); @@ -1223,6 +1226,82 @@ void tst_Widgets::tst_QCombobox() QBASELINE_CHECK_DEFERRED(takeScreenSnapshot(testWindow()->geometry()), "combobox"); } +void tst_Widgets::tst_QComboboxDelegate_data() +{ + QTest::addColumn<int>("paddingTest"); + QTest::addColumn<int>("widthTest"); + + QTest::addRow("padding0") << 2 << 0; + QTest::addRow("padding20") << 20 << 0; + QTest::addRow("padding50") << 50 << 0; + QTest::addRow("width0") << 2 << 0; + QTest::addRow("width20") << 2 << 20; + QTest::addRow("width150") << 2 << 450; +} + +void tst_Widgets::tst_QComboboxDelegate() +{ + QFETCH(int, paddingTest); + QFETCH(int, widthTest); + + testWindow()->resize(300, 300); + QScopedPointer<QComboBox> combobox(new QComboBox(testWindow())); + + class RectDelegate : public QAbstractItemDelegate + { + public: + int sizeHintPadding = 2; + int sizeHintWidth = 22; + RectDelegate(QObject *parent = nullptr) : QAbstractItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + Q_UNUSED(index); + QRect rect = option.rect; + int padding = sizeHintPadding; + const int height = 22; + const int width = height + sizeHintWidth; + int yOffset = (option.rect.height() - height) / 2; + int x = rect.x() + padding; + int y = rect.y() + yOffset; + painter->setClipRect(rect); + painter->setBrush(QBrush(Qt::blue)); + painter->drawRect(QRect(x, y, width, height)); + } + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + Q_UNUSED(option); + Q_UNUSED(index); + const int height = 22; + const int width = height; + return QSize(width + 2 * sizeHintPadding, height + 2 * sizeHintPadding); + } + }; + + auto rect1 = new RectDelegate(this); + rect1->sizeHintPadding = paddingTest; + rect1->sizeHintWidth = widthTest; + combobox->setLabelDrawingMode(QComboBox::LabelDrawingMode::UseDelegate); + combobox->setItemDelegate(rect1); + combobox->addItem("item1"); + + auto rect2 = new RectDelegate(this); + rect2->sizeHintPadding = paddingTest; + rect2->sizeHintWidth = widthTest; + combobox->setLabelDrawingMode(QComboBox::LabelDrawingMode::UseDelegate); + combobox->setItemDelegate(rect2); + combobox->addItem("item2"); + + QHBoxLayout layout; + layout.addWidget(combobox.get()); + testWindow()->setLayout(&layout); + takeStandardSnapshots(); + + QTest::keyClick(combobox.get(), Qt::Key_Down, Qt::AltModifier); + QBASELINE_CHECK_DEFERRED(takeScreenSnapshot(testWindow()->geometry()), "combobox"); +} + void tst_Widgets::tst_QCommandLinkButton_data() { QTest::addColumn<bool>("flat"); |