Update the books example

Remove the .ui file and write the equivalent implementation.

Replace the outdated star icons with new star icons. 5 empty stars
are now provided and filled in depending on the rating. The new stars
are SVG, however, we do not use an SvgRenderer as we cannot use the
QtSvg API from QtBase directly. It is safe to assume the SVG image
loading plugin is present, the worst case scenario would be empty icons,
but it would still build. We instead use QIcon.

For the rating, draw the star icons in a combobox, replacing the old spinbox.

Update the layout by moving the table view to the bottom, and
arranging the input fields at the top.

The scrollbar policies have been set to Qt::ScrollBarAsNeeded for the table view.

Fixes: QTBUG-118476
Pick-to: 6.5
Change-Id: I27c13534ab06e17531d155469a1cc6e7e05197af
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit 76b2852b7cff183ba31d6b09b00cbabdffb9ba71)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit a00c80e1ce1f0a901cdbe10366321c25e3135751)
This commit is contained in:
Ed Cooke 2023-11-01 14:18:14 +01:00 committed by Qt Cherry-pick Bot
parent de628f93a0
commit c4366e477d
12 changed files with 193 additions and 250 deletions

View File

@ -16,7 +16,7 @@ qt_standard_project_setup()
qt_add_executable(books
bookdelegate.cpp bookdelegate.h
bookwindow.cpp bookwindow.h bookwindow.ui
bookwindow.cpp bookwindow.h
initdb.h
main.cpp
)
@ -35,7 +35,8 @@ target_link_libraries(books PRIVATE
# Resources:
set(books_resource_files
"images/star.png"
"images/star.svg"
"images/star-filled.svg"
)
qt_add_resources(books "books"

View File

@ -6,9 +6,8 @@
#include <QtWidgets>
BookDelegate::BookDelegate(QObject *parent)
: QSqlRelationalDelegate(parent), star(QPixmap(":images/star.png"))
{
}
: QSqlRelationalDelegate(parent)
{}
void BookDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
@ -28,31 +27,34 @@ void BookDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
option.rect,
option.palette.color(cg, QPalette::Highlight));
int rating = model->data(index, Qt::DisplayRole).toInt();
int width = star.width();
int height = star.height();
int x = option.rect.x();
const int rating = model->data(index, Qt::DisplayRole).toInt();
const int width = iconDimension;
const int height = width;
// 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);
for (int i = 0; i < rating; ++i) {
painter->drawPixmap(x, y, star);
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));
} else {
starIcon.paint(painter, QRect(x, y, width, height));
}
x += width;
}
}
QPen pen = painter->pen();
painter->setPen(option.palette.color(QPalette::Mid));
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
painter->setPen(pen);
}
QSize BookDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == 5)
return QSize(5 * star.width(), star.height()) + QSize(1, 1);
return QSize(5 * iconDimension, iconDimension) + QSize(cellPadding, cellPadding);
// Since we draw the grid ourselves:
return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);
return QSqlRelationalDelegate::sizeHint(option, index) + QSize(cellPadding, cellPadding);
}
bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
@ -65,7 +67,7 @@ bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
int stars = qBound(0, int(0.7 + qreal(mouseEvent->position().toPoint().x()
- option.rect.x()) / star.width()), 5);
- option.rect.x()) / iconDimension), 5);
model->setData(index, QVariant(stars));
// So that the selection can change:
return false;

View File

@ -30,7 +30,8 @@ public:
const QModelIndex &index) const override;
private:
QPixmap star;
const int cellPadding = 6;
const int iconDimension = 24;
};
#endif

View File

@ -4,7 +4,6 @@ INCLUDEPATH += .
HEADERS = bookdelegate.h bookwindow.h initdb.h
RESOURCES = books.qrc
SOURCES = bookdelegate.cpp main.cpp bookwindow.cpp
FORMS = bookwindow.ui
QT += sql widgets widgets
requires(qtConfig(tableview))

View File

@ -1,5 +1,6 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>images/star.png</file>
<file>images/star.svg</file>
<file>images/star-filled.svg</file>
</qresource>
</RCC>

View File

