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 <richard.gustavsen@qt.io> Reviewed-by: Matthias Rauter <matthias.rauter@qt.io>
This commit is contained in:
parent
4ce7235fdd
commit
0ac8ab0d20
@ -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(QPalette::Highlight));
|
||||
}
|
||||
|
||||
if (option.state & QStyle::State_Selected)
|
||||
painter->fillRect(
|
||||
option.rect,
|
||||
option.palette.color(cg, 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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
ratingComboBox->setItemDelegate(new BookDelegate(0, this));
|
||||
ratingComboBox->setLabelDrawingMode(QComboBox::LabelDrawingMode::UseDelegate);
|
||||
ratingComboBox->addItems({"0", "1", "2", "3", "4", "5"});
|
||||
|
||||
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->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);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#define BOOKWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QComboBox)
|
||||
QT_FORWARD_DECLARE_CLASS(QGridLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user