aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/considerations.rst
blob: dda1d8b0db4e21dbcc774c9fafae0e13d76b91ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
.. _pysideapi2:

Considerations
==============

API Changes
-----------

One of the goals of |pymodname| is to be API compatible with PyQt,
with certain exceptions.

The latest considerations and known issues will be also reported
in the :ref:`developer-notes`.

__hash__() function return value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The hash value returned for the classes :class:`PySide6.QtCore.QDate`,
:class:`PySide6.QtCore.QDateTime`, :class:`PySide6.QtCore.QTime`, :class:`PySide6.QtCore.QUrl`
will be based on their string representations, thus objects with the same value will produce the
same hash.


QString
~~~~~~~

Methods and functions that change the contents of a QString argument were modified to receive an
immutable Python Unicode (or str) and return another Python Unicode/str as the modified string.

The following methods had their return types modified this way:

**Classes:** QAbstractSpinBox, QDateTimeEdit, QDoubleSpinBox, QSpinBox, QValidator

* ``fixup(string): string``
* ``validate(string, int): [QValidator.State, string, int]``

**Classes:** QDoubleValidator, QIntValidator, QRegExpValidator

* ``validate(string, int): [QValidator.State, string, int]``

**Class:** QClipboard

* ``text(string, QClipboard.Mode mode=QClipboard.Clipboard): [string, string]``

**Class:** QFileDialog

Instead of ``getOpenFileNameAndFilter()``, ``getOpenFileNamesAndFilter()`` and
``getSaveFileNameAndFilter()`` like PyQt does, PySide has modified the original methods to return
a tuple.

* ``getOpenFileName(QWidget parent=None, str caption=None, str dir=None, str filter=None, QFileDialog.Options options=0): [string, filter]``
* ``getOpenFileNames(QWidget parent=None, str caption=None, str dir=None, str filter=None, QFileDialog.Options options=0): [list(string), filter]``
* ``getSaveFileName(QWidget parent=None, str caption=None, str dir=None, str filter=None, QFileDialog.Options options=0): [string, filter]``

**Class:** QWebPage

* ``javaScriptPrompt(QWebFrame, string, string): [bool, string]``

**Classes:** QFontMetrics and QFontMetricsF

They had two new methods added. Both take a string of one character and convert to a QChar
(to call the C++ counterpart):

* ``widthChar(string)``
* ``boundingRectChar(string)``


QTextStream
~~~~~~~~~~~

Inside this class some renames were applied to avoid clashes with native Python functions.
They are: ``bin_()``, ``hex_()`` and ``oct_()``.
The only modification was the addition of the '_' character.


QVariant
~~~~~~~~

As ``QVariant`` was removed, any function expecting it can receive any Python object (``None`` is
an invalid ``QVariant``).
The same rule is valid when returning something: the returned ``QVariant`` will be converted to
its original Python object type.

When a method expects a ``QVariant::Type`` the programmer can use a string (the type name) or the
type itself.


qApp "macro"
~~~~~~~~~~~~

The C++ API of QtWidgets provides a macro called ``qApp`` that roughly expands to
``QtWidgets::QApplication->instance()``.

In PySide, we tried to create a macro-like experience.
For that, the ``qApp`` variable was implemented as a normal variable
that lives in the builtins.
After importing ``PySide6``, you can immediately use ``qApp``.

As a useful shortcut for the action "create an application if it was not created", we recommend::

    qApp or QtWidgets.QApplication()

or if you want to check if there is one, simply use the truth value::

    if qApp:
        # do something if an application was created
        pass

Comparing to ``None`` is also possible, but slightly over-specified.


Testing support
+++++++++++++++

For testing purposes, you can also get rid of the application by calling::

    qApp.shutdown()

As for 5.14.2, this is currently an experimental feature that is not fully tested.


Embedding status
++++++++++++++++

In embedded mode, application objects that are pre-created in C++ don't have a Python wrapper.
The ``qApp`` variable is created together with a wrapped application.
Therefore, ``qApp`` does not exist in that embedded mode.
Please note that you always can use ``QtWidgets.QApplication.instance()`` instead.


Abandoned Alternative
+++++++++++++++++++++

We also tried an alternative implementation with a ``qApp()`` function that was more *pythonic*
and problem free, but many people liked the ``qApp`` macro better for its brevity, so here it is.


Rich Comparison
~~~~~~~~~~~~~~~

There was a long-standing bug in the ``tp_richcompare`` implementation of PySide classes.

* When a class did not implement it, the default implementation of ``object`` is used.
  This implements ``==`` and ``!=`` like the ``is`` operator.

* When a class implements only a single function like ``<``, then the default implementation
  was disabled, and expressions like ``obj in sequence`` failed with ``NotImplemented``.

