Skip to content

Commit

Permalink
Use delegate to draw ComboBox Label
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
Magdalena Stojek authored and vohi committed Oct 29, 2024
1 parent 4ce7235 commit 0ac8ab0
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 53 deletions.
37 changes: 16 additions & 21 deletions examples/sql/books/bookdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion examples/sql/books/bookdelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,8 +29,12 @@ class BookDelegate : public QSqlRelationalDelegate
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
35 changes: 6 additions & 29 deletions examples/sql/books/bookwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions examples/sql/books/bookwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define BOOKWINDOW_H

#include <QMainWindow>

QT_FORWARD_DECLARE_CLASS(QComboBox)
QT_FORWARD_DECLARE_CLASS(QGridLayout)
QT_FORWARD_DECLARE_CLASS(QLabel)
Expand Down
1 change: 1 addition & 0 deletions src/widgets/itemviews/qabstractitemview.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ protected Q_SLOTS:
friend class QListModeViewBase;
friend class QListViewPrivate;
friend class QAbstractSlider;
friend class QComboBoxPrivate; // needed to call initViewItemOption
};

Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractItemView::EditTriggers)
Expand Down
71 changes: 69 additions & 2 deletions src/widgets/widgets/qcombobox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -3067,6 +3083,7 @@ void QComboBox::resizeEvent(QResizeEvent *)
*/
void QComboBox::paintEvent(QPaintEvent *)
{
Q_D(QComboBox);
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));

Expand All @@ -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);
}
}

/*!
Expand Down Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions src/widgets/widgets/qcombobox.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -85,6 +86,12 @@ class Q_WIDGETS_EXPORT QComboBox : public QWidget
};
Q_ENUM(SizeAdjustPolicy)

enum class LabelDrawingMode {
UseStyle,
UseDelegate,
};
Q_ENUM(LabelDrawingMode)

SizeAdjustPolicy sizeAdjustPolicy() const;
void setSizeAdjustPolicy(SizeAdjustPolicy policy);
int minimumContentsLength() const;
Expand Down Expand Up @@ -121,6 +128,9 @@ class Q_WIDGETS_EXPORT QComboBox : public QWidget
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;
Expand Down
2 changes: 2 additions & 0 deletions src/widgets/widgets/qcombobox_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate
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();
Expand Down Expand Up @@ -396,6 +397,7 @@ class Q_AUTOTEST_EXPORT QComboBoxPrivate : public QWidgetPrivate
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;
Expand Down
Loading

0 comments on commit 0ac8ab0

Please sign in to comment.