diff options
Diffstat (limited to 'sources/pyside6/doc/tutorials')
112 files changed, 8403 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst new file mode 100644 index 000000000..bd45f1f64 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/clickablebutton.rst @@ -0,0 +1,90 @@ +A Simple Button Tutorial +************************ + +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: +:: + + # Show the button + button.show() + # Run the main Qt loop + app.exec_() + +Here is the complete code for this example: +:: + + #!/usr/bin/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_() diff --git a/sources/pyside6/doc/tutorials/basictutorial/dialog.rst b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst new file mode 100644 index 000000000..0ced571db --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/dialog.rst @@ -0,0 +1,145 @@ +Creating a Simple PySide6 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() + layout.addWidget(self.edit) + layout.addWidget(self.button) + # Set dialog layout + self.setLayout(layout) + +So, we create the layout, add the widgets with `addWidget()`, +and finally we say that our **Form** will have our `QVBoxLayout` +as its layout. + +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 ("Hello {}".format(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: +:: + + 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 ("Hello %s" % 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_()) diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons.png b/sources/pyside6/doc/tutorials/basictutorial/icons.png Binary files differnew file mode 100644 index 000000000..0bcfd7d77 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png b/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png Binary files differnew file mode 100644 index 000000000..c7a532dfe --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/forward.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png b/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png Binary files differnew file mode 100644 index 000000000..d0beadb43 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/pause.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/play.png b/sources/pyside6/doc/tutorials/basictutorial/icons/play.png Binary files differnew file mode 100644 index 000000000..345685337 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/play.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png b/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png Binary files differnew file mode 100644 index 000000000..979f18565 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/previous.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png b/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png Binary files differnew file mode 100644 index 000000000..1e88ded3a --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/icons/stop.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/player-new.png b/sources/pyside6/doc/tutorials/basictutorial/player-new.png Binary files differnew file mode 100644 index 000000000..e1f660e5f --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/player-new.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/player.png b/sources/pyside6/doc/tutorials/basictutorial/player.png Binary files differnew file mode 100644 index 000000000..3060a990d --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/player.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/qml.rst b/sources/pyside6/doc/tutorials/basictutorial/qml.rst new file mode 100644 index 000000000..c972daa00 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/qml.rst @@ -0,0 +1,67 @@ +Your First Application Using PySide6 and QtQuick/QML +***************************************************** + +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 `view.qml`: + +.. code-block:: javascript + + import QtQuick 2.0 + + Rectangle { + width: 200 + height: 200 + color: "green" + + Text { + text: "Hello World" + anchors.centerIn: parent + } + } + +We start by importing `QtQuick 2.0`, 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 `anchors.centerIn: parent` makes +the text appear centered in relation to its immediate parent, which +is the Rectangle in this case. + +Now, let's see how the code looks on the PySide6. +Let's call it `main.py`: + +.. code-block:: python + + from PySide6.QtWidgets import QApplication + from PySide6.QtQuick import QQuickView + from PySide6.QtCore import QUrl + + app = QApplication([]) + view = QQuickView() + url = QUrl("view.qml") + + view.setSource(url) + view.show() + 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 `import QtQuick` and set the +source of the `QQuickView` object to the URL of your QML file. +Then, as any Qt widget, you call `QQuickView.show()`. + +.. note:: If you are programming for desktop, you should consider + adding `view.setResizeMode(QQuickView.SizeRootObjectToView)` + before showing the view. diff --git a/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst new file mode 100644 index 000000000..5ac560a8f --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst @@ -0,0 +1,169 @@ +Using `.qrc` Files (`pyside6-rcc`) +********************************** + +The `Qt Resource System`_ is a mechanism for storing binary files +in an application. + +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, `download the following set`_ +and use them. + +.. 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. + +.. _`download the following set`: icons/ + + +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` + +:: + + </ui> + <!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.rc -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/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/uifiles.rst b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst new file mode 100644 index 000000000..50bb2514c --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/uifiles.rst @@ -0,0 +1,187 @@ +Using `.ui` files from Designer or QtCreator with `QUiLoader` and `pyside6-uic` +******************************************************************************* + +This page describes the use of Qt Creator to create graphical +interfaces for your Qt for Python project. +You will need **Qt Creator** to design and modify your interface (UI file). + +If you don't know how to use Qt Creator, refer to the +`Using Qt Designer <http://doc.qt.io/qtcreator/creator-using-qt-designer.html>`_ +documentation page. + +At Qt Creator, create a new Qt Design Form, choose "Main Window" for template. +And save 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 +=================================== + +Another option 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 > 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("Cannot open {}: {}".format(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:: python + + python main.py diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgets.rst b/sources/pyside6/doc/tutorials/basictutorial/widgets.rst new file mode 100644 index 000000000..d86ba2623 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgets.rst @@ -0,0 +1,45 @@ +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_() + + +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 Binary files differnew file mode 100644 index 000000000..c30dd621b --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-no.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png Binary files differnew file mode 100644 index 000000000..eb90e216d --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-no.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png Binary files differnew file mode 100644 index 000000000..5a714977e --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-simple-yes.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png Binary files differnew file mode 100644 index 000000000..8ba49bd26 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling-yes.png diff --git a/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py new file mode 100644 index 000000000..94e44c5c5 --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.py @@ -0,0 +1,95 @@ +############################################################################# +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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("Item {}".format(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..8deef1d7f --- /dev/null +++ b/sources/pyside6/doc/tutorials/basictutorial/widgetstyling.rst @@ -0,0 +1,169 @@ +Widget Styling +************** + +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: 59-81 + +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..95b2092b3 --- /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: 40- + :emphasize-lines: 2-3,6,22-36,48-50 + + 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..a9ff38a30 --- /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: 40- + +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..720918008 --- /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 <http://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: 40- + +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: 40- + +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: 40- + :emphasize-lines: 8,11 + +.. literalinclude:: datavisualize4/main.py + :language: python + :linenos: + :lines: 40- + :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/datavisualize.tar.bz2 b/sources/pyside6/doc/tutorials/datavisualize/datavisualize.tar.bz2 Binary files differnew file mode 100644 index 000000000..5fe12769a --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize.tar.bz2 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..8a8a1dfda --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py @@ -0,0 +1,55 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..864cb1a6c --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py @@ -0,0 +1,78 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..5dac460a5 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py @@ -0,0 +1,88 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..17fe0e141 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py @@ -0,0 +1,69 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 = qApp.desktop().availableGeometry(self) + 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..ffb4d00b0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py @@ -0,0 +1,90 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..5113ae0b8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py @@ -0,0 +1,80 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..a60a4a1b1 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py @@ -0,0 +1,69 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 = qApp.desktop().availableGeometry(self) + 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..d9c50acb3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py @@ -0,0 +1,88 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 "{}".format(section) + + def data(self, index, role=Qt.DisplayRole): + column = index.column() + row = index.row() + + if role == Qt.DisplayRole: + if column == 0: + raw_date = self.input_dates[row] + date = "{}".format(raw_date.toPython()) + return date[:-3] + elif column == 1: + return "{:.2f}".format(self.input_magnitudes[row]) + 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..ffb4d00b0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py @@ -0,0 +1,90 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..0f9b14df3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py @@ -0,0 +1,91 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide6.QtCore import QDateTime, Qt +from PySide6.QtGui import QPainter +from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView, + QSizePolicy) +from PySide6.QtCharts import QtCharts + +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 = QtCharts.QChart() + self.chart.setAnimationOptions(QtCharts.QChart.AllAnimations) + + # Creating QChartView + self.chart_view = QtCharts.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..4b0920133 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py @@ -0,0 +1,69 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 = qApp.desktop().availableGeometry(self) + 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..d9c50acb3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py @@ -0,0 +1,88 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 "{}".format(section) + + def data(self, index, role=Qt.DisplayRole): + column = index.column() + row = index.row() + + if role == Qt.DisplayRole: + if column == 0: + raw_date = self.input_dates[row] + date = "{}".format(raw_date.toPython()) + return date[:-3] + elif column == 1: + return "{:.2f}".format(self.input_magnitudes[row]) + 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..3402ff856 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py @@ -0,0 +1,92 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..2934a361f --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py @@ -0,0 +1,131 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide6.QtCore import QDateTime, Qt +from PySide6.QtGui import QPainter +from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView, + QSizePolicy) +from PySide6.QtCharts import QtCharts + +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 = QtCharts.QChart() + self.chart.setAnimationOptions(QtCharts.QChart.AllAnimations) + self.add_series("Magnitude (Column 1)", [0, 1]) + + # Creating QChartView + self.chart_view = QtCharts.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 = QtCharts.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 = QtCharts.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 = QtCharts.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 + self.model.color = "{}".format(self.series.pen().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..450f6de41 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py @@ -0,0 +1,70 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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") + + # 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 = qApp.desktop().availableGeometry(self) + 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..abe3bf2ae --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py @@ -0,0 +1,88 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 "{}".format(section) + + def data(self, index, role=Qt.DisplayRole): + column = index.column() + row = index.row() + + if role == Qt.DisplayRole: + if column == 0: + raw_date = self.input_dates[row] + date = "{}".format(raw_date.toPython()) + return date[:-3] + elif column == 1: + return "{:.2f}".format(self.input_magnitudes[row]) + 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..b06b2fa15 --- /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: 40- + +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 Binary files differnew file mode 100644 index 000000000..075d796b8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png diff --git a/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png b/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png Binary files differnew file mode 100644 index 000000000..ddac43fc3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png diff --git a/sources/pyside6/doc/tutorials/datavisualize/index.rst b/sources/pyside6/doc/tutorials/datavisualize/index.rst new file mode 100644 index 000000000..ff18c654e --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/index.rst @@ -0,0 +1,26 @@ +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. + +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 :download:`here <datavisualize.tar.bz2>`. + 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..8ebdd2b08 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst @@ -0,0 +1,25 @@ +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: 40- + :emphasize-lines: 33,56-91 + +Now, run the application to visualize the earthquake magnitudes +data at different times. + +.. image:: images/datavisualization_app.png + +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..f7bf9337a --- /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: 40- + +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/expenses/expenses.rst b/sources/pyside6/doc/tutorials/expenses/expenses.rst new file mode 100644 index 000000000..2aa3fb459 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/expenses.rst @@ -0,0 +1,314 @@ +###################### +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/QtCharts.QChart.html>`_) that will + be embedded in a chart view + (`QChartView <https://doc.qt.io/qtforpython/PySide6/QtCharts/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: 45-59 + :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: 45-59 + :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: 46-58 + :emphasize-lines: 6 + +Notice that the code snippet adds a *File* menu with the *Exit* option only. + +First signal/slot connection +---------------------------- + +The *Exit* option must be connected to a slot that triggers the application to exit. The main +idea to achieve this, is the following: + +.. code-block:: python + + element.signal_name.connect(slot_name) + +All the interface's elements could be connected through signals to certain slots, +in the case of a `QAction`, the signal `triggered` can be used: + +.. code-block:: python + + exit_action.triggered.connect(slot_name) + +.. note:: Now a *slot* needs to be defined to exit the application, which can be done using + `QApplication.quit()`. If we put all these concepts together you will end up with the + following code: + +.. literalinclude:: steps/03-expenses.py + :linenos: + :lines: 56-65 + :emphasize-lines: 4, 8-10 + +Notice that the decorator `@Slot()` is required for each slot you declare to properly +register them. Slots are normal functions, but the main difference is that they +will be invokable from `Signals` of QObjects when connected. + +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: 46-53 + +With the `Widget` class in place, modify `MainWindow`'s initialization code + +.. literalinclude:: steps/04-expenses.py + :linenos: + :lines: 80-84 + +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: 48-73 + +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: 75-81 + +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. + +To distribute these input lines and buttons, you will use a `QVBoxLayout` that allows you to place +elements vertically inside a layout. + +.. literalinclude:: steps/06-expenses.py + :linenos: + :lines: 64-80 + +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: 42-47 + +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: 92-95 + +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()`, in +that way PySide6 knows internally how to register them into Qt. + +.. literalinclude:: steps/07-expenses.py + :linenos: + :lines: 100-129 + :emphasize-lines: 2,16,28 + +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[str]* which will be emitted every +time something inside changes, i.e.: each key stroke. +Notice that this time, there is a *[str]* section on the signal, this means that the signal +will also emit the value of the text that was changed, which will be really useful to verify +the current content of the `QLineEdit`. + +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: 99-100 + +The content of the *check_disable* slot will be really simple: + +.. literalinclude:: steps/08-expenses.py + :linenos: + :lines: 119-124 + +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: 66-68 + +Additionally the order of how you include widgets to the right +`QVBoxLayout` will also change. + +.. literalinclude:: steps/09-expenses.py + :linenos: + :lines: 81-91 + :emphasize-lines: 9 + +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: 103-109 + :emphasize-lines: 6 + +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: 139-151 + +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 Binary files differnew file mode 100644 index 000000000..7a6f6d1f0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/expenses_tool.png diff --git a/sources/pyside6/doc/tutorials/expenses/main.py b/sources/pyside6/doc/tutorials/expenses/main.py new file mode 100644 index 000000000..2eec5defb --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/main.py @@ -0,0 +1,207 @@ +############################################################################# +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Qt, Slot +from PySide6.QtGui import QAction, QPainter +from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, + QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget) +from PySide6.QtCharts import QtCharts + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # Chart + self.chart_view = QtCharts.QChartView() + self.chart_view.setRenderHint(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.setEnabled(False) + + self.right = QVBoxLayout() + self.right.setMargin(10) + self.right.addWidget(QLabel("Description")) + self.right.addWidget(self.description) + self.right.addWidget(QLabel("Price")) + self.right.addWidget(self.price) + self.right.addWidget(self.add) + self.right.addWidget(self.plot) + self.right.addWidget(self.chart_view) + self.right.addWidget(self.clear) + self.right.addWidget(self.quit) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + self.layout.addLayout(self.right) + + # Set the layout to the QWidget + self.setLayout(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() + + self.table.insertRow(self.items) + description_item = QTableWidgetItem(des) + price_item = QTableWidgetItem("{:.2f}".format(float(price))) + price_item.setTextAlignment(Qt.AlignRight) + + self.table.setItem(self.items, 0, description_item) + self.table.setItem(self.items, 1, price_item) + + self.description.setText("") + self.price.setText("") + + self.items += 1 + + @Slot() + def check_disable(self, s): + if not self.description.text() or not self.price.text(): + self.add.setEnabled(False) + else: + self.add.setEnabled(True) + + @Slot() + def plot_data(self): + # Get table information + series = QtCharts.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 = QtCharts.QChart() + chart.addSeries(series) + chart.legend().setAlignment(Qt.AlignLeft) + self.chart_view.setChart(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("{:.2f}".format(price)) + 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..cea2f5767 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/main_snake_prop.py @@ -0,0 +1,210 @@ +############################################################################# +## +## Copyright (C) 2020 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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.add_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 + + self.table.insert_row(self.items) + description_item = QTableWidgetItem(des) + price_item = QTableWidgetItem("{:.2f}".format(float(price))) + price_item.text_alignment = Qt.AlignRight + + 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 + + @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("{:.2f}".format(price)) + 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..efbbc59fc --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/01-expenses.py @@ -0,0 +1,59 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtWidgets import QApplication, QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self): + QMainWindow.__init__(self) + 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..783283d4f --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/02-expenses.py @@ -0,0 +1,71 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtGui import QAction +from PySide6.QtWidgets import QApplication, QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + + self.file_menu.addAction(exit_action) + + +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..bebf0b3ce --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/03-expenses.py @@ -0,0 +1,78 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCharts import QtCharts + + +class MainWindow(QMainWindow): + def __init__(self): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + + @Slot() + def exit_app(self, checked): + QApplication.quit() + + +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..a9a4c10a1 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/04-expenses.py @@ -0,0 +1,90 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction +from PySide6.QtWidgets import QApplication, QMainWindow, QWidget + + +class Widget(QWidget): + def __init__(self): + QWidget.__init__(self) + + # 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..3b22c2631 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/05-expenses.py @@ -0,0 +1,118 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction +from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QMainWindow, + QTableWidget, QTableWidgetItem, QWidget) + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + + # Set the layout to the QWidget + self.setLayout(self.layout) + + # 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..5e6bcc0e9 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/06-expenses.py @@ -0,0 +1,138 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction +from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, + QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget) + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # Right + self.description = QLineEdit() + self.price = QLineEdit() + self.add = QPushButton("Add") + self.clear = QPushButton("Clear") + self.quit = QPushButton("Quit") + + self.right = QVBoxLayout() + self.right.setMargin(10) + self.right.addWidget(QLabel("Description")) + self.right.addWidget(self.description) + self.right.addWidget(QLabel("Price")) + self.right.addWidget(self.price) + self.right.addWidget(self.add) + self.right.addStretch() + self.right.addWidget(self.clear) + self.right.addWidget(self.quit) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + self.layout.addLayout(self.right) + + # Set the layout to the QWidget + self.setLayout(self.layout) + + # 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..aa7ee6a3a --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/07-expenses.py @@ -0,0 +1,165 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction +from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, + QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget) + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # Right + self.description = QLineEdit() + self.price = QLineEdit() + self.add = QPushButton("Add") + self.clear = QPushButton("Clear") + self.quit = QPushButton("Quit") + + self.right = QVBoxLayout() + self.right.setMargin(10) + self.right.addWidget(QLabel("Description")) + self.right.addWidget(self.description) + self.right.addWidget(QLabel("Price")) + self.right.addWidget(self.price) + self.right.addWidget(self.add) + self.right.addStretch() + self.right.addWidget(self.quit) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + self.layout.addLayout(self.right) + + # Set the layout to the QWidget + self.setLayout(self.layout) + + # Signals and Slots + self.add.clicked.connect(self.add_element) + self.quit.clicked.connect(self.quit_application) + 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.setText("") + self.price.setText("") + + self.items += 1 + + @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(): + 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..0c5eb7c61 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/08-expenses.py @@ -0,0 +1,178 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction +from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, + QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget) + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # Right + self.description = QLineEdit() + self.price = QLineEdit() + self.add = QPushButton("Add") + self.clear = QPushButton("Clear") + self.quit = QPushButton("Quit") + + # Disabling 'Add' button + self.add.setEnabled(False) + + self.right = QVBoxLayout() + self.right.setMargin(10) + self.right.addWidget(QLabel("Description")) + self.right.addWidget(self.description) + self.right.addWidget(QLabel("Price")) + self.right.addWidget(self.price) + self.right.addWidget(self.add) + self.right.addStretch() + self.right.addWidget(self.clear) + self.right.addWidget(self.quit) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + self.layout.addLayout(self.right) + + # Set the layout to the QWidget + self.setLayout(self.layout) + + # Signals and Slots + self.add.clicked.connect(self.add_element) + self.quit.clicked.connect(self.quit_application) + 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() + + self.table.insertRow(self.items) + self.table.setItem(self.items, 0, QTableWidgetItem(des)) + self.table.setItem(self.items, 1, QTableWidgetItem(price)) + + self.description.setText("") + self.price.setText("") + + self.items += 1 + + @Slot() + def check_disable(self, s): + if not self.description.text() or not self.price.text(): + self.add.setEnabled(False) + else: + self.add.setEnabled(True) + + @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(): + 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..30e1d7f3f --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/09-expenses.py @@ -0,0 +1,185 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction, QPainter +from PySide6.QtWidgets import (QApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, + QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget) +from PySide6.QtCharts import QtCharts + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # Chart + self.chart_view = QtCharts.QChartView() + self.chart_view.setRenderHint(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.setEnabled(False) + + self.right = QVBoxLayout() + self.right.setMargin(10) + self.right.addWidget(QLabel("Description")) + self.right.addWidget(self.description) + self.right.addWidget(QLabel("Price")) + self.right.addWidget(self.price) + self.right.addWidget(self.add) + self.right.addWidget(self.plot) + self.right.addWidget(self.chart_view) + self.right.addWidget(self.clear) + self.right.addWidget(self.quit) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + self.layout.addLayout(self.right) + + # Set the layout to the QWidget + self.setLayout(self.layout) + + # Signals and Slots + self.add.clicked.connect(self.add_element) + self.quit.clicked.connect(self.quit_application) + 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() + + self.table.insertRow(self.items) + self.table.setItem(self.items, 0, QTableWidgetItem(des)) + self.table.setItem(self.items, 1, QTableWidgetItem(price)) + + self.description.setText("") + self.price.setText("") + + self.items += 1 + + @Slot() + def check_disable(self, s): + if not self.description.text() or not self.price.text(): + self.add.setEnabled(False) + else: + self.add.setEnabled(True) + + @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(): + 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.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..2329805a7 --- /dev/null +++ b/sources/pyside6/doc/tutorials/expenses/steps/10-expenses.py @@ -0,0 +1,207 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide6.QtCore import Qt, Slot +from PySide6.QtGui import QAction, QPainter +from PySide6.QtWidgets import (QQApplication, QHeaderView, QHBoxLayout, QLabel, QLineEdit, + QMainWindow, QPushButton, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget) +from PySide6.QtCharts import QtCharts + + +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.setColumnCount(2) + self.table.setHorizontalHeaderLabels(["Description", "Price"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # Chart + self.chart_view = QtCharts.QChartView() + self.chart_view.setRenderHint(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.setEnabled(False) + + self.right = QVBoxLayout() + self.right.setMargin(10) + self.right.addWidget(QLabel("Description")) + self.right.addWidget(self.description) + self.right.addWidget(QLabel("Price")) + self.right.addWidget(self.price) + self.right.addWidget(self.add) + self.right.addWidget(self.plot) + self.right.addWidget(self.chart_view) + self.right.addWidget(self.clear) + self.right.addWidget(self.quit) + + # QWidget Layout + self.layout = QHBoxLayout() + + #self.table_view.setSizePolicy(size) + self.layout.addWidget(self.table) + self.layout.addLayout(self.right) + + # Set the layout to the QWidget + self.setLayout(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() + + self.table.insertRow(self.items) + description_item = QTableWidgetItem(des) + price_item = QTableWidgetItem("{:.2f}".format(float(price))) + price_item.setTextAlignment(Qt.AlignRight) + + self.table.setItem(self.items, 0, description_item) + self.table.setItem(self.items, 1, price_item) + + self.description.setText("") + self.price.setText("") + + self.items += 1 + + @Slot() + def check_disable(self, s): + if not self.description.text() or not self.price.text(): + self.add.setEnabled(False) + else: + self.add.setEnabled(True) + + @Slot() + def plot_data(self): + # Get table information + series = QtCharts.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 = QtCharts.QChart() + chart.addSeries(series) + chart.legend().setAlignment(Qt.AlignLeft) + self.chart_view.setChart(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("{:.2f}".format(price)) + 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): + QMainWindow.__init__(self) + self.setWindowTitle("Tutorial") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut("Ctrl+Q") + exit_action.triggered.connect(self.exit_app) + + self.file_menu.addAction(exit_action) + self.setCentralWidget(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.resize(800, 600) + window.show() + + # Execute application + sys.exit(app.exec_()) diff --git a/sources/pyside6/doc/tutorials/index.rst b/sources/pyside6/doc/tutorials/index.rst new file mode 100644 index 000000000..a159725a7 --- /dev/null +++ b/sources/pyside6/doc/tutorials/index.rst @@ -0,0 +1,42 @@ +|project| Tutorials +==================== + +A collection of tutorials with "walkthrough" guides are +provided with |project| to help new users get started. 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. + +Basic tutorials +--------------- + +.. toctree:: + :maxdepth: 1 + + basictutorial/widgets.rst + basictutorial/qml.rst + basictutorial/clickablebutton.rst + basictutorial/dialog.rst + basictutorial/uifiles.rst + basictutorial/qrcfiles.rst + basictutorial/widgetstyling.rst + +Real use-cases applications +--------------------------- + +.. toctree:: + :maxdepth: 1 + + datavisualize/index.rst + expenses/expenses.rst + qmlapp/qmlapplication.rst + qmlintegration/qmlintegration.rst + qmlsqlintegration/qmlsqlintegration.rst + +C++ and Python +-------------- + +.. toctree:: + :maxdepth: 1 + + portingguide/index.rst 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..20b11065a --- /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: 40-44 + +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: 55-81 + +Python version +--------------- + +.. literalinclude:: createdb.py + :language: python + :linenos: + :lines: 44-65 + +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 +------------ + +.. literalinclude:: initdb.h + :language: c++ + :linenos: + :lines: 81-159 + +Python version +--------------- + +.. literalinclude:: createdb.py + :language: python + :linenos: + :lines: 65- + +.. 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: 40- + +Use the following command from the prompt to run: + +.. code-block:: + + 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..c3f27162d --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/createdb.py @@ -0,0 +1,131 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 Binary files differnew file mode 100644 index 000000000..164674220 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/images/chapter1_books.png 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..773e3fb74 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/initdb.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..5b42a61a1 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter1/main.py @@ -0,0 +1,59 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..4115f80cf --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..f1b432699 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..d6c16972b --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/bookdelegate.py @@ -0,0 +1,134 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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, parent=None): + QSqlRelationalDelegate.__init__(self, parent) + star_png = os.path.dirname(__file__) + "\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..a574218fd --- /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: 40-47 + +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: 54-59 + +Python version +--------------- + +.. literalinclude:: bookdelegate.py + :language: python + :linenos: + :lines: 47-54 + +.. 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 +------------ + +.. literalinclude:: bookdelegate.cpp + :language: c++ + :linenos: + :lines: 59- + +Python version +--------------- + +.. literalinclude:: bookdelegate.py + :language: python + :linenos: + :lines: 55- + +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: 40- + +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..c3f27162d --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/createdb.py @@ -0,0 +1,131 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 Binary files differnew file mode 100644 index 000000000..e456b7d8f --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books.png 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 Binary files differnew file mode 100644 index 000000000..82a5f449c --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/chapter2_books_with_relation.png diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.png b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.png Binary files differnew file mode 100644 index 000000000..87f4464bd --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/images/star.png 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..c9c907f90 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter2/main.py @@ -0,0 +1,63 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..0c0f1ee69 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate-old.py @@ -0,0 +1,134 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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) + star_png = os.path.dirname(__file__) + "\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..4594508d5 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookdelegate.py @@ -0,0 +1,133 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..76f3c9da8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..0b20e8ad5 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/bookwindow.py @@ -0,0 +1,138 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import print_function, absolute_import + +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><b>Title:</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><b>Author: </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><b>Genre:</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><b>Year:</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><b>Rating:</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..b2e06b144 --- /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: 40-53 + +.. 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:: + + pyside6-uic bookwindow.ui > 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: 57-140 + +Python version +--------------- + +.. literalinclude:: bookwindow.py + :language: python + :linenos: + :lines: 53-116 + +.. 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: 115- + +Python version +--------------- + +.. literalinclude:: bookwindow.py + :language: python + :linenos: + :lines: 117- + +Now that all the necessary pieces are in place, try to put +them together in ``main.py``. + +.. literalinclude:: main.py + :language: python + :linenos: + :lines: 40- + +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:: + + pyside6-rcc books.qrc > 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..c3f27162d --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/createdb.py @@ -0,0 +1,131 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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 Binary files differnew file mode 100644 index 000000000..952cb14e8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/chapter3-books.png diff --git a/sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.png b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.png Binary files differnew file mode 100644 index 000000000..87f4464bd --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/images/star.png 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..675f8032b --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/main-old.py @@ -0,0 +1,52 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..e957869ac --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/chapter3/main.py @@ -0,0 +1,53 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..81b63b7ea --- /dev/null +++ b/sources/pyside6/doc/tutorials/portingguide/hello_world_ex.py @@ -0,0 +1,76 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +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..12f76d3fc --- /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>` 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: 40- + +.. 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 Binary files differnew file mode 100644 index 000000000..30c621c9c --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/logo.png diff --git a/sources/pyside6/doc/tutorials/qmlapp/main.py b/sources/pyside6/doc/tutorials/qmlapp/main.py new file mode 100644 index 000000000..5946538d9 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/main.py @@ -0,0 +1,82 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +#!/usr/bin/env python +# -*- conding: utf-8 -*- + +import os, sys, urllib.request, json +import PySide6.QtQml +from PySide6.QtQuick import QQuickView +from PySide6.QtCore import QStringListModel, Qt, 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.rootContext().setContextProperty("myModel",my_model) + + #Load the QML file + qml_file = os.path.join(os.path.dirname(__file__),"view.qml") + view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file))) + + #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 Binary files differnew file mode 100644 index 000000000..93968a52d --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png diff --git a/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png Binary files differnew file mode 100644 index 000000000..c66d88723 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png Binary files differnew file mode 100644 index 000000000..98328074d --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png Binary files differnew file mode 100644 index 000000000..e01e2ebeb --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png diff --git a/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.png b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.png Binary files differnew file mode 100644 index 000000000..ec0ad3dea --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.png diff --git a/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst b/sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst new file mode 100644 index 000000000..082490f4b --- /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 context 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 v4.9 beta1 or later from + `http://download.qt.io + <http://download.qt.io/snapshots/qtcreator/4.9/4.9.0-beta1/>`_. + + +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: 40-60 + :emphasize-lines: 12-20 + +#. Now, set up the application window using + :ref:`PySide6.QtGui.QGuiApplication<qguiapplication>`, which manages the application-wide + settings. + + .. literalinclude:: main.py + :linenos: + :lines: 40-65 + :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 context + property, which will be consumed by the QML ListView item in ``view.qml``. + + .. literalinclude:: main.py + :linenos: + :lines: 40-70 + :emphasize-lines: 27-30 + +#. Load the ``view.qml`` to the ``QQuickView`` and call ``show()`` to + display the application window. + + .. literalinclude:: main.py + :linenos: + :lines: 40-79 + :emphasize-lines: 33-39 + +#. Finally, execute the application to start the event loop and clean up. + + .. literalinclude:: main.py + :linenos: + :lines: 40- + :emphasize-lines: 41-43 + +#. 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..c75052b29 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlapp/view.qml @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Python. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Page { + width: 640 + height: 480 + + 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..884d239b3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlintegration/main.py @@ -0,0 +1,113 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from os.path import abspath, dirname, join + +from PySide6.QtCore import QObject, Slot +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +from style_rc import * + + +class Bridge(QObject): + + @Slot(str, result=str) + def getColor(self, color_name): + if color_name.lower() == "red": + return "#ef9a9a" + elif color_name.lower() == "green": + return "#a5d6a7" + elif color_name.lower() == "blue": + return "#90caf9" + else: + return "white" + + @Slot(float, result=int) + def getSize(self, s): + size = int(s * 42) # Maximum font size + 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) + engine = QQmlApplicationEngine() + + # Instance of the Python object + bridge = Bridge() + + # Expose the Python object to QML + context = engine.rootContext() + context.setContextProperty("con", bridge) + + # Get the path of the current directory, and then add the name + # of the QML file, to load it. + qmlFile = join(dirname(__file__), 'view.qml') + engine.load(abspath(qmlFile)) + + 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..5bbb89609 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlintegration/qmlintegration.rst @@ -0,0 +1,111 @@ +######################## +QML Integration Tutorial +######################## + +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 through a context +property. 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: 98-108 + :emphasize-lines: 6,9 + + Notice that we specify the name of the context property, **con**, + and also we explicitly load our QML file. + +#. Define the `Bridge` class, containing all the logic for the context property: + + .. literalinclude:: main.py + :linenos: + :lines: 51-91 + +#. Now, go back to the QML file and connect the signals to the slots defined in the `Bridge` class: + + .. literalinclude:: view.qml + :linenos: + :lines: 85-93 + :emphasize-lines: 5-7 + + 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 context 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: 79-84 + :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: 64-70 + +#. 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 > style_rc.py` + And finally import it from your `main.py` script. + + .. literalinclude:: main.py + :linenos: + :lines: 41-48 + :emphasize-lines: 8 + + 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 Binary files differnew file mode 100644 index 000000000..cfeac9368 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_default.png diff --git a/sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.png b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.png Binary files differnew file mode 100644 index 000000000..47866c10e --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlintegration/textproperties_material.png diff --git a/sources/pyside6/doc/tutorials/qmlintegration/view.qml b/sources/pyside6/doc/tutorials/qmlintegration/view.qml new file mode 100644 index 000000000..97968d691 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlintegration/view.qml @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt for Python examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +import QtQuick 2.0 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls.Material 2.12 + +ApplicationWindow { + id: page + width: 800 + height: 400 + visible: true + + GridLayout { + id: grid + columns: 2 + rows: 3 + + ColumnLayout { + spacing: 2 + 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 + text: "Italic" + onToggled: { + leftlabel.font.italic = con.getItalic(italic.text) + leftlabel.font.bold = con.getBold(italic.text) + leftlabel.font.underline = con.getUnderline(italic.text) + + } + } + RadioButton { + id: bold + text: "Bold" + onToggled: { + leftlabel.font.italic = con.getItalic(bold.text) + leftlabel.font.bold = con.getBold(bold.text) + leftlabel.font.underline = con.getUnderline(bold.text) + } + } + RadioButton { + id: underline + text: "Underline" + onToggled: { + leftlabel.font.italic = con.getItalic(underline.text) + leftlabel.font.bold = con.getBold(underline.text) + leftlabel.font.underline = con.getUnderline(underline.text) + } + } + RadioButton { + id: noneradio + text: "None" + checked: true + onToggled: { + leftlabel.font.italic = con.getItalic(noneradio.text) + leftlabel.font.bold = con.getBold(noneradio.text) + leftlabel.font.underline = con.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 = con.getColor(red.text) + } + } + Button { + id: green + text: "Green" + highlighted: true + Material.accent: Material.Green + onClicked: { + leftlabel.color = con.getColor(green.text) + } + } + Button { + id: blue + text: "Blue" + highlighted: true + Material.accent: Material.Blue + onClicked: { + leftlabel.color = con.getColor(blue.text) + } + } + Button { + id: nonebutton + text: "None" + highlighted: true + Material.accent: Material.BlueGrey + onClicked: { + leftlabel.color = con.getColor(nonebutton.text) + } + } + } + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + Text { + id: rightlabel + color: "white" + 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 = con.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..487f5b36c --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/chat.qml @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Python. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +ApplicationWindow { + id: window + title: qsTr("Chat") + width: 640 + height: 960 + visible: true + ColumnLayout { + anchors.fill: parent + + 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 { + readonly property bool sentByMe: model.recipient !== "Me" + anchors.right: sentByMe ? parent.right : undefined + spacing: 6 + + Row { + id: messageRow + spacing: 6 + anchors.right: sentByMe ? parent.right : undefined + + Rectangle { + width: Math.min(messageText.implicitWidth + 24, listView.width - messageRow.spacing) + height: messageText.implicitHeight + 24 + radius: 15 + color: sentByMe ? "lightgrey" : "#ff627c" + + 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: { + chat_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 Binary files differnew file mode 100644 index 000000000..a0c189665 --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/example_list_view.png diff --git a/sources/pyside6/doc/tutorials/qmlsqlintegration/main.py b/sources/pyside6/doc/tutorials/qmlsqlintegration/main.py new file mode 100644 index 000000000..50a3d43fe --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/main.py @@ -0,0 +1,85 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import logging + +from PySide6.QtCore import QDir, QFile, QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtSql import QSqlDatabase + +from sqlDialog import SqlConversationModel + +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. + filename = "{}/chat-database.sqlite3".format(write_dir.absolutePath()) + + # 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() + sql_conversation_model = SqlConversationModel() + + engine = QQmlApplicationEngine() + # Export pertinent objects to QML + engine.rootContext().setContextProperty("chat_model", sql_conversation_model) + engine.load(QUrl("chat.qml")) + + 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..0df6dd30c --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/qmlsqlintegration.rst @@ -0,0 +1,225 @@ +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-5/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: 40-77 + +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: 80-91 + +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: 93-103 + +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: 105-112 + + +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: 114-128 + +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: 130-146 + +chat.qml +-------- + +Let's look at the ``chat.qml`` file. + + .. literalinclude:: chat.qml + :linenos: + :lines: 40-42 + +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: 44-49 + +``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. + +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: 50-53 + +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 +.. _ListView: https://doc.qt.io/qt-5/qml-qtquick-listview.html +.. _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: 53-99 + +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: 101-125 + +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: 40-50 + +``connectToDatabase()`` creates a connection with the SQLite database, creating the actual file +if it doesn't already exist. + + .. literalinclude:: main.py + :linenos: + :lines: 53-72 + + + +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 context property 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: 75-85 + +.. 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..a9745051b --- /dev/null +++ b/sources/pyside6/doc/tutorials/qmlsqlintegration/sqlDialog.py @@ -0,0 +1,146 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import datetime +import logging + +from PySide6.QtCore import Qt, Slot +from PySide6.QtSql import QSqlDatabase, QSqlQuery, QSqlRecord, QSqlTableModel + +table_name = "Conversations" + + +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) + + +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 = ( + "(recipient = '{}' AND author = 'Me') OR " "(recipient = 'Me' AND author='{}')" + ).format(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 + + 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('Message: "{}" \n Received by: "{}"'.format(message, recipient)) + + if not self.insertRecord(self.rowCount(), new_record): + logging.error("Failed to send message: {}".format(self.lastError().text())) + return + + self.submitAll() + self.select() |