@ -9,8 +9,6 @@
BookWindow::BookWindow()
{
ui.setupUi(this);
if (!QSqlDatabase::drivers().contains("QSQLITE"))
QMessageBox::critical(
this,
@ -25,71 +23,28 @@ BookWindow::BookWindow()
return;
}
// Create the data model:
model = new QSqlRelationalTableModel(ui.bookTable);
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->setTable("books");
// create the central widget for the window
window = new QWidget(this);
setCentralWidget(window);
// Remember the indexes of the columns:
authorIdx = model->fieldIndex("author");
genreIdx = model->fieldIndex("genre");
createLayout();
// Set the relations to the other database tables:
model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));
model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
createModel();
// Set the localized header captions:
model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));
model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));
model->setHeaderData(model->fieldIndex("title"),
Qt::Horizontal, tr("Title"));
model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));
model->setHeaderData(model->fieldIndex("rating"),
Qt::Horizontal, tr("Rating"));
// Populate the model:
// Populate the model
if (!model->select()) {
showError(model->lastError());
return;
}
// Set the model and hide the ID column:
ui.bookTable->setModel(model);
ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable));
ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);
ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);
configureWidgets();
// Initialize the Author combo box:
ui.authorEdit->setModel(model->relationModel(authorIdx));
ui.authorEdit->setModelColumn(
model->relationModel(authorIdx)->fieldIndex("name"));
// create the mappings between the UI elements and the SQL model
createMappings();
ui.genreEdit->setModel(model->relationModel(genreIdx));
ui.genreEdit->setModelColumn(
model->relationModel(genreIdx)->fieldIndex("name"));
tableView->setCurrentIndex(model->index(0, 0));
tableView->selectRow(0);
// Lock and prohibit resizing of the width of the rating column:
ui.bookTable->horizontalHeader()->setSectionResizeMode(
model->fieldIndex("rating"),
QHeaderView::ResizeToContents);
QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
mapper->setModel(model);
mapper->setItemDelegate(new BookDelegate(this));
mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
mapper->addMapping(ui.authorEdit, authorIdx);
mapper->addMapping(ui.genreEdit, genreIdx);
mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
connect(ui.bookTable->selectionModel(),
&QItemSelectionModel::currentRowChanged,
mapper,
&QDataWidgetMapper::setCurrentModelIndex
);
ui.bookTable->setCurrentIndex(model->index(0, 0));
ui.bookTable->selectRow(0);
createMenuBar();
}
@ -99,6 +54,140 @@ void BookWindow::showError(const QSqlError &err)
"Error initializing database: " + err.text());
}
void BookWindow::createLayout()
{
tableView = new QTableView(window);
gridLayout = new QGridLayout(window);
titleLabel = new QLabel(tr("Title:"), window);
titleLineEdit = new QLineEdit(window);
authorLabel = new QLabel(tr("Author:"), window);
authorComboBox = new QComboBox(window);
genreLabel = new QLabel(tr("Genre:"), window);
genreComboBox = new QComboBox(window);
yearLabel = new QLabel(tr("Year:"), window);
yearSpinBox = new QSpinBox(window);
ratingLabel = new QLabel(tr("Rating:"), window);
ratingComboBox = new QComboBox(window);
gridLayout->addWidget(titleLabel, 0, 0, Qt::AlignRight);
gridLayout->addWidget(titleLineEdit, 0, 1, 1, 3);
gridLayout->addWidget(authorLabel, 1, 0, Qt::AlignRight);
gridLayout->addWidget(authorComboBox, 1, 1);
gridLayout->addWidget(yearLabel, 1, 2, Qt::AlignRight);
gridLayout->addWidget(yearSpinBox, 1, 3);
gridLayout->addWidget(genreLabel, 2, 0, Qt::AlignRight);
gridLayout->addWidget(genreComboBox, 2, 1);
gridLayout->addWidget(ratingLabel, 2, 2, Qt::AlignRight);
gridLayout->addWidget(ratingComboBox, 2, 3);
gridLayout->addWidget(tableView, 3, 0, 1, 4, Qt::AlignCenter);
gridLayout->setColumnStretch(1, 1000);
gridLayout->setColumnStretch(3, 1000);
gridLayout->setContentsMargins(18, 18, 18, 18);
gridLayout->setSpacing(18);
gridLayout->setAlignment(Qt::AlignHCenter);
}
void BookWindow::createModel()
{
model = new QSqlRelationalTableModel(tableView);
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->setTable("books");
authorIdx = model->fieldIndex("author");
genreIdx = model->fieldIndex("genre");
// Set the relations to the other database tables
model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));
model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
// Set the localised header captions
model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));
model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));
model->setHeaderData(model->fieldIndex("title"),
Qt::Horizontal, tr("Title"));
model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));
model->setHeaderData(model->fieldIndex("rating"),
Qt::Horizontal, tr("Rating"));
}
void BookWindow::configureWidgets()
{
tableView->setModel(model);
tableView->setItemDelegate(new BookDelegate(tableView));
tableView->setColumnHidden(model->fieldIndex("id"), true);
tableView->verticalHeader()->setVisible(false);
tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
// Lock and prohibit resizing of the width of the columns
tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
tableView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
tableView->horizontalHeader()->setFixedHeight(tableView->rowHeight(0));
// increment by two to consider the frame
tableView->setFixedWidth(tableView->horizontalHeader()->length() +
tableView->verticalScrollBar()->sizeHint().width() + 2);
tableView->setMaximumHeight(tableView->verticalHeader()->length() +
tableView->horizontalHeader()->height() + 2);
authorComboBox->setModel(model->relationModel(authorIdx));
authorComboBox->setModelColumn(model->relationModel(authorIdx)->fieldIndex("name"));
genreComboBox->setModel(model->relationModel(genreIdx));
genreComboBox->setModelColumn(model->relationModel(genreIdx)->fieldIndex("name"));
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->setIconSize(iconSize);
}
void BookWindow::createMappings()
{
QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
mapper->setModel(model);
mapper->setItemDelegate(new BookDelegate(this));
mapper->addMapping(titleLineEdit, model->fieldIndex("title"));
mapper->addMapping(yearSpinBox, model->fieldIndex("year"));
mapper->addMapping(authorComboBox, authorIdx);
mapper->addMapping(genreComboBox, genreIdx);
mapper->addMapping(ratingComboBox, model->fieldIndex("rating"), "currentIndex");
connect(tableView->selectionModel(),
&QItemSelectionModel::currentRowChanged,
mapper,
&QDataWidgetMapper::setCurrentModelIndex
);
}
void BookWindow::createMenuBar()
{
QAction *quitAction = new QAction(tr("&Quit"), this);

View File

@ -7,9 +7,6 @@
#include <QtWidgets>
#include <QtSql>
#include "ui_bookwindow.h"
class BookWindow: public QMainWindow
{
Q_OBJECT
@ -21,11 +18,29 @@ private slots:
private:
void showError(const QSqlError &err);
Ui::BookWindow ui;
QSqlRelationalTableModel *model = nullptr;
int authorIdx = 0, genreIdx = 0;
void createLayout();
void createModel();
void configureWidgets();
void createMappings();
void createMenuBar();
QWidget *window = nullptr;
QGridLayout *gridLayout = nullptr;
QTableView *tableView = nullptr;
QLabel *titleLabel = nullptr;
QLineEdit *titleLineEdit = nullptr;
QLabel *authorLabel = nullptr;
QComboBox *authorComboBox = nullptr;
QLabel *genreLabel = nullptr;
QComboBox *genreComboBox = nullptr;
QLabel *yearLabel = nullptr;
QSpinBox *yearSpinBox = nullptr;
QLabel *ratingLabel = nullptr;
QComboBox *ratingComboBox = nullptr;
};
#endif

View File

@ -1,167 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BookWindow</class>
<widget class="QMainWindow" name="BookWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>601</width>
<height>420</height>
</rect>
</property>
<property name="windowTitle">
<string>Books</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QTableView" name="bookTable">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Details</string>
</property>
<layout class="QFormLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;b&gt;Title:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="titleEdit">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;b&gt;Author: &lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="authorEdit">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;b&gt;Genre:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="genreEdit">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&lt;b&gt;Year:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="yearEdit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>-1000</number>
</property>
<property name="maximum">
<number>2100</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;b&gt;Rating:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="ratingEdit">
<property name="maximum">
<number>5</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<tabstops>
<tabstop>bookTable</tabstop>
<tabstop>titleEdit</tabstop>
<tabstop>authorEdit</tabstop>
<tabstop>genreEdit</tabstop>
<tabstop>yearEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#0d0d0d"><path d="M8.41 18.138L12 15.845l3.59 2.323-.94-4.345 3.162-2.897-4.159-.392L12 6.43l-1.652 4.073-4.159.392 3.162 2.927-.94 4.315zm-1.346 3.696a1.04 1.04 0 0 1-1.567-1.104l1.318-6.033-4.476-4.11c-.665-.611-.293-1.726.604-1.808l5.866-.539 2.229-5.587c.348-.872 1.575-.872 1.923 0l2.229 5.587 5.866.539c.897.082 1.269 1.197.604 1.808l-4.476 4.11 1.318 6.033a1.04 1.04 0 0 1-1.567 1.104L12 18.681l-4.935 3.153z"/><path d="M12 5l-1.796 5.528H4.392l4.702 3.416-1.796 5.528L12 16.056l4.702 3.416-1.796-5.528 4.702-3.416h-5.812L12 5z"/></svg>

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path d="M8.41 18.138L12 15.845l3.59 2.323-.94-4.345 3.162-2.897-4.159-.392L12 6.43l-1.652 4.073-4.159.392 3.162 2.927-.94 4.315zm-1.346 3.696a1.04 1.04 0 0 1-1.567-1.104l1.318-6.033-4.476-4.11c-.665-.611-.293-1.726.604-1.808l5.866-.539 2.229-5.587c.348-.872 1.575-.872 1.923 0l2.229 5.587 5.866.539c.897.082 1.269 1.197.604 1.808l-4.476 4.11 1.318 6.033a1.04 1.04 0 0 1-1.567 1.104L12 18.681l-4.935 3.153z" fill="#0d0d0d"/></svg>

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB