aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/tutorials/portingguide/chapter2
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/doc/tutorials/portingguide/chapter2')
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.cpp96
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.h36
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.py101
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst93
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/createdb.py94
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books.pngbin0 -> 34658 bytes
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books_with_relation.pngbin0 -> 44122 bytes
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.pngbin0 -> 782 bytes
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter2/main.py26
9 files changed, 446 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.cpp b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.cpp
new file mode 100644
index 000000000..3d246667b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.cpp
@@ -0,0 +1,96 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "bookdelegate.h"
+
+#include <QtWidgets>
+
+BookDelegate::BookDelegate(QObject *parent)
+ : QSqlRelationalDelegate(parent), star(QPixmap(":images/star.png"))
+{
+}
+
+void BookDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ if (index.column() != 5) {
+ QStyleOptionViewItem opt = option;
+ // Since we draw the grid ourselves:
+ opt.rect.adjust(0, 0, -1, -1);
+ QSqlRelationalDelegate::paint(painter, opt, 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));
+
+ int rating = model->data(index, Qt::DisplayRole).toInt();
+ int width = star.width();
+ int height = star.height();
+ int x = option.rect.x();
+ int y = option.rect.y() + (option.rect.height() / 2) - (height / 2);
+ for (int i = 0; i < rating; ++i) {
+ painter->drawPixmap(x, y, star);
+ x += width;
+ }
+ // Since we draw the grid ourselves:
+ drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1));
+ }
+
+ 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);
+ // Since we draw the grid ourselves:
+ return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);
+}
+
+bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index)
+{
+ if (index.column() != 5)
+ return QSqlRelationalDelegate::editorEvent(event, model, option, index);
+
+ if (event->type() == QEvent::MouseButtonPress) {
+ QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
+ int stars = qBound(0, int(0.7 + qreal(mouseEvent->pos().x()
+ - option.rect.x()) / star.width()), 5);
+ model->setData(index, QVariant(stars));
+ // So that the selection can change:
+ return false;
+ }
+
+ return true;
+}
+
+QWidget *BookDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ if (index.column() != 4)
+ return QSqlRelationalDelegate::createEditor(parent, option, index);
+
+ // For editing the year, return a spinbox with a range from -1000 to 2100.
+ QSpinBox *sb = new QSpinBox(parent);
+ sb->setFrame(false);
+ sb->setMaximum(2100);
+ sb->setMinimum(-1000);
+
+ return sb;
+}
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.h b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.h
new file mode 100644
index 000000000..d0b157b39
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef BOOKDELEGATE_H
+#define BOOKDELEGATE_H
+
+#include <QModelIndex>
+#include <QPixmap>
+#include <QSize>
+#include <QSqlRelationalDelegate>
+
+QT_FORWARD_DECLARE_CLASS(QPainter)
+
+class BookDelegate : public QSqlRelationalDelegate
+{
+public:
+ BookDelegate(QObject *parent);
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+
+ QSize sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+
+ bool editorEvent(QEvent *event, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) override;
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+
+private:
+ QPixmap star;
+};
+
+#endif
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.py b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.py
new file mode 100644
index 000000000..2c2b80157
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import copy
+import os
+from pathlib import Path
+
+from PySide6.QtSql import QSqlRelationalDelegate
+from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
+ QStyle, QStyleOptionViewItem)
+from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
+from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+
+
+class BookDelegate(QSqlRelationalDelegate):
+ """Books delegate to rate the books"""
+
+ def __init__(self, parent=None):
+ QSqlRelationalDelegate.__init__(self, parent)
+ star_png = Path(__file__).parent / "images" / "star.png"
+ self.star = QPixmap(star_png)
+
+ def paint(self, painter, option, index):
+ """ Paint the items in the table.
+
+ If the item referred to by <index> is a StarRating, we
+ handle the painting ourselves. For the other items, we
+ let the base class handle the painting as usual.
+
+ In a polished application, we'd use a better check than
+ the column number to find out if we needed to paint the
+ stars, but it works for the purposes of this example.
+ """
+ if index.column() != 5:
+ # Since we draw the grid ourselves:
+ opt = copy.copy(option)
+ opt.rect = option.rect.adjusted(0, 0, -1, -1)
+ QSqlRelationalDelegate.paint(self, painter, opt, index)
+ else:
+ model = index.model()
+ if option.state & QStyle.State_Enabled:
+ if option.state & QStyle.State_Active:
+ color_group = QPalette.Normal
+ else:
+ color_group = QPalette.Inactive
+ else:
+ color_group = QPalette.Disabled
+
+ if option.state & QStyle.State_Selected:
+ painter.fillRect(option.rect,
+ option.palette.color(color_group, QPalette.Highlight))
+ rating = model.data(index, Qt.DisplayRole)
+ width = self.star.width()
+ height = self.star.height()
+ x = option.rect.x()
+ y = option.rect.y() + (option.rect.height() / 2) - (height / 2)
+ for i in range(rating):
+ painter.drawPixmap(x, y, self.star)
+ x += width
+
+ # Since we draw the grid ourselves:
+ self.drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1))
+
+ 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)
+
+ def sizeHint(self, option, index):
+ """ Returns the size needed to display the item in a QSize object. """
+ if index.column() == 5:
+ size_hint = QSize(5 * self.star.width(), self.star.height()) + QSize(1, 1)
+ return size_hint
+ # Since we draw the grid ourselves:
+ return QSqlRelationalDelegate.sizeHint(self, option, index) + QSize(1, 1)
+
+ def editorEvent(self, event, model, option, index):
+ if index.column() != 5:
+ return False
+
+ if event.type() == QEvent.MouseButtonPress:
+ mouse_pos = event.pos()
+ new_stars = int(0.7 + (mouse_pos.x() - option.rect.x()) / self.star.width())
+ stars = max(0, min(new_stars, 5))
+ model.setData(index, stars)
+ # So that the selection can change
+ return False
+
+ return True
+
+ def createEditor(self, parent, option, index):
+ if index.column() != 4:
+ return QSqlRelationalDelegate.createEditor(self, parent, option, index)
+
+ # For editing the year, return a spinbox with a range from -1000 to 2100.
+ spinbox = QSpinBox(parent)
+ spinbox.setFrame(False)
+ spinbox.setMaximum(2100)
+ spinbox.setMinimum(-1000)
+ return spinbox
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst b/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst
new file mode 100644
index 000000000..83ba3357b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/chapter2.rst
@@ -0,0 +1,93 @@
+Chapter 2: ``bookdelegate.cpp`` to ``bookdelegate.py``
+*******************************************************
+
+Now that your database is in place, port the C++ code for the
+``BookDelegate`` class. This class offers a delegate to present
+and edit the data in a ``QTableView``. It inherits
+``QSqlRelationalDelegate`` interface, which offers features
+specific for handling relational databases, such as a combobox
+editor for foreign key fields. To begin with, create
+``bookdelegate.py`` and add the following imports to it:
+
+.. literalinclude:: bookdelegate.py
+ :language: python
+ :linenos:
+ :lines: 3-10
+
+After the necessary ``import`` statements, port the
+constructor code for the ``BookDelegate`` class. Both
+the C++ and Python versions of this code initialize a
+``QSqlRelationalDelegate`` and ``QPixmap`` instance.
+Here is how they look:
+
+C++ version
+-------------
+
+.. literalinclude:: bookdelegate.cpp
+ :language: c++
+ :linenos:
+ :lines: 17-22
+
+Python version
+---------------
+
+.. literalinclude:: bookdelegate.py
+ :language: python
+ :linenos:
+ :lines: 10-17
+
+.. note:: The Python version loads the ``QPixmap`` using
+ the absolute path of ``star.png`` in the local
+ filesystem.
+
+As the default functionality offered by the
+``QSqlRelationalDelegate`` is not enough to present
+the books data, you must reimplement a few functions.
+For example, painting stars to represent the rating for
+each book in the table. Here is how the reimplemented
+code looks like:
+
+C++ version (bookdelegate)
+--------------------------
+
+.. literalinclude:: bookdelegate.cpp
+ :language: c++
+ :linenos:
+ :lines: 22-
+
+Python version (bookdelegate)
+-----------------------------
+
+.. literalinclude:: bookdelegate.py
+ :language: python
+ :linenos:
+ :lines: 18-
+
+Now that the delegate is in place, run the following
+``main.py`` to see how the data is presented:
+
+.. literalinclude:: main.py
+ :language: python
+ :linenos:
+ :lines: 3-
+
+Here is how the application will look when you run it:
+
+.. image:: images/chapter2_books.png
+ :alt: Books table data
+
+The only difference you'll notice now in comparison to
+:doc:`chapter 1 <../chapter1/chapter1>` is that the
+``rating`` column looks different.
+
+Try improving the table even further by adding these
+features:
+
+* Title for each column
+* SQL relation for the ``author_id`` and ``genre_id`` columns
+* Set a title to the window
+
+With these features, this is how your table will look like:
+
+.. image:: images/chapter2_books_with_relation.png
+ :alt: Books table with SQL relation
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/createdb.py b/sources/pyside6/doc/tutorials/portingguide/chapter2/createdb.py
new file mode 100644
index 000000000..da7d201a8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/createdb.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from datetime import date
+
+
+def add_book(q, title, year, authorId, genreId, rating):
+ q.addBindValue(title)
+ q.addBindValue(year)
+ q.addBindValue(authorId)
+ q.addBindValue(genreId)
+ q.addBindValue(rating)
+ q.exec_()
+
+
+def add_genre(q, name):
+ q.addBindValue(name)
+ q.exec_()
+ return q.lastInsertId()
+
+
+def add_author(q, name, birthdate):
+ q.addBindValue(name)
+ q.addBindValue(str(birthdate))
+ q.exec_()
+ return q.lastInsertId()
+
+BOOKS_SQL = """
+ create table books(id integer primary key, title varchar, author integer,
+ genre integer, year integer, rating integer)
+ """
+AUTHORS_SQL = """
+ create table authors(id integer primary key, name varchar, birthdate text)
+ """
+GENRES_SQL = """
+ create table genres(id integer primary key, name varchar)
+ """
+INSERT_AUTHOR_SQL = """
+ insert into authors(name, birthdate) values(?, ?)
+ """
+INSERT_GENRE_SQL = """
+ insert into genres(name) values(?)
+ """
+INSERT_BOOK_SQL = """
+ insert into books(title, year, author, genre, rating)
+ values(?, ?, ?, ?, ?)
+ """
+
+def init_db():
+ """
+ init_db()
+ Initializes the database.
+ If tables "books" and "authors" are already in the database, do nothing.
+ Return value: None or raises ValueError
+ The error value is the QtSql error instance.
+ """
+ def check(func, *args):
+ if not func(*args):
+ raise ValueError(func.__self__.lastError())
+ db = QSqlDatabase.addDatabase("QSQLITE")
+ db.setDatabaseName(":memory:")
+
+ check(db.open)
+
+ q = QSqlQuery()
+ check(q.exec_, BOOKS_SQL)
+ check(q.exec_, AUTHORS_SQL)
+ check(q.exec_, GENRES_SQL)
+ check(q.prepare, INSERT_AUTHOR_SQL)
+
+ asimovId = add_author(q, "Isaac Asimov", date(1920, 2, 1))
+ greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
+ pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
+
+ check(q.prepare,INSERT_GENRE_SQL)
+ sfiction = add_genre(q, "Science Fiction")
+ fiction = add_genre(q, "Fiction")
+ fantasy = add_genre(q, "Fantasy")
+
+ check(q.prepare,INSERT_BOOK_SQL)
+ add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
+ add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
+ add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
+ add_book(q, "Foundation's Edge", 1982, asimovId, sfiction, 3)
+ add_book(q, "Foundation and Earth", 1986, asimovId, sfiction, 4)
+ add_book(q, "Prelude to Foundation", 1988, asimovId, sfiction, 3)
+ add_book(q, "Forward the Foundation", 1993, asimovId, sfiction, 3)
+ add_book(q, "The Power and the Glory", 1940, greeneId, fiction, 4)
+ add_book(q, "The Third Man", 1950, greeneId, fiction, 5)
+ add_book(q, "Our Man in Havana", 1958, greeneId, fiction, 4)
+ add_book(q, "Guards! Guards!", 1989, pratchettId, fantasy, 3)
+ add_book(q, "Night Watch", 2002, pratchettId, fantasy, 3)
+ add_book(q, "Going Postal", 2004, pratchettId, fantasy, 3)
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books.png b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books.png
new file mode 100644
index 000000000..e456b7d8f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books_with_relation.png b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books_with_relation.png
new file mode 100644
index 000000000..82a5f449c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books_with_relation.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.png b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.png
new file mode 100644
index 000000000..87f4464bd
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/main.py b/sources/pyside6/doc/tutorials/portingguide/chapter2/main.py
new file mode 100644
index 000000000..3cc55fa46
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/main.py
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtCore import Qt
+from PySide6.QtSql import QSqlQueryModel
+from PySide6.QtWidgets import QTableView, QApplication
+
+import createdb
+from bookdelegate import BookDelegate
+
+if __name__ == "__main__":
+ app = QApplication()
+ createdb.init_db()
+
+ model = QSqlQueryModel()
+ model.setQuery("select title, author, genre, year, rating from books")
+
+ table = QTableView()
+ table.setModel(model)
+ table.setItemDelegate(BookDelegate())
+ table.resize(800, 600)
+ table.show()
+
+ sys.exit(app.exec())