aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/tutorials
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/doc/tutorials')
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/clickablebutton.pngbin0 -> 7059 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst95
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/dialog.pngbin0 -> 21263 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/dialog.rst149
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/greenapplication.pngbin0 -> 2089 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons.pngbin0 -> 1599 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons.zipbin0 -> 6156 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons/forward.pngbin0 -> 1113 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons/pause.pngbin0 -> 1001 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons/play.pngbin0 -> 970 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons/previous.pngbin0 -> 1050 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/icons/stop.pngbin0 -> 1064 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/player-new.pngbin0 -> 3369 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/player.pngbin0 -> 2631 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/qml.rst79
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst173
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst263
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/signals_slots.pngbin0 -> 14787 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/style.qss23
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/tablewidget.pngbin0 -> 14509 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst97
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/translations.pngbin0 -> 25318 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/translations.rst232
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/treewidget.pngbin0 -> 3709 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/treewidget.rst79
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/uifiles.pngbin0 -> 86503 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/uifiles.rst304
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgets.pngbin0 -> 23233 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgets.rst52
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.pngbin0 -> 11004 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.pngbin0 -> 1206 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.pngbin0 -> 1596 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.pngbin0 -> 24791 bytes
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py58
-rw-r--r--sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst171
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/add_chart.rst20
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst32
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst70
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/all_hour.csv8
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py18
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py41
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py51
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py31
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py53
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py43
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py32
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py51
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py53
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py54
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py32
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py51
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py55
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py95
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py32
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py51
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/filter_data.rst29
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.pngbin0 -> 17272 bytes
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.pngbin0 -> 49419 bytes
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/index.rst31
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst27
-rw-r--r--sources/pyside6/doc/tutorials/datavisualize/read_data.rst41
-rw-r--r--sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst126
-rw-r--r--sources/pyside6/doc/tutorials/debugging/qml_debugging.rst32
-rw-r--r--sources/pyside6/doc/tutorials/debugging/qtcreator/breakpoint_cpp.pngbin0 -> 42765 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_create.pngbin0 -> 40278 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_run_config.pngbin0 -> 43483 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst39
-rw-r--r--sources/pyside6/doc/tutorials/debugging/qtcreator/start_debugging_without_deployment.pngbin0 -> 49267 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/vscode/audioformat_cpp.pngbin0 -> 43650 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/vscode/audioformat_wrapper.pngbin0 -> 40677 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/vscode/breakpoint_gdb.pngbin0 -> 33684 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/vscode/find_process_gdb.pngbin0 -> 42303 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/vscode/python_set_interpreter.pngbin0 -> 4664 bytes
-rw-r--r--sources/pyside6/doc/tutorials/debugging/vscode/vscode.rst192
-rw-r--r--sources/pyside6/doc/tutorials/expenses/expenses.rst285
-rw-r--r--sources/pyside6/doc/tutorials/expenses/expenses_tool.pngbin0 -> 47826 bytes
-rw-r--r--sources/pyside6/doc/tutorials/expenses/main.py153
-rw-r--r--sources/pyside6/doc/tutorials/expenses/main_snake_prop.py177
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/01-expenses.py22
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/02-expenses.py31
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/03-expenses.py31
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/04-expenses.py45
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/05-expenses.py69
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/06-expenses.py89
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/07-expenses.py112
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/08-expenses.py123
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/09-expenses.py131
-rw-r--r--sources/pyside6/doc/tutorials/expenses/steps/10-expenses.py153
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml196
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc16
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc15
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg2
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_closed.svg38
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_open.svg38
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/generic_file.svg38
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/globe.svg38
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/info_sign.svg38
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/leaf.svg7
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/light_bulb.svg43
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/qt_logo.svg26
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/read.svg38
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/resize.svg6
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml93
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ColorScheme.qml118
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml160
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml156
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml45
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml177
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml23
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml146
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/WindowDragHandler.qml16
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir11
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py116
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md210
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject31
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/main.py50
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/resources/Colors.qml22
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/resources/colorscheme.pngbin0 -> 27675 bytes
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.gifbin0 -> 2082470 bytes
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webpbin0 -> 23628 bytes
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/scheme_manager.py97
-rw-r--r--sources/pyside6/doc/tutorials/extendedexplorer/schemes.json82
-rw-r--r--sources/pyside6/doc/tutorials/index.rst206
-rw-r--r--sources/pyside6/doc/tutorials/modelviewprogramming/qlistview-dnd.py137
-rw-r--r--sources/pyside6/doc/tutorials/modelviewprogramming/simplemodel-use.py44
-rw-r--r--sources/pyside6/doc/tutorials/modelviewprogramming/stringlistmodel.py124
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst89
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter1/createdb.py94
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter1/images/chapter1_books.pngbin0 -> 25391 bytes
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter1/initdb.h113
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter1/main.py22
-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
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate-old.py101
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate.py96
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/books.qrc5
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.cpp124
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.py99
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.ui149
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst121
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/createdb.py94
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/images/chapter3-books.pngbin0 -> 34624 bytes
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.pngbin0 -> 782 bytes
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/main-old.py15
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/chapter3/main.py16
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/hello_world_ex.py39
-rw-r--r--sources/pyside6/doc/tutorials/portingguide/index.rst194
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/logo.pngbin0 -> 6208 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/main.py46
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/newpyproject.pngbin0 -> 16091 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/projectsmode.pngbin0 -> 8848 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/pyprojname.pngbin0 -> 8068 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.pngbin0 -> 10062 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/qmlapplication.pngbin0 -> 10950 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst132
-rw-r--r--sources/pyside6/doc/tutorials/qmlapp/view.qml56
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/main.py77
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst128
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/qtquickcontrols2.conf10
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/style.qrc5
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/textproperties_default.pngbin0 -> 19347 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.pngbin0 -> 21170 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlintegration/view.qml160
-rw-r--r--sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml98
-rw-r--r--sources/pyside6/doc/tutorials/qmlsqlintegration/example_list_view.pngbin0 -> 6954 bytes
-rw-r--r--sources/pyside6/doc/tutorials/qmlsqlintegration/main.py52
-rw-r--r--sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst232
-rw-r--r--sources/pyside6/doc/tutorials/qmlsqlintegration/sqlDialog.py116
175 files changed, 10118 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.png b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.png
new file mode 100644
index 000000000..bfdc23fe0
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst
new file mode 100644
index 000000000..c5464640b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst
@@ -0,0 +1,95 @@
+Using a Simple Button
+=====================
+
+In this tutorial, we'll show you how to handle **signals and slots**
+using Qt for Python. **Signals and slots** is a Qt feature that lets
+your graphical widgets communicate with other graphical widgets or
+your python code. Our application creates a button that logs the
+`Button clicked, Hello!` message to the python console each time you
+click it.
+
+Let's start by importing the necessary PySide6 classes and python
+`sys` module:
+::
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QPushButton
+ from PySide6.QtCore import Slot
+
+Let's also create a python function that logs the message to the
+console:
+::
+
+ # Greetings
+ @Slot()
+ def say_hello():
+ print("Button clicked, Hello!")
+
+.. note:: The `@Slot()` is a decorator that identifies a function as
+ a slot. It is not important to understand why for now,
+ but use it always to avoid unexpected behavior.
+
+Now, as mentioned in previous examples you must create the
+`QApplication` to run your PySide6 code:
+::
+
+ # Create the Qt Application
+ app = QApplication(sys.argv)
+
+Let's create the clickable button, which is a `QPushButton` instance.
+To label the button, we pass a python string to the constructor:
+::
+
+ # Create a button
+ button = QPushButton("Click me")
+
+Before we show the button, we must connect it to the `say_hello()`
+function that we defined earlier. There are two ways of doing this;
+using the old style or the new style, which is more pythonic. Let's
+use the new style in this case. You can find more information about
+both these styles in the
+`Signals and Slots in PySide6 <https://wiki.qt.io/Qt_for_Python_Signals_and_Slots>`_
+wiki page.
+
+The `QPushButton` has a predefined signal called **clicked**, which
+is triggered every time the button is clicked. We'll connect this
+signal to the `say_hello()` function:
+::
+
+ # Connect the button to the function
+ button.clicked.connect(say_hello)
+
+Finally, we show the button and start the Qt main loop:
+
+.. code-block:: python
+
+ # Show the button
+ button.show()
+ # Run the main Qt loop
+ app.exec()
+
+Here is the complete code for this example:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QPushButton
+ from PySide6.QtCore import Slot
+
+ @Slot()
+ def say_hello():
+ print("Button clicked, Hello!")
+
+ # Create the Qt Application
+ app = QApplication(sys.argv)
+ # Create a button, connect it and show it
+ button = QPushButton("Click me")
+ button.clicked.connect(say_hello)
+ button.show()
+ # Run the main Qt loop
+ app.exec()
+
+After a few clicks, you will get something like this on your terminal:
+
+.. image:: clickablebutton.png
+ :alt: Clickable Button Example
diff --git a/sources/pyside6/doc/tutorials/basictutorial/dialog.png b/sources/pyside6/doc/tutorials/basictutorial/dialog.png
new file mode 100644
index 000000000..ad5690927
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/dialog.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/dialog.rst b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst
new file mode 100644
index 000000000..b7712672b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst
@@ -0,0 +1,149 @@
+Creating a Dialog Application
+=============================
+
+This tutorial shows how to build a simple dialog with some
+basic widgets. The idea is to let users provide their name
+in a ``QLineEdit``, and the dialog greets them on click of a
+``QPushButton``.
+
+Let us just start with a simple stub that creates and shows
+a dialog. This stub is updated during the course of this
+tutorial, but you can use this stub as is if you need to:
+::
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QDialog, QLineEdit, QPushButton
+
+ class Form(QDialog):
+
+ def __init__(self, parent=None):
+ super(Form, self).__init__(parent)
+ self.setWindowTitle("My Form")
+
+
+ if __name__ == '__main__':
+ # Create the Qt Application
+ app = QApplication(sys.argv)
+ # Create and show the form
+ form = Form()
+ form.show()
+ # Run the main Qt loop
+ sys.exit(app.exec())
+
+The imports aren't new to you, the same for the creation of the
+``QApplication`` and the execution of the Qt main loop.
+The only novelty here is the **class definition**.
+
+You can create any class that subclasses PySide6 widgets.
+In this case, we are subclassing ``QDialog`` to define a custom
+dialog, which we name as **Form**. We have also implemented the
+``init()`` method that calls the ``QDialog``'s init method with the
+parent widget, if any. Also, the new ``setWindowTitle()`` method
+just sets the title of the dialog window. In ``main()``, you can see
+that we are creating a *Form object* and showing it to the world.
+
+Create the Widgets
+------------------
+
+We are going to create two widgets: a ``QLineEdit`` where users can
+enter their name, and a ``QPushButton`` that prints the contents of
+the ``QLineEdit``.
+So, let's add the following code to the ``init()`` method of our Form:
+::
+
+ # Create widgets
+ self.edit = QLineEdit("Write my name here..")
+ self.button = QPushButton("Show Greetings")
+
+It's obvious from the code that both widgets will show the corresponding
+texts.
+
+Create a layout to organize the Widgets
+---------------------------------------
+
+Qt comes with layout-support that helps you organize the widgets
+in your application. In this case, let's use ``QVBoxLayout`` to lay out
+the widgets vertically. Add the following code to the ``init()`` method,
+after creating the widgets:
+::
+
+ # Create layout and add widgets
+ layout = QVBoxLayout(self)
+ layout.addWidget(self.edit)
+ layout.addWidget(self.button)
+
+So, we create the layout, add the widgets with ``addWidget()``.
+
+Create the function to greet and connect the Button
+---------------------------------------------------
+
+Finally, we just have to add a function to our custom **Form**
+and *connect* our button to it. Our function will be a part of
+the Form, so you have to add it after the ``init()`` function:
+::
+
+ # Greets the user
+ def greetings(self):
+ print(f"Hello {self.edit.text()}")
+
+Our function just prints the contents of the ``QLineEdit`` to the
+python console. We have access to the text by means of the
+``QLineEdit.text()`` method.
+
+Now that we have everything, we just need to *connect* the
+``QPushButton`` to the ``Form.greetings()`` method. To do so, add the
+following line to the ``init()`` method:
+::
+
+ # Add button signal to greetings slot
+ self.button.clicked.connect(self.greetings)
+
+Once executed, you can enter your name in the ``QLineEdit`` and watch
+the console for greetings.
+
+Complete code
+-------------
+
+Here is the complete code for this tutorial:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import (QLineEdit, QPushButton, QApplication,
+ QVBoxLayout, QDialog)
+
+ class Form(QDialog):
+
+ def __init__(self, parent=None):
+ super(Form, self).__init__(parent)
+ # Create widgets
+ self.edit = QLineEdit("Write my name here")
+ self.button = QPushButton("Show Greetings")
+ # Create layout and add widgets
+ layout = QVBoxLayout()
+ layout.addWidget(self.edit)
+ layout.addWidget(self.button)
+ # Set dialog layout
+ self.setLayout(layout)
+ # Add button signal to greetings slot
+ self.button.clicked.connect(self.greetings)
+
+ # Greets the user
+ def greetings(self):
+ print(f"Hello {self.edit.text()}")
+
+ if __name__ == '__main__':
+ # Create the Qt Application
+ app = QApplication(sys.argv)
+ # Create and show the form
+ form = Form()
+ form.show()
+ # Run the main Qt loop
+ sys.exit(app.exec())
+
+
+When you execute the code, and write down your name,
+the button will display messages on the terminal:
+
+.. image:: dialog.png
+ :alt: Simple Dialog Example
diff --git a/sources/pyside6/doc/tutorials/basictutorial/greenapplication.png b/sources/pyside6/doc/tutorials/basictutorial/greenapplication.png
new file mode 100644
index 000000000..29ea0a701
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/greenapplication.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons.png b/sources/pyside6/doc/tutorials/basictutorial/icons.png
new file mode 100644
index 000000000..a5a554eba
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons.zip b/sources/pyside6/doc/tutorials/basictutorial/icons.zip
new file mode 100644
index 000000000..e279e37b8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons.zip
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png b/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png
new file mode 100644
index 000000000..c7a532dfe
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png b/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png
new file mode 100644
index 000000000..d0beadb43
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/play.png b/sources/pyside6/doc/tutorials/basictutorial/icons/play.png
new file mode 100644
index 000000000..345685337
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons/play.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png b/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png
new file mode 100644
index 000000000..979f18565
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png b/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png
new file mode 100644
index 000000000..1e88ded3a
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/player-new.png b/sources/pyside6/doc/tutorials/basictutorial/player-new.png
new file mode 100644
index 000000000..8e45c757d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/player-new.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/player.png b/sources/pyside6/doc/tutorials/basictutorial/player.png
new file mode 100644
index 000000000..0563d3223
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/player.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/qml.rst b/sources/pyside6/doc/tutorials/basictutorial/qml.rst
new file mode 100644
index 000000000..49cd3d94a
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/qml.rst
@@ -0,0 +1,79 @@
+Your First QtQuick/QML Application
+**********************************
+
+QML_ is a declarative language that lets you develop applications
+faster than with traditional languages. It is ideal for designing the
+UI of your application because of its declarative nature. In QML, a
+user interface is specified as a tree of objects with properties. In
+this tutorial, we will show how to make a simple "Hello World"
+application with PySide6 and QML.
+
+A PySide6/QML application consists, at least, of two different files -
+a file with the QML description of the user interface, and a python file
+that loads the QML file. To make things easier, let's save both files in
+the same directory.
+
+Here is a simple QML file called :code:`view.qml`:
+
+.. code-block:: javascript
+
+ import QtQuick
+
+ Rectangle {
+ id: main
+ width: 200
+ height: 200
+ color: "green"
+
+ Text {
+ text: "Hello World"
+ anchors.centerIn: main
+ }
+ }
+
+We start by importing :code:`QtQuick`, which is a QML module.
+
+The rest of the QML code is pretty straightforward for those who
+have previously used HTML or XML files. Basically, we are creating
+a green rectangle with the size `200*200`, and adding a Text element
+that reads "Hello World". The code :code:`anchors.centerIn: main` makes
+the text appear centered within the object with :code:`id: main`,
+which is the Rectangle in this case.
+
+Now, let's see how the code looks on the PySide6.
+Let's call it :code:`main.py`:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication
+ from PySide6.QtQuick import QQuickView
+
+ if __name__ == "__main__":
+ app = QApplication()
+ view = QQuickView()
+
+ view.setSource("view.qml")
+ view.show()
+ sys.exit(app.exec())
+
+If you are already familiar with PySide6 and have followed our
+tutorials, you have already seen much of this code.
+The only novelties are that you must :code:`import QtQuick` and set the
+source of the :code:`QQuickView` object to the URL of your QML file.
+Then, similar to what you do with any Qt widget, you call
+:code:`QQuickView.show()`.
+
+.. note:: If you are programming for desktop, you should consider
+ adding `view.setResizeMode(QQuickView.SizeRootObjectToView)`
+ before showing the view.
+
+When you execute the :code:`main.py` script, you will see the following
+application:
+
+
+.. image:: greenapplication.png
+ :alt: Simple QML and Python example
+ :align: center
+
+.. _QML: https://doc.qt.io/qt-6/qmlapplications.html
diff --git a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst
new file mode 100644
index 000000000..858293beb
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst
@@ -0,0 +1,173 @@
+.. _using_qrc_files:
+
+Using ``.qrc`` Files (``pyside6-rcc``)
+**************************************
+
+The `Qt Resource System`_ is a mechanism for storing binary files
+in an application.
+
+The files will be embedded into the application and be acessible for the
+``QFile`` class and the constructors of the ``QIcon`` and ``QPixmap``
+classes taking a file name by using a special file name starting with ``:/``.
+
+The most common uses are for custom images, icons, fonts, among others.
+
+In this tutorial you will learn how to load custom images as button icons.
+
+For inspiration, we will try to adapt the multimedia player example
+from Qt.
+
+As you can see on the following image, the ``QPushButton`` that are used
+for the media actions (play, pause, stop, and so on) are using the
+default icons meant for such actions.
+
+.. image:: player.png
+ :alt: Multimedia Player Qt Example
+
+You could make the application more attractive by designing the icons,
+but in case you don't want to design them, you can download and use them.
+
+:download:`Download icons <icons.zip>`
+
+.. image:: icons.png
+ :alt: New Multimedia icons
+
+You can find more information about the ``rcc`` command, and ``.qrc`` file
+format, and the resource system in general in the `Qt Resource System`_
+site.
+
+
+The ``.qrc`` file
+=================
+
+Before running any command, add information about the resources to a ``.qrc``
+file.
+In the following example, notice how the resources are listed in ``icons.qrc``
+
+::
+
+ <!DOCTYPE RCC><RCC version="1.0">
+ <qresource>
+ <file>icons/play.png</file>
+ <file>icons/pause.png</file>
+ <file>icons/stop.png</file>
+ <file>icons/previous.png</file>
+ <file>icons/forward.png</file>
+ </qresource>
+ </RCC>
+
+
+Generating a Python file
+=========================
+
+Now that the ``icons.qrc`` file is ready, use the ``pyside6-rcc`` tool to generate
+a Python class containing the binary information about the resources
+
+To do this, we need to run::
+
+ pyside6-rcc icons.qrc -o rc_icons.py
+
+The ``-o`` option lets you specify the output filename,
+which is ``rc_icons.py`` in this case.
+
+To use the generated file, add the following import at the top of your main Python file::
+
+ import rc_icons
+
+
+Changes in the code
+===================
+
+As you are modifying an existing example, you need to modify the following
+lines:
+
+.. code-block:: python
+
+ from PySide6.QtGui import QIcon, QKeySequence
+ playIcon = self.style().standardIcon(QStyle.SP_MediaPlay)
+ previousIcon = self.style().standardIcon(QStyle.SP_MediaSkipBackward)
+ pauseIcon = self.style().standardIcon(QStyle.SP_MediaPause)
+ nextIcon = self.style().standardIcon(QStyle.SP_MediaSkipForward)
+ stopIcon = self.style().standardIcon(QStyle.SP_MediaStop)
+
+and replace them with the following:
+
+.. code-block:: python
+
+ from PySide6.QtGui import QIcon, QKeySequence, QPixmap
+ playIcon = QIcon(QPixmap(":/icons/play.png"))
+ previousIcon = QIcon(QPixmap(":/icons/previous.png"))
+ pauseIcon = QIcon(QPixmap(":/icons/pause.png"))
+ nextIcon = QIcon(QPixmap(":/icons/forward.png"))
+ stopIcon = QIcon(QPixmap(":/icons/stop.png"))
+
+This ensures that the new icons are used instead of the default ones provided
+by the application theme.
+Notice that the lines are not consecutive, but are in different parts
+of the file.
+
+After all your imports, add the following
+
+.. code-block:: python
+
+ import rc_icons
+
+Now, the constructor of your class should look like this:
+
+.. code-block:: python
+
+ def __init__(self):
+ super(MainWindow, self).__init__()
+
+ self.playlist = QMediaPlaylist()
+ self.player = QMediaPlayer()
+
+ toolBar = QToolBar()
+ self.addToolBar(toolBar)
+
+ fileMenu = self.menuBar().addMenu("&File")
+ openAction = QAction(QIcon.fromTheme("document-open"),
+ "&Open...", self, shortcut=QKeySequence.Open,
+ triggered=self.open)
+ fileMenu.addAction(openAction)
+ exitAction = QAction(QIcon.fromTheme("application-exit"), "E&xit",
+ self, shortcut="Ctrl+Q", triggered=self.close)
+ fileMenu.addAction(exitAction)
+
+ playMenu = self.menuBar().addMenu("&Play")
+ playIcon = QIcon(QPixmap(":/icons/play.png"))
+ self.playAction = toolBar.addAction(playIcon, "Play")
+ self.playAction.triggered.connect(self.player.play)
+ playMenu.addAction(self.playAction)
+
+ previousIcon = QIcon(QPixmap(":/icons/previous.png"))
+ self.previousAction = toolBar.addAction(previousIcon, "Previous")
+ self.previousAction.triggered.connect(self.previousClicked)
+ playMenu.addAction(self.previousAction)
+
+ pauseIcon = QIcon(QPixmap(":/icons/pause.png"))
+ self.pauseAction = toolBar.addAction(pauseIcon, "Pause")
+ self.pauseAction.triggered.connect(self.player.pause)
+ playMenu.addAction(self.pauseAction)
+
+ nextIcon = QIcon(QPixmap(":/icons/forward.png"))
+ self.nextAction = toolBar.addAction(nextIcon, "Next")
+ self.nextAction.triggered.connect(self.playlist.next)
+ playMenu.addAction(self.nextAction)
+
+ stopIcon = QIcon(QPixmap(":/icons/stop.png"))
+ self.stopAction = toolBar.addAction(stopIcon, "Stop")
+ self.stopAction.triggered.connect(self.player.stop)
+ playMenu.addAction(self.stopAction)
+
+ # many lines were omitted
+
+Executing the example
+=====================
+
+Run the application by calling ``python main.py`` to checkout the new icon-set:
+
+.. image:: player-new.png
+ :alt: New Multimedia Player Qt Example
+
+.. _`Qt Resource System`: https://doc.qt.io/qt-5/resources.html
diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst
new file mode 100644
index 000000000..0bfd9e276
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/signals_and_slots.rst
@@ -0,0 +1,263 @@
+.. _signals-and-slots:
+
+Signals and Slots
+=================
+
+Due to the nature of Qt, ``QObject``\s require a way to communicate, and that's
+the reason for this mechanism to be a **central feature of Qt**.
+
+In simple terms, you can understand **Signal and Slots** in the same way you
+interact with the lights in your house. When you move the light switch
+(signal) you get a result which may be that your light bulbs are switched
+on/off (slot).
+
+While developing interfaces, you can get a real example by the effect of
+clicking a button: the 'click' will be the signal, and the slot will be what
+happens when that button is clicked, like closing a window, saving a document,
+etc.
+
+.. note::
+ If you have experience with other frameworks or toolkits, it's likely
+ that you read a concept called 'callback'. Leaving the implementation
+ details aside, a callback will be related to a notification function,
+ passing a pointer to a function in case it's required due to the events
+ that happen in your program. This approach might sound similar, but
+ there are essential differences that make it an unintuitive approach,
+ like ensuring the type correctness of callback arguments, and some others.
+
+All classes that inherit from ``QObject`` or one of its subclasses, like
+``QWidget`` can contain signals and slots. **Signals are emitted by objects**
+when they change their state in a way that may be interesting to other objects.
+This is all the object does to communicate. It does not know or care whether
+anything is receiving the signals it emits. This is true information
+encapsulation, and ensures that the object can be used as a software component.
+
+**Slots can be used for receiving signals**, but they are also normal member
+functions. Just as an object does not know if anything receives its signals,
+a slot does not know if it has any signals connected to it. This ensures that
+truly independent components can be created with Qt.
+
+You can connect as many signals as you want to a single slot, and a signal can
+be connected to as many slots as you need. It is even possible to connect
+a signal directly to another signal. (This will emit the second signal
+immediately whenever the first is emitted.)
+
+Qt's widgets have many predefined signals and slots. For example,
+``QAbstractButton`` (base class of buttons in Qt) has a ``clicked()``
+signal and ``QLineEdit`` (single line input field) has a slot named
+``clear()``. So, a text input field with a button to clear the text
+could be implemented by placing a ``QToolButton`` to the right of the
+``QLineEdit`` and connecting its ``clicked()`` signal to the slot
+``clear()``. This is done using the ``connect()`` method of the signal:
+
+.. code-block:: python
+
+ button = QToolButton()
+ line_edit = QLineEdit()
+ button.clicked.connect(line_edit.clear)
+
+``connect()`` returns a ``QMetaObject.Connection`` object, which can be
+used with the ``disconnect()`` method to sever the connection.
+
+Signals can also be connected to free functions:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QPushButton
+
+
+ def function():
+ print("The 'function' has been called!")
+
+ app = QApplication()
+ button = QPushButton("Call function")
+ button.clicked.connect(function)
+ button.show()
+ sys.exit(app.exec())
+
+Connections can be spelled out in code or, for widget forms,
+designed in the
+`Signal-Slot Editor <https://doc.qt.io/qt-6/designer-connection-mode.html>`_
+of *Qt Widgets Designer*.
+
+The Signal Class
+----------------
+
+When writing classes in Python, signals are declared as class level
+variables of the class ``QtCore.Signal()``. A QWidget-based button
+that emits a ``clicked()`` signal could look as
+follows:
+
+.. code-block:: python
+
+ from PySide6.QtCore import Qt, Signal
+ from PySide6.QtWidgets import QWidget
+
+ class Button(QWidget):
+
+ clicked = Signal(Qt.MouseButton)
+
+ ...
+
+ def mousePressEvent(self, event):
+ self.clicked.emit(event.button())
+
+The constructor of ``Signal`` takes a tuple or a list of Python types
+and C types:
+
+.. code-block:: python
+
+ signal1 = Signal(int) # Python types
+ signal2 = Signal(QUrl) # Qt Types
+ signal3 = Signal(int, str, int) # more than one type
+ signal4 = Signal((float,), (QDate,)) # optional types
+
+In addition to that, it can receive also a named argument ``name`` that defines
+the signal name. If nothing is passed, the new signal will have the same name
+as the variable that it is being assigned to.
+
+.. code-block:: python
+
+ # TODO
+ signal5 = Signal(int, name='rangeChanged')
+ # ...
+ rangeChanged.emit(...)
+
+Another useful option of ``Signal`` is the arguments name,
+useful for QML applications to refer to the emitted values by name:
+
+.. code-block:: python
+
+ sumResult = Signal(int, arguments=['sum'])
+
+.. code-block:: javascript
+
+ Connections {
+ target: ...
+ function onSumResult(sum) {
+ // do something with 'sum'
+ }
+
+
+.. _slot-decorator:
+
+The Slot Class
+--------------
+
+Slots in QObject-derived classes should be indicated by the decorator
+``@QtCore.Slot()``. Again, to define a signature just pass the types
+similar to the ``QtCore.Signal()`` class.
+
+.. code-block:: python
+
+ @Slot(str)
+ def slot_function(self, s):
+ ...
+
+
+``Slot()`` also accepts a ``name`` and a ``result`` keyword.
+The ``result`` keyword defines the type that will be returned and can be a C or
+Python type. The ``name`` keyword behaves the same way as in ``Signal()``. If
+nothing is passed as name then the new slot will have the same name as the
+function that is being decorated.
+
+We recommend marking all methods used by signal connections with a
+``@QtCore.Slot()`` decorator. Not doing causes run-time overhead due to the
+method being added to the ``QMetaObject`` when creating the connection. This is
+particularly important for ``QObject`` classes registered with QML, where
+missing decorators can introduce bugs.
+
+Missing decorators can be diagnosed by setting activating warnings of the
+logging category ``qt.pyside.libpyside``; for example by setting the
+environment variable:
+
+.. code-block:: bash
+
+ export QT_LOGGING_RULES="qt.pyside.libpyside.warning=true"
+
+.. _overloading-signals-and-slots:
+
+Overloading Signals and Slots with Different Types
+--------------------------------------------------
+
+It is actually possible to use signals and slots of the same name with different
+parameter type lists. This is legacy from Qt 5 and not recommended for new code.
+In Qt 6, signals have distinct names for different types.
+
+The following example uses two handlers for a Signal and a Slot to showcase
+the different functionality.
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QPushButton
+ from PySide6.QtCore import QObject, Signal, Slot
+
+
+ class Communicate(QObject):
+ # create two new signals on the fly: one will handle
+ # int type, the other will handle strings
+ speak = Signal((int,), (str,))
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ self.speak[int].connect(self.say_something)
+ self.speak[str].connect(self.say_something)
+
+ # define a new slot that receives a C 'int' or a 'str'
+ # and has 'say_something' as its name
+ @Slot(int)
+ @Slot(str)
+ def say_something(self, arg):
+ if isinstance(arg, int):
+ print("This is a number:", arg)
+ elif isinstance(arg, str):
+ print("This is a string:", arg)
+
+ if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ someone = Communicate()
+
+ # emit 'speak' signal with different arguments.
+ # we have to specify the str as int is the default
+ someone.speak.emit(10)
+ someone.speak[str].emit("Hello everybody!")
+
+
+.. _signals-and-slots-strings:
+
+Specifying Signals and Slots by Method Signature Strings
+--------------------------------------------------------
+
+
+Signals and slots can also be specified as C++ method signature
+strings passed through the ``SIGNAL()`` and/or ``SLOT()`` functions:
+
+.. code-block:: python
+
+ from PySide6.QtCore import SIGNAL, SLOT
+
+ button.connect(SIGNAL("clicked(Qt::MouseButton)"),
+ action_handler, SLOT("action1(Qt::MouseButton)"))
+
+This is not normally recommended; it is only needed
+for a few cases where signals are only accessible via ``QMetaObject``
+(``QAxObject``, ``QAxWidget``, ``QDBusInterface`` or ``QWizardPage::registerField()``):
+
+.. code-block:: python
+
+ wizard.registerField("text", line_edit, "text",
+ SIGNAL("textChanged(QString)"))
+
+The signature strings can be found by querying ``QMetaMethod.methodSignature()``
+when introspecting ``QMetaObject``:
+
+.. code-block:: python
+
+ mo = widget.metaObject()
+ for m in range(mo.methodOffset(), mo.methodCount()):
+ print(mo.method(m).methodSignature())
+
+Slots should be decorated using :ref:`@Slot <slot-decorator>`.
diff --git a/sources/pyside6/doc/tutorials/basictutorial/signals_slots.png b/sources/pyside6/doc/tutorials/basictutorial/signals_slots.png
new file mode 100644
index 000000000..0801cf16e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/signals_slots.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/style.qss b/sources/pyside6/doc/tutorials/basictutorial/style.qss
new file mode 100644
index 000000000..b84b98f05
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/style.qss
@@ -0,0 +1,23 @@
+QListWidget {
+ color: #FFFFFF;
+ background-color: #33373B;
+}
+
+QListWidget::item {
+ height: 50px;
+}
+
+QListWidget::item:selected {
+ background-color: #2ABf9E;
+}
+
+QLabel {
+ background-color: #FFFFFF;
+ qproperty-alignment: AlignCenter;
+}
+
+QPushButton {
+ background-color: #2ABf9E;
+ padding: 20px;
+ font-size: 18px;
+}
diff --git a/sources/pyside6/doc/tutorials/basictutorial/tablewidget.png b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.png
new file mode 100644
index 000000000..e2549f7d0
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst
new file mode 100644
index 000000000..5c04529fd
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/tablewidget.rst
@@ -0,0 +1,97 @@
+Displaying Data Using a Table Widget
+====================================
+
+If you want to display data arranged in a table, use a ``QTableWidget`` to do
+so, without dealing with much configuration.
+
+Notice that using a ``QTableWidget`` is not the only path to display
+information in tables. You can also create a data model and display it using
+a ``QTableView``, but that is not in the scope of this tutorial.
+
+.. note:: This Widget is a ready-to-use version of something you can customize
+ further on. To know more about the Model/View architecture in Qt, refer to
+ its `official documentation <https://doc.qt.io/qt-6/model-view-programming.html>`_.
+
+1. Import ``QTableWidget``, ``QTableWidgetItem``, and ``QColor`` to display
+ background colors:
+
+ .. code-block:: python
+
+ import sys
+ from PySide6.QtGui import QColor
+ from PySide6.QtWidgets import (QApplication, QTableWidget,
+ QTableWidgetItem)
+
+2. Create a simple data model containing the list of names and hex codes for
+ different colors:
+
+ .. code-block:: python
+
+ colors = [("Red", "#FF0000"),
+ ("Green", "#00FF00"),
+ ("Blue", "#0000FF"),
+ ("Black", "#000000"),
+ ("White", "#FFFFFF"),
+ ("Electric Green", "#41CD52"),
+ ("Dark Blue", "#222840"),
+ ("Yellow", "#F9E56d")]
+
+3. Define a function to translate the hex code into an RGB equivalent:
+
+ .. code-block:: python
+
+ def get_rgb_from_hex(code):
+ code_hex = code.replace("#", "")
+ rgb = tuple(int(code_hex[i:i+2], 16) for i in (0, 2, 4))
+ return QColor.fromRgb(rgb[0], rgb[1], rgb[2])
+
+4. Initialize the ``QApplication`` singleton:
+
+ .. code-block:: python
+
+ app = QApplication()
+
+5. Configure the ``QTableWidget`` to have a number of rows equivalent
+ to the amount of items from the ``colors`` structure, and a number of
+ columns with the members of one color entry, plus one.
+ You can set the column name using the ``setHorizontalHeaderLabels`` as
+ described below:
+
+ .. code-block:: python
+
+ table = QTableWidget()
+ table.setRowCount(len(colors))
+ table.setColumnCount(len(colors[0]) + 1)
+ table.setHorizontalHeaderLabels(["Name", "Hex Code", "Color"])
+
+ .. note:: the reason of using ``+ 1`` is to include a new column where
+ we can display the color.
+
+6. Iterate the data structure, create the ``QTableWidgetItems`` instances, and
+ add them into the table using a ``x, y`` coordinate. Here the data is being
+ assigned row-per-row:
+
+ .. code-block:: python
+
+ for i, (name, code) in enumerate(colors):
+ item_name = QTableWidgetItem(name)
+ item_code = QTableWidgetItem(code)
+ item_color = QTableWidgetItem()
+ item_color.setBackground(get_rgb_from_hex(code))
+ table.setItem(i, 0, item_name)
+ table.setItem(i, 1, item_code)
+ table.setItem(i, 2, item_color)
+
+7. Show the table and execute the ``QApplication``.
+
+ .. code-block:: python
+
+ table.show()
+ sys.exit(app.exec())
+
+
+The final application will look like this:
+
+.. image:: tablewidget.png
+ :alt: QTableWidget example
+ :align: center
diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.png b/sources/pyside6/doc/tutorials/basictutorial/translations.png
new file mode 100644
index 000000000..b9fc1ba17
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/translations.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/translations.rst b/sources/pyside6/doc/tutorials/basictutorial/translations.rst
new file mode 100644
index 000000000..21c16cdcd
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/translations.rst
@@ -0,0 +1,232 @@
+.. _translations:
+
+Translating Applications
+========================
+
+.. image:: translations.png
+ :alt: Translation Image
+
+Qt Linguist
+-----------
+
+`Qt Linguist`_ and
+its related tools can be used to provide translations for applications.
+
+The :ref:`qt-linguist-example` example illustrates this. The example is
+very simple, it has a menu and shows a list of programming languages with
+multiselection.
+
+Translation works by passing the message strings through function calls that
+look up the translation. Each ``QObject`` instance provides a ``tr()``
+function for that purpose. There is also ``QCoreApplication.translate()``
+for adding translated texts to non-QObject classes.
+
+Qt ships its own translations containing the error messages and standard
+dialog captions.
+
+The linguist example has a number of messages enclosed in ``self.tr()``.
+The status bar message shown in response to a selection change uses
+a plural form depending on a count:
+
+.. code-block:: python
+
+ count = len(self._list_widget.selectionModel().selectedRows())
+ message = self.tr("%n language(s) selected", "", count)
+
+The translation workflow for the example is as follows:
+The translated messages are extracted using the ``lupdate`` tool,
+producing XML-based ``.ts`` files:
+
+.. code-block:: bash
+
+ pyside6-lupdate main.py -ts example_de.ts
+
+If ``example_de.ts`` already exists, it will be updated with the new
+messages added to the code in-between.
+
+If there are form files (``.ui``) and/or QML files (``.qml``) in the project,
+they should be passed to the ``pyside6-lupdate`` tool as well:
+
+.. code-block:: bash
+
+ pyside6-lupdate main.py main.qml form.ui -ts example_de.ts
+
+The source files generated by ``pyside6-uic`` from the form files
+should **not** be passed.
+
+The ``lupdate`` mode of ``pyside6-project`` can also be used for this. It
+collects all source files and runs ``pyside6-lupdate`` when ``.ts`` file(s)
+are given in the ``.pyproject`` file:
+
+.. code-block:: bash
+
+ pyside6-project lupdate .
+
+``.ts`` files are translated using *Qt Linguist*. Once this is complete,
+the files are converted to a binary form (``.qm`` files):
+
+.. code-block:: bash
+
+ pyside6-lrelease example_de.ts -qm example_de.qm
+
+``pyside6-project`` will build the ``.qm`` file automatically when
+``.ts`` file(s) are given in the ``.pyproject`` file:
+
+.. code-block:: bash
+
+ pyside6-project build .
+
+To avoid having to ship the ``.qm`` files, it is recommend
+to put them into a Qt resource file along with icons and other
+applications resources (see :ref:`using_qrc_files`).
+The resource file ``linguist.qrc`` provides the ``example_de.qm``
+under ``:/translations``:
+
+.. code-block:: xml
+
+ <!DOCTYPE RCC><RCC version="1.0">
+ <qresource prefix="translations">
+ <file>example_de.qm</file>
+ </qresource>
+ </RCC>
+
+At runtime, the translations need to be loaded using the ``QTranslator`` class:
+
+.. code-block:: python
+
+ path = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
+ translator = QTranslator(app)
+ if translator.load(QLocale.system(), 'qtbase', '_', path):
+ app.installTranslator(translator)
+ translator = QTranslator(app)
+ path = ':/translations'
+ if translator.load(QLocale.system(), 'example', '_', path):
+ app.installTranslator(translator)
+
+The code first loads the translations shipped for Qt and then
+the translations of the applications loaded from resources.
+
+The example can then be run in German:
+
+.. code-block:: bash
+
+ LANG=de python main.py
+
+.. _Qt Linguist: https://doc.qt.io/qt-6/qtlinguist-index.html
+
+GNU gettext
+-----------
+
+The `GNU gettext`_ module
+can be used to provide translations for applications.
+
+The :ref:`gettext-example` example illustrates this. The example is
+very simple, it has a menu and shows a list of programming languages with
+multiselection.
+
+Translation works by passing the message strings through function calls that
+look up the translation. It is common to alias the main translation function
+to ``_``. There is a special translation function for sentences that contain
+a plural form depending on a count ("{0} items(s) selected"). It is commonly
+aliased to ``ngettext``.
+
+Those functions are defined at the top:
+
+.. code-block:: python
+
+ import gettext
+ # ...
+ _ = None
+ ngettext = None
+
+and later assigned as follows:
+
+.. code-block:: python
+
+ src_dir = Path(__file__).resolve().parent
+ try:
+ translation = gettext.translation('example', localedir=src_dir / 'locales')
+ if translation:
+ translation.install()
+ _ = translation.gettext
+ ngettext = translation.ngettext
+ except FileNotFoundError:
+ pass
+ if not _:
+ _ = gettext.gettext
+ ngettext = gettext.ngettext
+
+This specifies that our translation file has the base name ``example`` and
+will be found in the source tree under ``locales``. The code will try
+to load a translation matching the current language.
+
+Messages to be translated look like:
+
+.. code-block:: python
+
+ file_menu = self.menuBar().addMenu(_("&File"))
+
+The status bar message shown in response to a selection change uses
+a plural form depending on a count:
+
+.. code-block:: python
+
+ count = len(self._list_widget.selectionModel().selectedRows())
+ message = ngettext("{0} language selected",
+ "{0} languages selected", count).format(count)
+
+The ``ngettext()`` function takes the singular form, plural form and the count.
+The returned string still contains the formatting placeholder, so it needs
+to be passed through ``format()``.
+
+In order to translate the messages to say German, a template file (``.pot``)
+is first created:
+
+.. code-block:: bash
+
+ mkdir -p locales/de_DE/LC_MESSAGES
+ xgettext -L Python -o locales/example.pot main.py
+
+This file has a few generic placeholders which can be replaced by the
+appropriate values. It is then copied to the ``de_DE/LC_MESSAGES`` directory.
+
+.. code-block:: bash
+
+ cd locales/de_DE/LC_MESSAGES/
+ cp ../../example.pot .
+
+Further adaptions need to be made to account for the German plural
+form and encoding:
+
+.. code-block::
+
+ "Project-Id-Version: PySide6 gettext example\n"
+ "POT-Creation-Date: 2021-07-05 14:16+0200\n"
+ "Language: de_DE\n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=UTF-8\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+Below, the translated messages can be given:
+
+.. code-block::
+
+ #: main.py:57
+ msgid "&File"
+ msgstr "&Datei"
+
+Finally, the ``.pot`` is converted to its binary form (machine object file,
+``.mo``), which needs to be deployed:
+
+.. code-block:: bash
+
+ msgfmt -o example.mo example.pot
+
+The example can then be run in German:
+
+.. code-block:: bash
+
+ LANG=de python main.py
+
+.. _GNU gettext: https://docs.python.org/3/library/gettext.html
diff --git a/sources/pyside6/doc/tutorials/basictutorial/treewidget.png b/sources/pyside6/doc/tutorials/basictutorial/treewidget.png
new file mode 100644
index 000000000..990fe977b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/treewidget.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst b/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst
new file mode 100644
index 000000000..f431cb5c4
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/treewidget.rst
@@ -0,0 +1,79 @@
+Displaying Data Using a Tree Widget
+===================================
+
+If you want to display data arranged in a tree, use a ``QTreeWidget`` to do so.
+
+Notice that using a ``QTreeWidget`` is not the only path to display
+information in trees. You can also create a data model and display it using a
+``QTreeView``, but that is not in the scope of this tutorial.
+
+.. note:: This Widget is a ready-to-use version of something you can customize
+ further on. To know more about the Model/View architecture in Qt, refer to
+ its `official documentation <https://doc.qt.io/qt-6/model-view-programming.html>`_.
+
+#. Import ``QTreeWidget`` and ``QTreeWidgetItem`` for this application:
+
+ .. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem
+
+#. Define a dictionary with project structures to display the information as a
+ tree, with files belonging to each project:
+
+ .. code-block:: python
+
+ data = {"Project A": ["file_a.py", "file_a.txt", "something.xls"],
+ "Project B": ["file_b.csv", "photo.jpg"],
+ "Project C": []}
+
+#. Initialize the ``QApplication`` singleton:
+
+ .. code-block:: python
+
+ app = QApplication()
+
+#. Configure the ``QTreeWidget`` to have two columns, one for the item name,
+ and the other for item type information of the files in the project
+ directories.
+ You can set the column name with the ``setHeaderLabels`` as described below:
+
+ .. code-block:: python
+
+ tree = QTreeWidget()
+ tree.setColumnCount(2)
+ tree.setHeaderLabels(["Name", "Type"])
+
+#. Iterate the data structure, create the ``QTreeWidgetItem`` elements, and add
+ the corresponding children to each parent.
+ We also extract the extension name for only the files and add them
+ into the second column.
+ In the constructor, you can see that each element (``QTreeWidgetItem``) is
+ added to different columns of the tree (``QTreeWidget``).
+
+ .. code-block:: python
+
+ items = []
+ for key, values in data.items():
+ item = QTreeWidgetItem([key])
+ for value in values:
+ ext = value.split(".")[-1].upper()
+ child = QTreeWidgetItem([value, ext])
+ item.addChild(child)
+ items.append(item)
+
+ tree.insertTopLevelItems(0, items)
+
+#. Show the tree and execute the ``QApplication``.
+
+ .. code-block:: python
+
+ tree.show()
+ sys.exit(app.exec())
+
+
+The final application will look like this:
+
+.. image:: treewidget.png
+ :alt: QTreeWidget example
+ :align: center
diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.png b/sources/pyside6/doc/tutorials/basictutorial/uifiles.png
new file mode 100644
index 000000000..918efec6d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst
new file mode 100644
index 000000000..cb945908d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst
@@ -0,0 +1,304 @@
+.. _using_ui_files:
+
+Using ``.ui`` files from Designer or QtCreator with ``QUiLoader`` and ``pyside6-uic``
+*************************************************************************************
+
+This page describes the use of
+`Qt Widgets Designer <https://doc.qt.io/qt-6/qtdesigner-manual.html>`_ to create
+graphical interfaces based on Qt Widgets for your Qt for Python project.
+*Qt Widgets Designer* is a graphical UI design tool which is available as a
+standalone binary (``pyside6-designer``) or embedded into the
+`Qt Creator IDE <https://doc.qt.io/qtcreator>`_. Its use within *Qt Creator*
+is described at
+`Using Qt Widgets Designer <https://doc.qt.io/qtcreator/creator-using-qt-designer.html>`_.
+
+.. image:: uifiles.png
+ :alt: Designer and the equivalent code
+
+The designs are stored in ``.ui`` files, which is an XML-based format. It will
+be converted to Python or C++ code populating a widget instance at project build
+time by the `pyside6-uic <https://doc.qt.io/qt-6/uic.html>`_ tool.
+
+To create a new Qt Design Form in *Qt Creator*, choose
+``File/New File Or Project`` and "Main Window" for template. Save it as
+``mainwindow.ui``. Add a ``QPushButton`` to the center of the centralwidget.
+
+Your file ``mainwindow.ui`` should look something like this:
+
+.. code-block:: xml
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <widget class="QPushButton" name="pushButton">
+ <property name="geometry">
+ <rect>
+ <x>110</x>
+ <y>80</y>
+ <width>201</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>PushButton</string>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QMenuBar" name="menuBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>20</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QToolBar" name="mainToolBar">
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <widget class="QStatusBar" name="statusBar"/>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+ </ui>
+
+Now we are ready to decide how to use the **UI file** from Python.
+
+Option A: Generating a Python class
+===================================
+
+The standard way to interact with a **UI file** is to generate a Python
+class from it. This is possible thanks to the ``pyside6-uic`` tool.
+To use this tool, you need to run the following command on a console::
+
+ pyside6-uic mainwindow.ui -o ui_mainwindow.py
+
+We redirect all the output of the command to a file called ``ui_mainwindow.py``,
+which will be imported directly::
+
+ from ui_mainwindow import Ui_MainWindow
+
+Now to use it, we should create a personalized class for our widget
+to **setup** this generated design.
+
+To understand the idea, let's take a look at the whole code:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QMainWindow
+ from PySide6.QtCore import QFile
+ from ui_mainwindow import Ui_MainWindow
+
+ class MainWindow(QMainWindow):
+ def __init__(self):
+ super(MainWindow, self).__init__()
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+
+ if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ window = MainWindow()
+ window.show()
+
+ sys.exit(app.exec())
+
+What is inside the *if* statement is already known from the previous
+examples, and our new basic class contains only two new lines
+that are in charge of loading the generated python class from the UI
+file:
+
+.. code-block:: python
+
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+
+.. note::
+
+ You must run ``pyside6-uic`` again every time you make changes
+ to the **UI file**.
+
+Option B: Loading it directly
+=============================
+
+To load the UI file directly, we will need a class from the **QtUiTools**
+module:
+
+.. code-block:: python
+
+ from PySide6.QtUiTools import QUiLoader
+
+The ``QUiLoader`` lets us load the **ui file** dynamically
+and use it right away:
+
+.. code-block:: python
+
+ ui_file = QFile("mainwindow.ui")
+ ui_file.open(QFile.ReadOnly)
+
+ loader = QUiLoader()
+ window = loader.load(ui_file)
+ window.show()
+
+The complete code of this example looks like this:
+
+.. code-block:: python
+
+ # File: main.py
+ import sys
+ from PySide6.QtUiTools import QUiLoader
+ from PySide6.QtWidgets import QApplication
+ from PySide6.QtCore import QFile, QIODevice
+
+ if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ ui_file_name = "mainwindow.ui"
+ ui_file = QFile(ui_file_name)
+ if not ui_file.open(QIODevice.ReadOnly):
+ print(f"Cannot open {ui_file_name}: {ui_file.errorString()}")
+ sys.exit(-1)
+ loader = QUiLoader()
+ window = loader.load(ui_file)
+ ui_file.close()
+ if not window:
+ print(loader.errorString())
+ sys.exit(-1)
+ window.show()
+
+ sys.exit(app.exec())
+
+Then to execute it we just need to run the following on a
+command prompt:
+
+.. code-block:: bash
+
+ python main.py
+
+.. note::
+
+ ``QUiLoader`` uses ``connect()`` calls taking the function signatures as string
+ arguments for signal/slot connections.
+ It is thus unable to handle Python types like ``str`` or ``list`` from
+ custom widgets written in Python since these types are internally mapped
+ to different C++ types.
+
+.. _designer_custom_widgets:
+
+Custom Widgets in Qt Widgets Designer
+=====================================
+
+*Qt Widgets Designer* is able to use user-provided (custom) widgets.
+They are shown in the widget box and can be dragged onto the form just like
+Qt's widgets (see
+`Using Custom Widgets with Qt Widgets Designer <https://doc.qt.io/qt-6/designer-using-custom-widgets.html>`_
+). Normally, this requires implementing the widget as a plugin to
+*Qt Widgets Designer* written in C++ implementing its
+`QDesignerCustomWidgetInterface`_ .
+
+Qt for Python provides a simple interface for this which is similar to
+:meth:`registerCustomWidget()<PySide6.QtUiTools.QUiLoader.registerCustomWidget>`.
+
+The widget needs to be provided as a Python module, as shown by
+the :ref:`widgetbinding-example` (file ``wigglywidget.py``) or
+the :ref:`task-menu-extension-example` (file ``tictactoe.py``).
+
+Registering this with *Qt Widgets Designer* is done by providing
+a registration script named ``register*.py`` and pointing
+the path-type environment variable ``PYSIDE_DESIGNER_PLUGINS``
+to the directory.
+
+The code of the registration script looks as follows:
+
+.. code-block:: python
+
+ # File: registerwigglywidget.py
+ from wigglywidget import WigglyWidget
+
+ import QtDesigner
+
+
+ TOOLTIP = "A cool wiggly widget (Python)"
+ DOM_XML = """
+ <ui language='c++'>
+ <widget class='WigglyWidget' name='wigglyWidget'>
+ <property name='geometry'>
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>200</height>
+ </rect>
+ </property>
+ <property name='text'>
+ <string>Hello, world</string>
+ </property>
+ </widget>
+ </ui>
+ """
+
+ QPyDesignerCustomWidgetCollection.registerCustomWidget(WigglyWidget, module="wigglywidget",
+ tool_tip=TOOLTIP, xml=DOM_XML)
+
+
+QPyDesignerCustomWidgetCollection provides an implementation of
+`QDesignerCustomWidgetCollectionInterface`_
+exposing custom widgets to *Qt Widgets Designer* with static convenience
+functions for registering types or adding instances of
+`QDesignerCustomWidgetInterface`_ .
+
+The function
+:meth:`registerCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.registerCustomWidget>`
+is used to register a widget type with *Qt Widgets Designer*. In the simple case, it
+can be used like ``QUiLoader.registerCustomWidget()``. It takes the custom widget
+type and some optional keyword arguments passing values that correspond to the
+getters of
+`QDesignerCustomWidgetInterface`_ :
+
+When launching *Qt Widgets Designer* via its launcher ``pyside6-designer``,
+the custom widget should be visible in the widget box.
+
+For advanced usage, it is also possible to pass the function an implementation
+of the class QDesignerCustomWidgetInterface instead of the type to
+:meth:`addCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.addCustomWidget>`.
+This is shown in taskmenuextension example, where a custom context menu
+is registered for the custom widget. The example is a port of the
+corresponding C++
+`Task Menu Extension Example <https://doc.qt.io/qt-6/qtdesigner-taskmenuextension-example.html>`_ .
+
+.. _QDesignerCustomWidgetCollectionInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetcollectioninterface.html
+.. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html
+
+Troubleshooting the Qt Widgets Designer Plugin
+++++++++++++++++++++++++++++++++++++++++++++++
+
+- The launcher ``pyside6-designer`` must be used. The standalone
+ *Qt Widgets Designer* will not load the plugin.
+- The menu item **Help/About Plugin** brings up a dialog showing the plugins
+ found and potential load error messages.
+- Check the console or Windows Debug view for further error messages.
+- Due to the buffering of output by Python, error messages may appear
+ only after *Qt Widgets Designer* has terminated.
+- When building Qt for Python, be sure to set the ``--standalone`` option
+ for the plugin to be properly installed.
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgets.png b/sources/pyside6/doc/tutorials/basictutorial/widgets.png
new file mode 100644
index 000000000..de7a969f9
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgets.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgets.rst b/sources/pyside6/doc/tutorials/basictutorial/widgets.rst
new file mode 100644
index 000000000..ef14c7e99
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgets.rst
@@ -0,0 +1,52 @@
+Your First QtWidgets Application
+*********************************
+
+As with any other programming framework,
+you start with the traditional "Hello World" program.
+
+Here is a simple example of a Hello World application in PySide6:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtWidgets import QApplication, QLabel
+
+ app = QApplication(sys.argv)
+ label = QLabel("Hello World!")
+ label.show()
+ app.exec()
+
+
+When you execute it the code, the application will look like:
+
+.. image:: widgets.png
+ :alt: Simple Widget
+
+
+For a widget application using PySide6, you must always start by
+importing the appropriate class from the `PySide6.QtWidgets` module.
+
+After the imports, you create a `QApplication` instance. As Qt can
+receive arguments from command line, you may pass any argument to
+the QApplication object. Usually, you don't need to pass any
+arguments so you can leave it as is, or use the following approach:
+
+.. code-block:: python
+
+ app = QApplication([])
+
+After the creation of the application object, we have created a
+`QLabel` object. A `QLabel` is a widget that can present text
+(simple or rich, like html), and images:
+
+.. code-block:: python
+
+ # This HTML approach will be valid too!
+ label = QLabel("<font color=red size=40>Hello World!</font>")
+
+.. note:: After creating the label, we call `show()` on it.
+
+Finally, we call `app.exec()` to enter the Qt main loop and start
+to execute the Qt code. In reality, it is only here where the label
+is shown, but this can be ignored for now.
+
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png
new file mode 100644
index 000000000..f8346533f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png
new file mode 100644
index 000000000..d510a80cd
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png
new file mode 100644
index 000000000..e7a0c0ef7
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png
new file mode 100644
index 000000000..9b83b8267
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py
new file mode 100644
index 000000000..106483b7b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py
@@ -0,0 +1,58 @@
+# 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.QtWidgets import (QApplication, QHBoxLayout, QLabel, QListWidget,
+ QListWidgetItem, QPushButton, QVBoxLayout,
+ QWidget)
+
+_placeholder = """
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
+velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+mollit anim id est laborum
+"""
+
+
+class Widget(QWidget):
+ def __init__(self, parent=None):
+ super(Widget, self).__init__(parent)
+
+ menu_widget = QListWidget()
+ for i in range(10):
+ item = QListWidgetItem(f"Item {i}")
+ item.setTextAlignment(Qt.AlignCenter)
+ menu_widget.addItem(item)
+
+ text_widget = QLabel(_placeholder)
+ button = QPushButton("Something")
+
+ content_layout = QVBoxLayout()
+ content_layout.addWidget(text_widget)
+ content_layout.addWidget(button)
+ main_widget = QWidget()
+ main_widget.setLayout(content_layout)
+
+ layout = QHBoxLayout()
+ layout.addWidget(menu_widget, 1)
+ layout.addWidget(main_widget, 4)
+ self.setLayout(layout)
+
+
+if __name__ == "__main__":
+ app = QApplication()
+
+ w = Widget()
+ w.show()
+
+ _style = None
+ with open("style.qss", "r") as f:
+ _style = f.read()
+ app.setStyleSheet(_style)
+
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst
new file mode 100644
index 000000000..2fa51c0a8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst
@@ -0,0 +1,171 @@
+.. _widgetstyling:
+
+Styling the Widgets Application
+===============================
+
+Qt Widgets application use a default theme depending on the platform.
+In some cases, there are system-wide configurations that modify the Qt theme,
+and applications are displayed differently.
+
+However, you can take care of your own widgets and provide a custom style
+to each component. As an example, look at the following simple snippet:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtCore import Qt
+ from PySide6.QtWidgets import QApplication, QLabel
+
+ if __name__ == "__main__":
+ app = QApplication()
+ w = QLabel("This is a placeholder text")
+ w.setAlignment(Qt.AlignCenter)
+ w.show()
+ sys.exit(app.exec())
+
+When you execute this code, you will see a simple ``QLabel`` aligned at the
+center, and with a placeholder text.
+
+.. image:: widgetstyling-simple-no.png
+ :alt: Simple Widget with no style
+
+You can style your application using the CSS-like syntax.
+For more information, see `Qt Style Sheets Reference`_.
+
+A ``QLabel`` can be styled differently by setting some of its CSS
+properties, such as ``background-color`` and ``font-family``,
+so let's see how does the code look like with these changes:
+
+.. code-block:: python
+
+ import sys
+ from PySide6.QtCore import Qt
+ from PySide6.QtWidgets import QApplication, QLabel
+
+ if __name__ == "__main__":
+ app = QApplication()
+ w = QLabel("This is a placeholder text")
+ w.setAlignment(Qt.AlignCenter)
+ w.setStyleSheet("""
+ background-color: #262626;
+ color: #FFFFFF;
+ font-family: Titillium;
+ font-size: 18px;
+ """)
+ w.show()
+ sys.exit(app.exec())
+
+Now when you run the code, notice that the ``QLabel`` looks different with your
+custom style:
+
+.. image:: widgetstyling-simple-yes.png
+ :alt: Simple Widget with Style
+
+
+.. note::
+
+ If you don't have the font ``Titillium`` installed, you can try with any
+ other you prefer.
+ Remember you can list your installed fonts using ``QFontDatabase``,
+ specifically the ``families()`` method.
+
+
+Styling each UI element separately like you did in the previous snippet is a
+lot of work. The easier alternative for this is to use Qt Style Sheets,
+which is one or more ``.qss`` files defining the style for the UI elements in
+your application.
+
+More examples can be found in the `Qt Style Sheet Examples`_ documentation
+page.
+
+
+.. _`Qt Style Sheets Reference`: https://doc.qt.io/qt-5/stylesheet-reference.html
+.. _`Qt Style Sheet Examples`: https://doc.qt.io/qt-5/stylesheet-examples.html
+
+Qt Style Sheets
+---------------
+
+.. warning::
+
+ Before starting modifying your application, keep in mind that you will be
+ responsible for all the graphical details of the application.
+ Altering margins, and sizes might end up looking strange or incorrect, so you
+ need to be careful when altering the style.
+ It's recommended to create a full new Qt style to cover all the possible
+ corner cases.
+
+A ``qss`` file is quite similar to a CSS file, but you need to specify the Widget
+component and optionally the name of the object::
+
+ QLabel {
+ background-color: red;
+ }
+
+ QLabel#title {
+ font-size: 20px;
+ }
+
+The first style defines a ``background-color`` for all ``QLabel`` objects in your
+application, whereas the later one styles the ``title`` object only.
+
+.. note::
+
+ You can set object names with the `setObjectName(str)` function to any Qt
+ object, for example: for a `label = QLabel("Test")`, you can write
+ `label.setObjectName("title")`
+
+
+Once you have a ``qss`` file for your application, you can apply it by reading
+the file and using the ``QApplication.setStyleSheet(str)`` function:
+
+.. code-block:: python
+
+ if __name__ == "__main__":
+ app = QApplication()
+
+ w = Widget()
+ w.show()
+
+ with open("style.qss", "r") as f:
+ _style = f.read()
+ app.setStyleSheet(_style)
+
+ sys.exit(app.exec())
+
+Having a general ``qss`` file allows you to decouple the styling aspects of
+the code, without mixing it in the middle of the general functionality, and you
+can simply enable it or disable it.
+
+Look at this new example, with more widgets components:
+
+.. literalinclude:: widgetstyling.py
+ :linenos:
+ :lines: 22-44
+
+This displays a two column widget, with a ``QListWidget`` on the left and a
+``QLabel`` and a ``QPushButton`` on the right. It looks like this when you run the
+code:
+
+.. image:: widgetstyling-no.png
+ :alt: Widget with no style
+
+If you add content to the previously described ``style.qss`` file, you can modify
+the look-n-feel of the previous example:
+
+.. literalinclude:: style.qss
+ :linenos:
+
+The style changes mainly the color of the different widgets, alter the
+alignment, and includes some spacing.
+You can also use state-based styling on the QListWidget *items* for example, to
+style them differently depending on whether they are *selected* or not.
+
+After applying all the styling alternatives you explored in this topic, notice
+that the ``QLabel`` example looks a lot different now.
+Try running the code to check its new look:
+
+.. image:: widgetstyling-yes.png
+ :alt: Widget with style
+
+You have the freedom to tune your style sheets and provide a really nice
+look-n-feel to all your applications.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst b/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst
new file mode 100644
index 000000000..59a9d9ee0
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst
@@ -0,0 +1,20 @@
+Chapter 5 - Add a chart view
+=============================
+
+A table is nice to present data, but a chart is even better. For this, you
+need the QtCharts module that provides many types of plots and options to
+graphically represent data.
+
+The placeholder for a plot is a QChartView, and inside that Widget you can
+place a QChart. As a first step, try including only this without any data to
+plot.
+
+Make the following highlighted changes to :code:`main_widget.py` from the
+previous chapter to add a QChartView:
+
+.. literalinclude:: datavisualize5/main_widget.py
+ :linenos:
+ :lines: 3-
+ :emphasize-lines: 2-3,6,22-36,47-49
+
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst
new file mode 100644
index 000000000..623372a07
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst
@@ -0,0 +1,32 @@
+Chapter 3 - Create an empty QMainWindow
+==========================================
+
+You can now think of presenting your data in a UI. A QMainWindow provides a
+convenient structure for GUI applications, such as a menu bar and status bar.
+The following image shows the layout that QMainWindow offers out-of-the box:
+
+.. image:: images/QMainWindow-layout.png
+ :alt: QMainWindow layout
+ :align: right
+
+In this case, let your application inherit from QMainWindow, and add the
+following UI elements:
+
+* A "File" menu to open a File dialog.
+* An "Exit" menu close the window.
+* A status message on the status bar when the application starts.
+
+In addition, you can define a fixed size for the window or adjust it based on
+the resolution you currently have. In the following snippet, you will see how
+window size is defined based on available screen width (80%) and height (70%).
+
+.. note:: You can achieve a similar structure using other Qt elements like
+ QMenuBar, QWidget, and QStatusBar. Refer the QMainWindow layout for
+ guidance.
+
+.. literalinclude:: datavisualize3/main_window.py
+ :language: python
+ :linenos:
+ :lines: 4-
+
+Try running the script to see what output you get with it.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst
new file mode 100644
index 000000000..f658640bf
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst
@@ -0,0 +1,70 @@
+Chapter 4 - Add a QTableView
+=============================
+
+Now that you have a QMainWindow, you can include a centralWidget to your
+interface. Usually, a QWidget is used to display data in most data-driven
+applications. Use a table view to display your data.
+
+The first step is to add a horizontal layout with just a QTableView. You
+can create a QTableView object and place it inside a QHBoxLayout. Once the
+QWidget is properly built, pass the object to the QMainWindow as its central
+widget.
+
+Remember that a QTableView needs a model to display information. In this case,
+you can use a QAbstractTableModel instance.
+
+.. note:: You could also use the default item model that comes with a
+ QTableWidget instead. QTableWidget is a convenience class that reduces
+ your codebase considerably as you don't need to implement a data model.
+ However, it's less flexible than a QTableView, as QTableWidget cannot be
+ used with just any data. For more insight about Qt's model-view framework,
+ refer to the
+ `Model View Programming <https://doc.qt.io/qt-5/model-view-programming.html>`
+ documentation.
+
+Implementing the model for your QTableView, allows you to:
+- set the headers,
+- manipulate the formats of the cell values (remember we have UTC time and float
+numbers),
+- set style properties like text alignment,
+- and even set color properties for the cell or its content.
+
+To subclass the QAbstractTable, you must reimplement its virtual methods,
+rowCount(), columnCount(), and data(). This way, you can ensure that the data
+is handled properly. In addition, reimplement the headerData() method to
+provide the header information to the view.
+
+Here is a script that implements the CustomTableModel:
+
+.. literalinclude:: datavisualize4/table_model.py
+ :language: python
+ :linenos:
+ :lines: 3-
+
+Now, create a QWidget that has a QTableView, and connect it to your
+CustomTableModel.
+
+.. literalinclude:: datavisualize4/main_widget.py
+ :language: python
+ :linenos:
+ :emphasize-lines: 12-17
+ :lines: 3-
+
+You also need minor changes to the :code:`main_window.py` and
+:code:`main.py` from chapter 3 to include the Widget inside the
+MainWindow.
+
+In the following snippets you'll see those changes highlighted:
+
+.. literalinclude:: datavisualize4/main_window.py
+ :language: python
+ :linenos:
+ :lines: 3-
+ :emphasize-lines: 8,11
+
+.. literalinclude:: datavisualize4/main.py
+ :language: python
+ :linenos:
+ :lines: 3-
+ :emphasize-lines: 46-47
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/all_hour.csv b/sources/pyside6/doc/tutorials/datavisualize/all_hour.csv
new file mode 100644
index 000000000..400947c3c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/all_hour.csv
@@ -0,0 +1,8 @@
+time,latitude,longitude,depth,mag,magType,nst,gap,dmin,rms,net,id,updated,place,type,horizontalError,depthError,magError,magNst,status,locationSource,magSource
+2019-01-10T12:11:24.810Z,34.1281662,-117.7754974,4.46,1.18,ml,22,69,0.04475,0.13,ci,ci38421072,2019-01-10T12:13:30.138Z,"3km NNW of La Verne, CA",earthquake,0.3,0.55,0.246,6,automatic,ci,ci
+2019-01-10T12:04:26.320Z,19.4433327,-155.6159973,0.72,1.79,md,22,99,0.04026,0.3,hv,hv70763571,2019-01-10T12:07:28.690Z,"26km E of Honaunau-Napoopoo, Hawaii",earthquake,0.6,1.79,0.28,6,automatic,hv,hv
+2019-01-10T11:57:48.980Z,33.3225,-116.3931667,4.84,0.62,ml,15,211,0.05776,0.16,ci,ci38421064,2019-01-10T12:01:29.166Z,"8km NNW of Borrego Springs, CA",earthquake,0.71,0.68,0.111,11,automatic,ci,ci
+2019-01-10T11:52:09.490Z,38.8356667,-122.8366699,1.28,2.74,md,25,77,0.003061,0.04,nc,nc73131566,2019-01-10T12:14:02.757Z,"10km NW of The Geysers, CA",earthquake,0.19,0.29,0.06,7,automatic,nc,nc
+2019-01-10T11:25:44.854Z,65.1082,-149.3701,20.6,2.1,ml,,,,1.02,ak,ak019gq2oer,2019-01-10T11:37:07.060Z,"60km NNW of North Nenana, Alaska",earthquake,,0.3,,,automatic,ak,ak
+2019-01-10T11:25:23.786Z,69.1518,-144.4977,10.4,3.7,ml,,,,0.74,ak,ak019gq2ndz,2019-01-10T11:47:11.284Z,"114km SSW of Kaktovik, Alaska",earthquake,,1.6,,,reviewed,ak,ak
+2019-01-10T11:16:11.761Z,61.3318,-150.0708,20.1,2.7,ml,,,,0.83,ak,ak019gq0ozj,2019-01-10T11:29:24.610Z,"15km NW of Anchorage, Alaska",earthquake,,0.4,,,automatic,ak,ak
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py
new file mode 100644
index 000000000..995b9906a
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py
@@ -0,0 +1,18 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import argparse
+import pandas as pd
+
+
+def read_data(fname):
+ return pd.read_csv(fname)
+
+
+if __name__ == "__main__":
+ options = argparse.ArgumentParser()
+ options.add_argument("-f", "--file", type=str, required=True)
+ args = options.parse_args()
+ data = read_data(args.file)
+ print(data)
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py
new file mode 100644
index 000000000..9ea96097c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import argparse
+import pandas as pd
+
+from PySide6.QtCore import QDateTime, QTimeZone
+
+
+def transform_date(utc, timezone=None):
+ utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ"
+ new_date = QDateTime().fromString(utc, utc_fmt)
+ if timezone:
+ new_date.setTimeZone(timezone)
+ return new_date
+
+
+def read_data(fname):
+ # Read the CSV content
+ df = pd.read_csv(fname)
+
+ # Remove wrong magnitudes
+ df = df.drop(df[df.mag < 0].index)
+ magnitudes = df["mag"]
+
+ # My local timezone
+ timezone = QTimeZone(b"Europe/Berlin")
+
+ # Get timestamp transformed to our timezone
+ times = df["time"].apply(lambda x: transform_date(x, timezone))
+
+ return times, magnitudes
+
+
+if __name__ == "__main__":
+ options = argparse.ArgumentParser()
+ options.add_argument("-f", "--file", type=str, required=True)
+ args = options.parse_args()
+ data = read_data(args.file)
+ print(data)
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py
new file mode 100644
index 000000000..bbf85e17d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+import argparse
+import pandas as pd
+
+from PySide6.QtCore import QDateTime, QTimeZone
+from PySide6.QtWidgets import QApplication
+from main_window import MainWindow
+
+
+def transform_date(utc, timezone=None):
+ utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ"
+ new_date = QDateTime().fromString(utc, utc_fmt)
+ if timezone:
+ new_date.setTimeZone(timezone)
+ return new_date
+
+
+def read_data(fname):
+ # Read the CSV content
+ df = pd.read_csv(fname)
+
+ # Remove wrong magnitudes
+ df = df.drop(df[df.mag < 0].index)
+ magnitudes = df["mag"]
+
+ # My local timezone
+ timezone = QTimeZone(b"Europe/Berlin")
+
+ # Get timestamp transformed to our timezone
+ times = df["time"].apply(lambda x: transform_date(x, timezone))
+
+ return times, magnitudes
+
+
+if __name__ == "__main__":
+ options = argparse.ArgumentParser()
+ options.add_argument("-f", "--file", type=str, required=True)
+ args = options.parse_args()
+ data = read_data(args.file)
+
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ window = MainWindow()
+ window.show()
+
+ sys.exit(app.exec())
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
new file mode 100644
index 000000000..745f2fefe
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot
+from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtWidgets import QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ QMainWindow.__init__(self)
+ self.setWindowTitle("Eartquakes information")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = QAction("Exit", self)
+ exit_action.setShortcut(QKeySequence.Quit)
+ exit_action.triggered.connect(self.close)
+
+ self.file_menu.addAction(exit_action)
+
+ # Status Bar
+ self.status = self.statusBar()
+ self.status.showMessage("Data loaded and plotted")
+
+ # Window dimensions
+ geometry = self.screen().availableGeometry()
+ self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py
new file mode 100644
index 000000000..87b962bd3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+import argparse
+import pandas as pd
+
+from PySide6.QtCore import QDateTime, QTimeZone
+from PySide6.QtWidgets import QApplication
+from main_window import MainWindow
+from main_widget import Widget
+
+
+def transform_date(utc, timezone=None):
+ utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ"
+ new_date = QDateTime().fromString(utc, utc_fmt)
+ if timezone:
+ new_date.setTimeZone(timezone)
+ return new_date
+
+
+def read_data(fname):
+ # Read the CSV content
+ df = pd.read_csv(fname)
+
+ # Remove wrong magnitudes
+ df = df.drop(df[df.mag < 0].index)
+ magnitudes = df["mag"]
+
+ # My local timezone
+ timezone = QTimeZone(b"Europe/Berlin")
+
+ # Get timestamp transformed to our timezone
+ times = df["time"].apply(lambda x: transform_date(x, timezone))
+
+ return times, magnitudes
+
+
+if __name__ == "__main__":
+ options = argparse.ArgumentParser()
+ options.add_argument("-f", "--file", type=str, required=True)
+ args = options.parse_args()
+ data = read_data(args.file)
+
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ widget = Widget(data)
+ window = MainWindow(widget)
+ window.show()
+
+ sys.exit(app.exec())
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py
new file mode 100644
index 000000000..2a2bfeb09
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import (QHBoxLayout, QHeaderView, QSizePolicy,
+ QTableView, QWidget)
+
+from table_model import CustomTableModel
+
+
+class Widget(QWidget):
+ def __init__(self, data):
+ QWidget.__init__(self)
+
+ # Getting the Model
+ self.model = CustomTableModel(data)
+
+ # Creating a QTableView
+ self.table_view = QTableView()
+ self.table_view.setModel(self.model)
+
+ # QTableView Headers
+ self.horizontal_header = self.table_view.horizontalHeader()
+ self.vertical_header = self.table_view.verticalHeader()
+ self.horizontal_header.setSectionResizeMode(
+ QHeaderView.ResizeToContents
+ )
+ self.vertical_header.setSectionResizeMode(
+ QHeaderView.ResizeToContents
+ )
+ self.horizontal_header.setStretchLastSection(True)
+
+ # QWidget Layout
+ self.main_layout = QHBoxLayout()
+ size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+
+ ## Left layout
+ size.setHorizontalStretch(1)
+ self.table_view.setSizePolicy(size)
+ self.main_layout.addWidget(self.table_view)
+
+ # Set the layout to the QWidget
+ self.setLayout(self.main_layout)
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py
new file mode 100644
index 000000000..ebe2997a1
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot
+from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtWidgets import QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ QMainWindow.__init__(self)
+ self.setWindowTitle("Eartquakes information")
+ self.setCentralWidget(widget)
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ ## Exit QAction
+ exit_action = QAction("Exit", self)
+ exit_action.setShortcut(QKeySequence.Quit)
+ exit_action.triggered.connect(self.close)
+
+ self.file_menu.addAction(exit_action)
+
+ # Status Bar
+ self.status = self.statusBar()
+ self.status.showMessage("Data loaded and plotted")
+
+ # Window dimensions
+ geometry = self.screen().availableGeometry()
+ self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py
new file mode 100644
index 000000000..08eeeeed6
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex
+from PySide6.QtGui import QColor
+
+
+class CustomTableModel(QAbstractTableModel):
+ def __init__(self, data=None):
+ QAbstractTableModel.__init__(self)
+ self.load_data(data)
+
+ def load_data(self, data):
+ self.input_dates = data[0].values
+ self.input_magnitudes = data[1].values
+
+ self.column_count = 2
+ self.row_count = len(self.input_magnitudes)
+
+ def rowCount(self, parent=QModelIndex()):
+ return self.row_count
+
+ def columnCount(self, parent=QModelIndex()):
+ return self.column_count
+
+ def headerData(self, section, orientation, role):
+ if role != Qt.DisplayRole:
+ return None
+ if orientation == Qt.Horizontal:
+ return ("Date", "Magnitude")[section]
+ else:
+ return f"{section}"
+
+ def data(self, index, role=Qt.DisplayRole):
+ column = index.column()
+ row = index.row()
+
+ if role == Qt.DisplayRole:
+ if column == 0:
+ date = self.input_dates[row].toPython()
+ return str(date)[:-3]
+ elif column == 1:
+ magnitude = self.input_magnitudes[row]
+ return f"{magnitude:.2f}"
+ elif role == Qt.BackgroundRole:
+ return QColor(Qt.white)
+ elif role == Qt.TextAlignmentRole:
+ return Qt.AlignRight
+
+ return None
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py
new file mode 100644
index 000000000..87b962bd3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+import argparse
+import pandas as pd
+
+from PySide6.QtCore import QDateTime, QTimeZone
+from PySide6.QtWidgets import QApplication
+from main_window import MainWindow
+from main_widget import Widget
+
+
+def transform_date(utc, timezone=None):
+ utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ"
+ new_date = QDateTime().fromString(utc, utc_fmt)
+ if timezone:
+ new_date.setTimeZone(timezone)
+ return new_date
+
+
+def read_data(fname):
+ # Read the CSV content
+ df = pd.read_csv(fname)
+
+ # Remove wrong magnitudes
+ df = df.drop(df[df.mag < 0].index)
+ magnitudes = df["mag"]
+
+ # My local timezone
+ timezone = QTimeZone(b"Europe/Berlin")
+
+ # Get timestamp transformed to our timezone
+ times = df["time"].apply(lambda x: transform_date(x, timezone))
+
+ return times, magnitudes
+
+
+if __name__ == "__main__":
+ options = argparse.ArgumentParser()
+ options.add_argument("-f", "--file", type=str, required=True)
+ args = options.parse_args()
+ data = read_data(args.file)
+
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ widget = Widget(data)
+ window = MainWindow(widget)
+ window.show()
+
+ sys.exit(app.exec())
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
new file mode 100644
index 000000000..15b7e97e5
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QDateTime, Qt
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
+ QSizePolicy)
+from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
+
+from table_model import CustomTableModel
+
+
+class Widget(QWidget):
+ def __init__(self, data):
+ QWidget.__init__(self)
+
+ # Getting the Model
+ self.model = CustomTableModel(data)
+
+ # Creating a QTableView
+ self.table_view = QTableView()
+ self.table_view.setModel(self.model)
+
+ # QTableView Headers
+ self.horizontal_header = self.table_view.horizontalHeader()
+ self.vertical_header = self.table_view.verticalHeader()
+ self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents)
+ self.vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents)
+ self.horizontal_header.setStretchLastSection(True)
+
+ # Creating QChart
+ self.chart = QChart()
+ self.chart.setAnimationOptions(QChart.AllAnimations)
+
+ # Creating QChartView
+ self.chart_view = QChartView(self.chart)
+ self.chart_view.setRenderHint(QPainter.Antialiasing)
+
+ # QWidget Layout
+ self.main_layout = QHBoxLayout()
+ size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+
+ ## Left layout
+ size.setHorizontalStretch(1)
+ self.table_view.setSizePolicy(size)
+ self.main_layout.addWidget(self.table_view)
+
+ ## Right Layout
+ size.setHorizontalStretch(4)
+ self.chart_view.setSizePolicy(size)
+ self.main_layout.addWidget(self.chart_view)
+
+ # Set the layout to the QWidget
+ self.setLayout(self.main_layout)
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py
new file mode 100644
index 000000000..ed12c1f8f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot
+from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtWidgets import QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ QMainWindow.__init__(self)
+ self.setWindowTitle("Eartquakes information")
+ self.setCentralWidget(widget)
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = QAction("Exit", self)
+ exit_action.setShortcut(QKeySequence.Quit)
+ exit_action.triggered.connect(self.close)
+
+ self.file_menu.addAction(exit_action)
+
+ # Status Bar
+ self.status = self.statusBar()
+ self.status.showMessage("Data loaded and plotted")
+
+ # Window dimensions
+ geometry = self.screen().availableGeometry()
+ self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py
new file mode 100644
index 000000000..08eeeeed6
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex
+from PySide6.QtGui import QColor
+
+
+class CustomTableModel(QAbstractTableModel):
+ def __init__(self, data=None):
+ QAbstractTableModel.__init__(self)
+ self.load_data(data)
+
+ def load_data(self, data):
+ self.input_dates = data[0].values
+ self.input_magnitudes = data[1].values
+
+ self.column_count = 2
+ self.row_count = len(self.input_magnitudes)
+
+ def rowCount(self, parent=QModelIndex()):
+ return self.row_count
+
+ def columnCount(self, parent=QModelIndex()):
+ return self.column_count
+
+ def headerData(self, section, orientation, role):
+ if role != Qt.DisplayRole:
+ return None
+ if orientation == Qt.Horizontal:
+ return ("Date", "Magnitude")[section]
+ else:
+ return f"{section}"
+
+ def data(self, index, role=Qt.DisplayRole):
+ column = index.column()
+ row = index.row()
+
+ if role == Qt.DisplayRole:
+ if column == 0:
+ date = self.input_dates[row].toPython()
+ return str(date)[:-3]
+ elif column == 1:
+ magnitude = self.input_magnitudes[row]
+ return f"{magnitude:.2f}"
+ elif role == Qt.BackgroundRole:
+ return QColor(Qt.white)
+ elif role == Qt.TextAlignmentRole:
+ return Qt.AlignRight
+
+ return None
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py
new file mode 100644
index 000000000..2d851cc14
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+import argparse
+import pandas as pd
+
+from PySide6.QtCore import QDateTime, QTimeZone
+from PySide6.QtWidgets import QApplication
+
+from main_window import MainWindow
+from main_widget import Widget
+
+
+def transform_date(utc, timezone=None):
+ utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ"
+ new_date = QDateTime().fromString(utc, utc_fmt)
+ if timezone:
+ new_date.setTimeZone(timezone)
+ return new_date
+
+
+def read_data(fname):
+ # Read the CSV content
+ df = pd.read_csv(fname)
+
+ # Remove wrong magnitudes
+ df = df.drop(df[df.mag < 0].index)
+ magnitudes = df["mag"]
+
+ # My local timezone
+ timezone = QTimeZone(b"Europe/Berlin")
+
+ # Get timestamp transformed to our timezone
+ times = df["time"].apply(lambda x: transform_date(x, timezone))
+
+ return times, magnitudes
+
+
+if __name__ == "__main__":
+ options = argparse.ArgumentParser()
+ options.add_argument("-f", "--file", type=str, required=True)
+ args = options.parse_args()
+ data = read_data(args.file)
+
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ # QWidget
+ widget = Widget(data)
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+
+ window.show()
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py
new file mode 100644
index 000000000..cbcc126a1
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QDateTime, Qt
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
+ QSizePolicy)
+from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
+
+from table_model import CustomTableModel
+
+
+class Widget(QWidget):
+ def __init__(self, data):
+ QWidget.__init__(self)
+
+ # Getting the Model
+ self.model = CustomTableModel(data)
+
+ # Creating a QTableView
+ self.table_view = QTableView()
+ self.table_view.setModel(self.model)
+
+ # QTableView Headers
+ resize = QHeaderView.ResizeToContents
+ self.horizontal_header = self.table_view.horizontalHeader()
+ self.vertical_header = self.table_view.verticalHeader()
+ self.horizontal_header.setSectionResizeMode(resize)
+ self.vertical_header.setSectionResizeMode(resize)
+ self.horizontal_header.setStretchLastSection(True)
+
+ # Creating QChart
+ self.chart = QChart()
+ self.chart.setAnimationOptions(QChart.AllAnimations)
+ self.add_series("Magnitude (Column 1)", [0, 1])
+
+ # Creating QChartView
+ self.chart_view = QChartView(self.chart)
+ self.chart_view.setRenderHint(QPainter.Antialiasing)
+
+ # QWidget Layout
+ self.main_layout = QHBoxLayout()
+ size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+
+ # Left layout
+ size.setHorizontalStretch(1)
+ self.table_view.setSizePolicy(size)
+ self.main_layout.addWidget(self.table_view)
+
+ # Right Layout
+ size.setHorizontalStretch(4)
+ self.chart_view.setSizePolicy(size)
+ self.main_layout.addWidget(self.chart_view)
+
+ # Set the layout to the QWidget
+ self.setLayout(self.main_layout)
+
+ def add_series(self, name, columns):
+ # Create QLineSeries
+ self.series = QLineSeries()
+ self.series.setName(name)
+
+ # Filling QLineSeries
+ for i in range(self.model.rowCount()):
+ # Getting the data
+ t = self.model.index(i, 0).data()
+ date_fmt = "yyyy-MM-dd HH:mm:ss.zzz"
+
+ x = QDateTime().fromString(t, date_fmt).toSecsSinceEpoch()
+ y = float(self.model.index(i, 1).data())
+
+ if x > 0 and y > 0:
+ self.series.append(x, y)
+
+ self.chart.addSeries(self.series)
+
+ # Setting X-axis
+ self.axis_x = QDateTimeAxis()
+ self.axis_x.setTickCount(10)
+ self.axis_x.setFormat("dd.MM (h:mm)")
+ self.axis_x.setTitleText("Date")
+ self.chart.addAxis(self.axis_x, Qt.AlignBottom)
+ self.series.attachAxis(self.axis_x)
+ # Setting Y-axis
+ self.axis_y = QValueAxis()
+ self.axis_y.setTickCount(10)
+ self.axis_y.setLabelFormat("%.2f")
+ self.axis_y.setTitleText("Magnitude")
+ self.chart.addAxis(self.axis_y, Qt.AlignLeft)
+ self.series.attachAxis(self.axis_y)
+
+ # Getting the color from the QChart to use it on the QTableView
+ color_name = self.series.pen().color().name()
+ self.model.color = f"{color_name}"
+
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
new file mode 100644
index 000000000..116e53dc3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot
+from PySide6.QtGui import QAction, QKeySequence, QScreen
+from PySide6.QtWidgets import QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ QMainWindow.__init__(self)
+ self.setWindowTitle("Eartquakes information")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = QAction("Exit", self)
+ exit_action.setShortcut(QKeySequence.Quit)
+ exit_action.triggered.connect(self.close)
+
+ self.file_menu.addAction(exit_action)
+
+ # Status Bar
+ self.status = self.statusBar()
+ self.status.showMessage("Data loaded and plotted")
+
+ # Window dimensions
+ geometry = self.screen().availableGeometry()
+ self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7)
+ self.setCentralWidget(widget)
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py
new file mode 100644
index 000000000..4952c0c38
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py
@@ -0,0 +1,51 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex
+from PySide6.QtGui import QColor
+
+
+class CustomTableModel(QAbstractTableModel):
+ def __init__(self, data=None):
+ QAbstractTableModel.__init__(self)
+ self.color = None
+ self.load_data(data)
+
+ def load_data(self, data):
+ self.input_dates = data[0].values
+ self.input_magnitudes = data[1].values
+
+ self.column_count = 2
+ self.row_count = len(self.input_magnitudes)
+
+ def rowCount(self, parent=QModelIndex()):
+ return self.row_count
+
+ def columnCount(self, parent=QModelIndex()):
+ return self.column_count
+
+ def headerData(self, section, orientation, role):
+ if role != Qt.DisplayRole:
+ return None
+ if orientation == Qt.Horizontal:
+ return ("Date", "Magnitude")[section]
+ else:
+ return f"{section}"
+
+ def data(self, index, role=Qt.DisplayRole):
+ column = index.column()
+ row = index.row()
+
+ if role == Qt.DisplayRole:
+ if column == 0:
+ date = self.input_dates[row].toPython()
+ return str(date)[:-3]
+ elif column == 1:
+ magnitude = self.input_magnitudes[row]
+ return f"{magnitude:.2f}"
+ elif role == Qt.BackgroundRole:
+ return (QColor(Qt.white), QColor(self.color))[column]
+ elif role == Qt.TextAlignmentRole:
+ return Qt.AlignRight
+
+ return None
diff --git a/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst
new file mode 100644
index 000000000..edfac3862
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst
@@ -0,0 +1,29 @@
+Chapter 2 - Filtering data
+===========================
+
+In the previous chapter, you learned how to read and print data that is a
+bit raw. Now, try to select a few columns and handle them properly.
+
+Start with these two columns: Time (time) and Magnitude (mag). After getting
+the information from these columns, filter and adapt the data. Try formatting
+the date to Qt types.
+
+There is not much to do for the Magnitude column, as it's just a floating point
+number. You could take special care to check if the data is correct. This could
+be done by filtering the data that follows the condition, "magnitude > 0", to
+avoid faulty data or unexpected behavior.
+
+The Date column provides data in UTC format (for example,
+2018-12-11T21:14:44.682Z), so you could easily map it to a QDateTime object
+defining the structure of the string. Additionally, you can adapt the time
+based on the timezone you are in, using QTimeZone.
+
+The following script filters and formats the CSV data as described earlier:
+
+.. literalinclude:: datavisualize2/main.py
+ :language: python
+ :linenos:
+ :lines: 3-
+
+Now that you have a tuple of QDateTime and float data, try improving the
+output further. That's what you'll learn in the following chapters.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png b/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png
new file mode 100644
index 000000000..075d796b8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png b/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png
new file mode 100644
index 000000000..8d7e4ba13
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/datavisualize/index.rst b/sources/pyside6/doc/tutorials/datavisualize/index.rst
new file mode 100644
index 000000000..35b56706a
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/index.rst
@@ -0,0 +1,31 @@
+Data Visualization Tool Tutorial
+*********************************
+
+In this tutorial, you'll learn about the data visualization capabilities
+of |project|. To start with, find some open data to visualize. For example,
+data about the magnitude of earthquakes during the last hour published on the
+US Geological Survey website. You could download the
+`All earthquakes <https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.csv>`_
+open data in a CSV format for this tutorial.
+
+.. image:: images/datavisualization_app.png
+ :width: 800
+ :alt: Data Visualization Screenshot
+
+In the following chapters of this tutorial you'll learn how to
+visualize data from a CSV in a line chart.
+
+.. toctree::
+ :glob:
+ :titlesonly:
+
+ read*
+ filter*
+ add_main*
+ add_tab*
+ add_chart*
+ plot*
+
+You can download the sources from `here`_.
+
+.. _here: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside6/doc/tutorials/datavisualize
diff --git a/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst
new file mode 100644
index 000000000..f71fca4c8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst
@@ -0,0 +1,27 @@
+Chapter 6 - Plot the data in the ChartView
+===========================================
+
+The last step of this tutorial is to plot the CSV data inside our QChart. For
+this, you need to go over our data and include the data on a QLineSeries.
+
+After adding the data to the series, you can modify the axis to properly
+display the QDateTime on the X-axis, and the magnitude values on the Y-axis.
+
+Here is the updated :code:`main_widget.py` that includes an additional
+function to plot data using a QLineSeries:
+
+.. literalinclude:: datavisualize6/main_widget.py
+ :language: python
+ :linenos:
+ :lines: 3-
+ :emphasize-lines: 33,56-91
+
+Now, run the application to visualize the earthquake magnitudes
+data at different times.
+
+.. image:: images/datavisualization_app.png
+ :width: 600
+ :alt: Data Visualization Screenshot
+
+Try modifying the sources to get different output. For example, you could try
+to plot more data from the CSV.
diff --git a/sources/pyside6/doc/tutorials/datavisualize/read_data.rst b/sources/pyside6/doc/tutorials/datavisualize/read_data.rst
new file mode 100644
index 000000000..53b0d1cb0
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/datavisualize/read_data.rst
@@ -0,0 +1,41 @@
+Chapter 1 - Reading data from a CSV
+===================================
+
+There are several ways to read data from a CSV file. The following are the most
+common ways:
+
+- Native reading
+- the `CSV module <https://docs.python.org/3/library/csv.html>`_
+- the `numpy module <https://www.numpy.org>`_
+- the `pandas module <https://pandas.pydata.org/>`_
+
+In this chapter, you will learn to use pandas to read and filter CSV data.
+In addition, you could pass the data file through a command-line option to your
+script.
+
+The following python script, :code:`main.py`, demonstrates how to do it:
+
+.. literalinclude:: datavisualize1/main.py
+ :language: python
+ :linenos:
+ :lines: 3-
+
+The Python script uses the :code:`argparse` module to accept and parse input
+from the command line. It then uses the input, which in this case is the filename,
+to read and print data to the prompt.
+
+Try running the script in the following way to check if you get desired output:
+
+::
+
+ $python datavisualize1/main.py -f all_hour.csv
+ time latitude longitude depth ... magNst status locationSource magSource
+ 0 2019-01-10T12:11:24.810Z 34.128166 -117.775497 4.46 ... 6.0 automatic ci ci
+ 1 2019-01-10T12:04:26.320Z 19.443333 -155.615997 0.72 ... 6.0 automatic hv hv
+ 2 2019-01-10T11:57:48.980Z 33.322500 -116.393167 4.84 ... 11.0 automatic ci ci
+ 3 2019-01-10T11:52:09.490Z 38.835667 -122.836670 1.28 ... 7.0 automatic nc nc
+ 4 2019-01-10T11:25:44.854Z 65.108200 -149.370100 20.60 ... NaN automatic ak ak
+ 5 2019-01-10T11:25:23.786Z 69.151800 -144.497700 10.40 ... NaN reviewed ak ak
+ 6 2019-01-10T11:16:11.761Z 61.331800 -150.070800 20.10 ... NaN automatic ak ak
+
+ [7 rows x 22 columns]
diff --git a/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst b/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst
new file mode 100644
index 000000000..85c326347
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/mixed_debugging.rst
@@ -0,0 +1,126 @@
+How To Debug a C++ Extension of a PySide6 Application?
+******************************************************
+
+When debugging PySide code, very often you would also like to debug the
+corresponding C++ extension of the PySide module. This is done by attaching your
+debugger to the Python interpreter. In this tutorial, we are going to take you
+through a comprehensive guide in building Qt 6, using the built Qt 6 to build
+PySide6, and then starting a debugging process in either *Qt Creator* or VSCode.
+
+With VSCode, you should be able to see the combined call stacks for both C++ and
+Python together. With *Qt Creator*, unfortunately you would only be able to
+debug the native C++ code of the PySide module; that is you won't be able to set
+breakpoints inside the Python code.
+
+.. note:: This tutorial is created on Ubuntu 20.04 LTS with the debugger as GDB.
+ As such, this tutorial is mainly focused on Linux users. Nevertheless, links to
+ setup everything in other platforms are mentioned along with each
+ sub-section.
+
+Let's get started.
+
+Install All The Library Dependencies Based on Your Platform
+-----------------------------------------------------------
+
+.. code-block:: bash
+
+ sudo apt install libfontconfig1-dev libfreetype6-dev \
+ libx11-dev libx11-xcb-dev libxext-dev libxfixes-dev \
+ libxi-dev libxrender-dev libxcb1-dev libxcb-glx0-dev \
+ libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev \
+ libxcb-icccm4-dev libxcb-sync-dev libxcb-xfixes0-dev \
+ libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev \
+ libxcb-util-dev libxcb-xinerama0-dev libxcb-xkb-dev \
+ libxkbcommon-dev libxkbcommon-x11-dev libatspi2.0-dev \
+ libopengl0 -y
+
+If you have to use the Qt Multimedia module, you have to install gstreamer also.
+
+.. code-block:: bash
+
+ sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
+ libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base \
+ gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
+ gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc \
+ gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl \
+ gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio
+
+Generally, any information on missing packages can be found by inspecting the
+config.summary in you CMake build folder.
+
+.. note:: For other platforms install the same packages using the instructions
+ mentioned here `Qt Install on Linux <https://doc.qt.io/qt-6/linux-requirements.html>`_
+
+Build Qt
+--------
+
+This is an optional step in case you only want to debug the CPython bindings or if you have DEBUG_SYMBOLS for Qt 6 already.
+
+There are multiple ways to build Qt - configure script or manually with CMake.
+Find the build system information `Qt 6 Build System
+<https://www.qt.io/blog/qt-6-build-system>`_.
+
+1. Get the source code.
+
+ .. code-block:: bash
+
+ git clone git://code.qt.io/qt/qt5.git
+ # Get submodules
+ cd qt5
+ perl init-repository
+
+2. Once you have the source code, the next step is to generate the build using
+ CMake, then building and installing it.
+
+ .. code-block:: bash
+
+ cmake -GNinja -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_INSTALL_PREFIX=/path/to/install/Qt -DBUILD_qtwebengine=OFF ..
+ cmake --build . --parallel
+ cmake --install .
+
+ As you notice here, we are skipping the Qt WebEngine module because this greatly
+ decreases the build time. However, PySide6 supports Qt WebEngine
+ features. So feel free to add it, if you need it.
+
+More instructions on building Qt 6 can be found in the following links:
+
+* https://wiki.qt.io/Building_Qt_6_from_Git
+* https://code.qt.io/cgit/qt/qtbase.git/tree/cmake/README.md
+* https://code.qt.io/cgit/qt/qtbase.git/tree/cmake/configure-cmake-mapping.md
+
+Add the **bin** and **lib** path to the environment variables
+--------------------------------------------------------------
+
+.. code-block:: bash
+
+ export PATH="/path/to/custom/qt/bin:$PATH"
+ export LD_LIBRARY_PATH="/path/to/custom/qt/lib:$LD_LIBRARY_PATH"
+
+Build PySide6 using the Qt 6 that you built earlier
+----------------------------------------------------
+
+Follow the steps mentioned `Getting Started - Qt for Python
+<https://doc.qt.io/qtforpython/gettingstarted/index.html>`_
+
+You may manually select the modules to install using the ``--module-subset`` cli
+argument for `setup.py`. This was my installation script
+
+.. code-block:: bash
+
+ python setup.py install --qpaths=/path/to/qpaths --debug \
+ --ignore-git --reuse-build
+
+It is recommended to use a Python virtual environment rather than installing in the global Python.
+
+Debugging the process using your preferred IDE
+----------------------------------------------
+
+The following sections guide you through the setup for *Qt Creator* or VSCode.
+
+.. toctree::
+ :glob:
+ :titlesonly:
+
+ qtcreator/qtcreator
+ vscode/vscode
diff --git a/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst b/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst
new file mode 100644
index 000000000..223e608fc
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/qml_debugging.rst
@@ -0,0 +1,32 @@
+Using Qt Creator's QML Debugger for a PySide6 QML Application
+*************************************************************
+
+Besides the C++ debugger, *Qt Creator* provides a `QML debugger`_ which lets you
+inspect JavaScript code. It works by connecting to a socket server run by the
+``QmlEngine`` instance. The port is passed on the command line. To enable it,
+add the below code to your QML application:
+
+.. code-block:: python
+
+ from argparse import ArgumentParser, RawTextHelpFormatter
+
+ ...
+
+ if __name__ == "__main__":
+ argument_parser = ArgumentParser(...)
+ argument_parser.add_argument("-qmljsdebugger", action="store",
+ help="Enable QML debugging")
+ options = argument_parser.parse_args()
+ if options.qmljsdebugger:
+ QQmlDebuggingEnabler.enableDebugging(True)
+ app = QApplication(sys.argv)
+
+
+For instructions on how to use the QML debugger, see
+`Debugging a Qt Quick Example Application`_.
+
+.. note:: The code should be removed or disabled when shipping the application
+ as it poses a security risk.
+
+.. _`QML debugger`: https://doc.qt.io/qtcreator/creator-debugging-qml.html
+.. _`Debugging a Qt Quick Example Application`: https://doc.qt.io/qtcreator/creator-qml-debugging-example.html
diff --git a/sources/pyside6/doc/tutorials/debugging/qtcreator/breakpoint_cpp.png b/sources/pyside6/doc/tutorials/debugging/qtcreator/breakpoint_cpp.png
new file mode 100644
index 000000000..c8197118e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/qtcreator/breakpoint_cpp.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_create.png b/sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_create.png
new file mode 100644
index 000000000..8362c3310
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_create.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_run_config.png b/sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_run_config.png
new file mode 100644
index 000000000..4f0bbe3d4
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/qtcreator/custom_executable_run_config.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst b/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst
new file mode 100644
index 000000000..a78a67fad
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/qtcreator/qtcreator.rst
@@ -0,0 +1,39 @@
+Debugging PySide with Qt Creator (Linux)
+****************************************
+
+As opposed to VSCode, presently *Qt Creator* does not support mixed mode debugging.
+However, we can debug the C++ implementation of the corresponding Python PySide
+code. Unlike VSCode, *Qt Creator* provides a very easy interface to attach GDB to
+the Python interpreter. It saves you from doing all the extra configuration
+steps, that have to be done with VSCode.
+
+Here are the steps:
+
+1. Set a breakpoint on the C++ code.
+
+2. Go to Projects -> Run -> Run Configuration -> Add. This is going to open a
+ new window shown below.
+
+ .. image:: custom_executable_create.png
+ :alt: creation of custom executable
+ :align: center
+
+3. Click on Custom Executable and `Create` a new configuration. Feed in the
+ details like shown below.
+
+ .. image:: custom_executable_run_config.png
+ :alt: run configuration of custom executable
+ :align: center
+
+4. Debug -> Start Debugging -> Start Debugging Without Deployment.
+
+ .. image:: start_debugging_without_deployment.png
+ :alt: start debugging without deployment
+ :align: center
+
+You will now hit you breakpoint and can start debugging your code.
+
+.. image:: breakpoint_cpp.png
+ :alt: breakpoint cpp
+ :align: center
+
diff --git a/sources/pyside6/doc/tutorials/debugging/qtcreator/start_debugging_without_deployment.png b/sources/pyside6/doc/tutorials/debugging/qtcreator/start_debugging_without_deployment.png
new file mode 100644
index 000000000..9ce0688d3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/qtcreator/start_debugging_without_deployment.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/vscode/audioformat_cpp.png b/sources/pyside6/doc/tutorials/debugging/vscode/audioformat_cpp.png
new file mode 100644
index 000000000..57577eea4
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/vscode/audioformat_cpp.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/vscode/audioformat_wrapper.png b/sources/pyside6/doc/tutorials/debugging/vscode/audioformat_wrapper.png
new file mode 100644
index 000000000..319577fea
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/vscode/audioformat_wrapper.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/vscode/breakpoint_gdb.png b/sources/pyside6/doc/tutorials/debugging/vscode/breakpoint_gdb.png
new file mode 100644
index 000000000..b9bc4acde
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/vscode/breakpoint_gdb.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/vscode/find_process_gdb.png b/sources/pyside6/doc/tutorials/debugging/vscode/find_process_gdb.png
new file mode 100644
index 000000000..65176d9bb
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/vscode/find_process_gdb.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/vscode/python_set_interpreter.png b/sources/pyside6/doc/tutorials/debugging/vscode/python_set_interpreter.png
new file mode 100644
index 000000000..1a26c9d9c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/vscode/python_set_interpreter.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/debugging/vscode/vscode.rst b/sources/pyside6/doc/tutorials/debugging/vscode/vscode.rst
new file mode 100644
index 000000000..b2a527b0e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/debugging/vscode/vscode.rst
@@ -0,0 +1,192 @@
+Debugging PySide with VSCode (Linux + Windows)
+**********************************************
+
+VSCode enables you to use more than one debugger in a single debugging session.
+This means that we can use Python PDB and GDB (or the MSVC debugger for Windows)
+in a single session. With VSCode you would be able to do the following:
+
+* See the call stacks for both Python and C++ together.
+* Put breakpoints in both the Python and the C++ code.
+* Step from Python to C++ code and vice versa.
+
+For Windows, see :ref:`creating_windows_debug_builds`.
+
+Let's get started with setting up everything and debugging a Python process.
+
+Setting the Python interpreter
+------------------------------
+
+In order to debug Python code, it is necessary to set the correct Python
+interpreter in VSCode - this will ensure that all Python integrations of VSCode
+use the same interpreter. However, this does not affect C++ debugging, and the
+Python executable path must be set for the corresponding launch target
+separately (see the section below).
+
+To set the Python interpreter, open a Python file and click the corresponding
+option on the right side of the VSCode status bar, which should look similar to
+this:
+
+.. image:: python_set_interpreter.png
+ :alt: set Python interpreter
+ :align: center
+
+Alternatively, open the VSCode command palette (F1 or Ctrl + Shift + P) and
+search for "Python: Select Interpreter".
+
+Creating Configurations in launch.json
+--------------------------------------
+
+``Run -> Add Configuration -> Python -> Python File``
+
+This should create a launch.json file which looks like this:
+
+.. code-block:: javascript
+
+ {
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Current File",
+ "type": "python",
+ "request": "launch",
+ "program": "${file}",
+ "console": "integratedTerminal"
+ }
+ ]
+ }
+
+It should already consist of a configuration named "Python: Current File",
+which allows us to debug the current open Python file.
+
+Now, we need to add a configuration to attach the C++ debugger to the Python
+process that is already running in debug mode. If you have the C/C++ extension
+installed and the appropriate debugger for your system, VSCode should be able
+to automatically offer to add a configuration. On Linux, this is suggested with
+the name
+
+* "C/C++: (gdb) Attach"
+
+and on Windows with the name
+
+* "C/C++: (Windows) Attach"
+
+Your launch.json should now look like this on Linux:
+
+.. code-block:: javascript
+
+ {
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Current File",
+ "type": "python",
+ "request": "launch",
+ "program": "${file}",
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "(gdb) Attach",
+ "type": "cppdbg",
+ "request": "attach",
+ "program": "/path/to/python",
+ "processId": "${command:pickProcess}",
+ "MIMode": "gdb", "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ }
+ ]
+ }
+
+And like this on Windows:
+
+.. code-block:: javascript
+
+ {
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Current File",
+ "type": "python",
+ "request": "launch",
+ "program": "${file}",
+ "console": "integratedTerminal"
+ },
+ {
+ "name": "(Windows) Attach",
+ "type": "cppvsdbg",
+ "request": "attach",
+ "processId": "${command:pickProcess}",
+ }
+ ]
+ }
+
+For Linux, also make sure that the value of "program" refers to your Python
+interpreter inside your virtual environment (for Windows this is not needed).
+We need the processId to attach the gdb debugger to the process. With
+"${command:pickProcess}", we find the processId on the go, as we will see later.
+
+Now, we are ready to debug.
+
+Debug The Process
+-----------------
+
+1. Set a breakpoint in the Python code.
+
+2. Go to ``Run And Debug`` (Ctrl + Shift + D) and run the "Python: Current File"
+ by clicking the run symbol (green right-arrow). This will hit the breakpoint
+ and will halt the Python debugger.
+
+3. Using the drop-down menu change from "Python:
+ Current File" to "(gdb) Attach" or "(Windows) Attach". Your setup should now
+ look like this.
+
+ .. image:: breakpoint_gdb.png
+ :alt: breakpoint before attach gdb
+ :align: center
+
+4. Run "(gdb) Attach" or "(Windows) Attach" and this should ask you for the
+ processId of the Python process to which you want to attach the C++ debugger.
+ VSCode also lets you search for the process by its name.
+
+ .. tip:: You can find the processId by running ``ps aux | grep python``
+
+ .. image:: find_process_gdb.png
+ :alt: find process vscode
+ :align: center
+
+5. VSCode might now ask you for superuser permissions. In that case, type 'y'
+ and enter your password.
+
+ .. code-block:: bash
+
+ Superuser access is required to attach to a process. Attaching as
+ superuser can potentially harm your computer. Do you want to continue?
+ [y/N]_
+
+6. That is it. You should now be able to hit the breakpoints that you have set
+ on the C++ counterparts.
+
+ .. figure:: audioformat_wrapper.png
+ :alt: Breakpoint set on the shiboken wrapper class
+ :align: left
+
+ Breakpoint set on the shiboken wrapper class
+
+ .. figure:: audioformat_cpp.png
+ :alt: Breakpoint set on C++ implementation
+ :align: left
+
+ Breakpoint set on C++ implementation
diff --git a/sources/pyside6/doc/tutorials/expenses/expenses.rst b/sources/pyside6/doc/tutorials/expenses/expenses.rst
new file mode 100644
index 000000000..2064488ae
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/expenses.rst
@@ -0,0 +1,285 @@
+Expenses Tool Tutorial
+======================
+
+In this tutorial you will learn the following concepts:
+ * creating user interfaces programatically,
+ * layouts and widgets,
+ * overloading Qt classes,
+ * connecting signal and slots,
+ * interacting with QWidgets,
+ * and building your own application.
+
+The requirements:
+ * A simple window for the application
+ (`QMainWindow <https://doc.qt.io/qtforpython/PySide6/QtWidgets/QMainWindow.html>`_).
+ * A table to keep track of the expenses
+ (`QTableWidget <https://doc.qt.io/qtforpython/PySide6/QtWidgets/QTableWidget.html>`_).
+ * Two input fields to add expense information
+ (`QLineEdit <https://doc.qt.io/qtforpython/PySide6/QtWidgets/QLineEdit.html>`_).
+ * Buttons to add information to the table, plot data, clear table, and exit the application
+ (`QPushButton <https://doc.qt.io/qtforpython/PySide6/QtWidgets/QPushButton.html>`_).
+ * A verification step to avoid invalid data entry.
+ * A chart to visualize the expense data
+ (`QChart <https://doc.qt.io/qtforpython/PySide6/QtCharts/QChart.html>`_) that will
+ be embedded in a chart view
+ (`QChartView <https://doc.qt.io/qtforpython/PySide6/QtCharts/QChartView.html>`_).
+
+Empty window
+------------
+
+The base structure for a `QApplication` is located inside the `if __name__ == "__main__":`
+code block.
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == "__main__":
+ app = QApplication([])
+ # ...
+ sys.exit(app.exec())
+
+Now, to start the development, create an empty window called `MainWindow`.
+You could do that by defining a class that inherits from `QMainWindow`.
+
+.. literalinclude:: steps/01-expenses.py
+ :linenos:
+ :lines: 8-22
+ :emphasize-lines: 1-4
+
+Now that our class is defined, create an instance of it and call `show()`.
+
+.. literalinclude:: steps/01-expenses.py
+ :linenos:
+ :lines: 8-22
+ :emphasize-lines: 10-12
+
+Menu bar
+--------
+
+Using a `QMainWindow` gives some features for free, among them a *menu bar*. To use it, you need
+to call the method `menuBar()` and populate it inside the `MainWindow` class.
+
+.. literalinclude:: steps/02-expenses.py
+ :linenos:
+ :lines: 9-19
+ :emphasize-lines: 10
+
+Notice that the code snippet adds a *File* menu with the *Exit* option only.
+
+The *Exit* option must be connected to a slot that triggers the application to exit. We pass
+``QWidget.close()`` here. After the last window has been closed, the application exits.
+
+Empty widget and data
+---------------------
+
+The `QMainWindow` enables us to set a central widget that will be displayed when showing the window
+(`read more <https://doc.qt.io/qt-5/qmainwindow.html#details>`_).
+This central widget could be another class derived from `QWidget`.
+
+Additionally, you will define example data to visualize later.
+
+.. literalinclude:: steps/04-expenses.py
+ :linenos:
+ :lines: 8-15
+
+With the `Widget` class in place, modify `MainWindow`'s initialization code
+
+.. literalinclude:: steps/04-expenses.py
+ :linenos:
+ :lines: 37-40
+
+Window layout
+-------------
+
+Now that the main empty window is in place, you need to start adding widgets to achieve the main
+goal of creating an expenses application.
+
+After declaring the example data, you can visualize it on a simple `QTableWidget`. To do so, you
+will add this procedure to the `Widget` constructor.
+
+.. warning:: Only for the example purpose a QTableWidget will be used,
+ but for more performance-critical applications the combination
+ of a model and a QTableView is encouraged.
+
+.. literalinclude:: steps/05-expenses.py
+ :linenos:
+ :lines: 11-31
+
+As you can see, the code also includes a `QHBoxLayout` that provides the container to place widgets
+horizontally.
+
+Additionally, the `QTableWidget` allows for customizing it, like adding the labels for the two
+columns that will be used, and to *stretch* the content to use the whole `Widget` space.
+
+The last line of code refers to *filling the table**, and the code to perform that task is
+displayed below.
+
+.. literalinclude:: steps/05-expenses.py
+ :linenos:
+ :lines: 33-39
+
+Having this process on a separate method is a good practice to leave the constructor more readable,
+and to split the main functions of the class in independent processes.
+
+
+Right side layout
+-----------------
+
+Because the data that is being used is just an example, you are required to include a mechanism to
+input items to the table, and extra buttons to clear the table's content, and also quit the
+application.
+
+For input lines along with descriptive labels, you will use a `QFormLayout`. Then,
+you will nest the form layout into a `QVBoxLayout` along with the buttons.
+
+.. literalinclude:: steps/06-expenses.py
+ :linenos:
+ :lines: 27-43
+
+Leaving the table on the left side and these newly included widgets to the right side
+will be just a matter to add a layout to our main `QHBoxLayout` as you saw in the previous
+example:
+
+.. literalinclude:: steps/06-expenses.py
+ :linenos:
+ :lines: 45-48
+
+The next step will be connecting those new buttons to slots.
+
+
+Adding elements
+---------------
+
+Each `QPushButton` have a signal called *clicked*, that is emitted when you click on the button.
+This will be more than enough for this example, but you can see other signals in the `official
+documentation <https://doc.qt.io/qtforpython/PySide6/QtWidgets/QAbstractButton.html#signals>`_.
+
+.. literalinclude:: steps/07-expenses.py
+ :linenos:
+ :lines: 50-52
+
+As you can see on the previous lines, we are connecting each *clicked* signal to different slots.
+In this example slots are normal class methods in charge of perform a determined task associated
+with our buttons. It is really important to decorate each method declaration with a `@Slot()`,
+that way, PySide6 knows internally how to register them into Qt and they
+will be invokable from `Signals` of QObjects when connected.
+
+
+.. literalinclude:: steps/07-expenses.py
+ :linenos:
+ :lines: 57-82
+ :emphasize-lines: 1, 23
+
+Since these slots are methods, we can access the class variables, like our `QTableWidget` to
+interact with it.
+
+The mechanism to add elements into the table is described as the following:
+
+ * get the *description* and *price* from the fields,
+ * insert a new empty row to the table,
+ * set the values for the empty row in each column,
+ * clear the input text fields,
+ * include the global count of table rows.
+
+To exit the application you can use the `quit()` method of the unique `QApplication` instance, and
+to clear the content of the table you can just set the table *row count*, and the internal count to
+zero.
+
+Verification step
+-----------------
+
+Adding information to the table needs to be a critical action that require a verification step
+to avoid adding invalid information, for example, empty information.
+
+You can use a signal from `QLineEdit` called *textChanged* which will be emitted every
+time something inside changes, i.e.: each key stroke.
+
+You can connect two different object's signal to the same slot, and this will be the case
+for your current application:
+
+.. literalinclude:: steps/08-expenses.py
+ :linenos:
+ :lines: 57-58
+
+The content of the *check_disable* slot will be really simple:
+
+.. literalinclude:: steps/08-expenses.py
+ :linenos:
+ :lines: 77-80
+
+You have two options, write a verification based on the current value
+of the string you retrieve, or manually get the whole content of both
+`QLineEdit`. The second is preferred in this case, so you can verify
+if the two inputs are not empty to enable the button *Add*.
+
+.. note:: Qt also provides a special class called
+ `QValidator <https://doc.qt.io/qtforpython/PySide6/QtGui/QValidator.html?highlight=qvalidator>`_
+ that you can use to validate any input.
+
+Empty chart view
+----------------
+
+New items can be added to the table, and the visualization is so far
+OK, but you can accomplish more by representing the data graphically.
+
+First you will include an empty `QChartView` placeholder into the right
+side of your application.
+
+.. literalinclude:: steps/09-expenses.py
+ :linenos:
+ :lines: 30-32
+
+Additionally the order of how you include widgets to the right
+`QVBoxLayout` will also change.
+
+.. literalinclude:: steps/09-expenses.py
+ :linenos:
+ :lines: 46-54
+ :emphasize-lines: 8
+
+Notice that before we had a line with `self.right.addStretch()`
+to fill up the vertical space between the *Add* and the *Clear* buttons,
+but now, with the `QChartView` it will not be necessary.
+
+Also, you need include a *Plot* button if you want to do it on-demand.
+
+Full application
+----------------
+
+For the final step, you will need to connect the *Plot* button
+to a slot that creates a chart and includes it into your `QChartView`.
+
+.. literalinclude:: steps/10-expenses.py
+ :linenos:
+ :lines: 62-67
+ :emphasize-lines: 3
+
+That is nothing new, since you already did it for the other buttons,
+but now take a look at how to create a chart and include it into
+your `QChartView`.
+
+.. literalinclude:: steps/10-expenses.py
+ :linenos:
+ :lines: 95-107
+
+The following steps show how to fill a `QPieSeries`:
+
+ * create a `QPieSeries`,
+ * iterate over the table row IDs,
+ * get the items at the *i* position,
+ * add those values to the *series*.
+
+Once the series has been populated with our data, you create a new `QChart`,
+add the series on it, and optionally set an alignment for the legend.
+
+The final line `self.chart_view.setChart(chart)` is in charge of bringing
+your newly created chart to the `QChartView`.
+
+The application will look like this:
+
+.. image:: expenses_tool.png
+
+And now you can see the whole code:
+
+.. literalinclude:: main.py
+ :linenos:
diff --git a/sources/pyside6/doc/tutorials/expenses/expenses_tool.png b/sources/pyside6/doc/tutorials/expenses/expenses_tool.png
new file mode 100644
index 000000000..7a6f6d1f0
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/expenses_tool.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/expenses/main.py b/sources/pyside6/doc/tutorials/expenses/main.py
new file mode 100644
index 000000000..a3a998470
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/main.py
@@ -0,0 +1,153 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import Qt, Slot
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import (QApplication, QFormLayout, QHeaderView,
+ QHBoxLayout, QLineEdit, QMainWindow,
+ QPushButton, QTableWidget, QTableWidgetItem,
+ QVBoxLayout, QWidget)
+from PySide6.QtCharts import QChartView, QPieSeries, QChart
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # Chart
+ self.chart_view = QChartView()
+ self.chart_view.setRenderHint(QPainter.Antialiasing)
+
+ # Right
+ self.description = QLineEdit()
+ self.description.setClearButtonEnabled(True)
+ self.price = QLineEdit()
+ self.price.setClearButtonEnabled(True)
+
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+ self.plot = QPushButton("Plot")
+
+ # Disabling 'Add' button
+ self.add.setEnabled(False)
+
+ form_layout = QFormLayout()
+ form_layout.addRow("Description", self.description)
+ form_layout.addRow("Price", self.price)
+ self.right = QVBoxLayout()
+ self.right.addLayout(form_layout)
+ self.right.addWidget(self.add)
+ self.right.addWidget(self.plot)
+ self.right.addWidget(self.chart_view)
+ self.right.addWidget(self.clear)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+ self.layout.addWidget(self.table)
+ self.layout.addLayout(self.right)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.plot.clicked.connect(self.plot_data)
+ self.clear.clicked.connect(self.clear_table)
+ self.description.textChanged.connect(self.check_disable)
+ self.price.textChanged.connect(self.check_disable)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ des = self.description.text()
+ price = float(self.price.text())
+
+ self.table.insertRow(self.items)
+ description_item = QTableWidgetItem(des)
+ price_item = QTableWidgetItem(f"{price:.2f}")
+ price_item.setTextAlignment(Qt.AlignRight)
+
+ self.table.setItem(self.items, 0, description_item)
+ self.table.setItem(self.items, 1, price_item)
+
+ self.description.clear()
+ self.price.clear()
+
+ self.items += 1
+
+ @Slot()
+ def check_disable(self, s):
+ enabled = bool(self.description.text() and self.price.text())
+ self.add.setEnabled(enabled)
+
+ @Slot()
+ def plot_data(self):
+ # Get table information
+ series = QPieSeries()
+ for i in range(self.table.rowCount()):
+ text = self.table.item(i, 0).text()
+ number = float(self.table.item(i, 1).text())
+ series.append(text, number)
+
+ chart = QChart()
+ chart.addSeries(series)
+ chart.legend().setAlignment(Qt.AlignLeft)
+ self.chart_view.setChart(chart)
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ description_item = QTableWidgetItem(desc)
+ price_item = QTableWidgetItem(f"{price:.2f}")
+ price_item.setTextAlignment(Qt.AlignRight)
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, description_item)
+ self.table.setItem(self.items, 1, price_item)
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.setRowCount(0)
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/main_snake_prop.py b/sources/pyside6/doc/tutorials/expenses/main_snake_prop.py
new file mode 100644
index 000000000..055544409
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/main_snake_prop.py
@@ -0,0 +1,177 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import QMargins, Qt, Slot, QSize
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import (QAction, QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit,
+ QMainWindow, QPushButton, QTableWidget, QTableWidgetItem,
+ QVBoxLayout, QWidget)
+from PySide6.QtCharts import QtCharts
+
+from __feature__ import snake_case, true_property
+
+
+class Widget(QWidget):
+ def __init__(self):
+ QWidget.__init__(self)
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.column_count = 2
+ self.table.horizontal_header_labels = ["Description", "Price"]
+ self.table.horizontal_header().section_resize_mode = QHeaderView.Stretch
+
+ # Chart
+ self.chart_view = QtCharts.QChartView()
+ self.chart_view.render_hint = QPainter.Antialiasing
+
+ # Right
+ self.description = QLineEdit()
+ self.price = QLineEdit()
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+ self.quit = QPushButton("Quit")
+ self.plot = QPushButton("Plot")
+
+ # Disabling 'Add' button
+ self.add.enabled = False
+
+ self.right = QVBoxLayout()
+
+ self.right.contents_margins = QMargins(10, 10, 10, 10)
+ self.right.add_widget(QLabel("Description"))
+ self.right.add_widget(self.description)
+ self.right.add_widget(QLabel("Price"))
+ self.right.add_widget(self.price)
+ self.right.add_widget(self.add)
+ self.right.add_widget(self.plot)
+ self.right.add_widget(self.chart_view)
+ self.right.add_widget(self.clear)
+ self.right.add_widget(self.quit)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout()
+
+ #self.table_view.setSizePolicy(size)
+ self.layout.add_widget(self.table)
+ self.layout.form_layout(self.right)
+
+ # Set the layout to the QWidget
+ self.set_layout(self.layout)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.quit.clicked.connect(self.quit_application)
+ self.plot.clicked.connect(self.plot_data)
+ self.clear.clicked.connect(self.clear_table)
+ self.description.textChanged[str].connect(self.check_disable)
+ self.price.textChanged[str].connect(self.check_disable)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ des = self.description.text
+ price = self.price.text
+
+ try:
+ price_item = QTableWidgetItem(f"{float(price):.2f}")
+ price_item.text_alignment = Qt.AlignRight
+
+ self.table.insert_row(self.items)
+ description_item = QTableWidgetItem(des)
+
+ self.table.set_item(self.items, 0, description_item)
+ self.table.set_item(self.items, 1, price_item)
+
+ self.description.text = ""
+ self.price.text = ""
+
+ self.items += 1
+ except ValueError:
+ print("Wrong price", price)
+
+ @Slot()
+ def check_disable(self, s):
+ if not self.description.text or not self.price.text:
+ self.add.enabled = False
+ else:
+ self.add.enabled = True
+
+ @Slot()
+ def plot_data(self):
+ # Get table information
+ series = QtCharts.QPieSeries()
+ for i in range(self.table.row_count):
+ text = self.table.item(i, 0).text()
+ number = float(self.table.item(i, 1).text())
+ series.append(text, number)
+
+ chart = QtCharts.QChart()
+ chart.add_series(series)
+ chart.legend().alignment = Qt.AlignLeft
+ self.chart_view.set_chart(chart)
+
+ @Slot()
+ def quit_application(self):
+ QApplication.quit()
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ description_item = QTableWidgetItem(desc)
+ price_item = QTableWidgetItem(f"{price:.2f}")
+ price_item.text_alignment = Qt.AlignRight
+ self.table.insert_row(self.items)
+ self.table.set_item(self.items, 0, description_item)
+ self.table.set_item(self.items, 1, price_item)
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.row_count = 0
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ QMainWindow.__init__(self)
+ self.window_title = "Tutorial"
+
+ # Menu
+ self.menu = self.menu_bar()
+ self.file_menu = self.menu.add_menu("File")
+
+ # Exit QAction
+ exit_action = QAction("Exit", self)
+ exit_action.shortcut = "Ctrl+Q"
+ exit_action.triggered.connect(self.exit_app)
+
+ self.file_menu.add_action(exit_action)
+ self.set_central_widget(widget)
+
+ @Slot()
+ def exit_app(self, checked):
+ QApplication.quit()
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.size = QSize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/01-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/01-expenses.py
new file mode 100644
index 000000000..27f2aef65
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/01-expenses.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import QApplication, QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ window = MainWindow()
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/02-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/02-expenses.py
new file mode 100644
index 000000000..039b818df
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/02-expenses.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import QApplication, QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ window = MainWindow()
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/03-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/03-expenses.py
new file mode 100644
index 000000000..039b818df
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/03-expenses.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import QApplication, QMainWindow
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+
+ window = MainWindow()
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/04-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/04-expenses.py
new file mode 100644
index 000000000..6723690a8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/04-expenses.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import QApplication, QMainWindow, QWidget
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/05-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/05-expenses.py
new file mode 100644
index 000000000..df0362fde
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/05-expenses.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout,
+ QMainWindow, QTableWidget, QTableWidgetItem,
+ QWidget)
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+ self.layout.addWidget(self.table)
+
+ # Fill example data
+ self.fill_table()
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(desc))
+ self.table.setItem(self.items, 1, QTableWidgetItem(str(price)))
+ self.items += 1
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/06-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/06-expenses.py
new file mode 100644
index 000000000..d19a6220f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/06-expenses.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import (QApplication, QFormLayout, QHeaderView,
+ QHBoxLayout, QLineEdit, QMainWindow, QPushButton,
+ QTableWidget, QTableWidgetItem, QVBoxLayout,
+ QWidget)
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # Right
+ self.description = QLineEdit()
+ self.description.setClearButtonEnabled(True)
+ self.price = QLineEdit()
+ self.price.setClearButtonEnabled(True)
+
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+
+ form_layout = QFormLayout()
+ form_layout.addRow("Description", self.description)
+ form_layout.addRow("Price", self.price)
+ self.right = QVBoxLayout()
+ self.right.addLayout(form_layout)
+ self.right.addWidget(self.add)
+ self.right.addStretch()
+ self.right.addWidget(self.clear)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+ self.layout.addWidget(self.table)
+ self.layout.addLayout(self.right)
+
+ # Fill example data
+ self.fill_table()
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(desc))
+ self.table.setItem(self.items, 1, QTableWidgetItem(str(price)))
+ self.items += 1
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/07-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/07-expenses.py
new file mode 100644
index 000000000..b1ed3fecc
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/07-expenses.py
@@ -0,0 +1,112 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import Slot
+from PySide6.QtWidgets import (QApplication, QFormLayout, QHeaderView,
+ QHBoxLayout, QLineEdit, QMainWindow, QPushButton,
+ QTableWidget, QTableWidgetItem, QVBoxLayout,
+ QWidget)
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # Right
+ self.description = QLineEdit()
+ self.description.setClearButtonEnabled(True)
+ self.price = QLineEdit()
+ self.price.setClearButtonEnabled(True)
+
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+
+ form_layout = QFormLayout()
+ form_layout.addRow("Description", self.description)
+ form_layout.addRow("Price", self.price)
+ self.right = QVBoxLayout()
+ self.right.addLayout(form_layout)
+ self.right.addWidget(self.add)
+ self.right.addStretch()
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+ self.layout.addWidget(self.table)
+ self.layout.addLayout(self.right)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.clear.clicked.connect(self.clear_table)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ des = self.description.text()
+ price = self.price.text()
+
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(des))
+ self.table.setItem(self.items, 1, QTableWidgetItem(price))
+
+ self.description.clear()
+ self.price.clear()
+
+ self.items += 1
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(desc))
+ self.table.setItem(self.items, 1, QTableWidgetItem(str(price)))
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.setRowCount(0)
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/08-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/08-expenses.py
new file mode 100644
index 000000000..0e7e21a32
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/08-expenses.py
@@ -0,0 +1,123 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import Slot
+from PySide6.QtWidgets import (QApplication, QFormLayout, QHeaderView,
+ QHBoxLayout, QLineEdit, QMainWindow, QPushButton,
+ QTableWidget, QTableWidgetItem, QVBoxLayout,
+ QWidget)
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # Right
+ self.description = QLineEdit()
+ self.description.setClearButtonEnabled(True)
+ self.price = QLineEdit()
+ self.price.setClearButtonEnabled(True)
+
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+
+ # Disabling 'Add' button
+ self.add.setEnabled(False)
+
+ form_layout = QFormLayout()
+ form_layout.addRow("Description", self.description)
+ form_layout.addRow("Price", self.price)
+ self.right = QVBoxLayout()
+ self.right.addLayout(form_layout)
+ self.right.addWidget(self.add)
+ self.right.addStretch()
+ self.right.addWidget(self.clear)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+ self.layout.addWidget(self.table)
+ self.layout.addLayout(self.right)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.clear.clicked.connect(self.clear_table)
+ self.description.textChanged.connect(self.check_disable)
+ self.price.textChanged.connect(self.check_disable)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ des = self.description.text()
+ price = self.price.text()
+
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(des))
+ self.table.setItem(self.items, 1, QTableWidgetItem(price))
+
+ self.description.clear()
+ self.price.clear()
+
+ self.items += 1
+
+ @Slot()
+ def check_disable(self, s):
+ enabled = bool(self.description.text() and self.price.text())
+ self.add.setEnabled(enabled)
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(desc))
+ self.table.setItem(self.items, 1, QTableWidgetItem(str(price)))
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.setRowCount(0)
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/09-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/09-expenses.py
new file mode 100644
index 000000000..279a46512
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/09-expenses.py
@@ -0,0 +1,131 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import Slot
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import (QApplication, QFormLayout, QHeaderView,
+ QHBoxLayout, QLineEdit, QMainWindow,
+ QPushButton, QTableWidget, QTableWidgetItem,
+ QVBoxLayout, QWidget)
+from PySide6.QtCharts import QChartView
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # Chart
+ self.chart_view = QChartView()
+ self.chart_view.setRenderHint(QPainter.Antialiasing)
+
+ # Right
+ self.description = QLineEdit()
+ self.description.setClearButtonEnabled(True)
+ self.price = QLineEdit()
+ self.price.setClearButtonEnabled(True)
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+ self.plot = QPushButton("Plot")
+
+ # Disabling 'Add' button
+ self.add.setEnabled(False)
+
+ form_layout = QFormLayout()
+ form_layout.addRow("Description", self.description)
+ form_layout.addRow("Price", self.price)
+ self.right = QVBoxLayout()
+ self.right.addLayout(form_layout)
+ self.right.addWidget(self.add)
+ self.right.addWidget(self.plot)
+ self.right.addWidget(self.chart_view)
+ self.right.addWidget(self.clear)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+
+ self.layout.addWidget(self.table)
+ self.layout.addLayout(self.right)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.clear.clicked.connect(self.clear_table)
+ self.description.textChanged.connect(self.check_disable)
+ self.price.textChanged.connect(self.check_disable)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ des = self.description.text()
+ price = self.price.text()
+
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(des))
+ self.table.setItem(self.items, 1, QTableWidgetItem(price))
+
+ self.description.clear()
+ self.price.clear()
+
+ self.items += 1
+
+ @Slot()
+ def check_disable(self, s):
+ enabled = bool(self.description.text() and self.price.text())
+ self.add.setEnabled(enabled)
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, QTableWidgetItem(desc))
+ self.table.setItem(self.items, 1, QTableWidgetItem(str(price)))
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.setRowCount(0)
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/expenses/steps/10-expenses.py b/sources/pyside6/doc/tutorials/expenses/steps/10-expenses.py
new file mode 100644
index 000000000..a3a998470
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/expenses/steps/10-expenses.py
@@ -0,0 +1,153 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtCore import Qt, Slot
+from PySide6.QtGui import QPainter
+from PySide6.QtWidgets import (QApplication, QFormLayout, QHeaderView,
+ QHBoxLayout, QLineEdit, QMainWindow,
+ QPushButton, QTableWidget, QTableWidgetItem,
+ QVBoxLayout, QWidget)
+from PySide6.QtCharts import QChartView, QPieSeries, QChart
+
+
+class Widget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.items = 0
+
+ # Example data
+ self._data = {"Water": 24.5, "Electricity": 55.1, "Rent": 850.0,
+ "Supermarket": 230.4, "Internet": 29.99, "Bars": 21.85,
+ "Public transportation": 60.0, "Coffee": 22.45, "Restaurants": 120}
+
+ # Left
+ self.table = QTableWidget()
+ self.table.setColumnCount(2)
+ self.table.setHorizontalHeaderLabels(["Description", "Price"])
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+
+ # Chart
+ self.chart_view = QChartView()
+ self.chart_view.setRenderHint(QPainter.Antialiasing)
+
+ # Right
+ self.description = QLineEdit()
+ self.description.setClearButtonEnabled(True)
+ self.price = QLineEdit()
+ self.price.setClearButtonEnabled(True)
+
+ self.add = QPushButton("Add")
+ self.clear = QPushButton("Clear")
+ self.plot = QPushButton("Plot")
+
+ # Disabling 'Add' button
+ self.add.setEnabled(False)
+
+ form_layout = QFormLayout()
+ form_layout.addRow("Description", self.description)
+ form_layout.addRow("Price", self.price)
+ self.right = QVBoxLayout()
+ self.right.addLayout(form_layout)
+ self.right.addWidget(self.add)
+ self.right.addWidget(self.plot)
+ self.right.addWidget(self.chart_view)
+ self.right.addWidget(self.clear)
+
+ # QWidget Layout
+ self.layout = QHBoxLayout(self)
+ self.layout.addWidget(self.table)
+ self.layout.addLayout(self.right)
+
+ # Signals and Slots
+ self.add.clicked.connect(self.add_element)
+ self.plot.clicked.connect(self.plot_data)
+ self.clear.clicked.connect(self.clear_table)
+ self.description.textChanged.connect(self.check_disable)
+ self.price.textChanged.connect(self.check_disable)
+
+ # Fill example data
+ self.fill_table()
+
+ @Slot()
+ def add_element(self):
+ des = self.description.text()
+ price = float(self.price.text())
+
+ self.table.insertRow(self.items)
+ description_item = QTableWidgetItem(des)
+ price_item = QTableWidgetItem(f"{price:.2f}")
+ price_item.setTextAlignment(Qt.AlignRight)
+
+ self.table.setItem(self.items, 0, description_item)
+ self.table.setItem(self.items, 1, price_item)
+
+ self.description.clear()
+ self.price.clear()
+
+ self.items += 1
+
+ @Slot()
+ def check_disable(self, s):
+ enabled = bool(self.description.text() and self.price.text())
+ self.add.setEnabled(enabled)
+
+ @Slot()
+ def plot_data(self):
+ # Get table information
+ series = QPieSeries()
+ for i in range(self.table.rowCount()):
+ text = self.table.item(i, 0).text()
+ number = float(self.table.item(i, 1).text())
+ series.append(text, number)
+
+ chart = QChart()
+ chart.addSeries(series)
+ chart.legend().setAlignment(Qt.AlignLeft)
+ self.chart_view.setChart(chart)
+
+ def fill_table(self, data=None):
+ data = self._data if not data else data
+ for desc, price in data.items():
+ description_item = QTableWidgetItem(desc)
+ price_item = QTableWidgetItem(f"{price:.2f}")
+ price_item.setTextAlignment(Qt.AlignRight)
+ self.table.insertRow(self.items)
+ self.table.setItem(self.items, 0, description_item)
+ self.table.setItem(self.items, 1, price_item)
+ self.items += 1
+
+ @Slot()
+ def clear_table(self):
+ self.table.setRowCount(0)
+ self.items = 0
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, widget):
+ super().__init__()
+ self.setWindowTitle("Tutorial")
+
+ # Menu
+ self.menu = self.menuBar()
+ self.file_menu = self.menu.addMenu("File")
+
+ # Exit QAction
+ exit_action = self.file_menu.addAction("Exit", self.close)
+ exit_action.setShortcut("Ctrl+Q")
+
+ self.setCentralWidget(widget)
+
+
+if __name__ == "__main__":
+ # Qt Application
+ app = QApplication(sys.argv)
+ # QWidget
+ widget = Widget()
+ # QMainWindow using QWidget as central widget
+ window = MainWindow(widget)
+ window.resize(800, 600)
+ window.show()
+
+ # Execute application
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml
new file mode 100644
index 000000000..faa9175d6
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/Main.qml
@@ -0,0 +1,196 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls.Basic
+import QtQuick.Layouts
+import FileSystemModule
+
+pragma ComponentBehavior: Bound
+
+ApplicationWindow {
+ id: root
+
+ property bool expandPath: false
+ property bool showLineNumbers: true
+ property string currentFilePath: ""
+
+ width: 1100
+ height: 600
+ minimumWidth: 200
+ minimumHeight: 100
+ visible: true
+ color: Colors.background
+ flags: Qt.Window | Qt.FramelessWindowHint
+ title: qsTr("File System Explorer Example")
+
+ function getInfoText() : string {
+ let out = root.currentFilePath
+ if (!out)
+ return qsTr("File System Explorer")
+ return root.expandPath ? out : out.substring(out.lastIndexOf("/") + 1, out.length)
+ }
+
+ menuBar: MyMenuBar {
+ dragWindow: root
+ infoText: root.getInfoText()
+ MyMenu {
+ title: qsTr("File")
+
+ Action {
+ text: qsTr("Increase Font")
+ shortcut: StandardKey.ZoomIn
+ onTriggered: editor.text.font.pixelSize += 1
+ }
+ Action {
+ text: qsTr("Decrease Font")
+ shortcut: StandardKey.ZoomOut
+ onTriggered: editor.text.font.pixelSize -= 1
+ }
+ Action {
+ text: root.showLineNumbers ? qsTr("Toggle Line Numbers OFF")
+ : qsTr("Toggle Line Numbers ON")
+ shortcut: "Ctrl+L"
+ onTriggered: root.showLineNumbers = !root.showLineNumbers
+ }
+ Action {
+ text: root.expandPath ? qsTr("Toggle Short Path")
+ : qsTr("Toggle Expand Path")
+ enabled: root.currentFilePath
+ onTriggered: root.expandPath = !root.expandPath
+ }
+ Action {
+ text: qsTr("Reset Filesystem")
+ enabled: sidebar.currentTabIndex === 1
+ onTriggered: fileSystemView.rootIndex = undefined
+ }
+ Action {
+ text: qsTr("Exit")
+ onTriggered: Qt.exit(0)
+ shortcut: StandardKey.Quit
+ }
+ }
+
+ MyMenu {
+ title: qsTr("Edit")
+
+ Action {
+ text: qsTr("Cut")
+ shortcut: StandardKey.Cut
+ enabled: editor.text.selectedText.length > 0
+ onTriggered: editor.text.cut()
+ }
+ Action {
+ text: qsTr("Copy")
+ shortcut: StandardKey.Copy
+ enabled: editor.text.selectedText.length > 0
+ onTriggered: editor.text.copy()
+ }
+ Action {
+ text: qsTr("Paste")
+ shortcut: StandardKey.Paste
+ enabled: editor.text.canPaste
+ onTriggered: editor.text.paste()
+ }
+ Action {
+ text: qsTr("Select All")
+ shortcut: StandardKey.SelectAll
+ enabled: editor.text.length > 0
+ onTriggered: editor.text.selectAll()
+ }
+ Action {
+ text: qsTr("Undo")
+ shortcut: StandardKey.Undo
+ enabled: editor.text.canUndo
+ onTriggered: editor.text.undo()
+ }
+ }
+ }
+ // Set up the layout of the main components in a row:
+ // [ Sidebar, Navigation, Editor ]
+ RowLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ // Stores the buttons that navigate the application.
+ Sidebar {
+ id: sidebar
+ dragWindow: root
+ Layout.preferredWidth: 50
+ Layout.fillHeight: true
+ }
+
+ // Allows resizing parts of the UI.
+ SplitView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ // Customized handle to drag between the Navigation and the Editor.
+ handle: Rectangle {
+ implicitWidth: 10
+ color: SplitHandle.pressed ? Colors.color2 : Colors.background
+ border.color: SplitHandle.hovered ? Colors.color2 : Colors.background
+ opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 1400
+ }
+ }
+ }
+
+ Rectangle {
+ id: navigationView
+ color: Colors.surface1
+ SplitView.preferredWidth: 250
+ SplitView.fillHeight: true
+ // The stack-layout provides different views, based on the
+ // selected buttons inside the sidebar.
+ StackLayout {
+ anchors.fill: parent
+ currentIndex: sidebar.currentTabIndex > 1 ? 1 : sidebar.currentTabIndex
+
+ // Shows the help text.
+ Text {
+ text: qsTr("This example shows how to use and visualize the file system.\n\n"
+ + "Customized Qt Quick Components have been used to achieve this look.\n\n"
+ + "You can edit the files but they won't be changed on the file system.\n\n"
+ + "Click on the folder icon to the left to get started.")
+ wrapMode: TextArea.Wrap
+ color: Colors.text
+ }
+
+ // Shows the files on the file system.
+ FileSystemView {
+ id: fileSystemView
+ color: Colors.surface1
+ onFileClicked: path => root.currentFilePath = path
+ }
+ }
+ }
+
+ // The main view that contains the editor or the scheme-manager.
+ StackLayout {
+ currentIndex: sidebar.currentTabIndex > 1 ? 1 : 0
+
+ SplitView.fillWidth: true
+ SplitView.fillHeight: true
+
+ Editor {
+ id: editor
+ showLineNumbers: root.showLineNumbers
+ currentFilePath: root.currentFilePath
+ }
+
+ ColorScheme {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+
+ }
+ }
+ }
+
+ ResizeButton {
+ resizeWindow: root
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc
new file mode 100644
index 000000000..ccd9eefec
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/app.qrc
@@ -0,0 +1,16 @@
+<RCC>
+ <qresource prefix="/qt/qml/FileSystemModule">
+ <file>qmldir</file>
+ <file>Main.qml</file>
+ <file>qml/About.qml</file>
+ <file>qml/Editor.qml</file>
+ <file>qml/Colors.qml</file>
+ <file>qml/FileSystemView.qml</file>
+ <file>qml/Icon.qml</file>
+ <file>qml/MyMenu.qml</file>
+ <file>qml/MyMenuBar.qml</file>
+ <file>qml/ResizeButton.qml</file>
+ <file>qml/Sidebar.qml</file>
+ <file>qml/WindowDragHandler.qml</file>
+ </qresource>
+</RCC>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc
new file mode 100644
index 000000000..69bddc018
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons.qrc
@@ -0,0 +1,15 @@
+<RCC>
+ <qresource>
+ <file>icons/app_icon.svg</file>
+ <file>icons/folder_closed.svg</file>
+ <file>icons/folder_open.svg</file>
+ <file>icons/generic_file.svg</file>
+ <file>icons/globe.svg</file>
+ <file>icons/info_sign.svg</file>
+ <file>icons/leaf.svg</file>
+ <file>icons/light_bulb.svg</file>
+ <file>icons/qt_logo.svg</file>
+ <file>icons/read.svg</file>
+ <file>icons/resize.svg</file>
+ </qresource>
+</RCC>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg
new file mode 100644
index 000000000..5aae4221f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/app_icon.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#EBDBB2" d="M13.25 8.5a.75.75 0 1 1-.75-.75.75.75 0 0 1 .75.75zM9.911 21.35l.816.578C10.819 21.798 13 18.666 13 13h-1a15.503 15.503 0 0 1-2.089 8.35zM4 6.703V10a2.002 2.002 0 0 1-2 2v1a2.002 2.002 0 0 1 2 2v3.297A3.707 3.707 0 0 0 7.703 22H9v-1H7.703A2.706 2.706 0 0 1 5 18.297V15a2.999 2.999 0 0 0-1.344-2.5A2.999 2.999 0 0 0 5 10V6.703A2.706 2.706 0 0 1 7.703 4H9V3H7.703A3.707 3.707 0 0 0 4 6.703zM20 10V6.703A3.707 3.707 0 0 0 16.297 3H15v1h1.297A2.706 2.706 0 0 1 19 6.703V10a2.999 2.999 0 0 0 1.344 2.5A2.999 2.999 0 0 0 19 15v3.297A2.706 2.706 0 0 1 16.297 21H15v1h1.297A3.707 3.707 0 0 0 20 18.297V15a2.002 2.002 0 0 1 2-2v-1a2.002 2.002 0 0 1-2-2z"/><path fill="none" d="M0 0h24v24H0z"/></svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_closed.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_closed.svg
new file mode 100644
index 000000000..36f119c96
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_closed.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-10 0 1792 1792"
+ id="svg51"
+ sodipodi:docname="folder_closed.svg"
+ width="1792"
+ height="1792"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs55" />
+ <sodipodi:namedview
+ id="namedview53"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.45033482"
+ inkscape:cx="842.70632"
+ inkscape:cy="896"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg51" />
+ <path
+ fill="currentColor"
+ d="m 1718,672 v 704 q 0,92 -66,158 -66,66 -158,66 H 278 q -92,0 -158,-66 -66,-66 -66,-158 V 416 q 0,-92 66,-158 66,-66 158,-66 h 320 q 92,0 158,66 66,66 66,158 v 32 h 672 q 92,0 158,66 66,66 66,158 z"
+ id="path49" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_open.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_open.svg
new file mode 100644
index 000000000..daa55a7a1
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/folder_open.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-10 0 1792 1792"
+ id="svg139"
+ sodipodi:docname="folder_open.svg"
+ width="1792"
+ height="1792"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs143" />
+ <sodipodi:namedview
+ id="namedview141"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.24358259"
+ inkscape:cx="149.84651"
+ inkscape:cy="1098.1901"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg139" />
+ <path
+ fill="currentColor"
+ d="M 1590,1376 V 672 q 0,-40 -28,-68 -28,-28 -68,-28 H 790 q -40,0 -68,-28 -28,-28 -28,-68 v -64 q 0,-40 -28,-68 -28,-28 -68,-28 H 278 q -40,0 -68,28 -28,28 -28,68 v 960 q 0,40 28,68 28,28 68,28 h 1216 q 40,0 68,-28 28,-28 28,-68 z m 128,-704 v 704 q 0,92 -66,158 -66,66 -158,66 H 278 q -92,0 -158,-66 -66,-66 -66,-158 V 416 q 0,-92 66,-158 66,-66 158,-66 h 320 q 92,0 158,66 66,66 66,158 v 32 h 672 q 92,0 158,66 66,66 66,158 z"
+ id="path137" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/generic_file.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/generic_file.svg
new file mode 100644
index 000000000..9c855676e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/generic_file.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-10 0 1792 1792"
+ id="svg147"
+ sodipodi:docname="generic_file.svg"
+ width="1792"
+ height="1792"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs151" />
+ <sodipodi:namedview
+ id="namedview149"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.12179129"
+ inkscape:cx="-578.85911"
+ inkscape:cy="1687.3127"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg147" />
+ <path
+ fill="currentColor"
+ d="m 1586,476 q 14,14 28,36 H 1142 V 40 q 22,14 36,28 z m -476,164 h 544 v 1056 q 0,40 -28,68 -28,28 -68,28 H 214 q -40,0 -68,-28 -28,-28 -28,-68 V 96 Q 118,56 146,28 174,0 214,0 h 800 v 544 q 0,40 28,68 28,28 68,28 z m 160,736 v -64 q 0,-14 -9,-23 -9,-9 -23,-9 H 534 q -14,0 -23,9 -9,9 -9,23 v 64 q 0,14 9,23 9,9 23,9 h 704 q 14,0 23,-9 9,-9 9,-23 z m 0,-256 v -64 q 0,-14 -9,-23 -9,-9 -23,-9 H 534 q -14,0 -23,9 -9,9 -9,23 v 64 q 0,14 9,23 9,9 23,9 h 704 q 14,0 23,-9 9,-9 9,-23 z m 0,-256 v -64 q 0,-14 -9,-23 -9,-9 -23,-9 H 534 q -14,0 -23,9 -9,9 -9,23 v 64 q 0,14 9,23 9,9 23,9 h 704 q 14,0 23,-9 9,-9 9,-23 z"
+ id="path145" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/globe.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/globe.svg
new file mode 100644
index 000000000..081433813
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/globe.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-10 0 1792 1792"
+ id="svg155"
+ sodipodi:docname="globe.svg"
+ width="1792"
+ height="1792"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs159" />
+ <sodipodi:namedview
+ id="namedview157"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.12179129"
+ inkscape:cx="504.9622"
+ inkscape:cy="1720.1558"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg155" />
+ <path
+ fill="currentColor"
+ d="m 886,128 q 209,0 385.5,103 176.5,103 279.5,279.5 103,176.5 103,385.5 0,209 -103,385.5 Q 1448,1458 1271.5,1561 1095,1664 886,1664 677,1664 500.5,1561 324,1458 221,1281.5 118,1105 118,896 118,687 221,510.5 324,334 500.5,231 677,128 886,128 Z m 274,521 q -2,1 -9.5,9.5 -7.5,8.5 -13.5,9.5 2,0 4.5,-5 2.5,-5 5,-11 2.5,-6 3.5,-7 6,-7 22,-15 14,-6 52,-12 34,-8 51,11 -2,-2 9.5,-13 11.5,-11 14.5,-12 3,-2 15,-4.5 12,-2.5 15,-7.5 l 2,-22 q -12,1 -17.5,-7 -5.5,-8 -6.5,-21 0,2 -6,8 0,-7 -4.5,-8 -4.5,-1 -11.5,1 -7,2 -9,1 -10,-3 -15,-7.5 -5,-4.5 -8,-16.5 -3,-12 -4,-15 -2,-5 -9.5,-10.5 -7.5,-5.5 -9.5,-10.5 -1,-2 -2.5,-5.5 -1.5,-3.5 -3,-6.5 -1.5,-3 -4,-5.5 -2.5,-2.5 -5.5,-2.5 -3,0 -7,5 -4,5 -7.5,10 -3.5,5 -4.5,5 -3,-2 -6,-1.5 -3,0.5 -4.5,1 -1.5,0.5 -4.5,3 -3,2.5 -5,3.5 -3,2 -8.5,3 -5.5,1 -8.5,2 15,-5 -1,-11 -10,-4 -16,-3 9,-4 7.5,-12 -1.5,-8 -8.5,-14 h 5 q -1,-4 -8.5,-8.5 -7.5,-4.5 -17.5,-8.5 -10,-4 -13,-6 -8,-5 -34,-9.5 -26,-4.5 -33,-0.5 -5,6 -4.5,10.5 0.5,4.5 4,14 3.5,9.5 3.5,12.5 1,6 -5.5,13 -6.5,7 -6.5,12 0,7 14,15.5 14,8.5 10,21.5 -3,8 -16,16 -13,8 -16,12 -5,8 -1.5,18.5 3.5,10.5 10.5,16.5 2,2 1.5,4 -0.5,2 -3.5,4.5 -3,2.5 -5.5,4 -2.5,1.5 -6.5,3.5 l -3,2 q -11,5 -20.5,-6 -9.5,-11 -13.5,-26 -7,-25 -16,-30 -23,-8 -29,1 -5,-13 -41,-26 -25,-9 -58,-4 6,-1 0,-15 -7,-15 -19,-12 3,-6 4,-17.5 1,-11.5 1,-13.5 3,-13 12,-23 1,-1 7,-8.5 6,-7.5 9.5,-13.5 3.5,-6 0.5,-6 35,4 50,-11 5,-5 11.5,-17 6.5,-12 10.5,-17 9,-6 14,-5.5 5,0.5 14.5,5.5 9.5,5 14.5,5 14,1 15.5,-11 1.5,-12 -7.5,-20 12,1 3,-17 -5,-7 -8,-9 -12,-4 -27,5 -8,4 2,8 -1,-1 -9.5,10.5 Q 927,340 919,346 q -8,6 -16,-5 -1,-1 -5.5,-13.5 Q 893,315 888,314 q -8,0 -16,15 3,-8 -11,-15 -14,-7 -24,-8 19,-12 -8,-27 -7,-4 -20.5,-5 -13.5,-1 -19.5,4 -5,7 -5.5,11.5 -0.5,4.5 5,8 5.5,3.5 10.5,5.5 5,2 11.5,4 6.5,2 8.5,3 14,10 8,14 -2,1 -8.5,3.5 -6.5,2.5 -11.5,4.5 -5,2 -6,4 -3,4 0,14 3,10 -2,14 -5,-5 -9,-17.5 -4,-12.5 -7,-16.5 7,9 -25,6 l -10,-1 q -4,0 -16,2 -12,2 -20.5,1 -8.5,-1 -13.5,-8 -4,-8 0,-20 1,-4 4,-2 -4,-3 -11,-9.5 -7,-6.5 -10,-8.5 -46,15 -94,41 6,1 12,-1 5,-2 13,-6.5 8,-4.5 10,-5.5 34,-14 42,-7 l 5,-5 q 14,16 20,25 -7,-4 -30,-1 -20,6 -22,12 7,12 5,18 -4,-3 -11.5,-10 -7.5,-7 -14.5,-11 -7,-4 -15,-5 -16,0 -22,1 -146,80 -235,222 7,7 12,8 4,1 5,9 1,8 2.5,11 1.5,3 11.5,-3 9,8 3,19 1,-1 44,27 19,17 21,21 3,11 -10,18 -1,-2 -9,-9 -8,-7 -9,-4 -3,5 0.5,18.5 3.5,13.5 10.5,12.5 -7,0 -9.5,16 -2.5,16 -2.5,35.5 0,19.5 -1,23.5 l 2,1 q -3,12 5.5,34.5 8.5,22.5 21.5,19.5 -13,3 20,43 6,8 8,9 3,2 12,7.5 9,5.5 15,10 6,4.5 10,10.5 4,5 10,22.5 6,17.5 14,23.5 -2,6 9.5,20 11.5,14 10.5,23 -1,0 -2.5,1 -1.5,1 -2.5,1 3,7 15.5,14 12.5,7 15.5,13 1,3 2,10 1,7 3,11 2,4 8,2 2,-20 -24,-62 -15,-25 -17,-29 -3,-5 -5.5,-15.5 Q 541,919 539,915 q 2,0 6,1.5 4,1.5 8.5,3.5 4.5,2 7.5,4 3,2 2,3 -3,7 2,17.5 5,10.5 12,18.5 7,8 17,19 10,11 12,13 6,6 14,19.5 8,13.5 0,13.5 9,0 20,10 11,10 17,20 5,8 8,26 3,18 5,24 2,7 8.5,13.5 6.5,6.5 12.5,9.5 l 16,8 q 0,0 13,7 5,2 18.5,10.5 13.5,8.5 21.5,11.5 10,4 16,4 6,0 14.5,-2.5 8.5,-2.5 13.5,-3.5 15,-2 29,15 14,17 21,21 36,19 55,11 -2,1 0.5,7.5 2.5,6.5 8,15.5 5.5,9 9,14.5 3.5,5.5 5.5,8.5 5,6 18,15 13,9 18,15 6,-4 7,-9 -3,8 7,20 10,12 18,10 14,-3 14,-32 -31,15 -49,-18 0,-1 -2.5,-5.5 -2.5,-4.5 -4,-8.5 -1.5,-4 -2.5,-8.5 -1,-4.5 0,-7.5 1,-3 5,-3 9,0 10,-3.5 1,-3.5 -2,-12.5 -3,-9 -4,-13 -1,-8 -11,-20 -10,-12 -12,-15 -5,9 -16,8 -11,-1 -16,-9 0,1 -1.5,5.5 -1.5,4.5 -1.5,6.5 -13,0 -15,-1 1,-3 2.5,-17.5 1.5,-14.5 3.5,-22.5 1,-4 5.5,-12 4.5,-8 7.5,-14.5 3,-6.5 4,-12.5 1,-6 -4.5,-9.5 -5.5,-3.5 -17.5,-2.5 -19,1 -26,20 -1,3 -3,10.5 -2,7.5 -5,11.5 -3,4 -9,7 -7,3 -24,2 -17,-1 -24,-5 -13,-8 -22.5,-29 -9.5,-21 -9.5,-37 0,-10 2.5,-26.5 2.5,-16.5 3,-25 0.5,-8.5 -5.5,-24.5 3,-2 9,-9.5 6,-7.5 10,-10.5 2,-1 4.5,-1.5 2.5,-0.5 4.5,0 2,0.5 4,-1.5 2,-2 3,-6 -1,-1 -4,-3 -3,-3 -4,-3 7,3 28.5,-1.5 21.5,-4.5 27.5,1.5 15,11 22,-2 0,-1 -2.5,-9.5 Q 870,931 872,926 q 5,27 29,9 3,3 15.5,5 12.5,2 17.5,5 3,2 7,5.5 4,3.5 5.5,4.5 1.5,1 5,-0.5 3.5,-1.5 8.5,-6.5 10,14 12,24 11,40 19,44 7,3 11,2 4,-1 4.5,-9.5 0.5,-8.5 0,-14 Q 1006,989 1005,982 l -1,-8 v -18 l -1,-8 q -15,-3 -18.5,-12 -3.5,-9 1.5,-18.5 5,-9.5 15,-18.5 1,-1 8,-3.5 7,-2.5 15.5,-6.5 8.5,-4 12.5,-8 21,-19 15,-35 7,0 11,-9 -1,0 -5,-3 -4,-3 -7.5,-5 -3.5,-2 -4.5,-2 9,-5 2,-16 5,-3 7.5,-11 2.5,-8 7.5,-10 9,12 21,2 7,-8 1,-16 5,-7 20.5,-10.5 15.5,-3.5 18.5,-9.5 7,2 8,-2 1,-4 1,-12 0,-8 3,-12 4,-5 15,-9 11,-4 13,-5 l 17,-11 q 3,-4 0,-4 18,2 31,-11 10,-11 -6,-20 3,-6 -3,-9.5 -6,-3.5 -15,-5.5 3,-1 11.5,-0.5 8.5,0.5 10.5,-1.5 15,-10 -7,-16 -17,-5 -43,12 z m -163,877 q 206,-36 351,-189 -3,-3 -12.5,-4.5 -9.5,-1.5 -12.5,-3.5 -18,-7 -24,-8 1,-7 -2.5,-13 -3.5,-6 -8,-9 -4.5,-3 -12.5,-8 -8,-5 -11,-7 -2,-2 -7,-6 -5,-4 -7,-5.5 -2,-1.5 -7.5,-4.5 -5.5,-3 -8.5,-2 -3,1 -10,1 l -3,1 q -3,1 -5.5,2.5 -2.5,1.5 -5.5,3 -3,1.5 -4,3 -1,1.5 0,2.5 -21,-17 -36,-22 -5,-1 -11,-5.5 -6,-4.5 -10.5,-7 -4.5,-2.5 -10,-1.5 -5.5,1 -11.5,7 -5,5 -6,15 -1,10 -2,13 -7,-5 0,-17.5 7,-12.5 2,-18.5 -3,-6 -10.5,-4.5 -7.5,1.5 -12,4.5 -4.5,3 -11.5,8.5 -7,5.5 -9,6.5 -2,1 -8.5,5.5 -6.5,4.5 -8.5,7.5 -3,4 -6,12 -3,8 -5,11 -2,-4 -11.5,-6.5 -9.5,-2.5 -9.5,-5.5 2,10 4,35 2,25 5,38 7,31 -12,48 -27,25 -29,40 -4,22 12,26 0,7 -8,20.5 -8,13.5 -7,21.5 0,6 2,16 z"
+ id="path153" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/info_sign.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/info_sign.svg
new file mode 100644
index 000000000..517f76360
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/info_sign.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-10 0 1792 1792"
+ id="svg163"
+ sodipodi:docname="info_sign.svg"
+ width="1792"
+ height="1792"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs167" />
+ <sodipodi:namedview
+ id="namedview165"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.48716518"
+ inkscape:cx="72.870561"
+ inkscape:cy="896"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg163" />
+ <path
+ fill="currentColor"
+ d="m 1142,1376 v -160 q 0,-14 -9,-23 -9,-9 -23,-9 h -96 V 672 q 0,-14 -9,-23 -9,-9 -23,-9 H 662 q -14,0 -23,9 -9,9 -9,23 v 160 q 0,14 9,23 9,9 23,9 h 96 v 320 h -96 q -14,0 -23,9 -9,9 -9,23 v 160 q 0,14 9,23 9,9 23,9 h 448 q 14,0 23,-9 9,-9 9,-23 z M 1014,480 V 320 q 0,-14 -9,-23 -9,-9 -23,-9 H 790 q -14,0 -23,9 -9,9 -9,23 v 160 q 0,14 9,23 9,9 23,9 h 192 q 14,0 23,-9 9,-9 9,-23 z m 640,416 q 0,209 -103,385.5 Q 1448,1458 1271.5,1561 1095,1664 886,1664 677,1664 500.5,1561 324,1458 221,1281.5 118,1105 118,896 118,687 221,510.5 324,334 500.5,231 677,128 886,128 1095,128 1271.5,231 1448,334 1551,510.5 1654,687 1654,896 Z"
+ id="path161" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/leaf.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/leaf.svg
new file mode 100644
index 000000000..c1cabb5cf
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/leaf.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 0 1034 1024">
+ <path fill="currentColor"
+d="M765 327l-14 -11l-538 502l503 -533l-14 -12q-48 -26 -106 -23.5t-109 19.5q-14 4 -28.5 10t-28.5 13q-30 14 -57.5 32.5t-52.5 41.5q-55 52 -90.5 119t-52.5 140q-5 21 -9 42t-7 42q-3 26 -5.5 52.5t-4.5 53.5q-1 22 1.5 45.5t33.5 21.5h11.5h10.5q78 -2 156 -6t153 -29
+q49 -16 94 -42.5t82 -62.5q27 -27 50 -60t39 -69.5t24 -75.5t5 -78q-3 -35 -12.5 -70t-33.5 -62v0zM844 160l30 30l-563 531z" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/light_bulb.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/light_bulb.svg
new file mode 100644
index 000000000..ed2ed55fb
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/light_bulb.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-10 0 1538 1538"
+ id="svg4"
+ sodipodi:docname="light_bulb.svg"
+ width="1538"
+ height="1538"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ id="namedview6"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ fit-margin-top="1"
+ fit-margin-left="1"
+ fit-margin-right="1"
+ fit-margin-bottom="1"
+ lock-margins="true"
+ inkscape:zoom="0.16"
+ inkscape:cx="1234.375"
+ inkscape:cy="409.375"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4" />
+ <path
+ fill="currentColor"
+ d="m 983,449 q 0,13 -9.5,22.5 Q 964,481 951,481 938,481 928.5,471.5 919,462 919,449 919,403 865,378 811,353 759,353 746,353 736.5,343.5 727,334 727,321 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 50,0 99.5,16 49.5,16 87,54 37.5,38 37.5,90 z m 160,0 q 0,-72 -34.5,-134 -34.5,-62 -90,-101.5 Q 963,174 895.5,151.5 828,129 759,129 690,129 622.5,151.5 555,174 499.5,213.5 444,253 409.5,315 375,377 375,449 q 0,101 68,180 10,11 30.5,33 20.5,22 30.5,33 128,153 141,298 h 228 q 13,-145 141,-298 10,-11 30.5,-33 20.5,-22 30.5,-33 68,-79 68,-180 z m 128,0 q 0,155 -103,268 -45,49 -74.5,87 -29.5,38 -59.5,95.5 -30,57.5 -34,107.5 47,28 47,82 0,37 -25,64 25,27 25,64 0,52 -45,81 13,23 13,47 0,46 -31.5,71 -31.5,25 -77.5,25 -20,44 -60,70 -40,26 -87,26 -47,0 -87,-26 -40,-26 -60,-70 -46,0 -77.5,-25 -31.5,-25 -31.5,-71 0,-24 13,-47 -45,-29 -45,-81 0,-37 25,-64 -25,-27 -25,-64 0,-54 47,-82 Q 514,957 484,899.5 454,842 424.5,804 395,766 350,717 247,604 247,449 247,350 291.5,264.5 336,179 408.5,122.5 481,66 572.5,33.5 664,1 759,1 q 95,0 186.5,32.5 91.5,32.5 164,89 72.5,56.5 117,142 Q 1271,350 1271,449 Z"
+ id="path2" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/qt_logo.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/qt_logo.svg
new file mode 100644
index 000000000..062daff3e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/qt_logo.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="462pt"
+ height="339pt"
+ viewBox="0 0 462 339"
+ version="1.1"
+ id="svg2"
+>
+ <path
+ fill="#41cd52"
+ d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z"
+ id="path6"/>
+ <path
+ d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z"
+ id="path8"
+ fill="#ffffff"/>
+ <path
+ d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z"
+ id="path10"
+ fill="#ffffff"/>
+ <path
+ fill="#41cd52"
+ d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z"
+ id="path12"/>
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/read.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/read.svg
new file mode 100644
index 000000000..c3af473d2
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/read.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ viewBox="-11 0 1792 1792"
+ id="svg184"
+ sodipodi:docname="read.svg"
+ width="1792"
+ height="1792"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs188" />
+ <sodipodi:namedview
+ id="namedview186"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ showgrid="false"
+ inkscape:zoom="0.24358259"
+ inkscape:cx="519.33104"
+ inkscape:cy="1089.9794"
+ inkscape:window-width="1846"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg184" />
+ <path
+ fill="currentColor"
+ d="m 1691.8576,478 q 40,57 18,129 l -275,906 q -19,64 -76.5,107.5 -57.5,43.5 -122.5,43.5 H 312.85764 q -77,0 -148.5,-53.5 Q 92.857644,1557 64.857644,1479 q -24,-67 -2,-127 0,-4 3,-27 3,-23 4,-37 1,-8 -3,-21.5 -4,-13.5 -3,-19.5 2,-11 8,-21 6,-10 16.5,-23.5 10.5,-13.5 16.499996,-23.5 23,-38 45,-91.5 22,-53.5 30,-91.5 3,-10 0.5,-30 -2.5,-20 -0.5,-28 3,-11 17,-28 14,-17 17,-23 21,-36 42,-92 21,-56 25,-90 1,-9 -2.5,-32 -3.5,-23 0.5,-28 4,-13 22,-30.5 18,-17.5 22,-22.5 19,-26 42.5,-84.5 23.5,-58.5 27.5,-96.5 1,-8 -3,-25.5 -4,-17.5 -2,-26.5 2,-8 9,-18 7,-10 18,-23 11,-13 17,-21 8,-12 16.5,-30.5 8.5,-18.5 15,-35 6.5,-16.5 16,-36 9.5,-19.5 19.5,-32 10,-12.5 26.5,-23.5 16.5,-11 36,-11.5 19.5,-0.5 47.5,5.5 l -1,3 q 38,-9 51,-9 h 760.99996 q 74,0 114,56 40,56 18,130 l -274,906 q -36,119 -71.5,153.5 -35.5,34.5 -128.5,34.5 H 208.85764 q -27,0 -38,15 -11,16 -1,43 24,70 144,70 h 922.99996 q 29,0 56,-15.5 27,-15.5 35,-41.5 l 300,-987 q 7,-22 5,-57 38,15 59,43 z m -1063.99996,2 q -4,13 2,22.5 6,9.5 20,9.5 h 607.99996 q 13,0 25.5,-9.5 12.5,-9.5 16.5,-22.5 l 21,-64 q 4,-13 -2,-22.5 -6,-9.5 -20,-9.5 H 690.85764 q -13,0 -25.5,9.5 -12.5,9.5 -16.5,22.5 z m -83,256 q -4,13 2,22.5 6,9.5 20,9.5 h 607.99996 q 13,0 25.5,-9.5 12.5,-9.5 16.5,-22.5 l 21,-64 q 4,-13 -2,-22.5 -6,-9.5 -20,-9.5 H 607.85764 q -13,0 -25.5,9.5 -12.5,9.5 -16.5,22.5 z"
+ id="path182" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/resize.svg b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/resize.svg
new file mode 100644
index 000000000..e86d612f3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/icons/resize.svg
@@ -0,0 +1,6 @@
+<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100'>
+ <line x1="00" y1="100" x2="100" y2="00" stroke="black" stroke-width="3" />
+ <line x1="20" y1="100" x2="100" y2="20" stroke="black" stroke-width="3" />
+ <line x1="40" y1="100" x2="100" y2="40" stroke="black" stroke-width="3" />
+ <line x1="60" y1="100" x2="100" y2="60" stroke="black" stroke-width="3" />
+</svg>
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml
new file mode 100644
index 000000000..178bf03e4
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/About.qml
@@ -0,0 +1,93 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls.Basic
+import FileSystemModule
+
+ApplicationWindow {
+ id: root
+ width: 650
+ height: 550
+ flags: Qt.Window | Qt.FramelessWindowHint
+ color: Colors.surface1
+
+ menuBar: MyMenuBar {
+ id: menuBar
+
+ dragWindow: root
+ implicitHeight: 27
+ infoText: "About Qt"
+ }
+
+ Image {
+ id: logo
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: 20
+
+ source: "../icons/qt_logo.svg"
+ sourceSize.width: 80
+ sourceSize.height: 80
+ fillMode: Image.PreserveAspectFit
+
+ smooth: true
+ antialiasing: true
+ asynchronous: true
+ }
+
+ ScrollView {
+ anchors.top: logo.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 20
+
+ TextArea {
+ selectedTextColor: Colors.textFile
+ selectionColor: Colors.selection
+ horizontalAlignment: Text.AlignHCenter
+ textFormat: Text.RichText
+
+ text: qsTr("<h3>About Qt</h3>"
+ + "<p>This program uses Qt version %1.</p>"
+ + "<p>Qt is a C++ toolkit for cross-platform application "
+ + "development.</p>"
+ + "<p>Qt provides single-source portability across all major desktop "
+ + "operating systems. It is also available for embedded Linux and other "
+ + "embedded and mobile operating systems.</p>"
+ + "<p>Qt is available under multiple licensing options designed "
+ + "to accommodate the needs of our various users.</p>"
+ + "<p>Qt licensed under our commercial license agreement is appropriate "
+ + "for development of proprietary/commercial software where you do not "
+ + "want to share any source code with third parties or otherwise cannot "
+ + "comply with the terms of GNU (L)GPL.</p>"
+ + "<p>Qt licensed under GNU (L)GPL is appropriate for the "
+ + "development of Qt&nbsp;applications provided you can comply with the terms "
+ + "and conditions of the respective licenses.</p>"
+ + "<p>Please see <a href=\"http://%2/\">%2</a> "
+ + "for an overview of Qt licensing.</p>"
+ + "<p>Copyright (C) %3 The Qt Company Ltd and other "
+ + "contributors.</p>"
+ + "<p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p>"
+ + "<p>Qt is The Qt Company Ltd product developed as an open source "
+ + "project. See <a href=\"http://%4/\">%4</a> for more information.</p>")
+ .arg(Application.version).arg("qt.io/licensing").arg("2023").arg("qt.io")
+ color: Colors.textFile
+ wrapMode: Text.WordWrap
+ readOnly: true
+ antialiasing: true
+ background: null
+
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link)
+ }
+ }
+ }
+
+ ResizeButton {
+ resizeWindow: root
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ColorScheme.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ColorScheme.qml
new file mode 100644
index 000000000..19c8cd905
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ColorScheme.qml
@@ -0,0 +1,118 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import FileSystemModule
+
+ColumnLayout {
+ id: colorScheme
+
+ spacing: 20
+
+ // Inline component that customizes TabButton
+ component MyTabButton: TabButton {
+ id: root
+
+ implicitWidth: 150
+ implicitHeight: 30
+ padding: 6
+ spacing: 6
+
+ contentItem: Text {
+ anchors.centerIn: parent
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ text: root.text
+ font.bold: true
+ color: Colors.text
+ }
+
+ background: Rectangle {
+ anchors.fill: parent
+ implicitHeight: 40
+
+ color: root.checked ? Colors.active : Colors.selection
+ Rectangle {
+ height: 4
+ width: parent.width
+ color: root.checked ? Colors.color1 : Colors.selection
+ }
+ }
+ }
+
+ Item {
+ // Spacer item
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+
+ Text {
+ Layout.alignment: Qt.AlignHCenter
+
+ text: "Select a Scheme!"
+ font.pointSize: 30
+ font.bold: true
+ color: Colors.text
+ }
+
+ // Display all the color-scheme names. The model is a string-list provided
+ // by our python class.
+ TabBar {
+ id: schemeSelector
+
+ Layout.alignment: Qt.AlignHCenter
+
+ background: Rectangle {
+ color: Colors.surface1
+ }
+
+ Repeater {
+ model: Colors.getKeys()
+ MyTabButton {
+ text: modelData
+ onClicked: {
+ Colors.setScheme(modelData)
+ themePreviewContainer.background
+ = (modelData === "Solarized") ? "#777777" : "#FEFAEC"
+ }
+ }
+ }
+ }
+
+ // The current colors can be visualized using the same method as above.
+ Rectangle {
+ id: themePreviewContainer
+
+ property color background: "#FEFAEC"
+
+ Layout.alignment: Qt.AlignHCenter
+
+ width: 700
+ height: 50
+ radius: 10
+ color: background
+
+ // Display all used colors inside a row
+ Row {
+ anchors.centerIn: parent
+ spacing: 10
+
+ Repeater {
+ model: Colors.currentColors
+ Rectangle {
+ width: 35
+ height: width
+ radius: width / 2
+ color: modelData
+ }
+ }
+ }
+ }
+ Item {
+ // Spacer item
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml
new file mode 100644
index 000000000..80f7c04c5
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Editor.qml
@@ -0,0 +1,160 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import FileSystemModule
+
+pragma ComponentBehavior: Bound
+
+// This is the text editor that displays the currently open file, including
+// their corresponding line numbers.
+Rectangle {
+ id: root
+
+ required property string currentFilePath
+ required property bool showLineNumbers
+ property alias text: textArea
+ property int currentLineNumber: -1
+ property int rowHeight: Math.ceil(fontMetrics.lineSpacing)
+
+ color: Colors.background
+
+ onWidthChanged: textArea.update()
+ onHeightChanged: textArea.update()
+
+ RowLayout {
+ anchors.fill: parent
+ // We use a flickable to synchronize the position of the editor and
+ // the line numbers. This is necessary because the line numbers can
+ // extend the available height.
+ Flickable {
+ id: lineNumbers
+
+ // Calculate the width based on the logarithmic scale.
+ Layout.preferredWidth: fontMetrics.averageCharacterWidth
+ * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
+ Layout.fillHeight: true
+
+ interactive: false
+ contentY: editorFlickable.contentY
+ visible: textArea.text !== "" && root.showLineNumbers
+
+ Column {
+ anchors.fill: parent
+ Repeater {
+ id: repeatedLineNumbers
+
+ model: LineNumberModel {
+ lineCount: textArea.text !== "" ? textArea.lineCount : 0
+ }
+
+ delegate: Item {
+ required property int index
+
+ width: parent.width
+ height: root.rowHeight
+ Label {
+ id: numbers
+
+ text: parent.index + 1
+
+ width: parent.width
+ height: parent.height
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ color: (root.currentLineNumber === parent.index)
+ ? Colors.iconIndicator : Qt.darker(Colors.text, 2)
+ font: textArea.font
+ }
+ Rectangle {
+ id: indicator
+
+ anchors.left: numbers.right
+ width: 1
+ height: parent.height
+ color: Qt.darker(Colors.text, 3)
+ }
+ }
+ }
+ }
+ }
+
+ Flickable {
+ id: editorFlickable
+
+ property alias textArea: textArea
+
+ // We use an inline component to customize the horizontal and vertical
+ // scroll-bars. This is convenient when the component is only used in one file.
+ component MyScrollBar: ScrollBar {
+ id: scrollBar
+ background: Rectangle {
+ implicitWidth: scrollBar.interactive ? 8 : 4
+ implicitHeight: scrollBar.interactive ? 8 : 4
+
+ opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
+ color: Colors.background
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 500
+ }
+ }
+ }
+ contentItem: Rectangle {
+ implicitWidth: scrollBar.interactive ? 8 : 4
+ implicitHeight: scrollBar.interactive ? 8 : 4
+ opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
+ color: Colors.color1
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 1000
+ }
+ }
+ }
+ }
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ ScrollBar.horizontal: MyScrollBar {}
+ ScrollBar.vertical: MyScrollBar {}
+
+ boundsBehavior: Flickable.StopAtBounds
+
+ TextArea.flickable: TextArea {
+ id: textArea
+ anchors.fill: parent
+
+ focus: false
+ topPadding: 0
+ leftPadding: 10
+
+ text: FileSystemModel.readFile(root.currentFilePath)
+ tabStopDistance: fontMetrics.averageCharacterWidth * 4
+
+ // Grab the current line number from the C++ interface.
+ onCursorPositionChanged: {
+ root.currentLineNumber = FileSystemModel.currentLineNumber(
+ textArea.textDocument, textArea.cursorPosition)
+ }
+
+ color: Colors.textFile
+ selectedTextColor: Colors.textFile
+ selectionColor: Colors.selection
+
+ textFormat: TextEdit.PlainText
+ renderType: Text.QtRendering
+ selectByMouse: true
+ antialiasing: true
+ background: null
+ }
+
+ FontMetrics {
+ id: fontMetrics
+ font: textArea.font
+ }
+ }
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml
new file mode 100644
index 000000000..db955168c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/FileSystemView.qml
@@ -0,0 +1,156 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Effects
+import QtQuick.Controls.Basic
+import FileSystemModule
+
+pragma ComponentBehavior: Bound
+
+// This is the file system view which gets populated by the C++ model.
+Rectangle {
+ id: root
+
+ signal fileClicked(string filePath)
+ property alias rootIndex: fileSystemTreeView.rootIndex
+
+ TreeView {
+ id: fileSystemTreeView
+
+ property int lastIndex: -1
+
+ anchors.fill: parent
+ model: FileSystemModel
+ rootIndex: FileSystemModel.rootIndex
+ boundsBehavior: Flickable.StopAtBounds
+ boundsMovement: Flickable.StopAtBounds
+ clip: true
+
+ Component.onCompleted: fileSystemTreeView.toggleExpanded(0)
+
+ // The delegate represents a single entry in the filesystem.
+ delegate: TreeViewDelegate {
+ id: treeDelegate
+ indentation: 8
+ implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
+ implicitHeight: 25
+
+ // Since we have the 'ComponentBehavior Bound' pragma, we need to
+ // require these properties from our model. This is a convenient way
+ // to bind the properties provided by the model's role names.
+ required property int index
+ required property url filePath
+ required property string fileName
+
+ indicator: Image {
+ id: directoryIcon
+
+ x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation)
+ anchors.verticalCenter: parent.verticalCenter
+ source: treeDelegate.hasChildren ? (treeDelegate.expanded
+ ? "../icons/folder_open.svg" : "../icons/folder_closed.svg")
+ : "../icons/generic_file.svg"
+ sourceSize.width: 20
+ sourceSize.height: 20
+ fillMode: Image.PreserveAspectFit
+
+ smooth: true
+ antialiasing: true
+ asynchronous: true
+ }
+
+ contentItem: Text {
+ text: treeDelegate.fileName
+ color: Colors.text
+ }
+
+ background: Rectangle {
+ color: (treeDelegate.index === fileSystemTreeView.lastIndex)
+ ? Colors.selection
+ : (hoverHandler.hovered ? Colors.active : "transparent")
+ }
+
+ // We color the directory icons with this MultiEffect, where we overlay
+ // the colorization color ontop of the SVG icons.
+ MultiEffect {
+ id: iconOverlay
+
+ anchors.fill: directoryIcon
+ source: directoryIcon
+ colorization: 1.0
+ brightness: 1.0
+ colorizationColor: {
+ const isFile = treeDelegate.index === fileSystemTreeView.lastIndex
+ && !treeDelegate.hasChildren;
+ if (isFile)
+ return Qt.lighter(Colors.folder, 3)
+
+ const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren;
+ if (isExpandedFolder)
+ return Colors.color2
+ else
+ return Colors.folder
+ }
+ }
+
+ HoverHandler {
+ id: hoverHandler
+ }
+
+ TapHandler {
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onSingleTapped: (eventPoint, button) => {
+ switch (button) {
+ case Qt.LeftButton:
+ fileSystemTreeView.toggleExpanded(treeDelegate.row)
+ fileSystemTreeView.lastIndex = treeDelegate.index
+ // If this model item doesn't have children, it means it's
+ // representing a file.
+ if (!treeDelegate.hasChildren)
+ root.fileClicked(treeDelegate.filePath)
+ break;
+ case Qt.RightButton:
+ if (treeDelegate.hasChildren)
+ contextMenu.popup();
+ break;
+ }
+ }
+ }
+
+ MyMenu {
+ id: contextMenu
+ Action {
+ text: qsTr("Set as root index")
+ onTriggered: {
+ fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0)
+ }
+ }
+ Action {
+ text: qsTr("Reset root index")
+ onTriggered: fileSystemTreeView.rootIndex = undefined
+ }
+ }
+ }
+
+ // Provide our own custom ScrollIndicator for the TreeView.
+ ScrollIndicator.vertical: ScrollIndicator {
+ active: true
+ implicitWidth: 15
+
+ contentItem: Rectangle {
+ implicitWidth: 6
+ implicitHeight: 6
+
+ color: Colors.color1
+ opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 500
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml
new file mode 100644
index 000000000..1f1d30c56
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenu.qml
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls.Basic
+import FileSystemModule
+
+Menu {
+ id: root
+
+ delegate: MenuItem {
+ id: menuItem
+ contentItem: Item {
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: 5
+
+ text: menuItem.text
+ color: enabled ? Colors.text : Colors.disabledText
+ }
+ Rectangle {
+ id: indicator
+
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ width: 6
+ height: parent.height
+
+ visible: menuItem.highlighted
+ color: Colors.color2
+ }
+ }
+ background: Rectangle {
+ implicitWidth: 210
+ implicitHeight: 35
+ color: menuItem.highlighted ? Colors.active : "transparent"
+ }
+ }
+ background: Rectangle {
+ implicitWidth: 210
+ implicitHeight: 35
+ color: Colors.surface2
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml
new file mode 100644
index 000000000..4874a2c03
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/MyMenuBar.qml
@@ -0,0 +1,177 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls.Basic
+import FileSystemModule
+
+// The MenuBar also serves as a controller for our window as we don't use any decorations.
+MenuBar {
+ id: root
+
+ required property ApplicationWindow dragWindow
+ property alias infoText: windowInfo.text
+
+ // Customization of the top level menus inside the MenuBar
+ delegate: MenuBarItem {
+ id: menuBarItem
+
+ contentItem: Text {
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ text: menuBarItem.text
+ font: menuBarItem.font
+ elide: Text.ElideRight
+ color: menuBarItem.highlighted ? Colors.textFile : Colors.text
+ opacity: enabled ? 1.0 : 0.3
+ }
+
+ background: Rectangle {
+ id: background
+
+ color: menuBarItem.highlighted ? Colors.selection : "transparent"
+ Rectangle {
+ id: indicator
+
+ width: 0; height: 3
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+
+ color: Colors.color1
+ states: State {
+ name: "active"
+ when: menuBarItem.highlighted
+ PropertyChanges {
+ indicator.width: background.width - 2
+ }
+ }
+ transitions: Transition {
+ NumberAnimation {
+ properties: "width"
+ duration: 175
+ }
+ }
+ }
+ }
+ }
+ // We use the contentItem property as a place to attach our window decorations. Beneath
+ // the usual menu entries within a MenuBar, it includes a centered information text, along
+ // with the minimize, maximize, and close buttons.
+ contentItem: RowLayout {
+ id: windowBar
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ spacing: root.spacing
+ Repeater {
+ id: menuBarItems
+
+ Layout.alignment: Qt.AlignLeft
+ model: root.contentModel
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Text {
+ id: windowInfo
+
+ width: parent.width; height: parent.height
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: windowActions.width
+ color: Colors.text
+ clip: true
+ }
+ }
+
+ RowLayout {
+ id: windowActions
+
+ Layout.alignment: Qt.AlignRight
+ Layout.fillHeight: true
+
+ spacing: 0
+
+ component InteractionButton: Rectangle {
+ id: interactionButton
+
+ signal action()
+ property alias hovered: hoverHandler.hovered
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: height
+
+ color: hovered ? Colors.background : "transparent"
+ HoverHandler {
+ id: hoverHandler
+ }
+ TapHandler {
+ id: tapHandler
+ onTapped: interactionButton.action()
+ }
+ }
+
+ InteractionButton {
+ id: minimize
+
+ onAction: root.dragWindow.showMinimized()
+ Rectangle {
+ anchors.centerIn: parent
+ color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ height: 2
+ width: parent.height - 14
+ }
+ }
+
+ InteractionButton {
+ id: maximize
+
+ onAction: root.dragWindow.showMaximized()
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 7
+ border.color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ border.width: 2
+ color: "transparent"
+ }
+ }
+
+ InteractionButton {
+ id: close
+
+ color: hovered ? "#ec4143" : "transparent"
+ onAction: root.dragWindow.close()
+ Rectangle {
+ anchors.centerIn: parent
+ width: parent.height - 8; height: 2
+
+ rotation: 45
+ antialiasing: true
+ transformOrigin: Item.Center
+ color: parent.hovered ? Colors.iconIndicator : Colors.icon
+
+ Rectangle {
+ anchors.centerIn: parent
+ width: parent.height
+ height: parent.width
+
+ antialiasing: true
+ color: parent.color
+ }
+ }
+ }
+ }
+ }
+
+ background: Rectangle {
+ color: Colors.surface2
+ // Make the empty space drag the specified root window.
+ WindowDragHandler {
+ dragWindow: root.dragWindow
+ }
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml
new file mode 100644
index 000000000..0df65bf82
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/ResizeButton.qml
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick.Controls
+import FileSystemModule
+
+Button {
+ required property ApplicationWindow resizeWindow
+
+ icon.width: 20; icon.height: 20
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ rightPadding: 3
+ bottomPadding: 3
+
+ icon.source: "../icons/resize.svg"
+ icon.color: hovered ? Colors.iconIndicator : Colors.icon
+
+ background: null
+ checkable: false
+ display: AbstractButton.IconOnly
+ onPressed: resizeWindow.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml
new file mode 100644
index 000000000..04880a55d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/Sidebar.qml
@@ -0,0 +1,146 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls.Basic
+import FileSystemModule
+
+Rectangle {
+ id: root
+
+ property alias currentTabIndex: topBar.currentIndex
+ required property ApplicationWindow dragWindow
+ readonly property int tabBarSpacing: 10
+
+ color: Colors.surface2
+
+ component SidebarEntry: Button {
+ id: sidebarButton
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+
+ icon.color: down || checked ? Colors.iconIndicator : Colors.icon
+ icon.width: 27
+ icon.height: 27
+
+ topPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ background: null
+
+ Rectangle {
+ id: indicator
+
+ anchors.verticalCenter: parent.verticalCenter
+ x: 2
+ width: 4
+ height: sidebarButton.icon.height * 1.2
+
+ visible: sidebarButton.checked
+ color: Colors.color1
+ }
+ }
+
+ // TabBar is designed to be horizontal, whereas we need a vertical bar.
+ // We can easily achieve that by using a Container.
+ component TabBar: Container {
+ id: tabBarComponent
+
+ Layout.fillWidth: true
+ // ButtonGroup ensures that only one button can be checked at a time.
+ ButtonGroup {
+ buttons: tabBarComponent.contentChildren
+
+ // We have to manage the currentIndex ourselves, which we do by setting it to the index
+ // of the currently checked button. We use setCurrentIndex instead of setting the
+ // currentIndex property to avoid breaking bindings. See "Managing the Current Index"
+ // in Container's documentation for more information.
+ onCheckedButtonChanged: tabBarComponent.setCurrentIndex(
+ Math.max(0, buttons.indexOf(checkedButton)))
+ }
+
+ contentItem: ColumnLayout {
+ spacing: tabBarComponent.spacing
+ Repeater {
+ model: tabBarComponent.contentModel
+ }
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: root
+ anchors.topMargin: root.tabBarSpacing
+ anchors.bottomMargin: root.tabBarSpacing
+
+ spacing: root.tabBarSpacing
+ TabBar {
+ id: topBar
+
+ spacing: root.tabBarSpacing
+ // Shows help text when clicked.
+ SidebarEntry {
+ id: infoTab
+ icon.source: "../icons/light_bulb.svg"
+ checkable: true
+ checked: true
+ }
+
+ // Shows the file system when clicked.
+ SidebarEntry {
+ id: filesystemTab
+
+ icon.source: "../icons/read.svg"
+ checkable: true
+ }
+
+ // Shows the scheme switcher
+ SidebarEntry {
+ icon.source: "../icons/leaf.svg"
+ checkable: true
+
+ Layout.alignment: Qt.AlignHCenter
+ }
+ }
+
+ // This item acts as a spacer to expand between the checkable and non-checkable buttons.
+ Item {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ // Make the empty space drag our main window.
+ WindowDragHandler {
+ dragWindow: root.dragWindow
+ }
+ }
+
+ TabBar {
+ id: bottomBar
+
+ spacing: root.tabBarSpacing
+ // Opens the Qt website in the system's web browser.
+ SidebarEntry {
+ id: qtWebsiteButton
+ icon.source: "../icons/globe.svg"
+ checkable: false
+ onClicked: Qt.openUrlExternally("https://www.qt.io/")
+ }
+
+ // Opens the About Qt Window.
+ SidebarEntry {
+ id: aboutQtButton
+
+ icon.source: "../icons/info_sign.svg"
+ checkable: false
+ onClicked: aboutQtWindow.visible = !aboutQtWindow.visible
+ }
+ }
+ }
+
+ About {
+ id: aboutQtWindow
+ visible: false
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/WindowDragHandler.qml b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/WindowDragHandler.qml
new file mode 100644
index 000000000..0e140aca3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qml/WindowDragHandler.qml
@@ -0,0 +1,16 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+// Allows dragging the window when placed on an unused section of the UI.
+DragHandler {
+
+ required property ApplicationWindow dragWindow
+
+ target: null
+ onActiveChanged: {
+ if (active) dragWindow.startSystemMove()
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir
new file mode 100644
index 000000000..e9c99b07b
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/FileSystemModule/qmldir
@@ -0,0 +1,11 @@
+module FileSystemModule
+Main 1.0 Main.qml
+About 1.0 qml/About.qml
+Editor 1.0 qml/Editor.qml
+MyMenu 1.0 qml/MyMenu.qml
+Sidebar 1.0 qml/Sidebar.qml
+MyMenuBar 1.0 qml/MyMenuBar.qml
+ColorScheme 1.0 qml/ColorScheme.qml
+ResizeButton 1.0 qml/ResizeButton.qml
+FileSystemView 1.0 qml/FileSystemView.qml
+WindowDragHandler 1.0 qml/WindowDragHandler.qml
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py b/sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py
new file mode 100644
index 000000000..688147726
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/editormodels.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import QFileSystemModel
+from PySide6.QtQuick import QQuickTextDocument
+from PySide6.QtQml import QmlElement, QmlSingleton
+from PySide6.QtCore import (Qt, QDir, QAbstractListModel, Slot, QFile, QTextStream,
+ QMimeDatabase, QFileInfo, QStandardPaths, QModelIndex,
+ Signal, Property)
+
+QML_IMPORT_NAME = "FileSystemModule"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+@QmlSingleton
+class FileSystemModel(QFileSystemModel):
+
+ rootIndexChanged = Signal()
+
+ def getDefaultRootDir():
+ return QStandardPaths.writableLocation(QStandardPaths.StandardLocation.HomeLocation)
+
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ self.mRootIndex = QModelIndex()
+ self.mDb = QMimeDatabase()
+ self.setFilter(QDir.Filter.AllEntries | QDir.Filter.Hidden | QDir.Filter.NoDotAndDotDot)
+ self.setInitialDirectory()
+
+ # check for the correct mime type and then read the file.
+ # returns the text file's content or an error message on failure
+ @Slot(str, result=str)
+ def readFile(self, path):
+ if path == "":
+ return ""
+
+ file = QFile(path)
+
+ mime = self.mDb.mimeTypeForFile(QFileInfo(file))
+ if ('text' in mime.comment().lower()
+ or any('text' in s.lower() for s in mime.parentMimeTypes())):
+ if file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text):
+ stream = QTextStream(file).readAll()
+ file.close()
+ return stream
+ else:
+ return self.tr("Error opening the file!")
+ return self.tr("File type not supported!")
+
+ @Slot(QQuickTextDocument, int, result=int)
+ def currentLineNumber(self, textDocument, cursorPosition):
+ td = textDocument.textDocument()
+ tb = td.findBlock(cursorPosition)
+ return tb.blockNumber()
+
+ def setInitialDirectory(self, path=getDefaultRootDir()):
+ dir = QDir(path)
+ if dir.makeAbsolute():
+ self.setRootPath(dir.path())
+ else:
+ self.setRootPath(self.getDefaultRootDir())
+ self.setRootIndex(self.index(dir.path()))
+
+ # we only need one column in this example
+ def columnCount(self, parent):
+ return 1
+
+ @Property(QModelIndex, notify=rootIndexChanged)
+ def rootIndex(self):
+ return self.mRootIndex
+
+ def setRootIndex(self, index):
+ if (index == self.mRootIndex):
+ return
+ self.mRootIndex = index
+ self.rootIndexChanged.emit()
+
+
+@QmlElement
+class LineNumberModel(QAbstractListModel):
+
+ lineCountChanged = Signal()
+
+ def __init__(self, parent=None):
+ self.mLineCount = 0
+ super().__init__(parent=parent)
+
+ @Property(int, notify=lineCountChanged)
+ def lineCount(self):
+ return self.mLineCount
+
+ @lineCount.setter
+ def lineCount(self, n):
+ if n < 0:
+ print("lineCount must be greater then zero")
+ return
+ if self.mLineCount == n:
+ return
+
+ if self.mLineCount < n:
+ self.beginInsertRows(QModelIndex(), self.mLineCount, n - 1)
+ self.mLineCount = n
+ self.endInsertRows()
+ else:
+ self.beginRemoveRows(QModelIndex(), n, self.mLineCount - 1)
+ self.mLineCount = n
+ self.endRemoveRows()
+
+ def rowCount(self, parent):
+ return self.mLineCount
+
+ def data(self, index, role):
+ if not self.checkIndex(index) or role != Qt.ItemDataRole.DisplayRole:
+ return
+ return index.row()
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md
new file mode 100644
index 000000000..0ac7bec18
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.md
@@ -0,0 +1,210 @@
+# Extending the file system explorer example
+
+This tutorial shows how to extend the
+[Filesystem Explorer Example](filesystemexplorer_example)
+by adding a simple scheme manager. This feature will allow you to switch color
+schemes during the application's runtime. The color schemes will be declared in
+JSON format and made available through a custom Python-QML plugin.
+
+![Extended Explorer GIF](resources/extendedexplorer.gif)
+
+## Defining the color schemes
+
+To define your color scheme, you can use the same color names as the original
+example, so you don't have to rename every occurrence. The original colors are
+defined in the `Colors.qml` file as follows:
+
+```{literalinclude} resources/Colors.qml
+---
+language: QML
+caption: true
+linenos: true
+lines: 7-22
+---
+```
+
+The `schemes.json` file holds the color schemes. To start implementing this, you
+can use the [Catppuccin](https://github.com/catppuccin/catppuccin) scheme.
+
+```{literalinclude} schemes.json
+---json
+caption: true
+linenos: true
+start-at: "Catppuccin"
+end-at: "},"
+---
+```
+
+In addition to the "Catppuccin" color scheme, four other color schemes got
+implemented: Nordic, One Dark, Gruvbox, and Solarized. However, feel free to get
+creative and experiment with **your** schemes.
+
+To define a new color scheme, copy the structure from above and provide your
+color values
+
+## Implement the scheme manager
+
+After defining the color schemes, you can implement the actual scheme manager.
+The manager will read the `schemes.json` file and provide QML bindings to switch
+between schemes during runtime.
+
+To implement the scheme manager, create a Python-QML plugin that exposes the
+`SchemeManager` object to QML. This object will have methods to load the color
+schemes from the `schemes.json` file and switch between them.
+
+Create a new Python file called `schememanager.py` in your project directory. In
+this file, define the **SchemeManager** class:
+
+```{literalinclude} scheme_manager.py
+---
+language: python
+caption: true
+linenos: true
+start-at: "QML_IMPORT_NAME"
+end-at: "class SchemeManager"
+---
+```
+
+To integrate smoothly into the already existing code, attach the SchemeManager
+to the same QML module that's already present with
+`QML_IMPORT_NAME = "FileSystemModule"`. Additionally, use the`@QmlNamedElement`
+decorator to smoothly transition to using the custom plugin instead of the
+`Colors.qml` file. With these changes, we can avoid editing all previous
+assignments like:
+
+```QML
+import FileSystemModule
+...
+Rectangle {
+ color: Colors.background
+}
+```
+
+The constructor reads the `schemes.json` file once upon application start and
+then calls the `setTheme` member function.
+
+```{literalinclude} scheme_manager.py
+---
+language: python
+caption: true
+linenos: true
+lines: 18-24
+---
+```
+
+By adding the `SchemeManager` as a callable QML element named **Colors** to the
+FileSystemModule, the class is now accessible in the code without the need to
+import it each time or edit previous assignments. This, in turn, will streamline
+the workflow.
+
+After defining the schemes in the JSON format and making the `SchemeManager`
+class a callable element from QML under the name **Colors**, there are two
+remaining steps to fully integrate the new scheme manager in the example.
+
+The **first step** is to create a function in the `SchemeManager` class that
+loads a color scheme from the JSON file. The **second step** is to make the
+individual colors available in QML with the *same name* as used before with the
+syntax `Colors.<previousName>` as assignable properties.
+
+
+```{literalinclude} scheme_manager.py
+---
+language: python
+caption: true
+linenos: true
+lines: 26-31
+---
+```
+
+The `setScheme` method is responsible for switching between color schemes. To
+make this method accessible in QML, use the `@Slot(str)` decorator and specify
+that it takes a string as its input parameter. In this method, we populate a
+dictionary with the color values from the JSON file.
+
+> Note: For simplicity reasons no other error checking is performed.
+> You would probably want to validate the keys contained in the json.
+
+```{literalinclude} scheme_manager.py
+---
+language: python
+caption: true
+linenos: true
+start-at: "@Property(QColor"
+end-at: "return"
+---
+```
+
+To make the color property assignable in QML, use the `@Property` decorator.
+We simply return the corresponding color value from the dictionary for each
+property. This process is repeated for all other colors that are used in the
+application.
+At this point the application should start with the colors provided by the
+active scheme in the constructor.
+
+## Add the scheme switching to QML
+
+To visualize the current scheme and enable interactive scheme switching, start
+by adding a new entry to the `Sidebar.qml` file.
+
+```{literalinclude} FileSystemModule/qml/Sidebar.qml
+---
+language: QML
+caption: true
+linenos: true
+lines: 99-105
+---
+```
+
+To update the main content area of the application to display the `ColorScheme`,
+the logic that checks the active index from the Sidebar buttons needs to be
+modified. The necessary changes will be made to the Main.qml file:
+
+```{literalinclude} FileSystemModule/Main.qml
+---
+language: QML
+caption: true
+linenos: true
+lines: 170-187
+---
+```
+
+In addition, change the behavior of the application so that there are two
+`StackLayouts`: one for the resizable navigation and one for the main content
+area where we display our color scheme switching functionality. These changes
+will also be made to the Main.qml file.
+
+```{literalinclude} FileSystemModule/Main.qml
+---
+language: QML
+caption: true
+linenos: true
+lines: 147-150
+---
+```
+
+To complete our implementation, the `ColorScheme.qml` file needs to be created.
+The implementation is straightforward and follows the same principles as in the
+original example. If anything is unclear, please refer to the documentation
+provided there. To display all colors and scheme names, use a `Repeater`. The
+model for the Repeater is provided by our `scheme_manager.py`file as a
+`QStringList`.
+
+```{literalinclude} FileSystemModule/qml/ColorScheme.qml
+---
+language: QML
+caption: true
+linenos: true
+lines: 97-111
+---
+```
+
+When examining the code in more detail, you will notice that there are different
+ways to retrieve the models. The `getKeys()` method is defined as a **Slot** and
+therefore requires parentheses when called. On the other hand, the `currentColors`
+model is defined as a **property** and is therefore assigned as a property in QML.
+The reason for this is to receive notifications when the color scheme is switched
+so that the colors displayed in the application can be updated. The keys for the
+color schemes are loaded only once at application startup and do not rely on any
+notifications.
+
+![Extended Explorer GIF](resources/colorscheme.png)
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject
new file mode 100644
index 000000000..77a3969ae
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/extendedexplorer.pyproject
@@ -0,0 +1,31 @@
+{
+ "files": [
+ "main.py",
+ "editormodels.py",
+ "scheme_manager.py",
+ "schemes.json",
+ "FileSystemModule/qmldir",
+ "FileSystemModule/app.qrc",
+ "FileSystemModule/icons.qrc",
+ "FileSystemModule/Main.qml",
+ "FileSystemModule/qml/About.qml",
+ "FileSystemModule/qml/ColorScheme.qml",
+ "FileSystemModule/qml/Editor.qml",
+ "FileSystemModule/qml/FileSystemView.qml",
+ "FileSystemModule/qml/MyMenu.qml",
+ "FileSystemModule/qml/MyMenuBar.qml",
+ "FileSystemModule/qml/ResizeButton.qml",
+ "FileSystemModule/qml/Sidebar.qml",
+ "FileSystemModule/qml/WindowDragHandler.qml",
+ "FileSystemModule/icons/app_icon.svg",
+ "FileSystemModule/icons/folder_closed.svg",
+ "FileSystemModule/icons/folder_open.svg",
+ "FileSystemModule/icons/generic_file.svg",
+ "FileSystemModule/icons/globe.svg",
+ "FileSystemModule/icons/info_sign.svg",
+ "FileSystemModule/icons/light_bulb.svg",
+ "FileSystemModule/icons/qt_logo.svg",
+ "FileSystemModule/icons/read.svg",
+ "FileSystemModule/icons/resize.svg"
+ ]
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/main.py b/sources/pyside6/doc/tutorials/extendedexplorer/main.py
new file mode 100644
index 000000000..f1e6e7d93
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/main.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""
+This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer.
+"""
+
+# Compile both resource files app.qrc and icons.qrc and include them here if you wish
+# to load them from the resource system. Currently, all resources are loaded locally
+# import FileSystemModule.rc_icons
+# import FileSystemModule.rc_app
+
+from scheme_manager import SchemeManager
+from editormodels import FileSystemModel
+import PySide6
+from PySide6.QtGui import QGuiApplication, QIcon
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtCore import QCommandLineParser
+
+import sys
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ app.setOrganizationName("QtProject")
+ app.setApplicationName("File System Explorer")
+ app.setApplicationVersion(PySide6.__version__)
+ app.setWindowIcon(QIcon("FileSystemModule/icons/app_icon.svg"))
+
+ parser = QCommandLineParser()
+ parser.setApplicationDescription("Qt Filesystemexplorer Example")
+ parser.addHelpOption()
+ parser.addVersionOption()
+ parser.addPositionalArgument("", "Initial directory", "[path]")
+ parser.process(app)
+ args = parser.positionalArguments()
+
+ engine = QQmlApplicationEngine()
+ # Include the path of this file to search for the 'qmldir' module
+ engine.addImportPath(sys.path[0])
+
+ engine.loadFromModule("FileSystemModule", "Main")
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ if (len(args) == 1):
+ fsm = engine.singletonInstance("FileSystemModule", "FileSystemModel")
+ fsm.setInitialDirectory(args[0])
+
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/resources/Colors.qml b/sources/pyside6/doc/tutorials/extendedexplorer/resources/Colors.qml
new file mode 100644
index 000000000..280f89286
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/resources/Colors.qml
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma Singleton
+import QtQuick
+
+QtObject {
+ readonly property color background: "#23272E"
+ readonly property color surface1: "#1E2227"
+ readonly property color surface2: "#090A0C"
+ readonly property color text: "#ABB2BF"
+ readonly property color textFile: "#C5CAD3"
+ readonly property color disabledText: "#454D5F"
+ readonly property color selection: "#2C313A"
+ readonly property color active: "#23272E"
+ readonly property color inactive: "#3E4452"
+ readonly property color folder: "#3D4451"
+ readonly property color icon: "#3D4451"
+ readonly property color iconIndicator: "#E5C07B"
+ readonly property color color1: "#E06B74"
+ readonly property color color2: "#62AEEF"
+}
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/resources/colorscheme.png b/sources/pyside6/doc/tutorials/extendedexplorer/resources/colorscheme.png
new file mode 100644
index 000000000..410538ca3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/resources/colorscheme.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.gif b/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.gif
new file mode 100644
index 000000000..9e59f64aa
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.gif
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp b/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp
new file mode 100644
index 000000000..79a14f34c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/resources/extendedexplorer.webp
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/scheme_manager.py b/sources/pyside6/doc/tutorials/extendedexplorer/scheme_manager.py
new file mode 100644
index 000000000..8d732093c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/scheme_manager.py
@@ -0,0 +1,97 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import json
+from pathlib import Path
+from PySide6.QtCore import Slot, QObject, Property, Signal
+from PySide6.QtGui import QColor
+from PySide6.QtQml import QmlNamedElement, QmlSingleton
+
+QML_IMPORT_NAME = "FileSystemModule"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlNamedElement("Colors")
+@QmlSingleton
+class SchemeManager(QObject):
+
+ schemeChanged = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ with open(Path(__file__).parent / "schemes.json", 'r') as f:
+ self.m_schemes = json.load(f)
+ self.m_activeScheme = {}
+ self.m_activeSchemeName = "Catppuccin"
+ self.setScheme(self.m_activeSchemeName)
+
+ @Slot(str)
+ def setScheme(self, theme):
+ for k, v in self.m_schemes[theme].items():
+ self.m_activeScheme[k] = QColor.fromString(v)
+ self.m_activeSchemeName = theme
+ self.schemeChanged.emit()
+
+ @Slot(result='QStringList')
+ def getKeys(self):
+ return self.m_schemes.keys()
+
+ @Property('QStringList', notify=schemeChanged)
+ def currentColors(self):
+ return self.m_schemes[self.m_activeSchemeName].values()
+
+ @Property(QColor, notify=schemeChanged)
+ def background(self):
+ return self.m_activeScheme["background"]
+
+ @Property(QColor, notify=schemeChanged)
+ def surface1(self):
+ return self.m_activeScheme["surface1"]
+
+ @Property(QColor, notify=schemeChanged)
+ def surface2(self):
+ return self.m_activeScheme["surface2"]
+
+ @Property(QColor, notify=schemeChanged)
+ def text(self):
+ return self.m_activeScheme["text"]
+
+ @Property(QColor, notify=schemeChanged)
+ def textFile(self):
+ return self.m_activeScheme["textFile"]
+
+ @Property(QColor, notify=schemeChanged)
+ def disabledText(self):
+ return self.m_activeScheme["disabledText"]
+
+ @Property(QColor, notify=schemeChanged)
+ def selection(self):
+ return self.m_activeScheme["selection"]
+
+ @Property(QColor, notify=schemeChanged)
+ def active(self):
+ return self.m_activeScheme["active"]
+
+ @Property(QColor, notify=schemeChanged)
+ def inactive(self):
+ return self.m_activeScheme["inactive"]
+
+ @Property(QColor, notify=schemeChanged)
+ def folder(self):
+ return self.m_activeScheme["folder"]
+
+ @Property(QColor, notify=schemeChanged)
+ def icon(self):
+ return self.m_activeScheme["icon"]
+
+ @Property(QColor, notify=schemeChanged)
+ def iconIndicator(self):
+ return self.m_activeScheme["iconIndicator"]
+
+ @Property(QColor, notify=schemeChanged)
+ def color1(self):
+ return self.m_activeScheme["color1"]
+
+ @Property(QColor, notify=schemeChanged)
+ def color2(self):
+ return self.m_activeScheme["color2"]
diff --git a/sources/pyside6/doc/tutorials/extendedexplorer/schemes.json b/sources/pyside6/doc/tutorials/extendedexplorer/schemes.json
new file mode 100644
index 000000000..e4f2d8c81
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/extendedexplorer/schemes.json
@@ -0,0 +1,82 @@
+{
+ "Catppuccin": {
+ "background": "#1E1E2E",
+ "surface1": "#181825",
+ "surface2": "#11111B",
+ "text": "#CDD6F4",
+ "textFile": "#CDD6F4",
+ "disabledText": "#363659",
+ "selection": "#45475A",
+ "active": "#1E1E2E",
+ "inactive": "#6C7086",
+ "folder": "#6C7086",
+ "icon": "#6C7086",
+ "iconIndicator": "#FFCC66",
+ "color1": "#CBA6F7",
+ "color2": "#89DCEB"
+ },
+ "Nordic": {
+ "background": "#2E3440",
+ "surface1": "#2B2F3A",
+ "surface2": "#262A35",
+ "text": "#D8DEE9",
+ "textFile": "#D8DEE9",
+ "disabledText": "#4D556A",
+ "selection": "#495468",
+ "active": "#2E3440",
+ "inactive": "#555B6A",
+ "folder": "#495468",
+ "icon": "#495468",
+ "iconIndicator": "#FFC40D",
+ "color1": "#81B6C6",
+ "color2": "#5E81AC"
+ },
+ "One Dark": {
+ "background": "#23272E",
+ "surface1": "#1E2227",
+ "surface2": "#090A0C",
+ "text": "#ABB2BF",
+ "textFile": "#C5CAD3",
+ "disabledText": "#2B303B",
+ "selection": "#2C313A",
+ "active": "#23272E",
+ "inactive": "#3E4452",
+ "folder": "#3D4451",
+ "icon": "#3D4451",
+ "iconIndicator": "#E5C07B",
+ "color1": "#E06B74",
+ "color2": "#62AEEF"
+ },
+ "Gruvbox": {
+ "background": "#292828",
+ "surface1": "#171819",
+ "surface2": "#090A0C",
+ "text": "#D4BE98",
+ "textFile": "#E1D2B7",
+ "disabledText": "#2C313A",
+ "selection": "#333130",
+ "active": "#292828",
+ "inactive": "#383737",
+ "folder": "#383737",
+ "icon": "#383737",
+ "iconIndicator": "#FFCC66",
+ "color1": "#A7B464",
+ "color2": "#D3869B"
+ },
+ "Solarized": {
+ "background": "#FDF6E3",
+ "surface1": "#EEE8D5",
+ "surface2": "#DDD6C1",
+ "text": "#6D6D6D",
+ "textFile": "#333333",
+ "disabledText": "#ADADAD",
+ "selection": "#D1CBB8",
+ "active": "#FDF6E3",
+ "inactive": "#8C8364",
+ "folder": "#5F5944",
+ "icon": "#5F5944",
+ "iconIndicator": "#002B36",
+ "color1": "#B58900",
+ "color2": "#6B9E75"
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/index.rst b/sources/pyside6/doc/tutorials/index.rst
new file mode 100644
index 000000000..8a69a3c6f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/index.rst
@@ -0,0 +1,206 @@
+Tutorials
+=========
+
+A collection of tutorials with walkthrough guides are
+provided with |project| to help new users get started.
+
+Some of these documents were ported from C++ to Python and cover a range of
+topics, from basic use of widgets to step-by-step tutorials that show how an
+application is put together.
+
+Qt Widgets: Basic tutorials
+---------------------------
+
+If you want to see the available widgets in action, you can check the
+`Qt Widget Gallery <https://doc.qt.io/qt-6/gallery.html>`_ to learn their
+names and how they look like.
+
+.. grid:: 1 3 3 3
+ :gutter: 2
+
+ .. grid-item-card:: Basic Widget
+ :class-item: cover-img
+ :link: basictutorial/widgets.html
+ :img-top: basictutorial/widgets.png
+
+ Your first QtWidgets Application
+
+ .. grid-item-card:: Basic Button
+ :class-item: cover-img
+ :link: basictutorial/clickablebutton.html
+ :img-top: basictutorial/clickablebutton.png
+
+ Using a Simple Button
+
+ .. grid-item-card:: Basic Connections
+ :class-item: cover-img
+ :link: basictutorial/signals_and_slots.html
+ :img-top: basictutorial/signals_slots.png
+
+ Signals and Slots
+
+ .. grid-item-card:: Basic Dialog
+ :class-item: cover-img
+ :link: basictutorial/dialog.html
+ :img-top: basictutorial/dialog.png
+
+ Creating a Dialog Application
+
+ .. grid-item-card:: Basic Table
+ :class-item: cover-img
+ :link: basictutorial/tablewidget.html
+ :img-top: basictutorial/tablewidget.png
+
+ Displaying Data Using a Table Widget
+
+ .. grid-item-card:: Basic Tree
+ :class-item: cover-img
+ :link: basictutorial/treewidget.html
+ :img-top: basictutorial/treewidget.png
+
+ Displaying Data Using a Tree Widget
+
+ .. grid-item-card:: Basic ``ui`` files
+ :class-item: cover-img
+ :link: basictutorial/uifiles.html
+ :img-top: basictutorial/uifiles.png
+
+ Using .ui files from Designer or QtCreator with QUiLoader and pyside6-uic
+
+ .. grid-item-card:: Basic ``qrc`` files
+ :class-item: cover-img
+ :link: basictutorial/qrcfiles.html
+ :img-top: basictutorial/player-new.png
+
+ Using .qrc Files (pyside6-rcc)
+
+ .. grid-item-card:: Basic Translations
+ :class-item: cover-img
+ :link: basictutorial/translations.html
+ :img-top: basictutorial/translations.png
+
+ Translating Applications
+
+ .. grid-item-card:: Basic Widget Style
+ :class-item: cover-img
+ :link: basictutorial/widgetstyling.html
+ :img-top: basictutorial/widgetstyling-yes.png
+
+ Styling the Widgets Application
+
+.. toctree::
+ :hidden:
+
+ basictutorial/widgets.rst
+ basictutorial/clickablebutton.rst
+ basictutorial/signals_and_slots.rst
+ basictutorial/dialog.rst
+ basictutorial/tablewidget.rst
+ basictutorial/treewidget.rst
+ basictutorial/uifiles.rst
+ basictutorial/qrcfiles.rst
+ basictutorial/translations.rst
+ basictutorial/widgetstyling.rst
+
+
+Quick/QML: Basic tutorials
+--------------------------
+
+.. grid:: 1 3 3 3
+ :gutter: 2
+
+ .. grid-item-card:: Basic Quick
+ :class-item: cover-img
+ :link: basictutorial/qml.html
+ :img-top: basictutorial/greenapplication.png
+
+ Your First QtQuick/QML Application
+
+ .. grid-item-card:: Basic QML Integration
+ :class-item: cover-img
+ :link: qmlintegration/qmlintegration.html
+ :img-top: qmlintegration/textproperties_material.png
+
+ Python-QML integration
+
+ .. grid-item-card:: QML Application
+ :class-item: cover-img
+ :link: qmlapp/qmlapplication.html
+ :img-top: qmlapp/qmlapplication.png
+
+ QML Application Tutorial (QtCreator)
+
+ .. grid-item-card:: Advanced QML Integration
+ :class-item: cover-img
+ :link: qmlsqlintegration/qmlsqlintegration.html
+ :img-top: qmlsqlintegration/example_list_view.png
+
+ QML, SQL and PySide Integration Tutorial
+
+ .. grid-item-card:: Extended Explorer
+ :class-item: cover-img
+ :link: extendedexplorer/extendedexplorer.html
+ :img-top: extendedexplorer/resources/extendedexplorer.webp
+
+ Extending an Qt Quick Controls example
+
+.. toctree::
+ :maxdepth: 1
+ :hidden:
+
+ basictutorial/qml.rst
+ qmlintegration/qmlintegration.rst
+ qmlapp/qmlapplication.rst
+ qmlsqlintegration/qmlsqlintegration.rst
+ extendedexplorer/extendedexplorer.md
+
+General Applications
+--------------------
+
+.. grid:: 1 3 3 3
+ :gutter: 2
+
+ .. grid-item-card:: Data Visualization
+ :class-item: cover-img
+ :link: datavisualize/index.html
+ :img-top: datavisualize/images/datavisualization_app.png
+
+ Data Visualization Tool
+
+ .. grid-item-card:: Expenses Application
+ :class-item: cover-img
+ :link: expenses/expenses.html
+ :img-top: expenses/expenses_tool.png
+
+ Expenses administration tool
+
+.. toctree::
+ :hidden:
+
+ datavisualize/index.rst
+ expenses/expenses.rst
+
+Qt Overviews
+------------
+
+.. toctree::
+ :maxdepth: 1
+
+ ../overviews/overviews-main.rst
+
+C++ and Python
+--------------
+
+.. toctree::
+ :maxdepth: 1
+
+ portingguide/index.rst
+
+Debug a PySide6 Application
+---------------------------
+.. toctree::
+ :maxdepth: 1
+
+ debugging/mixed_debugging.rst
+ debugging/qml_debugging.rst
+
diff --git a/sources/pyside6/doc/tutorials/modelviewprogramming/qlistview-dnd.py b/sources/pyside6/doc/tutorials/modelviewprogramming/qlistview-dnd.py
new file mode 100644
index 000000000..3a37cc0f3
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/modelviewprogramming/qlistview-dnd.py
@@ -0,0 +1,137 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtWidgets import (QAbstractItemView, QApplication, QMainWindow,
+ QListView)
+from PySide6.QtCore import (QByteArray, QDataStream, QIODevice, QMimeData,
+ QModelIndex, QStringListModel, Qt)
+
+
+class DragDropListModel(QStringListModel):
+ """A simple model that uses a QStringList as its data source."""
+
+ def __init__(self, strings, parent=None):
+ super().__init__(strings, parent)
+
+#! [0]
+
+ def canDropMimeData(self, data, action, row, column, parent):
+ if not data.hasFormat("application/vnd.text.list"):
+ return False
+
+ if column > 0:
+ return False
+
+ return True
+#! [0]
+#! [1]
+ def dropMimeData(self, data, action, row, column, parent):
+ if not self.canDropMimeData(data, action, row, column, parent):
+ return False
+
+ if action == Qt.IgnoreAction:
+ return True
+#! [1]
+
+#! [2]
+ begin_row = 0
+
+ if row != -1:
+ begin_row = row
+#! [2] #! [3]
+ elif parent.isValid():
+ begin_row = parent.row()
+#! [3] #! [4]
+ else:
+ begin_row = self.rowCount(QModelIndex())
+#! [4]
+
+#! [5]
+ encoded_data = data.data("application/vnd.text.list")
+ stream = QDataStream(encoded_data, QIODevice.ReadOnly)
+ new_items = []
+ while not stream.atEnd():
+ new_items.append(stream.readQString())
+#! [5]
+
+#! [6]
+ self.insertRows(begin_row, len(new_items), QModelIndex())
+ for text in new_items:
+ idx = self.index(begin_row, 0, QModelIndex())
+ self.setData(idx, text)
+ begin_row += 1
+
+ return True
+#! [6]
+
+#! [7]
+ def flags(self, index):
+ default_flags = super().flags(index)
+ if index.isValid():
+ return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | default_flags
+ return Qt.ItemIsDropEnabled | default_flags
+#! [7]
+
+#! [8]
+ def mimeData(self, indexes):
+ mime_data = QMimeData()
+ encoded_data = QByteArray()
+ stream = QDataStream(encoded_data, QIODevice.WriteOnly)
+ for index in indexes:
+ if index.isValid():
+ text = self.data(index, Qt.DisplayRole)
+ stream.writeQString(text)
+
+ mime_data.setData("application/vnd.text.list", encoded_data)
+ return mime_data
+#! [8]
+
+#! [9]
+ def mimeTypes(self):
+ return ["application/vnd.text.list"]
+#! [9]
+
+#! [10]
+ def supportedDropActions(self):
+ return Qt.CopyAction | Qt.MoveAction
+#! [10]
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ file_menu = self.menuBar().addMenu("&File")
+ quit_action = file_menu.addAction("E&xit")
+ quit_action.setShortcut("Ctrl+Q")
+
+#! [mainwindow0]
+ self._list_view = QListView(self)
+ self._list_view.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self._list_view.setDragEnabled(True)
+ self._list_view.setAcceptDrops(True)
+ self._list_view.setDropIndicatorShown(True)
+#! [mainwindow0]
+
+ quit_action.triggered.connect(self.close)
+
+ self.setup_list_items()
+
+ self.setCentralWidget(self._list_view)
+ self.setWindowTitle("List View")
+
+ def setup_list_items(self):
+ items = ["Oak", "Fir", "Pine", "Birch", "Hazel", "Redwood", "Sycamore",
+ "Chestnut", "Mahogany"]
+ model = DragDropListModel(items, self)
+ self._list_view.setModel(model)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ window = MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/modelviewprogramming/simplemodel-use.py b/sources/pyside6/doc/tutorials/modelviewprogramming/simplemodel-use.py
new file mode 100644
index 000000000..1bacfd829
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/modelviewprogramming/simplemodel-use.py
@@ -0,0 +1,44 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtWidgets import (QApplication, QFileSystemModel, QLabel,
+ QVBoxLayout, QWidget)
+from PySide6.QtGui import QPalette
+from PySide6.QtCore import QDir, Qt
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ window = QWidget()
+ layout = QVBoxLayout(window)
+ title = QLabel("Some items from the directory model", window)
+ title.setBackgroundRole(QPalette.Base)
+ title.setMargin(8)
+ layout.addWidget(title)
+
+#! [0]
+ model = QFileSystemModel()
+ model.setRootPath(QDir.currentPath())
+
+ def on_directory_loaded(directory):
+ parent_index = model.index(directory)
+ num_rows = model.rowCount(parent_index)
+#! [1]
+ for row in range(num_rows):
+ index = model.index(row, 0, parent_index)
+#! [1]
+#! [2]
+ text = model.data(index, Qt.DisplayRole)
+#! [2]
+ label = QLabel(text, window)
+ layout.addWidget(label)
+
+ model.directoryLoaded.connect(on_directory_loaded)
+#! [0]
+
+ window.setWindowTitle("A simple model example")
+ window.show()
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/modelviewprogramming/stringlistmodel.py b/sources/pyside6/doc/tutorials/modelviewprogramming/stringlistmodel.py
new file mode 100644
index 000000000..2c8493aa9
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/modelviewprogramming/stringlistmodel.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtWidgets import (QApplication, QListView)
+from PySide6.QtCore import QAbstractListModel, QStringListModel, QModelIndex, Qt
+
+
+#! [0]
+class StringListModel(QAbstractListModel):
+ def __init__(self, strings, parent=None):
+ super().__init__(parent)
+ self._strings = strings
+
+#! [0]
+ def rowCount(self, parent=QModelIndex()):
+ """Returns the number of items in the string list as the number of rows
+ in the model."""
+ return len(self._strings)
+#! [0]
+
+#! [1]
+ def data(self, index, role):
+ """Returns an appropriate value for the requested data.
+ If the view requests an invalid index, an invalid variant is returned.
+ Any valid index that corresponds to a string in the list causes that
+ string to be returned."""
+ row = index.row()
+ if not index.isValid() or row >= len(self._strings):
+ return None
+ if role != Qt.DisplayRole and role != Qt.EditRole:
+ return None
+ return self._strings[row]
+#! [1]
+
+#! [2]
+ def headerData(self, section, orientation, role=Qt.DisplayRole):
+ """Returns the appropriate header string depending on the orientation of
+ the header and the section. If anything other than the display role is
+ requested, we return an invalid variant."""
+ if role != Qt.DisplayRole:
+ return None
+ if orientation == Qt.Horizontal:
+ return f"Column {section}"
+ return f"Row {section}"
+#! [2]
+
+#! [3]
+ def flags(self, index):
+ """Returns an appropriate value for the item's flags. Valid items are
+ enabled, selectable, and editable."""
+
+ if not index.isValid():
+ return Qt.ItemIsEnabled
+ return super().flags(index) | Qt.ItemIsEditable
+#! [3]
+
+ #! [4]
+ def setData(self, index, value, role=Qt.EditRole):
+ """Changes an item in the string list, but only if the following conditions
+ are met:
+
+ # The index supplied is valid.
+ # The index corresponds to an item to be shown in a view.
+ # The role associated with editing text is specified.
+
+ The dataChanged() signal is emitted if the item is changed."""
+
+ if index.isValid() and role == Qt.EditRole:
+ self._strings[index.row()] = value
+ self.dataChanged.emit(index, index, {role})
+ return True
+#! [4] #! [5]
+ return False
+#! [5]
+
+#! [6]
+ def insertRows(self, position, rows, parent):
+ """Inserts a number of rows into the model at the specified position."""
+ self.beginInsertRows(QModelIndex(), position, position + rows - 1)
+ for row in range(rows):
+ self._strings.insert(position, "")
+ self.endInsertRows()
+ return True
+#! [6] #! [7]
+#! [7]
+
+#! [8]
+ def removeRows(self, position, rows, parent):
+ """Removes a number of rows from the model at the specified position."""
+ self.beginRemoveRows(QModelIndex(), position, position + rows - 1)
+ for row in range(rows):
+ del self._strings[position]
+ self.endRemoveRows()
+ return True
+#! [8] #! [9]
+#! [9]
+
+
+#! [main0]
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+#! [main1]
+ numbers = ["One", "Two", "Three", "Four", "Five"]
+ model = StringListModel(numbers)
+#! [main0] #! [main1] #! [main2] #! [main3]
+ view = QListView()
+#! [main2]
+ view.setWindowTitle("View onto a string list model")
+#! [main4]
+ view.setModel(model)
+#! [main3] #! [main4]
+
+ model.insertRows(5, 7, QModelIndex())
+ for row in range(5, 12):
+ index = model.index(row, 0, QModelIndex())
+ model.setData(index, f"{row+1}")
+
+#! [main5]
+ view.show()
+ sys.exit(app.exec())
+#! [main5]
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst b/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst
new file mode 100644
index 000000000..87fb97660
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/chapter1.rst
@@ -0,0 +1,89 @@
+Chapter 1: ``initDb.h`` to ``createDb.py``
+*******************************************
+
+To begin with, port the C++ code that creates an SQLite
+database and tables, and adds data to them. In this case,
+all C++ code related to this lives in ``initdb.h``. The
+code in this header file is divided into following parts:
+
+* ``initDb`` - Creates a db and the necessary tables
+* ``addBooks`` - Adds data to the **books** table.
+* ``addAuthor`` - Adds data to the **authors** table.
+* ``addGenre`` - Adds data to the **genres** table.
+
+To start with, add these following ``import`` statements at
+the beginning of ``createdb.py``:
+
+.. literalinclude:: createdb.py
+ :language: python
+ :linenos:
+ :lines: 3-5
+
+The ``initDb`` function does most of the work needed to
+set up the database, but it depends on the ``addAuthor``,
+``addGenre``, and ``addBook`` helper functions to populate
+the tables. Port these helper functions first. Here is how
+the C++ and Python versions of these functions look like:
+
+C++ version
+-----------
+
+.. literalinclude:: initdb.h
+ :language: c++
+ :linenos:
+ :lines: 9-33
+
+Python version
+--------------
+
+.. literalinclude:: createdb.py
+ :language: python
+ :linenos:
+ :lines: 7-28
+
+Now that the helper functions are in place, port ``initDb``.
+Here is how the C++ and Python versions of this function
+looks like:
+
+C++ version (initDb)
+--------------------
+
+.. literalinclude:: initdb.h
+ :language: c++
+ :linenos:
+ :lines: 35-112
+
+Python version (init_db)
+------------------------
+
+.. literalinclude:: createdb.py
+ :language: python
+ :linenos:
+ :lines: 28-
+
+.. note:: The Python version uses the ``check`` function to
+ execute the SQL statements instead of the ``if...else``
+ block like in the C++ version. Although both are valid
+ approaches, the earlier one produces code that looks
+ cleaner and shorter.
+
+Your Python code to set up the database is ready now. To
+test it, add the following code to ``main.py`` and run it:
+
+.. literalinclude:: main.py
+ :language: python
+ :linenos:
+ :lines: 3-
+
+Use the following command from the prompt to run:
+
+.. code-block:: bash
+
+ python main.py
+
+Your table will look like this:
+
+.. image:: images/chapter1_books.png
+
+Try modifying the SQL statment in ``main.py`` to get data
+from the ``genres`` or ``authors`` table.
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter1/createdb.py b/sources/pyside6/doc/tutorials/portingguide/chapter1/createdb.py
new file mode 100644
index 000000000..da7d201a8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/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/chapter1/images/chapter1_books.png b/sources/pyside6/doc/tutorials/portingguide/chapter1/images/chapter1_books.png
new file mode 100644
index 000000000..164674220
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/images/chapter1_books.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter1/initdb.h b/sources/pyside6/doc/tutorials/portingguide/chapter1/initdb.h
new file mode 100644
index 000000000..5596defc0
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/initdb.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef INITDB_H
+#define INITDB_H
+
+#include <QtSql>
+
+void addBook(QSqlQuery &q, const QString &title, int year, const QVariant &authorId,
+ const QVariant &genreId, int rating)
+{
+ q.addBindValue(title);
+ q.addBindValue(year);
+ q.addBindValue(authorId);
+ q.addBindValue(genreId);
+ q.addBindValue(rating);
+ q.exec();
+}
+
+QVariant addGenre(QSqlQuery &q, const QString &name)
+{
+ q.addBindValue(name);
+ q.exec();
+ return q.lastInsertId();
+}
+
+QVariant addAuthor(QSqlQuery &q, const QString &name, const QDate &birthdate)
+{
+ q.addBindValue(name);
+ q.addBindValue(birthdate);
+ q.exec();
+ return q.lastInsertId();
+}
+
+const auto BOOKS_SQL = QLatin1String(R"(
+ create table books(id integer primary key, title varchar, author integer,
+ genre integer, year integer, rating integer)
+ )");
+
+const auto AUTHORS_SQL = QLatin1String(R"(
+ create table authors(id integer primary key, name varchar, birthdate date)
+ )");
+
+const auto GENRES_SQL = QLatin1String(R"(
+ create table genres(id integer primary key, name varchar)
+ )");
+
+const auto INSERT_AUTHOR_SQL = QLatin1String(R"(
+ insert into authors(name, birthdate) values(?, ?)
+ )");
+
+const auto INSERT_BOOK_SQL = QLatin1String(R"(
+ insert into books(title, year, author, genre, rating)
+ values(?, ?, ?, ?, ?)
+ )");
+
+const auto INSERT_GENRE_SQL = QLatin1String(R"(
+ insert into genres(name) values(?)
+ )");
+
+QSqlError initDb()
+{
+ QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
+ db.setDatabaseName(":memory:");
+
+ if (!db.open())
+ return db.lastError();
+
+ QStringList tables = db.tables();
+ if (tables.contains("books", Qt::CaseInsensitive)
+ && tables.contains("authors", Qt::CaseInsensitive))
+ return QSqlError();
+
+ QSqlQuery q;
+ if (!q.exec(BOOKS_SQL))
+ return q.lastError();
+ if (!q.exec(AUTHORS_SQL))
+ return q.lastError();
+ if (!q.exec(GENRES_SQL))
+ return q.lastError();
+
+ if (!q.prepare(INSERT_AUTHOR_SQL))
+ return q.lastError();
+ QVariant asimovId = addAuthor(q, QLatin1String("Isaac Asimov"), QDate(1920, 2, 1));
+ QVariant greeneId = addAuthor(q, QLatin1String("Graham Greene"), QDate(1904, 10, 2));
+ QVariant pratchettId = addAuthor(q, QLatin1String("Terry Pratchett"), QDate(1948, 4, 28));
+
+ if (!q.prepare(INSERT_GENRE_SQL))
+ return q.lastError();
+ QVariant sfiction = addGenre(q, QLatin1String("Science Fiction"));
+ QVariant fiction = addGenre(q, QLatin1String("Fiction"));
+ QVariant fantasy = addGenre(q, QLatin1String("Fantasy"));
+
+ if (!q.prepare(INSERT_BOOK_SQL))
+ return q.lastError();
+ addBook(q, QLatin1String("Foundation"), 1951, asimovId, sfiction, 3);
+ addBook(q, QLatin1String("Foundation and Empire"), 1952, asimovId, sfiction, 4);
+ addBook(q, QLatin1String("Second Foundation"), 1953, asimovId, sfiction, 3);
+ addBook(q, QLatin1String("Foundation's Edge"), 1982, asimovId, sfiction, 3);
+ addBook(q, QLatin1String("Foundation and Earth"), 1986, asimovId, sfiction, 4);
+ addBook(q, QLatin1String("Prelude to Foundation"), 1988, asimovId, sfiction, 3);
+ addBook(q, QLatin1String("Forward the Foundation"), 1993, asimovId, sfiction, 3);
+ addBook(q, QLatin1String("The Power and the Glory"), 1940, greeneId, fiction, 4);
+ addBook(q, QLatin1String("The Third Man"), 1950, greeneId, fiction, 5);
+ addBook(q, QLatin1String("Our Man in Havana"), 1958, greeneId, fiction, 4);
+ addBook(q, QLatin1String("Guards! Guards!"), 1989, pratchettId, fantasy, 3);
+ addBook(q, QLatin1String("Night Watch"), 2002, pratchettId, fantasy, 3);
+ addBook(q, QLatin1String("Going Postal"), 2004, pratchettId, fantasy, 3);
+
+ return QSqlError();
+}
+
+#endif
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter1/main.py b/sources/pyside6/doc/tutorials/portingguide/chapter1/main.py
new file mode 100644
index 000000000..c1293bcbd
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/main.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtSql import QSqlQueryModel
+from PySide6.QtWidgets import QTableView, QApplication
+
+import createdb
+
+if __name__ == "__main__":
+ app = QApplication()
+ createdb.init_db()
+
+ model = QSqlQueryModel()
+ model.setQuery("select * from books")
+
+ table_view = QTableView()
+ table_view.setModel(model)
+ table_view.resize(800, 600)
+ table_view.show()
+ sys.exit(app.exec())
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())
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate-old.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate-old.py
new file mode 100644
index 000000000..b3187e054
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate-old.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, star_png, 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/chapter3/bookdelegate.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate.py
new file mode 100644
index 000000000..145d6b73e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate.py
@@ -0,0 +1,96 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import copy, os
+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, star_png, parent=None):
+ QSqlRelationalDelegate.__init__(self, parent)
+ self.star = QPixmap(":/images/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/chapter3/books.qrc b/sources/pyside6/doc/tutorials/portingguide/chapter3/books.qrc
new file mode 100644
index 000000000..d6ad21337
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/books.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/star.png</file>
+</qresource>
+</RCC>
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.cpp b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.cpp
new file mode 100644
index 000000000..6ec1b9e19
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.cpp
@@ -0,0 +1,124 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "bookwindow.h"
+#include "bookdelegate.h"
+#include "initdb.h"
+
+#include <QtSql>
+
+BookWindow::BookWindow()
+{
+ ui.setupUi(this);
+
+ if (!QSqlDatabase::drivers().contains("QSQLITE"))
+ QMessageBox::critical(
+ this,
+ "Unable to load database",
+ "This demo needs the SQLITE driver"
+ );
+
+ // Initialize the database:
+ QSqlError err = initDb();
+ if (err.type() != QSqlError::NoError) {
+ showError(err);
+ return;
+ }
+
+ // Create the data model:
+ model = new QSqlRelationalTableModel(ui.bookTable);
+ model->setEditStrategy(QSqlTableModel::OnManualSubmit);
+ model->setTable("books");
+
+ // Remember the indexes of the columns:
+ 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 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:
+ 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);
+
+ // Initialize the Author combo box:
+ ui.authorEdit->setModel(model->relationModel(authorIdx));
+ ui.authorEdit->setModelColumn(
+ model->relationModel(authorIdx)->fieldIndex("name"));
+
+ ui.genreEdit->setModel(model->relationModel(genreIdx));
+ ui.genreEdit->setModelColumn(
+ model->relationModel(genreIdx)->fieldIndex("name"));
+
+ // 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));
+ createMenuBar();
+}
+
+void BookWindow::showError(const QSqlError &err)
+{
+ QMessageBox::critical(this, "Unable to initialize Database",
+ "Error initializing database: " + err.text());
+}
+
+void BookWindow::createMenuBar()
+{
+ QAction *quitAction = new QAction(tr("&Quit"), this);
+ QAction *aboutAction = new QAction(tr("&About"), this);
+ QAction *aboutQtAction = new QAction(tr("&About Qt"), this);
+
+ QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
+ fileMenu->addAction(quitAction);
+
+ QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(aboutAction);
+ helpMenu->addAction(aboutQtAction);
+
+ connect(quitAction, &QAction::triggered, this, &BookWindow::close);
+ connect(aboutAction, &QAction::triggered, this, &BookWindow::about);
+ connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
+}
+
+void BookWindow::about()
+{
+ QMessageBox::about(this, tr("About Books"),
+ tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
+ "with a model/view framework."));
+}
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.py
new file mode 100644
index 000000000..bb033c6d2
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.py
@@ -0,0 +1,99 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtGui import QAction
+from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper,
+ QHeaderView, QMainWindow, QMessageBox)
+from PySide6.QtGui import QKeySequence
+from PySide6.QtSql import (QSqlRelation, QSqlRelationalTableModel, QSqlTableModel,
+ QSqlError)
+from PySide6.QtCore import QAbstractItemModel, QObject, QSize, Qt, Slot
+import createdb
+from ui_bookwindow import Ui_BookWindow
+from bookdelegate import BookDelegate
+
+
+class BookWindow(QMainWindow, Ui_BookWindow):
+ # """A window to show the books available"""
+
+ def __init__(self):
+ QMainWindow.__init__(self)
+ self.setupUi(self)
+
+ #Initialize db
+ createdb.init_db()
+
+ model = QSqlRelationalTableModel(self.bookTable)
+ model.setEditStrategy(QSqlTableModel.OnManualSubmit)
+ model.setTable("books")
+
+ # Remember the indexes of the columns:
+ author_idx = model.fieldIndex("author")
+ genre_idx = model.fieldIndex("genre")
+
+ # Set the relations to the other database tables:
+ model.setRelation(author_idx, QSqlRelation("authors", "id", "name"))
+ model.setRelation(genre_idx, QSqlRelation("genres", "id", "name"))
+
+ # Set the localized header captions:
+ model.setHeaderData(author_idx, Qt.Horizontal, self.tr("Author Name"))
+ model.setHeaderData(genre_idx, Qt.Horizontal, self.tr("Genre"))
+ model.setHeaderData(model.fieldIndex("title"), Qt.Horizontal, self.tr("Title"))
+ model.setHeaderData(model.fieldIndex("year"), Qt.Horizontal, self.tr("Year"))
+ model.setHeaderData(model.fieldIndex("rating"), Qt.Horizontal, self.tr("Rating"))
+
+ if not model.select():
+ print(model.lastError())
+
+ # Set the model and hide the ID column:
+ self.bookTable.setModel(model)
+ self.bookTable.setItemDelegate(BookDelegate(self.bookTable))
+ self.bookTable.setColumnHidden(model.fieldIndex("id"), True)
+ self.bookTable.setSelectionMode(QAbstractItemView.SingleSelection)
+
+ # Initialize the Author combo box:
+ self.authorEdit.setModel(model.relationModel(author_idx))
+ self.authorEdit.setModelColumn(model.relationModel(author_idx).fieldIndex("name"))
+
+ self.genreEdit.setModel(model.relationModel(genre_idx))
+ self.genreEdit.setModelColumn(model.relationModel(genre_idx).fieldIndex("name"))
+
+ # Lock and prohibit resizing of the width of the rating column:
+ self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"),
+ QHeaderView.ResizeToContents)
+
+ mapper = QDataWidgetMapper(self)
+ mapper.setModel(model)
+ mapper.setItemDelegate(BookDelegate(self))
+ mapper.addMapping(self.titleEdit, model.fieldIndex("title"))
+ mapper.addMapping(self.yearEdit, model.fieldIndex("year"))
+ mapper.addMapping(self.authorEdit, author_idx)
+ mapper.addMapping(self.genreEdit, genre_idx)
+ mapper.addMapping(self.ratingEdit, model.fieldIndex("rating"))
+
+ selection_model = self.bookTable.selectionModel()
+ selection_model.currentRowChanged.connect(mapper.setCurrentModelIndex)
+
+ self.bookTable.setCurrentIndex(model.index(0, 0))
+ self.create_menubar()
+
+ def showError(err):
+ QMessageBox.critical(self, "Unable to initialize Database",
+ "Error initializing database: " + err.text())
+
+ def create_menubar(self):
+ file_menu = self.menuBar().addMenu(self.tr("&File"))
+ quit_action = file_menu.addAction(self.tr("&Quit"))
+ quit_action.triggered.connect(qApp.quit)
+
+ help_menu = self.menuBar().addMenu(self.tr("&Help"))
+ about_action = help_menu.addAction(self.tr("&About"))
+ about_action.setShortcut(QKeySequence.HelpContents)
+ about_action.triggered.connect(self.about)
+ aboutQt_action = help_menu.addAction("&About Qt")
+ aboutQt_action.triggered.connect(qApp.aboutQt)
+
+ def about(self):
+ QMessageBox.about(self, self.tr("About Books"),
+ self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
+ "with a model/view framework."))
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.ui b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.ui
new file mode 100644
index 000000000..e1668288f
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.ui
@@ -0,0 +1,149 @@
+<ui version="4.0" >
+ <author></author>
+ <comment></comment>
+ <exportmacro></exportmacro>
+ <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="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox" >
+ <property name="title" >
+ <string>Books</string>
+ </property>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</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" >
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label_5" >
+ <property name="text" >
+ <string>&lt;b>Title:&lt;/b></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_2_2_2" >
+ <property name="text" >
+ <string>&lt;b>Author: &lt;/b></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>Genre:&lt;/b></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>Year:&lt;/b></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="maximum" >
+ <number>2100</number>
+ </property>
+ <property name="minimum" >
+ <number>-1000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" >
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>&lt;b>Rating:&lt;/b></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>
+ <pixmapfunction></pixmapfunction>
+ <tabstops>
+ <tabstop>bookTable</tabstop>
+ <tabstop>titleEdit</tabstop>
+ <tabstop>authorEdit</tabstop>
+ <tabstop>genreEdit</tabstop>
+ <tabstop>yearEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst b/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst
new file mode 100644
index 000000000..98d4d3982
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/chapter3.rst
@@ -0,0 +1,121 @@
+Chapter 3: Port ``bookdwindow.cpp`` to ``bookwindow.py``
+*********************************************************
+
+After the bookdelegate, port the C++ code for the
+``BookWindow`` class. It offers a QMainWindow, containing a
+``QTableView`` to present the books data, and a **Details**
+section with a set of input fields to edit the selected row
+in the table. To begin with, create the ``bookwindow.py``
+and add the following imports to it:
+
+.. literalinclude:: bookwindow.py
+ :language: python
+ :linenos:
+ :lines: 3-16
+
+.. note:: The imports include the ``BookDelegate`` you
+ ported earlier and the ``Ui_BookWindow``. The pyside-uic
+ tool generates the ``ui_bookwindow`` Python code based
+ on the ``bookwindow.ui`` XML file.
+
+To generate this Python code, run the following command on the
+prompt:
+
+.. code-block:: bash
+
+ pyside6-uic bookwindow.ui -o ui_bookwindow.py
+
+Try porting the remaining code now. To begin with, here is
+how both the versions of the constructor code looks:
+
+C++ version
+------------
+
+.. literalinclude:: bookwindow.cpp
+ :language: c++
+ :linenos:
+ :lines: 20-103
+
+Python version
+---------------
+
+.. literalinclude:: bookwindow.py
+ :language: python
+ :linenos:
+ :lines: 16-79
+
+.. note:: The Python version of the ``BookWindow`` class
+ definition inherits from both ``QMainWindow`` and
+ ``Ui_BookWindow``, which is defined in the
+ ``ui_bookwindow.py`` file that you generated earlier.
+
+Here is how the rest of the code looks like:
+
+C++ version
+------------
+
+.. literalinclude:: bookwindow.cpp
+ :language: c++
+ :linenos:
+ :lines: 78-
+
+Python version
+---------------
+
+.. literalinclude:: bookwindow.py
+ :language: python
+ :linenos:
+ :lines: 80-
+
+Now that all the necessary pieces are in place, try to put
+them together in ``main.py``.
+
+.. literalinclude:: main.py
+ :language: python
+ :linenos:
+ :lines: 3-
+
+Try running this to see if you get the following output:
+
+.. image:: images/chapter3-books.png
+ :alt: BookWindow with a QTableView and a few input fields
+
+Now, if you look back at :doc:`chapter2 <../chapter2/chapter2>`,
+you'll notice that the ``bookdelegate.py`` loads the
+``star.png`` from the filesytem. Instead, you could add it
+to a ``qrc`` file, and load from it. The later approach is
+rececommended if your application is targeted for
+different platforms, as most of the popular platforms
+employ stricter file access policy these days.
+
+To add the ``star.png`` to a ``.qrc``, create a file called
+``books.qrc`` and the following XML content to it:
+
+.. literalinclude:: books.qrc
+ :linenos:
+
+This is a simple XML file defining a list all resources that
+your application needs. In this case, it is the ``star.png``
+image only.
+
+Now, run the ``pyside6-rcc`` tool on the ``books.qrc`` file
+to generate ``rc_books.py``.
+
+.. code-block:: bash
+
+ pyside6-rcc books.qrc -o rc_books.py
+
+Once you have the Python script generated, make the
+following changes to ``bookdelegate.py`` and ``main.py``:
+
+.. literalinclude:: bookdelegate.py
+ :diff: ../chapter2/bookdelegate.py
+
+.. literalinclude:: main.py
+ :diff: main-old.py
+
+Although there will be no noticeable difference in the UI
+after these changes, using a ``.qrc`` is a better approach.
+
+Now that you have successfully ported the SQL Books example,
+you know how easy it is. Try porting another C++ application.
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/createdb.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/createdb.py
new file mode 100644
index 000000000..da7d201a8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/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/chapter3/images/chapter3-books.png b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/chapter3-books.png
new file mode 100644
index 000000000..952cb14e8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/chapter3-books.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.png b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.png
new file mode 100644
index 000000000..87f4464bd
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/main-old.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/main-old.py
new file mode 100644
index 000000000..164fc589a
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/main-old.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import QApplication
+from bookwindow import BookWindow
+
+if __name__ == "__main__":
+ app = QApplication([])
+
+ window = BookWindow()
+ window.resize(800, 600)
+ window.show()
+
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/main.py b/sources/pyside6/doc/tutorials/portingguide/chapter3/main.py
new file mode 100644
index 000000000..9a6575dc2
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/main.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from PySide6.QtWidgets import QApplication
+from bookwindow import BookWindow
+import rc_books
+
+if __name__ == "__main__":
+ app = QApplication([])
+
+ window = BookWindow()
+ window.resize(800, 600)
+ window.show()
+
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/portingguide/hello_world_ex.py b/sources/pyside6/doc/tutorials/portingguide/hello_world_ex.py
new file mode 100644
index 000000000..d67906fa7
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/hello_world_ex.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+import random
+
+from PySide6.QtWidgets import (QApplication, QLabel,
+ QPushButton, QVBoxLayout, QWidget)
+from PySide6.QtCore import Qt, Slot
+
+class MyWidget(QWidget):
+ def __init__(self):
+ super().__init__()
+
+ self.hello = ["Hallo Welt", "Hei maailma", "Hola Mundo", "Привет мир"]
+
+ self.button = QPushButton("Click me!")
+ self.text = QLabel("Hello World")
+ self.text.setAlignment(Qt.AlignCenter)
+
+ self.layout = QVBoxLayout()
+ self.layout.addWidget(self.text)
+ self.layout.addWidget(self.button)
+ self.setLayout(self.layout)
+
+ self.button.clicked.connect(self.magic)
+
+ @Slot()
+ def magic(self):
+ self.text.setText(random.choice(self.hello))
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ widget = MyWidget()
+ widget.resize(800, 600)
+ widget.show()
+
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/portingguide/index.rst b/sources/pyside6/doc/tutorials/portingguide/index.rst
new file mode 100644
index 000000000..0b89c2ff8
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/portingguide/index.rst
@@ -0,0 +1,194 @@
+Porting a C++ Application to Python
+*************************************
+
+Qt for Python lets you use Qt APIs in a Python application.
+So the next question is: What does it take to port an
+existing C++ application? Try porting a Qt C++ application
+to Python to understand this.
+
+Before you start, ensure that all the prerequisites for
+Qt for Python are met. See
+:doc:`Getting Started <../../gettingstarted/index>` for more
+information. In addition, familiarize yourself with the
+basic differences between Qt in C++ and in Python.
+
+Basic differences
+==================
+
+This section highlights some of the basic differences
+between C++ and Python, and how Qt differs between these
+two contexts.
+
+C++ vs Python
+--------------
+
+* In the interest of code reuse, both C++ and Python
+ provide ways for one file of code to use facilities
+ provided by another. In C++, this is done using the
+ ``#include`` directive to access the API definition of
+ the reused code. The Python equivalent is an ``import``
+ statement.
+* The constructor of a C++ class shares the name of its
+ class and automatically calls the constructor of any
+ base-classes (in a predefined order) before it runs.
+ In Python, the ``__init__()`` method is the constructor
+ of the class, and it can explicitly call base-class
+ constructors in any order.
+* C++ uses the keyword, ``this``, to implicitly refer to
+ the current object. In python, you need to explicitly
+ mention the current object as the first parameter
+ to each instance method of the class; it is conventionally
+ named ``self``.
+* And more importantly, forget about curly braces, {}, and
+ semi-colon, ;.
+* Precede variable definitions with the ``global`` keyword,
+ only if they need global scope.
+
+.. code:: python
+
+ var = None
+ def func(key, value = None):
+ """Does stuff with a key and an optional value.
+
+ If value is omitted or None, the value from func()'s
+ last call is reused.
+ """
+ global var
+ if value is None:
+ if var is None:
+ raise ValueError("Must pass a value on first call", key, value)
+ value = var
+ else:
+ var = value
+ doStuff(key, value)
+
+In this example, ``func()`` would treat ``var`` as a local
+name without the ``global`` statement. This would lead to
+a ``NameError`` in the ``value is None`` handling, on
+accessing ``var``. For more information about this, see
+`Python refernce documentation <python refdoc>`_.
+
+.. _python refdoc: https://docs.python.org/3/reference/simple_stmts.html#the-global-statement
+
+.. tip:: Python being an interpreted language, most often
+ the easiest way is to try your idea in the interperter.
+ You could call the ``help()`` function in the
+ interpreter on any built-in function or keyword in
+ Python. For example, a call to ``help(import)`` should
+ provide documentation about the ``import`` statment
+
+Last but not the least, try out a few examples to
+familiarize yourself with the Python coding style and
+follow the guidelines outlined in the
+`PEP8 - Style Guide <pep8>`_.
+
+.. _pep8: <https://www.python.org/dev/peps/pep-0008/#naming-conventions>
+
+.. code-block:: python
+
+ import sys
+
+ from PySide6.QtWidgets import QApplication, QLabel
+
+ app = QApplication(sys.argv)
+ label = QLabel("Hello World")
+ label.show()
+ sys.exit(app.exec())
+
+.. note:: Qt provides classes that are meant to manage
+ the application-specific requirements depending on
+ whether the application is console-only
+ (QCoreApplication), GUI with QtWidgets (QApplication),
+ or GUI without QtWidgets (QGuiApplication). These
+ classes load necessary plugins, such as the GUI
+ libraries required by an application. In this case, it is
+ QApplication that is initialized first as the application
+ has a GUI with QtWidgets.
+
+Qt in the C++ and Python context
+---------------------------------
+
+Qt behaves the same irrespective of whether it is used
+in a C++ or a Python application. Considering that C++
+and Python use different language semantics, some
+differences between the two variants of Qt are inevitable.
+Here are a few important ones that you must be aware of:
+
+* **Qt Properties**: ``Q_PROPERTY`` macros are used in C++ to add a
+ public member variable with getter and setter functions. Python's
+ alternative for this is the ``@property`` decorator before the
+ getter and setter function definitions.
+* **Qt Signals and Slots**: Qt offers a unique callback mechanism,
+ where a signal is emitted to notify the occurrence of an event, so
+ that slots connected to this signal can react to it. In C++,
+ the class definition must define the slots under the
+ ``public Q_SLOTS:`` and signals under ``Q_SIGNALS:``
+ access specifier. You connect these two using one of the
+ several variants of the QObject::connect() function. Python's
+ equivalent for this is the `@Slot`` decorator just before the
+ function definition. This is necessary to register the slots
+ with the QtMetaObject.
+* **QString, QVariant, and other types**
+
+ - Qt for Python does not provide access to QString and
+ QVariant. You must use Python's native types instead.
+ - QChar and QStringRef are represented as Python strings,
+ and QStringList is converted to a list of strings.
+ - QDate, QDateTime, QTime, and QUrl's __hash__() methods
+ return a string representation so that identical dates
+ (and identical date/times or times or URLs) have
+ identical hash values.
+ - QTextStream's bin(), hex(), and oct() functions are
+ renamed to bin_(), hex_(), and oct_() respectively. This
+ should avoid name conflicts with Python's built-in
+ functions.
+
+* **QByteArray**: A QByteArray is treated as a list of
+ bytes without encoding. Python 3 uses
+ "bytes". QString is represented as an encoded human readable string,
+ which means it is a "str".
+
+Here is the improved version of the Hello World example,
+demonstrating some of these differences:
+
+.. literalinclude:: hello_world_ex.py
+ :linenos:
+ :lines: 3-
+
+.. note:: The ``if`` block is just a good practice when
+ developing a Python application. It lets the Python file
+ behave differently depending on whether it is imported
+ as a module in another file or run directly. The
+ ``__name__`` variable will have different values in
+ these two scenarios. It is ``__main__`` when the file is
+ run directly, and the module's file name
+ (``hello_world_ex`` in this case) when imported as a
+ module. In the later case, everything defined in the
+ module except the ``if`` block is available to the
+ importing file.
+
+Notice that the QPushButton's ``clicked`` signal is
+connected to the ``magic`` function to randomly change the
+QLabel's ``text`` property. The `@Slot`` decorator marks
+the methods that are slots and informs the QtMetaObject about
+them.
+
+Porting a Qt C++ example
+=========================
+
+Qt offers several C++ examples to showcase its features and help
+beginners learn. You can try porting one of these C++ examples to
+Python. The
+`books SQL example <https://code.qt.io/cgit/qt/qtbase.git/tree/examples/sql/books>`_
+is a good starting point as it does not require you to write UI-specific code in
+Python, but can use its ``.ui`` file instead.
+
+The following chapters guides you through the porting process:
+
+.. toctree::
+ :glob:
+ :titlesonly:
+
+ chapter1/chapter1
+ chapter2/chapter2
+ chapter3/chapter3
diff --git a/sources/pyside6/doc/tutorials/qmlapp/logo.png b/sources/pyside6/doc/tutorials/qmlapp/logo.png
new file mode 100644
index 000000000..30c621c9c
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/logo.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/main.py b/sources/pyside6/doc/tutorials/qmlapp/main.py
new file mode 100644
index 000000000..8b1b25440
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/main.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+import urllib.request
+import json
+from pathlib import Path
+
+from PySide6.QtQuick import QQuickView
+from PySide6.QtCore import QStringListModel, QUrl
+from PySide6.QtGui import QGuiApplication
+
+
+if __name__ == '__main__':
+
+ #get our data
+ url = "http://country.io/names.json"
+ response = urllib.request.urlopen(url)
+ data = json.loads(response.read().decode('utf-8'))
+
+ #Format and sort the data
+ data_list = list(data.values())
+ data_list.sort()
+
+ #Set up the application window
+ app = QGuiApplication(sys.argv)
+ view = QQuickView()
+ view.setResizeMode(QQuickView.SizeRootObjectToView)
+
+ #Expose the list to the Qml code
+ my_model = QStringListModel()
+ my_model.setStringList(data_list)
+ view.setInitialProperties({"myModel": my_model})
+
+ #Load the QML file
+ qml_file = Path(__file__).parent / "view.qml"
+ view.setSource(QUrl.fromLocalFile(qml_file.resolve()))
+
+ #Show the window
+ if view.status() == QQuickView.Error:
+ sys.exit(-1)
+ view.show()
+
+ #execute and cleanup
+ app.exec()
+ del view
diff --git a/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png b/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png
new file mode 100644
index 000000000..93968a52d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png
new file mode 100644
index 000000000..c66d88723
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png
new file mode 100644
index 000000000..98328074d
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png
new file mode 100644
index 000000000..e01e2ebeb
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.png b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.png
new file mode 100644
index 000000000..ec0ad3dea
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst
new file mode 100644
index 000000000..c6d72e742
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst
@@ -0,0 +1,132 @@
+#########################
+QML Application Tutorial
+#########################
+
+This tutorial provides a quick walk-through of a python application
+that loads a QML file. QML is a declarative language that lets you
+design UIs faster than a traditional language, such as C++. The
+QtQml and QtQuick modules provides the necessary infrastructure for
+QML-based UIs.
+
+In this tutorial, you'll also learn how to provide data from Python
+as a QML initial property, which is then consumed by the ListView
+defined in the QML file.
+
+Before you begin, install the following prerequisites:
+
+* The `PySide6 <https://pypi.org/project/PySide6/>`_ Python packages.
+* *Qt Creator* from
+ `https://download.qt.io
+ <https://download.qt.io/snapshots/qtcreator/>`_.
+
+
+The following step-by-step instructions guide you through application
+development process using *Qt Creator*:
+
+#. Open *Qt Creator* and select **File > New File or Project..** menu item
+ to open following dialog:
+
+ .. image:: newpyproject.png
+
+#. Select **Qt for Python - Empty** from the list of application templates
+ and select **Choose**.
+
+ .. image:: pyprojname.png
+
+#. Give a **Name** to your project, choose its location in the
+ filesystem, and select **Finish** to create an empty ``main.py``
+ and ``main.pyproject``.
+
+ .. image:: pyprojxplor.png
+
+ This should create a ``main.py`` and ```main.pyproject`` files
+ for the project.
+
+#. Download :download:`view.qml<view.qml>` and :download:`logo.png <logo.png>`
+ and move them to your project folder.
+
+#. Double-click on ``main.pyproject`` to open it in edit mode, and append
+ ``view.qml`` and ``logo.png`` to the **files** list. This is how your
+ project file should look after this change:
+
+ .. code::
+
+ {
+ "files": ["main.py", "view.qml", "logo.png"]
+ }
+
+#. Now that you have the necessary bits for the application, import the
+ Python modules in your ``main.py``, and download country data and
+ format it:
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 3-23
+ :emphasize-lines: 7-9,14-17
+
+#. Now, set up the application window using
+ :ref:`PySide6.QtGui.QGuiApplication<qguiapplication>`, which manages the application-wide
+ settings.
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 3-28
+ :emphasize-lines: 23-25
+
+ .. note:: Setting the resize policy is important if you want the
+ root item to resize itself to fit the window or vice-a-versa.
+ Otherwise, the root item will retain its original size on
+ resizing the window.
+
+#. You can now expose the ``data_list`` variable as a QML initial
+ property, which will be consumed by the QML ListView item in ``view.qml``.
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 3-33
+ :emphasize-lines: 28-31
+
+#. Load the ``view.qml`` to the ``QQuickView`` and call ``show()`` to
+ display the application window.
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 3-42
+ :emphasize-lines: 33-40
+
+#. Finally, execute the application to start the event loop and clean up.
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 3-
+ :emphasize-lines: 42-44
+
+#. Your application is ready to be run now. Select **Projects** mode to
+ choose the Python version to run it.
+
+ .. image:: projectsmode.png
+
+Run the application by using the ``CTRL+R`` keyboard shortcut to see if it
+looks like this:
+
+.. image:: qmlapplication.png
+
+You could also watch the following video tutorial for guidance to develop
+this application:
+
+.. raw:: html
+
+ <div style="position: relative; padding-bottom: 56.25%; height: 0;
+ overflow: hidden; max-width: 100%; height: auto;">
+ <iframe src="https://www.youtube.com/embed/JxfiUx60Mbg" frameborder="0"
+ allowfullscreen style="position: absolute; top: 0; left: 0;
+ width: 100%; height: 100%;">
+ </iframe>
+ </div>
+
+********************
+Related information
+********************
+
+* `QML Reference <https://doc.qt.io/qt-5/qmlreference.html>`_
+* :doc:`../qmlintegration/qmlintegration`
diff --git a/sources/pyside6/doc/tutorials/qmlapp/view.qml b/sources/pyside6/doc/tutorials/qmlapp/view.qml
new file mode 100644
index 000000000..7f9b1d777
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlapp/view.qml
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+Page {
+ width: 640
+ height: 480
+ required property var myModel
+
+ header: Label {
+ color: "#15af15"
+ text: qsTr("Where do people use Qt?")
+ font.pointSize: 17
+ font.bold: true
+ font.family: "Arial"
+ renderType: Text.NativeRendering
+ horizontalAlignment: Text.AlignHCenter
+ padding: 10
+ }
+ Rectangle {
+ id: root
+ width: parent.width
+ height: parent.height
+
+ Image {
+ id: image
+ fillMode: Image.PreserveAspectFit
+ anchors.centerIn: root
+ source: "./logo.png"
+ opacity: 0.5
+
+ }
+
+ ListView {
+ id: view
+ anchors.fill: root
+ anchors.margins: 25
+ model: myModel
+ delegate: Text {
+ anchors.leftMargin: 50
+ font.pointSize: 15
+ horizontalAlignment: Text.AlignHCenter
+ text: display
+ }
+ }
+ }
+ NumberAnimation {
+ id: anim
+ running: true
+ target: view
+ property: "contentY"
+ duration: 500
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/main.py b/sources/pyside6/doc/tutorials/qmlintegration/main.py
new file mode 100644
index 000000000..0a751d7d1
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/main.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QObject, Slot
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine, QmlElement
+from PySide6.QtQuickControls2 import QQuickStyle
+
+import style_rc
+
+# To be used on the @QmlElement decorator
+# (QML_IMPORT_MINOR_VERSION is optional)
+QML_IMPORT_NAME = "io.qt.textproperties"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class Bridge(QObject):
+
+ @Slot(str, result=str)
+ def getColor(self, s):
+ if s.lower() == "red":
+ return "#ef9a9a"
+ elif s.lower() == "green":
+ return "#a5d6a7"
+ elif s.lower() == "blue":
+ return "#90caf9"
+ else:
+ return "white"
+
+ @Slot(float, result=int)
+ def getSize(self, s):
+ size = int(s * 34)
+ if size <= 0:
+ return 1
+ else:
+ return size
+
+ @Slot(str, result=bool)
+ def getItalic(self, s):
+ if s.lower() == "italic":
+ return True
+ else:
+ return False
+
+ @Slot(str, result=bool)
+ def getBold(self, s):
+ if s.lower() == "bold":
+ return True
+ else:
+ return False
+
+ @Slot(str, result=bool)
+ def getUnderline(self, s):
+ if s.lower() == "underline":
+ return True
+ else:
+ return False
+
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ QQuickStyle.setStyle("Material")
+ engine = QQmlApplicationEngine()
+
+ # Get the path of the current directory, and then add the name
+ # of the QML file, to load it.
+ qml_file = Path(__file__).parent / 'view.qml'
+ engine.load(qml_file)
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ sys.exit(app.exec())
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst b/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst
new file mode 100644
index 000000000..ff6fe3e31
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst
@@ -0,0 +1,128 @@
+Python-QML integration
+======================
+
+This tutorial provides a quick walk-through of a python application that loads, and interacts with
+a QML file. QML is a declarative language that lets you design UIs faster than a traditional
+language, such as C++. The QtQml and QtQuick modules provides the necessary infrastructure for
+QML-based UIs.
+
+In this tutorial, you will learn how to integrate Python with a QML application.
+This mechanism will help us to understand how to use Python as a backend for certain
+signals from the UI elements in the QML interface. Additionally, you will learn how to provide
+a modern look to your QML application using one of the features from Qt Quick Controls 2.
+
+The tutorial is based on an application that allow you to set many text properties, like increasing
+the font size, changing the color, changing the style, and so on. Before you begin, install the
+`PySide6 <https://pypi.org/project/PySide6/>`_ Python packages.
+
+The following step-by-step process will guide you through the key elements of the QML based
+application and PySide6 integration:
+
+#. First, let's start with the following QML-based UI:
+
+ .. image:: textproperties_default.png
+
+ The design is based on a `GridLayout`, containing two `ColumnLayout`.
+ Inside the UI you will find many `RadioButton`, `Button`, and a `Slider`.
+
+#. With the QML file in place, you can load it from Python:
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 63-76
+ :emphasize-lines: 4,9
+
+ Notice that we only need a :code:`QQmlApplicationEngine` to
+ :code:`load` the QML file.
+
+#. Define the ``Bridge`` class, containing all the logic for the element
+ that will be register in QML:
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 14-54
+ :emphasize-lines: 3,4,7
+
+ Notice that the registration happens thanks to the :code:`QmlElement`
+ decorator, that underneath uses the reference to the :code:`Bridge`
+ class and the variables :code:`QML_IMPORT_NAME` and
+ :code:`QML_IMPORT_MAJOR_VERSION`.
+
+#. Now, go back to the QML file and connect the signals to the slots defined in the ``Bridge`` class:
+
+ .. code:: js
+
+ Bridge {
+ id: bridge
+ }
+
+ Inside the :code:`ApplicationWindow` we declare a component
+ with the same name as the Python class, and provide an :code:`id:`.
+ This :code:`id` will help you to get a reference to the element
+ that was registered from Python.
+
+ .. literalinclude:: view.qml
+ :linenos:
+ :lines: 45-55
+ :emphasize-lines: 6-8
+
+ The properties *Italic*, *Bold*, and *Underline* are mutually
+ exclusive, this means only one can be active at any time.
+ To achieve this each time we select one of these options, we
+ check the three properties via the QML element property as you can
+ see in the above snippet.
+ Only one of the three will return *True*, while the other two
+ will return *False*, that is how we make sure only one is being
+ applied to the text.
+
+#. Each slot verifies if the selected option contains the text associated
+ to the property:
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 42-47
+ :emphasize-lines: 4,6
+
+ Returning *True* or *False* allows you to activate and deactivate
+ the properties of the QML UI elements.
+
+ It is also possible to return other values that are not *Boolean*,
+ like the slot in charge of returning the font size:
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 34-39
+
+#. Now, for changing the look of our application, you have two options:
+
+ 1. Use the command line: execute the python file adding the option, ``--style``::
+
+ python main.py --style material
+
+ 2. Use a ``qtquickcontrols2.conf`` file:
+
+ .. literalinclude:: qtquickcontrols2.conf
+ :linenos:
+
+ Then add it to your ``.qrc`` file:
+
+ .. literalinclude:: style.qrc
+ :linenos:
+
+ Generate the *rc* file running, ``pyside6-rcc style.qrc -o style_rc.py``
+ And finally import it from your ``main.py`` script.
+
+ .. literalinclude:: main.py
+ :linenos:
+ :lines: 4-12
+ :emphasize-lines: 9
+
+ You can read more about this configuration file
+ `here <https://doc.qt.io/qt-5/qtquickcontrols2-configuration.html>`_.
+
+ The final look of your application will be:
+
+ .. image:: textproperties_material.png
+
+You can :download:`view.qml <view.qml>` and
+:download:`main.py <main.py>` to try this example.
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/qtquickcontrols2.conf b/sources/pyside6/doc/tutorials/qmlintegration/qtquickcontrols2.conf
new file mode 100644
index 000000000..850646021
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/qtquickcontrols2.conf
@@ -0,0 +1,10 @@
+[Controls]
+Style=Material
+
+[Universal]
+Theme=System
+Accent=Red
+
+[Material]
+Theme=Dark
+Accent=Red
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/style.qrc b/sources/pyside6/doc/tutorials/qmlintegration/style.qrc
new file mode 100644
index 000000000..e313f5ed6
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/style.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/">
+ <file>qtquickcontrols2.conf</file>
+</qresource>
+</RCC>
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/textproperties_default.png b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_default.png
new file mode 100644
index 000000000..cfeac9368
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_default.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.png b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.png
new file mode 100644
index 000000000..47866c10e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlintegration/view.qml b/sources/pyside6/doc/tutorials/qmlintegration/view.qml
new file mode 100644
index 000000000..635603fac
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlintegration/view.qml
@@ -0,0 +1,160 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+import QtQuick 2.0
+import QtQuick.Layouts 1.11
+import QtQuick.Controls 2.1
+import QtQuick.Window 2.1
+import QtQuick.Controls.Material 2.1
+
+import io.qt.textproperties 1.0
+
+ApplicationWindow {
+ id: page
+ width: 800
+ height: 400
+ visible: true
+ Material.theme: Material.Dark
+ Material.accent: Material.Red
+
+ Bridge {
+ id: bridge
+ }
+
+ GridLayout {
+ id: grid
+ columns: 2
+ rows: 3
+
+ ColumnLayout {
+ spacing: 2
+ Layout.columnSpan: 1
+ Layout.preferredWidth: 400
+
+ Text {
+ id: leftlabel
+ Layout.alignment: Qt.AlignHCenter
+ color: "white"
+ font.pointSize: 16
+ text: "Qt for Python"
+ Layout.preferredHeight: 100
+ Material.accent: Material.Green
+ }
+
+ RadioButton {
+ id: italic
+ Layout.alignment: Qt.AlignLeft
+ text: "Italic"
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(italic.text)
+ leftlabel.font.bold = bridge.getBold(italic.text)
+ leftlabel.font.underline = bridge.getUnderline(italic.text)
+
+ }
+ }
+ RadioButton {
+ id: bold
+ Layout.alignment: Qt.AlignLeft
+ text: "Bold"
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(bold.text)
+ leftlabel.font.bold = bridge.getBold(bold.text)
+ leftlabel.font.underline = bridge.getUnderline(bold.text)
+ }
+ }
+ RadioButton {
+ id: underline
+ Layout.alignment: Qt.AlignLeft
+ text: "Underline"
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(underline.text)
+ leftlabel.font.bold = bridge.getBold(underline.text)
+ leftlabel.font.underline = bridge.getUnderline(underline.text)
+ }
+ }
+ RadioButton {
+ id: noneradio
+ Layout.alignment: Qt.AlignLeft
+ text: "None"
+ checked: true
+ onToggled: {
+ leftlabel.font.italic = bridge.getItalic(noneradio.text)
+ leftlabel.font.bold = bridge.getBold(noneradio.text)
+ leftlabel.font.underline = bridge.getUnderline(noneradio.text)
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: rightcolumn
+ spacing: 2
+ Layout.columnSpan: 1
+ Layout.preferredWidth: 400
+ Layout.preferredHeight: 400
+ Layout.fillWidth: true
+
+ RowLayout {
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+
+
+ Button {
+ id: red
+ text: "Red"
+ highlighted: true
+ Material.accent: Material.Red
+ onClicked: {
+ leftlabel.color = bridge.getColor(red.text)
+ }
+ }
+ Button {
+ id: green
+ text: "Green"
+ highlighted: true
+ Material.accent: Material.Green
+ onClicked: {
+ leftlabel.color = bridge.getColor(green.text)
+ }
+ }
+ Button {
+ id: blue
+ text: "Blue"
+ highlighted: true
+ Material.accent: Material.Blue
+ onClicked: {
+ leftlabel.color = bridge.getColor(blue.text)
+ }
+ }
+ Button {
+ id: nonebutton
+ text: "None"
+ highlighted: true
+ Material.accent: Material.BlueGrey
+ onClicked: {
+ leftlabel.color = bridge.getColor(nonebutton.text)
+ }
+ }
+ }
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+ Text {
+ id: rightlabel
+ color: "white"
+ Layout.alignment: Qt.AlignLeft
+ text: "Font size"
+ Material.accent: Material.White
+ }
+ Slider {
+ width: rightcolumn.width*0.6
+ Layout.alignment: Qt.AlignRight
+ id: slider
+ value: 0.5
+ onValueChanged: {
+ leftlabel.font.pointSize = bridge.getSize(value)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml b/sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml
new file mode 100644
index 000000000..da58ae9b2
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml
@@ -0,0 +1,98 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import ChatModel
+
+ApplicationWindow {
+ id: window
+ title: qsTr("Chat")
+ width: 640
+ height: 960
+ visible: true
+
+ SqlConversationModel {
+ id: chat_model
+ }
+
+ ColumnLayout {
+ anchors.fill: window
+
+ ListView {
+ id: listView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: pane.leftPadding + messageField.leftPadding
+ displayMarginBeginning: 40
+ displayMarginEnd: 40
+ verticalLayoutDirection: ListView.BottomToTop
+ spacing: 12
+ model: chat_model
+ delegate: Column {
+ anchors.right: sentByMe ? listView.contentItem.right : undefined
+ spacing: 6
+
+ readonly property bool sentByMe: model.recipient !== "Me"
+ Row {
+ id: messageRow
+ spacing: 6
+ anchors.right: sentByMe ? parent.right : undefined
+
+ Rectangle {
+ width: Math.min(messageText.implicitWidth + 24,
+ listView.width - (!sentByMe ? messageRow.spacing : 0))
+ height: messageText.implicitHeight + 24
+ radius: 15
+ color: sentByMe ? "lightgrey" : "steelblue"
+
+ Label {
+ id: messageText
+ text: model.message
+ color: sentByMe ? "black" : "white"
+ anchors.fill: parent
+ anchors.margins: 12
+ wrapMode: Label.Wrap
+ }
+ }
+ }
+
+ Label {
+ id: timestampText
+ text: Qt.formatDateTime(model.timestamp, "d MMM hh:mm")
+ color: "lightgrey"
+ anchors.right: sentByMe ? parent.right : undefined
+ }
+ }
+
+ ScrollBar.vertical: ScrollBar {}
+ }
+
+ Pane {
+ id: pane
+ Layout.fillWidth: true
+
+ RowLayout {
+ width: parent.width
+
+ TextArea {
+ id: messageField
+ Layout.fillWidth: true
+ placeholderText: qsTr("Compose message")
+ wrapMode: TextArea.Wrap
+ }
+
+ Button {
+ id: sendButton
+ text: qsTr("Send")
+ enabled: messageField.length > 0
+ onClicked: {
+ listView.model.send_message("machine", messageField.text, "Me");
+ messageField.text = "";
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/example_list_view.png b/sources/pyside6/doc/tutorials/qmlsqlintegration/example_list_view.png
new file mode 100644
index 000000000..a0c189665
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/example_list_view.png
Binary files differ
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/main.py b/sources/pyside6/doc/tutorials/qmlsqlintegration/main.py
new file mode 100644
index 000000000..314fd5aa5
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/main.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import sys
+import logging
+
+from PySide6.QtCore import QDir, QFile, QUrl
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtSql import QSqlDatabase
+
+# We import the file just to trigger the QmlElement type registration.
+import sqlDialog
+
+logging.basicConfig(filename="chat.log", level=logging.DEBUG)
+logger = logging.getLogger("logger")
+
+
+def connectToDatabase():
+ database = QSqlDatabase.database()
+ if not database.isValid():
+ database = QSqlDatabase.addDatabase("QSQLITE")
+ if not database.isValid():
+ logger.error("Cannot add database")
+
+ write_dir = QDir("")
+ if not write_dir.mkpath("."):
+ logger.error("Failed to create writable directory")
+
+ # Ensure that we have a writable location on all devices.
+ abs_path = write_dir.absolutePath()
+ filename = f"{abs_path}/chat-database.sqlite3"
+
+ # When using the SQLite driver, open() will create the SQLite
+ # database if it doesn't exist.
+ database.setDatabaseName(filename)
+ if not database.open():
+ logger.error("Cannot open database")
+ QFile.remove(filename)
+
+
+if __name__ == "__main__":
+ app = QGuiApplication()
+ connectToDatabase()
+
+ engine = QQmlApplicationEngine()
+ engine.load(QUrl("chat.qml"))
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ app.exec()
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst
new file mode 100644
index 000000000..eee3f807e
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst
@@ -0,0 +1,232 @@
+QML, SQL and PySide Integration Tutorial
+########################################
+
+This tutorial is very similar to the `Qt Chat Tutorial`_ one but it focuses on explaining how to
+integrate a SQL database into a PySide6 application using QML for its UI.
+
+.. _`Qt Chat Tutorial`: https://doc.qt.io/qt-6/qtquickcontrols-chattutorial-example.html
+
+sqlDialog.py
+------------
+
+We import the pertinent libraries to our program, define a global variable that hold the
+name of our table, and define the global function ``createTable()`` that creates a new table if it
+doesn't already exist.
+The database contains a single line to mock the beginning of a conversation.
+
+.. literalinclude:: sqlDialog.py
+ :linenos:
+ :lines: 4-43
+
+The ``SqlConversationModel`` class offers the read-only data model required for the non-editable
+contacts list. It derives from the :ref:`QSqlQueryModel` class, which is the logical choice for
+this use case.
+Then, we proceed to create the table, set its name to the one defined previously with the
+:meth:`~.QSqlTableModel.setTable` method.
+We add the necessary attributes to the table, to have a program that reflects the idea
+of a chat application.
+
+.. literalinclude:: sqlDialog.py
+ :linenos:
+ :lines: 47-59
+
+In ``setRecipient()``, you set a filter over the returned results from the database, and
+emit a signal every time the recipient of the message changes.
+
+.. literalinclude:: sqlDialog.py
+ :linenos:
+ :lines: 61-70
+
+The ``data()`` function falls back to ``QSqlTableModel``'s implementation if the role is not a
+custom user role.
+If you get a user role, we can subtract :meth:`~.QtCore.Qt.UserRole` from it to get the index of
+that field, and then use that index to find the value to be returned.
+
+.. literalinclude:: sqlDialog.py
+ :linenos:
+ :lines: 72-79
+
+
+In ``roleNames()``, we return a Python dictionary with our custom role and role names as key-values
+pairs, so we can use these roles in QML.
+Alternatively, it can be useful to declare an Enum to hold all of the role values.
+Note that ``names`` has to be a hash to be used as a dictionary key,
+and that's why we're using the ``hash`` function.
+
+.. literalinclude:: sqlDialog.py
+ :linenos:
+ :lines: 81-95
+
+The ``send_message()`` function uses the given recipient and message to insert a new record into
+the database.
+Using :meth:`~.QSqlTableModel.OnManualSubmit` requires you to also call ``submitAll()``,
+since all the changes will be cached in the model until you do so.
+
+.. literalinclude:: sqlDialog.py
+ :linenos:
+ :lines: 97-116
+
+chat.qml
+--------
+
+Let's look at the ``chat.qml`` file.
+
+.. literalinclude:: chat.qml
+ :linenos:
+ :lines: 4-6
+
+First, import the Qt Quick module.
+This gives us access to graphical primitives such as Item, Rectangle, Text, and so on.
+For a full list of types, see the `Qt Quick QML Types`_ documentation.
+We then add QtQuick.Layouts import, which we'll cover shortly.
+
+Next, import the Qt Quick Controls module.
+Among other things, this provides access to ``ApplicationWindow``, which replaces the existing
+root type, Window:
+
+Let's step through the ``chat.qml`` file.
+
+.. literalinclude:: chat.qml
+ :linenos:
+ :lines: 9-14
+
+``ApplicationWindow`` is a Window with some added convenience for creating a header and a footer.
+It also provides the foundation for popups and supports some basic styling, such as the background
+color.
+
+There are three properties that are almost always set when using ApplicationWindow: ``width``,
+``height``, and ``visible``.
+Once we've set these, we have a properly sized, empty window ready to be filled with content.
+
+Because we are exposing the :code:`SqlConversationModel` class to QML, we will
+declare a component to access it:
+
+.. literalinclude:: chat.qml
+ :linenos:
+ :lines: 16-18
+
+There are two ways of laying out items in QML: `Item Positioners`_ and `Qt Quick Layouts`_.
+
+- Item positioners (`Row`_, `Column`_, and so on) are useful for situations where the size of items
+ is known or fixed, and all that is required is to neatly position them in a certain formation.
+- The layouts in Qt Quick Layouts can both position and resize items, making them well suited for
+ resizable user interfaces.
+ Below, we use `ColumnLayout`_ to vertically lay out a `ListView`_ and a `Pane`_.
+
+ .. literalinclude:: chat.qml
+ :linenos:
+ :lines: 20-23
+
+ .. literalinclude:: chat.qml
+ :linenos:
+ :lines: 72-74
+
+Pane is basically a rectangle whose color comes from the application's style.
+It's similar to `Frame`_, but it has no stroke around its border.
+
+Items that are direct children of a layout have various `attached properties`_ available to them.
+We use `Layout.fillWidth`_ and `Layout.fillHeight`_ on the `ListView`_ to ensure that it takes as
+much space within the `ColumnLayout`_ as it can, and the same is done for the Pane.
+As `ColumnLayout`_ is a vertical layout, there aren't any items to the left or right of each child,
+so this results in each item consuming the entire width of the layout.
+
+On the other hand, the `Layout.fillHeight`_ statement in the `ListView`_ enables it to occupy the
+remaining space that is left after accommodating the Pane.
+
+.. _Item Positioners: https://doc.qt.io/qt-5/qtquick-positioning-layouts.html
+.. _Qt Quick Layouts: https://doc.qt.io/qt-5/qtquicklayouts-index.html
+.. _Row: https://doc.qt.io/qt-5/qml-qtquick-row.html
+.. _Column: https://doc.qt.io/qt-5/qml-qtquick-column.html
+.. _ColumnLayout: https://doc.qt.io/qt-5/qml-qtquick-layouts-columnlayout.html
+.. _ListView: https://doc.qt.io/qt-5/qml-qtquick-listview.html
+.. _Pane: https://doc.qt.io/qt-5/qml-qtquick-controls2-pane.html
+.. _Frame: https://doc.qt.io/qt-5/qml-qtquick-controls2-frame.html
+.. _attached properties: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html
+.. _Layout.fillWidth: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillWidth-attached-prop
+.. _Layout.fillHeight: https://doc.qt.io/qt-5/qml-qtquick-layouts-layout.html#fillHeight-attached-prop
+.. _Qt Quick QML Types: https://doc.qt.io/qt-5/qtquick-qmlmodule.html
+
+Let's look at the ``Listview`` in detail:
+
+.. literalinclude:: chat.qml
+ :linenos:
+ :lines: 23-70
+
+After filling the ``width`` and ``height`` of its parent, we also set some margins on the view.
+
+Next, we set `displayMarginBeginning`_ and `displayMarginEnd`_.
+These properties ensure that the delegates outside the view don't disappear when you
+scroll at the edges of the view.
+To get a better understanding, consider commenting out the properties and then rerun your code.
+Now watch what happens when you scroll the view.
+
+We then flip the vertical direction of the view, so that first items are at the bottom.
+
+Additionally, messages sent by the contact should be distinguished from those sent by a contact.
+For now, when a message is sent by you, we set a ``sentByMe`` property, to alternate between
+different contacts.
+Using this property, we distinguish between different contacts in two ways:
+
+* Messages sent by the contact are aligned to the right side of the screen by setting
+ ``anchors.right`` to ``parent.right``.
+* We change the color of the rectangle depending on the contact.
+ Since we don't want to display dark text on a dark background, and vice versa, we also set the
+ text color depending on who the contact is.
+
+At the bottom of the screen, we place a `TextArea`_ item to allow multi-line text input, and a
+button to send the message.
+We use Pane to cover the area under these two items:
+
+.. literalinclude:: chat.qml
+ :linenos:
+ :lines: 72-96
+
+The `TextArea`_ should fill the available width of the screen.
+We assign some placeholder text to provide a visual cue to the contact as to where they should begin
+typing.
+The text within the input area is wrapped to ensure that it does not go outside of the screen.
+
+Lastly, we have a button that allows us to call the ``send_message`` method we defined on
+``sqlDialog.py``, since we're just having a mock up example here and there is only one possible
+recipient and one possible sender for this conversation we're just using strings here.
+
+.. _displayMarginBeginning: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginBeginning-prop
+.. _displayMarginEnd: https://doc.qt.io/qt-5/qml-qtquick-listview.html#displayMarginEnd-prop
+.. _TextArea: https://doc.qt.io/qt-5/qml-qtquick-controls2-textarea.html
+
+
+main.py
+-------
+
+We use ``logging`` instead of Python's ``print()``, because it provides a better way to control the
+messages levels that our application will generate (errors, warnings, and information messages).
+
+.. literalinclude:: main.py
+ :linenos:
+ :lines: 4-16
+
+``connectToDatabase()`` creates a connection with the SQLite database, creating the actual file
+if it doesn't already exist.
+
+.. literalinclude:: main.py
+ :linenos:
+ :lines: 19-39
+
+A few interesting things happen in the ``main`` function:
+
+- Declaring a :ref:`QGuiApplication`.
+ You should use a :ref:`QGuiApplication` instead of :ref:`QApplication` because we're not
+ using the **QtWidgets** module.
+- Connecting to the database,
+- Declaring a :ref:`QQmlApplicationEngine`.
+ This allows you to access the QML Elements to connect Python
+ and QML from the conversation model we built on ``sqlDialog.py``.
+- Loading the ``.qml`` file that defines the UI.
+
+Finally, the Qt application runs, and your program starts.
+
+.. literalinclude:: main.py
+ :linenos:
+ :lines: 42-52
+
+.. image:: example_list_view.png
diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/sqlDialog.py b/sources/pyside6/doc/tutorials/qmlsqlintegration/sqlDialog.py
new file mode 100644
index 000000000..d728aee59
--- /dev/null
+++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/sqlDialog.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import datetime
+import logging
+
+from PySide6.QtCore import Qt, Slot
+from PySide6.QtSql import QSqlDatabase, QSqlQuery, QSqlRecord, QSqlTableModel
+from PySide6.QtQml import QmlElement
+
+table_name = "Conversations"
+QML_IMPORT_NAME = "ChatModel"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+def createTable():
+ if table_name in QSqlDatabase.database().tables():
+ return
+
+ query = QSqlQuery()
+ if not query.exec_(
+ """
+ CREATE TABLE IF NOT EXISTS 'Conversations' (
+ 'author' TEXT NOT NULL,
+ 'recipient' TEXT NOT NULL,
+ 'timestamp' TEXT NOT NULL,
+ 'message' TEXT NOT NULL,
+ FOREIGN KEY('author') REFERENCES Contacts ( name ),
+ FOREIGN KEY('recipient') REFERENCES Contacts ( name )
+ )
+ """
+ ):
+ logging.error("Failed to query database")
+
+ # This adds the first message from the Bot
+ # and further development is required to make it interactive.
+ query.exec_(
+ """
+ INSERT INTO Conversations VALUES(
+ 'machine', 'Me', '2019-01-07T14:36:06', 'Hello!'
+ )
+ """
+ )
+ logging.info(query)
+
+
+@QmlElement
+class SqlConversationModel(QSqlTableModel):
+ def __init__(self, parent=None):
+ super(SqlConversationModel, self).__init__(parent)
+
+ createTable()
+ self.setTable(table_name)
+ self.setSort(2, Qt.DescendingOrder)
+ self.setEditStrategy(QSqlTableModel.OnManualSubmit)
+ self.recipient = ""
+
+ self.select()
+ logging.debug("Table was loaded successfully.")
+
+ def setRecipient(self, recipient):
+ if recipient == self.recipient:
+ pass
+
+ self.recipient = recipient
+
+ filter_str = (f"(recipient = '{self.recipient}' AND author = 'Me') OR "
+ f"(recipient = 'Me' AND author='{self.recipient}')")
+ self.setFilter(filter_str)
+ self.select()
+
+ def data(self, index, role):
+ if role < Qt.UserRole:
+ return QSqlTableModel.data(self, index, role)
+
+ sql_record = QSqlRecord()
+ sql_record = self.record(index.row())
+
+ return sql_record.value(role - Qt.UserRole)
+
+ def roleNames(self):
+ """Converts dict to hash because that's the result expected
+ by QSqlTableModel"""
+ names = {}
+ author = "author".encode()
+ recipient = "recipient".encode()
+ timestamp = "timestamp".encode()
+ message = "message".encode()
+
+ names[hash(Qt.UserRole)] = author
+ names[hash(Qt.UserRole + 1)] = recipient
+ names[hash(Qt.UserRole + 2)] = timestamp
+ names[hash(Qt.UserRole + 3)] = message
+
+ return names
+
+ # This is a workaround because PySide doesn't provide Q_INVOKABLE
+ # So we declare this as a Slot to be able to call it from QML
+ @Slot(str, str, str)
+ def send_message(self, recipient, message, author):
+ timestamp = datetime.datetime.now()
+
+ new_record = self.record()
+ new_record.setValue("author", author)
+ new_record.setValue("recipient", recipient)
+ new_record.setValue("timestamp", str(timestamp))
+ new_record.setValue("message", message)
+
+ logging.debug(f'Message: "{message}" \n Received by: "{recipient}"')
+
+ if not self.insertRecord(self.rowCount(), new_record):
+ logging.error("Failed to send message: {self.lastError().text()}")
+ return
+
+ self.submitAll()
+ self.select()