This oversight was fixed in version 5.15.1 .


Features
--------

In |project|, we begin for the first time to support a more pythonic user interface.
With a special import statement, you can switch on features which replace certain aspects of
the Python interpreter. This is done by an import statement right after the PySide6 import.

snake_case
~~~~~~~~~~

With the statement:

.. code-block:: python

    from __feature__ import snake_case

all methods in the current module are switched from ``camelCase`` to ``snake_case``.
A single upper case letter is replaced by an underscore and the lower case letter.

true_property
~~~~~~~~~~~~~

With the statement:

.. code-block:: python

    from __feature__ import true_property

all getter and setter functions which are marked as a property in the Qt6 docs
are replaced by Python property objects. Properties are also listed as such
in the according QMetaObject of a class.

Example for both features
~~~~~~~~~~~~~~~~~~~~~~~~~

Some |project| snippet might read:

.. code-block:: python

    self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

With the above features selected, this reads:

.. code-block:: python

    self.table.horizontal_header().section_resize_mode = QHeaderView.Stretch

Additionally, properties can also be declared directly in Shiboken for
non Qt-libraries, see :ref:`property-declare`.

More about features
~~~~~~~~~~~~~~~~~~~

Detailed info about features can be found here: :ref:`feature-why`

Tools
~~~~~

|project| ships some Qt tools:

* ``pyside6-rcc``: Qt Resource Compiler. This is a command line tool
  that compiles ``.qrc`` files containing binary data, for example images,
  into executable Python code (see :ref:`using_qrc_files`).
* ``pyside6-uic``: Qt User Interface Compiler. This is a command line tool
  that compiles ``.ui`` files containing designs of Qt Widget-based forms
  into executable Python code (see :ref:`using_ui_files`).
* ``pyside6-assistant``: Qt Help Viewer. This is a graphical tool that can
  be used to view Qt documentation from Qt Compressed Help files (``.qhc``).
  Currently, only the binary without documentation sets is shipped to reduce the
  wheel size. For building the documentation, see :ref:`building_documentation`.
* ``pyside6-designer``: Qt User Interface Designer. This is a graphical tool
  to create designs of Qt Widget-based forms and use custom widgets
  (see :ref:`using_ui_files`, :ref:`designer_custom_widgets`).


.. _NewEnumSystem:

The New Python Enums
--------------------

The Motivation to use new Enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For a long time, there were just the Shiboken enums, which were modelled as exact as possible
after the existing enums in Qt. These enums are small classes which also inherit from
int.

Meanwhile, Python enums have been developed over the years. They have become a natural
part of modern Python. The implementation is perfectly modelled after the needs of Python
users. It is therefore just consequent to stop having two different enum implementations
in the same application and instead to use the new Python implementation everywhere.


Existing Work
~~~~~~~~~~~~~

The new enums beginning with PySide 6.3, replace the Shiboken enums
with Python variants, which harmonize the builtin enums with the already existing
``QEnum`` "macro" shown in the :ref:`QEnum` section.


Enums behavior in PySide
~~~~~~~~~~~~~~~~~~~~~~~~

In ``PySide 6.3`` there was a double implementation of old and new enums, where the
default was old enums.
The new approach to enum is the default in ``PySide 6.4`` and becomes mandatory
in ``PySide 6.6``. There exists the environment variable ``PYSIDE6_OPTION_PYTHON_ENUM``
with the default value of "1". There can also variations be selected by specifying
different flags, but the value of "0" (switching off) is no longer supported.

The still available options for switching some enum features off can be found in the
:ref:`enum-features` section.


The Differences between old and new Enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Python enums and Shiboken enums are more or less compatible with each other.
Tiny differences are in restrictions:

* Python enums cannot inherit from each other, whereas Shiboken enums can
* Python enums don't allow undefined values, Shiboken enums do
* Python enums always need exactly one argument, Shiboken enums have a default zero value
* Python enums rarely inherit from int, Shiboken enums always do

More visible are the differences between flags, as shown in the following:

The Shiboken flag constructor example has been in PySide prior to 6.3:

::

    flags = Qt.Alignment()
    enum = Qt.AlignmentFlag

with enum shortcuts like

::

    Qt.AlignLeft = Qt.AlignmentFlag.AlignLeft
    Qt.AlignTop  = Qt.AlignmentFlag.AlignTop

In PySide 6.3, these shortcuts and flags no longer exist (officially).
Instead, Python has an enum.Flags class which is a subclass of the enum.Enum class.
But don't be too scared, here comes the good news...


Doing a Smooth Transition from the Old Enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Changing all the enum code to suddenly use the new syntax is cumbersome and error-prone,
because such necessary changes are not easy to find.
Therefore a ``forgiveness mode`` was developed:

