summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMagdalena Stojek <[email protected]>2024-07-02 16:02:54 +0200
committerVolker Hilsheimer <[email protected]>2024-10-29 15:17:52 +0200
commit0ac8ab0d2011e0696d6d794cbf78ac2ca87f9460 (patch)
tree7f391d001041953ab72f034b0a642b6d5cf878af
parent4ce7235fdd6183478eebfcf2f1d6058a172b285a (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.cpp37
-rw-r--r--examples/sql/books/bookdelegate.h6
-rw-r--r--examples/sql/books/bookwindow.cpp35
-rw-r--r--examples/sql/books/bookwindow.h1
-rw-r--r--src/widgets/itemviews/qabstractitemview.h1
-rw-r--r--src/widgets/widgets/qcombobox.cpp71
-rw-r--r--src/widgets/widgets/qcombobox.h10
-rw-r--r--src/widgets/widgets/qcombobox_p.h2
-rw-r--r--tests/baseline/widgets/tst_baseline_widgets.cpp79
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");