The ``forgiveness mode`` allows you to continue using the old constructs but translates them
silently into the new ones. If you for example write

::

    flags = Qt.Alignment()
    enum = Qt.AlignLeft

    item.setForeground(QColor(Qt.green))

    flags_type = QPainter.RenderHints
    flags = QPainter.RenderHints()

    chart_view.setRenderHint(QPainter.Antialiasing)

you get in reality a construct that mimics the following code which is the
recommended way of writing Flags and Enums:

::

    flags = Qt.AlignmentFlag(0)
    enum = Qt.AlignmentFlag.AlignLeft

    item.setForeground(QColor(Qt.GlobalColor.green))

    flags_type = QPainter.RenderHint
    flags = QPainter.RenderHint(0)

    chart_view.setRenderHint(QPainter.RenderHint.Antialiasing)

This has the effect that you can initially ignore the difference between old and new enums,
as long as the new enums are properties of classes. (This does not work on global enums
which don't have a class, see ``Limitations`` below.)


Forgiveness Mode and Type Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When you inspect for instance ``QtCore.pyi``, you will only find the new enums, although
the old ones are still allowed. Also, line completion will only work with the new constructs
and never propose the old ones.

The reason to implement ``forgiveness mode`` this way was

* to make the transition as smooth as possible, but
* to encourage people to use the new enums whenever new code is written.

So you can continue to write:

::

    self.text.setAlignment(Qt.AlignCenter)

but this construct is used and recommended for the future:

::

    self.text.setAlignment(Qt.AlignmentFlag.AlignCenter)


Limitations
~~~~~~~~~~~

The forgiveness mode works very well whenever the enum class is embedded in a normal
PySide class. But there are a few global enums, where especially the ``QtMsgType``
is a problem:

::

    t = QtMsgType.QtDebugMsg

cannot be written in the shortcut form

::

    t = QtDebugMsg

because there is no surrounding PySide class that provides the forgiving mode
implementation. Typically, the needed changes are easily found because they often occur
in an import statement.

Permission API
--------------

The cross-platform permission APIs were introduced to Qt in version 6.5 which are currently relevant
to platforms macOS, iOS, Android and WebAssembly. With this API, your Qt application can check and
request permission for certain features like Camera, Microphone, Location, Bluetooth, Contacts,
Calendar. More about permission API can be read in this `Blog post`_.

When a PySide6 application that uses the permission API is run in interpreted mode, i.e.,
``python <main_file>.py``, the code implementing the permission API *will not work*. The only way
to make your PySide6 application using permission API work is to bundle the application. For Android,
this means using the `pyside6-android-deploy`_ tool and for macOS, this means using the
`pyside6-deploy`_ tool.

When running in interpreted mode, you can skip over the permission check/request using the following
*if* condition

::

    is_deployed = "__compiled__" in globals()
    if not is_deployed and sys.platform == "darwin":
        # code implementing permission check and request

This can also be seen in the PySide6 `Camera example`_. * __compiled__ * is a Nuitka attribute to
check if the application is run as a standalone application or run in interpreted mode with Python.

Android
~~~~~~~~

For Android, `pyside6-android-deploy`_ takes care of identifying the necessary permissions needed by
the application and adding those permissions to the *AndroidManifest.xml* using the
*<uses-permission>* element.

macOS
~~~~~

Since the Android platform does not automatically come bundled with a Python interpreter, it is
evident that to make a PySide6 application run on Android you have to package the PySide6
application. This is not the case for desktop platforms like macOS where a Python interpreter and
its packages can be installed and run quite easily.

The problem for macOS is that for the permission API to work you need a macOS bundle with an
*Info.plist* file that lists all the permissions required using the *usage description* string for
each permission used. When Python is run in interpreted mode, i.e., when you run Python, the Qt
permission API fetches the *Info.plist* from the Python interpreter by default which does not
contain the *usage description* strings for the permissions required. You can certainly modify the
*Info.plist* of the Python framework installation to make the Qt permission API work when running
a PySide6 application from the terminal. However, this is not recommended. Therefore, the only
viable solution is to bundle the PySide6 application as a macOS application bundle using
`pyside6-deploy`_. This macOS application bundle will have its own Info.plist file.

.. _`Blog post`: https://www.qt.io/blog/permission-apis-in-qt-6.5
.. _`Camera Example`: https://doc.qt.io/qtforpython-6/examples/example_multimedia_camera.html#camera-example
.. _`pyside6-android-deploy`: https://doc.qt.io/qtforpython-6/gettingstarted/package_details.html#deployment
.. _`pyside6-deploy`: https://doc.qt.io/qtforpython-6/gettingstarted/package_details.html#deployment