From 0609b97a84f8e17e00d584f0f75cb6803e14993c Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Sun, 16 Feb 2025 00:28:09 +1100 Subject: [PATCH] New upstream version 6.8.2.1 --- .gitignore | 4 +- .vscode/settings.json | 3 +- README.md | 2 +- README.pyside6_addons.md | 2 + build_scripts/__init__.py | 10 +- build_scripts/build_info_collector.py | 3 +- build_scripts/config.py | 181 ++- build_scripts/log.py | 1 + build_scripts/main.py | 203 +-- build_scripts/options.py | 206 +-- build_scripts/platforms/__init__.py | 1 + build_scripts/platforms/linux.py | 1 + build_scripts/platforms/macos.py | 9 +- build_scripts/platforms/unix.py | 26 +- build_scripts/platforms/windows_desktop.py | 77 +- build_scripts/qfp_tool.py | 18 +- build_scripts/qtinfo.py | 5 +- build_scripts/setup_runner.py | 7 +- build_scripts/utils.py | 115 +- build_scripts/wheel_files.py | 92 +- build_scripts/wheel_override.py | 3 +- build_scripts/wheel_utils.py | 1 + coin/dependencies.yaml | 5 +- coin/fetch_libclang_arm64.sh | 23 + coin/instructions/common_environment.yaml | 103 +- .../execute_android_instructions.yaml | 76 ++ .../execute_build_instructions.yaml | 133 +- .../execute_desktop_instructions.yaml | 133 ++ coin/instructions/execute_license_check.yaml | 31 - .../execute_test_instructions.yaml | 47 +- coin/instructions_utils.py | 262 ++++ coin/module_config.yaml | 30 +- coin_build_instructions.py | 164 +-- coin_test_instructions.py | 110 +- create_wheels.py | 53 +- doc/changelogs/changes-6.7.3 | 75 ++ doc/changelogs/changes-6.8.0 | 61 + doc/changelogs/changes-6.8.1 | 53 + doc/changelogs/changes-6.8.1.1 | 20 + doc/changelogs/changes-6.8.2 | 54 + doc/changelogs/changes-6.8.2.1 | 21 + examples/3d/simple3d/simple3d.py | 1 + .../eratosthenes/eratosthenes_asyncio.py | 1 + .../async/eratosthenes/eratosthenes_trio.py | 1 + examples/async/minimal/minimal_asyncio.py | 1 + examples/async/minimal/minimal_trio.py | 1 + examples/axcontainer/axviewer/axviewer.py | 1 + examples/bluetooth/btscanner/device.py | 21 +- examples/bluetooth/btscanner/main.py | 1 + examples/bluetooth/btscanner/service.py | 1 + examples/bluetooth/btscanner/service.ui | 2 +- examples/bluetooth/btscanner/ui_device.py | 2 +- examples/bluetooth/btscanner/ui_service.py | 4 +- .../heartrate_game/bluetoothbaseclass.py | 1 + .../heartrate_game/connectionhandler.py | 1 + .../bluetooth/heartrate_game/devicefinder.py | 1 + .../bluetooth/heartrate_game/devicehandler.py | 9 +- .../bluetooth/heartrate_game/deviceinfo.py | 1 + .../heartrate_game/heartrate_global.py | 1 + examples/bluetooth/heartrate_game/main.py | 5 +- .../heartrate_server/heartrate_server.py | 7 +- .../lowenergyscanner/characteristicinfo.py | 1 + examples/bluetooth/lowenergyscanner/device.py | 1 + .../bluetooth/lowenergyscanner/deviceinfo.py | 1 + examples/bluetooth/lowenergyscanner/main.py | 5 +- .../bluetooth/lowenergyscanner/serviceinfo.py | 1 + examples/charts/areachart/areachart.py | 5 +- examples/charts/audio/audio.py | 1 + examples/charts/barchart/barchart.py | 3 +- examples/charts/callout/callout.py | 3 +- examples/charts/chartthemes/main.py | 11 +- examples/charts/chartthemes/themewidget.ui | 42 +- examples/charts/chartthemes/ui_themewidget.py | 2 +- .../charts/donutbreakdown/donutbreakdown.py | 3 +- examples/charts/dynamicspline/chart.py | 1 + examples/charts/dynamicspline/main.py | 5 +- examples/charts/legend/legend.py | 3 +- examples/charts/lineandbar/lineandbar.py | 3 +- examples/charts/linechart/linechart.py | 3 +- examples/charts/logvalueaxis/logvalueaxis.py | 3 +- examples/charts/memoryusage/memoryusage.py | 1 + examples/charts/modeldata/modeldata.py | 19 +- examples/charts/nesteddonuts/nesteddonuts.py | 3 +- .../charts/percentbarchart/percentbarchart.py | 3 +- examples/charts/piechart/piechart.py | 3 +- .../charts/pointconfiguration/chartwindow.py | 4 +- .../doc/pointconfiguration.rst | 2 +- .../pointconfiguration/pointconfiguration.py | 1 + .../pointselectionandmarkers.py | 7 +- .../pointselectionandmarkers/utilities.py | 5 +- .../charts/qmlpolarchart/qmlpolarchart.py | 1 + .../temperaturerecords/temperaturerecords.py | 3 +- examples/charts/zoomlinechart/chart.py | 37 + examples/charts/zoomlinechart/chartview.py | 70 ++ .../zoomlinechart/doc/zoomlinechart.png | Bin 0 -> 33933 bytes .../zoomlinechart/doc/zoomlinechart.rst | 8 + examples/charts/zoomlinechart/main.py | 42 + .../zoomlinechart/zoomlinechart.pyproject | 7 + examples/corelib/ipc/sharedmemory/dialog.py | 5 +- examples/corelib/ipc/sharedmemory/dialog.ui | 35 +- .../ipc/sharedmemory/doc/sharedmemory.png | Bin 0 -> 14926 bytes .../ipc/sharedmemory/doc/sharedmemory.rst | 11 + examples/corelib/ipc/sharedmemory/main.py | 1 + .../corelib/ipc/sharedmemory/ui_dialog.py | 4 +- .../mimetypesbrowser/doc/mimetypesbrowser.rst | 1 - .../corelib/mimetypesbrowser/mainwindow.py | 15 +- .../corelib/mimetypesbrowser/mimetypemodel.py | 11 +- .../mimetypesbrowser/mimetypesbrowser.py | 1 + .../corelib/settingseditor/settingseditor.py | 87 +- examples/corelib/threads/mandelbrot.py | 51 +- examples/datavisualization/bars3d/bars3d.py | 1 + .../graphgallery/axesinputhandler.py | 1 + .../graphgallery/bargraph.py | 9 +- .../graphgallery/custominputhandler.py | 1 + .../graphgallery/graphmodifier.py | 1 + .../graphgallery/highlightseries.py | 1 + .../datavisualization/graphgallery/main.py | 1 + .../graphgallery/rainfalldata.py | 1 + .../graphgallery/scatterdatamodifier.py | 1 + .../graphgallery/scattergraph.py | 1 + .../graphgallery/surfacegraph.py | 9 +- .../graphgallery/surfacegraphmodifier.py | 1 + .../graphgallery/topographicseries.py | 1 + .../graphgallery/variantbardatamapping.py | 1 + .../graphgallery/variantbardataproxy.py | 1 + .../graphgallery/variantdataset.py | 1 + .../datavisualization/minimalsurface/main.py | 3 +- .../qmlsurfacegallery/datasource.py | 1 + .../qmlsurfacegallery/main.py | 3 +- examples/datavisualization/surface/main.py | 9 +- .../datavisualization/surface/surfacegraph.py | 1 + .../surface_model_numpy/main.py | 9 +- .../surface_model_numpy/surfacegraph.py | 9 +- .../datavisualization/surface_numpy/main.py | 9 +- .../surface_numpy/surfacegraph.py | 1 + examples/dbus/listnames/doc/listnames.rst | 7 + examples/dbus/listnames/listnames.py | 1 + examples/dbus/pingpong/doc/pingpong.rst | 8 + examples/dbus/pingpong/ping.py | 1 + examples/dbus/pingpong/pong.py | 1 + .../colorpaletteclient/abstractresource.py | 1 + .../demos/colorpaletteclient/basiclogin.py | 1 + examples/demos/colorpaletteclient/main.py | 5 +- .../colorpaletteclient/paginatedresource.py | 15 +- .../demos/colorpaletteclient/restservice.py | 1 + .../demos/documentviewer/abstractviewer.py | 5 +- .../documentviewer/jsonviewer/jsonviewer.py | 34 +- examples/demos/documentviewer/main.py | 1 + examples/demos/documentviewer/mainwindow.py | 3 +- examples/demos/documentviewer/mainwindow.ui | 11 +- .../documentviewer/pdfviewer/pdfviewer.py | 17 +- .../documentviewer/pdfviewer/zoomselector.py | 1 + .../demos/documentviewer/recentfilemenu.py | 1 + examples/demos/documentviewer/recentfiles.py | 1 + .../documentviewer/txtviewer/txtviewer.py | 20 +- .../demos/documentviewer/ui_mainwindow.py | 36 +- .../demos/documentviewer/viewerfactory.py | 1 + .../demos/osmbuildings/OSMBuildings/Main.qml | 233 ++++ .../OSMBuildings/OSMCameraController.qml | 234 ++++ .../OSMBuildings/customshadertiles.frag | 10 + .../demos/osmbuildings/OSMBuildings/qmldir | 3 + .../demos/osmbuildings/doc/osmbuildings.rst | 53 + .../demos/osmbuildings/doc/osmbuildings.webp | Bin 0 -> 100144 bytes examples/demos/osmbuildings/main.py | 24 + examples/demos/osmbuildings/manager.py | 104 ++ .../demos/osmbuildings/osmbuildings.pyproject | 5 + examples/demos/osmbuildings/request.py | 128 ++ .../doc/taskmenuextension.rst | 2 - examples/designer/taskmenuextension/main.py | 1 + .../taskmenuextension/registertictactoe.py | 1 + .../designer/taskmenuextension/tictactoe.py | 9 +- .../taskmenuextension/tictactoeplugin.py | 1 + .../taskmenuextension/tictactoetaskmenu.py | 11 +- .../external/matplotlib/widget3d/widget3d.py | 10 +- .../widget_gaussian/widget_gaussian.py | 1 + examples/external/networkx/main.py | 29 +- .../opencv/webcam_pattern_detection.py | 14 +- examples/external/pandas/dataframe_model.py | 7 +- .../scikit/staining_colors_separation.py | 1 + .../2d/hellographs/HelloGraphs/Main.qml | 85 +- examples/graphs/2d/hellographs/main.py | 1 + examples/graphs/3d/bars/Bars/Axes.qml | 41 + examples/graphs/3d/bars/Bars/Data.qml | 118 ++ examples/graphs/3d/bars/Bars/Main.qml | 484 ++++++++ examples/graphs/3d/bars/Bars/qmldir | 4 + examples/graphs/3d/bars/bars.pyproject | 9 + examples/graphs/3d/bars/doc/bars-example.webp | Bin 0 -> 48706 bytes examples/graphs/3d/bars/doc/bars.rst | 12 + examples/graphs/3d/bars/main.py | 24 + .../graphs/3d/minimalsurfacegraph/main.py | 21 +- .../minimalsurfacegraph.pyproject | 3 + .../3d/widgetgallery/axesinputhandler.py | 100 -- .../3d/widgetgallery/custominputhandler.py | 177 --- .../3d/widgetgallery/doc/widgetgallery.rst | 11 - examples/graphs/3d/widgetgallery/main.py | 41 - .../3d/widgetgallery/scatterdatamodifier.py | 149 --- .../bargraph.py | 175 ++- .../data/layer_1.png | Bin .../data/layer_2.png | Bin .../data/layer_3.png | Bin .../data/license.txt | 0 .../data/maptexture.jpg | Bin .../data/narrowarrow.mesh | Bin .../data/oilrig.mesh | Bin .../data/pipe.mesh | Bin .../data/raindata.txt | 0 .../data/refinery.mesh | Bin .../data/topography.png | Bin .../doc/widgetgraphgallery.rst | 11 + .../doc/widgetgraphgallery.webp} | Bin .../graphmodifier.py | 99 +- .../highlightseries.py | 27 +- examples/graphs/3d/widgetgraphgallery/main.py | 50 + .../rainfalldata.py | 3 +- .../widgetgraphgallery/scatterdatamodifier.py | 199 +++ .../scattergraph.py | 52 +- .../surfacegraph.py | 84 +- .../surfacegraphmodifier.py | 269 ++-- .../topographicseries.py | 7 +- .../variantbardatamapping.py | 1 + .../variantbardataproxy.py | 1 + .../variantdataset.py | 1 + .../widgetgraphgallery.pyproject} | 2 - examples/gui/analogclock/main.py | 13 +- examples/gui/rhiwindow/doc/rhiwindow.rst | 2 - examples/gui/rhiwindow/main.py | 19 +- examples/gui/rhiwindow/rhiwindow.py | 107 +- .../afterrequest/doc/afterrequest.rst | 5 - .../simplehttpserver/doc/simplehttpserver.rst | 5 + .../main.py | 17 +- .../simplehttpserver.pyproject | 3 + examples/installer_test/hello.py | 3 +- .../MapViewer/forms/MessageForm.ui.qml | 1 - examples/location/mapviewer/main.py | 5 +- .../multimedia/audiooutput/audiooutput.py | 36 +- .../multimedia/audiosource/audiosource.py | 34 +- .../audiosource/doc/audiosource.rst | 3 +- examples/multimedia/camera/camera.py | 26 +- examples/multimedia/camera/camera.ui | 12 +- examples/multimedia/camera/camera_mobile.ui | 10 +- examples/multimedia/camera/imagesettings.py | 1 + examples/multimedia/camera/imagesettings.ui | 8 +- examples/multimedia/camera/main.py | 1 + examples/multimedia/camera/metadatadialog.py | 14 +- examples/multimedia/camera/ui_camera.py | 10 +- .../multimedia/camera/ui_camera_mobile.py | 12 +- .../multimedia/camera/ui_imagesettings.py | 8 +- .../multimedia/camera/ui_videosettings.py | 10 +- .../camera/ui_videosettings_mobile.py | 12 +- examples/multimedia/camera/videosettings.py | 1 + examples/multimedia/camera/videosettings.ui | 10 +- .../multimedia/camera/videosettings_mobile.ui | 10 +- examples/multimedia/player/player.py | 16 +- examples/multimedia/screencapture/main.py | 1 + .../screencapture/screencapturepreview.py | 7 +- .../screencapture/screenlistmodel.py | 3 +- .../screencapture/windowlistmodel.py | 3 +- .../blockingfortuneclient.py | 14 +- .../doc/blockingfortuneclient.png | Bin 0 -> 7013 bytes .../doc/blockingfortuneclient.rst | 11 + examples/network/downloader/downloader.py | 13 +- .../fortuneclient/doc/fortuneclient.png | Bin 0 -> 7814 bytes .../fortuneclient/doc/fortuneclient.rst | 11 + .../network/fortuneclient/fortuneclient.py | 13 +- .../fortuneserver/doc/fortuneserver.png | Bin 0 -> 6167 bytes .../fortuneserver/doc/fortuneserver.rst | 11 + .../network/fortuneserver/fortuneserver.py | 7 +- .../network/googlesuggest/googlesuggest.py | 47 +- examples/network/googlesuggest/main.py | 1 + examples/network/googlesuggest/searchbox.py | 1 + examples/network/loopback/dialog.py | 16 +- examples/network/loopback/main.py | 1 + .../doc/threadedfortuneserver.png | Bin 0 -> 5558 bytes .../doc/threadedfortuneserver.rst | 12 + .../threadedfortuneserver.py | 12 +- examples/networkauth/redditclient/main.py | 1 + .../networkauth/redditclient/redditmodel.py | 3 +- .../networkauth/redditclient/redditwrapper.py | 1 + examples/opengl/contextinfo/contextinfo.py | 21 +- examples/opengl/contextinfo/requirements.txt | 1 + examples/opengl/hellogl2/glwidget.py | 9 +- examples/opengl/hellogl2/logo.py | 1 + examples/opengl/hellogl2/main.py | 11 +- examples/opengl/hellogl2/mainwindow.py | 5 +- examples/opengl/hellogl2/requirements.txt | 1 + examples/opengl/hellogl2/window.py | 11 +- examples/opengl/textures/requirements.txt | 1 + examples/opengl/textures/textures.py | 7 +- .../opengl/threadedqopenglwidget/glwidget.py | 1 + examples/opengl/threadedqopenglwidget/main.py | 9 +- .../threadedqopenglwidget/mainwindow.py | 1 + .../opengl/threadedqopenglwidget/renderer.py | 5 +- .../threadedqopenglwidget/requirements.txt | 1 + examples/pdf/quickpdfviewer/main.py | 1 + examples/pdfwidgets/pdfviewer/main.py | 1 + examples/pdfwidgets/pdfviewer/mainwindow.py | 1 + examples/pdfwidgets/pdfviewer/mainwindow.ui | 6 +- .../pdfwidgets/pdfviewer/ui_mainwindow.py | 22 +- examples/pdfwidgets/pdfviewer/zoomselector.py | 1 + examples/qml/editingmodel/main.py | 7 +- examples/qml/editingmodel/model.py | 11 +- examples/qml/signals/pytoqml1/main.py | 31 - .../qml/signals/pytoqml1/pytoqml1.pyproject | 3 - examples/qml/signals/pytoqml1/view.qml | 36 - examples/qml/signals/pytoqml2/main.py | 54 - .../qml/signals/pytoqml2/pytoqml2.pyproject | 3 - examples/qml/signals/pytoqml2/view.qml | 30 - .../qml/signals/qmltopy1/doc/qmltopy1.rst | 13 + examples/qml/signals/qmltopy1/main.py | 3 +- .../qml/signals/qmltopy2/doc/qmltopy2.rst | 12 + examples/qml/signals/qmltopy2/main.py | 3 +- .../qml/signals/qmltopy3/doc/qmltopy3.rst | 10 + examples/qml/signals/qmltopy3/main.py | 3 +- .../qml/signals/qmltopy4/doc/qmltopy4.rst | 11 + examples/qml/signals/qmltopy4/main.py | 3 +- examples/qml/textproperties/main.py | 5 +- .../adding/doc/adding.rst | 2 - .../extending-qml-advanced/adding/main.py | 1 + .../extending-qml-advanced/adding/person.py | 1 + .../advanced1-Base-project/birthdayparty.py | 1 + .../doc/advanced1-Base-project.rst | 2 - .../advanced1-Base-project/main.py | 1 + .../advanced1-Base-project/person.py | 1 + .../birthdayparty.py | 1 + .../advanced2-Inheritance-and-coercion.rst | 9 +- .../main.py | 1 + .../person.py | 1 + .../birthdayparty.py | 1 + .../doc/advanced3-Default-properties.rst | 2 - .../advanced3-Default-properties/main.py | 1 + .../advanced3-Default-properties/person.py | 1 + .../birthdayparty.py | 1 + .../doc/advanced4-Grouped-properties.rst | 2 - .../advanced4-Grouped-properties/main.py | 1 + .../advanced4-Grouped-properties/person.py | 1 + .../birthdayparty.py | 1 + .../doc/advanced5-Attached-properties.rst | 2 - .../advanced5-Attached-properties/main.py | 1 + .../advanced5-Attached-properties/person.py | 1 + .../birthdayparty.py | 1 + .../doc/advanced6-Property-value-source.rst | 4 +- .../happybirthdaysong.py | 1 + .../advanced6-Property-value-source/main.py | 1 + .../advanced6-Property-value-source/person.py | 1 + .../binding/birthdayparty.py | 1 + .../binding/doc/binding.rst | 14 +- .../binding/happybirthdaysong.py | 1 + .../extending-qml-advanced/binding/main.py | 1 + .../extending-qml-advanced/binding/person.py | 1 + .../extended/doc/extended.rst | 13 +- .../extending-qml-advanced/extended/main.py | 1 + .../methods/birthdayparty.py | 1 + .../methods/doc/methods.rst | 10 +- .../extending-qml-advanced/methods/main.py | 1 + .../extending-qml-advanced/methods/person.py | 1 + .../properties/birthdayparty.py | 1 + .../properties/doc/properties.rst | 4 +- .../extending-qml-advanced/properties/main.py | 1 + .../properties/person.py | 1 + .../extending-qml/chapter1-basics/basics.py | 7 +- .../chapter1-basics/doc/chapter1-basics.rst | 31 +- .../chapter2-methods/doc/chapter2-methods.rst | 4 +- .../extending-qml/chapter2-methods/methods.py | 7 +- .../chapter3-bindings/bindings.py | 7 +- .../doc/chapter3-bindings.rst | 6 +- .../customPropertyTypes.py | 7 +- .../doc/chapter4-customPropertyTypes.rst | 11 +- .../doc/chapter5-listproperties.rst | 2 - .../chapter5-listproperties/listproperties.py | 7 +- .../chapter6-plugins/Charts/piechart.py | 1 + .../chapter6-plugins/Charts/pieslice.py | 1 + .../chapter6-plugins/doc/chapter6-plugins.rst | 8 +- examples/qml/usingmodel/usingmodel.py | 15 +- .../models/objectlistmodel/objectlistmodel.py | 3 +- .../models/stringlistmodel/stringlistmodel.py | 1 + examples/quick/painteditem/painteditem.py | 9 +- .../rendercontrol_opengl/cuberenderer.py | 5 +- .../rendercontrol_opengl/main.py | 3 +- .../rendercontrol_opengl/requirements.txt | 1 + .../window_singlethreaded.py | 3 +- .../quick/scenegraph/openglunderqml/main.py | 7 +- .../openglunderqml/requirements.txt | 1 + .../scenegraph/openglunderqml/squircle.py | 13 +- .../openglunderqml/squirclerenderer.py | 9 +- .../scenegraph_customgeometry/main.py | 9 +- examples/quick/shared/FlickrRssModel.qml | 1 - examples/quick/window/main.py | 1 + .../quick3d/customgeometry/examplepoint.py | 4 +- .../quick3d/customgeometry/exampletriangle.py | 8 +- examples/quick3d/customgeometry/main.py | 5 +- examples/quick3d/intro/main.py | 5 +- .../proceduraltexture/gradienttexture.py | 3 +- examples/quick3d/proceduraltexture/main.py | 6 +- .../contactslist/contactmodel.py | 9 +- examples/quickcontrols/contactslist/main.py | 5 +- .../doc/filesystemexplorer.rst | 2 - .../filesystemexplorer/editormodels.py | 1 + .../quickcontrols/filesystemexplorer/main.py | 5 +- examples/quickcontrols/gallery/gallery.py | 5 +- .../remoteobjects/modelview/doc/modelview.rst | 12 + .../modelview/modelviewclient.py | 1 + .../modelview/modelviewserver.py | 19 +- .../samplebinding/doc/samplebinding.pyproject | 3 +- examples/samplebinding/doc/samplebinding.rst | 3 +- examples/samplebinding/main.py | 1 + .../doc/scriptableapplication.pyproject | 4 +- examples/scriptableapplication/mainwindow.cpp | 14 + examples/scriptableapplication/mainwindow.h | 3 + examples/serialbus/can/bitratebox.py | 1 + examples/serialbus/can/canbusdeviceinfobox.py | 5 +- examples/serialbus/can/canbusdeviceinfobox.ui | 2 +- .../serialbus/can/canbusdeviceinfodialog.py | 1 + .../serialbus/can/canbusdeviceinfodialog.ui | 2 +- examples/serialbus/can/connectdialog.py | 20 +- examples/serialbus/can/connectdialog.ui | 8 +- examples/serialbus/can/main.py | 1 + examples/serialbus/can/mainwindow.py | 9 +- examples/serialbus/can/mainwindow.ui | 8 +- examples/serialbus/can/receivedframesmodel.py | 20 +- examples/serialbus/can/receivedframesview.py | 5 +- examples/serialbus/can/sendframebox.py | 1 + examples/serialbus/can/sendframebox.ui | 8 +- .../serialbus/can/ui_canbusdeviceinfobox.py | 4 +- .../can/ui_canbusdeviceinfodialog.py | 2 +- examples/serialbus/can/ui_connectdialog.py | 8 +- examples/serialbus/can/ui_mainwindow.py | 18 +- examples/serialbus/can/ui_sendframebox.py | 8 +- .../serialbus/modbus/modbusclient/main.py | 1 + .../modbus/modbusclient/mainwindow.py | 56 +- .../modbus/modbusclient/mainwindow.ui | 10 +- .../modbus/modbusclient/settingsdialog.py | 9 +- .../modbus/modbusclient/settingsdialog.ui | 6 +- .../modbus/modbusclient/ui_mainwindow.py | 16 +- .../modbus/modbusclient/ui_settingsdialog.py | 4 +- .../modbus/modbusclient/writeregistermodel.py | 26 +- examples/serialport/terminal/console.py | 9 +- examples/serialport/terminal/main.py | 1 + examples/serialport/terminal/mainwindow.py | 5 +- examples/serialport/terminal/mainwindow.ui | 2 +- .../serialport/terminal/settingsdialog.py | 49 +- .../serialport/terminal/settingsdialog.ui | 6 +- examples/serialport/terminal/ui_mainwindow.py | 14 +- .../serialport/terminal/ui_settingsdialog.py | 4 +- examples/spatialaudio/audiopanning/main.py | 41 +- .../speech/hello_speak/doc/hello_speak.rst | 1 - examples/speech/hello_speak/main.py | 1 + examples/speech/hello_speak/mainwindow.py | 1 + examples/speech/hello_speak/mainwindow.ui | 20 +- examples/speech/hello_speak/ui_mainwindow.py | 20 +- examples/sql/books/bookdelegate.py | 3 +- examples/sql/books/bookwindow.py | 12 +- examples/sql/books/bookwindow.ui | 2 +- examples/sql/books/createdb.py | 1 + examples/sql/books/main.py | 1 + examples/sql/books/ui_bookwindow.py | 4 +- .../sql/relationaltablemodel/connection.py | 1 + .../relationaltablemodel.py | 9 +- .../statemachine/moveblocks/moveblocks.py | 19 +- .../statemachine/ping_pong/doc/ping_pong.rst | 12 + .../statemachine/ping_pong/doc/pingpong.png | Bin 0 -> 7843 bytes examples/statemachine/ping_pong/ping_pong.py | 1 + examples/statemachine/rogue/doc/rogue.png | Bin 0 -> 10364 bytes examples/statemachine/rogue/doc/rogue.rst | 11 + examples/statemachine/rogue/rogue.py | 29 +- .../statemachine/trafficlight/trafficlight.py | 11 +- .../part1/Finance/AddDialog.qml | 128 ++ .../part1/Finance/FinanceDelegate.qml | 86 ++ .../part1/Finance/FinancePieChart.qml | 48 + .../part1/Finance/FinanceView.qml | 47 + .../finance_manager/part1/Finance/Main.qml | 128 ++ .../finance_manager/part1/Finance/qmldir | 6 + .../finance_manager/part1/doc/part1.md | 11 + .../finance_manager/part1/financemodel.py | 94 ++ .../tutorials/finance_manager/part1/main.py | 25 + .../finance_manager/part1/part1.pyproject | 12 + .../part2/Finance/AddDialog.qml | 129 ++ .../part2/Finance/FinanceDelegate.qml | 86 ++ .../part2/Finance/FinancePieChart.qml | 47 + .../part2/Finance/FinanceView.qml | 46 + .../finance_manager/part2/Finance/Main.qml | 128 ++ .../finance_manager/part2/Finance/qmldir | 6 + .../finance_manager/part2/database.py | 68 + .../finance_manager/part2/doc/part2.md | 11 + .../finance_manager/part2/financemodel.py | 101 ++ .../tutorials/finance_manager/part2/main.py | 30 + .../finance_manager/part2/part2.pyproject | 14 + .../finance_manager/part2/requirements.txt | 1 + .../finance_manager/part3/Backend/database.py | 62 + .../finance_manager/part3/Backend/main.py | 16 + .../finance_manager/part3/Backend/rest_api.py | 58 + .../part3/Frontend/Finance/AddDialog.qml | 129 ++ .../Frontend/Finance/FinanceDelegate.qml | 86 ++ .../Frontend/Finance/FinancePieChart.qml | 47 + .../part3/Frontend/Finance/FinanceView.qml | 46 + .../part3/Frontend/Finance/Main.qml | 128 ++ .../part3/Frontend/Finance/qmldir | 6 + .../part3/Frontend/financemodel.py | 109 ++ .../finance_manager/part3/Frontend/main.py | 26 + .../finance_manager/part3/doc/part3.md | 11 + .../finance_manager/part3/part3.pyproject | 16 + .../finance_manager/part3/requirements.txt | 3 + examples/uitools/uiloader/doc/uiloader.rst | 7 + examples/uitools/uiloader/uiloader.py | 3 +- examples/utils/pyside_config.py | 6 +- examples/webchannel/standalone/core.py | 1 + examples/webchannel/standalone/dialog.py | 1 + examples/webchannel/standalone/main.py | 8 +- examples/webchannel/standalone/ui_dialog.py | 2 +- .../standalone/websocketclientwrapper.py | 1 + .../standalone/websockettransport.py | 1 + .../nanobrowser/quicknanobrowser.py | 5 +- .../markdowneditor/doc/markdowneditor.png | Bin 0 -> 41883 bytes .../markdowneditor/doc/markdowneditor.rst | 11 + .../markdowneditor/document.py | 1 + .../webenginewidgets/markdowneditor/main.py | 1 + .../markdowneditor/mainwindow.py | 25 +- .../markdowneditor/mainwindow.ui | 9 +- .../markdowneditor/previewpage.py | 1 + .../markdowneditor/resources/index.html | 3 - .../markdowneditor/ui_mainwindow.py | 4 +- .../webenginewidgets/notifications/main.py | 6 +- .../notifications/notificationpopup.py | 7 +- .../webenginewidgets/simplebrowser/browser.py | 11 +- .../simplebrowser/browserwindow.py | 87 +- .../simplebrowser/certificateerrordialog.ui | 16 +- .../simplebrowser/downloadmanagerwidget.py | 1 + .../simplebrowser/downloadmanagerwidget.ui | 18 +- .../simplebrowser/downloadwidget.py | 1 + .../simplebrowser/downloadwidget.ui | 15 +- .../webenginewidgets/simplebrowser/main.py | 5 +- .../simplebrowser/passworddialog.ui | 8 +- .../simplebrowser/tabwidget.py | 23 +- .../ui_certificateerrordialog.py | 14 +- .../simplebrowser/ui_downloadmanagerwidget.py | 13 +- .../simplebrowser/ui_downloadwidget.py | 5 +- .../simplebrowser/ui_passworddialog.py | 10 +- .../webenginewidgets/simplebrowser/webpage.py | 1 + .../simplebrowser/webpopupwindow.py | 7 +- .../webenginewidgets/simplebrowser/webview.py | 9 +- .../widgetsnanobrowser/widgetsnanobrowser.py | 1 + .../webview/minibrowser/doc/minibrowser.rst | 8 + .../webview/minibrowser/doc/minibrowser.webp | Bin 0 -> 125798 bytes .../webview/minibrowser/images/left-32.png | Bin 0 -> 831 bytes .../webview/minibrowser/images/refresh-32.png | Bin 0 -> 1301 bytes .../webview/minibrowser/images/right-32.png | Bin 0 -> 822 bytes .../minibrowser/images/settings-32.png | Bin 0 -> 5947 bytes .../webview/minibrowser/images/stop-32.png | Bin 0 -> 639 bytes examples/webview/minibrowser/main.py | 60 + examples/webview/minibrowser/main.qml | 153 +++ .../webview/minibrowser/minibrowser.pyproject | 4 + examples/webview/minibrowser/qml.qrc | 9 + examples/webview/minibrowser/rc_qml.py | 368 ++++++ examples/widgetbinding/dialog.py | 1 + .../widgetbinding/doc/widgetbinding.pyproject | 5 +- examples/widgetbinding/main.py | 1 + .../widgetbinding/registerwigglywidget.py | 1 + examples/widgetbinding/wigglywidget.py | 1 + .../animation/animatedtiles/animatedtiles.py | 34 +- .../animation/appchooser/appchooser.py | 11 +- examples/widgets/animation/easing/easing.py | 28 +- examples/widgets/animation/easing/form.ui | 16 +- examples/widgets/animation/easing/ui_form.py | 26 +- examples/widgets/animation/states/states.py | 3 +- .../widgets/animation/states/states_rc.py | 1 + .../widgets/desktop/screenshot/screenshot.py | 22 +- .../widgets/desktop/systray/doc/systray.png | Bin 0 -> 16137 bytes .../widgets/desktop/systray/doc/systray.rst | 10 + examples/widgets/desktop/systray/main.py | 1 + examples/widgets/desktop/systray/window.py | 15 +- .../dialogs/classwizard/classwizard.py | 17 +- .../dialogs/classwizard/listchooser.py | 4 +- .../dialogs/extension/doc/extension.png | Bin 0 -> 13523 bytes .../dialogs/extension/doc/extension.rst | 11 + .../widgets/dialogs/extension/extension.py | 9 +- .../dialogs/licensewizard/licensewizard.py | 19 +- .../widgets/dialogs/licensewizard/main.py | 1 + .../standarddialogs/doc/standarddialogs.png | Bin 0 -> 28634 bytes .../standarddialogs/doc/standarddialogs.rst | 8 + .../standarddialogs/standarddialogs.py | 93 +- .../widgets/dialogs/tabdialog/tabdialog.py | 15 +- .../trivialwizard/doc/trivialwizard.png | Bin 0 -> 18190 bytes .../trivialwizard/doc/trivialwizard.rst | 11 + .../dialogs/trivialwizard/trivialwizard.py | 1 + .../draggableicons/draggableicons.py | 24 +- .../draggabletext/doc/draggabletext.png | Bin 0 -> 9329 bytes .../draggabletext/doc/draggabletext.rst | 8 + .../draggabletext/draggabletext.py | 18 +- .../widgets/draganddrop/dropsite/droparea.py | 17 +- .../draganddrop/dropsite/dropsitewindow.py | 13 +- examples/widgets/draganddrop/dropsite/main.py | 1 + .../widgets/effects/blurpicker/blureffect.py | 1 + .../widgets/effects/blurpicker/blurpicker.py | 13 +- examples/widgets/effects/blurpicker/main.py | 1 + .../widgets/effects/lighting/doc/lighting.rst | 11 + .../effects/lighting/doc/lighting.webp | Bin 0 -> 9430 bytes examples/widgets/effects/lighting/lighting.py | 19 +- examples/widgets/gettext/doc/gettext.rst | 2 - examples/widgets/gettext/main.py | 3 +- .../graphicsview/anchorlayout/anchorlayout.py | 55 +- .../anchorlayout/doc/anchorlayout.rst | 12 + .../anchorlayout/doc/anchorlayout.webp | Bin 0 -> 4234 bytes .../collidingmice/collidingmice.py | 21 +- .../collidingmice/doc/collidingmice.rst | 15 + .../collidingmice/doc/collidingmice.webp | Bin 0 -> 10604 bytes .../graphicsview/collidingmice/mice_rc.py | 1 + .../graphicsview/diagramscene/diagramscene.py | 77 +- .../diagramscene/doc/diagramscene.rst | 11 + .../diagramscene/doc/diagramscene.webp | Bin 0 -> 15208 bytes .../dragdroprobot/doc/dragdroprobot.png | Bin 0 -> 26994 bytes .../dragdroprobot/doc/dragdroprobot.rst | 12 + .../dragdroprobot/dragdroprobot.py | 45 +- .../elasticnodes/doc/elasticnodes.png | Bin 0 -> 20101 bytes .../elasticnodes/doc/elasticnodes.rst | 15 + .../graphicsview/elasticnodes/elasticnodes.py | 60 +- .../widgets/imageviewer/doc/imageviewer.rst | 12 + .../widgets/imageviewer/doc/imageviewer.webp | Bin 0 -> 49984 bytes examples/widgets/imageviewer/imageviewer.py | 38 +- examples/widgets/imageviewer/main.py | 1 + .../itemviews/address_book/adddialogwidget.py | 9 +- .../itemviews/address_book/address_book.py | 1 + .../itemviews/address_book/addresswidget.py | 21 +- .../itemviews/address_book/newaddresstab.py | 3 +- .../itemviews/address_book/tablemodel.py | 19 +- .../basicfiltermodel/basicsortfiltermodel.py | 15 +- .../basicfiltermodel/doc/basicfiltermodel.rst | 11 + .../doc/basicfiltermodel.webp | Bin 0 -> 38116 bytes examples/widgets/itemviews/dirview/dirview.py | 1 + .../doc/editabletreemodel.rst | 1 - .../itemviews/editabletreemodel/main.py | 1 + .../itemviews/editabletreemodel/mainwindow.py | 21 +- .../itemviews/editabletreemodel/treeitem.py | 1 + .../itemviews/editabletreemodel/treemodel.py | 18 +- .../itemviews/fetchmore/doc/fetchmore.png | Bin 0 -> 28481 bytes .../itemviews/fetchmore/doc/fetchmore.rst | 10 + .../widgets/itemviews/fetchmore/fetchmore.py | 17 +- .../widgets/itemviews/jsonmodel/jsonmodel.py | 21 +- .../spinboxdelegate/spinboxdelegate.py | 5 +- .../widgets/itemviews/spreadsheet/main.py | 3 +- .../itemviews/spreadsheet/spreadsheet.py | 34 +- .../spreadsheet/spreadsheetdelegate.py | 12 +- .../itemviews/spreadsheet/spreadsheetitem.py | 21 +- .../itemviews/stardelegate/stardelegate.py | 9 +- .../itemviews/stardelegate/stareditor.py | 1 + .../itemviews/stardelegate/starrating.py | 9 +- .../layouts/basiclayouts/basiclayouts.py | 4 +- .../layouts/borderlayout/borderlayout.py | 5 +- .../layouts/dynamiclayouts/dynamiclayouts.py | 15 +- .../widgets/layouts/flowlayout/flowlayout.py | 7 +- examples/widgets/linguist/doc/linguist.rst | 2 - examples/widgets/linguist/main.py | 5 +- .../mainwindows/application/application.py | 32 +- .../application/doc/application.png | Bin 0 -> 39984 bytes .../application/doc/application.rst | 12 + .../mainwindows/dockwidgets/dockwidgets.py | 36 +- examples/widgets/mainwindows/mdi/doc/mdi.png | Bin 0 -> 33375 bytes examples/widgets/mainwindows/mdi/doc/mdi.rst | 9 + examples/widgets/mainwindows/mdi/mdi.py | 41 +- .../painting/basicdrawing/basicdrawing.py | 120 +- .../concentriccircles/concentriccircles.py | 11 +- examples/widgets/painting/painter/painter.py | 45 +- examples/widgets/painting/plot/plot.py | 3 +- .../simplerhiwidget/doc/simplerhiwidget.rst | 4 +- .../rhi/simplerhiwidget/examplewidget.py | 21 +- examples/widgets/rhi/simplerhiwidget/main.py | 1 + .../richtext/orderform/doc/orderform.rst | 9 + .../richtext/orderform/doc/orderform.webp | Bin 0 -> 25490 bytes .../widgets/richtext/orderform/orderform.py | 22 +- .../syntaxhighlighter/syntaxhighlighter.py | 17 +- examples/widgets/richtext/textedit/main.py | 1 + .../widgets/richtext/textedit/textedit.py | 117 +- .../widgets/richtext/textobject/textobject.py | 5 +- .../thread_signals/doc/thread_signals.png | Bin 0 -> 2582 bytes .../thread_signals/doc/thread_signals.rst | 9 + .../widgets/thread_signals/thread_signals.py | 1 + .../regularexpression/regularexpression.py | 1 + .../regularexpressiondialog.py | 59 +- .../widgets/tutorials/addressbook/part1.py | 3 +- .../widgets/tutorials/addressbook/part2.py | 9 +- .../widgets/tutorials/addressbook/part3.py | 9 +- .../widgets/tutorials/addressbook/part4.py | 7 +- .../widgets/tutorials/addressbook/part5.py | 7 +- .../widgets/tutorials/addressbook/part6.py | 7 +- .../widgets/tutorials/addressbook/part7.py | 9 +- examples/widgets/tutorials/cannon/t1.py | 1 + examples/widgets/tutorials/cannon/t10.py | 9 +- examples/widgets/tutorials/cannon/t11.py | 15 +- examples/widgets/tutorials/cannon/t12.py | 21 +- examples/widgets/tutorials/cannon/t13.py | 31 +- examples/widgets/tutorials/cannon/t14.py | 47 +- examples/widgets/tutorials/cannon/t2.py | 3 +- examples/widgets/tutorials/cannon/t3.py | 3 +- examples/widgets/tutorials/cannon/t4.py | 3 +- examples/widgets/tutorials/cannon/t5.py | 5 +- examples/widgets/tutorials/cannon/t6.py | 5 +- examples/widgets/tutorials/cannon/t7.py | 5 +- examples/widgets/tutorials/cannon/t8.py | 5 +- examples/widgets/tutorials/cannon/t9.py | 9 +- .../widgets/tutorials/modelview/1_readonly.py | 5 +- .../tutorials/modelview/2_formatting.py | 19 +- .../tutorials/modelview/3_changingmodel.py | 7 +- .../widgets/tutorials/modelview/4_headers.py | 7 +- .../widgets/tutorials/modelview/5_edit.py | 7 +- .../widgets/tutorials/modelview/6_treeview.py | 1 + .../tutorials/modelview/7_selections.py | 3 +- .../widgets/charactermap/characterwidget.py | 12 +- .../widgets/charactermap/fontinfodialog.py | 3 +- examples/widgets/widgets/charactermap/main.py | 1 + .../widgets/charactermap/mainwindow.py | 13 +- .../widgets/digitalclock/digitalclock.py | 3 +- examples/widgets/widgets/tetrix/tetrix.py | 39 +- examples/widgets/widgetsgallery/main.py | 1 + .../widgets/widgetsgallery/widgetgallery.py | 21 +- examples/xml/dombookmarks/dombookmarks.py | 36 +- requirements-coin.txt | 12 + requirements-doc.txt | 13 +- requirements.txt | 18 +- setup.py | 1 + sources/pyside-tools/CMakeLists.txt | 20 +- sources/pyside-tools/android_deploy.py | 25 +- .../pyside-tools/cmake/PySideAndroid.cmake | 5 +- sources/pyside-tools/deploy.py | 61 +- sources/pyside-tools/deploy_lib/__init__.py | 8 + .../deploy_lib/android/__init__.py | 4 + .../deploy_lib/android/android_config.py | 120 +- .../deploy_lib/android/android_helper.py | 12 +- .../deploy_lib/android/android_utilities.py | 311 +++++ .../deploy_lib/android/buildozer.py | 6 +- .../android/recipes/PySide6/__init__.tmpl.py | 1 + .../recipes/shiboken6/__init__.tmpl.py | 1 + sources/pyside-tools/deploy_lib/commands.py | 23 +- sources/pyside-tools/deploy_lib/config.py | 389 +++--- sources/pyside-tools/deploy_lib/default.spec | 12 +- .../deploy_lib/dependency_util.py | 32 +- .../pyside-tools/deploy_lib/deploy_util.py | 87 +- .../pyside-tools/deploy_lib/nuitka_helper.py | 96 +- .../pyside-tools/deploy_lib/python_helper.py | 1 + sources/pyside-tools/metaobjectdump.py | 49 +- sources/pyside-tools/project.py | 129 +- sources/pyside-tools/project.pyproject | 4 +- .../{project => project_lib}/__init__.py | 2 + .../project_lib/design_studio_project.py | 65 + .../{project => project_lib}/newproject.py | 4 +- .../{project => project_lib}/project_data.py | 28 +- .../{project => project_lib}/utils.py | 34 +- sources/pyside-tools/pyside_tool.py | 10 +- sources/pyside-tools/qml.py | 10 +- sources/pyside-tools/qtpy2cpp.py | 1 + sources/pyside-tools/qtpy2cpp_lib/astdump.py | 1 + .../pyside-tools/qtpy2cpp_lib/formatter.py | 1 + sources/pyside-tools/qtpy2cpp_lib/nodedump.py | 1 + sources/pyside-tools/qtpy2cpp_lib/qt.py | 37 +- .../qtpy2cpp_lib/tests/baseline/basic_test.py | 1 + .../qtpy2cpp_lib/tests/test_qtpy2cpp.py | 1 + .../pyside-tools/qtpy2cpp_lib/tokenizer.py | 1 + sources/pyside-tools/qtpy2cpp_lib/visitor.py | 1 + sources/pyside-tools/requirements-android.txt | 1 + sources/pyside6/.cmake.conf | 4 +- .../Qt3DAnimation/typesystem_3danimation.xml | 4 +- .../PySide6/Qt3DCore/typesystem_3dcore.xml | 2 +- .../Qt3DExtras/typesystem_3dextras.xml | 2 +- .../PySide6/Qt3DInput/typesystem_3dinput.xml | 2 +- .../PySide6/Qt3DLogic/typesystem_3dlogic.xml | 2 +- .../Qt3DRender/typesystem_3drender.xml | 2 +- sources/pyside6/PySide6/QtAsyncio/__init__.py | 39 +- sources/pyside6/PySide6/QtAsyncio/events.py | 179 +-- sources/pyside6/PySide6/QtAsyncio/futures.py | 32 +- sources/pyside6/PySide6/QtAsyncio/tasks.py | 78 +- sources/pyside6/PySide6/QtCore/CMakeLists.txt | 4 + .../PySide6/QtCore/glue/core_snippets.cpp | 12 +- .../PySide6/QtCore/glue/qeasingcurve_glue.cpp | 3 +- .../PySide6/QtCore/glue/qeasingcurve_glue.h | 2 +- .../PySide6/QtCore/glue/qtcorehelper.cpp | 66 + .../PySide6/QtCore/typesystem_core_common.xml | 336 +++-- .../PySide6/QtDBus/typesystem_dbus.xml | 4 +- .../typesystem_datavisualization.xml | 2 +- .../qpydesignercustomwidgetcollection.cpp | 2 +- .../pyside6/PySide6/QtGraphs/CMakeLists.txt | 36 +- .../PySide6/QtGraphs/typesystem_graphs.xml | 200 +-- .../PySide6/QtGraphsWidgets/CMakeLists.txt | 54 + .../typesystem_graphswidgets.xml | 124 ++ sources/pyside6/PySide6/QtGui/CMakeLists.txt | 14 +- .../PySide6/QtGui/typesystem_gui_common.xml | 172 +-- .../PySide6/QtGui/typesystem_gui_rhi.xml | 1 + sources/pyside6/PySide6/QtHelp/CMakeLists.txt | 1 + .../PySide6/QtHelp/typesystem_help.xml | 11 +- .../PySide6/QtHttpServer/CMakeLists.txt | 1 + .../QtHttpServer/typesystem_httpserver.xml | 7 +- .../pyside6/PySide6/QtLocation/CMakeLists.txt | 1 + .../QtLocation/typesystem_location.xml | 3 + .../PySide6/QtMultimedia/CMakeLists.txt | 7 +- .../QtMultimedia/typesystem_multimedia.xml | 8 +- .../typesystem_multimediawidgets.xml | 2 +- .../pyside6/PySide6/QtNetwork/CMakeLists.txt | 3 + .../PySide6/QtNetwork/typesystem_network.xml | 73 +- .../PySide6/QtNetworkAuth/CMakeLists.txt | 1 + .../QtNetworkAuth/typesystem_networkauth.xml | 5 +- .../typesystem_openglwidgets.xml | 2 +- .../QtPdfWidgets/typesystem_pdfwidgets.xml | 3 +- .../typesystem_printsupport_common.xml | 3 +- .../PySide6/QtQml/pysideqmlvolatilebool.cpp | 1 + .../pyside6/PySide6/QtQml/typesystem_qml.xml | 25 +- .../pyside6/PySide6/QtQuick/CMakeLists.txt | 5 +- .../PySide6/QtQuick/typesystem_quick.xml | 5 +- .../PySide6/QtQuick3D/typesystem_quick3d.xml | 10 + .../typesystem_quickcontrols2.xml | 2 +- .../typesystem_quickwidgets.xml | 2 +- .../QtSerialBus/typesystem_serialbus.xml | 45 +- .../typesystem_spatialaudio.xml | 4 +- .../typesystem_statemachine.xml | 15 +- .../QtSvgWidgets/typesystem_svgwidgets.xml | 2 +- .../PySide6/QtTest/typesystem_test.xml | 2 +- .../PySide6/QtWebEngineCore/CMakeLists.txt | 15 +- .../QtWebEngineCore/glue/webengineframe.cpp | 26 + .../QtWebEngineCore/glue/webengineframe.h | 17 + .../glue/webenginepage_functors.cpp | 57 + .../glue/webenginepage_functors.h | 32 + .../typesystem_webenginecore.xml | 112 +- .../typesystem_webenginequick.xml | 3 +- .../PySide6/QtWebEngineWidgets/CMakeLists.txt | 3 - .../typesystem_webenginewidgets.xml | 7 +- .../pyside6/PySide6/QtWebView/CMakeLists.txt | 37 + .../PySide6/QtWebView/typesystem_webview.xml | 10 + .../QtWidgets/typesystem_widgets_common.xml | 28 +- .../pyside6/PySide6/QtXml/typesystem_xml.xml | 1 - sources/pyside6/PySide6/__init__.py.in | 63 +- sources/pyside6/PySide6/doc/qtcore.rst | 95 ++ .../doc/{qtqml_functions.rst => qtqml.rst} | 0 .../pyside6/PySide6/doc/qtwebenginecore.rst | 75 ++ sources/pyside6/PySide6/glue/qhttpserver.cpp | 23 +- sources/pyside6/PySide6/glue/qtcore.cpp | 211 ++-- sources/pyside6/PySide6/glue/qtgui.cpp | 52 +- sources/pyside6/PySide6/glue/qtnetwork.cpp | 12 +- .../pyside6/PySide6/glue/qtnetworkauth.cpp | 5 +- sources/pyside6/PySide6/glue/qtquick3d.cpp | 21 + sources/pyside6/PySide6/glue/qtserialbus.cpp | 18 +- .../pyside6/PySide6/glue/qtstatemachine.cpp | 3 +- sources/pyside6/PySide6/glue/qttest.cpp | 2 +- sources/pyside6/PySide6/glue/qtuitools.cpp | 4 +- .../pyside6/PySide6/glue/qtwebenginecore.cpp | 103 +- .../PySide6/glue/qtwebenginewidgets.cpp | 121 -- sources/pyside6/PySide6/glue/qtwidgets.cpp | 28 +- sources/pyside6/PySide6/glue/qtxml.cpp | 8 +- sources/pyside6/PySide6/qpytextobject.h | 2 - sources/pyside6/PySide6/qtcorehelper.h | 23 + sources/pyside6/PySide6/support/__init__.py | 1 + sources/pyside6/PySide6/support/deprecated.py | 1 + .../pyside6/PySide6/support/generate_pyi.py | 2 + sources/pyside6/PySide6/templates/common.xml | 2 +- .../pyside6/PySide6/templates/core_common.xml | 72 +- .../pyside6/PySide6/templates/gui_common.xml | 38 +- .../PySide6/templates/opengl_common.xml | 2 +- .../PySide6/templates/widgets_common.xml | 8 +- .../pyside6/cmake/Macros/PySideModules.cmake | 47 +- sources/pyside6/cmake/PySideHelpers.cmake | 43 +- sources/pyside6/cmake/PySideSetup.cmake | 5 +- sources/pyside6/doc/CMakeLists.txt | 20 +- .../doc/PySide6/QtAsyncio/coroutines.png | Bin 0 -> 48067 bytes .../pyside6/doc/PySide6/QtAsyncio/index.rst | 15 +- sources/pyside6/doc/_static/css/qt_style.css | 82 ++ sources/pyside6/doc/_tags/android.rst | 2 + sources/pyside6/doc/additionaldocs.lst | 1100 ++++++++--------- sources/pyside6/doc/api.rst | 40 +- .../index.rst | 38 +- .../linux.rst | 72 +- .../doc/building_from_source/macOS.rst | 121 ++ .../windows.rst | 57 +- sources/pyside6/doc/commercial/index.rst | 8 +- sources/pyside6/doc/conf.py.in | 36 +- sources/pyside6/doc/considerations.rst | 18 +- sources/pyside6/doc/contents.rst | 7 +- .../doc/deployment/deployment-cxfreeze.rst | 6 +- .../doc/deployment/deployment-nuitka.rst | 13 - .../deployment-pyside6-android-deploy.rst | 106 +- .../deployment/deployment-pyside6-deploy.rst | 40 +- sources/pyside6/doc/developer/add_module.rst | 8 +- .../doc/developer/add_port_example.rst | 4 + sources/pyside6/doc/developer/add_tool.rst | 2 +- .../pyside6/doc/developer/documentation.rst | 24 +- sources/pyside6/doc/developer/index.rst | 3 + .../doc/developer/mypy-correctness.rst | 188 +++ sources/pyside6/doc/developer/qtasyncio.rst | 67 + .../pyside6/doc/developer/signalmanager.md | 58 + .../pyside6/doc/developer/signature_doc.rst | 59 +- sources/pyside6/doc/extras/QtCharts.rst | 2 - .../pyside6/doc/extras/QtCore.ClassInfo.rst | 46 +- .../pyside6/doc/extras/QtCore.Property.rst | 177 +-- sources/pyside6/doc/extras/QtCore.QEnum.rst | 35 +- sources/pyside6/doc/extras/QtCore.QFlag.rst | 97 +- sources/pyside6/doc/extras/QtCore.Signal.rst | 78 +- sources/pyside6/doc/extras/QtCore.Slot.rst | 59 +- sources/pyside6/doc/extras/QtCore.rst | 14 +- .../doc/extras/QtDataVisualization.rst | 2 - ...Designer.QPyDesignerContainerExtension.rst | 11 +- ...gner.QPyDesignerCustomWidgetCollection.rst | 69 +- ...signer.QPyDesignerMemberSheetExtension.rst | 11 +- ...tDesigner.QPyDesignerTaskMenuExtension.rst | 11 +- sources/pyside6/doc/extras/QtGui.rst | 40 +- sources/pyside6/doc/extras/QtMultimedia.rst | 2 - sources/pyside6/doc/extras/QtNetwork.rst | 5 + sources/pyside6/doc/extras/QtOpenGL.rst | 1 - sources/pyside6/doc/extras/QtPositioning.rst | 2 - sources/pyside6/doc/extras/QtPrintSupport.rst | 5 + .../pyside6/doc/extras/QtQml.ListProperty.rst | 7 +- .../doc/extras/QtQml.QPyQmlParserStatus.rst | 6 +- .../QtQml.QPyQmlPropertyValueSource.rst | 6 +- .../pyside6/doc/extras/QtQml.QmlAnonymous.rst | 25 +- .../pyside6/doc/extras/QtQml.QmlAttached.rst | 51 +- .../pyside6/doc/extras/QtQml.QmlElement.rst | 33 +- .../pyside6/doc/extras/QtQml.QmlExtended.rst | 45 +- .../pyside6/doc/extras/QtQml.QmlForeign.rst | 37 +- .../doc/extras/QtQml.QmlNamedElement.rst | 32 +- .../pyside6/doc/extras/QtQml.QmlSingleton.rst | 45 +- .../doc/extras/QtQml.QmlUncreatable.rst | 32 +- sources/pyside6/doc/extras/QtQml.rst | 77 +- sources/pyside6/doc/extras/QtQuick.rst | 82 ++ .../pyside6/doc/extras/QtQuickControls2.rst | 2 +- sources/pyside6/doc/extras/QtQuickTest.rst | 3 +- sources/pyside6/doc/extras/QtSensors.rst | 2 - sources/pyside6/doc/extras/QtSql.rst | 5 + sources/pyside6/doc/extras/QtWebChannel.rst | 2 - sources/pyside6/doc/extras/QtWebSockets.rst | 2 - sources/pyside6/doc/extras/QtWebView.rst | 25 + sources/pyside6/doc/extras/QtWidgets.rst | 9 +- .../{gettingstarted => faq}/porting_from2.rst | 2 + sources/pyside6/doc/faq/typesoffiles.rst | 4 +- sources/pyside6/doc/faq/whatisqt.rst | 2 +- .../{quickstart.rst => gettingstarted.rst} | 152 ++- sources/pyside6/doc/gettingstarted/macOS.rst | 85 -- .../doc/images/screenshot_hello_quick.png | Bin 0 -> 30329 bytes ...hello.png => screenshot_hello_widgets.png} | Bin sources/pyside6/doc/index.rst | 29 +- sources/pyside6/doc/inheritance_graph.py | 3 +- sources/pyside6/doc/json_inheritance.py | 1 + sources/pyside6/doc/modules.rst | 205 +-- .../{gettingstarted => }/package_details.rst | 0 .../doc/{gettingstarted => }/packages.png | Bin sources/pyside6/doc/pyside-config.qdocconf.in | 16 + sources/pyside6/doc/qdoc_spawner.py.in | 19 +- .../pyside6/doc/qtattributionsscannertorst.py | 1 + .../doc/qtmodules/pyside-qtgraphs.qdocconf.in | 2 + .../qtmodules/pyside-qthttpserver.qdocconf.in | 2 + .../qtmodules/pyside-qtlocation.qdocconf.in | 2 + .../qtmodules/pyside-qtquick3d.qdocconf.in | 2 + .../pyside-qtstatemachine.qdocconf.in | 3 + .../qtmodules/pyside-qtwebview.qdocconf.in | 2 + sources/pyside6/doc/tools/index.rst | 30 +- sources/pyside6/doc/tools/pyside-designer.rst | 4 +- sources/pyside6/doc/tools/pyside-linguist.rst | 2 +- sources/pyside6/doc/tools/pyside-lrelease.rst | 4 +- sources/pyside6/doc/tools/pyside-lupdate.rst | 4 +- sources/pyside6/doc/tools/pyside-project.rst | 4 +- sources/pyside6/doc/tools/pyside-qml.rst | 20 +- sources/pyside6/doc/tools/pyside-rcc.rst | 3 +- sources/pyside6/doc/tools/pyside-uic.rst | 5 +- sources/pyside6/doc/tools/pyside6-balsam.rst | 3 +- .../pyside6/doc/tools/pyside6-balsamui.rst | 1 - .../doc/tools/pyside6-qmlimportscanner.rst | 2 +- sources/pyside6/doc/tools/pyside6-qsb.rst | 2 +- .../pyside6/doc/tools/pyside6-svgtoqml.rst | 16 + .../basictutorial/clickablebutton.rst | 3 + .../doc/tutorials/basictutorial/dialog.rst | 3 + .../doc/tutorials/basictutorial/qml.rst | 35 +- .../doc/tutorials/basictutorial/qrcfiles.rst | 106 +- .../basictutorial/signals_and_slots.rst | 55 +- .../tutorials/basictutorial/tablewidget.rst | 3 + .../tutorials/basictutorial/translations.rst | 9 +- .../tutorials/basictutorial/treewidget.rst | 3 + .../doc/tutorials/basictutorial/uifiles.rst | 5 +- .../doc/tutorials/basictutorial/widgets.rst | 4 +- .../tutorials/basictutorial/widgetstyling.py | 1 + .../tutorials/basictutorial/widgetstyling.rst | 3 +- .../doc/tutorials/datavisualize/add_chart.rst | 5 +- .../datavisualize/add_mainwindow.rst | 3 + .../tutorials/datavisualize/add_tableview.rst | 4 +- .../datavisualize/datavisualize1/main.py | 2 +- .../datavisualize/datavisualize2/main.py | 2 +- .../datavisualize/datavisualize3/main.py | 2 +- .../datavisualize3/main_window.py | 1 + .../datavisualize/datavisualize4/main.py | 2 +- .../datavisualize4/main_widget.py | 12 +- .../datavisualize4/main_window.py | 5 +- .../datavisualize4/table_model.py | 2 +- .../datavisualize/datavisualize5/main.py | 2 +- .../datavisualize5/main_widget.py | 1 + .../datavisualize5/main_window.py | 3 +- .../datavisualize5/table_model.py | 2 +- .../datavisualize/datavisualize6/main.py | 1 + .../datavisualize6/main_widget.py | 2 +- .../datavisualize6/main_window.py | 1 + .../datavisualize6/table_model.py | 1 + .../tutorials/datavisualize/filter_data.rst | 3 + .../doc/tutorials/datavisualize/index.rst | 3 + .../datavisualize/plot_datapoints.rst | 3 + .../doc/tutorials/datavisualize/read_data.rst | 3 + .../tutorials/debugging/mixed_debugging.rst | 6 +- .../doc/tutorials/debugging/qml_debugging.rst | 3 + .../debugging/qtcreator/qtcreator.rst | 1 - .../pyside6/doc/tutorials/embedded/boot2qt.md | 176 +++ .../tutorials/embedded/boot2qt_launcher.png | Bin 0 -> 42549 bytes .../doc/tutorials/expenses/expenses.rst | 3 + .../pyside6/doc/tutorials/expenses/main.py | 1 + .../doc/tutorials/expenses/main_snake_prop.py | 1 + .../tutorials/expenses/steps/01-expenses.py | 1 + .../tutorials/expenses/steps/02-expenses.py | 1 + .../tutorials/expenses/steps/03-expenses.py | 1 + .../tutorials/expenses/steps/04-expenses.py | 1 + .../tutorials/expenses/steps/05-expenses.py | 1 + .../tutorials/expenses/steps/06-expenses.py | 1 + .../tutorials/expenses/steps/07-expenses.py | 1 + .../tutorials/expenses/steps/08-expenses.py | 1 + .../tutorials/expenses/steps/09-expenses.py | 1 + .../tutorials/expenses/steps/10-expenses.py | 1 + .../extendedexplorer/editormodels.py | 1 + .../extendedexplorer/extendedexplorer.md | 4 +- .../doc/tutorials/extendedexplorer/main.py | 9 +- .../extendedexplorer/scheme_manager.py | 1 + .../doc/tutorials/finance_manager/index.md | 52 + .../tutorials/finance_manager/part1/part1.md | 400 ++++++ .../part1/resources/finance_manager.gif | Bin 0 -> 548595 bytes .../part1/resources/finance_manager.webp | Bin 0 -> 13644 bytes .../resources/finance_manager_android.gif | Bin 0 -> 870692 bytes .../tutorials/finance_manager/part2/part2.md | 116 ++ .../tutorials/finance_manager/part3/part3.md | 204 +++ sources/pyside6/doc/tutorials/index.rst | 75 +- .../modelviewprogramming/qlistview-dnd.py | 1 + .../modelviewprogramming/simplemodel-use.py | 1 + .../modelviewprogramming/stringlistmodel.py | 1 + .../portingguide/chapter1/createdb.py | 1 + .../tutorials/portingguide/chapter1/main.py | 1 + .../portingguide/chapter2/bookdelegate.py | 1 + .../portingguide/chapter2/createdb.py | 1 + .../tutorials/portingguide/chapter2/main.py | 1 + .../portingguide/chapter3/bookdelegate-old.py | 1 + .../portingguide/chapter3/bookdelegate.py | 1 + .../portingguide/chapter3/bookwindow.py | 1 + .../portingguide/chapter3/createdb.py | 1 + .../portingguide/chapter3/main-old.py | 1 + .../tutorials/portingguide/chapter3/main.py | 1 + .../tutorials/portingguide/hello_world_ex.py | 1 + .../doc/tutorials/portingguide/index.rst | 14 +- .../qmlapp/{view.qml => App/Main.qml} | 0 .../doc/tutorials/qmlapp/{ => App}/logo.png | Bin .../pyside6/doc/tutorials/qmlapp/App/qmldir | 2 + sources/pyside6/doc/tutorials/qmlapp/main.py | 23 +- .../doc/tutorials/qmlapp/qmlapplication.rst | 33 +- .../{view.qml => QmlIntegration/Main.qml} | 0 .../qmlintegration/QmlIntegration/qmldir | 2 + .../doc/tutorials/qmlintegration/main.py | 43 +- .../qmlintegration/qmlintegration.rst | 34 +- .../{chat.qml => App/Main.qml} | 4 +- .../tutorials/qmlsqlintegration/App/qmldir | 2 + .../tutorials/qmlsqlintegration/Main/qmldir | 2 + .../doc/tutorials/qmlsqlintegration/main.py | 22 +- .../qmlsqlintegration/qmlsqlintegration.rst | 51 +- .../tutorials/qmlsqlintegration/sqlDialog.py | 31 +- sources/pyside6/libpyside/CMakeLists.txt | 4 +- sources/pyside6/libpyside/class_property.cpp | 8 +- .../pyside6/libpyside/dynamicqmetaobject.cpp | 53 +- .../pyside6/libpyside/dynamicqmetaobject.h | 6 +- sources/pyside6/libpyside/dynamicslot.cpp | 418 +++++++ sources/pyside6/libpyside/dynamicslot_p.h | 50 + sources/pyside6/libpyside/feature_select.cpp | 29 +- .../pyside6/libpyside/globalreceiverv2.cpp | 323 ----- sources/pyside6/libpyside/globalreceiverv2.h | 115 -- sources/pyside6/libpyside/pyside.cpp | 153 ++- sources/pyside6/libpyside/pyside6.pc.in | 1 - .../libpyside/pysideclassdecorator.cpp | 6 +- .../libpyside/pysideclassdecorator_p.h | 8 +- sources/pyside6/libpyside/pysideclassinfo.cpp | 4 +- .../pyside6/libpyside/pysidemetafunction.cpp | 18 +- sources/pyside6/libpyside/pysideproperty.cpp | 100 +- sources/pyside6/libpyside/pysideproperty_p.h | 2 +- sources/pyside6/libpyside/pysideqenum.cpp | 10 +- .../pyside6/libpyside/pysideqslotobject_p.cpp | 30 +- .../pyside6/libpyside/pysideqslotobject_p.h | 27 +- sources/pyside6/libpyside/pysidesignal.cpp | 299 +++-- sources/pyside6/libpyside/pysidesignal.h | 15 +- sources/pyside6/libpyside/pysidesignal_p.h | 12 +- sources/pyside6/libpyside/pysideslot.cpp | 8 +- sources/pyside6/libpyside/pysideweakref.cpp | 4 +- sources/pyside6/libpyside/qobjectconnect.cpp | 183 ++- sources/pyside6/libpyside/qobjectconnect.h | 4 + sources/pyside6/libpyside/signalmanager.cpp | 517 +++----- sources/pyside6/libpyside/signalmanager.h | 38 +- .../libpysideqml/pysideqmlattached.cpp | 6 +- .../libpysideqml/pysideqmlextended.cpp | 4 +- .../pyside6/libpysideqml/pysideqmlforeign.cpp | 4 +- .../libpysideqml/pysideqmllistproperty.cpp | 33 +- .../libpysideqml/pysideqmlmetacallerror.cpp | 14 +- .../libpysideqml/pysideqmlregistertype.cpp | 22 +- .../libpysideqml/pysideqmluncreatable.cpp | 2 +- .../pyside6/plugins/designer/CMakeLists.txt | 2 +- .../designer/designercustomwidgets.cpp | 38 +- .../pyside6/plugins/uitools/customwidget.cpp | 2 +- sources/pyside6/pyside_version.py | 1 + sources/pyside6/qtexampleicons/CMakeLists.txt | 2 +- sources/pyside6/qtexampleicons/module.c | 1 - .../tests/Qt3DExtras/qt3dextras_test.py | 10 +- sources/pyside6/tests/QtAsyncio/bug_2790.py | 47 + sources/pyside6/tests/QtAsyncio/bug_2799.py | 30 + .../pyside6/tests/QtAsyncio/qasyncio_test.py | 1 + .../QtAsyncio/qasyncio_test_cancel_task.py | 1 + .../qasyncio_test_cancel_taskgroup.py | 15 +- .../tests/QtAsyncio/qasyncio_test_chain.py | 1 + .../tests/QtAsyncio/qasyncio_test_executor.py | 1 + .../tests/QtAsyncio/qasyncio_test_queues.py | 1 + .../QtAsyncio/qasyncio_test_threadsafe.py | 1 + .../tests/QtAsyncio/qasyncio_test_time.py | 1 + .../tests/QtAsyncio/qasyncio_test_uncancel.py | 61 + .../pyside6/tests/QtBluetooth/localdevice.py | 1 + .../QtBluetooth/lowenergy_characteristics.py | 1 + .../pyside6/tests/QtCharts/QtCharts.pyproject | 3 +- .../tests/QtCharts/qcharts_numpy_test.py | 2 +- .../pyside6/tests/QtCharts/qcharts_test.py | 5 +- .../pyside6/tests/QtCore/attr_cache_py3k.py | 1 + .../tests/QtCore/blocking_signals_test.py | 5 +- sources/pyside6/tests/QtCore/bug_1019.py | 1 + sources/pyside6/tests/QtCore/bug_1063.py | 3 +- sources/pyside6/tests/QtCore/bug_1069.py | 3 +- sources/pyside6/tests/QtCore/bug_1313.py | 1 + sources/pyside6/tests/QtCore/bug_278_test.py | 3 +- sources/pyside6/tests/QtCore/bug_300_test.py | 1 + sources/pyside6/tests/QtCore/bug_324.py | 1 + sources/pyside6/tests/QtCore/bug_332.py | 1 + sources/pyside6/tests/QtCore/bug_408.py | 3 +- sources/pyside6/tests/QtCore/bug_428.py | 1 + sources/pyside6/tests/QtCore/bug_462.py | 5 +- sources/pyside6/tests/QtCore/bug_505.py | 1 + sources/pyside6/tests/QtCore/bug_515.py | 1 + sources/pyside6/tests/QtCore/bug_606.py | 10 +- sources/pyside6/tests/QtCore/bug_656.py | 1 + sources/pyside6/tests/QtCore/bug_686.py | 1 + sources/pyside6/tests/QtCore/bug_699.py | 1 + sources/pyside6/tests/QtCore/bug_706.py | 1 + sources/pyside6/tests/QtCore/bug_820.py | 4 +- sources/pyside6/tests/QtCore/bug_826.py | 8 +- sources/pyside6/tests/QtCore/bug_829.py | 11 +- sources/pyside6/tests/QtCore/bug_835.py | 3 +- sources/pyside6/tests/QtCore/bug_920.py | 1 + sources/pyside6/tests/QtCore/bug_927.py | 1 + sources/pyside6/tests/QtCore/bug_931.py | 1 + sources/pyside6/tests/QtCore/bug_938.py | 3 +- sources/pyside6/tests/QtCore/bug_953.py | 1 + sources/pyside6/tests/QtCore/bug_987.py | 1 + sources/pyside6/tests/QtCore/bug_994.py | 3 +- .../pyside6/tests/QtCore/bug_PYSIDE-164.py | 3 +- .../pyside6/tests/QtCore/bug_PYSIDE-2745.py | 1 + sources/pyside6/tests/QtCore/bug_PYSIDE-41.py | 1 + sources/pyside6/tests/QtCore/bug_PYSIDE-42.py | 1 + .../pyside6/tests/QtCore/child_event_test.py | 4 +- .../pyside6/tests/QtCore/classinfo_test.py | 4 +- sources/pyside6/tests/QtCore/deepcopy_test.py | 3 +- .../pyside6/tests/QtCore/deletelater_test.py | 4 +- .../tests/QtCore/destroysignal_test.py | 2 +- .../tests/QtCore/duck_punching_test.py | 8 +- .../pyside6/tests/QtCore/emoji_string_test.py | 6 +- .../errormessages_with_features_test.py | 23 +- .../tests/QtCore/feature_with_uic/window.py | 2 +- .../tests/QtCore/feature_with_uic/window.ui | 2 +- .../tests/QtCore/feature_with_uic_test.py | 7 +- sources/pyside6/tests/QtCore/hash_test.py | 1 + sources/pyside6/tests/QtCore/inherits_test.py | 1 + .../QtCore/loggingcategorymacros_test.py | 1 + sources/pyside6/tests/QtCore/max_signals.py | 1 + .../tests/QtCore/missing_symbols_test.py | 1 + .../pyside6/tests/QtCore/mockclass_test.py | 4 +- .../tests/QtCore/multiple_feature_test.py | 1 + .../pyside6/tests/QtCore/python_conversion.py | 1 + sources/pyside6/tests/QtCore/qabs_test.py | 1 + .../tests/QtCore/qabstractitemmodel_test.py | 27 +- .../tests/QtCore/qanimationgroup_test.py | 1 + .../pyside6/tests/QtCore/qbitarray_test.py | 18 +- .../qbytearray_concatenation_operator_test.py | 3 +- .../QtCore/qbytearray_operator_iadd_test.py | 3 +- .../tests/QtCore/qbytearray_operator_test.py | 1 + .../pyside6/tests/QtCore/qbytearray_test.py | 3 +- .../pyside6/tests/QtCore/qcalendar_test.py | 1 + sources/pyside6/tests/QtCore/qcbor_test.py | 19 +- .../pyside6/tests/QtCore/qcollator_test.py | 18 +- .../tests/QtCore/qcommandlineparser_test.py | 4 +- .../QtCore/qcoreapplication_argv_test.py | 1 + .../QtCore/qcoreapplication_instance_test.py | 2 +- .../tests/QtCore/qcoreapplication_test.py | 1 + .../pyside6/tests/QtCore/qdatastream_test.py | 23 +- sources/pyside6/tests/QtCore/qdate_test.py | 1 + .../pyside6/tests/QtCore/qdatetime_test.py | 1 + sources/pyside6/tests/QtCore/qdir_test.py | 12 +- .../pyside6/tests/QtCore/qeasingcurve_test.py | 1 + sources/pyside6/tests/QtCore/qenum_test.py | 36 +- sources/pyside6/tests/QtCore/qevent_test.py | 13 +- sources/pyside6/tests/QtCore/qfile_test.py | 7 +- .../pyside6/tests/QtCore/qfileinfo_test.py | 4 +- .../pyside6/tests/QtCore/qfileread_test.py | 3 +- sources/pyside6/tests/QtCore/qflags_test.py | 60 +- sources/pyside6/tests/QtCore/qhandle_test.py | 3 +- .../tests/QtCore/qinstallmsghandler_test.py | 6 +- .../QtCore/qiodevice_buffered_read_test.py | 1 + sources/pyside6/tests/QtCore/qiopipe_test.py | 1 + .../tests/QtCore/qjsondocument_test.py | 1 + sources/pyside6/tests/QtCore/qlinef_test.py | 3 +- sources/pyside6/tests/QtCore/qlocale_test.py | 15 +- .../pyside6/tests/QtCore/qlockfile_test.py | 1 + .../QtCore/qmessageauthenticationcode_test.py | 4 +- .../pyside6/tests/QtCore/qmetaobject_test.py | 3 +- .../pyside6/tests/QtCore/qmetatype_test.py | 1 + .../tests/QtCore/qmimedatabase_test.py | 8 +- .../qmodelindex_internalpointer_test.py | 6 +- .../QtCore/qobject_children_segfault_test.py | 1 + .../QtCore/qobject_connect_notify_test.py | 4 +- .../tests/QtCore/qobject_destructor.py | 1 + .../tests/QtCore/qobject_event_filter_test.py | 1 + .../tests/QtCore/qobject_inherits_test.py | 5 +- .../QtCore/qobject_objectproperty_test.py | 4 +- .../tests/QtCore/qobject_parent_test.py | 9 +- .../tests/QtCore/qobject_property_test.py | 1 + .../QtCore/qobject_protected_methods_test.py | 7 +- sources/pyside6/tests/QtCore/qobject_test.py | 5 +- .../tests/QtCore/qobject_timer_event_test.py | 3 +- .../QtCore/qobject_tr_as_instance_test.py | 3 +- .../QtCore/qoperatingsystemversion_test.py | 1 + sources/pyside6/tests/QtCore/qpoint_test.py | 6 +- sources/pyside6/tests/QtCore/qprocess_test.py | 3 +- .../tests/QtCore/qproperty_decorator.py | 1 + .../tests/QtCore/qrandomgenerator_test.py | 1 + sources/pyside6/tests/QtCore/qrect_test.py | 1 + .../tests/QtCore/qregularexpression_test.py | 5 +- .../pyside6/tests/QtCore/qresource_test.py | 8 +- .../pyside6/tests/QtCore/qrunnable_test.py | 4 +- .../pyside6/tests/QtCore/qsettings_test.ini | 1 - .../pyside6/tests/QtCore/qsettings_test.py | 7 +- .../tests/QtCore/qsharedmemory_client.py | 4 +- .../tests/QtCore/qsharedmemory_test.py | 2 +- sources/pyside6/tests/QtCore/qsize_test.py | 2 +- .../pyside6/tests/QtCore/qslot_object_test.py | 1 + .../tests/QtCore/qsocketnotifier_test.py | 5 +- sources/pyside6/tests/QtCore/qsrand_test.py | 1 + .../tests/QtCore/qstandardpaths_test.py | 2 +- .../pyside6/tests/QtCore/qstorageinfo_test.py | 1 + sources/pyside6/tests/QtCore/qstring_test.py | 1 + sources/pyside6/tests/QtCore/qsysinfo_test.py | 9 +- .../pyside6/tests/QtCore/qtextstream_test.py | 8 +- .../tests/QtCore/qthread_prod_cons_test.py | 3 +- .../tests/QtCore/qthread_signal_test.py | 1 + sources/pyside6/tests/QtCore/qthread_test.py | 4 +- .../tests/QtCore/qtimer_singleshot_test.py | 1 + .../tests/QtCore/qtimer_timeout_test.py | 4 +- .../pyside6/tests/QtCore/qtimezone_test.py | 4 +- .../pyside6/tests/QtCore/qtnamespace_test.py | 1 + sources/pyside6/tests/QtCore/qurl_test.py | 11 +- .../pyside6/tests/QtCore/qurlquery_test.py | 2 +- sources/pyside6/tests/QtCore/quuid_test.py | 1 + .../tests/QtCore/qversionnumber_test.py | 1 + sources/pyside6/tests/QtCore/repr_test.py | 6 +- sources/pyside6/tests/QtCore/resources.qrc | 1 - sources/pyside6/tests/QtCore/resources_mc.py | 1 + .../tests/QtCore/setprop_on_ctor_test.py | 1 + sources/pyside6/tests/QtCore/signal_sender.py | 13 +- .../tests/QtCore/snake_prop_feature_test.py | 31 +- .../tests/QtCore/staticMetaObject_test.py | 1 + .../tests/QtCore/static_method_test.py | 2 +- .../tests/QtCore/thread_signals_test.py | 1 + sources/pyside6/tests/QtCore/tr_noop_test.py | 2 +- .../pyside6/tests/QtCore/translation_test.py | 2 +- .../tests/QtCore/unaryoperator_test.py | 4 +- sources/pyside6/tests/QtCore/unicode_test.py | 3 +- .../pyside6/tests/QtCore/versioninfo_test.py | 3 +- sources/pyside6/tests/QtDBus/test_dbus.py | 5 +- .../datavisualization_test.py | 1 + sources/pyside6/tests/QtGraphs/CMakeLists.txt | 2 +- .../pyside6/tests/QtGraphs/QtGraphs.pyproject | 3 + .../tests/QtGraphs/qgraphs_numpy_test.py | 49 + .../tests/QtGraphsWidgets/CMakeLists.txt | 1 + sources/pyside6/tests/QtGui/bug_1091.py | 1 + sources/pyside6/tests/QtGui/bug_367.py | 1 + sources/pyside6/tests/QtGui/bug_493.py | 9 +- sources/pyside6/tests/QtGui/bug_606.py | 1 + sources/pyside6/tests/QtGui/bug_617.py | 3 +- sources/pyside6/tests/QtGui/bug_652.py | 1 + sources/pyside6/tests/QtGui/bug_660.py | 1 + sources/pyside6/tests/QtGui/bug_716.py | 4 +- sources/pyside6/tests/QtGui/bug_740.py | 6 +- sources/pyside6/tests/QtGui/bug_743.py | 1 + sources/pyside6/tests/QtGui/bug_785.py | 1 + sources/pyside6/tests/QtGui/bug_991.py | 1 + sources/pyside6/tests/QtGui/bug_PYSIDE-344.py | 1 + sources/pyside6/tests/QtGui/deepcopy_test.py | 14 +- .../pyside6/tests/QtGui/event_filter_test.py | 3 +- .../float_to_int_implicit_conversion_test.py | 3 +- .../pyside6/tests/QtGui/pyside_reload_test.py | 3 +- sources/pyside6/tests/QtGui/qbrush_test.py | 15 +- .../pyside6/tests/QtGui/qcolor_reduce_test.py | 1 + sources/pyside6/tests/QtGui/qcolor_test.py | 9 +- sources/pyside6/tests/QtGui/qcursor_test.py | 6 +- .../QtGui/qdatastream_gui_operators_test.py | 9 +- .../tests/QtGui/qdesktopservices_test.py | 1 + sources/pyside6/tests/QtGui/qfont_test.py | 1 + .../pyside6/tests/QtGui/qfontmetrics_test.py | 87 +- .../tests/QtGui/qguiapplication_test.py | 1 + sources/pyside6/tests/QtGui/qicon_test.py | 1 + sources/pyside6/tests/QtGui/qimage_test.py | 7 +- .../pyside6/tests/QtGui/qimage_win_test.py | 5 +- .../tests/QtGui/qitemselection_test.py | 2 +- .../pyside6/tests/QtGui/qkeysequence_test.py | 12 +- sources/pyside6/tests/QtGui/qpainter_test.py | 14 +- .../pyside6/tests/QtGui/qpdfwriter_test.py | 5 +- sources/pyside6/tests/QtGui/qpen_test.py | 12 +- .../pyside6/tests/QtGui/qpixelformat_test.py | 12 +- .../tests/QtGui/qpixmap_constructor.py | 1 + sources/pyside6/tests/QtGui/qpixmap_test.py | 21 +- .../pyside6/tests/QtGui/qpixmapcache_test.py | 6 +- sources/pyside6/tests/QtGui/qpolygonf_test.py | 1 + .../tests/QtGui/qradialgradient_test.py | 1 + .../pyside6/tests/QtGui/qrasterwindow_test.py | 11 +- sources/pyside6/tests/QtGui/qregion_test.py | 1 + sources/pyside6/tests/QtGui/qshortcut_test.py | 5 +- .../tests/QtGui/qstandarditemmodel_test.py | 4 +- .../tests/QtGui/qstring_qkeysequence_test.py | 2 +- .../pyside6/tests/QtGui/qstylehints_test.py | 5 +- .../tests/QtGui/qtextdocument_functions.py | 7 +- .../QtGui/qtextdocument_undoredo_test.py | 2 +- .../tests/QtGui/qtextdocumentwriter_test.py | 3 +- sources/pyside6/tests/QtGui/qtextline_test.py | 4 +- .../pyside6/tests/QtGui/qtransform_test.py | 4 +- sources/pyside6/tests/QtGui/repr_test.py | 21 +- .../QtGui/timed_app_and_patching_test.py | 1 + sources/pyside6/tests/QtGui/xpm_data.py | 1 + sources/pyside6/tests/QtHelp/help_test.py | 1 + .../tests/QtHelp/helpsearchengine_test.py | 3 +- sources/pyside6/tests/QtLocation/location.py | 1 + .../pyside6/tests/QtMultimedia/audio_test.py | 11 +- .../QtMultimediaWidgets/qmultimediawidgets.py | 1 + .../tests/QtNetwork/accessManager_test.py | 1 + sources/pyside6/tests/QtNetwork/bug_1084.py | 1 + sources/pyside6/tests/QtNetwork/bug_446.py | 4 +- .../pyside6/tests/QtNetwork/dnslookup_test.py | 5 +- .../pyside6/tests/QtNetwork/qhostinfo_test.py | 3 +- .../tests/QtNetwork/qipv6address_test.py | 1 + .../tests/QtNetwork/qpassworddigestor_test.py | 5 +- .../pyside6/tests/QtNetwork/tcpserver_test.py | 1 + .../pyside6/tests/QtNetwork/udpsocket_test.py | 7 +- sources/pyside6/tests/QtNfc/test_nfc.py | 1 + .../tests/QtOpenGL/qopenglbuffer_test.py | 24 +- .../tests/QtOpenGL/qopenglwindow_test.py | 13 +- .../tests/QtPositioning/positioning.py | 1 + .../pyside6/tests/QtPrintSupport/bug_500.py | 1 + .../returnquadruplesofnumbers_test.py | 5 +- sources/pyside6/tests/QtQml/bug_1029.py | 4 +- sources/pyside6/tests/QtQml/bug_451.py | 1 + sources/pyside6/tests/QtQml/bug_456.py | 1 + sources/pyside6/tests/QtQml/bug_557.py | 5 +- sources/pyside6/tests/QtQml/bug_726.py | 1 + sources/pyside6/tests/QtQml/bug_814.py | 6 +- sources/pyside6/tests/QtQml/bug_814.qml | 1 - sources/pyside6/tests/QtQml/bug_825.py | 6 +- sources/pyside6/tests/QtQml/bug_825_old.py | 8 +- sources/pyside6/tests/QtQml/bug_847.py | 6 +- sources/pyside6/tests/QtQml/bug_847.qml | 1 - sources/pyside6/tests/QtQml/bug_915.py | 3 +- sources/pyside6/tests/QtQml/bug_926.py | 2 +- sources/pyside6/tests/QtQml/bug_951.py | 1 + sources/pyside6/tests/QtQml/bug_995.py | 6 +- sources/pyside6/tests/QtQml/bug_997.py | 3 +- .../pyside6/tests/QtQml/connect_python_qml.py | 12 +- .../pyside6/tests/QtQml/groupedproperty.py | 1 + .../tests/QtQml/javascript_exceptions.py | 13 +- sources/pyside6/tests/QtQml/listproperty.py | 1 + .../tests/QtQml/qmlregistertype_test.py | 1 + .../tests/QtQml/qqmlapplicationengine_test.py | 3 +- .../pyside6/tests/QtQml/qqmlcomponent_test.py | 1 + .../QtQml/qqmlincubator_incubateWhile.py | 17 +- .../pyside6/tests/QtQml/qqmlnetwork_test.py | 5 +- .../tests/QtQml/qquickitem_grabToImage.py | 1 + .../pyside6/tests/QtQml/qquickview_test.py | 5 +- .../pyside6/tests/QtQml/registerattached.py | 1 + .../pyside6/tests/QtQml/registerextended.py | 1 + .../pyside6/tests/QtQml/registerforeign.py | 14 +- .../tests/QtQml/registerparserstatus.py | 1 + .../pyside6/tests/QtQml/registerqmlfile.py | 1 + .../tests/QtQml/registersingletontype.py | 22 +- sources/pyside6/tests/QtQml/registertype.py | 15 +- .../tests/QtQml/registeruncreatabletype.py | 15 +- .../pyside6/tests/QtQml/signal_arguments.py | 13 +- sources/pyside6/tests/QtQml/signal_types.py | 1 + sources/pyside6/tests/QtQml/viewmodel.qml | 1 - .../tst_quicktestmainwithsetup.py | 1 + sources/pyside6/tests/QtScxml/test_dynamic.py | 3 +- sources/pyside6/tests/QtSensors/sensors.py | 5 +- sources/pyside6/tests/QtSerialPort/serial.py | 27 +- sources/pyside6/tests/QtSql/bug_1013.py | 1 + .../QtSql/qsqldatabaseandqueries_test.py | 11 +- .../pyside6/tests/QtSql/qvarianttype_test.py | 1 + .../pyside6/tests/QtStateMachine/bug_1031.py | 1 + .../qabstracttransition_test.py | 4 +- .../tests/QtStateMachine/qstate_test.py | 4 +- .../QtStateMachine/qstatemachine_test.py | 4 +- .../QtStateMachine/setprop_on_ctor_test.py | 1 + .../pyside6/tests/QtSvg/qsvggenerator_test.py | 2 +- .../pyside6/tests/QtSvg/qsvgrenderer_test.py | 6 +- .../tests/QtSvgWidgets/qsvgwidget_test.py | 6 +- sources/pyside6/tests/QtTest/click_test.py | 9 +- .../pyside6/tests/QtTest/eventfilter_test.py | 9 +- .../pyside6/tests/QtTest/qsignalspy_test.py | 5 +- .../pyside6/tests/QtTest/qvalidator_test.py | 15 +- .../pyside6/tests/QtTest/touchevent_test.py | 1 + .../QtTextToSpeech/qtexttospeech_test.py | 4 +- sources/pyside6/tests/QtUiTools/action.ui | 46 +- sources/pyside6/tests/QtUiTools/bug_1060.py | 1 + sources/pyside6/tests/QtUiTools/bug_360.py | 4 +- sources/pyside6/tests/QtUiTools/bug_376.py | 2 +- sources/pyside6/tests/QtUiTools/bug_392.py | 2 +- sources/pyside6/tests/QtUiTools/bug_426.py | 8 +- sources/pyside6/tests/QtUiTools/bug_552.py | 4 +- sources/pyside6/tests/QtUiTools/bug_797.py | 2 +- sources/pyside6/tests/QtUiTools/bug_909.py | 1 + sources/pyside6/tests/QtUiTools/bug_909.ui | 2 +- sources/pyside6/tests/QtUiTools/bug_913.py | 1 + sources/pyside6/tests/QtUiTools/bug_958.py | 1 + sources/pyside6/tests/QtUiTools/bug_965.py | 1 + sources/pyside6/tests/QtUiTools/bug_965.ui | 32 +- .../tests/QtUiTools/loadUiType_test.py | 2 +- sources/pyside6/tests/QtUiTools/minimal.ui | 10 + .../tests/QtUiTools/pycustomwidget2.ui | 6 +- sources/pyside6/tests/QtUiTools/test.ui | 4 +- sources/pyside6/tests/QtUiTools/ui_test.py | 1 + .../pyside6/tests/QtUiTools/uiloader_test.py | 5 +- .../qwebenginecookiestore_test.py | 1 + .../web_engine_custom_scheme.py | 16 +- .../pyside-474-qtwebengineview.py | 11 +- .../pyside6/tests/QtWebView/CMakeLists.txt | 5 + .../tests/QtWebView/QtWebView.pyproject | 3 + .../pyside6/tests/QtWebView/test_webview.py | 33 + .../pyside6/tests/QtWidgets/action_clear.py | 1 + .../tests/QtWidgets/add_action_test.py | 10 +- sources/pyside6/tests/QtWidgets/api2_test.py | 5 +- .../tests/QtWidgets/application_test.py | 10 +- sources/pyside6/tests/QtWidgets/bug_1002.py | 1 + sources/pyside6/tests/QtWidgets/bug_1006.py | 5 +- sources/pyside6/tests/QtWidgets/bug_1048.py | 1 + sources/pyside6/tests/QtWidgets/bug_1077.py | 3 +- sources/pyside6/tests/QtWidgets/bug_172.py | 1 + sources/pyside6/tests/QtWidgets/bug_243.py | 1 + sources/pyside6/tests/QtWidgets/bug_307.py | 2 +- sources/pyside6/tests/QtWidgets/bug_338.py | 1 + sources/pyside6/tests/QtWidgets/bug_389.py | 3 +- sources/pyside6/tests/QtWidgets/bug_400.py | 1 + sources/pyside6/tests/QtWidgets/bug_429.py | 1 + sources/pyside6/tests/QtWidgets/bug_430.py | 1 + sources/pyside6/tests/QtWidgets/bug_433.py | 1 + sources/pyside6/tests/QtWidgets/bug_467.py | 1 + sources/pyside6/tests/QtWidgets/bug_480.py | 4 +- sources/pyside6/tests/QtWidgets/bug_512.py | 1 + sources/pyside6/tests/QtWidgets/bug_525.py | 1 + sources/pyside6/tests/QtWidgets/bug_546.py | 1 + sources/pyside6/tests/QtWidgets/bug_547.py | 3 +- sources/pyside6/tests/QtWidgets/bug_549.py | 4 +- sources/pyside6/tests/QtWidgets/bug_569.py | 1 + sources/pyside6/tests/QtWidgets/bug_575.py | 5 +- sources/pyside6/tests/QtWidgets/bug_576.py | 4 +- sources/pyside6/tests/QtWidgets/bug_585.py | 6 +- sources/pyside6/tests/QtWidgets/bug_589.py | 1 + sources/pyside6/tests/QtWidgets/bug_635.py | 1 + sources/pyside6/tests/QtWidgets/bug_640.py | 1 + sources/pyside6/tests/QtWidgets/bug_653.py | 1 + sources/pyside6/tests/QtWidgets/bug_662.py | 1 + sources/pyside6/tests/QtWidgets/bug_667.py | 1 + sources/pyside6/tests/QtWidgets/bug_668.py | 1 + sources/pyside6/tests/QtWidgets/bug_674.py | 4 +- sources/pyside6/tests/QtWidgets/bug_675.py | 1 + sources/pyside6/tests/QtWidgets/bug_688.py | 7 +- sources/pyside6/tests/QtWidgets/bug_693.py | 1 + sources/pyside6/tests/QtWidgets/bug_696.py | 4 +- sources/pyside6/tests/QtWidgets/bug_711.py | 2 +- sources/pyside6/tests/QtWidgets/bug_722.py | 1 + sources/pyside6/tests/QtWidgets/bug_728.py | 7 +- sources/pyside6/tests/QtWidgets/bug_736.py | 7 +- sources/pyside6/tests/QtWidgets/bug_750.py | 3 +- sources/pyside6/tests/QtWidgets/bug_778.py | 1 + sources/pyside6/tests/QtWidgets/bug_793.py | 1 + sources/pyside6/tests/QtWidgets/bug_811.py | 1 + sources/pyside6/tests/QtWidgets/bug_834.py | 5 +- sources/pyside6/tests/QtWidgets/bug_836.py | 9 +- sources/pyside6/tests/QtWidgets/bug_844.py | 1 + sources/pyside6/tests/QtWidgets/bug_854.py | 4 +- sources/pyside6/tests/QtWidgets/bug_860.py | 1 + sources/pyside6/tests/QtWidgets/bug_862.py | 1 + sources/pyside6/tests/QtWidgets/bug_871.py | 2 +- sources/pyside6/tests/QtWidgets/bug_879.py | 4 +- sources/pyside6/tests/QtWidgets/bug_919.py | 3 +- sources/pyside6/tests/QtWidgets/bug_921.py | 5 +- sources/pyside6/tests/QtWidgets/bug_941.py | 8 +- sources/pyside6/tests/QtWidgets/bug_964.py | 6 +- sources/pyside6/tests/QtWidgets/bug_967.py | 1 + sources/pyside6/tests/QtWidgets/bug_972.py | 14 +- sources/pyside6/tests/QtWidgets/bug_979.py | 1 + sources/pyside6/tests/QtWidgets/bug_988.py | 1 + sources/pyside6/tests/QtWidgets/bug_998.py | 1 + .../tests/QtWidgets/customproxywidget_test.py | 13 +- .../QtWidgets/grandparent_method_test.py | 1 + .../tests/QtWidgets/hashabletype_test.py | 2 +- .../pyside6/tests/QtWidgets/import_test.py | 2 +- .../tests/QtWidgets/keep_reference_test.py | 4 +- .../tests/QtWidgets/missing_symbols_test.py | 1 + .../tests/QtWidgets/paint_event_test.py | 5 +- .../tests/QtWidgets/parent_method_test.py | 2 +- .../tests/QtWidgets/private_mangle_test.py | 14 +- .../tests/QtWidgets/python_properties_test.py | 1 + .../qabstracttextdocumentlayout_test.py | 7 +- .../tests/QtWidgets/qaccessible_test.py | 7 +- .../pyside6/tests/QtWidgets/qaction_test.py | 2 +- .../pyside6/tests/QtWidgets/qapp_issue_585.py | 1 + sources/pyside6/tests/QtWidgets/qapp_test.py | 1 + .../qapplication_exit_segfault_test.py | 5 +- .../tests/QtWidgets/qapplication_test.py | 1 + .../pyside6/tests/QtWidgets/qcolormap_test.py | 1 + .../pyside6/tests/QtWidgets/qdialog_test.py | 1 + .../tests/QtWidgets/qdynamic_signal.py | 11 +- .../tests/QtWidgets/qfontdialog_test.py | 1 + .../tests/QtWidgets/qformlayout_test.py | 7 +- .../QtWidgets/qgraphicsitem_isblocked_test.py | 1 + .../tests/QtWidgets/qgraphicsitem_test.py | 1 + .../QtWidgets/qgraphicsobjectreimpl_test.py | 1 + .../QtWidgets/qgraphicsproxywidget_test.py | 13 +- .../tests/QtWidgets/qgraphicsscene_test.py | 3 +- .../tests/QtWidgets/qinputdialog_get_test.py | 2 +- .../tests/QtWidgets/qkeysequenceedit_test.py | 1 + .../pyside6/tests/QtWidgets/qlabel_test.py | 1 + .../tests/QtWidgets/qlayout_ref_test.py | 9 +- .../pyside6/tests/QtWidgets/qlayout_test.py | 21 +- .../tests/QtWidgets/qlcdnumber_test.py | 1 + .../tests/QtWidgets/qlistwidget_test.py | 5 +- .../tests/QtWidgets/qlistwidgetitem_test.py | 1 + .../tests/QtWidgets/qmainwindow_test.py | 6 +- sources/pyside6/tests/QtWidgets/qmenu_test.py | 1 + .../pyside6/tests/QtWidgets/qmenuadd_test.py | 1 + .../tests/QtWidgets/qobject_mi_test.py | 6 +- .../pyside6/tests/QtWidgets/qpicture_test.py | 2 +- .../tests/QtWidgets/qpushbutton_test.py | 6 +- .../pyside6/tests/QtWidgets/qsplitter_test.py | 2 +- .../pyside6/tests/QtWidgets/qstyle_test.py | 6 +- .../tests/QtWidgets/qstyleoption_test.py | 1 + .../tests/QtWidgets/qtableview_test.py | 2 +- .../tests/QtWidgets/qtabwidget_test.py | 1 + .../tests/QtWidgets/qtabwidgetclear_test.py | 1 + .../tests/QtWidgets/qtextedit_signal_test.py | 2 +- .../pyside6/tests/QtWidgets/qtextedit_test.py | 1 + .../pyside6/tests/QtWidgets/qtoolbar_test.py | 1 + .../pyside6/tests/QtWidgets/qtoolbox_test.py | 1 + .../pyside6/tests/QtWidgets/qtreeview_test.py | 3 +- .../tests/QtWidgets/qtreewidget_test.py | 1 + .../tests/QtWidgets/qtreewidgetitem_test.py | 5 +- .../pyside6/tests/QtWidgets/qvariant_test.py | 7 +- .../tests/QtWidgets/qwidget_setlayout_test.py | 4 +- .../pyside6/tests/QtWidgets/qwidget_test.py | 2 +- .../tests/QtWidgets/reference_count_test.py | 6 +- .../pyside6/tests/QtWidgets/signature_test.py | 1 + .../tests/QtWidgets/standardpixmap_test.py | 4 +- .../tests/QtWidgets/test_module_template.py | 2 +- .../virtual_protected_inheritance_test.py | 2 +- .../QtWidgets/virtual_pure_override_test.py | 6 +- .../tests/QtWidgets/wrong_return_test.py | 1 + .../pyside6/tests/QtXml/qdomdocument_test.py | 2 +- sources/pyside6/tests/init_paths.py | 1 + sources/pyside6/tests/mac/qmacstyle_test.py | 4 +- sources/pyside6/tests/manually/bug_841.py | 1 + sources/pyside6/tests/manually/lazytiming.py | 1 + sources/pyside6/tests/manually/sizebench.py | 151 +++ .../pyside6/tests/pysidetest/CMakeLists.txt | 5 +- .../tests/pysidetest/all_modules_load_test.py | 13 +- sources/pyside6/tests/pysidetest/bug_1016.py | 1 + sources/pyside6/tests/pysidetest/connection.h | 21 + .../pysidetest/constructor_properties_test.py | 39 +- .../tests/pysidetest/container_test.py | 1 + .../tests/pysidetest/decoratedslot_test.py | 2 +- .../pysidetest/delegatecreateseditor_test.py | 10 +- sources/pyside6/tests/pysidetest/enum_test.py | 63 +- .../homonymoussignalandmethod_test.py | 2 +- .../pyside6/tests/pysidetest/iterable_test.py | 1 + .../tests/pysidetest/list_signal_test.py | 2 +- .../pysidetest/mixin_signal_slots_test.py | 6 +- .../tests/pysidetest/mock_as_slot_test.py | 1 + .../tests/pysidetest/modelview_test.py | 2 +- .../pysidetest/multiple_inheritance_test.py | 7 +- .../tests/pysidetest/mypy_correctness_test.py | 68 + .../new_inherited_functions_test.py | 85 +- sources/pyside6/tests/pysidetest/notify_id.py | 1 + .../tests/pysidetest/properties_test.py | 1 + .../tests/pysidetest/property_python_test.py | 10 +- ...yenum_relax_options_and_lazy_star_test.py} | 23 +- .../tests/pysidetest/pysidetest_global.h | 1 + .../pysidetest/qapp_like_a_macro_test.py | 15 +- .../pyside6/tests/pysidetest/qvariant_test.py | 14 +- sources/pyside6/tests/pysidetest/repr_test.py | 2 +- .../tests/pysidetest/shared_pointer_test.py | 2 +- .../tests/pysidetest/signal_slot_warning.py | 1 + .../pysidetest/signal_tp_descr_get_test.py | 1 + .../pysidetest/signalandnamespace_test.py | 2 +- .../signalemissionfrompython_test.py | 29 +- .../signalinstance_equality_test.py | 1 + .../pysidetest/signalwithdefaultvalue_test.py | 1 + .../tests/pysidetest/snake_case_sub.py | 8 +- .../tests/pysidetest/snake_case_test.py | 9 +- .../pyside6/tests/pysidetest/testobject.cpp | 7 + sources/pyside6/tests/pysidetest/testobject.h | 5 +- sources/pyside6/tests/pysidetest/testview.h | 1 - .../tests/pysidetest/true_property_test.py | 3 +- .../tests/pysidetest/typedef_signal_test.py | 1 + .../pysidetest/typesystem_pysidetest.xml | 3 +- .../pyside6/tests/pysidetest/version_test.py | 2 +- .../pyside6/tests/registry/existence_test.py | 1 + .../pyside6/tests/registry/init_platform.py | 31 +- .../tests/registry/scrape_testresults.py | 36 +- sources/pyside6/tests/registry/util.py | 2 +- .../tests/signals/anonymous_slot_leak_test.py | 1 + .../tests/signals/args_dont_match_test.py | 1 + sources/pyside6/tests/signals/bug_189.py | 1 + sources/pyside6/tests/signals/bug_311.py | 1 + sources/pyside6/tests/signals/bug_312.py | 1 + sources/pyside6/tests/signals/bug_319.py | 1 + sources/pyside6/tests/signals/bug_79.py | 1 + .../pyside6/tests/signals/decorators_test.py | 1 + .../pyside6/tests/signals/disconnect_test.py | 1 + .../tests/signals/invalid_callback_test.py | 1 + .../pyside6/tests/signals/lambda_gui_test.py | 1 + sources/pyside6/tests/signals/lambda_test.py | 1 + .../tests/signals/leaking_signal_test.py | 1 + .../signals/multiple_connections_gui_test.py | 1 + .../signals/multiple_connections_test.py | 1 + .../pyside6/tests/signals/pysignal_test.py | 5 +- .../signals/qobject_callable_connect_test.py | 1 + .../tests/signals/qobject_destroyed_test.py | 1 + .../tests/signals/qobject_receivers_test.py | 11 +- .../tests/signals/qobject_sender_test.py | 1 + sources/pyside6/tests/signals/ref01_test.py | 1 + sources/pyside6/tests/signals/ref02_test.py | 1 + sources/pyside6/tests/signals/ref03_test.py | 1 + sources/pyside6/tests/signals/ref04_test.py | 1 + sources/pyside6/tests/signals/ref05_test.py | 1 + sources/pyside6/tests/signals/ref06_test.py | 1 + .../signals/segfault_proxyparent_test.py | 1 + .../tests/signals/self_connect_test.py | 1 + .../tests/signals/short_circuit_test.py | 1 + .../signals/signal2signal_connect_test.py | 1 + .../tests/signals/signal_across_threads.py | 1 + .../tests/signals/signal_autoconnect_test.py | 1 + .../signal_connectiontype_support_test.py | 3 +- .../tests/signals/signal_emission_gui_test.py | 1 + .../tests/signals/signal_emission_test.py | 43 +- .../pyside6/tests/signals/signal_enum_test.py | 1 + .../pyside6/tests/signals/signal_func_test.py | 1 + .../signals/signal_manager_refcount_test.py | 1 + .../tests/signals/signal_newenum_test.py | 5 +- .../tests/signals/signal_number_limit_test.py | 1 + .../tests/signals/signal_object_test.py | 3 +- .../tests/signals/signal_signature_test.py | 4 +- .../signal_with_primitive_type_test.py | 1 + .../signals/slot_reference_count_test.py | 1 + .../tests/signals/static_metaobject_test.py | 1 + sources/pyside6/tests/support/voidptr_test.py | 1 + .../tests/tools/list-class-hierarchy.py | 33 +- .../metaobjectdump/test_metaobjectdump.py | 1 + .../extensive_android_deploy_test.py | 1 + .../test_pyside6_android_deploy.py | 53 +- .../pyside6-deploy/extensive_deploy_test.py | 1 + .../pyside6-deploy/test_pyside6_deploy.py | 240 +++- .../tools/pyside6-project/CMakeLists.txt | 1 + .../example_drumpad/Drumpad.qmlproject | 58 + .../example_drumpad/Drumpad.qrc | 12 + .../example_drumpad/Drumpad/TestComponent.qml | 11 + .../example_drumpad/Drumpad/qmldir | 3 + .../example_drumpad/DrumpadContent/App.qml | 20 + .../DrumpadContent/MainScreen.ui.qml | 19 + .../example_drumpad/DrumpadContent/qmldir | 3 + .../example_drumpad/Python/.pyproject | 3 + .../Python/autogen/settings.py | 39 + .../example_drumpad/Python/main.py | 26 + .../example_drumpad/Resources/resource1.txt | 1 + .../example_drumpad/Resources/resource2.txt | 1 + .../example_drumpad/qtquickcontrols2.conf | 6 + .../pyside6-project/test_pyside6_project.py | 158 +++ .../tools/pyside6-qml/test_pyside6_qml.py | 1 + sources/pyside6/tests/util/color.py | 1 + .../tests/util/helper/basicpyslotcase.py | 3 +- .../pyside6/tests/util/helper/docmodifier.py | 3 +- sources/pyside6/tests/util/helper/helper.py | 1 + .../tests/util/helper/timedqapplication.py | 1 + .../tests/util/helper/timedqguiapplication.py | 1 + .../tests/util/helper/usesqapplication.py | 1 + sources/pyside6/tests/util/httpd.py | 13 +- sources/pyside6/tests/util/processtimer.py | 3 +- .../pyside6/tests/util/test_processtimer.py | 1 + .../pyside6/tests/util/valgrind-python.supp | 2 - sources/shiboken6/.cmake.conf | 4 +- sources/shiboken6/AUTHORS | 1 - sources/shiboken6/ApiExtractor/AUTHORS | 1 - sources/shiboken6/ApiExtractor/CMakeLists.txt | 1 + .../ApiExtractor/abstractmetabuilder.cpp | 170 +-- .../abstractmetabuilder_helpers.cpp | 8 +- .../ApiExtractor/abstractmetabuilder_p.h | 25 +- .../ApiExtractor/abstractmetaenum.cpp | 2 +- .../ApiExtractor/abstractmetafield.cpp | 19 +- .../ApiExtractor/abstractmetafunction.cpp | 83 +- .../ApiExtractor/abstractmetafunction.h | 8 +- .../ApiExtractor/abstractmetalang.cpp | 71 +- .../shiboken6/ApiExtractor/abstractmetalang.h | 2 +- .../ApiExtractor/abstractmetalang_typedefs.h | 1 + .../ApiExtractor/abstractmetatype.cpp | 6 +- .../shiboken6/ApiExtractor/addedfunction.cpp | 10 +- .../ApiExtractor/anystringview_helpers.cpp | 22 + .../ApiExtractor/anystringview_helpers.h | 2 + .../shiboken6/ApiExtractor/apiextractor.cpp | 72 +- sources/shiboken6/ApiExtractor/apiextractor.h | 1 - .../ApiExtractor/apiextractorresult.h | 1 + .../ApiExtractor/clangparser/clangbuilder.cpp | 21 +- .../ApiExtractor/clangparser/clangbuilder.h | 2 +- .../clangparser/clangdebugutils.cpp | 8 +- .../ApiExtractor/clangparser/clangparser.cpp | 17 +- .../ApiExtractor/clangparser/clangparser.h | 4 +- .../ApiExtractor/clangparser/clangutils.cpp | 8 +- .../clangparser/compilersupport.cpp | 5 +- .../clangparser/compilersupport.h | 2 +- .../ApiExtractor/classdocumentation.cpp | 86 +- .../ApiExtractor/classdocumentation.h | 3 +- .../shiboken6/ApiExtractor/complextypeentry.h | 3 + .../ApiExtractor/conditionalstreamreader.cpp | 1 - sources/shiboken6/ApiExtractor/docparser.h | 49 +- .../shiboken6/ApiExtractor/documentation.cpp | 20 +- .../shiboken6/ApiExtractor/documentation.h | 31 +- .../ApiExtractor/documentation_enums.h | 27 + .../shiboken6/ApiExtractor/doxygenparser.cpp | 34 +- .../shiboken6/ApiExtractor/doxygenparser.h | 5 +- .../ApiExtractor/enclosingclassmixin.h | 2 +- sources/shiboken6/ApiExtractor/graph.h | 156 ++- sources/shiboken6/ApiExtractor/messages.cpp | 58 + sources/shiboken6/ApiExtractor/messages.h | 15 + .../shiboken6/ApiExtractor/modifications.h | 11 +- .../ApiExtractor/parser/codemodel.cpp | 21 +- .../shiboken6/ApiExtractor/parser/codemodel.h | 24 +- .../shiboken6/ApiExtractor/parser/enumvalue.h | 2 +- .../ApiExtractor/parser/typeinfo.cpp | 17 +- .../shiboken6/ApiExtractor/parser/typeinfo.h | 3 +- .../ApiExtractor/predefined_templates.cpp | 18 +- .../shiboken6/ApiExtractor/propertyspec.cpp | 9 +- .../shiboken6/ApiExtractor/qtdocparser.cpp | 273 ++-- sources/shiboken6/ApiExtractor/qtdocparser.h | 34 +- .../shiboken6/ApiExtractor/reporthandler.cpp | 4 +- .../ApiExtractor/tests/CMakeLists.txt | 1 - .../ApiExtractor/tests/testcontainer.cpp | 1 - .../tests/testdtorinformation.cpp | 2 - .../ApiExtractor/tests/testfunctiontag.cpp | 1 - .../tests/testmodifydocumentation.cpp | 7 +- .../ApiExtractor/tests/testnamespace.cpp | 1 - .../tests/testnumericaltypedef.cpp | 1 - .../tests/testprimitivetypetag.cpp | 1 - .../ApiExtractor/tests/testrefcounttag.cpp | 2 - .../tests/testreferencetopointer.cpp | 2 - .../ApiExtractor/tests/testremovefield.cpp | 2 - .../tests/testremoveoperatormethod.cpp | 1 - .../tests/testreverseoperators.cpp | 1 - .../ApiExtractor/tests/testtemplates.cpp | 6 +- .../ApiExtractor/tests/testtoposort.cpp | 1 - .../ApiExtractor/tests/testtyperevision.cpp | 2 - sources/shiboken6/ApiExtractor/textstream.cpp | 4 +- .../shiboken6/ApiExtractor/typedatabase.cpp | 10 +- sources/shiboken6/ApiExtractor/typedatabase.h | 1 + sources/shiboken6/ApiExtractor/typeparser.cpp | 10 +- sources/shiboken6/ApiExtractor/typesystem.cpp | 49 +- .../shiboken6/ApiExtractor/typesystem_enums.h | 5 + .../ApiExtractor/typesystemparser.cpp | 75 +- .../ApiExtractor/typesystemtypeentry.h | 6 + sources/shiboken6/ApiExtractor/xmlutils.cpp | 4 +- sources/shiboken6/CMakeLists.txt | 1 - sources/shiboken6/COPYING.libsample | 1 - sources/shiboken6/COPYING.libshiboken | 1 - sources/shiboken6/cmake/ShibokenHelpers.cmake | 48 +- .../target_python_info/CMakeLists.txt | 1 - .../target_qt_info/CMakeLists.txt | 1 - sources/shiboken6/data/CMakeLists.txt | 1 - sources/shiboken6/data/docgenerator.1 | 1 - sources/shiboken6/data/generatorrunner.1 | 1 - sources/shiboken6/data/generatorrunner.pc.in | 1 - .../shiboken6/doc/_static/css/qt_style.css | 12 + sources/shiboken6/doc/examples/index.rst | 4 +- sources/shiboken6/doc/scripts/patch_qhp.py | 1 + sources/shiboken6/doc/shibokengenerator.rst | 3 +- sources/shiboken6/doc/shibokenmodule.rst | 216 ++-- sources/shiboken6/doc/typesystem.rst | 3 +- .../shiboken6/doc/typesystem_arguments.rst | 223 ++-- .../doc/typesystem_codegeneration.rst | 1 - .../shiboken6/doc/typesystem_containers.rst | 1 - .../doc/typesystem_conversionrule.rst | 1 - .../doc/typesystem_documentation.rst | 5 + .../doc/typesystem_manipulating_objects.rst | 8 +- .../doc/typesystem_modify_function.rst | 44 - .../doc/typesystem_solving_compilation.rst | 6 +- .../doc/typesystem_specifying_types.rst | 65 +- .../shiboken6/doc/typesystem_templates.rst | 2 +- .../shiboken6/doc/typesystem_variables.rst | 6 +- sources/shiboken6/generator/generator.cpp | 100 +- sources/shiboken6/generator/generator.h | 29 +- .../shiboken6/generator/generatorcontext.cpp | 1 + .../shiboken6/generator/generatorcontext.h | 28 +- sources/shiboken6/generator/main.cpp | 10 +- .../generator/qtdoc/qtdocgenerator.cpp | 590 +++++++-- .../generator/qtdoc/qtdocgenerator.h | 58 +- .../generator/qtdoc/qtxmltosphinx.cpp | 143 +-- .../shiboken6/generator/qtdoc/qtxmltosphinx.h | 20 +- .../generator/qtdoc/qtxmltosphinxinterface.h | 11 + .../generator/shiboken/cppgenerator.cpp | 717 +++++++---- .../generator/shiboken/cppgenerator.h | 37 +- .../shiboken/cppgenerator_container.cpp | 75 +- .../shiboken/cppgenerator_smartpointer.cpp | 12 +- .../generator/shiboken/headergenerator.cpp | 51 +- .../generator/shiboken/headergenerator.h | 9 +- .../generator/shiboken/overloaddata.cpp | 122 +- .../generator/shiboken/overloaddata.h | 2 +- .../generator/shiboken/shibokengenerator.cpp | 180 +-- .../generator/shiboken/shibokengenerator.h | 8 +- sources/shiboken6/libshiboken/CMakeLists.txt | 2 +- sources/shiboken6/libshiboken/basewrapper.cpp | 386 +++--- sources/shiboken6/libshiboken/basewrapper.h | 65 +- sources/shiboken6/libshiboken/basewrapper_p.h | 105 +- .../shiboken6/libshiboken/bindingmanager.cpp | 64 +- .../shiboken6/libshiboken/bindingmanager.h | 3 +- .../libshiboken/embed/embedding_generator.py | 8 +- .../libshiboken/embed/module_collector.py | 1 + .../libshiboken/embed/signature_bootstrap.py | 3 +- sources/shiboken6/libshiboken/gilstate.cpp | 1 - sources/shiboken6/libshiboken/gilstate.h | 1 - sources/shiboken6/libshiboken/helper.cpp | 11 +- sources/shiboken6/libshiboken/pep384ext.h | 2 + sources/shiboken6/libshiboken/pep384impl.cpp | 258 ++-- sources/shiboken6/libshiboken/pep384impl.h | 11 +- .../libshiboken/sbkarrayconverter.cpp | 8 +- .../shiboken6/libshiboken/sbkarrayconverter.h | 2 +- .../shiboken6/libshiboken/sbkconverter.cpp | 120 +- sources/shiboken6/libshiboken/sbkconverter.h | 52 +- .../shiboken6/libshiboken/sbkconverter_p.h | 4 +- .../shiboken6/libshiboken/sbkcppstring.cpp | 2 +- sources/shiboken6/libshiboken/sbkenum.cpp | 29 +- sources/shiboken6/libshiboken/sbkenum.h | 5 +- sources/shiboken6/libshiboken/sbkerrors.cpp | 53 +- sources/shiboken6/libshiboken/sbkerrors.h | 7 + .../shiboken6/libshiboken/sbkfeature_base.cpp | 28 +- sources/shiboken6/libshiboken/sbkmodule.cpp | 207 ++-- sources/shiboken6/libshiboken/sbkmodule.h | 17 +- sources/shiboken6/libshiboken/sbkpython.h | 36 +- sources/shiboken6/libshiboken/sbkstring.cpp | 18 +- sources/shiboken6/libshiboken/sbkstring.h | 12 +- .../shiboken6/libshiboken/sbktypefactory.cpp | 9 +- sources/shiboken6/libshiboken/shiboken.h | 2 +- .../shiboken6/libshiboken/shibokenbuffer.h | 8 +- sources/shiboken6/libshiboken/signature.h | 4 +- .../libshiboken/signature/signature.cpp | 217 +++- .../signature/signature_extend.cpp | 24 +- .../signature/signature_globals.cpp | 10 +- .../signature/signature_helper.cpp | 54 +- sources/shiboken6/libshiboken/signature_p.h | 1 + .../libshiboken/threadstatesaver.cpp | 1 - .../shiboken6/libshiboken/threadstatesaver.h | 1 - sources/shiboken6/libshiboken/voidptr.cpp | 3 +- sources/shiboken6/shiboken_tool.py | 1 + sources/shiboken6/shiboken_version.py | 1 + sources/shiboken6/shibokenmodule/Shiboken.pyi | 4 +- .../files.dir/shibokensupport/__init__.py | 1 + .../files.dir/shibokensupport/feature.py | 1 + .../shibokensupport/fix-complaints.py | 1 + .../shibokensupport/signature/__init__.py | 3 +- .../shibokensupport/signature/errorhandler.py | 1 + .../signature/importhandler.py | 1 + .../shibokensupport/signature/layout.py | 199 ++- .../shibokensupport/signature/lib/__init__.py | 1 + .../shibokensupport/signature/lib/enum_sig.py | 62 +- .../signature/lib/pyi_generator.py | 158 ++- .../shibokensupport/signature/lib/tool.py | 3 + .../shibokensupport/signature/loader.py | 2 + .../shibokensupport/signature/mapping.py | 136 +- .../shibokensupport/signature/parser.py | 102 +- .../shibokenmodule/shibokenmodule.cpp | 21 +- .../shibokenmodule/typesystem_shiboken.xml | 9 + .../shiboken6/tests/libminimal/CMakeLists.txt | 1 - sources/shiboken6/tests/libminimal/listuser.h | 1 - sources/shiboken6/tests/libminimal/obj.cpp | 1 - sources/shiboken6/tests/libminimal/obj.h | 1 - sources/shiboken6/tests/libminimal/val.h | 1 - .../shiboken6/tests/libother/CMakeLists.txt | 2 - .../tests/libother/othermultiplederived.h | 2 +- .../shiboken6/tests/libsample/CMakeLists.txt | 1 - sources/shiboken6/tests/libsample/collector.h | 1 - sources/shiboken6/tests/libsample/complex.h | 1 - sources/shiboken6/tests/libsample/derived.h | 1 - sources/shiboken6/tests/libsample/filter.h | 2 - .../shiboken6/tests/libsample/injectcode.h | 1 - sources/shiboken6/tests/libsample/listuser.h | 1 - sources/shiboken6/tests/libsample/main.cpp | 1 - .../tests/libsample/modified_constructor.cpp | 2 - .../tests/libsample/modified_constructor.h | 1 - .../tests/libsample/noimplicitconversion.h | 1 - .../shiboken6/tests/libsmart/CMakeLists.txt | 1 - .../tests/libsmart/stdsharedptrtestbench.cpp | 20 + .../tests/libsmart/stdsharedptrtestbench.h | 4 + .../tests/minimalbinding/CMakeLists.txt | 4 + .../minimalbinding/brace_pattern_test.py | 1 + .../minimalbinding/containeruser_test.py | 1 + .../tests/minimalbinding/listuser_test.py | 1 + .../tests/minimalbinding/minbool_test.py | 1 + .../tests/minimalbinding/obj_test.py | 1 + .../tests/minimalbinding/spanuser_test.py | 1 + .../tests/minimalbinding/typedef_test.py | 1 + .../tests/minimalbinding/val_test.py | 5 +- .../tests/otherbinding/CMakeLists.txt | 5 +- .../collector_external_operator_test.py | 1 + ...class_without_implicit_conversions_test.py | 1 + .../extended_multiply_operator_test.py | 1 + .../tests/otherbinding/module_reload_test.py | 1 + .../otherbinding/new_ctor_operator_test.py | 1 + .../tests/otherbinding/objtypehashes_test.py | 1 + .../tests/otherbinding/otherderived_test.py | 1 + .../otherbinding/othertypesystypedef_test.py | 1 + .../tests/otherbinding/signature_test.py | 1 + .../tests/otherbinding/smartptr_test.py | 1 + .../tests/otherbinding/star_import_test.py | 1 + .../otherbinding/test_module_template.py | 1 + .../tests/otherbinding/typediscovery_test.py | 18 +- .../usersprimitivefromothermodule_test.py | 1 + .../tests/otherbinding/wrongctor_test.py | 1 + .../shiboken6/tests/qtxmltosphinx/main.cpp | 10 +- .../qtxmltosphinxtest/qtxmltosphinxtest.cpp | 2 +- .../tests/samplebinding/CMakeLists.txt | 4 + .../tests/samplebinding/__del___test.py | 1 + .../tests/samplebinding/abstract_test.py | 3 +- .../tests/samplebinding/addedfunction_test.py | 1 + .../addedfunction_with_container_args_test.py | 1 + .../argumentmodifications_test.py | 1 + .../tests/samplebinding/array_numpy_test.py | 1 + .../samplebinding/array_sequence_test.py | 1 + .../tests/samplebinding/bug_554_test.py | 1 + .../tests/samplebinding/bug_704_test.py | 3 +- .../tests/samplebinding/bytearray_test.py | 1 + .../tests/samplebinding/child_return_test.py | 1 + .../tests/samplebinding/class_fields_test.py | 1 + .../tests/samplebinding/collector_test.py | 1 + .../tests/samplebinding/complex_test.py | 1 + .../samplebinding/conversion_operator_test.py | 1 + .../tests/samplebinding/copy_test.py | 1 + .../tests/samplebinding/ctorconvrule_test.py | 1 + .../tests/samplebinding/cyclic_test.py | 1 + .../tests/samplebinding/date_test.py | 1 + .../tests/samplebinding/decisor_test.py | 9 +- .../tests/samplebinding/delete_test.py | 1 + .../tests/samplebinding/deprecated_test.py | 1 + .../tests/samplebinding/derived_test.py | 5 +- .../tests/samplebinding/duck_punching_test.py | 1 + .../tests/samplebinding/echo_test.py | 1 + .../tests/samplebinding/enum_test.py | 48 +- .../enumfromremovednamespace_test.py | 3 +- .../event_loop_call_virtual_test.py | 3 +- .../samplebinding/event_loop_thread_test.py | 3 +- .../tests/samplebinding/exception_test.py | 1 + .../tests/samplebinding/filter_test.py | 3 +- .../tests/samplebinding/handleholder_test.py | 1 + .../tests/samplebinding/hashabletype_test.py | 1 + .../tests/samplebinding/ignorederefop_test.py | 1 + .../implicitconv_numerical_test.py | 1 + .../tests/samplebinding/implicitconv_test.py | 13 +- .../samplebinding/inheritanceandscope_test.py | 1 + .../tests/samplebinding/injectcode_test.py | 1 + .../tests/samplebinding/innerclass_test.py | 1 + .../tests/samplebinding/intlist_test.py | 9 +- .../tests/samplebinding/intwrapper_test.py | 1 + .../invalid_virtual_return_test.py | 1 + .../samplebinding/keep_reference_test.py | 1 + .../tests/samplebinding/list_test.py | 1 + .../tests/samplebinding/lock_test.py | 1 + .../shiboken6/tests/samplebinding/map_test.py | 1 + .../tests/samplebinding/metaclass_test.py | 3 +- .../samplebinding/mi_virtual_methods_test.py | 5 +- .../tests/samplebinding/mixed_mi_test.py | 3 +- .../tests/samplebinding/modelindex_test.py | 1 + .../tests/samplebinding/modelview_test.py | 1 + .../tests/samplebinding/modifications_test.py | 16 +- .../modified_constructor_test.py | 1 + .../modifiedvirtualmethods_test.py | 1 + .../multi_cpp_inheritance_test.py | 1 + .../samplebinding/multiple_derived_test.py | 1 + .../tests/samplebinding/namespace_test.py | 5 +- .../tests/samplebinding/newdivision_test.py | 1 + .../samplebinding/nondefaultctor_test.py | 1 + .../samplebinding/nontypetemplate_test.py | 1 + .../tests/samplebinding/nonzero_test.py | 1 + .../samplebinding/numericaltypedef_test.py | 1 + .../tests/samplebinding/numpy_test.py | 1 + .../tests/samplebinding/objecttype_test.py | 1 + .../objecttype_with_named_args_test.py | 1 + .../samplebinding/objecttypebyvalue_test.py | 1 + .../samplebinding/objecttypelayout_test.py | 1 + .../samplebinding/objecttypeoperators_test.py | 1 + ...pereferenceasvirtualmethodargument_test.py | 1 + .../tests/samplebinding/oddbool_test.py | 3 +- .../tests/samplebinding/onlycopyclass_test.py | 1 + .../tests/samplebinding/overflow_test.py | 1 + .../samplebinding/overload_sorting_test.py | 7 +- .../tests/samplebinding/overload_test.py | 112 +- .../samplebinding/overloadwithdefault_test.py | 21 +- .../ownership_argument_invalidation_test.py | 1 + .../ownership_delete_child_in_cpp_test.py | 1 + .../ownership_delete_child_in_python_test.py | 1 + .../ownership_delete_parent_test.py | 1 + .../ownership_invalidate_after_use_test.py | 15 +- .../ownership_invalidate_child_test.py | 1 + ...wnership_invalidate_nonpolymorphic_test.py | 1 + .../ownership_invalidate_parent_test.py | 1 + .../ownership_reparenting_test.py | 1 + .../ownership_transference_test.py | 1 + .../tests/samplebinding/pair_test.py | 1 + .../shiboken6/tests/samplebinding/pen_test.py | 3 +- .../tests/samplebinding/point_test.py | 1 + .../tests/samplebinding/pointerholder_test.py | 1 + .../pointerprimitivetype_test.py | 1 + .../tests/samplebinding/pointf_test.py | 1 + .../primitivereferenceargument_test.py | 1 + .../tests/samplebinding/privatector_test.py | 1 + .../tests/samplebinding/privatedtor_test.py | 1 + .../tests/samplebinding/protected_test.py | 98 +- .../tests/samplebinding/pstrlist_test.py | 1 + .../tests/samplebinding/pystr_test.py | 1 + .../tests/samplebinding/python_thread_test.py | 1 + .../receive_null_cstring_test.py | 1 + .../tests/samplebinding/reference_test.py | 1 + .../samplebinding/referencetopointer_test.py | 1 + .../tests/samplebinding/renaming_test.py | 3 +- .../tests/samplebinding/return_null_test.py | 1 + .../tests/samplebinding/richcompare_test.py | 1 + .../tests/samplebinding/sample_test.py | 1 + .../tests/samplebinding/simplefile_test.py | 1 + .../tests/samplebinding/size_test.py | 1 + .../tests/samplebinding/snakecase_test.py | 1 + .../static_nonstatic_methods_test.py | 1 + .../tests/samplebinding/stdcomplex_test.py | 1 + .../shiboken6/tests/samplebinding/str_test.py | 1 + .../tests/samplebinding/strlist_test.py | 13 +- .../templateinheritingclass_test.py | 5 +- .../tests/samplebinding/time_test.py | 16 +- .../tests/samplebinding/transform_test.py | 1 + .../samplebinding/typeconverters_test.py | 1 + .../tests/samplebinding/typedealloc_test.py | 1 + .../samplebinding/typedtordoublefree_test.py | 1 + .../tests/samplebinding/typesystem_sample.xml | 141 ++- .../samplebinding/typesystypedef_test.py | 1 + .../tests/samplebinding/unsafe_parent_test.py | 1 + .../tests/samplebinding/useraddedctor_test.py | 1 + .../tests/samplebinding/virtualdtor_test.py | 1 + .../samplebinding/virtualmethods_test.py | 1 + .../samplebinding/visibilitychange_test.py | 1 + .../tests/samplebinding/voidholder_test.py | 1 + .../tests/samplebinding/weakref_test.py | 1 + .../samplebinding/writableclassdict_test.py | 1 + sources/shiboken6/tests/shiboken_paths.py | 11 +- .../shiboken6/tests/shiboken_test_helper.py | 2 +- .../tests/shibokenmodule/module_test.py | 1 + .../tests/smartbinding/CMakeLists.txt | 5 + .../tests/smartbinding/smart_pointer_test.py | 3 +- .../tests/smartbinding/std_optional_test.py | 3 + .../tests/smartbinding/std_shared_ptr_test.py | 12 +- .../tests/smartbinding/std_unique_ptr_test.py | 1 + .../tests/smartbinding/typesystem_smart.xml | 7 +- .../tests/test_generator/CMakeLists.txt | 1 - .../tests/test_generator/dummygenerator.cpp | 1 - .../dummygentest-project.txt.in | 1 - .../tests/test_generator/dummygentest.cpp | 1 - .../tests/test_generator/dummygentest.h | 1 - .../test_generator/dummygentestconfig.h.in | 1 - .../shiboken6/tests/test_generator/main.cpp | 1 - testing/__init__.py | 1 + testing/blacklist.py | 3 +- testing/buildlog.py | 3 +- testing/command.py | 12 +- testing/helper.py | 1 + testing/parser.py | 3 +- testing/runner.py | 3 +- testing/wheel_tester.py | 44 +- testrunner.py | 1 + tools/broken_links.py | 52 + tools/checklibs.py | 2 +- tools/create_changelog.py | 34 +- .../android_utilities.py | 107 +- tools/cross_compile_android/main.py | 111 +- tools/cross_compile_android/requirements.txt | 3 + .../templates/cross_compile.tmpl.sh | 10 +- .../templates/toolchain_default.tmpl.cmake | 2 +- tools/debug_renamer.py | 1 + tools/debug_windows.py | 1 + tools/doc_modules.py | 1 + tools/dump_metaobject.py | 1 + tools/example_gallery/main.py | 195 ++- tools/install-p311.sh | 35 + tools/leak_finder.py | 3 +- tools/license_check.py | 1 + tools/metaobject_dump.py | 1 + tools/missing_bindings/config.py | 75 +- tools/missing_bindings/main.py | 57 +- tools/qtcpp2py.py | 1 + tools/regenerate_example_resources.py | 1 + tools/regenerate_example_ui.py | 1 + tools/release_notes/main.py | 197 +++ tools/scanqtclasses.py | 123 ++ tools/snippets_translate/converter.py | 12 +- tools/snippets_translate/handlers.py | 3 +- tools/snippets_translate/main.py | 30 +- tools/snippets_translate/module_classes.py | 3 + tools/snippets_translate/override.py | 11 +- tools/snippets_translate/parse_utils.py | 1 + .../tests/test_converter.py | 20 +- .../snippets_translate/tests/test_snippets.py | 1 + tools/uic_test.py | 6 +- wheel_artifacts/pyproject.toml.base | 17 +- 2115 files changed, 26519 insertions(+), 12826 deletions(-) create mode 100644 coin/fetch_libclang_arm64.sh create mode 100644 coin/instructions/execute_android_instructions.yaml create mode 100644 coin/instructions/execute_desktop_instructions.yaml delete mode 100644 coin/instructions/execute_license_check.yaml create mode 100644 coin/instructions_utils.py create mode 100644 doc/changelogs/changes-6.7.3 create mode 100644 doc/changelogs/changes-6.8.0 create mode 100644 doc/changelogs/changes-6.8.1 create mode 100644 doc/changelogs/changes-6.8.1.1 create mode 100644 doc/changelogs/changes-6.8.2 create mode 100644 doc/changelogs/changes-6.8.2.1 create mode 100644 examples/charts/zoomlinechart/chart.py create mode 100644 examples/charts/zoomlinechart/chartview.py create mode 100644 examples/charts/zoomlinechart/doc/zoomlinechart.png create mode 100644 examples/charts/zoomlinechart/doc/zoomlinechart.rst create mode 100644 examples/charts/zoomlinechart/main.py create mode 100644 examples/charts/zoomlinechart/zoomlinechart.pyproject create mode 100644 examples/corelib/ipc/sharedmemory/doc/sharedmemory.png create mode 100644 examples/corelib/ipc/sharedmemory/doc/sharedmemory.rst create mode 100644 examples/dbus/listnames/doc/listnames.rst create mode 100644 examples/dbus/pingpong/doc/pingpong.rst create mode 100644 examples/demos/osmbuildings/OSMBuildings/Main.qml create mode 100644 examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml create mode 100644 examples/demos/osmbuildings/OSMBuildings/customshadertiles.frag create mode 100644 examples/demos/osmbuildings/OSMBuildings/qmldir create mode 100644 examples/demos/osmbuildings/doc/osmbuildings.rst create mode 100644 examples/demos/osmbuildings/doc/osmbuildings.webp create mode 100644 examples/demos/osmbuildings/main.py create mode 100644 examples/demos/osmbuildings/manager.py create mode 100644 examples/demos/osmbuildings/osmbuildings.pyproject create mode 100644 examples/demos/osmbuildings/request.py create mode 100644 examples/graphs/3d/bars/Bars/Axes.qml create mode 100644 examples/graphs/3d/bars/Bars/Data.qml create mode 100644 examples/graphs/3d/bars/Bars/Main.qml create mode 100644 examples/graphs/3d/bars/Bars/qmldir create mode 100644 examples/graphs/3d/bars/bars.pyproject create mode 100644 examples/graphs/3d/bars/doc/bars-example.webp create mode 100644 examples/graphs/3d/bars/doc/bars.rst create mode 100644 examples/graphs/3d/bars/main.py create mode 100644 examples/graphs/3d/minimalsurfacegraph/minimalsurfacegraph.pyproject delete mode 100644 examples/graphs/3d/widgetgallery/axesinputhandler.py delete mode 100644 examples/graphs/3d/widgetgallery/custominputhandler.py delete mode 100644 examples/graphs/3d/widgetgallery/doc/widgetgallery.rst delete mode 100644 examples/graphs/3d/widgetgallery/main.py delete mode 100644 examples/graphs/3d/widgetgallery/scatterdatamodifier.py rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/bargraph.py (55%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/layer_1.png (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/layer_2.png (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/layer_3.png (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/license.txt (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/maptexture.jpg (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/narrowarrow.mesh (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/oilrig.mesh (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/pipe.mesh (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/raindata.txt (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/refinery.mesh (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/data/topography.png (100%) create mode 100644 examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst rename examples/graphs/3d/{widgetgallery/doc/widgetgallery.webp => widgetgraphgallery/doc/widgetgraphgallery.webp} (100%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/graphmodifier.py (82%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/highlightseries.py (75%) create mode 100644 examples/graphs/3d/widgetgraphgallery/main.py rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/rainfalldata.py (97%) create mode 100644 examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/scattergraph.py (66%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/surfacegraph.py (79%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/surfacegraphmodifier.py (71%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/topographicseries.py (89%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/variantbardatamapping.py (98%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/variantbardataproxy.py (98%) rename examples/graphs/3d/{widgetgallery => widgetgraphgallery}/variantdataset.py (96%) rename examples/graphs/3d/{widgetgallery/widgetgallery.pyproject => widgetgraphgallery/widgetgraphgallery.pyproject} (91%) delete mode 100644 examples/httpserver/afterrequest/doc/afterrequest.rst create mode 100644 examples/httpserver/simplehttpserver/doc/simplehttpserver.rst rename examples/httpserver/{afterrequest => simplehttpserver}/main.py (57%) create mode 100644 examples/httpserver/simplehttpserver/simplehttpserver.pyproject create mode 100644 examples/network/blockingfortuneclient/doc/blockingfortuneclient.png create mode 100644 examples/network/blockingfortuneclient/doc/blockingfortuneclient.rst create mode 100644 examples/network/fortuneclient/doc/fortuneclient.png create mode 100644 examples/network/fortuneclient/doc/fortuneclient.rst create mode 100644 examples/network/fortuneserver/doc/fortuneserver.png create mode 100644 examples/network/fortuneserver/doc/fortuneserver.rst create mode 100644 examples/network/threadedfortuneserver/doc/threadedfortuneserver.png create mode 100644 examples/network/threadedfortuneserver/doc/threadedfortuneserver.rst create mode 100644 examples/opengl/contextinfo/requirements.txt create mode 100644 examples/opengl/hellogl2/requirements.txt create mode 100644 examples/opengl/textures/requirements.txt create mode 100644 examples/opengl/threadedqopenglwidget/requirements.txt delete mode 100644 examples/qml/signals/pytoqml1/main.py delete mode 100644 examples/qml/signals/pytoqml1/pytoqml1.pyproject delete mode 100644 examples/qml/signals/pytoqml1/view.qml delete mode 100644 examples/qml/signals/pytoqml2/main.py delete mode 100644 examples/qml/signals/pytoqml2/pytoqml2.pyproject delete mode 100644 examples/qml/signals/pytoqml2/view.qml create mode 100644 examples/qml/signals/qmltopy1/doc/qmltopy1.rst create mode 100644 examples/qml/signals/qmltopy2/doc/qmltopy2.rst create mode 100644 examples/qml/signals/qmltopy3/doc/qmltopy3.rst create mode 100644 examples/qml/signals/qmltopy4/doc/qmltopy4.rst create mode 100644 examples/quick/rendercontrol/rendercontrol_opengl/requirements.txt create mode 100644 examples/quick/scenegraph/openglunderqml/requirements.txt create mode 100644 examples/remoteobjects/modelview/doc/modelview.rst create mode 100644 examples/statemachine/ping_pong/doc/ping_pong.rst create mode 100644 examples/statemachine/ping_pong/doc/pingpong.png create mode 100644 examples/statemachine/rogue/doc/rogue.png create mode 100644 examples/statemachine/rogue/doc/rogue.rst create mode 100644 examples/tutorials/finance_manager/part1/Finance/AddDialog.qml create mode 100644 examples/tutorials/finance_manager/part1/Finance/FinanceDelegate.qml create mode 100644 examples/tutorials/finance_manager/part1/Finance/FinancePieChart.qml create mode 100644 examples/tutorials/finance_manager/part1/Finance/FinanceView.qml create mode 100644 examples/tutorials/finance_manager/part1/Finance/Main.qml create mode 100644 examples/tutorials/finance_manager/part1/Finance/qmldir create mode 100644 examples/tutorials/finance_manager/part1/doc/part1.md create mode 100644 examples/tutorials/finance_manager/part1/financemodel.py create mode 100644 examples/tutorials/finance_manager/part1/main.py create mode 100644 examples/tutorials/finance_manager/part1/part1.pyproject create mode 100644 examples/tutorials/finance_manager/part2/Finance/AddDialog.qml create mode 100644 examples/tutorials/finance_manager/part2/Finance/FinanceDelegate.qml create mode 100644 examples/tutorials/finance_manager/part2/Finance/FinancePieChart.qml create mode 100644 examples/tutorials/finance_manager/part2/Finance/FinanceView.qml create mode 100644 examples/tutorials/finance_manager/part2/Finance/Main.qml create mode 100644 examples/tutorials/finance_manager/part2/Finance/qmldir create mode 100644 examples/tutorials/finance_manager/part2/database.py create mode 100644 examples/tutorials/finance_manager/part2/doc/part2.md create mode 100644 examples/tutorials/finance_manager/part2/financemodel.py create mode 100644 examples/tutorials/finance_manager/part2/main.py create mode 100644 examples/tutorials/finance_manager/part2/part2.pyproject create mode 100644 examples/tutorials/finance_manager/part2/requirements.txt create mode 100644 examples/tutorials/finance_manager/part3/Backend/database.py create mode 100644 examples/tutorials/finance_manager/part3/Backend/main.py create mode 100644 examples/tutorials/finance_manager/part3/Backend/rest_api.py create mode 100644 examples/tutorials/finance_manager/part3/Frontend/Finance/AddDialog.qml create mode 100644 examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceDelegate.qml create mode 100644 examples/tutorials/finance_manager/part3/Frontend/Finance/FinancePieChart.qml create mode 100644 examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceView.qml create mode 100644 examples/tutorials/finance_manager/part3/Frontend/Finance/Main.qml create mode 100644 examples/tutorials/finance_manager/part3/Frontend/Finance/qmldir create mode 100644 examples/tutorials/finance_manager/part3/Frontend/financemodel.py create mode 100644 examples/tutorials/finance_manager/part3/Frontend/main.py create mode 100644 examples/tutorials/finance_manager/part3/doc/part3.md create mode 100644 examples/tutorials/finance_manager/part3/part3.pyproject create mode 100644 examples/tutorials/finance_manager/part3/requirements.txt create mode 100644 examples/uitools/uiloader/doc/uiloader.rst create mode 100644 examples/webenginewidgets/markdowneditor/doc/markdowneditor.png create mode 100644 examples/webenginewidgets/markdowneditor/doc/markdowneditor.rst create mode 100644 examples/webview/minibrowser/doc/minibrowser.rst create mode 100644 examples/webview/minibrowser/doc/minibrowser.webp create mode 100644 examples/webview/minibrowser/images/left-32.png create mode 100644 examples/webview/minibrowser/images/refresh-32.png create mode 100644 examples/webview/minibrowser/images/right-32.png create mode 100644 examples/webview/minibrowser/images/settings-32.png create mode 100644 examples/webview/minibrowser/images/stop-32.png create mode 100644 examples/webview/minibrowser/main.py create mode 100644 examples/webview/minibrowser/main.qml create mode 100644 examples/webview/minibrowser/minibrowser.pyproject create mode 100644 examples/webview/minibrowser/qml.qrc create mode 100644 examples/webview/minibrowser/rc_qml.py create mode 100644 examples/widgets/desktop/systray/doc/systray.png create mode 100644 examples/widgets/desktop/systray/doc/systray.rst create mode 100644 examples/widgets/dialogs/extension/doc/extension.png create mode 100644 examples/widgets/dialogs/extension/doc/extension.rst create mode 100644 examples/widgets/dialogs/standarddialogs/doc/standarddialogs.png create mode 100644 examples/widgets/dialogs/standarddialogs/doc/standarddialogs.rst create mode 100644 examples/widgets/dialogs/trivialwizard/doc/trivialwizard.png create mode 100644 examples/widgets/dialogs/trivialwizard/doc/trivialwizard.rst create mode 100644 examples/widgets/draganddrop/draggabletext/doc/draggabletext.png create mode 100644 examples/widgets/draganddrop/draggabletext/doc/draggabletext.rst create mode 100644 examples/widgets/effects/lighting/doc/lighting.rst create mode 100644 examples/widgets/effects/lighting/doc/lighting.webp create mode 100644 examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.rst create mode 100644 examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.webp create mode 100644 examples/widgets/graphicsview/collidingmice/doc/collidingmice.rst create mode 100644 examples/widgets/graphicsview/collidingmice/doc/collidingmice.webp create mode 100644 examples/widgets/graphicsview/diagramscene/doc/diagramscene.rst create mode 100644 examples/widgets/graphicsview/diagramscene/doc/diagramscene.webp create mode 100644 examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.png create mode 100644 examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.rst create mode 100644 examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.png create mode 100644 examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.rst create mode 100644 examples/widgets/imageviewer/doc/imageviewer.rst create mode 100644 examples/widgets/imageviewer/doc/imageviewer.webp create mode 100644 examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.rst create mode 100644 examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.webp create mode 100644 examples/widgets/itemviews/fetchmore/doc/fetchmore.png create mode 100644 examples/widgets/itemviews/fetchmore/doc/fetchmore.rst create mode 100644 examples/widgets/mainwindows/application/doc/application.png create mode 100644 examples/widgets/mainwindows/application/doc/application.rst create mode 100644 examples/widgets/mainwindows/mdi/doc/mdi.png create mode 100644 examples/widgets/mainwindows/mdi/doc/mdi.rst create mode 100644 examples/widgets/richtext/orderform/doc/orderform.rst create mode 100644 examples/widgets/richtext/orderform/doc/orderform.webp create mode 100644 examples/widgets/thread_signals/doc/thread_signals.png create mode 100644 examples/widgets/thread_signals/doc/thread_signals.rst create mode 100644 requirements-coin.txt create mode 100644 sources/pyside-tools/deploy_lib/android/android_utilities.py rename sources/pyside-tools/{project => project_lib}/__init__.py (93%) create mode 100644 sources/pyside-tools/project_lib/design_studio_project.py rename sources/pyside-tools/{project => project_lib}/newproject.py (97%) rename sources/pyside-tools/{project => project_lib}/project_data.py (91%) rename sources/pyside-tools/{project => project_lib}/utils.py (70%) create mode 100644 sources/pyside6/PySide6/QtGraphsWidgets/CMakeLists.txt create mode 100644 sources/pyside6/PySide6/QtGraphsWidgets/typesystem_graphswidgets.xml create mode 100644 sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp create mode 100644 sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h create mode 100644 sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp create mode 100644 sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h create mode 100644 sources/pyside6/PySide6/QtWebView/CMakeLists.txt create mode 100644 sources/pyside6/PySide6/QtWebView/typesystem_webview.xml create mode 100644 sources/pyside6/PySide6/doc/qtcore.rst rename sources/pyside6/PySide6/doc/{qtqml_functions.rst => qtqml.rst} (100%) create mode 100644 sources/pyside6/PySide6/doc/qtwebenginecore.rst create mode 100644 sources/pyside6/PySide6/glue/qtquick3d.cpp delete mode 100644 sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp create mode 100644 sources/pyside6/doc/PySide6/QtAsyncio/coroutines.png rename sources/pyside6/doc/{gettingstarted => building_from_source}/index.rst (97%) rename sources/pyside6/doc/{gettingstarted => building_from_source}/linux.rst (51%) create mode 100644 sources/pyside6/doc/building_from_source/macOS.rst rename sources/pyside6/doc/{gettingstarted => building_from_source}/windows.rst (59%) create mode 100644 sources/pyside6/doc/developer/mypy-correctness.rst create mode 100644 sources/pyside6/doc/developer/qtasyncio.rst create mode 100644 sources/pyside6/doc/developer/signalmanager.md create mode 100644 sources/pyside6/doc/extras/QtQuick.rst create mode 100644 sources/pyside6/doc/extras/QtWebView.rst rename sources/pyside6/doc/{gettingstarted => faq}/porting_from2.rst (99%) rename sources/pyside6/doc/{quickstart.rst => gettingstarted.rst} (61%) delete mode 100644 sources/pyside6/doc/gettingstarted/macOS.rst create mode 100644 sources/pyside6/doc/images/screenshot_hello_quick.png rename sources/pyside6/doc/images/{screenshot_hello.png => screenshot_hello_widgets.png} (100%) rename sources/pyside6/doc/{gettingstarted => }/package_details.rst (100%) rename sources/pyside6/doc/{gettingstarted => }/packages.png (100%) create mode 100644 sources/pyside6/doc/qtmodules/pyside-qtgraphs.qdocconf.in create mode 100644 sources/pyside6/doc/qtmodules/pyside-qthttpserver.qdocconf.in create mode 100644 sources/pyside6/doc/qtmodules/pyside-qtlocation.qdocconf.in create mode 100644 sources/pyside6/doc/qtmodules/pyside-qtquick3d.qdocconf.in create mode 100644 sources/pyside6/doc/qtmodules/pyside-qtstatemachine.qdocconf.in create mode 100644 sources/pyside6/doc/qtmodules/pyside-qtwebview.qdocconf.in create mode 100644 sources/pyside6/doc/tools/pyside6-svgtoqml.rst create mode 100644 sources/pyside6/doc/tutorials/embedded/boot2qt.md create mode 100644 sources/pyside6/doc/tutorials/embedded/boot2qt_launcher.png create mode 100644 sources/pyside6/doc/tutorials/finance_manager/index.md create mode 100644 sources/pyside6/doc/tutorials/finance_manager/part1/part1.md create mode 100644 sources/pyside6/doc/tutorials/finance_manager/part1/resources/finance_manager.gif create mode 100644 sources/pyside6/doc/tutorials/finance_manager/part1/resources/finance_manager.webp create mode 100644 sources/pyside6/doc/tutorials/finance_manager/part1/resources/finance_manager_android.gif create mode 100644 sources/pyside6/doc/tutorials/finance_manager/part2/part2.md create mode 100644 sources/pyside6/doc/tutorials/finance_manager/part3/part3.md rename sources/pyside6/doc/tutorials/qmlapp/{view.qml => App/Main.qml} (100%) rename sources/pyside6/doc/tutorials/qmlapp/{ => App}/logo.png (100%) create mode 100644 sources/pyside6/doc/tutorials/qmlapp/App/qmldir rename sources/pyside6/doc/tutorials/qmlintegration/{view.qml => QmlIntegration/Main.qml} (100%) create mode 100644 sources/pyside6/doc/tutorials/qmlintegration/QmlIntegration/qmldir rename sources/pyside6/doc/tutorials/qmlsqlintegration/{chat.qml => App/Main.qml} (95%) create mode 100644 sources/pyside6/doc/tutorials/qmlsqlintegration/App/qmldir create mode 100644 sources/pyside6/doc/tutorials/qmlsqlintegration/Main/qmldir create mode 100644 sources/pyside6/libpyside/dynamicslot.cpp create mode 100644 sources/pyside6/libpyside/dynamicslot_p.h delete mode 100644 sources/pyside6/libpyside/globalreceiverv2.cpp delete mode 100644 sources/pyside6/libpyside/globalreceiverv2.h create mode 100644 sources/pyside6/tests/QtAsyncio/bug_2790.py create mode 100644 sources/pyside6/tests/QtAsyncio/bug_2799.py create mode 100644 sources/pyside6/tests/QtAsyncio/qasyncio_test_uncancel.py create mode 100644 sources/pyside6/tests/QtGraphs/QtGraphs.pyproject create mode 100644 sources/pyside6/tests/QtGraphs/qgraphs_numpy_test.py create mode 100644 sources/pyside6/tests/QtGraphsWidgets/CMakeLists.txt create mode 100644 sources/pyside6/tests/QtWebView/CMakeLists.txt create mode 100644 sources/pyside6/tests/QtWebView/QtWebView.pyproject create mode 100644 sources/pyside6/tests/QtWebView/test_webview.py create mode 100644 sources/pyside6/tests/manually/sizebench.py create mode 100644 sources/pyside6/tests/pysidetest/connection.h create mode 100644 sources/pyside6/tests/pysidetest/mypy_correctness_test.py rename sources/pyside6/tests/pysidetest/{pyenum_relax_options_test.py => pyenum_relax_options_and_lazy_star_test.py} (86%) create mode 100644 sources/pyside6/tests/tools/pyside6-project/CMakeLists.txt create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Drumpad.qmlproject create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Drumpad.qrc create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Drumpad/TestComponent.qml create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Drumpad/qmldir create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/DrumpadContent/App.qml create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/DrumpadContent/MainScreen.ui.qml create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/DrumpadContent/qmldir create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/.pyproject create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/autogen/settings.py create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Python/main.py create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Resources/resource1.txt create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/Resources/resource2.txt create mode 100644 sources/pyside6/tests/tools/pyside6-project/example_drumpad/qtquickcontrols2.conf create mode 100644 sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py create mode 100644 sources/shiboken6/ApiExtractor/documentation_enums.h delete mode 100644 sources/shiboken6/doc/typesystem_modify_function.rst create mode 100644 tools/broken_links.py create mode 100755 tools/install-p311.sh create mode 100644 tools/release_notes/main.py create mode 100644 tools/scanqtclasses.py diff --git a/.gitignore b/.gitignore index 27fd3efc..e898e699 100644 --- a/.gitignore +++ b/.gitignore @@ -12,13 +12,15 @@ sources/pyside6/doc/codesnippets/* # Auto-generated examples sources/pyside6/doc/examples/* +# Auto-generated release notes +sources/pyside6/doc/release_notes/* + distribute-*.egg distribute-*.tar.gz explore2 build_history/2* *.qdocconf -*.qdocconf.in *.egg-info *.pyc diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d19a40b..51dbfdb2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,6 @@ "python.testing.unittestEnabled": true, "flake8.args": [ "--config=.flake8" - ] + ], + "grammarly.files.include": ["**/*.rst", "**/*.md"], } diff --git a/README.md b/README.md index 35f5a906..bb98271d 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ using `setup.py build`: * `--ignore-git`, will skip the fetching and checkout steps for supermodule and all submodules. * `--limited-api=yes|no`, default yes if applicable. Set or clear the limited - API flag. Ignored for Python 2. + API flag. * `--module-subset`, allows for specifying the Qt modules to be built. A minimal set is: `--module-subset=Core,Gui,Test,Widgets`. * `--package-timestamp`, allows specifying the timestamp that will be used as diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index e6044c4a..a4257670 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -23,6 +23,7 @@ it includes the following Qt modules: * QtCharts * QtDataVisualization * QtGraphs +* QtGraphsWidgets * QtMultimedia * QtMultimediaWidgets * QtNetworkAuth @@ -48,6 +49,7 @@ it includes the following Qt modules: * QtHttpServer * QtLocation * QtAsyncio +* QtWebView ### Documentation and Bugs diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py index 128bb239..17975e34 100644 --- a/build_scripts/__init__.py +++ b/build_scripts/__init__.py @@ -1,5 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations +from pathlib import Path PYSIDE = 'pyside6' PYSIDE_MODULE = 'PySide6' @@ -20,7 +22,8 @@ PYSIDE_UNIX_BIN_TOOLS = ["lupdate", "qmlls", "qsb", "balsam", - "balsamui"] + "balsamui", + "svgtoqml",] # tools that are bundled as .app in macOS, but are normal executables in Linux and Windows PYSIDE_UNIX_BUNDLED_TOOLS = ["assistant", @@ -37,3 +40,8 @@ PYSIDE_UNIX_LIBEXEC_TOOLS = ["uic", # all Qt tools are in 'bin' folder in Windows PYSIDE_WINDOWS_BIN_TOOLS = PYSIDE_UNIX_LIBEXEC_TOOLS + PYSIDE_LINUX_BIN_TOOLS + +PYSIDE_MULTIMEDIA_LIBS = ["avcodec", "avformat", "avutil", + "swresample", "swscale"] + +PYPROJECT_PATH = Path(__file__).parents[1] / "wheel_artifacts" / "pyproject.toml.base" diff --git a/build_scripts/build_info_collector.py b/build_scripts/build_info_collector.py index 30ce187c..0c8becf4 100644 --- a/build_scripts/build_info_collector.py +++ b/build_scripts/build_info_collector.py @@ -1,5 +1,6 @@ # Copyright (C) 2021 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import os import platform @@ -129,7 +130,7 @@ def get_py_library(build_type, py_version, py_prefix, py_libdir, py_include_dir) return py_library -class BuildInfoCollectorMixin(object): +class BuildInfoCollectorMixin: build_base: str build_lib: str cmake: str diff --git a/build_scripts/config.py b/build_scripts/config.py index 0a6eebf7..f11fbb5e 100644 --- a/build_scripts/config.py +++ b/build_scripts/config.py @@ -1,15 +1,23 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations +import os import sys -from .log import log, LogLevel from pathlib import Path +from typing import Any -from . import PYSIDE, PYSIDE_MODULE, SHIBOKEN -from .utils import available_pyside_tools +from . import PYPROJECT_PATH, PYSIDE, PYSIDE_MODULE, SHIBOKEN +from .log import LogLevel, log +from .utils import available_pyside_tools, Singleton +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib -class Config(object): + +class Config(metaclass=Singleton): def __init__(self): # Constants self._build_type_all = "all" @@ -22,7 +30,7 @@ class Config(object): # The setup.py invocation type. # top-level # internal - self.invocation_type = None + self.invocation_type: str = "" # The type of the top-level build. # all - build shiboken6 module, shiboken6-generator and PySide6 @@ -30,11 +38,11 @@ class Config(object): # shiboken6 - build only shiboken6 module # shiboken6-generator - build only the shiboken6-generator # pyside6 - build only PySide6 modules - self.build_type = None + self.build_type: str = "" # The internal build type, used for internal invocations of # setup.py to build a specific module only. - self.internal_build_type = None + self.internal_build_type: str = "" # Options that can be given to --build-type and # --internal-build-type @@ -45,40 +53,51 @@ class Config(object): # Names to be passed to setuptools.setup() name key, # so not package name, but rather project name as it appears # in the wheel name and on PyPi. - self.shiboken_module_st_name = SHIBOKEN - self.shiboken_generator_st_name = f"{SHIBOKEN}-generator" - self.pyside_st_name = PYSIDE_MODULE + self.shiboken_module_st_name: str = SHIBOKEN + self.shiboken_generator_st_name: str = f"{SHIBOKEN}-generator" + self.pyside_st_name: str = PYSIDE_MODULE # Path to CMake toolchain file when intending to cross compile # the project. - self.cmake_toolchain_file = None + self.cmake_toolchain_file: str | os.PathLike = "" # Store where host shiboken is built during a cross-build. - self.shiboken_host_query_path = None - - # Used by check_allowed_python_version to validate the - # interpreter version. - self.python_version_classifiers = [ - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - ] + self.shiboken_host_query_path: str = "" + + self.setup_script_dir: str | os.PathLike = "" + + # Getting data from base pyproject.toml file to be consistent + + if not PYPROJECT_PATH.exists(): + log.error("'pyproject.toml.base' not found in '{pyproject_path}'") - self.setup_script_dir = None + with open(PYPROJECT_PATH, "rb") as f: + _pyproject_data = tomllib.load(f)["project"] + + self.setup_kwargs: dict[str, Any] = {} + self.setup_kwargs['long_description_content_type'] = 'text/markdown' + + self.setup_kwargs['keywords'] = _pyproject_data["keywords"] + _author, _email = _pyproject_data["authors"][0] + self.setup_kwargs['author'] = _author + self.setup_kwargs['author_email'] = _email + self.setup_kwargs['url'] = _pyproject_data["urls"]["Homepage"] + self.setup_kwargs['license'] = _pyproject_data["license"]["text"] + self.setup_kwargs['python_requires'] = _pyproject_data["requires-python"] + + self.classifiers = _pyproject_data["classifiers"] + self.setup_kwargs['classifiers'] = self.classifiers def init_config(self, - build_type=None, - internal_build_type=None, + build_type="", + internal_build_type="", cmd_class_dict=None, package_version=None, ext_modules=None, - setup_script_dir=None, - cmake_toolchain_file=None, + setup_script_dir: str | os.PathLike = "", + cmake_toolchain_file: str | os.PathLike = "", log_level=LogLevel.INFO, - qt_install_path: Path = None): + qt_install_dir: str | os.PathLike = ""): """ Sets up the global singleton config which is used in many parts of the setup process. @@ -103,24 +122,14 @@ class Config(object): self.cmake_toolchain_file = cmake_toolchain_file - setup_kwargs = {} - setup_kwargs['long_description'] = self.get_long_description() - setup_kwargs['long_description_content_type'] = 'text/markdown' - setup_kwargs['keywords'] = 'Qt' - setup_kwargs['author'] = 'Qt for Python Team' - setup_kwargs['author_email'] = 'pyside@qt-project.org' - setup_kwargs['url'] = 'https://www.pyside.org' - setup_kwargs['download_url'] = 'https://download.qt.io/official_releases/QtForPython' - setup_kwargs['license'] = 'LGPL' - setup_kwargs['zip_safe'] = False - setup_kwargs['cmdclass'] = cmd_class_dict - setup_kwargs['version'] = package_version - setup_kwargs['python_requires'] = ">=3.9, <3.13" + self.setup_kwargs['long_description'] = self.get_long_description() + self.setup_kwargs['cmdclass'] = cmd_class_dict + self.setup_kwargs['version'] = package_version if log_level == LogLevel.QUIET: # Tells setuptools to be quiet, and only print warnings or errors. # Makes way less noise in the terminal when building. - setup_kwargs['verbose'] = 0 + self.setup_kwargs['verbose'] = 0 # Setting these two keys is still a bit of a discussion point. # In general not setting them will allow using "build" and @@ -138,54 +147,29 @@ class Config(object): # The only plausible usage of it, is if we will implement a # correctly functioning setup.py develop command (or bdist_egg). # But currently that doesn't seem to work. - setup_kwargs['packages'] = self.get_setup_tools_packages_for_current_build() - setup_kwargs['package_dir'] = self.get_package_name_to_dir_path_mapping() + self.setup_kwargs['packages'] = self.get_setup_tools_packages_for_current_build() + self.setup_kwargs['package_dir'] = self.get_package_name_to_dir_path_mapping() # Add a bogus extension module (will never be built here since # we are overriding the build command to do it using cmake) so # things like bdist_egg will know that there are extension # modules and will name the dist with the full platform info. - setup_kwargs['ext_modules'] = ext_modules - - common_classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Environment :: MacOS X', - 'Environment :: X11 Applications :: Qt', - 'Environment :: Win32 (MS Windows)', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', - 'License :: Other/Proprietary License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: POSIX', - 'Operating System :: POSIX :: Linux', - 'Operating System :: Microsoft', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: C++'] - common_classifiers.extend(self.python_version_classifiers) - common_classifiers.extend([ - 'Topic :: Database', - 'Topic :: Software Development', - 'Topic :: Software Development :: Code Generators', - 'Topic :: Software Development :: Libraries :: Application Frameworks', - 'Topic :: Software Development :: User Interfaces', - 'Topic :: Software Development :: Widget Sets']) - setup_kwargs['classifiers'] = common_classifiers + self.setup_kwargs['ext_modules'] = ext_modules package_name = self.package_name() if self.internal_build_type == self.shiboken_module_option_name: - setup_kwargs['name'] = self.shiboken_module_st_name - setup_kwargs['description'] = "Python / C++ bindings helper module" - setup_kwargs['entry_points'] = {} + self.setup_kwargs['name'] = self.shiboken_module_st_name + self.setup_kwargs['description'] = "Python / C++ bindings helper module" + self.setup_kwargs['entry_points'] = {} elif self.internal_build_type == self.shiboken_generator_option_name: - setup_kwargs['name'] = self.shiboken_generator_st_name - setup_kwargs['description'] = "Python / C++ bindings generator" - setup_kwargs['install_requires'] = [ + self.setup_kwargs['name'] = self.shiboken_generator_st_name + self.setup_kwargs['description'] = "Python / C++ bindings generator" + self.setup_kwargs['install_requires'] = [ f"{self.shiboken_module_st_name}=={package_version}" ] - setup_kwargs['entry_points'] = { + self.setup_kwargs['entry_points'] = { 'console_scripts': [ f'{SHIBOKEN} = {package_name}.scripts.shiboken_tool:main', f'{SHIBOKEN}-genpyi = {package_name}.scripts.shiboken_tool:genpyi', @@ -193,19 +177,20 @@ class Config(object): } elif self.internal_build_type == self.pyside_option_name: - setup_kwargs['name'] = self.pyside_st_name - setup_kwargs['description'] = ("Python bindings for the Qt cross-platform application " - "and UI framework") - setup_kwargs['install_requires'] = [ + self.setup_kwargs['name'] = self.pyside_st_name + self.setup_kwargs['description'] = ( + "Python bindings for the Qt cross-platform application and UI framework" + ) + self.setup_kwargs['install_requires'] = [ f"{self.shiboken_module_st_name}=={package_version}" ] - if qt_install_path: - _pyside_tools = available_pyside_tools(qt_tools_path=qt_install_path) + if qt_install_dir: + _pyside_tools = available_pyside_tools(qt_tools_path=Path(qt_install_dir)) # replacing pyside6-android_deploy by pyside6-android-deploy for consistency - # Also, the tool should not exist in any other platform than Linux + # Also, the tool should not exist in any other platform than Linux and macOS _console_scripts = [] - if ("android_deploy" in _pyside_tools) and sys.platform.startswith("linux"): + if ("android_deploy" in _pyside_tools) and sys.platform in ["linux", "darwin"]: _console_scripts = [(f"{PYSIDE}-android-deploy =" " PySide6.scripts.pyside_tool:android_deploy")] _pyside_tools.remove("android_deploy") @@ -213,9 +198,7 @@ class Config(object): _console_scripts.extend([f'{PYSIDE}-{tool} = {package_name}.scripts.pyside_tool:' f'{tool}' for tool in _pyside_tools]) - setup_kwargs['entry_points'] = {'console_scripts': _console_scripts} - - self.setup_kwargs = setup_kwargs + self.setup_kwargs['entry_points'] = {'console_scripts': _console_scripts} def get_long_description(self): readme_filename = 'README.md' @@ -228,31 +211,23 @@ class Config(object): elif self.is_internal_pyside_build(): readme_filename = f'README.{PYSIDE}.md' - content = '' - changes = '' - try: - with open(self.setup_script_dir / readme_filename) as f: - readme = f.read() - except Exception as e: - log.error(f"Couldn't read contents of {readme_filename}. {e}") - raise + with open(Path(self.setup_script_dir) / readme_filename) as f: + readme = f.read() # Don't include CHANGES.rst for now, because we have not decided # how to handle change files yet. include_changes = False if include_changes: try: - with open(self.setup_script_dir / changes_filename) as f: + changes = '' + with open(Path(self.setup_script_dir) / changes_filename) as f: changes = f.read() except Exception as e: log.error(f"Couldn't read contents of {changes_filename}. {e}") raise - content += readme - - if changes: - content += f"\n\n{changes}" + return f"{readme}\n\n{changes}" - return content + return readme def package_name(self): """ diff --git a/build_scripts/log.py b/build_scripts/log.py index c9ccf3fb..dfff76a0 100644 --- a/build_scripts/log.py +++ b/build_scripts/log.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import logging diff --git a/build_scripts/main.py b/build_scripts/main.py index bf71f996..62602ef4 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -1,17 +1,18 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import importlib import os import platform import re import sys +import subprocess import sysconfig import time from packaging.version import parse as parse_version from pathlib import Path from shutil import copytree, rmtree -from textwrap import dedent # PYSIDE-1760: Pre-load setuptools modules early to avoid racing conditions. # may be touched (should be avoided anyway, btw.) @@ -38,7 +39,7 @@ from .platforms.windows_desktop import prepare_packages_win32 from .qtinfo import QtInfo from .utils import (copydir, copyfile, detect_clang, get_numpy_location, get_python_dict, - linux_fix_rpaths_for_library, macos_fix_rpaths_for_library, + linux_fix_rpaths_for_library, macos_fix_rpaths_for_library, parse_modules, platform_cmake_options, remove_tree, run_process, run_process_output, update_env_path, which) from . import PYSIDE, PYSIDE_MODULE, SHIBOKEN @@ -119,7 +120,7 @@ def get_allowed_python_versions(): pattern = r'Programming Language :: Python :: (\d+)\.(\d+)' supported = [] - for line in config.python_version_classifiers: + for line in config.classifiers: found = re.search(pattern, line) if found: major = int(found.group(1)) @@ -160,6 +161,22 @@ def prepare_build(): qt_src_dir = maybe_qt_src_dir +def get_soname(clang_lib_path: Path) -> str: + """Getting SONAME from a shared library using readelf. Works only on Linux. + """ + clang_lib_path = Path(clang_lib_path) + try: + result = subprocess.run(['readelf', '-d', str(clang_lib_path)], + capture_output=True, text=True, check=True) + for line in result.stdout.split('\n'): + if 'SONAME' in line: + soname = line.split('[')[1].split(']')[0] + return soname + except subprocess.CalledProcessError as e: + print(f"Failed to get SONAME: {e}") + return None + + class PysideInstall(_install, CommandMixin): user_options = _install.user_options + CommandMixin.mixin_user_options @@ -390,18 +407,18 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): for ext in config.get_buildable_extensions(): self.build_extension(ext) - if OPTION["BUILDTESTS"]: - # we record the latest successful build and note the - # build directory for supporting the tests. - timestamp = time.strftime('%Y-%m-%d_%H%M%S') - build_history = setup_script_dir / 'build_history' - unique_dir = build_history / timestamp - unique_dir.mkdir(parents=True) - fpath = unique_dir / 'build_dir.txt' - with open(fpath, 'w') as f: - print(self.build_dir, file=f) - print(self.build_classifiers, file=f) - log.info(f"Created {build_history}") + # We always record the history, whether tests are built or not. + # Record the latest successful build and note the + # build directory for supporting the tests or other tools. + timestamp = time.strftime('%Y-%m-%d_%H%M%S') + build_history = setup_script_dir / 'build_history' + unique_dir = build_history / timestamp + unique_dir.mkdir(parents=True) + fpath = unique_dir / 'build_dir.txt' + with open(fpath, 'w') as f: + print(self.build_dir, file=f) + print(self.build_classifiers, file=f) + log.info(f"Created {build_history}") if not OPTION["SKIP_PACKAGING"]: # Build patchelf if needed @@ -448,6 +465,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): except Exception as e: log.warning(f'problem renaming "{self.st_build_dir}"') log.warning(f'ignored error: {type(e).__name__}: {e}') + else: log.info("Skipped preparing and building packages.") log.info(f"--- Build completed ({elapsed()}s)") @@ -468,27 +486,13 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): log.info(f"Make generator: {self.make_generator}") log.info(f"Make jobs: {OPTION['JOBS']}") log.info("-" * 3) - log.info(f"setup.py directory: {self.script_dir}") - log.info(f"Build scripts directory: {build_scripts_dir}") - log.info(f"Sources directory: {self.sources_dir}") - log.info(dedent(f""" - Building {config.package_name()} will create and touch directories - in the following order: - make build directory -> - make install directory -> - setuptools build directory -> - setuptools install directory - (usually path-installed-python/lib/python*/site-packages/*) - """)) + log.info(f"setup.py directory: {self.script_dir}") + log.info(f"Build scripts directory: {build_scripts_dir}") + log.info(f"Sources directory: {self.sources_dir}") log.info(f"make build directory: {self.build_dir}") log.info(f"make install directory: {self.install_dir}") log.info(f"setuptools build directory: {self.st_build_dir}") log.info(f"setuptools install directory: {setuptools_install_prefix}") - log.info(dedent(f""" - make-installed site-packages directory: {self.site_packages_dir} - (only relevant for copying files from 'make install directory' - to 'setuptools build directory' - """)) log.info("-" * 3) log.info(f"Python executable: {self.py_executable}") log.info(f"Python includes: {self.py_include_dir}") @@ -508,7 +512,11 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): log.info("-" * 3) if sys.platform == 'win32': log.info(f"OpenSSL dll directory: {OPTION['OPENSSL']}") - if sys.platform == 'darwin': + # for cross-compilation it is possible to use a macOS host, but + # pyside_macos_deployment_target is not relevant for the target. + # The only exception here is when we are trying to cross-compile from intel mac to m1 mac. + # This case is not supported yet. + if sys.platform == 'darwin' and not self.is_cross_compile: pyside_macos_deployment_target = (macos_pyside_min_deployment_target()) log.info(f"MACOSX_DEPLOYMENT_TARGET set to: {pyside_macos_deployment_target}") log.info("=" * 30) @@ -587,6 +595,15 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): cmake_cmd.append(f"-DCMAKE_UNITY_BUILD_BATCH_SIZE={batch_size}") log.info("Using UNITY build") + if OPTION['SHIBOKEN_FORCE_PROCESS_SYSTEM_HEADERS']: + cmake_cmd.append("-DPYSIDE_TREAT_QT_INCLUDE_DIRS_AS_NON_SYSTEM=ON") + log.info("Shiboken will now process system Qt headers") + + if OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS']: + extra_include_paths = ';'.join(OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS'].split(',')) + cmake_cmd.append(f"-DSHIBOKEN_FORCE_PROCESS_SYSTEM_INCLUDE_PATHS={extra_include_paths}") + log.info(f"Shiboken will now process system headers from: {extra_include_paths}") + cmake_cmd += [ "-G", self.make_generator, f"-DBUILD_TESTS={self.build_tests}", @@ -627,24 +644,11 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): f"Path given: {config_dir}") if OPTION["MODULE_SUBSET"]: - module_sub_set = '' - for m in OPTION["MODULE_SUBSET"].split(','): - if m.startswith('Qt'): - m = m[2:] - if module_sub_set: - module_sub_set += ';' - module_sub_set += m - cmake_cmd.append(f"-DMODULES={module_sub_set}") + cmake_cmd.append(f"-DMODULES={parse_modules(OPTION['MODULE_SUBSET'])}") if OPTION["SKIP_MODULES"]: - skip_modules = '' - for m in OPTION["SKIP_MODULES"].split(','): - if m.startswith('Qt'): - m = m[2:] - if skip_modules: - skip_modules += ';' - skip_modules += m - cmake_cmd.append(f"-DSKIP_MODULES={skip_modules}") + cmake_cmd.append(f"-DSKIP_MODULES={parse_modules(OPTION['SKIP_MODULES'])}") + # Add source location for generating documentation cmake_src_dir = OPTION["QT_SRC"] if OPTION["QT_SRC"] else qt_src_dir if cmake_src_dir: @@ -680,21 +684,28 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): if OPTION['NO_OVERRIDE_OPTIMIZATION_FLAGS']: cmake_cmd.append("-DQFP_NO_OVERRIDE_OPTIMIZATION_FLAGS=1") - if OPTION["LIMITED_API"] == "yes": - cmake_cmd.append("-DFORCE_LIMITED_API=yes") - elif OPTION["LIMITED_API"] == "no": - cmake_cmd.append("-DFORCE_LIMITED_API=no") - elif not OPTION["LIMITED_API"]: + if not OPTION["LIMITED_API"]: if sys.platform == 'win32' and self.debug: cmake_cmd.append("-DFORCE_LIMITED_API=no") else: - raise SetupError("option limited-api must be 'yes' or 'no' " - "(default yes if applicable, i.e. Python " - "version >= 3.9 and release build if on Windows)") + if OPTION["LIMITED_API"].lower() in ("yes", "y", "1", "true"): + cmake_cmd.append("-DFORCE_LIMITED_API=yes") + elif OPTION["LIMITED_API"].lower() in ("no", "n", "0", "false"): + cmake_cmd.append("-DFORCE_LIMITED_API=no") + else: + raise SetupError( + "Option '--limited-api' must be 'yes' or 'no'." + f"Default is yes if Python version >= {get_allowed_python_versions()[0]} " + "and Release build on Windows" + ) if OPTION["DISABLE_PYI"]: cmake_cmd.append("-DDISABLE_PYI=yes") + if OPTION["UNOPTIMIZE"]: + value = OPTION["UNOPTIMIZE"] + cmake_cmd.append(f"-DSHIBOKEN_UNOPTIMIZE={value}") + if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE: cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON") else: @@ -745,7 +756,9 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): cmake_cmd += platform_cmake_options() - if sys.platform == 'darwin': + # for a macOS host, cross-compilation is possible, but for the host system as such + # we only build shiboken. Hence the following code can be skipped. + if sys.platform == 'darwin' and not self.is_cross_compile: if OPTION["MACOS_ARCH"]: # also tell cmake which architecture to use cmake_cmd.append(f"-DCMAKE_OSX_ARCHITECTURES:STRING={OPTION['MACOS_ARCH']}") @@ -823,6 +836,9 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): # cross-compiling pyside. cmake_cmd.append(f"-DQFP_SHIBOKEN_TARGET_PATH={self.install_dir}") + if OPTION["SKIP_MYPY_TEST"]: + cmake_cmd.append("-DSKIP_MYPY_TEST=1") + if self.cmake_toolchain_file: cmake_cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={self.cmake_toolchain_file}") @@ -842,13 +858,6 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): if run_process(cmd_make) != 0: raise SetupError(f"Error compiling {extension}") - if sys.version_info == (3, 6) and sys.platform == "darwin": - # Python 3.6 has a Sphinx problem because of docutils 0.17 . - # Instead of pinning v0.16, setting the default encoding fixes that. - # Since other platforms are not affected, we restrict this to macOS. - if "UTF-8" not in os.environ.get("LC_ALL", ""): - os.environ["LC_ALL"] = "en_US.UTF-8" - if OPTION["BUILD_DOCS"]: if extension.lower() == SHIBOKEN: found = importlib.util.find_spec("sphinx") @@ -1036,10 +1045,27 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): if clang_lib_path.exists(): basename = clang_lib_path.name + # In case of static libclang we don't need the lib file inside the wheel + if '.a' == clang_lib_path.suffix: + log.info("Skip copying libclang archive to the package.") + return + log.info(f"Copying libclang shared library {clang_lib_path} to the package " f"folder as {basename}.") destination_path = destination_dir / basename + # It is possible that the resolved libclang has a different SONAME + # For example the actual libclang might be named libclang.so.14.0.0 and its + # SONAME might be libclang.so.13 + # In this case, the ideal approach is to find the SONAME and create a symlink to the + # actual libclang in the destination directory. But, Python packaging (setuptools) + # does not support symlinks. + # So, we rename the actual libclang to the SONAME and copy it to the destination + if sys.platform == 'linux': + soname = get_soname(clang_lib_path) + if soname and soname != clang_lib_path.name: + destination_path = destination_path.parent / soname + # Need to modify permissions in case file is not writable # (a reinstall would cause a permission denied error). copyfile(clang_lib_path, @@ -1200,10 +1226,6 @@ class PysideBaseDocs(Command, CommandMixin): user_options = CommandMixin.mixin_user_options def __init__(self, *args, **kwargs): - if args[0].commands[0] == "build_rst_docs": - args[0].commands[0] = "build_base_docs" - log.warning("'build_rst_docs' is deprecated and will be removed. " - "Please use 'build_base_docs' instead.") self.command_name = "build_base_docs" Command.__init__(self, *args, **kwargs) CommandMixin.__init__(self) @@ -1280,21 +1302,42 @@ class PysideBaseDocs(Command, CommandMixin): raise SetupError(f"Error running CMake for {self.doc_dir}") if self.name == PYSIDE: + def run_script(script_path, args=None): + cmd = [sys.executable, os.fspath(script_path)] + if args: + cmd.extend(args) + if run_process(cmd) != 0: + raise SetupError(f"Error running {script_path}") + self.sphinx_src = self.out_dir / "base" + # Generates the .rst files from the examples example_gallery = config.setup_script_dir / "tools" / "example_gallery" / "main.py" - assert example_gallery.is_file() - example_gallery_cmd = [sys.executable, os.fspath(example_gallery)] - if OPTION["LOG_LEVEL"] == LogLevel.QUIET: - example_gallery_cmd.append("--quiet") - qt_src_dir = OPTION['QT_SRC'] - if qt_src_dir: - example_gallery_cmd.extend(["--qt-src-dir", qt_src_dir]) - if run_process(example_gallery_cmd) != 0: - raise SetupError(f"Error running example gallery for {self.doc_dir}") + if example_gallery.is_file(): + example_gallery_args = [] + if OPTION["LOG_LEVEL"] == LogLevel.QUIET: + example_gallery_args.append("--quiet") + qt_src_dir = OPTION['QT_SRC'] + if qt_src_dir: + example_gallery_args.extend(["--qt-src-dir", qt_src_dir]) + run_script(example_gallery, example_gallery_args) + else: + log.warning("Example gallery script for generating .rst for examples" + f"not found: {example_gallery}") + + # Generates the .rst files from the release notes + release_notes = config.setup_script_dir / "tools" / "release_notes" / "main.py" + if release_notes.is_file(): + release_notes_args = [] + if OPTION["LOG_LEVEL"] != LogLevel.QUIET: + release_notes_args.append("--verbose") + run_script(release_notes, release_notes_args) + else: + log.warning("Release notes script for generating .rst for release notes" + f"not found: {release_notes}") elif self.name == SHIBOKEN: self.sphinx_src = self.out_dir - sphinx_cmd = ["sphinx-build", "-b", "html", "-j", "auto", "-c", + sphinx_cmd = ["sphinx-build", "-b", "html", "-j", "auto", "-n", "-c", str(self.sphinx_src), str(self.doc_dir), str(self.out_dir)] if run_process(sphinx_cmd) != 0: @@ -1316,8 +1359,6 @@ cmd_class_dict = { 'install': PysideInstall, 'install_lib': PysideInstallLib, 'build_base_docs': PysideBaseDocs, - # TODO: Remove build_rst_docs in the next version, see PYSIDE-2504 - 'build_rst_docs': PysideBaseDocs, } if wheel_module_exists: pyside_bdist_wheel = get_bdist_wheel_override() diff --git a/build_scripts/options.py b/build_scripts/options.py index bfaf0326..5963a398 100644 --- a/build_scripts/options.py +++ b/build_scripts/options.py @@ -1,5 +1,6 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from setuptools import Command @@ -9,7 +10,7 @@ from pathlib import Path from .log import log, LogLevel from .qtinfo import QtInfo -from .utils import memoize, which +from .utils import memoize, which, Singleton _AVAILABLE_MKSPECS = ["ninja", "msvc", "mingw"] if sys.platform == "win32" else ["ninja", "make"] @@ -25,6 +26,7 @@ Additional options: --shiboken-host-path Path to host shiboken package when cross-compiling --qt-host-path Path to host Qt installation when cross-compiling --disable-pyi Disable .pyi file generation + --skip-mypy-test Skip the mypy test which can take long in debug builds """ @@ -39,7 +41,7 @@ def _warn_deprecated_option(option, replacement=None): log.warning(w) -class Options(object): +class Options(metaclass=Singleton): def __init__(self): # Dictionary containing values of all the possible options. @@ -101,88 +103,85 @@ class Options(object): self.dict[name] = value return value + def find_qtpaths(self): + # Skip the first run that will trigger the three different build + # stated of the setup process + if self.dict["internal-build-type"] is None: + return None + # for these command --qtpaths should not be required + no_qtpaths_commands = ["--help", "--help-commands", "--qt-target-path", "build_base_docs"] -options = Options() - - -def has_option(*args, **kwargs): - return options.has_option(*args, **kwargs) - - -def option_value(*args, **kwargs): - return options.option_value(*args, **kwargs) - + for no_qtpaths_command in no_qtpaths_commands: + if any(no_qtpaths_command in argument for argument in sys.argv): + return None -def _jobs_option_value(): - """Option value for parallel builds.""" - value = option_value('parallel', short_option_name='j') - if value: - return f"-j{value}" if not value.startswith('-j') else value - return '' + qtpaths = self.option_value("qtpaths") + if qtpaths is not None: + return qtpaths + # if qtpaths is not given as cli option, try to find it in PATH + qtpaths = which("qtpaths6") + if qtpaths is not None: + return str(Path(qtpaths).resolve()) -def find_qtpaths(): - # for these command --qtpaths should not be required - no_qtpaths_commands = ["--help", "--help-commands", "--qt-target-path", "build_base_docs", - "build_rst_docs"] + qtpaths = which("qtpaths") + if qtpaths is not None: + return str(Path(qtpaths).resolve()) - for no_qtpaths_command in no_qtpaths_commands: - if any(no_qtpaths_command in argument for argument in sys.argv): - return None + if qtpaths is None: + sys.exit(-1) - qtpaths = option_value("qtpaths") - if qtpaths: return qtpaths - # if qtpaths is not given as cli option, try to find it in PATH - qtpaths = which("qtpaths6") - if qtpaths: - return str(qtpaths.resolve()) - - qtpaths = which("qtpaths") - if qtpaths: - return str(qtpaths.resolve()) - - return qtpaths - - -# Declare options which need to be known when instantiating the setuptools -# commands or even earlier during SetupRunner.run(). -OPTION = { - "BUILD_TYPE": option_value("build-type"), - "INTERNAL_BUILD_TYPE": option_value("internal-build-type"), - # number of parallel build jobs - "JOBS": _jobs_option_value(), - # Legacy, not used any more. - "JOM": has_option('jom'), - "MACOS_USE_LIBCPP": has_option("macos-use-libc++"), - "LOG_LEVEL": option_value("log-level", remove=False), - "QUIET": has_option('quiet'), - "VERBOSE_BUILD": has_option('verbose-build'), - "SNAPSHOT_BUILD": has_option("snapshot-build"), - "LIMITED_API": option_value("limited-api"), - "DISABLE_PYI": has_option("disable-pyi"), - "PACKAGE_TIMESTAMP": option_value("package-timestamp"), - # This is used automatically by setuptools.command.install object, to - # specify the final installation location. - "FINAL_INSTALL_PREFIX": option_value("prefix", remove=False), - "CMAKE_TOOLCHAIN_FILE": option_value("cmake-toolchain-file"), - "SHIBOKEN_HOST_PATH": option_value("shiboken-host-path"), - "SHIBOKEN_HOST_PATH_QUERY_FILE": option_value("internal-shiboken-host-path-query-file"), - "QT_HOST_PATH": option_value("qt-host-path"), - # This is used to identify the template for doc builds - "QTPATHS": find_qtpaths() - # This is an optional command line option. If --qtpaths is not provided via command-line, - # then qtpaths is checked inside PATH variable -} - -_deprecated_option_jobs = option_value('jobs') -if _deprecated_option_jobs: - _warn_deprecated_option('jobs', 'parallel') - OPTION["JOBS"] = _deprecated_option_jobs - - -class CommandMixin(object): + def _jobs_option_value(self): + """Option value for parallel builds.""" + value = self.option_value('parallel', short_option_name='j') + + _deprecated_option_jobs = self.option_value('jobs') + if _deprecated_option_jobs: + _warn_deprecated_option('jobs', 'parallel') + value = _deprecated_option_jobs + + if value: + return f"-j{value}" if not value.startswith('-j') else value + return '' + + def resolve(self): + return { + "BUILD_TYPE": self.option_value("build-type"), + "INTERNAL_BUILD_TYPE": self.option_value("internal-build-type"), + # number of parallel build jobs + "JOBS": self._jobs_option_value(), + # Legacy, not used any more. + "JOM": self.has_option('jom'), + "MACOS_USE_LIBCPP": self.has_option("macos-use-libc++"), + "LOG_LEVEL": self.option_value("log-level", remove=False), + "QUIET": self.has_option('quiet'), + "VERBOSE_BUILD": self.has_option('verbose-build'), + "SNAPSHOT_BUILD": self.has_option("snapshot-build"), + "LIMITED_API": self.option_value("limited-api"), + "UNOPTIMIZE": self.option_value("unoptimize"), + "DISABLE_PYI": self.has_option("disable-pyi"), + "SKIP_MYPY_TEST": self.has_option("skip-mypy-test"), + "PACKAGE_TIMESTAMP": self.option_value("package-timestamp"), + # This is used automatically by setuptools.command.install object, to + # specify the final installation location. + "FINAL_INSTALL_PREFIX": self.option_value("prefix", remove=False), + "CMAKE_TOOLCHAIN_FILE": self.option_value("cmake-toolchain-file"), + "SHIBOKEN_HOST_PATH": self.option_value("shiboken-host-path"), + "SHIBOKEN_HOST_PATH_QUERY_FILE": self.option_value( + "internal-shiboken-host-path-query-file" + ), + "QT_HOST_PATH": self.option_value("qt-host-path"), + # This is used to identify the template for doc builds + "QTPATHS": self.find_qtpaths() + # This is an optional command line option. + # If --qtpaths is not provided via command-line, + # then qtpaths is checked inside PATH variable + } + + +class CommandMixin: """Mixin for the setuptools build/install commands handling the options.""" _static_class_finalized_once = False @@ -247,7 +246,27 @@ class CommandMixin(object): ('plat-name=', None, 'The platform name for which we are cross-compiling'), ('unity', None, 'Use CMake UNITY_BUILD_MODE (obsolete)'), ('no-unity', None, 'Disable CMake UNITY_BUILD_MODE'), - ('unity-build-batch-size=', None, 'Value of CMAKE_UNITY_BUILD_BATCH_SIZE') + ('unity-build-batch-size=', None, 'Value of CMAKE_UNITY_BUILD_BATCH_SIZE'), + # shiboken-force-process-system-headers option is specifically used to tell the clang + # inside shiboken to process the system headers, when building against a system Qt. + # + # This option is specific for Flatpak and OS distro builds of PySide6. So, use with + # caution as it may also try to parse other global headers. + ('shiboken-force-process-system-headers', None, + 'When building PySide against system Qt, shiboken does not ignore the system Qt headers'), + # shiboken-extra-inlude-paths option is specifically used to tell the clang inside shiboken + # to include extra paths when parsing the headers. Use with caution. + ('shiboken-extra-include-paths=', None, + 'Extra include paths for shiboken. Comma separated.'), + # flatpak option is used to build PySide6 for Flatpak. Flatpak is a special case where + # some of the headers for the Qt modules are located as system headers in /usr/include in + # the KDE flatpak SDK. Therefore --shiboken-force-process-system headers will be by + # default enabled when --flatpak is enabled. + # Apart from that, headers for certain Qt modules like QtWebEngine, QtPdf etc. are located + # in /app/include from the Flapak WebEngine baseapp. Therefore when the --flatpak option is + # enabled, the extra include path of /app/include will be added to the option + # --shiboken-extra-include-paths. + ('flatpak', None, 'Build PySide6 for Flatpak.'), ] def __init__(self): @@ -288,7 +307,7 @@ class CommandMixin(object): self.shiboken_target_path = None self.python_target_path = None self.is_cross_compile = False - self.cmake_toolchain_file = None + self.cmake_toolchain_file: str = "" self.make_spec = None self.macos_arch = None self.macos_sysroot = None @@ -309,6 +328,9 @@ class CommandMixin(object): self.unity = False self.no_unity = False self.unity_build_batch_size = "16" + self.shiboken_force_process_system_headers = False + self.shiboken_extra_include_paths = None + self.flatpak = False # When initializing a command other than the main one (so the # first one), we need to copy the user options from the main @@ -357,7 +379,7 @@ class CommandMixin(object): # because we DON'T want those to be found when cross compiling. # Currently when cross compiling, qt-target-path MUST be used. using_cmake_toolchain_file = False - cmake_toolchain_file = None + cmake_toolchain_file: str = "" if OPTION["CMAKE_TOOLCHAIN_FILE"]: self.is_cross_compile = True using_cmake_toolchain_file = True @@ -429,6 +451,12 @@ class CommandMixin(object): "Unity build mode is now the default.") OPTION['UNITY'] = not self.no_unity OPTION['UNITY_BUILD_BATCH_SIZE'] = self.unity_build_batch_size + OPTION['SHIBOKEN_FORCE_PROCESS_SYSTEM_HEADERS'] = self.shiboken_force_process_system_headers + OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS'] = self.shiboken_extra_include_paths + OPTION['FLATPAK'] = self.flatpak + if OPTION['FLATPAK']: + OPTION['SHIBOKEN_FORCE_PROCESS_SYSTEM_HEADERS'] = True + OPTION['SHIBOKEN_EXTRA_INCLUDE_PATHS'] = '/app/include' qtpaths_abs_path = None if self.qtpaths and Path(self.qtpaths).exists(): @@ -461,18 +489,17 @@ class CommandMixin(object): qt_target_path=qt_target_path, cmake_toolchain_file=cmake_toolchain_file) - if 'build_base_docs' not in sys.argv and 'build_rst_docs' not in sys.argv: + if 'build_base_docs' not in sys.argv: try: QtInfo().prefix_dir except Exception as e: if not self.qt_target_path: log.error( - "\nCould not find Qt. You can pass the --qt-target-path= option " - "as a hint where to find Qt. Error was:\n\n\n") + "Could not find Qt. You can pass the --qt-target-path= option " + "as a hint where to find Qt.\n") else: - log.error( - f"\nCould not find Qt via provided option --qt-target-path={qt_target_path}" - "Error was:\n\n\n") + log.error("Could not find Qt via provided option " + f"--qt-target-path={qt_target_path}\n") raise e OPTION['CMAKE'] = self.cmake.resolve() @@ -551,9 +578,8 @@ class CommandMixin(object): # while cross-compiling. # Skip this process for the 'build_base_docs' command if (not self.is_cross_compile - and not self.qt_target_path - and 'build_base_docs' not in sys.argv - and 'build_rst_docs' not in sys.argv): + and not self.qt_target_path + and 'build_base_docs' not in sys.argv): # Enforce usage of qmake in QtInfo if it was given explicitly. if self.qmake: self.has_qmake_option = True @@ -598,3 +624,7 @@ class CommandMixin(object): return False return True + + +# OPTION dictionary that will be imported in other build_scripts +OPTION = Options().resolve() diff --git a/build_scripts/platforms/__init__.py b/build_scripts/platforms/__init__.py index 853aaad7..498a6e52 100644 --- a/build_scripts/platforms/__init__.py +++ b/build_scripts/platforms/__init__.py @@ -1,2 +1,3 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations diff --git a/build_scripts/platforms/linux.py b/build_scripts/platforms/linux.py index b4c66d94..32ad9dac 100644 --- a/build_scripts/platforms/linux.py +++ b/build_scripts/platforms/linux.py @@ -1,5 +1,6 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from pathlib import Path diff --git a/build_scripts/platforms/macos.py b/build_scripts/platforms/macos.py index dbe60d34..31d55278 100644 --- a/build_scripts/platforms/macos.py +++ b/build_scripts/platforms/macos.py @@ -1,5 +1,6 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import fnmatch from pathlib import Path @@ -21,7 +22,7 @@ def _macos_patch_executable(name, _vars=None): macos_add_rpath(rpath, binary) -def prepare_standalone_package_macos(pyside_build, _vars): +def prepare_standalone_package_macos(pyside_build, _vars, is_android=False): built_modules = _vars['built_modules'] constrain_modules = None @@ -119,7 +120,11 @@ def prepare_standalone_package_macos(pyside_build, _vars): ignored_modules = [] if not pyside_build.is_webengine_built(built_modules): ignored_modules.extend(['libQt6WebEngine*.dylib']) + accepted_modules = ['libQt6*.6.dylib'] + if is_android: + accepted_modules = ['libQt6*.so', '*-android-dependencies.xml'] + if constrain_modules: accepted_modules = [f"libQt6{module}*.6.dylib" for module in constrain_modules] @@ -156,6 +161,8 @@ def prepare_standalone_package_macos(pyside_build, _vars): # /plugins/* -> /{st_package_name}/Qt/plugins plugins_target = destination_qt_dir / "plugins" filters = ["*.dylib"] + if is_android: + filters = ["*.so"] copydir("{qt_plugins_dir}", plugins_target, _filter=filters, recursive=True, diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py index 3333f5f9..35d6dd1c 100644 --- a/build_scripts/platforms/unix.py +++ b/build_scripts/platforms/unix.py @@ -1,5 +1,6 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import sys from pathlib import Path @@ -11,7 +12,8 @@ from ..utils import copydir, copyfile, copy_qt_metatypes, makefile from .. import PYSIDE, SHIBOKEN from .linux import prepare_standalone_package_linux from .macos import prepare_standalone_package_macos -from .. import PYSIDE_UNIX_BIN_TOOLS, PYSIDE_UNIX_LIBEXEC_TOOLS, PYSIDE_UNIX_BUNDLED_TOOLS +from .. import (PYSIDE_UNIX_BIN_TOOLS, PYSIDE_UNIX_LIBEXEC_TOOLS, PYSIDE_UNIX_BUNDLED_TOOLS, + PYSIDE_MULTIMEDIA_LIBS) def _macos_copy_gui_executable(name, _vars=None): @@ -122,9 +124,9 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): scripts = ["pyside_tool.py", "metaobjectdump.py", "project.py", "qml.py", "qtpy2cpp.py", "deploy.py"] - script_dirs = ["qtpy2cpp_lib", "deploy_lib", "project"] + script_dirs = ["qtpy2cpp_lib", "deploy_lib", "project_lib"] - if sys.platform.startswith("linux"): + if sys.platform in ["linux", "darwin"]: scripts.append("android_deploy.py") scripts.append("requirements-android.txt") script_dirs.extend(["deploy_lib/android", @@ -198,10 +200,10 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): # /share/{st_package_name}/glue/* -> # /{st_package_name}/glue - copydir( - "{install_dir}/share/{st_package_name}/glue", - "{st_build_dir}/{st_package_name}/glue", - _vars=_vars) + for glue in ['glue', 'doc']: + source = "{install_dir}/share/{st_package_name}/" + glue + target = "{st_build_dir}/{st_package_name}/" + glue + copydir(source, target, _vars=_vars) if not is_android: # /pyside6/{st_package_name}/support/* -> @@ -232,12 +234,20 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False): "{st_build_dir}/{st_package_name}/jar", _vars=_vars) + # Some libraries specific to Linux/Android from 6.8 + # eg: the libav* libraries are required for the multimedia module + if config.is_internal_pyside_build() and sys.platform != "darwin": + qt_multimedia_filters = [f"lib{lib}*.so*" for lib in PYSIDE_MULTIMEDIA_LIBS] + copydir("{qt_lib_dir}", destination_qt_dir / "lib", + _filter=qt_multimedia_filters, + recursive=False, _vars=_vars, force_copy_symlinks=True) + # Copy Qt libs to package if OPTION["STANDALONE"]: if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build(): _vars['built_modules'] = generated_config['built_modules'] if sys.platform == 'darwin': - prepare_standalone_package_macos(pyside_build, _vars) + prepare_standalone_package_macos(pyside_build, _vars, is_android=is_android) else: prepare_standalone_package_linux(pyside_build, _vars, cross_build, is_android=is_android) diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index 9c29953b..eaab7acb 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -1,5 +1,6 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import functools import os @@ -11,8 +12,8 @@ from ..log import log from ..config import config from ..options import OPTION from ..utils import (copydir, copyfile, copy_qt_metatypes, - download_and_extract_7z, filter_match, makefile) -from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS + download_and_extract_7z, filter_match, makefile, in_coin) +from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_MULTIMEDIA_LIBS def prepare_packages_win32(pyside_build, _vars): @@ -116,7 +117,7 @@ def prepare_packages_win32(pyside_build, _vars): target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script}" copyfile(src, target, force=False, _vars=_vars) - for script_dir in ("qtpy2cpp_lib", "deploy_lib", "project"): + for script_dir in ("qtpy2cpp_lib", "deploy_lib", "project_lib"): src = f"{{install_dir}}/bin/{script_dir}" target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script_dir}" # Exclude subdirectory tests @@ -152,10 +153,9 @@ def prepare_packages_win32(pyside_build, _vars): # /share/{st_package_name}/glue/* -> # /{st_package_name}/glue - copydir( - "{install_dir}/share/{st_package_name}/glue", - destination_dir / "glue", - _vars=_vars) + for glue in ['glue', 'doc']: + source = "{install_dir}/share/{st_package_name}/" + glue + copydir(source, destination_dir / glue, _vars=_vars) # /pyside6/{st_package_name}/support/* -> # /{st_package_name}/support/* @@ -217,8 +217,7 @@ msvc_redist = [ def copy_msvc_redist_files(destination_dir): - in_coin = os.environ.get('COIN_LAUNCH_PARAMETERS', None) - if in_coin is None: + if not in_coin(): log.info("Qt dependency DLLs (MSVC redist) will not be copied.") return @@ -244,18 +243,15 @@ def copy_msvc_redist_files(destination_dir): raise FileNotFoundError(msg) -def copy_qt_dependency_dlls(_vars, destination_qt_dir, artifacts): +def download_qt_dependency_dlls(_vars, destination_qt_dir, artifacts): # Extract Qt dependency dlls when building on Qt CI. - in_coin = os.environ.get('COIN_LAUNCH_PARAMETERS', None) - if in_coin is None: + if not in_coin(): log.info("Qt dependency DLLs will not be downloaded and extracted.") return with tempfile.TemporaryDirectory() as temp_path: redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/" - zip_file = "pyside_qt_deps_64_2019.7z" - if "{target_arch}".format(**_vars) == "32": - zip_file = "pyside_qt_deps_32_2019.7z" + zip_file = "pyside_qt_deps_681_64_2022.7z" try: download_and_extract_7z(redist_url + zip_file, temp_path) except Exception as e: @@ -284,40 +280,31 @@ def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars): copy_translations = False copy_qt_conf = False copy_qt_permanent_artifacts = False - copy_clang = True # /bin/*.dll and Qt *.exe -> /{st_package_name} - qt_artifacts_permanent = [ - "avcodec-60.dll", - "avformat-60.dll", - "avutil-58.dll", - "swresample-4.dll", - "swscale-7.dll", - "opengl*.dll", - "designer.exe", - "linguist.exe", - "lrelease.exe", - "lupdate.exe", - "lconvert.exe", - "qtdiag.exe" - ] - - # Choose which EGL library variants to copy. - qt_artifacts_egl = [ - "libEGL{}.dll", - "libGLESv2{}.dll" - ] - if pyside_build.qtinfo.build_type != 'debug_and_release': - egl_suffix = '*' - elif pyside_build.debug: - egl_suffix = 'd' - else: - egl_suffix = '' - qt_artifacts_egl = [a.format(egl_suffix) for a in qt_artifacts_egl] + qt_multimedia_artifacts_permanent = [f"{lib}-*.dll" for lib in PYSIDE_MULTIMEDIA_LIBS] + + def qt_rhi_artifacts_permanent(): + result = ["opengl*.dll"] + if pyside_build.qtinfo.build_type != 'debug_and_release': + suffix = '*' + elif pyside_build.debug: + suffix = 'd' + else: + suffix = '' + result.append(f"libEGL{suffix}.dll") # Needed in Qt 6 (ANGLE)? + result.append(f"libGLESv2{suffix}.dll") + return result if copy_qt_permanent_artifacts: - artifacts = qt_artifacts_permanent + qt_artifacts_egl - copy_qt_dependency_dlls(_vars, destination_qt_dir, artifacts) + download_qt_dependency_dlls(_vars, destination_qt_dir, qt_rhi_artifacts_permanent()) + if not copydir("{qt_bin_dir}", destination_qt_dir, + _filter=qt_multimedia_artifacts_permanent, + recursive=False, _vars=_vars): + msg = "The multimedia artifacts were not found." + log.warning(msg) + if in_coin(): # Allow local developer builds without -DQT_DEPLOY_FFMPEG... + raise FileNotFoundError(msg) # /bin/*.dll and Qt *.pdbs -> /{st_package_name} part two # File filter to copy only debug or only release files. diff --git a/build_scripts/qfp_tool.py b/build_scripts/qfp_tool.py index abaf48fc..01c05f14 100644 --- a/build_scripts/qfp_tool.py +++ b/build_scripts/qfp_tool.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import datetime import os @@ -11,7 +12,6 @@ import warnings from argparse import ArgumentParser, RawTextHelpFormatter from enum import Enum, auto from pathlib import Path -from typing import List DESC = """ Utility script for working with Qt for Python. @@ -101,14 +101,14 @@ def which(needle: str): return None -def command_log_string(args: List[str], directory: Path): +def command_log_string(args: list[str], directory: Path): result = f'[{directory.name}]' for arg in args: result += f' "{arg}"' if ' ' in arg else f' {arg}' return result -def execute(args: List[str]): +def execute(args: list[str]): """Execute a command and print to log""" log_string = command_log_string(args, Path.cwd()) print(log_string) @@ -172,7 +172,7 @@ def edit_config_file(): """ Config file handling, cache and read function """ -config_dict = {} +config_dict: dict = {} def read_config_file(file_name): @@ -263,15 +263,15 @@ def read_config_python_binary() -> str: def get_config_file(base_name) -> Path: global user - home = os.getenv('HOME') + home = os.getenv('HOME', default="") if IS_WINDOWS: # Set a HOME variable on Windows such that scp. etc. # feel at home (locating .ssh). if not home: - home = os.getenv('HOMEDRIVE') + os.getenv('HOMEPATH') + home = os.getenv('HOMEDRIVE', default="") + os.getenv('HOMEPATH', default="") os.environ['HOME'] = home user = os.getenv('USERNAME') - config_file = Path(os.getenv('APPDATA')) / base_name + config_file = Path(os.getenv('APPDATA', default="")) / base_name else: user = os.getenv('USER') config_dir = Path(home) / '.config' @@ -290,7 +290,7 @@ def build(target: str): acceleration = read_acceleration_config() if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD: arguments.append(INCREDIBUILD_CONSOLE) - arguments.appendh('--avoid') # caching, v0.96.74 + arguments.append('--avoid') # caching, v0.96.74 arguments.extend([read_config_python_binary(), 'setup.py', target]) build_arguments = read_config_build_arguments() if opt_verbose and LOG_LEVEL_OPTION in build_arguments: @@ -341,7 +341,7 @@ def run_tests(): """Run tests redirected into a log file with a time stamp""" logfile_name = datetime.datetime.today().strftime("test_%Y%m%d_%H%M.txt") binary = sys.executable - command = f'"{binary}" testrunner.py test > {logfile_name}' + command = f'"{binary}" testrunner.py test --reruns 2 > {logfile_name}' print(command_log_string([command], Path.cwd())) start_time = time.time() result = 0 if opt_dry_run else os.system(command) diff --git a/build_scripts/qtinfo.py b/build_scripts/qtinfo.py index 1eb7c490..352fad46 100644 --- a/build_scripts/qtinfo.py +++ b/build_scripts/qtinfo.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import os import subprocess @@ -9,7 +10,7 @@ from .utils import (configure_cmake_project, parse_cmake_project_message_info, platform_cmake_options) -class QtInfo(object): +class QtInfo: _instance = None # singleton helpers def __new__(cls): # __new__ always a classmethod @@ -31,7 +32,7 @@ class QtInfo(object): self._force_qmake = False self._use_cmake = False self._qt_target_path = None - self._cmake_toolchain_file = None + self._cmake_toolchain_file: str = "" # Dict to cache qmake values. self._query_dict = {} diff --git a/build_scripts/setup_runner.py b/build_scripts/setup_runner.py index 9bcbb4af..ec679801 100644 --- a/build_scripts/setup_runner.py +++ b/build_scripts/setup_runner.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import os import sys @@ -18,7 +19,7 @@ from build_scripts.utils import run_process from build_scripts.log import log, LogLevel -class SetupRunner(object): +class SetupRunner: def __init__(self, orig_argv): self.invocations_list = [] @@ -93,7 +94,7 @@ class SetupRunner(object): setup_cmd.append(self.construct_cmd_line_argument(name, value)) # Add --reuse-build option if requested and not already present. - if (reuse_build and command in ('bdist_wheel', 'build', 'build_base_docs', 'build_rst_docs', 'install') + if (reuse_build and command in ('bdist_wheel', 'build', 'build_base_docs', 'install') and not self.cmd_line_argument_is_in_args("reuse-build", modified_argv)): setup_cmd.append(self.construct_cmd_line_argument("reuse-build")) return setup_cmd @@ -187,7 +188,7 @@ class SetupRunner(object): setup_script_dir=self.setup_script_dir, cmake_toolchain_file=OPTION["CMAKE_TOOLCHAIN_FILE"], log_level=OPTION["LOG_LEVEL"], - qt_install_path=qt_install_path) + qt_install_dir=qt_install_path) # Enable logging for both the top-level invocation of setup.py # as well as for child invocations. We we now use diff --git a/build_scripts/utils.py b/build_scripts/utils.py index 74d9e6fc..29f2545d 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import errno import fnmatch @@ -11,7 +12,8 @@ import stat import subprocess import sys import tempfile -import urllib.request as urllib +import urllib.request +import urllib.error from collections import defaultdict from pathlib import Path from textwrap import dedent, indent @@ -27,6 +29,15 @@ except NameError: WindowsError = None +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + + def which(name): """ Like shutil.which, but accepts a string or a PathLike and returns a Path @@ -36,9 +47,8 @@ def which(name): if isinstance(name, Path): name = str(name) path = shutil.which(name) - if path is None: - raise TypeError("None was returned") - path = Path(path) + if path is not None: + path = Path(path) except TypeError as e: log.error(f"{name} was not found in PATH: {e}") return path @@ -71,7 +81,12 @@ def get_numpy_location(): if 'site-' in p: numpy = Path(p).resolve() / 'numpy' if numpy.is_dir(): - return os.fspath(numpy / 'core' / 'include') + candidate = numpy / '_core' / 'include' # Version 2 + if not candidate.is_dir(): + candidate = numpy / 'core' / 'include' # Version 1 + if candidate.is_dir(): + return os.fspath(candidate) + log.warning(f"Cannot find numpy include dir under {numpy}") return None @@ -119,6 +134,7 @@ def copyfile(src, dst, force=True, _vars=None, force_copy_symlink=False, # We use 'strict=False' to mimic os.path.realpath in case # the directory doesn't exist. + # TODO: This code can probably be removed when 'python setup.py install' usage is removed. link_target_path = src.resolve(strict=False) if link_target_path.parent == src.parent: link_target = Path(link_target_path.name) @@ -129,7 +145,7 @@ def copyfile(src, dst, force=True, _vars=None, force_copy_symlink=False, os.chdir(target_dir) if link_name.exists(): if (link_name.is_symlink() - and os.readlink(link_name) == link_target): + and os.readlink(link_name) == str(link_target)): log.info(f"Symlink already exists\n {link_name} ->\n {link_target}") return dst os.remove(link_name) @@ -511,9 +527,9 @@ def download_and_extract_7z(fileurl, target): for i in range(1, 10): try: log.info(f"Downloading fileUrl {fileurl}, attempt #{i}") - localfile, info = urllib.urlretrieve(fileurl) + localfile, info = urllib.request.urlretrieve(fileurl) break - except urllib.URLError: + except urllib.error.URLError: pass if not localfile: log.error(f"Error downloading {fileurl} : {info}") @@ -789,6 +805,12 @@ def linux_run_read_elf(executable_path): def linux_set_rpaths(patchelf, executable_path, rpath_string): """ Patches the `executable_path` with a new rpath string. """ + path = Path(executable_path) + mode = path.stat().st_mode + if (mode & stat.S_IWUSR) == 0: + log.info(f"patchelf: {executable_path} is read-only, making writeable.") + path.chmod(mode | stat.S_IWUSR) + cmd = [str(patchelf), '--set-rpath', str(rpath_string), str(executable_path)] if run_process(cmd) != 0: @@ -932,48 +954,6 @@ def get_python_dict(python_script_path): raise -def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): - _pExe = "python" - _env = f"{os.environ.get('PYSIDE_VIRTUALENV') or 'env'+python_ver}" - env_python = f"{_env}/bin/python" - env_pip = f"{_env}/bin/pip" - - if host == "Windows": - log.info("New virtualenv to build {targetArch} in {hostArch} host") - _pExe = "python.exe" - # With windows we are creating building 32-bit target in 64-bit host - if hostArch == "X86_64" and targetArch == "X86": - if python_ver.startswith("3"): - var = f"PYTHON{python_ver}-32_PATH" - log.info(f"Try to find python from {var} env variable") - _path = Path(os.getenv(var, "")) - _pExe = _path / "python.exe" - if not _pExe.is_file(): - log.warning(f"Can't find python.exe from {_pExe}, using default python3") - _pExe = Path(os.getenv("PYTHON3_32_PATH")) / "python.exe" - else: - _pExe = Path(os.getenv("PYTHON2_32_PATH")) / "python.exe" - else: - if python_ver.startswith("3"): - var = f"PYTHON{python_ver}-64_PATH" - log.info(f"Try to find python from {var} env variable") - _path = Path(os.getenv(var, "")) - _pExe = _path / "python.exe" - if not _pExe.is_file(): - log.warning(f"Can't find python.exe from {_pExe}, using default python3") - _pExe = Path(os.getenv("PYTHON3_PATH")) / "python.exe" - env_python = f"{_env}\\Scripts\\python.exe" - env_pip = f"{_env}\\Scripts\\pip.exe" - else: - _pExe = f"python{python_ver}" - try: - run_instruction([_pExe, "--version"], f"Failed to guess python version {_pExe}") - except Exception as e: - print(f"Exception {type(e).__name__}: {e}") - _pExe = "python3" - return (_pExe, _env, env_pip, env_python) - - def run_instruction(instruction, error, initial_env=None): if initial_env is None: initial_env = os.environ @@ -984,26 +964,6 @@ def run_instruction(instruction, error, initial_env=None): exit(result) -def get_ci_qtpaths_path(ci_install_dir, ci_host_os): - qtpaths_path = f"--qtpaths={ci_install_dir}" - if ci_host_os == "MacOS": - return f"{qtpaths_path}/bin/qtpaths" - elif ci_host_os == "Windows": - return f"{qtpaths_path}\\bin\\qtpaths.exe" - else: - return f"{qtpaths_path}/bin/qtpaths" - - -def get_ci_qmake_path(ci_install_dir, ci_host_os): - qmake_path = f"--qmake={ci_install_dir}" - if ci_host_os == "MacOS": - return f"{qmake_path}/bin/qmake" - elif ci_host_os == "Windows": - return f"{qmake_path}\\bin\\qmake.exe" - else: - return f"{qmake_path}/bin/qmake" - - def parse_cmake_conf_assignments_by_key(source_dir): """ Parses a .cmake.conf file that contains set(foo "bar") assignments @@ -1161,3 +1121,18 @@ def copy_qt_metatypes(destination_qt_dir, _vars): copydir(qt_meta_types_dir, destination_qt_dir / rel_meta_data_dir, _filter=["*.json"], recursive=False, _vars=_vars, force_copy_symlinks=True) + + +def in_coin(): + return os.environ.get('COIN_UNIQUE_JOB_ID', None) is not None + + +def parse_modules(modules: str) -> str: + module_sub_set = "" + for m in modules.split(','): + if m.startswith('Qt'): + m = m[2:] + if module_sub_set: + module_sub_set += ';' + module_sub_set += m + return module_sub_set diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index 2112bba9..7ee04a7b 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -1,15 +1,16 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import json import sys from dataclasses import Field, dataclass, field -from typing import Dict, List _pyside_package_path = None _module_json_file_path = None +_multimedia_libs = ["avcodec", "avformat", "avutil", "swresample", "swscale"] def set_pyside_package_path(p): @@ -49,23 +50,24 @@ class ModuleData: name: str ext: str = "" # Libraries not related to Qt modules - lib: List[str] = field(default_factory=list) + lib: list[str] = field(default_factory=list) # Libraries related to Qt modules - qtlib: List[str] = field(default_factory=list) + qtlib: list[str] = field(default_factory=list) # Files from the Qt/qml directory - qml: List[str] = field(default_factory=list) - pyi: List[str] = field(default_factory=list) - translations: List[str] = field(default_factory=list) - typesystems: List[str] = field(default_factory=list) - include: List[str] = field(default_factory=list) - glue: List[str] = field(default_factory=list) - metatypes: List[str] = field(default_factory=list) - plugins: List[str] = field(default_factory=list) + qml: list[str] = field(default_factory=list) + pyi: list[str] = field(default_factory=list) + translations: list[str] = field(default_factory=list) + typesystems: list[str] = field(default_factory=list) + include: list[str] = field(default_factory=list) + glue: list[str] = field(default_factory=list) + doc_glue: list[str] = field(default_factory=list) + metatypes: list[str] = field(default_factory=list) + plugins: list[str] = field(default_factory=list) # For special cases when a file/directory doesn't fall into # the previous categories. - extra_dirs: List[str] = field(default_factory=list) - extra_files: List[str] = field(default_factory=list) + extra_dirs: list[str] = field(default_factory=list) + extra_files: list[str] = field(default_factory=list) # Once the object is created, this method will be executed # and automatically will initialize some of the files that are @@ -87,6 +89,7 @@ class ModuleData: self.typesystems.append(f"typesystem_{_lo}.xml") self.include.append(f"Qt{self.name}/*.h") self.glue.append(f"qt{_lo}.cpp") + self.doc_glue.append(f"qt{_lo}.rst") if not len(self.metatypes): self.metatypes.append(f"qt6{_lo}_relwithdebinfo_metatypes.json") @@ -122,6 +125,7 @@ class ModuleData: self.typesystems = [f"typesystems/{i}" for i in self.typesystems] self.include = [f"include/{i}" for i in self.include] self.glue = [f"glue/{i}" for i in self.glue] + self.doc_glue = [f"doc/{i}" for i in self.doc_glue] def macos_pyside_wrappers_lib(self, s): if s.startswith("Qt"): @@ -130,7 +134,7 @@ class ModuleData: return f"{s}.*{self.ext}*" @classmethod - def get_fields(cls) -> Dict[str, Field]: + def get_fields(cls) -> dict[str, Field]: return cls.__dataclass_fields__ @staticmethod @@ -150,7 +154,7 @@ class ModuleData: # for each module that will be included in the wheel. # PySide wheel -def wheel_files_pyside_essentials() -> List[ModuleData]: +def wheel_files_pyside_essentials() -> list[ModuleData]: files = [ module_QtCore(), module_QtGui(), @@ -186,7 +190,7 @@ def wheel_files_pyside_essentials() -> List[ModuleData]: # PySide Addons wheel -def wheel_files_pyside_addons() -> List[ModuleData]: +def wheel_files_pyside_addons() -> list[ModuleData]: files = [ module_Qt3DAnimation(), module_Qt3DCore(), @@ -199,6 +203,7 @@ def wheel_files_pyside_addons() -> List[ModuleData]: module_QtCharts(), module_QtDataVisualization(), module_QtGraphs(), + module_QtGraphsWidgets(), module_QtMultimedia(), module_QtMultimediaWidgets(), module_QtNetworkAuth(), @@ -224,6 +229,10 @@ def wheel_files_pyside_addons() -> List[ModuleData]: module_QtHttpServer(), module_QtLocation(), module_QtAsyncio(), + module_QtWebView(), + # This is not an actual module, but it's required in order + # to add the 'Quick' components of the WebView. + module_QtWebViewQuick(), ] return files @@ -462,7 +471,9 @@ def module_QtQml() -> ModuleData: "libQt6QmlNetwork", "libQt6QmlWorkerScript", "libQt6QmlXmlListModel", - "libQt6QmlCompiler" + "libQt6QmlCompiler", + "libQt6QmlMeta", + "libQt6LabsPlatform", ] _include = [ @@ -489,6 +500,8 @@ def module_QtQml() -> ModuleData: "qt6qmlmodels_relwithdebinfo_metatypes.json", "qt6qmlworkerscript_relwithdebinfo_metatypes.json", "qt6qmlxmllistmodel_relwithdebinfo_metatypes.json", + "qt6qmlmeta_relwithdebinfo_metatypes.json", + "qt6labsplatform_relwithdebinfo_metatypes.json", ] _qml = [ @@ -549,6 +562,8 @@ def module_QtQuick() -> ModuleData: "qt6quicktest_relwithdebinfo_metatypes.json", "qt6quicktestutilsprivate_relwithdebinfo_metatypes.json", "qt6quicktimeline_relwithdebinfo_metatypes.json", + "qt6quickvectorimage_relwithdebinfo_metatypes.json", + "qt6quickvectorimagegeneratorprivate_relwithdebinfo_metatypes.json", ] _qtlib = [ "libQt6QuickEffects", @@ -562,15 +577,15 @@ def module_QtQuick() -> ModuleData: "libQt6QuickTest", "libQt6QuickTimeline", "libQt6QuickTimelineBlendTrees", + "libQt6QuickVectorImage", + "libQt6QuickVectorImageGenerator" ] - # Adding GraphicalEffects files - data.qml.append("Qt5Compat/GraphicalEffects") - data.qtlib.extend(_qtlib) data.metatypes.extend(_metatypes) json_data = get_module_json_data("Quick") data.plugins = get_module_plugins(json_data) + data.extra_files.append("svgtoqml*") return data @@ -589,6 +604,9 @@ def module_QtQuickControls2() -> ModuleData: data.qtlib.append("libQt6QuickControls2MaterialStyleImpl") data.qtlib.append("libQt6QuickControls2Universal") data.qtlib.append("libQt6QuickControls2UniversalStyleImpl") + # FluentWinUI3 Style is available for all platforms, even if it + # was originally intended for Windows. + data.qtlib.append("libQt6QuickControls2FluentWinUI3StyleImpl") if sys.platform == "win32": data.qtlib.append("libQt6QuickControls2WindowsStyleImpl") elif sys.platform == "darwin": @@ -747,6 +765,7 @@ def module_QtQuick3D() -> ModuleData: "libQt63DQuickInput", "libQt63DQuickRender", "libQt63DQuickScene2D", + "libQt6Quick3DXr", ] _metatypes = [ @@ -767,6 +786,7 @@ def module_QtQuick3D() -> ModuleData: "qt6quick3druntimerender_relwithdebinfo_metatypes.json", "qt6quick3dutils_relwithdebinfo_metatypes.json", "qt6shadertools_relwithdebinfo_metatypes.json", + "qt6quick3dxr_relwithdebinfo_metatypes.json" ] json_data = get_module_json_data("Quick3DAssetImport") @@ -777,6 +797,9 @@ def module_QtQuick3D() -> ModuleData: data.extra_files.append("qsb*") data.extra_files.append("balsam*") + # Adding GraphicalEffects files + data.qml.append("Qt5Compat/GraphicalEffects") + return data @@ -841,6 +864,12 @@ def module_QtGraphs() -> ModuleData: return data +def module_QtGraphsWidgets() -> ModuleData: + data = ModuleData("GraphsWidgets") + + return data + + def module_QtMultimedia() -> ModuleData: data = ModuleData("Multimedia") data.qtlib.append("libQt6MultimediaQuick") @@ -850,9 +879,14 @@ def module_QtMultimedia() -> ModuleData: data.translations.append("qtmultimedia_*") data.plugins = get_module_plugins(json_data) - if sys.platform == "win32": - data.extra_files.extend(["avcodec-60.dll", "avformat-60.dll", "avutil-58.dll", - "swresample-4.dll", "swscale-7.dll"]) + linux_multimedia_libs = [f"Qt/lib/lib{lib}*.so*" for lib in _multimedia_libs] + linux_multimedia_libs.append("Qt/lib/libQt6FFmpegStub*.so*") + platform_files = { + "win32": [f"{lib}-*.dll" for lib in _multimedia_libs], + "darwin": [f"Qt/lib/lib{lib}.*.dylib" for lib in _multimedia_libs], + "linux": linux_multimedia_libs} + extra_files = platform_files.get(sys.platform, []) + data.extra_files.extend(extra_files) return data @@ -1036,3 +1070,15 @@ def module_QtAsyncio() -> ModuleData: def module_QtExampleIcons() -> ModuleData: data = ModuleData("ExampleIcons") return data + + +def module_QtWebView() -> ModuleData: + data = ModuleData("WebView") + json_data = get_module_json_data("WebView") + data.plugins = get_module_plugins(json_data) + return data + + +def module_QtWebViewQuick() -> ModuleData: + data = ModuleData("WebViewQuick") + return data diff --git a/build_scripts/wheel_override.py b/build_scripts/wheel_override.py index f3f9f17a..b389bb87 100644 --- a/build_scripts/wheel_override.py +++ b/build_scripts/wheel_override.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import platform @@ -47,7 +48,7 @@ class PysideBuildWheel(_bdist_wheel, CommandMixin): def finalize_options(self): CommandMixin.mixin_finalize_options(self) - if sys.platform == 'darwin': + if sys.platform == 'darwin' and not self.is_cross_compile: # Override the platform name to contain the correct # minimum deployment target. # This is used in the final wheel name. diff --git a/build_scripts/wheel_utils.py b/build_scripts/wheel_utils.py index 5ec26c74..aa4ef02c 100644 --- a/build_scripts/wheel_utils.py +++ b/build_scripts/wheel_utils.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import time from pathlib import Path diff --git a/coin/dependencies.yaml b/coin/dependencies.yaml index c03b1b8a..478bdfdf 100644 --- a/coin/dependencies.yaml +++ b/coin/dependencies.yaml @@ -1,6 +1,6 @@ product_dependency: ../../qt/qt5: - ref: "3f005f1e2e88485dbf541200ba3fafcad6ea84ad" + ref: "480041bb0bfd400f29dd49facfaa924dacac5c03" dependency_source: supermodule dependencies: [ "../../qt/qt3d", @@ -36,5 +36,6 @@ dependencies: [ "../../qt/qtwayland", "../../qt/qtwebchannel", "../../qt/qtwebengine", - "../../qt/qtwebsockets" + "../../qt/qtwebsockets", + "../../qt/qtwebview", ] diff --git a/coin/fetch_libclang_arm64.sh b/coin/fetch_libclang_arm64.sh new file mode 100644 index 00000000..c99e3357 --- /dev/null +++ b/coin/fetch_libclang_arm64.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# Download the file +wget -q https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z +if [ $? -ne 0 ]; then + echo "Error: Failed to download libclang archive" >&2 + exit 1 +fi + +# Unzip the contents to /home/qt +7z x libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z -o/home/qt +if [ $? -ne 0 ]; then + echo "Error: Failed to extract libclang archive" >&2 + exit 1 +fi + +# Remove the 7z file after extraction +rm libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z +if [ $? -ne 0 ]; then + echo "Error: Failed to remove libclang archive" >&2 + exit 1 +fi diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 441a0180..7663014e 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -140,8 +140,8 @@ instructions: variableValue: "/Users/qt/.local/bin/:" enable_if: condition: property - property: host.osVersion - equals_value: MacOS_11_00 + property: host.os + equals_value: MacOS - type: PrependToEnvironmentVariable variableName: PATH variableValue: "/Users/qt/work/install/bin:" @@ -191,20 +191,8 @@ instructions: condition: property property: target.compiler equals_value: ICC_18 - - type: EnvironmentVariable - variableName: LLVM_INSTALL_DIR - variableValue: "{{.Env.LLVM_DYNAMIC_LIBS_100}}" - disable_if: - condition: and - conditions: - - condition: property - property: target.arch - equals_value: X86_64-ARM64 # When target arch is universal binary, we can use the default libclang - - condition: property - property: host.os - equals_value: MacOS - type: ExecuteCommand - command: "sudo apt-get install python3-pip libclang-11-dev clang -y" + command: "sudo apt-get install python3-pip -y" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -219,7 +207,7 @@ instructions: userMessageOnFailure: > Failed to install dependencies - type: ExecuteCommand - command: "python3 -m pip install -U setuptools==69.1.1" + command: "chmod +x coin/fetch_libclang_arm64.sh" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -232,10 +220,25 @@ instructions: property: host.arch equals_value: AARCH64 userMessageOnFailure: > - Failed to install setuptools + Failed to make coin/fetch_libclang_arm64.sh executable + - type: ExecuteCommand + command: "coin/fetch_libclang_arm64.sh" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: and + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: host.arch + equals_value: AARCH64 + userMessageOnFailure: > + Failed to download libclang from Qt servers - type: EnvironmentVariable variableName: LLVM_INSTALL_DIR - variableValue: "/usr/lib/llvm-11/lib" + variableValue: "/home/qt/libclang" enable_if: condition: and conditions: @@ -251,11 +254,69 @@ instructions: enable_if: condition: property property: host.osVersion - in_values: [RHEL_8_6, RHEL_8_8] + in_values: [RHEL_8_6, RHEL_8_8, RHEL_8_10] - type: EnvironmentVariable variableName: interpreter variableValue: "python3" enable_if: # Enable for others than rhl 8.* condition: property - property: target.osVersion - not_in_values: [RHEL_8_6, RHEL_8_8] + property: host.osVersion + not_in_values: [RHEL_8_6, RHEL_8_8, RHEL_8_10] + # ToDo: can be removed after 3.11 is available on qt5#3.8 on macOS + # start of ToDo + - type: ExecuteCommand + command: "tools/install-p311.sh" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + ignoreExitCode: true + enable_if: + condition: and + conditions: + - condition: property + property: host.os + equals_value: MacOS + - condition: property + property: target.os + equals_value: Android + userMessageOnFailure: > + Failed to install python 3.11 + - type: PrependToEnvironmentVariable + variableName: PATH + variableValue: "/Users/qt/python311/bin:" + enable_if: + condition: and + conditions: + - condition: property + property: host.os + equals_value: MacOS + - condition: property + property: target.os + equals_value: Android + # end of ToDo + - type: ExecuteCommand + command: "virtualenv -p {{.Env.interpreter}} {{.AgentWorkingDir}}/env" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: or + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to create env + - type: EnvironmentVariable + variableName: ENV_PYTHON + variableValue: "{{.AgentWorkingDir}}/env/bin/python" + - type: AppendToEnvironmentVariable + variableName: PATH + variableValue: ":{{.AgentWorkingDir}}/env/bin/" + enable_if: # Enable for others than rhl 8.* + condition: property + property: target.os + equals_value: Android + + diff --git a/coin/instructions/execute_android_instructions.yaml b/coin/instructions/execute_android_instructions.yaml new file mode 100644 index 00000000..ab4d0299 --- /dev/null +++ b/coin/instructions/execute_android_instructions.yaml @@ -0,0 +1,76 @@ +type: Group +instructions: + - type: ExecuteCommand + command: "{{.Env.interpreter}} -m pip install --user -r requirements.txt" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: or + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to install deps + - type: ExecuteCommand + command: "{{.Env.interpreter}} -m pip install --user -r tools/cross_compile_android/requirements.txt" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + ignoreExitCode: true + enable_if: + condition: or + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to install deps + - type: ExecuteCommand + command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /Users/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT_DEFAULT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name aarch64 --coin" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + ignoreExitCode: true + enable_if: + condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to execute build instructions on macOS + - type: ExecuteCommand + command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /home/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT_DEFAULT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name x86_64 --coin" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + ignoreExitCode: true + enable_if: + condition: and + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: target.arch + equals_value: X86_64 + userMessageOnFailure: > + Failed to execute build instructions on Linux + # ToDo: Do we need x86 build? + # - type: ExecuteCommand + # command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /home/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT_DEFAULT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name x86 --clean-cache all --coin" + # maxTimeInSeconds: 14400 + # maxTimeBetweenOutput: 1200 + # enable_if: + # condition: and + # conditions: + # - condition: property + # property: host.os + # equals_value: Linux + # - condition: property + # property: target.arch + # equals_value: X86 + # userMessageOnFailure: > + # Failed to execute build instructions on Linux diff --git a/coin/instructions/execute_build_instructions.yaml b/coin/instructions/execute_build_instructions.yaml index 317adae2..310f00e9 100644 --- a/coin/instructions/execute_build_instructions.yaml +++ b/coin/instructions/execute_build_instructions.yaml @@ -1,128 +1,21 @@ type: Group instructions: - - type: ExecuteCommand - command: "python3 -m pip install -U setuptools==69.1.1" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 1200 + - type: Group + instructions: + - !include "{{pyside/pyside-setup}}/execute_android_instructions.yaml" enable_if: - condition: property - property: host.os - equals_value: MacOS - userMessageOnFailure: > - Failed to install setuptools on macOS - - type: ExecuteCommand - command: "python3 -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=ALL" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 1200 - enable_if: - condition: and - conditions: - - condition: property - property: target.arch - equals_value: X86_64 - - condition: property - property: host.os - equals_value: MacOS - userMessageOnFailure: > - Failed to execute build instructions on macOS - - type: ExecuteCommand - command: "python3 -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch=X86_64-ARM64 --phase=ALL" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 1200 + condition: property + property: target.os + equals_value: Android + - type: Group + instructions: + - !include "{{pyside/pyside-setup}}/execute_desktop_instructions.yaml" enable_if: condition: and conditions: - condition: property - property: target.arch - equals_value: X86_64-ARM64 - - condition: property - property: host.os - equals_value: MacOS - userMessageOnFailure: > - Failed to execute build instructions on macOS - - type: EnvironmentVariable - variableName: PYSIDE_SIGNING_DIR - variableValue: "{{.AgentWorkingDir}}\\pyside\\{{.Env.TESTED_MODULE_COIN}}\\build\\qfpa-p3.10\\package_for_wheels" - enable_if: - condition: property - property: host.os - equals_value: Windows - - type: ExecuteCommand - command: "{{.Env.interpreter}} -m pip install -U pip setuptools==69.1.1 --user" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 1200 - enable_if: - condition: property - property: host.os - equals_value: Linux - userMessageOnFailure: > - Failed to install setuptools on Linux - - type: ExecuteCommand - command: "{{.Env.interpreter}} -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}} --targetArch={{.Env.TARGET_ARCH_COIN}}--phase=ALL" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 600 - enable_if: - condition: property - property: host.os - equals_value: Linux - userMessageOnFailure: > - Failed to execute build instructions on Linux - - type: ExecuteCommand - command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U setuptools==69.1.1" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 1200 - enable_if: - condition: property - property: host.os - equals_value: Windows - userMessageOnFailure: > - Failed to install setuptools on Windows - - type: ExecuteCommand - command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=BUILD" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 600 - enable_if: - condition: property - property: host.os - equals_value: Windows - userMessageOnFailure: > - Failed to execute build instructions on Windows - - type: SignPackage - directory: "{{.Env.PYSIDE_SIGNING_DIR}}" - maxTimeInSeconds: 1200 - maxTimeBetweenOutput: 1200 - enable_if: - condition: property - property: host.os - equals_value: Windows - - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}\\pyside\\pyside-setup" - enable_if: - condition: property - property: host.os - equals_value: Windows - - type: ExecuteCommand - command: "c:\\users\\qt\\MSVC.bat {{.Env.PYSIDE_VIRTUALENV}}\\Scripts\\python.exe create_wheels.py" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 300 - enable_if: - condition: property - property: host.os - equals_value: Windows - userMessageOnFailure: > - Failed to create wheels for Windows - - type: ExecuteCommand - command: "{{.Env.PYSIDE_VIRTUALENV}}/bin/python create_wheels.py" - maxTimeInSeconds: 14400 - maxTimeBetweenOutput: 300 - enable_if: - condition: or - conditions: - - condition: property - property: host.os - equals_value: MacOS + property: host.compiler + not_equals_value: Mingw - condition: property - property: host.os - equals_value: Linux - userMessageOnFailure: > - Failed to create wheels + property: target.os + not_equals_value: Android diff --git a/coin/instructions/execute_desktop_instructions.yaml b/coin/instructions/execute_desktop_instructions.yaml new file mode 100644 index 00000000..bece46b2 --- /dev/null +++ b/coin/instructions/execute_desktop_instructions.yaml @@ -0,0 +1,133 @@ +type: Group +instructions: + - type: ExecuteCommand + command: "python3 -m pip install -r requirements-coin.txt" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to install requirements-coin.txt dependencies on macOS + - type: ExecuteCommand + command: "python3 -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=ALL" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: and + conditions: + - condition: property + property: target.arch + equals_value: X86_64 + - condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to execute build instructions on macOS + - type: ExecuteCommand + command: "python3 -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch=X86_64-ARM64 --phase=ALL" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: and + conditions: + - condition: property + property: target.arch + equals_value: X86_64-ARM64 + - condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to execute build instructions on macOS + - type: EnvironmentVariable + variableName: PYSIDE_SIGNING_DIR + variableValue: "{{.AgentWorkingDir}}\\pyside\\{{.Env.TESTED_MODULE_COIN}}\\build\\qfpa-p3.10\\package_for_wheels" + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: ExecuteCommand + command: "{{.Env.interpreter}} -m pip install -r requirements-coin.txt --user" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: and + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: host.osVersion + not_equals_value: Ubuntu_24_04 + userMessageOnFailure: > + Failed to install requirements-coin.txt dependencies on Linux + - type: ExecuteCommand + command: "{{.Env.interpreter}} -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}} --targetArch={{.Env.TARGET_ARCH_COIN}}--phase=ALL" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 600 + enable_if: + condition: property + property: host.os + equals_value: Linux + userMessageOnFailure: > + Failed to execute build instructions on Linux + - type: ExecuteCommand + command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -r requirements-coin.txt" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to install requirements-coin.txt dependencies on Windows + - type: ExecuteCommand + command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=BUILD" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 600 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to execute build instructions on Windows + - type: SignPackage + directory: "{{.Env.PYSIDE_SIGNING_DIR}}" + maxTimeInSeconds: 1200 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}\\pyside\\pyside-setup" + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: ExecuteCommand + command: "c:\\users\\qt\\MSVC.bat {{.Env.PYSIDE_VIRTUALENV}}\\Scripts\\python.exe create_wheels.py" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to create wheels for Windows + - type: ExecuteCommand + command: "{{.Env.PYSIDE_VIRTUALENV}}/bin/python create_wheels.py" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: or + conditions: + - condition: property + property: host.os + equals_value: MacOS + - condition: property + property: host.os + equals_value: Linux + userMessageOnFailure: > + Failed to create wheels diff --git a/coin/instructions/execute_license_check.yaml b/coin/instructions/execute_license_check.yaml deleted file mode 100644 index 0bf4d3ca..00000000 --- a/coin/instructions/execute_license_check.yaml +++ /dev/null @@ -1,31 +0,0 @@ -type: Group -instructions: - - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}" - - type: InstallSourceArchive - maxTimeInSeconds: 600 - maxTimeBetweenOutput: 600 - project: qt/qtbase - ref: "CURRENT_BRANCH" - directory: qt/qtbase - userMessageOnFailure: > - Could not install source archive. Please investigate why. - - type: InstallSourceArchive - maxTimeInSeconds: 600 - maxTimeBetweenOutput: 600 - project: qt/qtqa - ref: master - directory: qt/qtqa-latest - userMessageOnFailure: > - Could not install source archive. Please investigate why. - - type: EnvironmentVariable - variableName: QT_MODULE_TO_TEST - variableValue: pyside/pyside-setup - userMessageOnFailure: > - Failed to set environment variable. This should not happen. - - type: ExecuteCommand - command: perl qt/qtqa-latest/tests/prebuild/license/tst_licenses.pl - maxTimeInSeconds: 7200 - maxTimeBetweenOutput: 120 - userMessageOnFailure: > - Failed to run license check script. diff --git a/coin/instructions/execute_test_instructions.yaml b/coin/instructions/execute_test_instructions.yaml index 780b27ec..7399ccb4 100644 --- a/coin/instructions/execute_test_instructions.yaml +++ b/coin/instructions/execute_test_instructions.yaml @@ -5,7 +5,7 @@ enable_if: not_contains_value: LicenseCheck instructions: - type: ExecuteCommand - command: "python3 -m pip install -U setuptools==69.1.1" + command: "python3 -m pip install -r requirements-coin.txt" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -13,7 +13,7 @@ instructions: property: host.os equals_value: MacOS userMessageOnFailure: > - Failed to install setuptools on macOS + Failed to install requirements-coin.txt on macOS - type: ExecuteCommand command: "python3 -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=ARM64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" maxTimeInSeconds: 14400 @@ -22,11 +22,14 @@ instructions: condition: and conditions: - condition: property - property: host.osVersion - equals_value: MacOS_11_00 + property: host.os + equals_value: MacOS - condition: property property: host.arch equals_value: ARM64 + - condition: property + property: target.os + not_equals_value: Android userMessageOnFailure: > Failed to execute test instructions on arm mac - type: ExecuteCommand @@ -42,10 +45,13 @@ instructions: - condition: property property: host.arch equals_value: X86_64 + - condition: property + property: target.os + not_equals_value: Android userMessageOnFailure: > Failed to execute test instructions on macOS - type: ExecuteCommand - command: "{{.Env.interpreter}} -m pip install -U pip setuptools==69.1.1 --user" + command: "{{.Env.interpreter}} -m pip install -r requirements-coin.txt --user" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -53,19 +59,24 @@ instructions: property: host.os equals_value: Linux userMessageOnFailure: > - Failed to install setuptools on Linux + Failed to install requirements-coin.txt on Linux - type: ExecuteCommand command: "{{.Env.interpreter}} -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 600 enable_if: - condition: property - property: host.os - equals_value: Linux + condition: and + conditions: + - condition: property + property: host.os + equals_value: Linux + - condition: property + property: target.os + not_equals_value: Android userMessageOnFailure: > Failed to execute test instructions on Linux - type: ExecuteCommand - command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -U pip setuptools==69.1.1 --user" + command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -m pip install -r requirements-coin.txt --user" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: @@ -73,14 +84,22 @@ instructions: property: host.os equals_value: Windows userMessageOnFailure: > - Failed to install setuptools on Windows + Failed to install requirements-coin.txt on Windows - type: ExecuteCommand command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=c:\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 600 enable_if: - condition: property - property: host.os - equals_value: Windows + condition: and + conditions: + - condition: property + property: host.os + equals_value: Windows + - condition: property + property: target.os + not_equals_value: Android + - condition: property + property: host.compiler + not_equals_value: Mingw userMessageOnFailure: > Failed to execute test instructions on 64 bit Windows diff --git a/coin/instructions_utils.py b/coin/instructions_utils.py new file mode 100644 index 00000000..176a6d22 --- /dev/null +++ b/coin/instructions_utils.py @@ -0,0 +1,262 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations + +import calendar +import datetime +import os +import site +import sys +from pathlib import Path + +from build_scripts.options import Options +from build_scripts.utils import (parse_cmake_conf_assignments_by_key, + remove_tree, run_instruction) + +options = Options() + + +class CI: + def __init__(self): + # Values must match COIN thrift + self.HOST_OS = options.option_value("os") + self.TARGET_OS = options.option_value("targetOs") + self.HOST_ARCH = options.option_value("hostArch") + self.TARGET_ARCH = options.option_value("targetArch") + self.HOST_OS_VER = options.option_value("osVer") + self.ENV_INSTALL_DIR = options.option_value("instdir") + self.ENV_AGENT_DIR = options.option_value("agentdir") or "." + self.COMPILER = options.option_value("compiler") + self.USE_SCCACHE = options.option_value("compiler-launcher") + self.INTEGRATION_ID = options.option_value("coinIntegrationId") or str( + calendar.timegm(datetime.datetime.now().timetuple()) + ) + self.FEATURES = [] + _ci_features = options.option_value("features") + if _ci_features is not None: + for f in _ci_features.split(", "): + self.FEATURES.append(f) + self.RELEASE_CONF = options.has_option("packaging") + self.TEST_PHASE = options.option_value("phase") + if self.TEST_PHASE not in ["ALL", "BUILD"]: + self.TEST_PHASE = "ALL" + + +def get_ci_exe_path(ci_install_dir, ci_host_os, qtexe): + """ + qtexe can only be 'qmake' or 'qtpaths' + """ + ext = "" + if ci_host_os == "Windows": + ext = ".exe" + + _path = Path(ci_install_dir) / "bin" / f"{qtexe}{ext}" + + return f"--{qtexe}={_path}" + + +def get_env_or_raise(name: str) -> str: + o = os.getenv(name) + if o is None: + raise Exception(f"Variable not defined: {name}") + return o + + +def get_qtci_virtualenv(python_ver, log, host, host_arch, target_arch): + _exe = "python" + _env = os.environ.get("PYSIDE_VIRTUALENV") or f"env{python_ver}" + env_python = f"{_env}/bin/python" + env_pip = f"{_env}/bin/pip" + + if host == "Windows": + log.info("New virtualenv to build {target_arch} in {host_arch} host") + _exe = "python.exe" + if python_ver.startswith("3"): + var = f"PYTHON{python_ver}-64_PATH" + log.info(f"Try to find python from {var} env variable") + _path = Path(os.getenv(var, "")) + _exe = _path / "python.exe" + if not _exe.is_file(): + log.warning(f"Can't find python.exe from {_exe}, using default python3") + _exe = Path(get_env_or_raise("PYTHON3_PATH")) / "python.exe" + env_python = rf"{_env}\Scripts\python.exe" + env_pip = rf"{_env}\Scripts\pip.exe" + else: + _exe = f"python{python_ver}" + try: + run_instruction([_exe, "--version"], f"Failed to guess python version {_exe}") + except Exception as e: + print(f"Exception {type(e).__name__}: {e}") + _exe = "python3" + return (_exe, _env, env_pip, env_python) + + +def get_current_script_path(): + """Returns the absolute path containing this script.""" + try: + this_file = __file__ + except NameError: + this_file = sys.argv[0] + this_file = Path(this_file).resolve() + return this_file.parents[0] + + +def is_snapshot_build(): + """ + Returns True if project needs to be built with --snapshot-build + + This is true if the version found in .cmake.conf is not a + pre-release version (no alphas, betas). + + This eliminates the need to remove the --snapshot-build option + on a per-release branch basis (less things to remember to do + for a release). + """ + # This returns pyside-setup/coin/ so we go one level down + # to get the root of the repo + setup_script_dir = get_current_script_path() + pyside_project_dir = setup_script_dir / ".." / "sources" / "pyside6" + + d = parse_cmake_conf_assignments_by_key(str(pyside_project_dir)) + release_version_type = d.get("pyside_PRE_RELEASE_VERSION_TYPE") + pre_release_version = d.get("pyside_PRE_RELEASE_VERSION") + if pre_release_version and release_version_type: + return True + return False + + +def get_architecture(ci): + return "32" if ci.TARGET_ARCH == "X86" else "64" + + +def get_python_version(ci): + python_ver = "3" + if ci.TARGET_OS == "Linux" and ci.HOST_ARCH != "aarch64": + python_ver = "3.11" + elif ci.TARGET_OS == "Windows": + python_ver = "3.10.0" + return python_ver + + +def remove_variables(vars): + for env_var in vars: + if os.environ.get(env_var): + del os.environ[env_var] + + +def setup_virtualenv(python, exe, env, pip, log): + # Within Ubuntu 24.04 one can't install anything with pip to outside of + # virtual env. Trust that we already have proper virtualenv installed. + if os.environ.get("HOST_OSVERSION_COIN") != "ubuntu_24_04": + run_instruction( + [str(python), "-m", "pip", "install", "--user", "virtualenv==20.7.2"], + "Failed to pin virtualenv", + ) + # installing to user base might not be in PATH by default. + env_path = Path(str(site.USER_BASE)) / "bin" + v_env = env_path / "virtualenv" + if sys.platform == "win32": + env_path = os.path.join(site.USER_BASE, "Scripts") + v_env = os.path.join(env_path, "virtualenv.exe") + try: + run_instruction([str(v_env), "--version"], "Using default virtualenv") + except Exception as e: + log.info("Failed to use the default virtualenv") + log.info(f"{type(e).__name__}: {e}") + v_env = "virtualenv" + run_instruction([str(v_env), "-p", str(exe), str(env)], "Failed to create virtualenv") + # Pip is always upgraded when CI template is provisioned, + # upgrading it in later phase may cause perm issue + run_instruction( + [str(pip), "install", "-r", "requirements.txt"], "Failed to install dependencies" + ) + + +def call_setup(python_ver, ci, phase, log, buildnro=0): + print("call_setup") + print("python_ver", python_ver) + print("phase", phase) + exe, env, pip, env_python = get_qtci_virtualenv( + python_ver, log, ci.HOST_OS, ci.HOST_ARCH, ci.TARGET_ARCH + ) + + if phase not in ["BUILD", "TEST"]: + sys.exit(1) + + remove_tree(env, True) + # Pinning the virtualenv before creating one + # Use pip3 if possible while pip seems to install the virtualenv to wrong dir in some OS + python = "python3" + if sys.platform == "win32": + python = Path(get_env_or_raise("PYTHON3_PATH")) / "python.exe" + + if phase == "BUILD": + setup_virtualenv(python, exe, env, pip, log) + elif phase == "TEST": + + if ci.HOST_OS == "MacOS" and ci.HOST_ARCH == "ARM64": + v_env = "virtualenv" + run_instruction([str(v_env), "-p", str(exe), str(env)], "Failed to create virtualenv") + run_instruction( + [pip, "install", "-r", "requirements.txt"], "Failed to install dependencies" + ) + else: + setup_virtualenv(python, exe, env, pip, log) + # Install distro to replace missing platform.linux_distribution() in python3.8 + run_instruction([pip, "install", "distro"], "Failed to install distro") + + if phase == "BUILD": + cmd = [ + env_python, + "-u", + "setup.py", + "build", + "--standalone", + "--unity", + "--build-tests", + "--log-level=verbose", + "--limited-api=yes", + ] + + if ci.TARGET_ARCH == "X86_64-ARM64": + cmd += ["--macos-arch='x86_64;arm64'"] + + if ci.USE_SCCACHE: + cmd += [f"--compiler-launcher={ci.USE_SCCACHE}"] + + if is_snapshot_build(): + cmd += ["--snapshot-build"] + + qtpaths_path = get_ci_exe_path(ci.ENV_INSTALL_DIR, ci.HOST_OS, "qtpaths") + cmd.append(qtpaths_path) + + # Due to certain older CMake versions generating very long paths + # (at least with CMake 3.6.2) when using the export() function, + # pass the shorter paths option on Windows so we don't hit + # the path character length limit (260). + if ci.HOST_OS == "Windows": + cmd += ["--shorter-paths"] + + cmd += ["--package-timestamp=" + ci.INTEGRATION_ID] + + env = os.environ + run_instruction(cmd, "Failed to run setup.py for build", initial_env=env) + elif phase == "TEST": + cmd = [ + env_python, + "testrunner.py", + "test", + "--blacklist", + "build_history/blacklist.txt", + f"--buildno={buildnro}", + ] + run_instruction(cmd, "Failed to run testrunner.py") + + qmake_path = get_ci_exe_path(ci.ENV_INSTALL_DIR, ci.HOST_OS, "qmake") + + # Try to install built wheels, and build some buildable examples. + if ci.RELEASE_CONF: + wheel_tester_path = os.path.join("testing", "wheel_tester.py") + # Run the test for the new set of wheels + cmd = [env_python, wheel_tester_path, qmake_path, "--wheels-dir=dist", "--new"] + run_instruction(cmd, "Error while running wheel_tester.py on new wheels") diff --git a/coin/module_config.yaml b/coin/module_config.yaml index e5c2fdc8..450365ca 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -51,36 +51,32 @@ accept_configuration: - condition: property property: target.os not_contains_value: IOS - - condition: and - conditions: - condition: property property: host.osVersion - equals_value: MacOS_11_00 - - condition: property - property: host.arch - equals_value: ARM64 - - condition: property - property: features - contains_value: TestOnly - - condition: property - property: features - contains_value: Packaging + not_equals_value: MacOS_12 - condition: and # Restore LoA config conditions: - condition: property - property: host.osVersion - equals_value: Debian_11_6 + property: host.os + equals_value: Linux - condition: property property: host.arch equals_value: AARCH64 + - condition: property + property: target.arch + equals_value: AARCH64 - condition: property property: features - not_contains_value: DebianPackaging + contains_value: Packaging + - condition: and + conditions: + - condition: property + property: target.os + equals_value: Android - condition: property property: features contains_value: Packaging - machine_type: Build: cores: 8 @@ -109,7 +105,7 @@ upload_pyside: &upload_pyside instructions: LicenseCheck: - - !include "{{pyside/pyside-setup}}/execute_license_check.yaml" + - !include "{{qt/qtbase}}/run_license_check.yaml" Build: - !include "{{pyside/pyside-setup}}/common_environment.yaml" - !include "{{pyside/pyside-setup}}/find_path_to_msvc_compiler.yaml" diff --git a/coin_build_instructions.py b/coin_build_instructions.py index 83d46977..b5ade822 100644 --- a/coin_build_instructions.py +++ b/coin_build_instructions.py @@ -1,165 +1,31 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -import calendar -import datetime +from __future__ import annotations + import logging import os -import os.path -import site import sys from build_scripts.log import log -from build_scripts.options import has_option, option_value -from build_scripts.utils import (expand_clang_variables, get_ci_qtpaths_path, - get_qtci_virtualEnv, - parse_cmake_conf_assignments_by_key, - remove_tree, run_instruction) - -log.setLevel(logging.INFO) - -# Values must match COIN thrift -CI_HOST_OS = option_value("os") -CI_TARGET_OS = option_value("targetOs") -CI_HOST_ARCH = option_value("hostArch") -CI_TARGET_ARCH = option_value("targetArch") -CI_HOST_OS_VER = option_value("osVer") -CI_ENV_INSTALL_DIR = option_value("instdir") -CI_ENV_AGENT_DIR = option_value("agentdir") -CI_COMPILER = option_value("compiler") -CI_USE_SCCACHE = option_value("compiler-launcher") -CI_INTEGRATION_ID = option_value("coinIntegrationId") or str( - calendar.timegm(datetime.datetime.now().timetuple()) -) -CI_FEATURES = [] -_ci_features = option_value("features") -if _ci_features is not None: - for f in _ci_features.split(", "): - CI_FEATURES.append(f) -CI_RELEASE_CONF = has_option("packaging") -CI_TEST_PHASE = option_value("phase") -if CI_TEST_PHASE not in ["ALL", "BUILD"]: - CI_TEST_PHASE = "ALL" - - -def get_current_script_path(): - """Returns the absolute path containing this script.""" - try: - this_file = __file__ - except NameError: - this_file = sys.argv[0] - this_file = os.path.abspath(this_file) - return os.path.dirname(this_file) - - -def is_snapshot_build(): - """ - Returns True if project needs to be built with --snapshot-build - - This is true if the version found in .cmake.conf is not a - pre-release version (no alphas, betas). - - This eliminates the need to remove the --snapshot-build option - on a per-release branch basis (less things to remember to do - for a release). - """ - setup_script_dir = get_current_script_path() - pyside_project_dir = os.path.join(setup_script_dir, "sources", "pyside6") - - d = parse_cmake_conf_assignments_by_key(pyside_project_dir) - release_version_type = d.get("pyside_PRE_RELEASE_VERSION_TYPE") - pre_release_version = d.get("pyside_PRE_RELEASE_VERSION") - if pre_release_version and release_version_type: - return True - return False - - -def call_setup(python_ver, phase): - print("call_setup") - print("python_ver", python_ver) - print("phase", phase) - _pExe, _env, env_pip, env_python = get_qtci_virtualEnv( - python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH - ) - - if phase in ["BUILD"]: - remove_tree(_env, True) - # Pinning the virtualenv before creating one - # Use pip3 if possible while pip seems to install the virtualenv to wrong dir in some OS - python3 = "python3" - if sys.platform == "win32": - python3 = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe") - run_instruction( - [python3, "-m", "pip", "install", "--user", "virtualenv==20.7.2"], - "Failed to pin virtualenv", - ) - # installing to user base might not be in PATH by default. - env_path = os.path.join(site.USER_BASE, "bin") - v_env = os.path.join(env_path, "virtualenv") - if sys.platform == "win32": - env_path = os.path.join(site.USER_BASE, "Scripts") - v_env = os.path.join(env_path, "virtualenv.exe") - try: - run_instruction([str(v_env), "--version"], "Using default virtualenv") - except Exception as e: - log.info("Failed to use the default virtualenv") - log.info(f"{type(e).__name__}: {e}") - v_env = "virtualenv" - run_instruction([str(v_env), "-p", str(_pExe), str(_env)], "Failed to create virtualenv") - # When the 'python_ver' variable is empty, we are using Python 2 - # Pip is always upgraded when CI template is provisioned, - # upgrading it in later phase may cause perm issue - run_instruction( - [str(env_pip), "install", "-r", "requirements.txt"], "Failed to install dependencies" - ) +from build_scripts.utils import expand_clang_variables +from coin.instructions_utils import (CI, call_setup, get_architecture, + get_python_version, remove_variables) - cmd = [env_python, "-u", "setup.py"] - if phase in ["BUILD"]: - cmd += ["build", "--standalone", "--unity"] - - cmd += ["--build-tests", "--log-level=verbose"] - - if CI_TARGET_ARCH == "X86_64-ARM64": - cmd += ["--macos-arch='x86_64;arm64'"] - - if CI_USE_SCCACHE: - cmd += [f"--compiler-launcher={CI_USE_SCCACHE}"] - - cmd += ["--limited-api=yes"] - - if is_snapshot_build(): - cmd += ["--snapshot-build"] - - qtpaths_path = get_ci_qtpaths_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) - cmd.append(qtpaths_path) +if __name__ == "__main__": - # Due to certain older CMake versions generating very long paths - # (at least with CMake 3.6.2) when using the export() function, - # pass the shorter paths option on Windows so we don't hit - # the path character length limit (260). - if CI_HOST_OS == "Windows": - cmd += ["--shorter-paths"] + ci = CI() + log.setLevel(logging.INFO) + # Remove some environment variables that impact cmake + arch = get_architecture(ci) + expand_clang_variables(arch) - cmd += ["--package-timestamp=" + CI_INTEGRATION_ID] + remove_variables(["CC", "CXX"]) - env = os.environ - run_instruction(cmd, "Failed to run setup.py for build", initial_env=env) + python_ver = get_python_version(ci) + if ci.TEST_PHASE in ["ALL", "BUILD"]: + call_setup(python_ver, ci, "BUILD", log) -if __name__ == "__main__": - # Remove some environment variables that impact cmake - arch = "32" if CI_TARGET_ARCH == "X86" else "64" - expand_clang_variables(arch) - for env_var in ["CC", "CXX"]: - if os.environ.get(env_var): - del os.environ[env_var] - python_ver = "3" - if CI_TARGET_OS in ["Linux"] and CI_HOST_ARCH != "aarch64": - python_ver = "3.11" - wheel_package_dir = "qfpa-p3.6" - if CI_TARGET_OS in ["Windows"]: - python_ver = "3.10.0" - if CI_TEST_PHASE in ["ALL", "BUILD"]: - call_setup(python_ver, "BUILD") # Until CI has a feature to set more dynamic signing dir, make sure it actually exist if os.environ.get("QTEST_ENVIRONMENT") == "ci" and sys.platform == "win32": signing_dir = str(os.environ.get("PYSIDE_SIGNING_DIR")) diff --git a/coin_test_instructions.py b/coin_test_instructions.py index b0e71fba..a6e38b51 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -1,107 +1,27 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -import os +from __future__ import annotations + import logging -import site -import sys +import os from build_scripts.log import log -from build_scripts.options import has_option, option_value -from build_scripts.utils import (expand_clang_variables, get_ci_qmake_path, - get_qtci_virtualEnv, remove_tree, run_instruction) - -log.setLevel(logging.INFO) - -# Values must match COIN thrift -CI_HOST_OS = option_value("os") -CI_TARGET_OS = option_value("targetOs") -CI_HOST_ARCH = option_value("hostArch") -CI_TARGET_ARCH = option_value("targetArch") -CI_HOST_OS_VER = option_value("osVer") -CI_ENV_INSTALL_DIR = option_value("instdir") -CI_ENV_AGENT_DIR = option_value("agentdir") or "." -CI_COMPILER = option_value("compiler") -CI_FEATURES = [] -_ci_features = option_value("features") -if _ci_features is not None: - for f in _ci_features.split(', '): - CI_FEATURES.append(f) -CI_RELEASE_CONF = has_option("packaging") - - -def call_testrunner(python_ver, buildnro): - _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, - CI_TARGET_ARCH) - remove_tree(_env, True) - # Pinning the virtualenv before creating one - # Use pip3 if possible while pip seems to install the virtualenv to wrong dir in some OS - python3 = "python3" - if sys.platform == "win32": - python3 = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe") - - # we shouldn't install anything outside of virtualenv, while m1 is not virtualized yet - if CI_HOST_OS == "MacOS" and CI_HOST_ARCH == "ARM64": - v_env = "virtualenv" - run_instruction([str(v_env), "-p", str(_pExe), str(_env)], "Failed to create virtualenv") - run_instruction([env_pip, "install", "-r", "requirements.txt"], - "Failed to install dependencies") - else: - run_instruction([python3, "-m", "pip", "install", "--user", "virtualenv==20.7.2"], - "Failed to pin virtualenv") - # installing to user base might not be in PATH by default. - env_path = os.path.join(site.USER_BASE, "bin") - v_env = os.path.join(env_path, "virtualenv") - if sys.platform == "win32": - env_path = os.path.join(site.USER_BASE, "Scripts") - v_env = os.path.join(env_path, "virtualenv.exe") - try: - run_instruction([str(v_env), "--version"], "Using default virtualenv") - except Exception as e: - log.info("Failed to use the default virtualenv") - log.info(f"{type(e).__name__}: {e}") - v_env = "virtualenv" - run_instruction([str(v_env), "-p", str(_pExe), str(_env)], "Failed to create virtualenv") - # When the 'python_ver' variable is empty, we are using Python 2 - # Pip is always upgraded when CI template is provisioned, - # upgrading it in later phase may cause perm issue - run_instruction([env_pip, "install", "-r", "requirements.txt"], - "Failed to install dependencies") - # Install distro to replace missing platform.linux_distribution() in python3.8 - run_instruction([env_pip, "install", "distro"], "Failed to install distro") +from build_scripts.utils import expand_clang_variables +from coin.instructions_utils import (CI, call_setup, get_architecture, + get_python_version, remove_variables) - cmd = [env_python, "testrunner.py", "test", "--blacklist", "build_history/blacklist.txt", - f"--buildno={buildnro}"] - run_instruction(cmd, "Failed to run testrunner.py") - - qmake_path = get_ci_qmake_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) - - # Try to install built wheels, and build some buildable examples. - if CI_RELEASE_CONF: - wheel_tester_path = os.path.join("testing", "wheel_tester.py") - # Run the test for the new set of wheels - cmd = [env_python, wheel_tester_path, qmake_path, "--wheels-dir=dist", "--new"] - run_instruction(cmd, "Error while running wheel_tester.py on new wheels") - - -def run_test_instructions(): +if __name__ == "__main__": + ci = CI() + log.setLevel(logging.INFO) # Remove some environment variables that impact cmake - arch = '32' if CI_TARGET_ARCH == 'X86' else '64' + arch = get_architecture(ci) expand_clang_variables(arch) - for env_var in ['CC', 'CXX']: - if os.environ.get(env_var): - del os.environ[env_var] - os.chdir(CI_ENV_AGENT_DIR) - testRun = 0 + remove_variables(["CC", "CXX"]) - # In win machines, there are additional python versions to test with - if CI_HOST_OS == "Windows": - call_testrunner("3.10.0", str(testRun)) - elif CI_HOST_OS == "Linux": - call_testrunner("3.11", str(testRun)) - else: - call_testrunner("3", str(testRun)) + python_ver = get_python_version(ci) + os.chdir(ci.ENV_AGENT_DIR) + testRun = 0 -if __name__ == "__main__": - run_test_instructions() + call_setup(python_ver, ci, "TEST", log) diff --git a/create_wheels.py b/create_wheels.py index 063b59c4..32a89a6a 100644 --- a/create_wheels.py +++ b/create_wheels.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import os import platform @@ -10,7 +11,6 @@ from argparse import ArgumentParser, Namespace from dataclasses import dataclass from pathlib import Path from shutil import copy, rmtree, copytree -from typing import List, Optional, Tuple import build # type: ignore import pyproject_hooks @@ -29,13 +29,13 @@ PYSIDE_DESCRIPTION = "Python bindings for the Qt cross-platform application and @dataclass class SetupData: name: str - version: str + version: tuple[str, str] description: str readme: str - console_scripts: List[str] + console_scripts: list[str] -def get_version_from_package(name: str, package_path: Path) -> str: +def get_version_from_package(name: str, package_path: Path) -> tuple[str, str]: # Get version from the already configured '__init__.py' file version = "" with open(package_path / name / "__init__.py") as f: @@ -46,7 +46,7 @@ def get_version_from_package(name: str, package_path: Path) -> str: return version, f"{name}.__init__.__version__" -def create_module_plugin_json(wheel_name: str, data: List[ModuleData], package_path: Path): +def create_module_plugin_json(wheel_name: str, data: list[ModuleData], package_path: Path): all_plugins = {} for module in data: @@ -60,7 +60,7 @@ def create_module_plugin_json(wheel_name: str, data: List[ModuleData], package_p json.dump(all_plugins, fp, indent=4) -def get_manifest(wheel_name: str, data: List[ModuleData], package_path: Path) -> str: +def get_manifest(wheel_name: str, data: list[ModuleData], package_path: Path) -> str: lines = [] for module in data: @@ -122,7 +122,11 @@ def get_platform_tag() -> str: module_name = config_py.name[:-3] _spec = importlib.util.spec_from_file_location(f"{module_name}", config_py) + if _spec is None: + raise RuntimeError(f"Unable to create ModuleSpec from {str(config_py)}") _module = importlib.util.module_from_spec(_spec) + if _spec.loader is None: + raise RuntimeError(f"ModuleSpec for {module_name} has no valid loader.") _spec.loader.exec_module(module=_module) target = _module.__qt_macos_min_deployment_target__ @@ -180,13 +184,13 @@ def generate_pyproject_toml(artifacts: Path, setup: SetupData) -> str: with open(artifacts / "pyproject.toml.base") as f: content = ( f.read() - .replace("PROJECT_NAME", f'"{setup.name}"') - .replace("PROJECT_VERSION", f'"{setup.version[1]}"') - .replace("PROJECT_DESCRIPTION", f'"{setup.description}"') - .replace("PROJECT_README", f'"{setup.readme}"') - .replace("PROJECT_TAG", f'"{_tag}"') - .replace("PROJECT_SCRIPTS", _console_scripts) - .replace("PROJECT_DEPENDENCIES", f"{_dependencies}") + .replace('"PROJECT_NAME"', f'"{setup.name}"') + .replace('"PROJECT_VERSION"', f'"{setup.version[1]}"') + .replace('"PROJECT_DESCRIPTION"', f'"{setup.description}"') + .replace('"PROJECT_README"', f'"{setup.readme}"') + .replace('"PROJECT_TAG"', f'"{_tag}"') + .replace('# PROJECT_SCRIPTS', _console_scripts) + .replace('"PROJECT_DEPENDENCIES"', f"{_dependencies}") ) return content @@ -221,7 +225,7 @@ def generate_setup_py(artifacts: Path, setup: SetupData): return content -def wheel_shiboken_generator(package_path: Path) -> Tuple[SetupData, None]: +def wheel_shiboken_generator(package_path: Path) -> tuple[SetupData, None]: setup = SetupData( name="shiboken6_generator", version=get_version_from_package("shiboken6_generator", package_path), @@ -236,7 +240,7 @@ def wheel_shiboken_generator(package_path: Path) -> Tuple[SetupData, None]: return setup, None -def wheel_shiboken_module(package_path: Path) -> Tuple[SetupData, None]: +def wheel_shiboken_module(package_path: Path) -> tuple[SetupData, None]: setup = SetupData( name="shiboken6", version=get_version_from_package("shiboken6", package_path), @@ -248,15 +252,15 @@ def wheel_shiboken_module(package_path: Path) -> Tuple[SetupData, None]: return setup, None -def wheel_pyside6_essentials(package_path: Path) -> Tuple[SetupData, List[ModuleData]]: +def wheel_pyside6_essentials(package_path: Path) -> tuple[SetupData, list[ModuleData]]: packaged_qt_tools_path = package_path / "PySide6" set_pyside_package_path(packaged_qt_tools_path) _pyside_tools = available_pyside_tools(packaged_qt_tools_path, package_for_wheels=True) # replacing pyside6-android_deploy by pyside6-android-deploy for consistency - # Also, the tool should not exist in any other platform than Linux + # Also, the tool should not exist in any other platform than Linux and macOS _console_scripts = [] - if ("android_deploy" in _pyside_tools) and sys.platform.startswith("linux"): + if ("android_deploy" in _pyside_tools) and sys.platform in ("linux", "darwin"): _console_scripts = ['pyside6-android-deploy = "PySide6.scripts.pyside_tool:android_deploy"'] _pyside_tools.remove("android_deploy") @@ -276,7 +280,7 @@ def wheel_pyside6_essentials(package_path: Path) -> Tuple[SetupData, List[Module return setup, data -def wheel_pyside6_addons(package_path: Path) -> Tuple[SetupData, List[ModuleData]]: +def wheel_pyside6_addons(package_path: Path) -> tuple[SetupData, list[ModuleData]]: setup = SetupData( name="PySide6_Addons", version=get_version_from_package("PySide6", package_path), # we use 'PySide6' here @@ -290,7 +294,7 @@ def wheel_pyside6_addons(package_path: Path) -> Tuple[SetupData, List[ModuleData return setup, data -def wheel_pyside6(package_path: Path) -> Tuple[SetupData, Optional[List[ModuleData]]]: +def wheel_pyside6(package_path: Path) -> tuple[SetupData, list[ModuleData] | None]: setup = SetupData( name="PySide6", version=get_version_from_package("PySide6", package_path), @@ -302,7 +306,7 @@ def wheel_pyside6(package_path: Path) -> Tuple[SetupData, Optional[List[ModuleDa return setup, None -def wheel_pyside6_examples(package_path: Path) -> Tuple[SetupData, Optional[List[ModuleData]]]: +def wheel_pyside6_examples(package_path: Path) -> tuple[SetupData, list[ModuleData] | None]: setup = SetupData( name="PySide6_Examples", version=get_version_from_package("PySide6", package_path), @@ -397,6 +401,8 @@ if __name__ == "__main__": help="The directory where PySide was build (must contain a " "'package_for_wheels' folder" ) + parser.add_argument("--no-examples", "-e", action="store_true", + help="Do not build examples wheel") options = parser.parse_args() # Sanity check between the available modules, @@ -439,8 +445,9 @@ if __name__ == "__main__": "PySide6_Essentials": wheel_pyside6_essentials, "PySide6_Addons": wheel_pyside6_addons, "PySide6": wheel_pyside6, - "PySide6_Examples": wheel_pyside6_examples, } + if not options.no_examples: + wheels["PySide6_Examples"] = wheel_pyside6_examples for name, wheel_info in wheels.items(): @@ -481,7 +488,7 @@ if __name__ == "__main__": print("-- Copy configuration files to create the wheel") if name == "PySide6_Examples": copy_examples_for_wheel(package_path) - _files: List[Path] = base_files + [Path(setup.readme)] + _files: list[Path] = base_files + [Path(setup.readme)] for fname in _files: copy(fname, package_path) diff --git a/doc/changelogs/changes-6.7.3 b/doc/changelogs/changes-6.7.3 new file mode 100644 index 00000000..93ea3609 --- /dev/null +++ b/doc/changelogs/changes-6.7.3 @@ -0,0 +1,75 @@ +Qt for Python 6.7.3 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - A --flatpak option has been added to setup.py, enabling a flatpak build + of Qt for Python. + - [PYSIDE-769] QtAsyncio: The application argument has been removed + from the loop policy. + - [PYSIDE-1612] Deployment: Nuitka has been updated to 2.3.7. Scanning for + QML dependencies has been fixed to skip some directories. + - [PYSIDE-1612] Android Cross Compilation: INSTSONAME has been added. + - [PYSIDE-1877] Properties of type QAbstractItemModel can now be used in QML. + - [PYSIDE-2192] PySide Qt Gui applications can now be used in interactive + mode, for example notebooks. + - [PYSIDE-2517] Type hints: The signatures of QObject.findChild()/ + findChildren() have been improved to reflect the type passed + in. + - [PYSIDE-2622] Deployment: Nuitka --standalone mode is now supported. + - [PYSIDE-2656] QtMultimedia on macOS has been fixed. + - [PYSIDE-2702] An option to force processing system headers has been added + for cases where Qt is installed into the system. + - [PYSIDE-2752] Type hints: A syntax error caused by empty Enums has been + fixed. + - [PYSIDE-2766] Android Deployment: pyside6-android-deploy now works macOS, + too. + - [PYSIDE-2785] Deployment: 'dist-packages' is now skipped similar + to 'site-packages' when scanning for QML dependencies. + - [PYSIDE-2788] Type hints: The signature of QFormLayout.getLayoutPosition() + has been fixed. + - [PYSIDE-2789] numpy 2.0 is now supported. + - [PYSIDE-2790] QtAsyncio: cancel count and uncancel() have been added. + - [PYSIDE-2796] A potential crash in currentOpcode_Is_CallMethNoArgs() + has been fixed. + - [PYSIDE-2799] QtAsyncio: A hang when an exception occurs inside a + TaskGroup body has been fixed. + - [PYSIDE-2803] Desktop Deployment: Overflows of command lines on Windows + have been fixed. + - [PYSIDE-2806] Desktop Deployment: The application name has been fixed. + - [PYSIDE-2814] Deployment: Arguments with spaces can now be used for + "extra_args" due to using shlex for splitting the command + line arguments. + - [PYSIDE-2819] The correct libclang is now used for arm64. + - [PYSIDE-2825] QtWebView has been added. + - [PYSIDE-2828] Documentation: The .qrc tutorial has been updated. + - [PYSIDE-2833] The QML tutorials have been updated. + - [PYSIDE-2834] QDir.entry(Info)List(QDir.Filter, QDir.SortFlags) + has been fixed to work with Python 3.11 and later. + - [PYSIDE-2836] PySide6/__init__.py now has a static list of modules, + enabling code checkers to work. + - [PYSIDE-2870] A crash when using QStateMachine.postEvent() has been + fixed. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + +- [PYSIDE-2834] Enumerations have been excluded from argument type checks + for sequences. This addresses a problem showing in Python + 3.11 causing the wrong function overloads to be used. +- [PYSIDE-2780] A potential refcounting bug in Lazy loading has been fixed. diff --git a/doc/changelogs/changes-6.8.0 b/doc/changelogs/changes-6.8.0 new file mode 100644 index 00000000..af5ee0f8 --- /dev/null +++ b/doc/changelogs/changes-6.8.0 @@ -0,0 +1,61 @@ +Qt for Python 6.8.0 is a minor release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-769] QtAsyncio: The application argument has been removed + from the loop policy. + - [PYSIDE-2221] The code for signal connections for Python callables not + targeting a QMetaMethod has be reimplemented to simplify + code and prepare for removal of the GIL. + - [PYSIDE-2404] Lazy loading has been extended to smart pointers + and nested types. + - [PYSIDE-2620] pyside6-svgtoqml has been added. + - [PYSIDE-2701] The size of the generated code has been reduced. + - [PYSIDE-2701] Python formats with "#" in it are now supported. + - [PYSIDE-2711] An undesired invalidation of QObjects/QWidgets after + calling parent(Widget)() has been fixed. + - [PYSIDE-2766] Android wheels are now provided. + - [PYSIDE-2786] Modern typing syntax is now used in examples and QtAsyncio + code, enabled by a future import for Python 3.9, too. + - [PYSIDE-2805] An overload taking a slot connected to QMessageBox.accepted + has been added to QMessageBox.open(). + - [PYSIDE-2810] The number of QByteArray conversions in libpyside's + signal handling has been reduced. + - [PYSIDE-2820] Opaque containers of type QList can now be used for initial + properties in QML. + - [PYSIDE-2803] Deployment: An issue in QML import scanning causing + overly long command lines has been fixed. + - [PYSIDE-2837] The documentation has been restructured and updated. + - [PYSIDE-2859] QModelRoleDataSpan for implementing + QAbstractItemModel.multiData() has been added. + - [PYSIDE-2840] Enumeration properties for Qt Designer custom widgets + have been fixed. + - [PYSIDE-2845] QSemaphore.tryAcquire() has been fixed to work with + the snake case feature. The existing C++ function + try_acquire() has been removed. + - [PYSIDE-2846] Type hints: Many issues in the generated stub files + have been fixed to enable checking with mypy. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2792] Potential mismatches caused by nested classes + when looking up converters by name have been fixed. + - [PYSIDE-1735] The generated code for enumerations has been refactored + to consider underlying types. diff --git a/doc/changelogs/changes-6.8.1 b/doc/changelogs/changes-6.8.1 new file mode 100644 index 00000000..fe910bca --- /dev/null +++ b/doc/changelogs/changes-6.8.1 @@ -0,0 +1,53 @@ +Qt for Python 6.8.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-1106] Documentation: Inner classes are now shown nested + into their outer classes for referencing to work correctly. + - [PYSIDE-2689] Documentation: A workaround has been made to fix the search + context of class pages. + - [PYSIDE-1612] Deployment: The bundling of QML files has been improved + by using nuitka's 'include-data-dir' command line option. + - [PYSIDE-2206] The OSM-buildings demo (maps part) and + the zoomlinechart example have been added. + - [PYSIDE-2211] Type hints: Errors resulting from inconsistencies + between binary operators and their corresponding + in-place operators have been fixed. + - [PYSIDE-2751] Support for Python 3.13 has been added. + - [PYSIDE-2825] Missing libraries required for QtWebView have been added. + - [PYSIDE-2846] Type hints: mypy support has been greatly improved + and the .pyi files are now tested. + - [PYSIDE-2884] Type hints: typing.Callable has been fixed to include the + parameters. + - [PYSIDE-2886] Type hints: Multiple signatures and Union expressions + have been fixed. + - [PYSIDE-2891] An issue affecting 32bit builds has been fixed. + - [PYSIDE-2893] The installation of the module doc snippet files has + been fixed. + - [PYSIDE-2895] PySide's __init__.py no longer contains + build-directory-dependent code and PySide6.__all__ has been + fixed to again work. + - [PYSIDE-2901] Documentation: Tutorials and reference pages + related to Signal and Slots have been improved. + - [PYSIDE-2903] Type hints: Duplicated occurrences of '#' in the pyi type + specifications causing errors have been fixed. + - [PYSIDE-2910] Deployment: Bundling of the FluentWinUI3 + style for Qt Quick Controls has been fixed. + - [PYSIDE-2913] QWebEngineDesktopMediaRequest has been added. + - [PYSIDE-2915] Type hints: Rich compare functions are now supported. + - [PYSIDE-2917] numpy version 2 is now used Python 3.10+. diff --git a/doc/changelogs/changes-6.8.1.1 b/doc/changelogs/changes-6.8.1.1 new file mode 100644 index 00000000..272bccf6 --- /dev/null +++ b/doc/changelogs/changes-6.8.1.1 @@ -0,0 +1,20 @@ +Qt for Python 6.8.1.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-2964] Added WebView to macOS wheels. diff --git a/doc/changelogs/changes-6.8.2 b/doc/changelogs/changes-6.8.2 new file mode 100644 index 00000000..6a8eb622 --- /dev/null +++ b/doc/changelogs/changes-6.8.2 @@ -0,0 +1,54 @@ +Qt for Python 6.8.2 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-487] Some missing bindings were added. + - [PYSIDE-841] The simple bar graph (QtGraphs) and zoomlinechart (QtCharts) + examples have been added. + - [PYSIDE-1106] Documentation: A number of missing documentation pages has + been added. The module pages were updated and improved, now + showing the QML types more prominently. Parameter names and + documentation of global and/or added functions have been + added. + - [PYSIDE-1612] Deployment: Development folders are no longer included in the + deployed executable. A bug parsing "from . import x" has been + fixed. .webp files are now supported. Nuitka has been upgraded + to 2.5.1. Non-existent plugins are now disregarded. + - [PYSIDE-1612] Android Deployment: The Android NDK is now automatically + downloaded. + - [PYSIDE-1735] Examples and tests have been changed to use fully qualified + enumerations. + - [PYSIDE-2624] Type hints: Positional parameters are now used according to + PEP 570. + - [PYSIDE-2846] Type hints: Properties in constructors are now supported + (including snake_case). Empty enums have been fixed for + mypy 1.14. + - [PYSIDE-2846] A crash related to inner classes in Qt3DRender has been fixed. + - [PYSIDE-2936] A crash using QDBusUnixFileDescriptor in + QDBusAbstractInterface::call() has been fixed. + - [PYSIDE-2939] Type hints: The heuristic for ambiguous signature removal + has been fixed. + - [PYSIDE-2941] Type hints: Some optional return annotations have been fixed. + - [PYSIDE-2946] A crash registering smart pointer types conversions of class + hierarchies has been fixed. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-2701] The generated code has been streamlined. diff --git a/doc/changelogs/changes-6.8.2.1 b/doc/changelogs/changes-6.8.2.1 new file mode 100644 index 00000000..47d2ca37 --- /dev/null +++ b/doc/changelogs/changes-6.8.2.1 @@ -0,0 +1,21 @@ +Qt for Python 6.8.2.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-3000] A bug in 6.8.2 causing the MSVC runtime libraries no longer + being shipped has been fixed. diff --git a/examples/3d/simple3d/simple3d.py b/examples/3d/simple3d/simple3d.py index eef0dca5..23ceae85 100644 --- a/examples/3d/simple3d/simple3d.py +++ b/examples/3d/simple3d/simple3d.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qt3d/simple-cpp example from Qt v5.x""" diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py index c5d7fc2f..54378daa 100644 --- a/examples/async/eratosthenes/eratosthenes_asyncio.py +++ b/examples/async/eratosthenes/eratosthenes_asyncio.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import (Qt, QObject, Signal, Slot) from PySide6.QtGui import (QColor, QFont, QPalette) diff --git a/examples/async/eratosthenes/eratosthenes_trio.py b/examples/async/eratosthenes/eratosthenes_trio.py index 89d3d1ce..9530077e 100644 --- a/examples/async/eratosthenes/eratosthenes_trio.py +++ b/examples/async/eratosthenes/eratosthenes_trio.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import (Qt, QEvent, QObject, QTimer, Signal, Slot) from PySide6.QtGui import (QColor, QFont, QPalette) diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py index a6c4708b..7ffd5030 100644 --- a/examples/async/minimal/minimal_asyncio.py +++ b/examples/async/minimal/minimal_asyncio.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget) diff --git a/examples/async/minimal/minimal_trio.py b/examples/async/minimal/minimal_trio.py index 5ae19fa3..5d331c10 100644 --- a/examples/async/minimal/minimal_trio.py +++ b/examples/async/minimal/minimal_trio.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import (Qt, QEvent, QObject, Signal, Slot) from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget) diff --git a/examples/axcontainer/axviewer/axviewer.py b/examples/axcontainer/axviewer/axviewer.py index bcd4c271..a591b168 100644 --- a/examples/axcontainer/axviewer/axviewer.py +++ b/examples/axcontainer/axviewer/axviewer.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 Active Qt Viewer example""" diff --git a/examples/bluetooth/btscanner/device.py b/examples/bluetooth/btscanner/device.py index c75f5b8a..b36673a4 100644 --- a/examples/bluetooth/btscanner/device.py +++ b/examples/bluetooth/btscanner/device.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QPoint, Qt, Slot from PySide6.QtGui import QColor @@ -33,7 +34,7 @@ class DeviceDiscoveryDialog(QDialog): self.host_mode_state_changed(self._local_device.hostMode()) # add context menu for devices to be able to pair device - self._ui.list.setContextMenuPolicy(Qt.CustomContextMenu) + self._ui.list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self._ui.list.customContextMenuRequested.connect(self.display_pairing_menu) self._local_device.pairingFinished.connect(self.pairing_done) @@ -45,11 +46,11 @@ class DeviceDiscoveryDialog(QDialog): if not items: item = QListWidgetItem(label) pairing_status = self._local_device.pairingStatus(info.address()) - if (pairing_status == QBluetoothLocalDevice.Paired - or pairing_status == QBluetoothLocalDevice.AuthorizedPaired): - item.setForeground(QColor(Qt.green)) + if (pairing_status == QBluetoothLocalDevice.Pairing.Paired + or pairing_status == QBluetoothLocalDevice.Pairing.AuthorizedPaired): + item.setForeground(QColor(Qt.GlobalColor.green)) else: - item.setForeground(QColor(Qt.black)) + item.setForeground(QColor(Qt.GlobalColor.black)) self._ui.list.addItem(item) @Slot() @@ -77,9 +78,9 @@ class DeviceDiscoveryDialog(QDialog): @Slot(bool) def on_discoverable_clicked(self, clicked): if clicked: - self._local_device.setHostMode(QBluetoothLocalDevice.HostDiscoverable) + self._local_device.setHostMode(QBluetoothLocalDevice.HostMode.HostDiscoverable) else: - self._local_device.setHostMode(QBluetoothLocalDevice.HostConnectable) + self._local_device.setHostMode(QBluetoothLocalDevice.HostMode.HostConnectable) @Slot(bool) def on_power_clicked(self, clicked): @@ -90,10 +91,10 @@ class DeviceDiscoveryDialog(QDialog): @Slot("QBluetoothLocalDevice::HostMode") def host_mode_state_changed(self, mode): - self._ui.power.setChecked(mode != QBluetoothLocalDevice.HostPoweredOff) - self._ui.discoverable.setChecked(mode == QBluetoothLocalDevice.HostDiscoverable) + self._ui.power.setChecked(mode != QBluetoothLocalDevice.HostMode.HostPoweredOff) + self._ui.discoverable.setChecked(mode == QBluetoothLocalDevice.HostMode.HostDiscoverable) - on = mode != QBluetoothLocalDevice.HostPoweredOff + on = mode != QBluetoothLocalDevice.HostMode.HostPoweredOff self._ui.scan.setEnabled(on) self._ui.discoverable.setEnabled(on) diff --git a/examples/bluetooth/btscanner/main.py b/examples/bluetooth/btscanner/main.py index a54a862a..cab83de2 100644 --- a/examples/bluetooth/btscanner/main.py +++ b/examples/bluetooth/btscanner/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the bluetooth/btscanner example from Qt v6.x""" diff --git a/examples/bluetooth/btscanner/service.py b/examples/bluetooth/btscanner/service.py index 31df8a9e..928c8cde 100644 --- a/examples/bluetooth/btscanner/service.py +++ b/examples/bluetooth/btscanner/service.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtWidgets import QDialog diff --git a/examples/bluetooth/btscanner/service.ui b/examples/bluetooth/btscanner/service.ui index 4ca12ee0..5a4bd637 100644 --- a/examples/bluetooth/btscanner/service.ui +++ b/examples/bluetooth/btscanner/service.ui @@ -27,7 +27,7 @@ - QDialogButtonBox::Close + QDialogButtonBox::StandardButton::Close diff --git a/examples/bluetooth/btscanner/ui_device.py b/examples/bluetooth/btscanner/ui_device.py index b443b2bc..bf56002d 100644 --- a/examples/bluetooth/btscanner/ui_device.py +++ b/examples/bluetooth/btscanner/ui_device.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'device.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/bluetooth/btscanner/ui_service.py b/examples/bluetooth/btscanner/ui_service.py index ccc36677..151daceb 100644 --- a/examples/bluetooth/btscanner/ui_service.py +++ b/examples/bluetooth/btscanner/ui_service.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'service.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -38,7 +38,7 @@ class Ui_ServiceDiscovery(object): self.buttonBox = QDialogButtonBox(ServiceDiscovery) self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setStandardButtons(QDialogButtonBox.Close) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Close) self.verticalLayout.addWidget(self.buttonBox) diff --git a/examples/bluetooth/heartrate_game/bluetoothbaseclass.py b/examples/bluetooth/heartrate_game/bluetoothbaseclass.py index cc5c9dbd..6278b041 100644 --- a/examples/bluetooth/heartrate_game/bluetoothbaseclass.py +++ b/examples/bluetooth/heartrate_game/bluetoothbaseclass.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal, Slot diff --git a/examples/bluetooth/heartrate_game/connectionhandler.py b/examples/bluetooth/heartrate_game/connectionhandler.py index 7bf60bbc..17b1500d 100644 --- a/examples/bluetooth/heartrate_game/connectionhandler.py +++ b/examples/bluetooth/heartrate_game/connectionhandler.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/bluetooth/heartrate_game/devicefinder.py b/examples/bluetooth/heartrate_game/devicefinder.py index e581d12e..5c00e7c9 100644 --- a/examples/bluetooth/heartrate_game/devicefinder.py +++ b/examples/bluetooth/heartrate_game/devicefinder.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtBluetooth import (QBluetoothDeviceDiscoveryAgent, diff --git a/examples/bluetooth/heartrate_game/devicehandler.py b/examples/bluetooth/heartrate_game/devicehandler.py index df34052b..8599c029 100644 --- a/examples/bluetooth/heartrate_game/devicehandler.py +++ b/examples/bluetooth/heartrate_game/devicehandler.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import struct @@ -57,7 +58,7 @@ class DeviceHandler(BluetoothBaseClass): self.m_stop = QDateTime() self.m_measurements = [] - self.m_addressType = QLowEnergyController.PublicAddress + self.m_addressType = QLowEnergyController.RemoteAddressType.PublicAddress self.m_demoTimer = QTimer() @@ -70,16 +71,16 @@ class DeviceHandler(BluetoothBaseClass): @Property(int) def addressType(self): - if self.m_addressType == QLowEnergyController.RandomAddress: + if self.m_addressType == QLowEnergyController.RemoteAddressType.RandomAddress: return DeviceHandler.AddressType.RANDOM_ADDRESS return DeviceHandler.AddressType.PUBLIC_ADDRESS @addressType.setter def addressType(self, type): if type == DeviceHandler.AddressType.PUBLIC_ADDRESS: - self.m_addressType = QLowEnergyController.PublicAddress + self.m_addressType = QLowEnergyController.RemoteAddressType.PublicAddress elif type == DeviceHandler.AddressType.RANDOM_ADDRESS: - self.m_addressType = QLowEnergyController.RandomAddress + self.m_addressType = QLowEnergyController.RemoteAddressType.RandomAddress @Slot(QLowEnergyController.Error) def controllerErrorOccurred(self, device): diff --git a/examples/bluetooth/heartrate_game/deviceinfo.py b/examples/bluetooth/heartrate_game/deviceinfo.py index 5fd5c327..136bbbac 100644 --- a/examples/bluetooth/heartrate_game/deviceinfo.py +++ b/examples/bluetooth/heartrate_game/deviceinfo.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/bluetooth/heartrate_game/heartrate_global.py b/examples/bluetooth/heartrate_game/heartrate_global.py index de5c37ac..384eb93a 100644 --- a/examples/bluetooth/heartrate_game/heartrate_global.py +++ b/examples/bluetooth/heartrate_game/heartrate_global.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os import sys diff --git a/examples/bluetooth/heartrate_game/main.py b/examples/bluetooth/heartrate_game/main.py index 3cb4f067..00df7de6 100644 --- a/examples/bluetooth/heartrate_game/main.py +++ b/examples/bluetooth/heartrate_game/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the bluetooth/heartrate-game example from Qt v6.x""" @@ -48,6 +49,6 @@ if __name__ == '__main__': if not engine.rootObjects(): sys.exit(-1) - ex = QCoreApplication.exec() + exit_code = QCoreApplication.exec() del engine - sys.exit(ex) + sys.exit(exit_code) diff --git a/examples/bluetooth/heartrate_server/heartrate_server.py b/examples/bluetooth/heartrate_server/heartrate_server.py index abbf4eb7..2bb8e3af 100644 --- a/examples/bluetooth/heartrate_server/heartrate_server.py +++ b/examples/bluetooth/heartrate_server/heartrate_server.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the bluetooth/heartrate-server example from Qt v6.x""" @@ -28,7 +29,7 @@ if __name__ == '__main__': #! [Advertising Data] advertising_data = QLowEnergyAdvertisingData() - advertising_data.setDiscoverability(QLowEnergyAdvertisingData.DiscoverabilityGeneral) + advertising_data.setDiscoverability(QLowEnergyAdvertisingData.Discoverability.DiscoverabilityGeneral) # noqa: E501 advertising_data.setIncludePowerLevel(True) advertising_data.setLocalName("HeartRateServer") advertising_data.setServices([QBluetoothUuid.ServiceClassUuid.HeartRate]) @@ -38,13 +39,13 @@ if __name__ == '__main__': char_data = QLowEnergyCharacteristicData() char_data.setUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement) char_data.setValue(QByteArray(2, 0)) - char_data.setProperties(QLowEnergyCharacteristic.Notify) + char_data.setProperties(QLowEnergyCharacteristic.PropertyType.Notify) client_config = QLowEnergyDescriptorData( QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration, QByteArray(2, 0)) char_data.addDescriptor(client_config) service_data = QLowEnergyServiceData() - service_data.setType(QLowEnergyServiceData.ServiceTypePrimary) + service_data.setType(QLowEnergyServiceData.ServiceType.ServiceTypePrimary) service_data.setUuid(QBluetoothUuid.ServiceClassUuid.HeartRate) service_data.addCharacteristic(char_data) #! [Service Data] diff --git a/examples/bluetooth/lowenergyscanner/characteristicinfo.py b/examples/bluetooth/lowenergyscanner/characteristicinfo.py index 42bde875..18f4c420 100644 --- a/examples/bluetooth/lowenergyscanner/characteristicinfo.py +++ b/examples/bluetooth/lowenergyscanner/characteristicinfo.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtBluetooth import QLowEnergyCharacteristic, QBluetoothUuid diff --git a/examples/bluetooth/lowenergyscanner/device.py b/examples/bluetooth/lowenergyscanner/device.py index 09108cf6..afa30ff8 100644 --- a/examples/bluetooth/lowenergyscanner/device.py +++ b/examples/bluetooth/lowenergyscanner/device.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import warnings from PySide6.QtBluetooth import (QBluetoothDeviceDiscoveryAgent, QLowEnergyController, QBluetoothDeviceInfo, QBluetoothUuid, QLowEnergyService) diff --git a/examples/bluetooth/lowenergyscanner/deviceinfo.py b/examples/bluetooth/lowenergyscanner/deviceinfo.py index 35a56882..0952f8d7 100644 --- a/examples/bluetooth/lowenergyscanner/deviceinfo.py +++ b/examples/bluetooth/lowenergyscanner/deviceinfo.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/bluetooth/lowenergyscanner/main.py b/examples/bluetooth/lowenergyscanner/main.py index ec12f99e..35c82ed3 100644 --- a/examples/bluetooth/lowenergyscanner/main.py +++ b/examples/bluetooth/lowenergyscanner/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the bluetooth/lowenergyscanner example from Qt v6.x""" @@ -22,6 +23,6 @@ if __name__ == '__main__': if not engine.rootObjects(): sys.exit(-1) - ex = QCoreApplication.exec() + exit_code = QCoreApplication.exec() del engine - sys.exit(ex) + sys.exit(exit_code) diff --git a/examples/bluetooth/lowenergyscanner/serviceinfo.py b/examples/bluetooth/lowenergyscanner/serviceinfo.py index cddffe66..30df4e0b 100644 --- a/examples/bluetooth/lowenergyscanner/serviceinfo.py +++ b/examples/bluetooth/lowenergyscanner/serviceinfo.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtBluetooth import QLowEnergyService diff --git a/examples/charts/areachart/areachart.py b/examples/charts/areachart/areachart.py index 164264ff..4fdd3587 100644 --- a/examples/charts/areachart/areachart.py +++ b/examples/charts/areachart/areachart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the areachart example from Qt v6.x""" @@ -49,11 +50,11 @@ class TestChart(QMainWindow): self.chart.addSeries(self.series) self.chart.setTitle("Simple areachart example") self.chart.createDefaultAxes() - self.chart.axes(Qt.Horizontal)[0].setRange(0, 20) + self.chart.axes(Qt.Orientation.Horizontal)[0].setRange(0, 20) self.chart.axes(Qt.Vertical)[0].setRange(0, 10) self._chart_view = QChartView(self.chart) - self._chart_view.setRenderHint(QPainter.Antialiasing) + self._chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(self._chart_view) diff --git a/examples/charts/audio/audio.py b/examples/charts/audio/audio.py index 8ba0b20c..1d603f88 100644 --- a/examples/charts/audio/audio.py +++ b/examples/charts/audio/audio.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the charts/audio example from Qt v5.x""" diff --git a/examples/charts/barchart/barchart.py b/examples/charts/barchart/barchart.py index 66126ad8..793a013d 100644 --- a/examples/charts/barchart/barchart.py +++ b/examples/charts/barchart/barchart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the linechart example from Qt v6.x""" @@ -55,7 +56,7 @@ class TestChart(QMainWindow): self.chart.legend().setAlignment(Qt.AlignBottom) self._chart_view = QChartView(self.chart) - self._chart_view.setRenderHint(QPainter.Antialiasing) + self._chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(self._chart_view) diff --git a/examples/charts/callout/callout.py b/examples/charts/callout/callout.py index 622938d1..d8ba8e6e 100644 --- a/examples/charts/callout/callout.py +++ b/examples/charts/callout/callout.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Callout example from Qt v5.x""" @@ -148,7 +149,7 @@ class View(QGraphicsView): self._chart.createDefaultAxes() self._chart.setAcceptHoverEvents(True) - self.setRenderHint(QPainter.Antialiasing) + self.setRenderHint(QPainter.RenderHint.Antialiasing) self.scene().addItem(self._chart) self._coordX = QGraphicsSimpleTextItem(self._chart) diff --git a/examples/charts/chartthemes/main.py b/examples/charts/chartthemes/main.py index 5787710c..b7a57029 100644 --- a/examples/charts/chartthemes/main.py +++ b/examples/charts/chartthemes/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Chart Themes example from Qt v5.x""" @@ -144,7 +145,7 @@ class ThemeWidget(QWidget): lower_series = upper_series chart.createDefaultAxes() - axis_x = chart.axes(Qt.Horizontal)[0] + axis_x = chart.axes(Qt.Orientation.Horizontal)[0] axis_x.setRange(0, self.value_count - 1) axis_y = chart.axes(Qt.Vertical)[0] axis_y.setRange(0, self.value_max) @@ -187,7 +188,7 @@ class ThemeWidget(QWidget): chart.addSeries(series) chart.createDefaultAxes() - axis_x = chart.axes(Qt.Horizontal)[0] + axis_x = chart.axes(Qt.Orientation.Horizontal)[0] axis_x.setRange(0, self.value_max) axis_y = chart.axes(Qt.Vertical)[0] axis_y.setRange(0, self.value_count) @@ -226,7 +227,7 @@ class ThemeWidget(QWidget): chart.addSeries(series) chart.createDefaultAxes() - axis_x = chart.axes(Qt.Horizontal)[0] + axis_x = chart.axes(Qt.Orientation.Horizontal)[0] axis_x.setRange(0, self.value_max) axis_y = chart.axes(Qt.Vertical)[0] axis_y.setRange(0, self.value_count) @@ -247,7 +248,7 @@ class ThemeWidget(QWidget): chart.addSeries(series) chart.createDefaultAxes() - axis_x = chart.axes(Qt.Horizontal)[0] + axis_x = chart.axes(Qt.Orientation.Horizontal)[0] axis_x.setRange(0, self.value_max) axis_y = chart.axes(Qt.Vertical)[0] axis_y.setRange(0, self.value_count) @@ -293,7 +294,7 @@ class ThemeWidget(QWidget): # Update antialiasing checked = self.ui.antialiasCheckBox.isChecked() for chart in self.charts: - chart.setRenderHint(QPainter.Antialiasing, checked) + chart.setRenderHint(QPainter.RenderHint.Antialiasing, checked) # Update animation options idx = self.ui.animatedComboBox.currentIndex() diff --git a/examples/charts/chartthemes/themewidget.ui b/examples/charts/chartthemes/themewidget.ui index ad1e02f0..2e42a9d5 100644 --- a/examples/charts/chartthemes/themewidget.ui +++ b/examples/charts/chartthemes/themewidget.ui @@ -56,7 +56,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -77,24 +77,64 @@ currentIndexChanged(int) ThemeWidgetForm update_ui() + + + 20 + 20 + + + 20 + 20 + + antialiasCheckBox toggled(bool) ThemeWidgetForm update_ui() + + + 20 + 20 + + + 20 + 20 + + legendComboBox currentIndexChanged(int) ThemeWidgetForm update_ui() + + + 20 + 20 + + + 20 + 20 + + animatedComboBox currentIndexChanged(int) ThemeWidgetForm update_ui() + + + 20 + 20 + + + 20 + 20 + + diff --git a/examples/charts/chartthemes/ui_themewidget.py b/examples/charts/chartthemes/ui_themewidget.py index 711615b3..c46e4905 100644 --- a/examples/charts/chartthemes/ui_themewidget.py +++ b/examples/charts/chartthemes/ui_themewidget.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'themewidget.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/charts/donutbreakdown/donutbreakdown.py b/examples/charts/donutbreakdown/donutbreakdown.py index 9111d4aa..d6792357 100644 --- a/examples/charts/donutbreakdown/donutbreakdown.py +++ b/examples/charts/donutbreakdown/donutbreakdown.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Donut Chart Breakdown example from Qt v5.x""" @@ -138,7 +139,7 @@ if __name__ == "__main__": window = QMainWindow() chart_view = QChartView(donut_breakdown) - chart_view.setRenderHint(QPainter.Antialiasing) + chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) window.setCentralWidget(chart_view) available_geometry = window.screen().availableGeometry() size = available_geometry.height() * 0.75 diff --git a/examples/charts/dynamicspline/chart.py b/examples/charts/dynamicspline/chart.py index 59ddef13..0858007f 100644 --- a/examples/charts/dynamicspline/chart.py +++ b/examples/charts/dynamicspline/chart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import random diff --git a/examples/charts/dynamicspline/main.py b/examples/charts/dynamicspline/main.py index 8405824d..f74614e5 100644 --- a/examples/charts/dynamicspline/main.py +++ b/examples/charts/dynamicspline/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Dynamic Spline example from Qt v5.x""" import sys @@ -17,9 +18,9 @@ if __name__ == "__main__": chart = Chart() chart.setTitle("Dynamic spline chart") chart.legend().hide() - chart.setAnimationOptions(QChart.AllAnimations) + chart.setAnimationOptions(QChart.AnimationOption.AllAnimations) chart_view = QChartView(chart) - chart_view.setRenderHint(QPainter.Antialiasing) + chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) window.setCentralWidget(chart_view) window.resize(400, 300) window.show() diff --git a/examples/charts/legend/legend.py b/examples/charts/legend/legend.py index 5417a940..cdc42052 100644 --- a/examples/charts/legend/legend.py +++ b/examples/charts/legend/legend.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Legend example from Qt v5.x""" @@ -99,7 +100,7 @@ class MainWidget(QWidget): self.chart.legend().setVisible(True) self.chart.legend().setAlignment(Qt.AlignBottom) - self.chart_view.setRenderHint(QPainter.Antialiasing) + self.chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) def show_legend_spinbox(self): self.legend_settings.setVisible(True) diff --git a/examples/charts/lineandbar/lineandbar.py b/examples/charts/lineandbar/lineandbar.py index 62721c76..fe559a64 100644 --- a/examples/charts/lineandbar/lineandbar.py +++ b/examples/charts/lineandbar/lineandbar.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the line/bar example from Qt v5.x""" @@ -66,7 +67,7 @@ class TestChart(QMainWindow): self.chart.legend().setAlignment(Qt.AlignBottom) self._chart_view = QChartView(self.chart) - self._chart_view.setRenderHint(QPainter.Antialiasing) + self._chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(self._chart_view) diff --git a/examples/charts/linechart/linechart.py b/examples/charts/linechart/linechart.py index cee4c16d..3a779e83 100644 --- a/examples/charts/linechart/linechart.py +++ b/examples/charts/linechart/linechart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the linechart example from Qt v5.x""" @@ -33,7 +34,7 @@ class TestChart(QMainWindow): self.chart.setTitle("Simple line chart example") self._chart_view = QChartView(self.chart) - self._chart_view.setRenderHint(QPainter.Antialiasing) + self._chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(self._chart_view) diff --git a/examples/charts/logvalueaxis/logvalueaxis.py b/examples/charts/logvalueaxis/logvalueaxis.py index e727c120..5e86eaf2 100644 --- a/examples/charts/logvalueaxis/logvalueaxis.py +++ b/examples/charts/logvalueaxis/logvalueaxis.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Logarithmic Axis Example from Qt v5.x""" @@ -42,7 +43,7 @@ class TestChart(QMainWindow): self.series.attachAxis(self._axis_y) self._chart_view = QChartView(self.chart) - self._chart_view.setRenderHint(QPainter.Antialiasing) + self._chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(self._chart_view) diff --git a/examples/charts/memoryusage/memoryusage.py b/examples/charts/memoryusage/memoryusage.py index 3ba42368..543b5f56 100644 --- a/examples/charts/memoryusage/memoryusage.py +++ b/examples/charts/memoryusage/memoryusage.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 Charts example: Simple memory usage viewer""" diff --git a/examples/charts/modeldata/modeldata.py b/examples/charts/modeldata/modeldata.py index 0e36f777..5fd88be3 100644 --- a/examples/charts/modeldata/modeldata.py +++ b/examples/charts/modeldata/modeldata.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Model Data example from Qt v5.x""" @@ -37,10 +38,10 @@ class CustomTableModel(QAbstractTableModel): return self.column_count def headerData(self, section, orientation, role): - if role != Qt.DisplayRole: + if role != Qt.ItemDataRole.DisplayRole: return None - if orientation == Qt.Horizontal: + if orientation == Qt.Orientation.Horizontal: if section % 2 == 0: return "x" else: @@ -48,12 +49,12 @@ class CustomTableModel(QAbstractTableModel): else: return str(section + 1) - def data(self, index, role=Qt.DisplayRole): - if role == Qt.DisplayRole: + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole: return self.input_data[index.row()][index.column()] - elif role == Qt.EditRole: + elif role == Qt.ItemDataRole.EditRole: return self.input_data[index.row()][index.column()] - elif role == Qt.BackgroundRole: + elif role == Qt.ItemDataRole.BackgroundRole: for color, rect in self.mapping.items(): if rect.contains(index.column(), index.row()): return QColor(color) @@ -61,8 +62,8 @@ class CustomTableModel(QAbstractTableModel): return QColor(Qt.white) return None - def setData(self, index, value, role=Qt.EditRole): - if index.isValid() and role == Qt.EditRole: + def setData(self, index, value, role=Qt.ItemDataRole.EditRole): + if index.isValid() and role == Qt.ItemDataRole.EditRole: self.input_data[index.row()][index.column()] = float(value) self.dataChanged.emit(index, index) return True @@ -122,7 +123,7 @@ class TableWidget(QWidget): self.chart.createDefaultAxes() self.chart_view = QChartView(self.chart) - self.chart_view.setRenderHint(QPainter.Antialiasing) + self.chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.chart_view.setMinimumSize(640, 480) # create main layout diff --git a/examples/charts/nesteddonuts/nesteddonuts.py b/examples/charts/nesteddonuts/nesteddonuts.py index 49173c33..bb566ffa 100644 --- a/examples/charts/nesteddonuts/nesteddonuts.py +++ b/examples/charts/nesteddonuts/nesteddonuts.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Nested Donuts example from Qt v5.x""" @@ -21,7 +22,7 @@ class Widget(QWidget): self.setMinimumSize(800, 600) self.donuts = [] self.chart_view = QChartView() - self.chart_view.setRenderHint(QPainter.Antialiasing) + self.chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.chart = self.chart_view.chart() self.chart.legend().setVisible(False) self.chart.setTitle("Nested donuts demo") diff --git a/examples/charts/percentbarchart/percentbarchart.py b/examples/charts/percentbarchart/percentbarchart.py index 9f70c032..82f64e99 100644 --- a/examples/charts/percentbarchart/percentbarchart.py +++ b/examples/charts/percentbarchart/percentbarchart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Percent Bar Chart example from Qt v5.x""" @@ -50,7 +51,7 @@ class MainWindow(QMainWindow): chart.legend().setAlignment(Qt.AlignBottom) chart_view = QChartView(chart) - chart_view.setRenderHint(QPainter.Antialiasing) + chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(chart_view) diff --git a/examples/charts/piechart/piechart.py b/examples/charts/piechart/piechart.py index e71359a9..317e17fe 100644 --- a/examples/charts/piechart/piechart.py +++ b/examples/charts/piechart/piechart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Pie Chart Example from Qt v5.x""" @@ -35,7 +36,7 @@ class TestChart(QMainWindow): self.chart.legend().hide() self._chart_view = QChartView(self.chart) - self._chart_view.setRenderHint(QPainter.Antialiasing) + self._chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(self._chart_view) diff --git a/examples/charts/pointconfiguration/chartwindow.py b/examples/charts/pointconfiguration/chartwindow.py index 36b10aa1..70ef8426 100644 --- a/examples/charts/pointconfiguration/chartwindow.py +++ b/examples/charts/pointconfiguration/chartwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Selected Point Configuration Example from Qt 6.5""" from PySide6.QtCore import QPointF, Slot @@ -7,7 +8,6 @@ from PySide6.QtGui import QColor, QIcon, QPainter from PySide6.QtWidgets import QMainWindow, QLineEdit, QLabel, QComboBox from PySide6.QtWidgets import QCheckBox, QWidget, QGridLayout, QHBoxLayout from PySide6.QtCharts import QLineSeries, QXYSeries, QChart, QChartView -from typing import Union PointConfig = QXYSeries.PointConfiguration @@ -95,7 +95,7 @@ class ChartWindow(QMainWindow): self._select_point(4) @Slot(QPointF) - def _select_point(self, point: Union[QPointF, int]): + def _select_point(self, point: QPointF | int): try: index = (self._series.points().index(point.toPoint()) if isinstance(point, QPointF) else point) diff --git a/examples/charts/pointconfiguration/doc/pointconfiguration.rst b/examples/charts/pointconfiguration/doc/pointconfiguration.rst index ffe865d5..479d7b4e 100644 --- a/examples/charts/pointconfiguration/doc/pointconfiguration.rst +++ b/examples/charts/pointconfiguration/doc/pointconfiguration.rst @@ -141,4 +141,4 @@ and ``@index``. These are replaced with the x value, y value, and index of the p More information about that can be found in the documentation for `QtCharts.QXYSeries.pointLabelsFormat`_. -.. _`QtCharts.QXYSeries.pointLabelsFormat`: https://doc.qt.io/qtforpython/PySide6/QtCharts/QXYSeries.html#PySide6.QtCharts.PySide6.QtCharts.QXYSeries.pointLabelsFormat +.. _`QtCharts.QXYSeries.pointLabelsFormat`: https://doc.qt.io/qt-6/qxyseries.html#pointLabelsFormat-prop diff --git a/examples/charts/pointconfiguration/pointconfiguration.py b/examples/charts/pointconfiguration/pointconfiguration.py index d8c90d2d..b7bf4306 100644 --- a/examples/charts/pointconfiguration/pointconfiguration.py +++ b/examples/charts/pointconfiguration/pointconfiguration.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Light Markers Points Selection example from Qt v6.2""" import sys diff --git a/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py b/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py index df7b6168..a8064c1b 100644 --- a/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py +++ b/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Light Markers Points Selection example from Qt v6.2""" import sys @@ -48,7 +49,7 @@ if __name__ == "__main__": chart.legend().setVisible(False) chart_view = QChartView(chart) - chart_view.setRenderHint(QPainter.Antialiasing) + chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) control_widget = QWidget(window) control_layout = QGridLayout(control_widget) @@ -87,7 +88,7 @@ if __name__ == "__main__": @Slot(int) def display_unselected_points(checkbox_state): - if checkbox_state: + if checkbox_state == Qt.CheckState.Checked: series.setLightMarker( Utilities.get_point_representation( Utilities.point_type(char_point_combobox.currentIndex()), marker_size)) @@ -96,7 +97,7 @@ if __name__ == "__main__": show_unselected_points_label = QLabel("Display unselected points: ") show_unselected_points_checkbox.setChecked(True) - show_unselected_points_checkbox.stateChanged.connect(display_unselected_points) + show_unselected_points_checkbox.checkStateChanged.connect(display_unselected_points) control_label = QLabel("Marker and Selection Controls") control_label.setAlignment(Qt.AlignHCenter) diff --git a/examples/charts/pointselectionandmarkers/utilities.py b/examples/charts/pointselectionandmarkers/utilities.py index b27a2542..124cdad4 100644 --- a/examples/charts/pointselectionandmarkers/utilities.py +++ b/examples/charts/pointselectionandmarkers/utilities.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtGui import QImage, QPainter, QColor from PySide6.QtCore import Qt @@ -11,7 +12,7 @@ def rectangle(point_type, image_size): image = QImage(image_size, image_size, QImage.Format_RGB32) painter = QPainter() painter.begin(image) - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.fillRect(0, 0, image_size, image_size, point_type[2]) painter.end() return image @@ -26,7 +27,7 @@ def circle(point_type, image_size): image.fill(QColor(0, 0, 0, 0)) painter = QPainter() painter.begin(image) - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.setBrush(point_type[2]) pen = painter.pen() pen.setWidth(0) diff --git a/examples/charts/qmlpolarchart/qmlpolarchart.py b/examples/charts/qmlpolarchart/qmlpolarchart.py index 6391fc30..594ac7a6 100644 --- a/examples/charts/qmlpolarchart/qmlpolarchart.py +++ b/examples/charts/qmlpolarchart/qmlpolarchart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the QML Polar Chart Example from Qt v5.x""" diff --git a/examples/charts/temperaturerecords/temperaturerecords.py b/examples/charts/temperaturerecords/temperaturerecords.py index 358f3818..ad1cdbf7 100644 --- a/examples/charts/temperaturerecords/temperaturerecords.py +++ b/examples/charts/temperaturerecords/temperaturerecords.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Temperature Records example from Qt v5.x""" @@ -47,7 +48,7 @@ class MainWindow(QMainWindow): chart.legend().setAlignment(Qt.AlignBottom) chart_view = QChartView(chart) - chart_view.setRenderHint(QPainter.Antialiasing) + chart_view.setRenderHint(QPainter.RenderHint.Antialiasing) self.setCentralWidget(chart_view) diff --git a/examples/charts/zoomlinechart/chart.py b/examples/charts/zoomlinechart/chart.py new file mode 100644 index 00000000..2b83e57b --- /dev/null +++ b/examples/charts/zoomlinechart/chart.py @@ -0,0 +1,37 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from PySide6.QtWidgets import QGesture, QGestureEvent +from PySide6.QtCore import Qt, QEvent +from PySide6.QtCharts import QChart + + +class Chart(QChart): + def __init__(self, + ChartType=QChart.ChartType.ChartTypeCartesian, + QGraphicsItem=None, + WindowType=Qt.WindowFlags): + super().__init__() + + self.grabGesture(Qt.PanGesture) + self.grabGesture(Qt.PinchGesture) + + def sceneEvent(self, event: QEvent): + + if event.type() == QEvent.Gesture: + return self.gestureEvent(event) + + return super().sceneEvent(event) + + def gestureEvent(self, event: QGestureEvent): + + if gesture := event.gesture(Qt.PanGesture): + pan = gesture + self.scroll(-pan.delta().x(), pan.delta().y()) + + if gesture := event.gesture(Qt.PinchGesture): + pinch = gesture + + if pinch.changeFlags() & QGesture.QPinchGesture.ScaleFactorChanged: + self.zoom(pinch.scaleFactor()) + + return True diff --git a/examples/charts/zoomlinechart/chartview.py b/examples/charts/zoomlinechart/chartview.py new file mode 100644 index 00000000..b4dcab94 --- /dev/null +++ b/examples/charts/zoomlinechart/chartview.py @@ -0,0 +1,70 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from PySide6.QtCharts import QChart, QChartView +from PySide6.QtGui import QKeyEvent, QMouseEvent +from PySide6.QtCore import QEvent, Qt +from PySide6.QtWidgets import QGraphicsView + + +class ChartView(QChartView): + def __init__(self, chart, parent=None): + super().__init__(chart, parent) + + self.setRubberBand(QChartView.RectangleRubberBand) + self._isTouching = False + + def viewPortEvent(self, event: QEvent): + + if event.type() == QMouseEvent.TouchBegin: + self._isTouching = True + + self.chart().setAnimationOptions(QChart.NoAnimation) + + return super().viewPortEvent(event) + + def mousePressEvent(self, event: QMouseEvent): + + if self._isTouching: + return + + return super().mousePressEvent(event) + + def mouseMoveEvent(self, event: QMouseEvent): + + if self._isTouching: + return + + return super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event: QMouseEvent): + + if self._isTouching: + self._isTouching = False + + self.chart().setAnimationOptions(QChart.SeriesAnimations) + + return super().mouseReleaseEvent(event) + + def keyPressEvent(self, event: QKeyEvent): + + key = event.key() + if key == Qt.Key_Plus: + self.chart().zoomIn() + + elif key == Qt.Key_Minus: + self.chart().zoomOut() + + elif key == Qt.Key_Left: + self.chart().scroll(-10, 0) + + elif key == Qt.Key_Right: + self.chart().scroll(10, 0) + + elif key == Qt.Key_Up: + self.chart().scroll(0, 10) + + elif key == Qt.Key_Down: + self.chart().scroll(0, -10) + + else: + QGraphicsView.keyPressEvent(event) diff --git a/examples/charts/zoomlinechart/doc/zoomlinechart.png b/examples/charts/zoomlinechart/doc/zoomlinechart.png new file mode 100644 index 0000000000000000000000000000000000000000..528d55734269aa7d003d2745766b0122d6dbf08c GIT binary patch literal 33933 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 image diff --git a/examples/corelib/ipc/sharedmemory/dialog.ui b/examples/corelib/ipc/sharedmemory/dialog.ui index e99d6fb3..fff08206 100644 --- a/examples/corelib/ipc/sharedmemory/dialog.ui +++ b/examples/corelib/ipc/sharedmemory/dialog.ui @@ -1,7 +1,8 @@ - + + Dialog - - + + 0 0 @@ -9,33 +10,33 @@ 322 - + Dialog - - - - + + + + Load Image From File... - - - + + + Launch two of these dialogs. In the first, press the top button and load an image from a file. In the second, press the bottom button and display the loaded image from shared memory. - - Qt::AlignCenter + + Qt::AlignmentFlag::AlignCenter - + true - - - + + + Display Image From Shared Memory diff --git a/examples/corelib/ipc/sharedmemory/doc/sharedmemory.png b/examples/corelib/ipc/sharedmemory/doc/sharedmemory.png new file mode 100644 index 0000000000000000000000000000000000000000..53244d3f52e13720eda43b48be9a08fd17617274 GIT binary patch literal 14926 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 literal 0 HcmV?d00001 diff --git a/examples/corelib/ipc/sharedmemory/doc/sharedmemory.rst b/examples/corelib/ipc/sharedmemory/doc/sharedmemory.rst new file mode 100644 index 00000000..01f8a320 --- /dev/null +++ b/examples/corelib/ipc/sharedmemory/doc/sharedmemory.rst @@ -0,0 +1,11 @@ +IPC: Shared Memory +================== + +Demonstrates how to share image data between different processes using the +Shared Memory IPC mechanism. It corresponds to the Qt example +`Shared Memory Example `_. + +.. image:: sharedmemory.png + :align: center + :alt: sharedmemory screenshot + :width: 400 diff --git a/examples/corelib/ipc/sharedmemory/main.py b/examples/corelib/ipc/sharedmemory/main.py index e497c8de..ef873378 100644 --- a/examples/corelib/ipc/sharedmemory/main.py +++ b/examples/corelib/ipc/sharedmemory/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the ipc/sharedmemory example from Qt v6.x""" diff --git a/examples/corelib/ipc/sharedmemory/ui_dialog.py b/examples/corelib/ipc/sharedmemory/ui_dialog.py index 2cd544f4..8676cecd 100644 --- a/examples/corelib/ipc/sharedmemory/ui_dialog.py +++ b/examples/corelib/ipc/sharedmemory/ui_dialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'dialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -32,7 +32,7 @@ class Ui_Dialog(object): self.label = QLabel(Dialog) self.label.setObjectName(u"label") - self.label.setAlignment(Qt.AlignCenter) + self.label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.label.setWordWrap(True) self.gridLayout.addWidget(self.label, 1, 0, 1, 1) diff --git a/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst index e8e4d1ec..be733fc4 100644 --- a/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst +++ b/examples/corelib/mimetypesbrowser/doc/mimetypesbrowser.rst @@ -7,4 +7,3 @@ A Python application that demonstrates the analogous example in C++ .. image:: mimetypesbrowser.png :width: 400 :alt: mimetypebrowser screenshot - diff --git a/examples/corelib/mimetypesbrowser/mainwindow.py b/examples/corelib/mimetypesbrowser/mainwindow.py index 76404eeb..8842823f 100644 --- a/examples/corelib/mimetypesbrowser/mainwindow.py +++ b/examples/corelib/mimetypesbrowser/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from mimetypemodel import MimeTypeModel from PySide6.QtCore import (QDir, QFileInfo, QMimeDatabase, QModelIndex, Qt, @@ -40,7 +41,7 @@ class MainWindow(QMainWindow): self.about_action = QAction( "About Qt", self, - shortcut=QKeySequence(QKeySequence.HelpContents), + shortcut=QKeySequence(QKeySequence.StandardKey.HelpContents), triggered=QApplication.aboutQt, ) @@ -63,7 +64,7 @@ class MainWindow(QMainWindow): self.items = self.m_model.findItems( "application/octet-stream", - Qt.MatchContains | Qt.MatchFixedString | Qt.MatchRecursive, + Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchFixedString | Qt.MatchFlag.MatchRecursive ) if self.items: @@ -106,7 +107,7 @@ class MainWindow(QMainWindow): input_dialog = QInputDialog(self) input_dialog.setWindowTitle("Find") input_dialog.setLabelText("Text") - if input_dialog.exec() != QDialog.Accepted: + if input_dialog.exec() != QDialog.DialogCode.Accepted: return value = input_dialog.textValue().strip() @@ -115,9 +116,9 @@ class MainWindow(QMainWindow): self.m_find_matches.clear() self.m_find_index = 0 - items = self.m_model.findItems( - value, Qt.MatchContains | Qt.MatchFixedString | Qt.MatchRecursive - ) + flags = (Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchFixedString + | Qt.MatchFlag.MatchRecursive) + items = self.m_model.findItems(value, flags) for item in items: self.m_find_matches.append(self.m_model.indexFromItem(item)) @@ -157,5 +158,5 @@ class MainWindow(QMainWindow): self.m_find_previous_action.setEnabled(self.find_next_previous_enabled) def _select_and_goto(self, index: QModelIndex): - self.m_tree_view.scrollTo(index, QAbstractItemView.PositionAtCenter) + self.m_tree_view.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter) self.m_tree_view.setCurrentIndex(index) diff --git a/examples/corelib/mimetypesbrowser/mimetypemodel.py b/examples/corelib/mimetypesbrowser/mimetypemodel.py index 5243f110..12df5c86 100644 --- a/examples/corelib/mimetypesbrowser/mimetypemodel.py +++ b/examples/corelib/mimetypesbrowser/mimetypemodel.py @@ -1,18 +1,17 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from typing import List +from __future__ import annotations from PySide6.QtCore import QMimeDatabase, QMimeType, QModelIndex, QObject, Qt, qWarning from PySide6.QtGui import QStandardItem, QStandardItemModel -mimeTypeRole = Qt.UserRole + 1 -iconQueriedRole = Qt.UserRole + 2 +mimeTypeRole = Qt.ItemDataRole.UserRole + 1 +iconQueriedRole = Qt.ItemDataRole.UserRole + 2 def createRow(t: QMimeType): name_item = QStandardItem(t.name()) - flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled + flags = Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled name_item.setData(t, mimeTypeRole) name_item.setData(False, iconQueriedRole) name_item.setFlags(flags) @@ -29,7 +28,7 @@ class MimeTypeModel(QStandardItemModel): def populate(self): mime_database = QMimeDatabase() - all_types: List[QMimeType] = mime_database.allMimeTypes() + all_types: list[QMimeType] = mime_database.allMimeTypes() # Move top level types to rear end of list, sort this partition, # create top level items and truncate the list. diff --git a/examples/corelib/mimetypesbrowser/mimetypesbrowser.py b/examples/corelib/mimetypesbrowser/mimetypesbrowser.py index 4742a31b..4f7b6959 100644 --- a/examples/corelib/mimetypesbrowser/mimetypesbrowser.py +++ b/examples/corelib/mimetypesbrowser/mimetypesbrowser.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the corelib/mimetypes/mimetypebrowser example from from Qt""" diff --git a/examples/corelib/settingseditor/settingseditor.py b/examples/corelib/settingseditor/settingseditor.py index f87a2f4b..7da39a77 100644 --- a/examples/corelib/settingseditor/settingseditor.py +++ b/examples/corelib/settingseditor/settingseditor.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/tools/settingseditor example from Qt v5.x""" @@ -27,7 +28,7 @@ class TypeChecker: def __init__(self, parent=None): self.bool_exp = QRegularExpression('^(true)|(false)$') assert self.bool_exp.isValid() - self.bool_exp.setPatternOptions(QRegularExpression.CaseInsensitiveOption) + self.bool_exp.setPatternOptions(QRegularExpression.PatternOption.CaseInsensitiveOption) self.byteArray_exp = QRegularExpression(r'^[\x00-\xff]*$') assert self.byteArray_exp.isValid() @@ -105,13 +106,13 @@ class TypeChecker: min(int(match.captured(3)), 255), min(int(match.captured(4)), 255)) if isinstance(original_value, QDate): - value = QDate.fromString(text, Qt.ISODate) + value = QDate.fromString(text, Qt.DateFormat.ISODate) return value if value.isValid() else None if isinstance(original_value, QDateTime): - value = QDateTime.fromString(text, Qt.ISODate) + value = QDateTime.fromString(text, Qt.DateFormat.ISODate) return value if value.isValid() else None if isinstance(original_value, QTime): - value = QTime.fromString(text, Qt.ISODate) + value = QTime.fromString(text, Qt.DateFormat.ISODate) return value if value.isValid() else None if isinstance(original_value, QPoint): match = self.point_exp.match(text) @@ -172,8 +173,8 @@ class MainWindow(QMainWindow): self.load_ini_file(file_name) def load_ini_file(self, file_name): - settings = QSettings(file_name, QSettings.IniFormat) - if settings.status() != QSettings.NoError: + settings = QSettings(file_name, QSettings.Format.IniFormat) + if settings.status() != QSettings.Status.NoError: return self.set_settings_object(settings) self.fallbacks_action.setEnabled(False) @@ -185,7 +186,7 @@ class MainWindow(QMainWindow): "Property List Files (*.plist)") if file_name: - settings = QSettings(file_name, QSettings.NativeFormat) + settings = QSettings(file_name, QSettings.Format.NativeFormat) self.set_settings_object(settings) self.fallbacks_action.setEnabled(False) @@ -196,7 +197,7 @@ class MainWindow(QMainWindow): QLineEdit.Normal, 'HKEY_CURRENT_USER\\') if ok and path != '': - settings = QSettings(path, QSettings.NativeFormat) + settings = QSettings(path, QSettings.Format.NativeFormat) self.set_settings_object(settings) self.fallbacks_action.setEnabled(False) @@ -321,15 +322,17 @@ class LocationDialog(QDialog): self.locations_groupbox = QGroupBox("Setting Locations") self.locations_table = QTableWidget() - self.locations_table.setSelectionMode(QAbstractItemView.SingleSelection) - self.locations_table.setSelectionBehavior(QAbstractItemView.SelectRows) - self.locations_table.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.locations_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.locations_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.locations_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self.locations_table.setColumnCount(2) self.locations_table.setHorizontalHeaderLabels(("Location", "Access")) - self.locations_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) - self.locations_table.horizontalHeader().resizeSection(1, 180) + header = self.locations_table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + header.resizeSection(1, 180) - self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) self.format_combo.activated.connect(self.update_locations) self.scope_cCombo.activated.connect(self.update_locations) @@ -360,15 +363,15 @@ class LocationDialog(QDialog): def format(self): if self.format_combo.currentIndex() == 0: - return QSettings.NativeFormat + return QSettings.Format.NativeFormat else: - return QSettings.IniFormat + return QSettings.Format.IniFormat def scope(self): if self.scope_cCombo.currentIndex() == 0: - return QSettings.UserScope + return QSettings.Scope.UserScope else: - return QSettings.SystemScope + return QSettings.Scope.SystemScope def organization(self): return self.organization_combo.currentText() @@ -385,12 +388,12 @@ class LocationDialog(QDialog): for i in range(2): if i == 0: - if self.scope() == QSettings.SystemScope: + if self.scope() == QSettings.Scope.SystemScope: continue - actual_scope = QSettings.UserScope + actual_scope = QSettings.Scope.UserScope else: - actual_scope = QSettings.SystemScope + actual_scope = QSettings.Scope.SystemScope for j in range(2): if j == 0: @@ -419,13 +422,13 @@ class LocationDialog(QDialog): disable = False else: item1.setText("Read-only") - self.button_box.button(QDialogButtonBox.Ok).setDisabled(disable) + self.button_box.button(QDialogButtonBox.StandardButton.Ok).setDisabled(disable) else: item1.setText("Read-only fallback") if disable: - item0.setFlags(item0.flags() & ~Qt.ItemIsEnabled) - item1.setFlags(item1.flags() & ~Qt.ItemIsEnabled) + item0.setFlags(item0.flags() & ~Qt.ItemFlag.ItemIsEnabled) + item1.setFlags(item1.flags() & ~Qt.ItemFlag.ItemIsEnabled) self.locations_table.setItem(row, 0, item0) self.locations_table.setItem(row, 1, item1) @@ -441,8 +444,8 @@ class SettingsTree(QTreeWidget): self.setItemDelegate(VariantDelegate(self._type_checker, self)) self.setHeaderLabels(("Setting", "Type", "Value")) - self.header().setSectionResizeMode(0, QHeaderView.Stretch) - self.header().setSectionResizeMode(2, QHeaderView.Stretch) + self.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) + self.header().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) self.settings = None self.refresh_timer = QTimer() @@ -451,12 +454,12 @@ class SettingsTree(QTreeWidget): self.group_icon = QIcon() style = self.style() - self.group_icon.addPixmap(style.standardPixmap(QStyle.SP_DirClosedIcon), - QIcon.Normal, QIcon.Off) - self.group_icon.addPixmap(style.standardPixmap(QStyle.SP_DirOpenIcon), - QIcon.Normal, QIcon.On) + self.group_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_DirClosedIcon), + QIcon.Mode.Normal, QIcon.State.Off) + self.group_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_DirOpenIcon), + QIcon.Mode.Normal, QIcon.State.On) self.key_icon = QIcon() - self.key_icon.addPixmap(style.standardPixmap(QStyle.SP_FileIcon)) + self.key_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_FileIcon)) self.refresh_timer.timeout.connect(self.maybe_refresh) @@ -514,7 +517,7 @@ class SettingsTree(QTreeWidget): self.itemChanged.connect(self.update_setting) def event(self, event): - if event.type() == QEvent.WindowActivate: + if event.type() == QEvent.Type.WindowActivate: if self.isActiveWindow() and self.auto_refresh: self.maybe_refresh() @@ -528,7 +531,7 @@ class SettingsTree(QTreeWidget): key = ancestor.text(0) + '/' + key ancestor = ancestor.parent() - self.settings.setValue(key, item.data(2, Qt.UserRole)) + self.settings.setValue(key, item.data(2, Qt.ItemDataRole.UserRole)) if self.auto_refresh: self.refresh() @@ -542,7 +545,7 @@ class SettingsTree(QTreeWidget): child = self.child_at(parent, child_index) child.setText(1, '') child.setText(2, '') - child.setData(2, Qt.UserRole, None) + child.setData(2, Qt.ItemDataRole.UserRole, None) self.move_item_forward(parent, child_index, divider_index) else: child = self.create_item(group, parent, divider_index) @@ -580,7 +583,7 @@ class SettingsTree(QTreeWidget): value = self.settings.value(key, type=value_type) child.setText(1, value.__class__.__name__) child.setText(2, VariantDelegate.display_text(value)) - child.setData(2, Qt.UserRole, value) + child.setData(2, Qt.ItemDataRole.UserRole, value) while divider_index < self.child_count(parent): self.delete_item(parent, divider_index) @@ -597,7 +600,7 @@ class SettingsTree(QTreeWidget): item = QTreeWidgetItem(self, after) item.setText(0, text) - item.setFlags(item.flags() | Qt.ItemIsEditable) + item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) return item def delete_item(self, parent, index): @@ -637,7 +640,7 @@ class VariantDelegate(QItemDelegate): def paint(self, painter, option, index): if index.column() == 2: - value = index.model().data(index, Qt.UserRole) + value = index.model().data(index, Qt.ItemDataRole.UserRole) if not self.is_supported_type(value): my_option = QStyleOptionViewItem(option) my_option.state &= ~QStyle.State_Enabled @@ -650,7 +653,7 @@ class VariantDelegate(QItemDelegate): if index.column() != 2: return None - original_value = index.model().data(index, Qt.UserRole) + original_value = index.model().data(index, Qt.ItemDataRole.UserRole) if not self.is_supported_type(original_value): return None @@ -671,7 +674,7 @@ class VariantDelegate(QItemDelegate): def setEditorData(self, editor, index): if not editor: return - value = index.model().data(index, Qt.UserRole) + value = index.model().data(index, Qt.ItemDataRole.UserRole) if isinstance(editor, QCheckBox): editor.setCheckState(Qt.Checked if value else Qt.Unchecked) elif isinstance(editor, QSpinBox): @@ -688,7 +691,7 @@ class VariantDelegate(QItemDelegate): state, text, _ = validator.validate(text, 0) if state != QValidator.Acceptable: return None - original_value = index.model().data(index, Qt.UserRole) + original_value = index.model().data(index, Qt.ItemDataRole.UserRole) return self._type_checker.from_string(text, original_value) def setModelData(self, editor, model, index): @@ -700,8 +703,8 @@ class VariantDelegate(QItemDelegate): else: value = self.value_from_lineedit(editor, model, index) if value is not None: - model.setData(index, value, Qt.UserRole) - model.setData(index, self.display_text(value), Qt.DisplayRole) + model.setData(index, value, Qt.ItemDataRole.UserRole) + model.setData(index, self.display_text(value), Qt.ItemDataRole.DisplayRole) @staticmethod def is_supported_type(value): diff --git a/examples/corelib/threads/mandelbrot.py b/examples/corelib/threads/mandelbrot.py index 4689813d..af53b343 100644 --- a/examples/corelib/threads/mandelbrot.py +++ b/examples/corelib/threads/mandelbrot.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the corelib/threads/mandelbrot example from Qt v5.x, originating from PyQt""" @@ -8,7 +9,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter import sys from PySide6.QtCore import (Signal, QMutex, QElapsedTimer, QMutexLocker, - QPoint, QPointF, QSize, Qt, QThread, + QPoint, QPointF, QRectF, QSize, Qt, QThread, QWaitCondition, Slot) from PySide6.QtGui import QColor, QImage, QPainter, QPixmap, qRgb from PySide6.QtWidgets import QApplication, QWidget @@ -72,7 +73,7 @@ class RenderThread(QThread): self._result_size = resultSize if not self.isRunning(): - self.start(QThread.LowPriority) + self.start(QThread.Priority.LowPriority) else: self.restart = True self.condition.wakeOne() @@ -90,7 +91,7 @@ class RenderThread(QThread): half_width = resultSize.width() // 2 half_height = resultSize.height() // 2 - image = QImage(resultSize, QImage.Format_RGB32) + image = QImage(resultSize, QImage.Format.Format_RGB32) curpass = 0 @@ -193,7 +194,7 @@ class RenderThread(QThread): g = pow(g * s, 0.8) b = pow(b * s, 0.8) - return qRgb(r * 255, g * 255, b * 255) + return qRgb(int(r * 255), int(g * 255), int(b * 255)) class MandelbrotWidget(QWidget): @@ -213,16 +214,16 @@ class MandelbrotWidget(QWidget): self.thread.rendered_image.connect(self.update_pixmap) self.setWindowTitle("Mandelbrot") - self.setCursor(Qt.CrossCursor) + self.setCursor(Qt.CursorShape.CrossCursor) self._info = '' def paintEvent(self, event): with QPainter(self) as painter: - painter.fillRect(self.rect(), Qt.black) + painter.fillRect(self.rect(), Qt.GlobalColor.black) if self.pixmap.isNull(): - painter.setPen(Qt.white) - painter.drawText(self.rect(), Qt.AlignCenter, + painter.setPen(Qt.GlobalColor.white) + painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, "Rendering initial image, please wait...") return @@ -249,31 +250,33 @@ class MandelbrotWidget(QWidget): metrics = painter.fontMetrics() text_width = metrics.horizontalAdvance(text) - painter.setPen(Qt.NoPen) + painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(QColor(0, 0, 0, 127)) - painter.drawRect((self.width() - text_width) / 2 - 5, 0, text_width + 10, - metrics.lineSpacing() + 5) - painter.setPen(Qt.white) - painter.drawText((self.width() - text_width) / 2, - metrics.leading() + metrics.ascent(), text) + box = QRectF((self.width() - text_width) / 2 - 5, 0, + text_width + 10, metrics.lineSpacing() + 5) + painter.drawRect(box) + painter.setPen(Qt.GlobalColor.white) + pos = QPointF((self.width() - text_width) / 2, + metrics.leading() + metrics.ascent()) + painter.drawText(pos, text) def resizeEvent(self, event): self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) def keyPressEvent(self, event): - if event.key() == Qt.Key_Plus: + if event.key() == Qt.Key.Key_Plus: self.zoom(ZOOM_IN_FACTOR) - elif event.key() == Qt.Key_Minus: + elif event.key() == Qt.Key.Key_Minus: self.zoom(ZOOM_OUT_FACTOR) - elif event.key() == Qt.Key_Left: + elif event.key() == Qt.Key.Key_Left: self.scroll(-SCROLL_STEP, 0) - elif event.key() == Qt.Key_Right: + elif event.key() == Qt.Key.Key_Right: self.scroll(+SCROLL_STEP, 0) - elif event.key() == Qt.Key_Down: + elif event.key() == Qt.Key.Key_Down: self.scroll(0, -SCROLL_STEP) - elif event.key() == Qt.Key_Up: + elif event.key() == Qt.Key.Key_Up: self.scroll(0, +SCROLL_STEP) - elif event.key() == Qt.Key_Q: + elif event.key() == Qt.Key.Key_Q: self.close() else: super(MandelbrotWidget, self).keyPressEvent(event) @@ -284,18 +287,18 @@ class MandelbrotWidget(QWidget): self.zoom(pow(ZOOM_IN_FACTOR, num_steps)) def mousePressEvent(self, event): - if event.buttons() == Qt.LeftButton: + if event.buttons() == Qt.MouseButton.LeftButton: self._last_drag_pos = event.position() def mouseMoveEvent(self, event): - if event.buttons() & Qt.LeftButton: + if event.buttons() & Qt.MouseButton.LeftButton: pos = event.position() self._pixmap_offset += pos - self._last_drag_pos self._last_drag_pos = pos self.update() def mouseReleaseEvent(self, event): - if event.button() == Qt.LeftButton: + if event.button() == Qt.MouseButton.LeftButton: pos = event.position() self._pixmap_offset += pos - self._last_drag_pos self._last_drag_pos = QPointF() diff --git a/examples/datavisualization/bars3d/bars3d.py b/examples/datavisualization/bars3d/bars3d.py index f1283fcb..587c91c3 100644 --- a/examples/datavisualization/bars3d/bars3d.py +++ b/examples/datavisualization/bars3d/bars3d.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 QtDataVisualization example""" diff --git a/examples/datavisualization/graphgallery/axesinputhandler.py b/examples/datavisualization/graphgallery/axesinputhandler.py index 7f721d4a..25f2e9b4 100644 --- a/examples/datavisualization/graphgallery/axesinputhandler.py +++ b/examples/datavisualization/graphgallery/axesinputhandler.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import Enum from math import sin, cos, degrees diff --git a/examples/datavisualization/graphgallery/bargraph.py b/examples/datavisualization/graphgallery/bargraph.py index 7938a5ca..6d72c695 100644 --- a/examples/datavisualization/graphgallery/bargraph.py +++ b/examples/datavisualization/graphgallery/bargraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from graphmodifier import GraphModifier @@ -123,20 +124,20 @@ class BarGraph(QObject): reflectionCheckBox.setText("Show reflections") reflectionCheckBox.setChecked(False) - rotationSliderX = QSlider(Qt.Horizontal, self._barsWidget) + rotationSliderX = QSlider(Qt.Orientation.Horizontal, self._barsWidget) rotationSliderX.setTickInterval(30) rotationSliderX.setTickPosition(QSlider.TicksBelow) rotationSliderX.setMinimum(-180) rotationSliderX.setValue(0) rotationSliderX.setMaximum(180) - rotationSliderY = QSlider(Qt.Horizontal, self._barsWidget) + rotationSliderY = QSlider(Qt.Orientation.Horizontal, self._barsWidget) rotationSliderY.setTickInterval(15) rotationSliderY.setTickPosition(QSlider.TicksAbove) rotationSliderY.setMinimum(-90) rotationSliderY.setValue(0) rotationSliderY.setMaximum(90) - fontSizeSlider = QSlider(Qt.Horizontal, self._barsWidget) + fontSizeSlider = QSlider(Qt.Orientation.Horizontal, self._barsWidget) fontSizeSlider.setTickInterval(10) fontSizeSlider.setTickPosition(QSlider.TicksBelow) fontSizeSlider.setMinimum(1) @@ -176,7 +177,7 @@ class BarGraph(QObject): axisTitlesFixedCB.setText("Axis titles fixed") axisTitlesFixedCB.setChecked(True) - axisLabelRotationSlider = QSlider(Qt.Horizontal, self._barsWidget) + axisLabelRotationSlider = QSlider(Qt.Orientation.Horizontal, self._barsWidget) axisLabelRotationSlider.setTickInterval(10) axisLabelRotationSlider.setTickPosition(QSlider.TicksBelow) axisLabelRotationSlider.setMinimum(0) diff --git a/examples/datavisualization/graphgallery/custominputhandler.py b/examples/datavisualization/graphgallery/custominputhandler.py index 0402be60..6c856bf1 100644 --- a/examples/datavisualization/graphgallery/custominputhandler.py +++ b/examples/datavisualization/graphgallery/custominputhandler.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import Enum from math import sin, cos, degrees diff --git a/examples/datavisualization/graphgallery/graphmodifier.py b/examples/datavisualization/graphgallery/graphmodifier.py index e8d94ee9..f79e3d2b 100644 --- a/examples/datavisualization/graphgallery/graphmodifier.py +++ b/examples/datavisualization/graphgallery/graphmodifier.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from math import atan, degrees diff --git a/examples/datavisualization/graphgallery/highlightseries.py b/examples/datavisualization/graphgallery/highlightseries.py index b5495b42..ea52799e 100644 --- a/examples/datavisualization/graphgallery/highlightseries.py +++ b/examples/datavisualization/graphgallery/highlightseries.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QPoint, Qt, Slot from PySide6.QtGui import QLinearGradient, QVector3D diff --git a/examples/datavisualization/graphgallery/main.py b/examples/datavisualization/graphgallery/main.py index 4b57b85d..9eb87ff5 100644 --- a/examples/datavisualization/graphgallery/main.py +++ b/examples/datavisualization/graphgallery/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt DataVisualization graphgallery example from Qt v6.x""" diff --git a/examples/datavisualization/graphgallery/rainfalldata.py b/examples/datavisualization/graphgallery/rainfalldata.py index 18d6de8d..28b17791 100644 --- a/examples/datavisualization/graphgallery/rainfalldata.py +++ b/examples/datavisualization/graphgallery/rainfalldata.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/datavisualization/graphgallery/scatterdatamodifier.py b/examples/datavisualization/graphgallery/scatterdatamodifier.py index 7c16ef7b..52e27488 100644 --- a/examples/datavisualization/graphgallery/scatterdatamodifier.py +++ b/examples/datavisualization/graphgallery/scatterdatamodifier.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from math import cos, degrees, sqrt diff --git a/examples/datavisualization/graphgallery/scattergraph.py b/examples/datavisualization/graphgallery/scattergraph.py index ffcd428e..14a69028 100644 --- a/examples/datavisualization/graphgallery/scattergraph.py +++ b/examples/datavisualization/graphgallery/scattergraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, QSize, Qt from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton, diff --git a/examples/datavisualization/graphgallery/surfacegraph.py b/examples/datavisualization/graphgallery/surfacegraph.py index d42f496f..1ecd0d4d 100644 --- a/examples/datavisualization/graphgallery/surfacegraph.py +++ b/examples/datavisualization/graphgallery/surfacegraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from surfacegraphmodifier import SurfaceGraphModifier @@ -127,19 +128,19 @@ class SurfaceGraph(QObject): selectionVBox.addWidget(modeSliceColumnRB) selectionGroupBox.setLayout(selectionVBox) axisGroupBox = QGroupBox("Axis ranges") - axisMinSliderX = QSlider(Qt.Horizontal) + axisMinSliderX = QSlider(Qt.Orientation.Horizontal) axisMinSliderX.setMinimum(0) axisMinSliderX.setTickInterval(1) axisMinSliderX.setEnabled(True) - axisMaxSliderX = QSlider(Qt.Horizontal) + axisMaxSliderX = QSlider(Qt.Orientation.Horizontal) axisMaxSliderX.setMinimum(1) axisMaxSliderX.setTickInterval(1) axisMaxSliderX.setEnabled(True) - axisMinSliderZ = QSlider(Qt.Horizontal) + axisMinSliderZ = QSlider(Qt.Orientation.Horizontal) axisMinSliderZ.setMinimum(0) axisMinSliderZ.setTickInterval(1) axisMinSliderZ.setEnabled(True) - axisMaxSliderZ = QSlider(Qt.Horizontal) + axisMaxSliderZ = QSlider(Qt.Orientation.Horizontal) axisMaxSliderZ.setMinimum(1) axisMaxSliderZ.setTickInterval(1) axisMaxSliderZ.setEnabled(True) diff --git a/examples/datavisualization/graphgallery/surfacegraphmodifier.py b/examples/datavisualization/graphgallery/surfacegraphmodifier.py index 68499fac..9cd0afea 100644 --- a/examples/datavisualization/graphgallery/surfacegraphmodifier.py +++ b/examples/datavisualization/graphgallery/surfacegraphmodifier.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from math import sqrt, sin diff --git a/examples/datavisualization/graphgallery/topographicseries.py b/examples/datavisualization/graphgallery/topographicseries.py index 350edcb4..95995e36 100644 --- a/examples/datavisualization/graphgallery/topographicseries.py +++ b/examples/datavisualization/graphgallery/topographicseries.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtGui import QImage, QVector3D diff --git a/examples/datavisualization/graphgallery/variantbardatamapping.py b/examples/datavisualization/graphgallery/variantbardatamapping.py index 50bdefa6..5b1986b8 100644 --- a/examples/datavisualization/graphgallery/variantbardatamapping.py +++ b/examples/datavisualization/graphgallery/variantbardatamapping.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Signal diff --git a/examples/datavisualization/graphgallery/variantbardataproxy.py b/examples/datavisualization/graphgallery/variantbardataproxy.py index f69ebaf8..ad2b2a7c 100644 --- a/examples/datavisualization/graphgallery/variantbardataproxy.py +++ b/examples/datavisualization/graphgallery/variantbardataproxy.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtDataVisualization import QBarDataProxy, QBarDataItem diff --git a/examples/datavisualization/graphgallery/variantdataset.py b/examples/datavisualization/graphgallery/variantdataset.py index 752bc388..c9b8ab1a 100644 --- a/examples/datavisualization/graphgallery/variantdataset.py +++ b/examples/datavisualization/graphgallery/variantdataset.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Signal diff --git a/examples/datavisualization/minimalsurface/main.py b/examples/datavisualization/minimalsurface/main.py index db720dce..6b455bc4 100644 --- a/examples/datavisualization/minimalsurface/main.py +++ b/examples/datavisualization/minimalsurface/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os import sys @@ -23,7 +24,7 @@ if __name__ == '__main__': print(DESCRIPTION) surface = Q3DSurface() - surface.setFlags(surface.flags() ^ Qt.FramelessWindowHint) + surface.setFlags(surface.flags() ^ Qt.WindowType.FramelessWindowHint) axis = surface.axisX() axis.setTitle("X") axis.setTitleVisible(True) diff --git a/examples/datavisualization/qmlsurfacegallery/datasource.py b/examples/datavisualization/qmlsurfacegallery/datasource.py index aca2a6ab..05fa2183 100644 --- a/examples/datavisualization/qmlsurfacegallery/datasource.py +++ b/examples/datavisualization/qmlsurfacegallery/datasource.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from math import sin, pi diff --git a/examples/datavisualization/qmlsurfacegallery/main.py b/examples/datavisualization/qmlsurfacegallery/main.py index 7b980bc1..edc41eaf 100644 --- a/examples/datavisualization/qmlsurfacegallery/main.py +++ b/examples/datavisualization/qmlsurfacegallery/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt DataVisualization qmlsurfacegallery example from Qt v6.x""" @@ -31,7 +32,7 @@ if __name__ == "__main__": qml_file = Path(__file__).resolve().parent / "qml" / "qmlsurfacegallery" / "main.qml" viewer.setSource(QUrl.fromLocalFile(qml_file)) - viewer.setResizeMode(QQuickView.SizeRootObjectToView) + viewer.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) viewer.show() ex = app.exec() diff --git a/examples/datavisualization/surface/main.py b/examples/datavisualization/surface/main.py index f4397018..7f91f1e9 100644 --- a/examples/datavisualization/surface/main.py +++ b/examples/datavisualization/surface/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -77,19 +78,19 @@ class Window(QWidget): selection_vbox.addWidget(mode_slice_column_rb) selection_group_box.setLayout(selection_vbox) - axis_min_slider_x = QSlider(Qt.Horizontal, self) + axis_min_slider_x = QSlider(Qt.Orientation.Horizontal, self) axis_min_slider_x.setMinimum(0) axis_min_slider_x.setTickInterval(1) axis_min_slider_x.setEnabled(True) - axis_max_slider_x = QSlider(Qt.Horizontal, self) + axis_max_slider_x = QSlider(Qt.Orientation.Horizontal, self) axis_max_slider_x.setMinimum(1) axis_max_slider_x.setTickInterval(1) axis_max_slider_x.setEnabled(True) - axis_min_slider_z = QSlider(Qt.Horizontal, self) + axis_min_slider_z = QSlider(Qt.Orientation.Horizontal, self) axis_min_slider_z.setMinimum(0) axis_min_slider_z.setTickInterval(1) axis_min_slider_z.setEnabled(True) - axis_max_slider_z = QSlider(Qt.Horizontal, self) + axis_max_slider_z = QSlider(Qt.Orientation.Horizontal, self) axis_max_slider_z.setMinimum(1) axis_max_slider_z.setTickInterval(1) axis_max_slider_z.setEnabled(True) diff --git a/examples/datavisualization/surface/surfacegraph.py b/examples/datavisualization/surface/surfacegraph.py index 69eea716..26a910e8 100644 --- a/examples/datavisualization/surface/surfacegraph.py +++ b/examples/datavisualization/surface/surfacegraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math from pathlib import Path diff --git a/examples/datavisualization/surface_model_numpy/main.py b/examples/datavisualization/surface_model_numpy/main.py index f4397018..7f91f1e9 100644 --- a/examples/datavisualization/surface_model_numpy/main.py +++ b/examples/datavisualization/surface_model_numpy/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -77,19 +78,19 @@ class Window(QWidget): selection_vbox.addWidget(mode_slice_column_rb) selection_group_box.setLayout(selection_vbox) - axis_min_slider_x = QSlider(Qt.Horizontal, self) + axis_min_slider_x = QSlider(Qt.Orientation.Horizontal, self) axis_min_slider_x.setMinimum(0) axis_min_slider_x.setTickInterval(1) axis_min_slider_x.setEnabled(True) - axis_max_slider_x = QSlider(Qt.Horizontal, self) + axis_max_slider_x = QSlider(Qt.Orientation.Horizontal, self) axis_max_slider_x.setMinimum(1) axis_max_slider_x.setTickInterval(1) axis_max_slider_x.setEnabled(True) - axis_min_slider_z = QSlider(Qt.Horizontal, self) + axis_min_slider_z = QSlider(Qt.Orientation.Horizontal, self) axis_min_slider_z.setMinimum(0) axis_min_slider_z.setTickInterval(1) axis_min_slider_z.setEnabled(True) - axis_max_slider_z = QSlider(Qt.Horizontal, self) + axis_max_slider_z = QSlider(Qt.Orientation.Horizontal, self) axis_max_slider_z.setMinimum(1) axis_max_slider_z.setTickInterval(1) axis_max_slider_z.setEnabled(True) diff --git a/examples/datavisualization/surface_model_numpy/surfacegraph.py b/examples/datavisualization/surface_model_numpy/surfacegraph.py index c0c1acec..30b49b8a 100644 --- a/examples/datavisualization/surface_model_numpy/surfacegraph.py +++ b/examples/datavisualization/surface_model_numpy/surfacegraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import numpy as np import math @@ -23,9 +24,9 @@ SAMPLE_MIN = -8.0 SAMPLE_MAX = 8.0 -X_ROLE = Qt.UserRole + 1 -Y_ROLE = Qt.UserRole + 2 -Z_ROLE = Qt.UserRole + 3 +X_ROLE = Qt.ItemDataRole.UserRole + 1 +Y_ROLE = Qt.ItemDataRole.UserRole + 2 +Z_ROLE = Qt.ItemDataRole.UserRole + 3 class SqrtSinModel(QAbstractTableModel): @@ -64,7 +65,7 @@ class SqrtSinModel(QAbstractTableModel): def columnCount(self, index=QModelIndex()): return self._x.size - def data(self, index, role=Qt.DisplayRole): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): row = index.row() col = index.column() if role == X_ROLE: diff --git a/examples/datavisualization/surface_numpy/main.py b/examples/datavisualization/surface_numpy/main.py index f4397018..7f91f1e9 100644 --- a/examples/datavisualization/surface_numpy/main.py +++ b/examples/datavisualization/surface_numpy/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -77,19 +78,19 @@ class Window(QWidget): selection_vbox.addWidget(mode_slice_column_rb) selection_group_box.setLayout(selection_vbox) - axis_min_slider_x = QSlider(Qt.Horizontal, self) + axis_min_slider_x = QSlider(Qt.Orientation.Horizontal, self) axis_min_slider_x.setMinimum(0) axis_min_slider_x.setTickInterval(1) axis_min_slider_x.setEnabled(True) - axis_max_slider_x = QSlider(Qt.Horizontal, self) + axis_max_slider_x = QSlider(Qt.Orientation.Horizontal, self) axis_max_slider_x.setMinimum(1) axis_max_slider_x.setTickInterval(1) axis_max_slider_x.setEnabled(True) - axis_min_slider_z = QSlider(Qt.Horizontal, self) + axis_min_slider_z = QSlider(Qt.Orientation.Horizontal, self) axis_min_slider_z.setMinimum(0) axis_min_slider_z.setTickInterval(1) axis_min_slider_z.setEnabled(True) - axis_max_slider_z = QSlider(Qt.Horizontal, self) + axis_max_slider_z = QSlider(Qt.Orientation.Horizontal, self) axis_max_slider_z.setMinimum(1) axis_max_slider_z.setTickInterval(1) axis_max_slider_z.setEnabled(True) diff --git a/examples/datavisualization/surface_numpy/surfacegraph.py b/examples/datavisualization/surface_numpy/surfacegraph.py index c9f6e75a..654c9d05 100644 --- a/examples/datavisualization/surface_numpy/surfacegraph.py +++ b/examples/datavisualization/surface_numpy/surfacegraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import numpy as np import math diff --git a/examples/dbus/listnames/doc/listnames.rst b/examples/dbus/listnames/doc/listnames.rst new file mode 100644 index 00000000..3cae6b95 --- /dev/null +++ b/examples/dbus/listnames/doc/listnames.rst @@ -0,0 +1,7 @@ +D-Bus List Names Example +======================== + +Shows how to access the D-Bus bus daemon service. + +List Names is a command-line example which shows how to access the Qt D-Bus bus +daemon service. The example prints various information about the bus daemon service diff --git a/examples/dbus/listnames/listnames.py b/examples/dbus/listnames/listnames.py index 2240a06d..1c279f5d 100644 --- a/examples/dbus/listnames/listnames.py +++ b/examples/dbus/listnames/listnames.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the QtDBus listnames example from Qt v6.x""" diff --git a/examples/dbus/pingpong/doc/pingpong.rst b/examples/dbus/pingpong/doc/pingpong.rst new file mode 100644 index 00000000..1531920e --- /dev/null +++ b/examples/dbus/pingpong/doc/pingpong.rst @@ -0,0 +1,8 @@ +D-Bus Ping Pong Example +======================= + +Demonstrates a simple message system using D-Bus. + +Ping Pong is a command-line example that demonstrates the basics of Qt D-Bus. +A message is sent to another application and there is a confirmation of the +message. diff --git a/examples/dbus/pingpong/ping.py b/examples/dbus/pingpong/ping.py index d61f2549..2e652244 100644 --- a/examples/dbus/pingpong/ping.py +++ b/examples/dbus/pingpong/ping.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the QtDBus pingpong example from Qt v6.x (ping client)""" diff --git a/examples/dbus/pingpong/pong.py b/examples/dbus/pingpong/pong.py index 0dec6eda..371291f2 100644 --- a/examples/dbus/pingpong/pong.py +++ b/examples/dbus/pingpong/pong.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the QtDBus pingpong example from Qt v6.x (pong server)""" diff --git a/examples/demos/colorpaletteclient/abstractresource.py b/examples/demos/colorpaletteclient/abstractresource.py index 3f3a7ed6..49e6168f 100644 --- a/examples/demos/colorpaletteclient/abstractresource.py +++ b/examples/demos/colorpaletteclient/abstractresource.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject from PySide6.QtQml import QmlAnonymous diff --git a/examples/demos/colorpaletteclient/basiclogin.py b/examples/demos/colorpaletteclient/basiclogin.py index b9139c2e..1090f4d0 100644 --- a/examples/demos/colorpaletteclient/basiclogin.py +++ b/examples/demos/colorpaletteclient/basiclogin.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from functools import partial diff --git a/examples/demos/colorpaletteclient/main.py b/examples/demos/colorpaletteclient/main.py index a249b9fa..f32c1580 100644 --- a/examples/demos/colorpaletteclient/main.py +++ b/examples/demos/colorpaletteclient/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt RESTful API client demo from Qt v6.x""" @@ -28,6 +29,6 @@ if __name__ == "__main__": if not engine.rootObjects(): sys.exit(-1) - ex = app.exec() + exit_code = app.exec() del engine - sys.exit(ex) + sys.exit(exit_code) diff --git a/examples/demos/colorpaletteclient/paginatedresource.py b/examples/demos/colorpaletteclient/paginatedresource.py index b7f036c4..db399a7d 100644 --- a/examples/demos/colorpaletteclient/paginatedresource.py +++ b/examples/demos/colorpaletteclient/paginatedresource.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from dataclasses import dataclass @@ -27,9 +28,9 @@ class ColorUser: @QmlElement class ColorUserModel (QAbstractListModel): - IdRole = Qt.UserRole + 1 - EmailRole = Qt.UserRole + 2 - AvatarRole = Qt.UserRole + 3 + IdRole = Qt.ItemDataRole.UserRole + 1 + EmailRole = Qt.ItemDataRole.UserRole + 2 + AvatarRole = Qt.ItemDataRole.UserRole + 3 def __init__(self, parent=None): super().__init__(parent) @@ -86,10 +87,10 @@ class Color: @QmlElement class ColorModel (QAbstractListModel): - IdRole = Qt.UserRole + 1 - ColorRole = Qt.UserRole + 2 - NameRole = Qt.UserRole + 3 - PantoneValueRole = Qt.UserRole + 4 + IdRole = Qt.ItemDataRole.UserRole + 1 + ColorRole = Qt.ItemDataRole.UserRole + 2 + NameRole = Qt.ItemDataRole.UserRole + 3 + PantoneValueRole = Qt.ItemDataRole.UserRole + 4 def __init__(self, parent=None): super().__init__(parent) diff --git a/examples/demos/colorpaletteclient/restservice.py b/examples/demos/colorpaletteclient/restservice.py index d334ecd0..852c50fe 100644 --- a/examples/demos/colorpaletteclient/restservice.py +++ b/examples/demos/colorpaletteclient/restservice.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Property, Signal, ClassInfo from PySide6.QtNetwork import (QNetworkAccessManager, QRestAccessManager, diff --git a/examples/demos/documentviewer/abstractviewer.py b/examples/demos/documentviewer/abstractviewer.py index 4039fe9f..2c07c10c 100644 --- a/examples/demos/documentviewer/abstractviewer.py +++ b/examples/demos/documentviewer/abstractviewer.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject @@ -147,10 +148,10 @@ class AbstractViewer(QObject): if not self.hasContent(): self.statusMessage("No content to print.", type) return - printer = QPrinter(QPrinter.HighResolution) + printer = QPrinter(QPrinter.PrinterMode.HighResolution) dlg = QPrintDialog(printer, self.mainWindow()) dlg.setWindowTitle("Print Document") - if dlg.exec() == QDialog.Accepted: + if dlg.exec() == QDialog.DialogCode.Accepted: self.printDocument(printer) else: self.statusMessage("Printing canceled!", type) diff --git a/examples/demos/documentviewer/jsonviewer/jsonviewer.py b/examples/demos/documentviewer/jsonviewer/jsonviewer.py index 1e5a34ca..0cdf5369 100644 --- a/examples/demos/documentviewer/jsonviewer/jsonviewer.py +++ b/examples/demos/documentviewer/jsonviewer/jsonviewer.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import json @@ -104,19 +105,20 @@ class JsonItemModel(QAbstractItemModel): return None item = self.itemFromIndex(index) - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: if index.column() == 0: return item.key() if index.column() == 1: return item.value() - elif role == Qt.EditRole: + elif role == Qt.ItemDataRole.EditRole: if index.column() == 1: return item.value() return None def headerData(self, section, orientation, role): return (self._headers[section] - if role == Qt.DisplayRole and orientation == Qt.Horizontal else None) + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal + else None) def index(self, row, column, parent=QModelIndex()): if not self.hasIndex(row, column, parent): @@ -187,14 +189,14 @@ class JsonViewer(AbstractViewer): zoomInIcon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn) a = menu.addAction(zoomInIcon, "&+Expand all", self._tree.expandAll) tb.addAction(a) - a.setPriority(QAction.LowPriority) - a.setShortcut(QKeySequence.New) + a.setPriority(QAction.Priority.LowPriority) + a.setShortcut(QKeySequence.StandardKey.New) zoomOutIcon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut) a = menu.addAction(zoomOutIcon, "&-Collapse all", self._tree.collapseAll) tb.addAction(a) - a.setPriority(QAction.LowPriority) - a.setShortcut(QKeySequence.New) + a.setPriority(QAction.Priority.LowPriority) + a.setShortcut(QKeySequence.StandardKey.New) if not self._searchKey: self._searchKey = QLineEdit(tb) @@ -217,13 +219,13 @@ class JsonViewer(AbstractViewer): index = self._tree.model().index(i, 0) self._toplevel.addItem(index.data()) item = self._toplevel.item(i) - item.setData(Qt.UserRole, index) + item.setData(Qt.ItemDataRole.UserRole, index) item.setToolTip(f"Toplevel Item {i}") self._toplevel.setAcceptDrops(True) self._tree.setDragEnabled(True) - self._tree.setContextMenuPolicy(Qt.CustomContextMenu) - self._toplevel.setContextMenuPolicy(Qt.CustomContextMenu) + self._tree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self._toplevel.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self._toplevel.itemClicked.connect(self.onTopLevelItemClicked) self._toplevel.itemDoubleClicked.connect(self.onTopLevelItemDoubleClicked) @@ -258,7 +260,7 @@ class JsonViewer(AbstractViewer): self.disablePrinting() file_name = QDir.toNativeSeparators(self._file.fileName()) type = "open" - self._file.open(QIODevice.ReadOnly) + self._file.open(QIODevice.OpenModeFlag.ReadOnly) self._text = self._file.readAll().data().decode("utf-8") self._file.close() @@ -277,7 +279,7 @@ class JsonViewer(AbstractViewer): return self._tree.model() is not None def indexOf(self, item): - return QModelIndex(item.data(Qt.UserRole)) + return QModelIndex(item.data(Qt.ItemDataRole.UserRole)) @Slot(QListWidgetItem) def onTopLevelItemClicked(self, item): @@ -359,14 +361,14 @@ class JsonViewer(AbstractViewer): if not index.isValid(): return - item = QListWidgetItem(index.data(Qt.DisplayRole), self._toplevel) - item.setData(Qt.UserRole, index) + item = QListWidgetItem(index.data(Qt.ItemDataRole.DisplayRole), self._toplevel) + item.setData(Qt.ItemDataRole.UserRole, index) # Set a tooltip that shows where the item is located in the tree parent = index.parent() - tooltip = index.data(Qt.DisplayRole).toString() + tooltip = index.data(Qt.ItemDataRole.DisplayRole).toString() while parent.isValid(): - tooltip = parent.data(Qt.DisplayRole).toString() + "." + tooltip + tooltip = parent.data(Qt.ItemDataRole.DisplayRole).toString() + "." + tooltip parent = parent.parent() item.setToolTip(tooltip) diff --git a/examples/demos/documentviewer/main.py b/examples/demos/documentviewer/main.py index 2af373ef..5159b9c3 100644 --- a/examples/demos/documentviewer/main.py +++ b/examples/demos/documentviewer/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt Document Viewer demo from Qt v6.x""" diff --git a/examples/demos/documentviewer/mainwindow.py b/examples/demos/documentviewer/mainwindow.py index e9abd0be..d72cca28 100644 --- a/examples/demos/documentviewer/mainwindow.py +++ b/examples/demos/documentviewer/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, QMessageBox) from PySide6.QtCore import (QDir, QFile, QFileInfo, QSettings, Slot) @@ -66,7 +67,7 @@ class MainWindow(QMainWindow): def onActionOpenTriggered(self): fileDialog = QFileDialog(self, "Open Document", self._currentDir.absolutePath()) - while (fileDialog.exec() == QDialog.Accepted + while (fileDialog.exec() == QDialog.DialogCode.Accepted and not self.openFile(fileDialog.selectedFiles()[0])): pass diff --git a/examples/demos/documentviewer/mainwindow.ui b/examples/demos/documentviewer/mainwindow.ui index 482061fc..3406d6ed 100644 --- a/examples/demos/documentviewer/mainwindow.ui +++ b/examples/demos/documentviewer/mainwindow.ui @@ -28,11 +28,11 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QTabWidget::West + QTabWidget::TabPosition::West 0 @@ -70,7 +70,7 @@ 0 0 798 - 479 + 472 @@ -88,7 +88,7 @@ 0 0 983 - 23 + 26 @@ -234,8 +234,7 @@ - - .. + Quit diff --git a/examples/demos/documentviewer/pdfviewer/pdfviewer.py b/examples/demos/documentviewer/pdfviewer/pdfviewer.py index d5695e4d..a1f4e744 100644 --- a/examples/demos/documentviewer/pdfviewer/pdfviewer.py +++ b/examples/demos/documentviewer/pdfviewer/pdfviewer.py @@ -1,10 +1,11 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from math import sqrt from PySide6.QtWidgets import (QListView, QTreeView) -from PySide6.QtGui import QIcon, QPainter +from PySide6.QtGui import QIcon, QKeySequence, QPainter from PySide6.QtCore import (QDir, QIODevice, QModelIndex, QPointF, Slot) from PySide6.QtPrintSupport import QPrinter @@ -60,20 +61,16 @@ class PdfViewer(AbstractViewer): self._toolBar.addSeparator() self._toolBar.addWidget(self._zoomSelector) - actionZoomIn = self._toolBar.addAction("Zoom in") - actionZoomIn.setToolTip("Increase zoom level") icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, QIcon(":/demos/documentviewer/images/zoom-in.png")) - actionZoomIn.setIcon(icon) - self._toolBar.addAction(actionZoomIn) + actionZoomIn = self._toolBar.addAction(icon, "Zoom in", QKeySequence.StandardKey.ZoomIn) + actionZoomIn.setToolTip("Increase zoom level") actionZoomIn.triggered.connect(self.onActionZoomInTriggered) - actionZoomOut = self._toolBar.addAction("Zoom out") - actionZoomOut.setToolTip("Decrease zoom level") icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, QIcon(":/demos/documentviewer/images/zoom-out.png")) - actionZoomOut.setIcon(icon) - self._toolBar.addAction(actionZoomOut) + actionZoomOut = self._toolBar.addAction(icon, "Zoom out", QKeySequence.StandardKey.ZoomOut) + actionZoomOut.setToolTip("Decrease zoom level") actionZoomOut.triggered.connect(self.onActionZoomOutTriggered) nav.backAvailableChanged.connect(self._actionBack.setEnabled) @@ -129,7 +126,7 @@ class PdfViewer(AbstractViewer): def openPdfFile(self): self.disablePrinting() - if self._file.open(QIODevice.ReadOnly): + if self._file.open(QIODevice.OpenModeFlag.ReadOnly): self._document.load(self._file) documentTitle = self._document.metaData(QPdfDocument.MetaDataField.Title) diff --git a/examples/demos/documentviewer/pdfviewer/zoomselector.py b/examples/demos/documentviewer/pdfviewer/zoomselector.py index e5da5708..70133d99 100644 --- a/examples/demos/documentviewer/pdfviewer/zoomselector.py +++ b/examples/demos/documentviewer/pdfviewer/zoomselector.py @@ -1,5 +1,6 @@ # Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QComboBox from PySide6.QtCore import Signal, Slot diff --git a/examples/demos/documentviewer/recentfilemenu.py b/examples/demos/documentviewer/recentfilemenu.py index 36c7dc9f..be5ffaf4 100644 --- a/examples/demos/documentviewer/recentfilemenu.py +++ b/examples/demos/documentviewer/recentfilemenu.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QMenu from PySide6.QtCore import Signal, Slot diff --git a/examples/demos/documentviewer/recentfiles.py b/examples/demos/documentviewer/recentfiles.py index fdfc56d4..3f6e5e7a 100644 --- a/examples/demos/documentviewer/recentfiles.py +++ b/examples/demos/documentviewer/recentfiles.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import Enum, auto diff --git a/examples/demos/documentviewer/txtviewer/txtviewer.py b/examples/demos/documentviewer/txtviewer/txtviewer.py index ef5a38fc..31754eae 100644 --- a/examples/demos/documentviewer/txtviewer/txtviewer.py +++ b/examples/demos/documentviewer/txtviewer/txtviewer.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import (QDialog, QFileDialog, QPlainTextEdit) @@ -31,7 +32,7 @@ class TxtViewer(AbstractViewer): cutIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(":/demos/documentviewer/images/cut.png")) cutAct = QAction(cutIcon, "Cut", self) - cutAct.setShortcuts(QKeySequence.Cut) + cutAct.setShortcuts(QKeySequence.StandardKey.Cut) cutAct.setStatusTip("Cut the current selection's contents to the clipboard") cutAct.triggered.connect(self._textEdit.cut) editMenu.addAction(cutAct) @@ -40,7 +41,7 @@ class TxtViewer(AbstractViewer): copyIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(":/demos/documentviewer/images/copy.png")) copyAct = QAction(copyIcon, "Copy", self) - copyAct.setShortcuts(QKeySequence.Copy) + copyAct.setShortcuts(QKeySequence.StandardKey.Copy) copyAct.setStatusTip("Copy the current selection's contents to the clipboard") copyAct.triggered.connect(self._textEdit.copy) editMenu.addAction(copyAct) @@ -49,7 +50,7 @@ class TxtViewer(AbstractViewer): pasteIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(":/demos/documentviewer/images/paste.png")) pasteAct = QAction(pasteIcon, "Paste", self) - pasteAct.setShortcuts(QKeySequence.Paste) + pasteAct.setShortcuts(QKeySequence.StandardKey.Paste) pasteAct.setStatusTip("Paste the clipboard's contents into the current selection") pasteAct.triggered.connect(self._textEdit.paste) editMenu.addAction(pasteAct) @@ -87,13 +88,14 @@ class TxtViewer(AbstractViewer): def openFile(self): type = "open" file_name = QDir.toNativeSeparators(self._file.fileName()) - if not self._file.open(QFile.ReadOnly | QFile.Text): + if not self._file.open(QFile.OpenModeFlag.ReadOnly + | QFile.OpenModeFlag.Text): err = self._file.errorString() self.statusMessage(f"Cannot read file {file_name}:\n{err}.", type) return in_str = QTextStream(self._file) - QGuiApplication.setOverrideCursor(Qt.WaitCursor) + QGuiApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) if self._textEdit.toPlainText(): self._textEdit.clear() self.disablePrinting() @@ -116,8 +118,8 @@ class TxtViewer(AbstractViewer): def saveFile(self, file): file_name = QDir.toNativeSeparators(self._file.fileName()) errorMessage = "" - QGuiApplication.setOverrideCursor(Qt.WaitCursor) - if file.open(QFile.WriteOnly | QFile.Text): + QGuiApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) + if file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text): out = QTextStream(file) out << self._textEdit.toPlainText() else: @@ -135,8 +137,8 @@ class TxtViewer(AbstractViewer): def saveDocumentAs(self): dialog = QFileDialog(self.mainWindow()) dialog.setWindowModality(Qt.WindowModal) - dialog.setAcceptMode(QFileDialog.AcceptSave) - if dialog.exec() != QDialog.Accepted: + dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) + if dialog.exec() != QDialog.DialogCode.Accepted: return False files = dialog.selectedFiles() diff --git a/examples/demos/documentviewer/ui_mainwindow.py b/examples/demos/documentviewer/ui_mainwindow.py index 5773ae5b..3c410047 100644 --- a/examples/demos/documentviewer/ui_mainwindow.py +++ b/examples/demos/documentviewer/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -27,12 +27,12 @@ class Ui_MainWindow(object): MainWindow.setObjectName(u"MainWindow") MainWindow.resize(983, 602) icon = QIcon() - icon.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) MainWindow.setWindowIcon(icon) self.actionOpen = QAction(MainWindow) self.actionOpen.setObjectName(u"actionOpen") icon1 = QIcon() - icon1.addFile(u":/demos/documentviewer/images/open.png", QSize(), QIcon.Normal, QIcon.Off) + icon1.addFile(u":/demos/documentviewer/images/open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionOpen.setIcon(icon1) self.actionAbout = QAction(MainWindow) self.actionAbout.setObjectName(u"actionAbout") @@ -41,18 +41,18 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon2 = QIcon.fromTheme(iconThemeName) else: - icon2.addFile(u":/demos/documentviewer/images/help-about.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon2.addFile(u":/demos/documentviewer/images/help-about.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionAbout.setIcon(icon2) self.actionForward = QAction(MainWindow) self.actionForward.setObjectName(u"actionForward") icon3 = QIcon() - icon3.addFile(u":/demos/documentviewer/images/go-next.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon3.addFile(u":/demos/documentviewer/images/go-next.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionForward.setIcon(icon3) self.actionBack = QAction(MainWindow) self.actionBack.setObjectName(u"actionBack") icon4 = QIcon() - icon4.addFile(u":/demos/documentviewer/images/go-previous.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon4.addFile(u":/demos/documentviewer/images/go-previous.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionBack.setIcon(icon4) self.actionPrint = QAction(MainWindow) self.actionPrint.setObjectName(u"actionPrint") @@ -62,29 +62,23 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon5 = QIcon.fromTheme(iconThemeName) else: - icon5.addFile(u":/demos/documentviewer/images/print2x.png", QSize(), QIcon.Normal, QIcon.Off) + icon5.addFile(u":/demos/documentviewer/images/print2x.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionPrint.setIcon(icon5) self.actionAboutQt = QAction(MainWindow) self.actionAboutQt.setObjectName(u"actionAboutQt") icon6 = QIcon() - icon6.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Normal, QIcon.Off) - icon6.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Normal, QIcon.On) + icon6.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + icon6.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Mode.Normal, QIcon.State.On) self.actionAboutQt.setIcon(icon6) self.actionRecent = QAction(MainWindow) self.actionRecent.setObjectName(u"actionRecent") icon7 = QIcon() - icon7.addFile(u":/demos/documentviewer/images/document-open-recent.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon7.addFile(u":/demos/documentviewer/images/document-open-recent.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionRecent.setIcon(icon7) self.actionQuit = QAction(MainWindow) self.actionQuit.setObjectName(u"actionQuit") - icon8 = QIcon() - iconThemeName = u"application-exit" - if QIcon.hasThemeIcon(iconThemeName): - icon8 = QIcon.fromTheme(iconThemeName) - else: - icon8.addFile(u".", QSize(), QIcon.Normal, QIcon.Off) - + icon8 = QIcon(QIcon.fromTheme(u"application-exit")) self.actionQuit.setIcon(icon8) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") @@ -97,10 +91,10 @@ class Ui_MainWindow(object): self.verticalLayout_2.setObjectName(u"verticalLayout_2") self.splitter = QSplitter(self.viewArea) self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Horizontal) + self.splitter.setOrientation(Qt.Orientation.Horizontal) self.tabWidget = QTabWidget(self.splitter) self.tabWidget.setObjectName(u"tabWidget") - self.tabWidget.setTabPosition(QTabWidget.West) + self.tabWidget.setTabPosition(QTabWidget.TabPosition.West) self.bookmarkTab = QWidget() self.bookmarkTab.setObjectName(u"bookmarkTab") self.tabWidget.addTab(self.bookmarkTab, "") @@ -119,7 +113,7 @@ class Ui_MainWindow(object): self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") - self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 798, 479)) + self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 798, 472)) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.splitter.addWidget(self.scrollArea) @@ -131,7 +125,7 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 983, 23)) + self.menubar.setGeometry(QRect(0, 0, 983, 26)) self.qtFileMenu = QMenu(self.menubar) self.qtFileMenu.setObjectName(u"qtFileMenu") self.menuHelp = QMenu(self.menubar) diff --git a/examples/demos/documentviewer/viewerfactory.py b/examples/demos/documentviewer/viewerfactory.py index ecae6770..0d32cbfe 100644 --- a/examples/demos/documentviewer/viewerfactory.py +++ b/examples/demos/documentviewer/viewerfactory.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import Enum, auto diff --git a/examples/demos/osmbuildings/OSMBuildings/Main.qml b/examples/demos/osmbuildings/OSMBuildings/Main.qml new file mode 100644 index 00000000..aa0f65aa --- /dev/null +++ b/examples/demos/osmbuildings/OSMBuildings/Main.qml @@ -0,0 +1,233 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Window +import QtQuick3D +import QtQuick3D.Helpers + +import OSMBuildings + +Window { + width: 1024 + height: 768 + visible: true + title: qsTr("OSM Buildings") + + OSMManager { + id: osmManager + + onMapsDataReady: function( mapData, tileX, tileY, zoomLevel ){ + mapModels.addModel(mapData, tileX, tileY, zoomLevel) + } + } + + Component { + id: chunkModelMap + Node { + property variant mapData: null + property int tileX: 0 + property int tileY: 0 + property int zoomLevel: 0 + Model { + id: basePlane + position: Qt.vector3d( osmManager.tileSizeX * tileX, osmManager.tileSizeY * -tileY, 0.0 ) + scale: Qt.vector3d( osmManager.tileSizeX / 100., osmManager.tileSizeY / 100., 0.5) + source: "#Rectangle" + materials: [ + CustomMaterial { + property TextureInput tileTexture: TextureInput { + enabled: true + texture: Texture { + textureData: CustomTextureData { + Component.onCompleted: setImageData( mapData ) + } } + } + shadingMode: CustomMaterial.Shaded + cullMode: Material.BackFaceCulling + fragmentShader: "customshadertiles.frag" + } + ] + } + } + } + + + View3D { + id: v3d + anchors.fill: parent + + environment: ExtendedSceneEnvironment { + id: env + backgroundMode: SceneEnvironment.Color + clearColor: "#8099b3" + fxaaEnabled: true + fog: Fog { + id: theFog + color:"#8099b3" + enabled: true + depthEnabled: true + depthFar: 600 + } + } + + Node { + id: originNode + eulerRotation: Qt.vector3d(50.0, 0.0, 0.0) + PerspectiveCamera { + id: cameraNode + frustumCullingEnabled: true + clipFar: 600 + clipNear: 100 + fieldOfView: 90 + z: 100 + + onZChanged: originNode.updateManagerCamera() + + } + Component.onCompleted: updateManagerCamera() + + onPositionChanged: updateManagerCamera() + + onRotationChanged: updateManagerCamera() + + function updateManagerCamera(){ + osmManager.setCameraProperties( originNode.position, + originNode.right, cameraNode.z, + cameraController.minimumZoom, + cameraController.maximumZoom, + originNode.eulerRotation.x, + cameraController.minimumTilt, + cameraController.maximumTilt ) + } + } + + DirectionalLight { + color: Qt.rgba(1.0, 1.0, 0.95, 1.0) + ambientColor: Qt.rgba(0.5, 0.45, 0.45, 1.0) + rotation: Quaternion.fromEulerAngles(-10, -45, 0) + } + + Node { + id: mapModels + + function addModel(mapData, tileX, tileY, zoomLevel) + { + chunkModelMap.createObject( mapModels, { "mapData": mapData, + "tileX": tileX, + "tileY": tileY, + "zoomLevel": zoomLevel + } ) + } + } + + OSMCameraController { + id: cameraController + origin: originNode + camera: cameraNode + } + } + + Item { + id: tokenArea + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 10 + Text { + id: tokenInputArea + visible: false + anchors.left: parent.left + anchors.bottom: parent.bottom + color: "white" + styleColor: "black" + style: Text.Outline + text: "Open street map tile token: " + Rectangle { + border.width: 1 + border.color: "black" + anchors.fill: tokenTxtInput + anchors.rightMargin: -30 + Text { + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 2 + anchors.rightMargin: 8 + color: "blue" + styleColor: "white" + style: Text.Outline + text: "OK" + Behavior on scale { + NumberAnimation { + easing.type: Easing.OutBack + } + } + MouseArea { + anchors.fill: parent + anchors.margins: -10 + onPressedChanged: { + if (pressed) + parent.scale = 0.9 + else + parent.scale = 1.0 + } + onClicked: { + tokenInputArea.visible = false + osmManager.setToken(tokenTxtInput.text) + tokenWarning.demoToken = osmManager.isDemoToken() + tokenWarning.visible = true + } + } + } + } + TextInput { + id: tokenTxtInput + clip: true + anchors.left: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: -3 + height: tokenTxtInput.contentHeight + 5 + width: 110 + leftPadding: 5 + rightPadding: 5 + } + } + + Text { + id: tokenWarning + property bool demoToken: true + anchors.left: parent.left + anchors.bottom: parent.bottom + color: "white" + styleColor: "black" + style: Text.Outline + text: demoToken ? "You are using the OSM limited demo token " : + "You are using a token " + Text { + anchors.left: parent.right + color: "blue" + styleColor: "white" + style: Text.Outline + text: "click here to change" + Behavior on scale { + NumberAnimation { + easing.type: Easing.OutBack + } + } + MouseArea { + anchors.fill: parent + onPressedChanged: { + if (pressed) + parent.scale = 0.9 + else + parent.scale = 1.0 + } + onClicked: { + tokenWarning.visible = false + tokenTxtInput.text = osmManager.token() + tokenInputArea.visible = true + } + } + } + } + } +} diff --git a/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml b/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml new file mode 100644 index 00000000..114a6dea --- /dev/null +++ b/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml @@ -0,0 +1,234 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick3D + +Item { + id: root + required property Node origin + required property Camera camera + + property real xSpeed: 0.05 + property real ySpeed: 0.05 + + property bool xInvert: false + property bool yInvert: false + + property bool mouseEnabled: true + property bool panEnabled: true + + readonly property bool inputsNeedProcessing: status.useMouse || status.isPanning + + readonly property real minimumZoom: 30 + readonly property real maximumZoom: 200 + + readonly property real minimumTilt: 0 + readonly property real maximumTilt: 80 + + implicitWidth: parent.width + implicitHeight: parent.height + + Connections { + target: camera + Component.onCompleted: { + onZChanged() + } + + function onZChanged() { + // Adjust near/far values based on distance + let distance = camera.z + if (distance < 1) { + camera.clipNear = 0.01 + camera.clipFar = 100 + } else if (distance < 100) { + camera.clipNear = 0.1 + camera.clipFar = 1000 + } else { + camera.clipNear = 1 + camera.clipFar = 10000 + } + } + } + + DragHandler { + id: dragHandler + target: null + enabled: mouseEnabled + acceptedModifiers: Qt.NoModifier + acceptedButtons: Qt.RightButton + onCentroidChanged: { + mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false); + } + + onActiveChanged: { + if (active) + mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y)); + else + mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y)); + } + } + + DragHandler { + id: ctrlDragHandler + target: null + enabled: mouseEnabled && panEnabled + //acceptedModifiers: Qt.ControlModifier + onCentroidChanged: { + panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)); + } + + onActiveChanged: { + if (active) + startPan(Qt.vector2d(centroid.position.x, centroid.position.y)); + else + endPan(); + } + } + + PinchHandler { + id: pinchHandler + target: null + enabled: mouseEnabled + + property real distance: 0.0 + onCentroidChanged: { + panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)) + } + + onActiveChanged: { + if (active) { + startPan(Qt.vector2d(centroid.position.x, centroid.position.y)) + distance = root.camera.z + } else { + endPan() + distance = 0.0 + } + } + onScaleChanged: { + + camera.z = distance * (1 / scale) + camera.z = Math.min(Math.max(camera.z, minimumZoom), maximumZoom) + } + } + + TapHandler { + onTapped: root.forceActiveFocus() + } + + WheelHandler { + id: wheelHandler + orientation: Qt.Vertical + target: null + enabled: mouseEnabled + onWheel: event => { + let delta = -event.angleDelta.y * 0.01; + camera.z += camera.z * 0.1 * delta + camera.z = Math.min(Math.max(camera.z, minimumZoom), maximumZoom) + } + } + + function mousePressed(newPos) { + root.forceActiveFocus() + status.currentPos = newPos + status.lastPos = newPos + status.useMouse = true; + } + + function mouseReleased(newPos) { + status.useMouse = false; + } + + function mouseMoved(newPos: vector2d) { + status.currentPos = newPos; + } + + function startPan(pos: vector2d) { + status.isPanning = true; + status.currentPanPos = pos; + status.lastPanPos = pos; + } + + function endPan() { + status.isPanning = false; + } + + function panEvent(newPos: vector2d) { + status.currentPanPos = newPos; + } + + FrameAnimation { + id: updateTimer + running: root.inputsNeedProcessing + onTriggered: status.processInput(frameTime * 100) + } + + QtObject { + id: status + + property bool useMouse: false + property bool isPanning: false + + property vector2d lastPos: Qt.vector2d(0, 0) + property vector2d lastPanPos: Qt.vector2d(0, 0) + property vector2d currentPos: Qt.vector2d(0, 0) + property vector2d currentPanPos: Qt.vector2d(0, 0) + + property real rotateAlongZ: 0 + property real rotateAlongXY: 50.0 + + function processInput(frameDelta) { + if (useMouse) { + // Get the delta + var delta = Qt.vector2d(lastPos.x - currentPos.x, + lastPos.y - currentPos.y); + + var rotateX = delta.x * xSpeed * frameDelta + if ( xInvert ) + rotateX = -rotateX + rotateAlongZ += rotateX; + let rotateAlongZRad = rotateAlongZ * (Math.PI / 180.) + + origin.rotate(rotateX, Qt.vector3d(0.0, 0.0, -1.0), Node.SceneSpace) + + var rotateY = delta.y * -ySpeed * frameDelta + if ( yInvert ) + rotateY = -rotateY; + + let preRotateAlongXY = rotateAlongXY + rotateY + if ( preRotateAlongXY <= maximumTilt && preRotateAlongXY >= minimumTilt ) + { + rotateAlongXY = preRotateAlongXY + origin.rotate(rotateY, Qt.vector3d(Math.cos(rotateAlongZRad), Math.sin(-rotateAlongZRad), 0.0), Node.SceneSpace) + } + + lastPos = currentPos; + } + + if (isPanning) { + let delta = currentPanPos.minus(lastPanPos); + delta.x = -delta.x + + delta.x = (delta.x / root.width) * camera.z * frameDelta + delta.y = (delta.y / root.height) * camera.z * frameDelta + + let velocity = Qt.vector3d(0, 0, 0) + // X Movement + let xDirection = origin.right + velocity = velocity.plus(Qt.vector3d(xDirection.x * delta.x, + xDirection.y * delta.x, + xDirection.z * delta.x)); + // Z Movement + let zDirection = origin.right.crossProduct(Qt.vector3d(0.0, 0.0, -1.0)) + velocity = velocity.plus(Qt.vector3d(zDirection.x * delta.y, + zDirection.y * delta.y, + zDirection.z * delta.y)); + + origin.position = origin.position.plus(velocity) + + lastPanPos = currentPanPos + } + } + } + +} diff --git a/examples/demos/osmbuildings/OSMBuildings/customshadertiles.frag b/examples/demos/osmbuildings/OSMBuildings/customshadertiles.frag new file mode 100644 index 00000000..67c32bf2 --- /dev/null +++ b/examples/demos/osmbuildings/OSMBuildings/customshadertiles.frag @@ -0,0 +1,10 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +void MAIN() { + vec2 tc = UV0; + BASE_COLOR = vec4( texture(tileTexture, vec2(tc.x, 1.0 - tc.y )).xyz, 1.0 ); + ROUGHNESS = 0.3; + METALNESS = 0.0; + FRESNEL_POWER = 1.0; +} diff --git a/examples/demos/osmbuildings/OSMBuildings/qmldir b/examples/demos/osmbuildings/OSMBuildings/qmldir new file mode 100644 index 00000000..02ff1ac9 --- /dev/null +++ b/examples/demos/osmbuildings/OSMBuildings/qmldir @@ -0,0 +1,3 @@ +module OSMBuildings +Main 1.0 Main.qml +OSMCameraController 1.0 OSMCameraController.qml diff --git a/examples/demos/osmbuildings/doc/osmbuildings.rst b/examples/demos/osmbuildings/doc/osmbuildings.rst new file mode 100644 index 00000000..487eb1d8 --- /dev/null +++ b/examples/demos/osmbuildings/doc/osmbuildings.rst @@ -0,0 +1,53 @@ +OSM Buildings +============= + + +This application shows a map obtained from OpenStreetMap (OSM) servers or a +locally limited data set when the server is unavailable using +:mod:`Qt Quick 3D `. + +It is a subset of the equivalent C++ demo, which in addition displays +buildings. This functionality requires a special license key, though. + +Queue handling +-------------- + +The application uses a queue to handle concurrent requests to boost up the +loading process of maps and building data. + +Fetching and parsing data +------------------------- + +A custom request handler class is implemented for fetching the data from the +OSM map servers. + +The downloaded ``PNG`` data is sent to a custom ``QQuick3DTextureData`` item to +convert the ``PNG`` format to a texture for map tiles. + +The application uses camera position, orientation, zoom level, and tilt to find +the nearest tiles in the view. + +Controls +-------- + +When you run the application, use the following controls for navigation. + ++---------+---------------------------+---------+ +| | Windows | Android | ++---------+---------------------------+---------+ +| Pan | Left mouse button + drag | Drag | ++---------+---------------------------+---------+ +| Zoom | Mouse wheel | Pinch | ++---------+---------------------------+---------+ +| Rotate | Right mouse button + drag | n/a | ++---------+---------------------------+---------+ + +Rendering +--------- + +Every chunk of the map tile consists of a QML model (the 3D geometry) and a +custom material which uses a rectangle as a base to render the tilemap texture. + +.. image:: osmbuildings.webp + :align: center + :alt: OSM Buildings Demo diff --git a/examples/demos/osmbuildings/doc/osmbuildings.webp b/examples/demos/osmbuildings/doc/osmbuildings.webp new file mode 100644 index 0000000000000000000000000000000000000000..e9cfc82dc6d08cc51ad0b9982a96dd26ba6426f6 GIT binary patch literal 100144 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ΝR}aN<_y1-mYWt^x2J?QJIto5=G*{mT&vjvx zj7p@9To7yJoxq^@;Lv)D$B4yGIo~&o3yU_7C)>%m4FMFe&O>qRY5*A*4#Bn*oato9 zm{x1CT`^w7SGjzw9>b4=R{zKBoMQ+P8QNN|)iO#afZOp580mlT0%eXmSItu#WS(t5 zT#z0qX~&ea>DsY`XN0#0)A+CF(;`+?qel;YAO*XrCeINN%tnaN4vBo6@f)3;6Ia6h zoEc}n>|n;m@q({MGs3WDtW02J;TWKOJ&Sr6stq0n^BIR z5^1N1E^2eamrYbqTh6oHnW1g=*eoEr8+I#dW=P1|t%Y@vceoZhKmteR`Je5!AXcITN z!DXe=ZYHPKrp8`Q**VWm^?Beqp%wirHLCDQa?a1ok0y-73W#RomG3G&&EeA7I%>L| zg%~rdPIM)PXU-d^y#qwyi>r$dw2?~1qRdpfhR1$pgo4b5AcY+|wrZH63kwHsB{%X; zQHZItM@!}(g1t7tLJ z@r&AdMkiOrgYn6HqNuu&dHdz>5jJACo)o!=eeW!)Lil>?&TPKkeq<$wCX};poynlN zcU3~jYZG|b=H~VvsNQ};6#X^?^x`!dJn*6wv~F7 z3|69UXEsNE4UcU29ps+Oo$yo&iAw4Zcpu4->urML(%m0tFsL;6NX^r&8s*c^xh19Gy*#4>tbYHv$%J1{< zc`;i>&1R7y+Q$$3# z#wUfFkb>p}W%~)?AEU+j53FCL_aS*#vn~fwJKx>jo?!p9_k9iRYPD)IIL_Iv-P{H|ImQ}0s}Ks@rk4S`mjj#e>6imI zb9A%PP~oh1FrC^%UNJ<5l3b@Bk@CeSh0K=KZ;Cmkr;s=NHrwq?vzaZZE-m{E6@--b4P~0aA;bnFIvH1c zNTcLH; z4l`C-SACO=81I?#H%{pRYCvXJ z`VMs1f#Ife$}Z;zc%1a z$F1eK-|MZ0%-aDNEj8)@Dd7^zYbT0JfeI6(r!QUn1+Ac6;l>wjJ?N&RJN+=yqE-61sfPVx zHUGd6=qy-QObr3{O}JI^EYmrC8?1Q?tKq0kGPlIkFB zSgG~6>{R5krdA}y-cttqje7jDA39icR{rj}H%}Za4z!svxEN~76z?INr{pLeo*@L7 zK%K*_kxo%2Ner25s#rE_RPEMOdQk)q2?UUKU235V40D~N;8-7h>nfgT9M-uC3@OhR z4H`IAx1BIl8whzfAH*kJ`Qi+=&}e|%+L90y0w`O^Qh+flX4I0glTt6!2>9~sDWy@tmpCVi zo*hq)O_#0e<31mUR+|kt2EDs!qVT)zc=WL^tL?eH+t0Cr=t^Hbi;LqJU$0b9OsftW4UuwS6|{YkstK-HIExXDR6B`#&_Vl!!j;Ch(NS*T%2amoTv)|DOE- zc|pL=gsO~1Baq^x8o=9Svm8u_#YA^+GU%rMqm(_)!3iS7ahK;2Fn;>Jp7|KD{g^vq zqnI@k+-f$d#Ra3((Q~59Dghu)PnN9-whw20Jo!E+#1AoYyda=jw9mgwpwQ|*hjyYC zqqgC45Hyg*mWEXp;V+#X|9x1gg(Kan@25=pX4Pa&I!n2Nm5{c(Q)Q zu_`T@GHYzkXUN|mB~3YB)D0WRxo5bag!5Nug>HoaivO^r2VuY5j{&Ik!G6RNH1b@5 zU<3`-O0iJ;r2jcrx6N*sSLqC&hGzukWM+H4zk*Ayw|}<$5=@BOrNR;~lud3)J@+A6vsRYEAP21Otd-sEI^M-$JZQ ze|~*NAZ!YcxJnKSxlI8-vq0hQ1MIUm*_cHhT3N?5tnWLQyP-A(te+i@KRlOJcM4m} zj~UPT?&}H&xG5=U0%>#Y5!i#}%iLov;et`kfwkkt@&Q7yn%yt)%&R( zZqBHsfNnq#Yg2lXh!nZOw zxnBKH{~78tDg?)?nG<0BDvZm!oCHZ|8=z;_> zW!B#55q|quG(jyn4aG7A>~=iY`W19qM8TcloLhAd zT}CWClpt6Bixwy!{n*XQy&@6+VxTcY5oD*ho;rLnnY@L;=_L>MAfM6o?83(h$R_Q? zi>VvL(1$pqJ>cm1lEULYgMDq0M^`1_ZNA+~4WzeDp+UDWd_=?fx?xJVNTpTzOH>|~ z5j3AKc~A?F{-$nguHfkn3IHbpp)HYv#Hpx0tNw}5-$Sn^Dc`$vWCO03im_70$rYEP zSUD*1W$BD4Au}XSUL9Q!IHANH8 z^3J{Q)J@^i7xrS`7E0ZI(tVw{L(;EKY$z7n5nZNL;y>EfgNdZf7o|#Ii1;=F{FeDp zbAbUXeU}1tQ`QY+s#C)HM>`f7o#YxM$rh4$BXE<&YB=K@SHyIc_ z)oOm+&?@+WF0*M;cHkWrd0@1`UY2zH!vnd0W$;kG{fhRSGHVD+!o`giSCrpW4U|--d+DZuHM=F8Fa>&I2$ji^lPZ&!L_{Q?iaj&;H=!v zx=zBWB#a=;sR8ktKZAV8lckDBoeI_qQuu3Kp2o?<-kjm4B)K>aQ!i2CesHSkP<0@? zuOEa3S8h=X9p>fIPwVZH9iE6##-q*35!!&OoR0wve2pHjvdF2g+q zewMab8f`F;ZY!81fLy8J5op9y?5`e-#WE`X+04tRp#hQKLRR*-7lbbAeUzT;2Vkt@Vs2u1!SI9qG? zj(hBa+O|b4IV4+Ozlf_Tc#Z#wYVMa#wi_>(RvFEl?=(S`Q5c*pJl_hEoc83IzaCUG zLhs1(PSZH(&~aX^21^nx`E~{pTJ*R@NJk-WEdm|krEiTy7@4B@r7oxU5C%7U3xr!< z-=J>J(n{oo+p)M9CWm`qM`FXCMVRG>qY@aJ@7mY?pbY@BL{>Vk3@ijB9^Zu<%U1gK z@ZfWdUqx$zpTLv=Ow8lI=@nw{pSd)L-}>Kkn4BHCQk`s+`%+_OHtnL!Z+W%PR&r!$ z*QT?wa)=%K?v@8B1#_6-RRe$O$mKnT*b~Q7afMV2qq; z`ae;s33y4+m*UV^HNI7YLkb+WGa(-CUA^P%gK#1kCTE{OJ*;5#yadE#Joc2YT|hub zs9jcIRYLF!f6uX}Q(EAt+)y3SuZVu64aT(Kb@)34HYSfUPRLS?cv=4$h!l6YQoXkK zMMJX7%v57|N>}SP@4bFamL3y?YA#AJyOSD(aY*cgMyZ<~UL9bkSxW#m&gJ>NK%t#@ z$6q<)0K|{@fyh$zg98@b4RuW&;lbH_i31jLnBu&9>~Mdun)oZJZ@w~<_AMkw06$h8 zbuTW708JjtGL73%clcUCb!dd5G!9a>t4qx1$S0yq@7rsVThpUDo)Zcsiemcm}1J+Y5b=(CiyTDQ_Q# zdR%cXUX8Be8S*?R_vGRLLqNR0)p4-#O6zF^Jcd+guOP`g#x^>21XLP)c2%FZceK8z z8T|{1Jw48aQq={X4i51GgHY8TbxsxoZYJ5BjLHHD7Me5DH-3V$;l19DBCSk7=~5)y z&u2CirQS|G7TFONx3-L$2Wk-mK9u~Z%qz%u! ze48IP&4V&{q{Ii@OBrY`yA%w^DH;}*edf_V={QKJsu80zxtr>Ly|{Er{Sx0_Yri^wgB z6vdgjxdafYDxJzn8I*+Hzf$k~G8eO5A+w?=(LSo&g6Aect`ZpoaqF?CARk9zkoFPA0hcKUn76_uybci%Nmim*8^@@MM4&TJp?1%M*=&nzhMy4;ae!;;TSAAbxey(Zf@#wf#GZpf-BS zP#0691{iu%E5wy?Pny|9R?NVF4P9MFuD4XbRuB_WkMw|*BeQMv{Xow9-=l|G#x8!x zq|^OdEW=vG`CI4h-zReH_f7mg$~A~(qFA%D***k_V>(TVHhTA){S?cC+|-f86Kkfg zkR@_x#;K<=|Ja&rE7G%dsD-wB^(iV`&tEp6qSNG-=PN-hs}onE6~7a~%6Al~eAh@+ zSJb2g?YXeH6=G;xXE%3P&P)NVK!SAlb+0}Xm7CJCg!9&gkI`W;5~9SMKIO zm?#>bc{wXrI)PGr>d|kWC-y5?4!#G`S;jK{I4|47yV{-S)@jP_Fj2>FYn=~D7TE4| zuANtyW1&DwRrTW#v$<=>&1oN`6NVZC$gN(A=-JvJb~!6~Oh zyMsU5CL@WRE~jE6oj;Ji|C2Jo04NH{DyvJuc|FNtb=2u)w(-94J;x*duq(Fz4Ko&v zi?feS0sjGq#sXIm?6}5KhU!~oqN>*ucKSBs=Vi3Ex3dc^`k-SOW6mB^SZKA#xQgrAP9?S;^p;`6RCVfYa!V;V@G|si$cN_eVCa6bpD`J#x&c0Q zpdy0TRa(~nB68oV;D2$0blhz%QxM!??_LlH@-wK^k9{V_Hb+*i2SKJ+d!IGO z`w}n_TiH0rkS3AEa;nz$Em^Ph8+Mrc+MA78HofZ*Nub$>+eC7zedYh+laweY2_wQ- zu|6?&{^5_HF`E>X$e|tlfFJyWeXwh%YPRAJCLJhPYYMlu2j0NRe2+K!_k=5}sp(Yd z^_Z_2O6H&&bvcbyLfgLJ3JNcnEmT3LoY(W^LB^$?}GyEKlRkJrUsO! z^5yMWxhp2Y<~ihLtOh?YvbUcD1AHQ@Lo}GS7Aj|r8 zD)}a!gE6zD5bWtAbxenMF<~_1nT^6U5)r?;7w%yiHe79TOZYO)H!L0JE@=Q&!W^$# zlh?A+o$|J+7@!Eq-$#_iM^6Oh5J;H82KR_UZmE&&^E@>vHd~otD&|+~X+o4GW=P4} z-Km4gGR!?-Bmlt6+PpKqJ}@qEps+%cL@Iu_ReD*du@J= z3bYJ>0Zxe#F+c&GH%6X>f>^N8qM7EWZN?g$j|yuE^+=$5>->d_Ud+wrIlta5o^S}0 zuW363uT_CFS5S>rJF70&dU>HXXl-Cg(Lm%oz0Q%9JJYc|uzSRYaRbZ4FKI_g8kC?%{BR)qbv^$uFe zCKa}o0&&W9X&?+%Q>+X6>Wwy14Upd5D%Creh^ryUt`ItWBVX7^3~}XSneO9fQG`+v z^7lJTjeCR-uEu0KQGUe5i1NMVG5I-r!>~|~P&zTkHF$Kd(UPX{OBF<-p}KJefyMly z7W9Eg0X8y{4UNWrB*7Z8d3h-pTqb+b+_WDQwwA9SR0g!63XQ6>r+`fWh(G&EhU&`k zX{87h>>F0$LWP?ZmtS8Qr38or*7^3Vz-DEPyEggd_rDdnWU+w@2l*A0P|gUDbtmU- zjioAFZ$1&?$&*j4FiVgEX@ZOtnvn>RO)P;M?T-7WBBDo8gc7T1r{}K0M6cwSbJ7$u25Z zR1da#rIA*QjFVKI^SFJs04DG~7y;Q&pghu3LsEY14KZf+`kh?3OX4;Ac4J3hGUtyr z;US@Tkpe13{oRE!Ma?%e)nPF-TIRM2&+1uWEv>j#fQYEf~`Xr+v zAX|E%!tWJj9$N_s&rbSVLr-q>{)k@jTFGFax8n)Qf{|@zbCxAFQH^_0?fTE88B4K8%IuhnzH7v*2n%i?@UC251e@yd2A<^dP^mS+ zU|+UteWT;EV>XB&xNTNHWUdBD$$aVWU{(B~9r>OTsjOd$KyPW8oR z5_?{}X7QkLBg2EAhKE;$iy_k%)v~(9`#jNEr*ey)z@SSnXSIdBmD2>s<4A8638FnZ zd4SU3ZW{a{$CtTkdl}fc%X)+>kb#|>(7$UDzDM#tc1FC~I`E}8etGiHodgrY{XAYg z{tA&Vd|Nc^UM)Ri%8ycTy69F=vE5A%FDU5Ih>;lvs^kLWWIW|&ib!DdSw1WelnQxv{4{p4d}viz z3UGt+`o&USil@OVRsSUxwie}AM<;RJHSY0o>HsMSsBheB$|EPv~_tjuolG^IkSI(Z8r*2%x3- z&g~4U?rQ01>?2`_cANrkMDe ztnCF4U2k^9hA$c1Eg$awSKdkzZ+}UrsX33jow{B+>ZM0m2#CGv1xFhn7L%uCUXT7Nrhnq0P}0pqr@F=N?l#3^3-)6f79l$} z&cpOL>+IdmX^Il?>S!hMCU^_&E=jp1uGPIDSdCSDn<@DUXgx(#wW8C_jFcRgC%L#X z$>J@ahn4VH&j0Xq2>s)I5-LOh>`u^5R*J|L`|RusR01(t-hp@EYo@#vf$gw?=DIeL zw<{7lqLJ!jqq6Na6ZF27aQUi-b1Ob`G7~~(DWyh(qwN3?0&?AlfNEX{!9iCg$T^p zwa=>5V3=+a9>~jLr-P@QDe{{b&G8ro7dPIWFv71R3J1TMT}iD5PoUW^sB0Luqh9?Q zqSZ{W3=hhp2q)_2+Eyn7eC6Tzg2Rn&aRb9{6*!)QguEZ~G~cG2;$eTBx;$J9G&$id zHLL2meXifu{mXVNSuU29(28%DW$Sk5k2NJ@VqUK(LzYD)!i-*gsx$I4GMAVtgFG?7 zvavBU9T@OX3t}YyUIk4uGP-Z%L^AE^_i3@U>F;c;du}7Dv#CYsx;#_kSit0ECyZ+$ z*-|WFK^Du)p_x?tHQ@!fnvVx{3D6;C8HlBZI#6ZK4K=qE&!TCyd^G$@OBQOH9R1Nu z<8CnZ67}l~wOBXEU8x3i5D*u&=4D^+Iq`C6J>(V1vDe7~4X$QvxZU7Ki%f`IpCZ5DuoJt&$O=V@38Z_?gU+rNd9co1jM$gn|2mu#rkcESUst;f3miSo z=E;l3P*(!(BBVq%gJ&==7vGS8g*=Y#eAnM!o*b1Rpl{^p5+>gb>!ws$oC$JK_)%FAsFtXa` zk@Q4_c^oQHhNL@R`W%E`i|Q1agwgwA0`p*&WIF{4!&SW2MS?u}8@zs7DbrtLfNp~y2Bre-2Z0VXv3d^(qwce2b=UW$ zsS1j|asWrQN*&jZ@2``As~dF7am4QL7Bk=pjKVv|L22m4L%;zAOmJTPupsteJcUB< z*)dHDcE=pc#HK_Onfp_=?&h0ny7*;nUjomDQD;6BI<+2mtc_iH!V+W7Yv-@Gj;s?1 z4Dm-$gNs$(N!=#5I&(B$7QfkfU;5Tho%eBj)Q7k-q%4lU{kfgJLh`7sd55f8O>EfM zJNhFBW3RL!2mSldo+KC%Hn~-2TXY#UwFLz_Ui1j4q8IKjX#_Iq`XfIrI;Eh3vZ0WC zT^86cs4ldwb5Ae>n4m|>0}~5Do8v@YDFYcl#;}?WW@za~{-y%lW|T~nl0<@Qq%yXb zJg-gBnN~~-X||1<&y`?w%T4rCi_n_vvKLJpCpMT*;U}f`I&M!8F-+d0M~xf^?|J-Y zhM|o2))eJ-&!wWE&ITjc)+r#H*nZ=Kou)6SayXD;zhrJ_Rn2Er z{T~R6&~%TM2apPGzBC(1f(znr*(9!gz1(UY1jlLMEVsSd*pD!M;dIXMS9$O6)STNL z)osb{LHM$&5N&Mz@94r5NWQ^m1EnP)yVhe*JoU3BvD9-g2xDbDA>*n>ecGw~vc@jr z?2Aa4ZKNsLY1&AU${<8*>1WPZ1Y-}D7ikQy-ubKDl$ta|(_|`)SF##=b>ILGk7me+ zCKZ}OTk~Wv`^BH9pw0*@p*u_A7xieXE_7hAz@It^eh27aKPPjtfb19Qb9#@ z;AGLarsUE`QV5%`6Iv&a6VLYMTDifQPwsrdqhjY*?_Fy-fJn%qh)W-+gh*$yfLhBX zEI9YI5Me*BHRG^@FHE$HO4ME^Fm~#)Bx~JcMT1^fQ%MF~MG(*i2$yXNwPNY_=yGJ9GZ$!En5_85@rWeVtI~XIBTmBv7F5q4*3|*Rll6C z5qR)k?u#W^F; zVAxYye*~|0`8)@A7hw}X6#U;G+FZ9-w!Vj?FOp0c4n3dr60XzprnY<`|m=&{6NCy0El(Zk^1<$(c zqB=y4IhFR1vdm@B0u_x{&WGbuu{+QS!^vfQGX_Tkk!pa!m3Bw#))+FlSw42ZXJW|4 zABDbilv?``jeS&eyp#9MiC)#p3gXL4D@^bOo6gD2vD;QwQq(ZcyFw%UL&%O6c}nSj zf?8lK5h_O|`HI&=I8v!AX>h)>~-SIjI)xKh1uL^^G;Ytn_)0W#AM14-Yxtc z6(fNe(wn6)vG-`swQvI}984Th!n`keYf_KM##k80pvD zl#f)l7AC-%G}yPRcCa9Q1Yq?QuVJNJK_x}0M9p%K?_`y;z+F}BFnT_Jk+6eXcVAO4 zg%-BTxEdp=#P{YEdhdj~zk#b3-xHbbCP?HQ% z=yql?k22{wni1r=9Vkf%1NkvKLG3I3M;3F%K{5V9k!AA)bL-a(%9}wa#=J;0PTx&) zLAS^6SIXiq(uG2!eR=UH*%#=a0!rMf+-d!lOXhG1tK@SEAQGcqG}y!y?vjHCo!2M3 zm|L%9`=w_^)bRY|_=H4yU;h5Lr8&c1sh^094nP>u4afNRmWf#nfZV+IxLqiTwl9V< zzYGEb+=L&8L!0TN-q_@ea(=ArX3>+JnNDF1vO*AW^S_A z19Fd1)d0W+SVofoO=d>6B>pf~1%Fjc`sH(!M^DUXb<>=EF{ZbZw4S8eHG?!&=5h6P z+jA3{-SB@qGwtbX{0bijM}+88k+Ui)a8d*ce@KFmbr*h$m_MuKNU)M;#=mTL;suWco#Y*`~+nTBPlr2>E^V zhr(`delI2!T-ohA4An3qPr$0&x1R<{1;O~bwecb{0#6>gBQ>6l3v03_A%!X&06yCklV+S^)rN zb5`TAUyuDU&T01jGv|L3w$@ZzrQv^8m#@u0G^m7T;xa?mZ9Ob?x>6d3j{o{=QowSX zbFMgPDu&PHrmCjZmx_s$vdj&oqA%yrHL-Kx!tERX_yw-H@$+&qjF|^l}n0X9?-|+vg#JQFiA~T72-`3Ho%M+WEt;gAWY0^ zk5OW<#JUnxGGJW#gk+m6A{cwa|1&px)dsy2#wG_25y#QsS217?-s1D@s+*!aNU zzj((ko+d~7#J|^6ZsYex+R)mLap>ii+(_lM%NF5vFMwUtdzI(N&ZANaB@5SksP6J} z7ya-GyUbP}_^qkd4ZQq$s6VXJ{R<&tuq(mGYNCsNXk-{u8$oZiHYoqrO07xMA}HBQ z5Lk&kl_u7jj_*0eUj&V18gn>AP1$b&Go$#o3anA|Aj+L@I=8g&`;l5Qk3!3F*`bqS z6y-`zd4z5DWj)@H{c1o!3(TUC=E=Sjf8S)rk2RQaB*5Q4XS-gFO>XTnr86q1?zYy} z$rBd262dgnTqQY1mG5F$4lWr1i*!mQ@$^(IHZAGkO?E`c=m>z|va*L%FQB_}CrPjZ zO}AbakQH5ZL)V`^YSblUe>IOz&KPX?Hj*F9StajF(4&9mv#m~PR4jWr^}a6FY?l-q zTrh=>XKx?HxRHVo5aE}bMdWw=IHQ4O zbK0Xb)zS4%+-i%sWMnl0N;W4;MpF<#2;B_`d{*p|e2T!MRf;s6Nq4hp^8>(Q*X!L? z0()JnA6-?Crp&e{oa-WGr-bf4F*rnuTHj6mEj@Wx%tpE(vgc81$Ul$gE}NU4aa$#x zV4hQA>ad@UIIR_6XEG(n@n|oBlalVWnh?s&=%f63NATQaoBwE347%MsvQ*tGs~;Sj z3x{6CcHCJoVwXNUl~1E7Y_93~6~*S40XN#kL&gzG2x?#NgS=O%AJ|vGgk7mP`Oh)5 zlI2Q@sFWD)~3Gm)SQlXHtoekED|dnvj$ z2DJNE;R4Qfwl%7P|Dj?t9$Lz;a8#z7P$Jt_p7UhE&@A*jByCA=D+MnUHhG{WT*N3< zT06@j9m;tIMpx!KZ}9DR9#`PpvQ_u0UX9W!t(qj*e%l zsW@Ur7`4SllwGms2k5t2uJ|(Khp&BNdxD?dW#oxN8p%EXOuzza39@=(9+9$rLAWjv z6hzJ_tYLl2Ejk+T>hJd42^BKJr^i!3K1g2%Q@kKt7%(PdFv>j54#LpjK}6_x+DCY5 z=@#(VcyOVtqq<@z>EwTv84n5vucC)q#O7`iCslNVsS{^1`eIQITK@PDgZZOEf2*RZ zJ%{#_T;F*eG*ios{a1H7;Y9^RT~y|17p7=DzyeuieA?DSx?j9J4gA??p=di6x!K=KhjL4AYxEfP*4X zePFS2c_o82)4nr_2ek(|v&0iZfLwC-HeFJuRF_`3_~>h_H?9R8fJ5B{={A(^w7h_uwE( z-?;9-nHD*A{(rgq48<=sLGS`;BEP8q(Cm1LpG50`9PbldEH(BB+9l<#IaY z-%pT=g*&Eimu?5)L^|aC)bYD2aF-RbNfP~Cr+v4$;deW62l>RJ@1sLwF(?tHj~Gg> za2oseVsWP{d)0`eWPYDmMIo(@GepFnQ~S+a zT^}?u^&w8A4DNDID^5iiE%=Hf092b;u{xP;j%d|tN)5f1@kRT|HIW9W@EeC--LofMF+b)1=I9ALv4Tl2d z?hB9zjl=I%PsrcllLcBYk<5*XKS!!a>(kDxkUO^R$YD!6YY0C3dnGzoq(QptBw8%{ zIYtx)-beh=7``>_uI>b|?Es}KIqSxY0f2kcxD;}YdE9h3Gw}kb`zm|M&iIGGjQuw{ zJXzV-WfE?X%k0A!>KGF13q!JxZZVZ6xwCe<9r!sc-%sJ8-tS|yN~E1C8U%EG)>^>X42XaJa(APMxgcscuNH1wi0!^|wDhk;@u#FBi? z&rBi#*trIApbnq3WA!jH!XfwH(?A}F5{we}@4ZSq1-1AyPRxEXySeouelkt!O%zzW%q z&Gr$1QdFP85dw-vUjsVTbw*Z;X=+*VrBjx;!9dRw9QKWzW^M)bA9);^Jy>+>9p&pm zmy`P}^7QE}1xHoZNDKl<=Z9DSjdowdX|Ag%ZbFcPFHVD50%w_Y+DoTm?j**RL`_0D z#PKd-s=woq5jr!w| zgimW|z7Qv-EumaX57RS>Q=6*!{;lS0EX=}wha1ly%YS)%DnTUyGC0I>>hJGKMLzQ(i%Ral!XyvRGQJzfAVZfCq(6R;e3EnOQo`k`gt;N4zWfz{ zFulOJ5vV`L8)I7(HX8~hNRpO4Ybx3dG+kiBnRs^>s2E$0$XELyg2r?IyH~GQ?&e7p z)GNZ((>J^73jhQYYyI0Iqv?IWnt&Gj?yT z*QAa;!t#rlzQ5VYxUjHQ_z8KHHxd@@Aa0NG5jy$_8MYKucgu4p$^G;QMbb`gr=$Q?6NY6#c?1%RDL`|?e@5aN@4RibZ z5TO|OfqKXL1>*F$mW0cKYwEY3f$~d=ZdxkQmD-?$B>DRArZtmr-nWPque>s1bg|Y| z$AK5(4f`m)T17B|fh}Uo;dW3#**X66rtPd>>T8a!{nctjkbv*gxiU$w6HYT72f;<>U0GN64 zMJI-5X6CuO?^4GD!V0ms29LWAN|)TjBr6j6Q)7HF6o25aD8;+Y+yS3>Nodk8Ct=o- zRaZ8^2EcL}pPz}WvRX{;fKCc^xm%)a*}#`RtYQJkmgY^@;%Y7NBDWz)^^Jvcf7QWf zNH9xhAwpg|LH%_sPKGMIelX3luS*+@VPP7MMjH-gYGuB)@`IkC9R+@$sI+8D{nK)x zAsz5d7}sP~enSec#JDoN3Y=fup?m&jWT7!#=P|tCyMRQZrG~7$MB4f>8IAemrO0;r`>;>q~b*Lw+^uGEbxK-qUi@j}(kxSU8L;3fJO5tg; z>y+{*(%Gv6aPiss;!#9X7izg>bp!8Xd?EQ|6S74(#sJub>`~Z2ktUzMqO(oO(EZ@z zr1s>6b|=E=n6 zc5ht1o(!C2u!z`THJy7hAiqd417VbkX*q)={L}CgI99U1Ov-dt){PA6d^DdV{X37e z5Fd%MagoU&?4Dkunh0$N5m=6VORgocg-tDkF49uz#WaQz?-`w-m(C2p6f1Jau_5Ef z^ee;MsU^tAySpSeI1InM6KcCeW?5S#xHH9t=j3$vZT1Dh;8f$z$cUn0m~miKp~h!F z6KIMJD-elA{utcBR0>rQ5z?pK7Jds8(|aiHkv;GkkFnJ7I)Y^h9-@>B5cagr9aWF- zk5P*#M1LBL`Mi{BRloSrKKVIOtUap`oJK*A{|8v9Q47_L-7jNh%2A!APrClh>aa@V zI$Mxh^5iwQ-(J31VNGwryuEDr#_T$cMkN-ib{Zf;B=@FGk24_P!VN@DfpgjIK(H=_DNx z88`|&oGZwV96XLx%Km*_(ple&k!PpUcSbx-ciMeesRW&XH;mOr0o;G?<4U_c2Gv)V zaX|QVukqwvJMKxUL#MysS1w)XgB`Ifxs3hjMMJCqNH&s|Gm(Bf8>#9e&BYJKv>e~wI_g+UryY8%5olYbt+vW$2v<#l^q@SD zh{e4PU-VQ3w7~u`eUY1eA#0E3Jkfh_!<6CbXpf-*w`f5#=uma1ci5+O^OX#hL&iv= zH#tv*^^&d3)UNE-1J3Ujwo;MF1WDmrxvohnW1$yNjnab5hWLX(8wbBJ3kFPRQ8Bqx zsem%N+}XVp+hPI?k{*GjeefK2dmykQB|XZS&o8B)qI!KM;(ong$F*ARlDIJ1SoNVS z^Ip}un>aFddmPlw%_B`j4cRi&&l;eS#Mc_4J9Jl6gsn6O9YSDg(^=iSZw%d;Lz?qi zU5$V=eOzsXN}4sxi7y=E6v@F?GkX66MWo_;KMEvgs>cZG-{(XN$4x0+@x4z@CFL24ov`-1i>qET)uBS%~ zXYN1XeW}HshAP4D@r$}k=%HO}*KbN(`#83;6;iGmkoMz?S2JJacv(R2gFQ$&;rXFv zDg6Nt{dt0_?|0@4@{*p>8rms{5V(7ok^jPldFrOanwz#JsO2GGV+{=}ihiWsR(?lX z7|%l#7-yj7+p%`^l4S8YL}29Lpqr8m(mSue?7VRw3yJSKD$boSwA@w|o!3*$uiB%h zR~Z=pVKIbgYgmxm*9y%+)BF5lu7+)_m-2f)14zAg+Qy0~yrM^CF>FWM=_sqyjB$O< z@F0hq(Aro~ODoa#hC$I8Aa+-exA=j7|>^I9b zaU(S12oe+w>c3Rnh!&c%5SJrsFDed2EE4hYAG}b>Ssc$?n4xVhNKKN@4NPG6C)1AK zt(+y=A>CZ4@+`vvUb=m;IfbxBJ zq2*%@B)dHQI*&t>56Q(9{Qt8H~ zt8c?s$h9k-UiGg19nSDR$Lb=N(|0ePGMIAa@6OfUHT|iD6GxKD@yiw`a{S_=^M^By zD5Ka!I)lgBVlnNn8#zpTF zJ?BI2T=II$(c)&);L?Nm)1CGwhj!T51oVXElSdNJ7zeUh9TH;5MFGKS$bF>d&KxwZ zph`y8`ku@uWOPDJHiFtFDtj=x&`+(YJKg%?sSbpFBJ`FCo&A4B`4b1v?PdDiX#{Fe z>tEPOamgm(ut)h4t=p0I2olGx+_CP4g%8hfy-(rX(o&Dw+_PFxG35rbBLvMY8 zK9t$1huOZgUG-YLLrON{z$tS0l+*%ezHI?z5U1=Iw)S>Mviq-ruFLC~2Sby|L%1Cm!He!=uC15gnplglBuCvc0dbS5gX~uWXi-ud(n48HU4OD26(l9hY59Y}d-1;Dkn5kri+eDnf zG+ov_WS}BhJ&4?wd1?U!D~N*UmB!Es;~JCGGESpN!dEH6UG3Q}vv4^;IKYrh^`MCL z^AI^_hs&|i;bC`9PJ=QiD1;E(058ZHuA4eJM|C(MiRDYfrbv_^%58%AZnDn#ib=lA zH4_Lr(|t{q^cdoNg~!FUo8wRFnf$-A{f-Nx*iNm|z;`i0SOFIwEoJW36r8iF#c1~q zF!BLRK|U*Urg=xAvhF`CNQ7Am=czc~Xrf>Ej_MZ~LEm&JIurh&?&stMg(s=L$OW_) zzPVRGIRMh^0NuumH){;Ks>zQI=t2Z0>|G!orr1ft5Rk>cM{%d~)`85KQPuiwd*$H%W{? zecqB?duR9cpJD+&=*yDqM$E%i`dzO8*RT{MBwVAXF81h)pb!W}LHw1pHvoMEi zJ5MS!YJ;0Uw(ACP@19XoZbb^Z=6a4X0-s)NjlNk1*sT1=5=Pd&>y-Y6* z9&#!x(bD5pFszi~Tj(eeiMP+Wb;;#`Qxib?x}~13AO6;hF~hEIkeKbKFs-|ZX?e(r zjq2e+iTNgtnc?8*WTO%>}4qPq(}r-|uJ z_iI0*XU*~zTK@Q1k8(bPZ(K;uz(F7*Q8%_{7j%tWK4j293l2(^;jnmA&E{ND-yArN z7QsJ)Q<3XA0q%Fi$$jYC@K4tM$Prj1GtUayfcoP;XJb7qBb}nm6PrEwH_#j zzf6_|-lhV4xLpN5_6Q!cn4iS5vInh3&@#?^^lInRcENDSgNh-6DVJm^sBP#M{koA> zOyuBR?**gM<#4hR>b68#Qm@ggdfvJ?%WBxpGmO%_(S1{TKUkn)%Kdr2-D`4}`~~LL z6Xml3Y7D-16vBeo3OwC9mzL(l*#ZYU9%c!$z9}2uiLrYuwf)EwN0}>j7d{UPv#?(X zbfJIUQLKUjCuboHw*}$2vBOXJ&i|TxMpGk?<~B$(@!1yHTc#Ani1ET%W#kLM?Sa@6R^K?bDhsQ!5em47T9x8XN=cG+qZyE} zw<_;U_%0(H=#$+Exm(ytE=uERHaQzkpF&6&+^Pq-liBm< zqSg?s7j)~E_c-c!VXXmYx*HdiVFI+`*Py5gI2|WG)&9l5L4jdyKo<*m1D_a#%RacQ zUkf?^n zBr`Akx?$S-(o*L3&hj2&9gB-FUkSjo&=!#dEEK{U$tYw}Dm0mQq@-W2r9p&PQ8aYj z&D@N|NN^8F`Cn&uFEWG+Gd9o0x6)Mp}Ohd{{D}4W`^KD0W#5UShdJ@aC z2mhrMkm>X`rG&0De07z7{=^Q%X4m~--1f_?PfdX0vv;z@%92>eZmYIfEzi_l79W?N zXS5&FLzgK;QR}6~;oHH%ipvz+dE=XH_fOL@WN06oVOMgUJsVjXOb$t@YS^ubA1o4Vd{L*Yt0 zv=#lU5?kt=jV{3QyRxxss!YiXoOsQWSM2NB$IZF+^HU{_v8NGa&<`8w-o-b}*^}OH z8$Pvs)J0}z3cev!I~H&G^Dw8;ylCo*0sD8EV>R&qEdpR)-pCSSksE!RM^n}0@b@QG z0L4*(blD`Ik7A&vkR^Xf@0DA%J#OSD`w1zg_7%J?px)RpVMK*g76Fn5o-z*hP`?uC zAzj}m21oP;w-rZCcgb8$nIc?* zAS5HEJpG##IgF3e`d*Zahm$?#FOpr0{Hg7US2)c@^2goI4v-015l>F5YjI~p$wYyo zx=li%h}HcKoyKFH7NjWb^9EG7ObA=8P?ETQM{0s=&*-;Chr4iwn79oOFW&)YRSs%X z`HNBm<;;7Eiz#@9Q7&Ez`4bz%l#I#`Wrfpsxtj;edXWjy8H%M7+8QZFxe`%P%^^*4 zvf^4+$^I){zo<%dr&TI8df)6(cSZ{kut)4d0-@GN^?m;kBj`gQVSPZS+>!_S;JT1S z4T&cZoED>g;c?GxIh4&%fyROZX>EpC{?9X=LjA$Tn>hofz)Q_&tW-gZW!+KFq4nuB zWbc+fCwnT)J&=3kljhjYn+4?_)c8 zSB%wdJhZ=!SLJsBM^u-=b8x{b6GhBGe9u?;hFJcWjSmwRo0Tf*I5RJxlMjM;;XH<^ zGv+p`;?xgkE`prJ;1N*bL1%heir8;9%8~e0j~cww(PRJ0Hl+WPQIrC>ZUx_{?#x4W z?-#||PDVo2#y%KWkF6GCEgfwK>mTcKZzp&;o4NN|ZkYjf|j8SgpiSt$SHK`OWf&_YkWKaQ?bAPTEiz9PzpB_TqXkO}A z`3R+ZnY$&ErzMji%LxBer_{9Es*hqk3QzK)vWHfJ(5#ZcOeT+L$uU8lR>E1L<8)Mm#@3ESejM@v~{UTb$i#L53 zh8R(=NDk3l-BsF3AqDu!h?tfFIS#X`onMZRyLFVt8YhdxPN$(yGPUR}y~cgs-Z}Sq zZph(euf=Yf9RQ|S1xT{s$M@gdW0!-_lmkC+H%`YNU_j0Vk5p`RtdRO}Fio9fu2Khe zxeccE6PwOH(-@ZzeSWNkF#0Po3zS=K3v# zR1+6OufMI1%lZgqmv8UEKF07Ufmjs~9JKKvnR+LkY^Y`ulz^CEa5&+tKUQVw$3TgV z*l11o3TXo9ed*B$riCxzKRpQ%k(92Oij0<>->&bk&8V5dJDdh`6@v>l{l=5?N~#$0 z_NioBl7%-ee=t)+b<8wFGYW;;KBGG z<+3;^)5Sb5*zzO0c)dMI<%iBzwR5=D>SEL#VSjFE#%rliz%Nu82(4z$B&Y-XU|{W; zPc2<l)Sa#Sq1^q>Y?*t%>5~|?>*61#-`H4s$c1vT zj)kUIv?cVPsL$rAMSvQWL-DyQ`D8dD@J{9V>pmbR2JpZ4cf9JeN&RBAmdCq7x;q*j zXQ9{uMQF<4^Rmr446`t^3~~en2?gZ+5P+U}kS6&+COJ~HDKTBw(HUBih2VP@iJr8S zC&$?`^g&r&Ci&_~eo>g<&)AAqvks9T7m)kSv~MedEVb@9n=c*bKJ|R4n-yY8QF&~irw{^E^ke+c*WKXU zxLL4@AGOg0^KNfreApyn=nA9c*0oP?dPLON9asE=-69}t#EHhcF)F-buSn7UGO{@% z;Rkwg7o;e#_OwyaJt!p0NOmqO)5d-ITzJ;h?u?RU#&8=7$7rL2v@5=cFnyxKH&M87 zl@zEOLxR$@?oUqe^_W{I(@c)Ad5+w}tLrSwg2z~{mbCQb8e-PcfD|r_`w75sf@xgt zfwz)|+}BO#ja(npTRK2mE;D<}uQzvx*lV=ZVXaiabC~#5Ia6hQA@R5`4c+4UNq4@^ zrY{QJtnJ-5czysR<| zms2rCdSxXWPL!TXFO_!BygJI6WT8LTXy>r(liF`vqJOavwe1=S|b$8ry}$N>g}q>0uSTZXz!x)V=So zKORBouM+IYh)CFBY=|pIo<~OMMFox(+T4YVFywGj{oZakRkolk3wBtfR}mdY^;OUh z^=rS#L#Mu^l#GK!mp%5NOm-=QBc!$43U54-s(9EbjH)X@@d3dOn=qZ3%6#e+PJq=< z-;$@5;jkTsqi46>a$kn=U}931{Psi^wVhazy5GMEpf14ZyC=v|tgZzz(f0J%hx=73l9|v^pK)je8?p%Go|ueYsR=+oYclEWP-Tn7ZG^0uME8q-DJqUk{pVAoH5! z(jNp@_sH~ERb9{mRS7x<%hhf0IH||OF`PiT{**)C z6M^^8+Jp-ZW^04ZFHz4+E`mG)gnk#;BC3ng7T+sG*WJF%W9fGWh3*z`vaD{r|JojM?~Y>994*=j3Iz1TtDq4vQG6g`Y`RJt?jYFrXwq>{tnu#2-{e%#5b_w(lNgtfK>7A$fNmpWNYK-X-ry`hsO(im<+s^Jb zwBJG6a8|Kw*cXznjGJt09+tpH%;F%p#RBM#?S&Zm#$Sd45|rNd*8W0tmHpK9j~{5|Z&hv)lqQ zC&;#@46Ylxn0KodsA-^yBS`wWa2RTcm>|s~@jJZpdgX7yP)2Kkh zpsYudMOgio@S#g0-@b+?M}MQw>~zL&*x>;Mngeo0@vBO+pUg*-e7(b=Uhf(}ur=SxOYWpp$~nVt2UEP$mGKxL)* zd5cHecH)$;?Bx(_5Ifyxss*!mDc}3I^co!x^O*k#a3D+w`DoUwcYT9Lu!wayQMVqG z;@e=_1@koR!L3nwFp&PQfF<^SL_431^}PjD&hYY}w*HGMPZ+Zpab?#xH7^ub1!oE)h0IC}E>&?W zt{HC7cn=Jo3<2(E$imMKgF3c5qEXHpH7Nh_cO=nj6j zM(#ytdUMd?-F3XC9|u>REQL5p#FOOXW2ZpJ1UeZDl=hD0*kOtAu*H^Zyu~DSxgRXw z_m6ymns*A?Y18&{S3*c6qH$&|MEZ1V>bd2*uVgeG4c9fz-E z%)~F|0ZTkN5`V7#_Ne;<_}-my=pp^@PxIUA^*JW$cxGvBj{qE(e4*ROjReH<<_=r( zCwIz`1AAh!jVO5k!2q~p`G77v8?U9AmDk5$DsqEQzk`M1c)vJpY%p=IA6l#T86To{ zhwNp`D5ur#G$aDV}vh@);C7h_<_;Q+#*Tr-z(LK?*dM zh=Hs`i6NJX0h5l~#!PW$Pqi*#ixSQ&r6lPVbL`ZDC!68L03Z&2kY8>l0)IA5=Mf^_28!~crV4N|)1(~5@6KqZWwn;?^(`@c zqX;`_nFD?T%8W?2b6x{zV(Locc)U#Ao_))1>#8|xUVpb(8_YXm%#FKb2o-Q|W*NBC z8z5!p`;?AfJIefC*EnyiQyvvjb}UAwie@A-k3VfC^QGAH_#Wb}OIx^lHN}P11MRDE zrmFUlKa?8RtA}J<$)PN-WVEvR;g#*7%RV@WpNkZOB8DgzdPPuR&JFj>Z~8-^moN;W zmf5a2RinnU9F^QX2;iPPY)eY&u zeF;;uAvK0N91c!5-egD}ZOJ(gZOP=K)snT0>bxnn_5sH4m1@`&mG~9`>Dl6_9KctA zPgz7?FoeQ~Ztw%mS%|qP0O@UcTK?yZiB6^>?vATyW&*!ZZQ8=W%z@&RnL&SH(5nHA z;b&=G0rsqwB~%hm0pG3Loi}30Y=rIXe6s>KRea@#tR$yM!7qeRl*>8ZP(GQ>04Dz^ z7|;7qOc7&@Ow^SVN^4DI;baXhR7*WghmH;*6Du$%pmFy5%`*cNp9;36$n2H-oy!}O zTq@-Uqph2Z_&heiw*iht141o zl%_ce`r;SO!ez;a>{s?#HAg>VEd#jOXde zf41c<*hT!S%mAE>^Gl=sKY}NCt+3Ccn*P9DrXquPzOI5RMovDNLWE%9^oo7+_nj{~2Zcc&N1GB8)qq@hc zNq83C90l_*0J)FxWjRF$fD5Y=xny5rH-?dHdK=~uk+1?|x6T%s3bI@<9<#&_HiZ`z z*WZ3tmZ_A-$w!wRFDRKsv{ZZK@4IBC68Y>E#J>sa-FsJTNg8W(=TVb>J%-?q#tZ(#B!|?_N7K+|q}yCW)aKS<>c~LG z=gD#=B}SlIs$r^(OrB_fLS0zqnhWyUllf|y#S0d$AvpxBLEjWpM^p8@KW~khR(%sL zarpB*G_D!#PLVh4UugJ%ou;^}8fD$*F{_L8te5I4QVw+NeS;z3&yJ0?GEFAW6V+%D zF0sUv^Osd77L+sxD+oaP$76e*xn6(wqLl#1#O8k{VXTbk1(eEtJZRQ2Wpb}KL$e0& zzB!r*b+`jCfFgOt9YCy=VR{*E4>yl{Yd&a}xC(<6TgokOpN69VvffkUMC9?+soTUg zA^8w+>KKK{GTczkKufx%N?4ebL|QNIN%mIle&L_f*!IruAhovu_-75J+>x7xHp$bP zrh!@au*W67M)HuxYqu{8jVcp0zsgu9R#s^y7H)c%U4F?N%%5H~x*8q5Pzym?|9X1{ z78W_byBPmymGE}!uW7&+VZfe{WNUL>@LeQr}g1c4om$sx3-M1Z{% zg_#sWok}xSd~e-1rqnx(PdhCp^SJv5oaG0K$@N7l(2t$lqEw+_UxB?r6b4YxMxK{G zcYOP{d;!(4)cB4KHgZN`hOW!j8o)HJIY0GoEGm>)Tf_joYV@~GmeV|+!EGRC77B#c z?tTxy`z;MnFr&pGRRANKw#?wQ6Ll*9R4~)!+hg<(?E>~+4KC?nt6CLsbN4rD7HXCw z_RoFbqp|nLC49o2#Yp1l-x#%gF2@&Z45FD@SNu7uw#rD}xst)OT4l?{E&NXFz1n(x z^WcV<5p_p{^)tr-1LSZf-NU9W#y>8p%#)vdHT`pVK3N@Fvdvj29TE5P9q4X9!K4Is zdG_uDSqOiHuNUOrPaw4re$srb9^XA9)GKG;f0SClKkx=&UPMM@OiSZp>DV_($3-l{Lr7y#cnfZSOEOtJIePD z2ZK+2mj8QKv?D=_E3A|ttOpJxlh~-QO{^NW3-f*%Q(C*a1A^jBy*4^`9HR!px_k^k zrcRrb!vycg9A=yV&W4mkS_SFPSs|yW!pg=M=GI3(d-NFI@g;)hb5UpwHH7+}c+e-T zxfq%peP$&KLmbpBG{5Eqe^jXsXn^wrVPfQ6oU_d0Y=$EbQ)R`eU82fiwW-DI>q3Eq zDXu)K1<-ubwDMuRH%hKG`(NRhq{azioOkM~9YeR&l<=faT7h_2dVoAE3U-?bf!iI3`%%HE)VR>u)N1Ncoq_Pg1o&F}5?Z ze5@pIG9=dd*Wo7O>LptOwlAaytE3`Y#+O!P%5`7lSGd*@=l&4OpN=4oBvQ=Yt)+Uy zN4Dpu#E2AXynrkC>`d@$q|5(2RcGls_Sz}8obxq;Amjhn_*)GfJUl6@J*v)qx>D{T zr&h*X;}27`g;FWK7EAo>B*cwWK6xFA`%Kc#NAO{zDVG2rh2>UmPHErg&AX4s2UK|R zqlqZzpWG?~`P~&DmWyxz;UwPBpQDDzs7x@J;rZu~C?Lv%%jr4nr6zNQXnI@6$K9~m zHVQ*B%QjR$_&cq3laff_Kaoo7g=H3lJyOkfSD33<{`ee#}d5X z;rnY70|Y z61E*dC@4j25Vfid(;AyCjj;VQi{qz02gYDoAfZ{q0M$=Cn(vtNqpZawK@y<*Hx*?n)~%!fP&OHp55L?XKG@#xvUoC^slu5vFY3u9 zCv$HzMQIky(%xQ}ww%wvt@ILs49?(Cagwj^kF zi2Trae~X*6`6gX^(h;20wuBa#?I$6ak$GI6Lq`2mU(7_nE`un^vG1OwZ`Nptv+?3~Rvs^Q z8iyh{bra5l*mIv{mUV{H1U)iZSgXB_NwtAp8G*6A7r`aAU6nx=E$FMHz%77n!Ccyh zQBn7Ae9*$>1Oi|ULmz7$W1Z$7OAbalIBl-2>jzFnxk1US1lV_Um|i1D18XR|R|sy& z?_ang%VA;g@-%jYmz4BlS`x;P<@^JyA^iTeBzOv^)-?|Sby_7xagRSbsTY?$)~2)@ zC9MO=;JBQ$^sB-HfPEA&)hrCq7}b`Ct5i`7H0Br&*@@h4RWnGu`l)^%^KjLnk?$1ar?p z$>(<~R2M}J2jdu4)7J^qcskg&%H_5=&Uu((;hS1mX^9_K$y%(GtSHKpQezgNu7qxdyWnENFfxg0O>`@lmJ}&TBBJQ09PpOO4 zlZ!^-f}iFg+GLzc+{>0#-AW^O+3v@iw`Z=c+~rV$=b@1c0T0tm>OXW*`RS@EQH<~r zF^|960~BxYzvwuyCWpGP4)`>&go07^03Y$o$c;OESWf8*QCSm-r%2LXkyrFscZRqQEcyl`T zQ^^%5jDyeD%Y1x#-k}^J8OJQM0of?K49O(ZjDt$fY9GFyG>c4y`Opf7_S70|J4Pk} zj4>B5$eO1z9;B%Q&FEzZ2>}W79X@idb^}Qp;8C8_8(4m#Qe%|?k2e8e@%jjI z1S~ppT~zL&avh!Z93ZbJaT!sjOu64P8Jgs)qOkGPz=;Z)zawJ8J}=@(4X#?M)0Ueh zQQ8Q|;7l=%`Isz%i_-i)0p5eLms6mPEyo{-=C))5_a06X9=n{A)U-`TII{RYq^1Po z&2T~NNlc1fSp}^`T_WfGH!KWW|FR%_z%eFqoo8s+Bf|=Na37~z2Y)8FV38b^f~wm` zu99}4Qejq;C^M$dGoyzbgjWak%-Cn~-<2(2dy9<%i3Nc_AOcBFz1NkomG`<)sNuCALIn0%l^2@XlbqCr*8=^~g13$O68uLJ4jnb@ zy}VKlvsi#Z1Z;I1cOCvFa8DR+?cwMnHp>htY&B0a&sz^aemxE38NW=XVk*Tl?)>SQ z{}9b^FJn1S@&wGN-@N$b&R15CDw&(Caz}3o`vlErrhSE}D39{j!ya6-Z}kE_aL5O2 zq!SDOwSMA8RrPr8yP2+l!qs{6D&O0viu6V>?-jPKDKV|^R8E#qJ|~YFPw{S|56^KU zRp5(?2Lo%7b-jlZ^!B9KZ1bV730bLL$=U(tPy-E_byd&EBj92Pg%;ak+&qOw=XH|u zWBj(WVsPN{(Xd(2!>8dxfSW$EO_`C`YK>?9b$&geO^(0L= zJrSSVt+MDkm^C-O{^C)0i$bN>3jTTtsb>>dCiq^*uHoA*CGZ=ST&`Avq)Di?A6s?_ z0QXir%mPFG5L|QhXstuJwL?rAsP?Jfq(-MF6@z(g=nC69CsI%ycle)_nLMJYj`r6E z|3UlH$#>a>MOrD0*4qXQpE*HROCw-{*P2g@&APFyq$niT z=;=dGi&940P*t%te)3o0JQ1(utz{W|+0-v*pxv%4-1oCbak*6_(;ZALh0T1&?p-a< zGX5}Ah)4G!upNg2xHMSa*3U3$f?~{jC@E!Nx%ZGG;>1J7O$As%XH4AEsx{O02W1ui zl*x~&*njRmLxkmQ6?}g?I0`xQ&R*ZNeLBC@^q5_sdPkfzOm80ytH4)PV1eF{ciu{9 z9*b7a)lm<=1Q&ze4mA`SECuvhw_kSN0nf>)SWYVUsSOxud^MlXhq~HECPO$(KlU3i zG6%;2t>(kj-UH7Grf?dA$w{DI`?6+X(?)XzR5hrkA zfMOJrZJg8@MPF7K%J=H%O{uA9FzENY@w$nnFR^d~Y#{COdPC*9u9Nr^#HQMMplY#8DG3B}=`Qw%Gc6%RW@?ag0XVlJ`g{^_Hx&;COwQ`gr zc8wDR2SXAibkvuC7}jRtyEsfhqzvO7ZEXbQ@4}YIlitwH%S(QtEtTVAN-)6zqWM0b z4J&7HKNTs~>T41>^P_G`Z&Z=~;;4MtB62Z)I)s$=S6e631la*iM#&|zFGb+-|8YRn ziRtvW&CEGK_{L3f?-(VPU8>xdj$rOb-BV-(BDuQ}t}sW^J8Ib}gyP<^9n3?xOgBByk)iN)LI7R09%i7s9W~8?BdKgCxV12 zfeSi&^5-pJ<@K`pAA5DQBNC{m7LTdo2A3`AG_q(F>O>-pANMHmX$%Duk8o;~cahXY zuTpJ}&Hmo#+w-1dx0}oVudT6a&JMo1R{0T=xEW{EjCeF8C*t~9eH@Ubj>}kwu@F%I z!9y5`2s1O5gAQg?Z4WbT*al33k{Vvw7Wg&iIzcMEC&dHVW`3dFo;Ce90K9yrh)yB zoX!5C_GZ)-<-9fOAD1$m%~h!OWuME?V-1%>0Ih+BJi;Lb$y&_AtI?P)h&erNNTz@o zrQXk;T9bE!YwO&O8x>`mdt~Kl41oeFEA}F3CEj$Ry=}gvNv?|-Y@pX5{Lj~+`R#H9 zN=O9>Uk74M=%ES_>yF*`sw0ch*dpFXdY0Zu6~OK+JrK7_;ovG&q&Wt&s6}nh0_f_v zIO>*IbU?DR61_QQs>swHL)Yu5I#!s>PTgO%=pPCg)4{ox1BNNtP)|Fcn-QxNl1PiN z^2gRs#(;CA{5*w96dKp`DBJt%aoKvcankuhcV>aH6A)E?m=CERQ-Cx-?pON0=l zl?3N3F%Z>fJ)M7j1_nyMO zzqRW3$oUCFMx`_b-djcGD=yI%S1@S!eA&x)yk*1$q=b!+J;La4X8q|k9gn|QAhd>{ znhk<)aej=X&54RybNDk<52HMg0VRral?L>u8t7AcGE`K0dKiu&vCfo9ZQ`A};d(z` z4&T7>&jL!2xhS*?ru++->hHgrup*cXYxCnG%cur%v!&QJK1~iRFwt6*mJRO`SOwXjg-8z42v@aBbW8S=HiLd}BkT}54J(0b&H90!zUK7DLJg@5dA2>Q5 z#?O{u4{I}So3hB!K=RJ@23IZp^;8~5p=4VS;y1Ei#LSh`SH1-&mI9!hEC&Z8iM37A z)uwa&3Yxpm$*ZaE%%;TX*-tRuCuiedtMrKopy;{ZY3@8{2&s!L({*J9FAtlJ_Pm+e zOA+zRI`1SW2_TaJDDw__|9*0n%mViTnDmZ!sx*KFEWywv@!b^XWMtvDS0{Uh-AGj# zo<5wcv0sQv4`y+z_%(VK-bBIZ0pY&>#Y0YJiWont>cv$&);J^jHl) zmb>0BWN6>(X{lem)RL^K$6Bfe+knO0o_@$;olYKHY%o8ewJhV_eGhX36ep1>_*JY$ z?@CAbd-!IZQk@n#O2~CtY#r)cgP2?cq`S*-*-IQnuGVMN&anIagW60R@4(!a_g1@l z;;>KXFZ%RWNOaEV&&D>^o1B(`^`doRw0|uV{%l~!9+=ymFndwOT9;?MkqzL4D_0X8 zGF4y*l$2mRs`#}$X7TA(mfQu?*nrN}TnHhC&(v5zB;QLk1S|ol|RH zuphudCkJ-6^EjJLlyqKq z+l$ER()TT3y9_`t>|P5otIJWWcA_5#z_d%-h>eq_uk_KC(%3~w;03(IFt#!}@9l-N z9gO2+xFq_v6d-3!qM-q)=-ppt9&8lsdBo@6V{I_QZ$uQev9h;6*8Rle37Vi3tdsKp z9N5&JLfeTCS+9vdwS`PVR$ydnU2S%fh;TM~bI9kro%z=5K92cL@S0y(X}0m6%@?9w zi3jS@hes@Otxi)geG41)sdLpYz^N)P#8dg7ieT4ytE1|_5Q+4j)=4kx{Mq=Q4S>=> zXFYM4EW+!cZ5PsI$u1zR_jATjP$bvpQDmb{03oc5kVAG4e_hZO(}nS$bY!PDD%_^8 zN6+}iF+ot1ZdQdsld~?pY^8h{PXyoOnogh%bQvxu{LXgeaWA@fa;+nj*70yT&KcJ^5IC zwG}Hm>)=4T$gpx&p!Rj*yoefIR#mOdyNG~Kc(`0fw#*M^hyYsiYLk3~!PW&&uhdA7 zCo3MiM@F&+V%EgzG@)Hn`miKa<4}@!!y5NrL3L{#xCx)9}ooS+N zkh0%xF-xCIO%eHO)5@c4?>Lwk7&os1fKTQW(pC!X!g5eeB^&5tpLklrLb~7N^0siR z#O~$MNtH1z#z`6S7OJgd1q&M0Ay*p6nB9!myMf4QFTDCDAQID!U(ZL2{Imm`?;)JY z*wRnlYC*F^HBI{MhxB)v7|rxl^I>D}2^!a@-cK92%#dR>DpijZg`K_DC#2lJdm5Ka z5&~5_b)Dsz6RU;qk#R0})D8tigzkk_`1Xjx&PmArs8#TS8a`n%83xR;H2GNq&Y+@q zJhu$zjt0BRR(-}7yJDCsv4LS*R2+OO|8=qZmAh9!KEZK6X1=q@#5_myhn`kyNkn`4c-oAZ*xs@7Ym4EI!!uKb0H z4k%l714)Gm3k&Dxv~e6lX2q`FZ&u_|IXlnh|z8n&$&nIN|!U*rA+T3c2v=1Keg}pBZt=+64Qig0k z;kFYN${fdTN}&_dxRs#25%ojt%;6$qbKUSu0yfJ=z2^Q%M09(d`7w?8W3*31rK3)6 z11*Jjefzvyd;rq1Lt@i-hwl^gT<1pt*Q1~JJ2k4^u`tE<>~lo}iM>ko>Ys(@+3Ke; zot=p<_bIg1o-OvJ!;T5P_^XuXc_pWR@o)PS_hnXr`-Nh99l|_o!lZi8QdV_xFH$wU zhO2rj%(5;D0!0{|v<5y!L>d&AD7X$_;DV@4oXRCJphiUHi&-KkVVwWa~n-N!(Io*dSy<=lu!$duZVJPRULpJG!WLkMp-qJ|W3N`9*X4+gD} zi-yF*__jfrf?_S|wt#q7ZF$Y;bNA}{E$pJJDF$Pxvtz5kuK2p=kJo460)R+EoH9FKB`pO*@SubLE5G?%29euacY=c zSPrb#!nD~*Fis3;jEmQnSISv<%sTw+9keinMq{wy_{~Q)>?-Z7op*iu=}gUNRR|+0 zwqcx{6l^r{A2=c?JkYL85BY2VRA<*OB>zaI#7Q9zB}#d+Y1F4#)Cq!mN_EqKe9_JA zDLZmi>hLbPCSK&vs>NN^v1buEDcQ$ze2`^x3a6c2;f=}xeCJ+>pzL!-%d$3xwXVz7 z_2Ra8uqv3Aigd~nXIE6T>mq!*`$Xdm2RNU#SBAamd7A{wg|@bT8-qsVn7i9N`;Ew^MjU0fVTskU zW&R$=zc~8DX+W1=N|ks2q4CNaJxUXLYj4sQVc_Xf*!9f5{1D4``PiLycfIklCjOkU zAi7x0KnjT_42sZDuFB?mPwCRlV^&nI>WE_<#p0-!OlJBGet1`oayQMAsW2j(%c-et zIdx?9Lf|?OOn#n}ObBv8Q=yRWX{SNOL&_2jxp+3jM%ruS9uK zEGI)G>qLOW>eXK$&PB=#=U^$?`GR~s39#cjUGIkT;{wBPjMo)}Op;TgB_n}( zFI@j1c&|)uV^~ct2Vs#2ykhmOkHSzE^Pp4C?HH=xLN8-%e8voFifoGQ|qWFBSoZ#2Bu zT2k@h4?w(ZiP}-Pp0Bw`2y}Af@)h|3!FR#}`KxWTHDIvv`sFd|rrHkp22s6%V zv@kjdjWF&IHNI1m zh?Vu4D2GS038Jcwh-4UgB_o?8WqO;kvCCM|?}e;xu)WmrGH@hxcY zV$o^KjDJnbf<+8p+m`A>!eys%>h1*{*rP|en?om=Jrb$qt45Gq1B-f0`i3o6GJV9Q zJ^ktCc~jV3xCe`}%az?k9T5i1Rlod4TW%DQ06ptvJb$#wK0H-bfVs&q!dYJ&6)jLl zzBU!6!f0BRdPtr`Gl3>LBLtV-wrS8BTp^H$NP{IV38*7QH|TCVy@%)gFn)XZfgF4m z^EU~tkyG3ax+=S3>USy%)!Cna%ZHlkx^7V0Q>+{AG78pN{#jH67E3I5A-M(#lpc5L z5@YU8Gq^TE$%!0L(lmT^k;LCcoWRzN^Q-3jsK}Y{AQdm8T?D(xq#!X`u*(v$Z3nWf zvSUaVv|i|p?xT^?$_dQ})N1L_4tHK{PoA0W<|Yi->poaZb0RtnFrHu%-`j8#CGLQD z)zo<|XLT2G)5JzEpXbB<*HrW{il|hocr>Ivpv`}*q&!JY4~rm$wC3^oM2Lts z_FEqXPoXV^XXtS3#h=v?9Ik1?agz!PW6|Qd1SbRS3E1zl4?s3;SF~pb^Xul*h^+k& zrQ_v#=wgfnqjwIU0aAkqX7r6C%T7IL^C~rA%y7%|sb}8FpC7Gpns$nVmVt+~PH%0? z#7v9fq-bw;-LZAk8(B59JA+ws;EIN4q&KJX7pMVSqA#A4dtkkx$G+TS-okY*#Y$QgK;00^-YIUCF#^2PiR zu@rl#^$6;iy{`$>t3r5CDp40wr9crzO;!?TL!YviXWskkQwp)+8L*0@f}G8TDuL@L zlosvsjBEnh`@Fb|${9pw>f>aEQ6N(!7^P=dN+k|QjoN<2?D_Y%D_f<)y-*zo47v2! zP5HL7^jegDd{dHA<+i>_@bK(hJ-urfDS$^DY)u|nR~doa2kS=3T@Ze|EjZf(Bj%O> zu}lknOsxiFD(LtvI8C@tg*w*}1VdM1vVT})l8qiMCjfY3jBe;B-D9(_CB183bR?(O z;%5VFSgQUGzIP;3>}P3w`+w0O8m6uugXg9rrlA-}~$xKdOc z{|IN*rY1KL!_ud}dC+?XqeP`vr&ztIW6(4SNbE`L7Z@1}3mmd6Rrvbm!bJSl+yXLz~cu^eeK8_N8rcewcHh*YQW_6gYIBwPP$N4ANQY}j!q>D(%kNl8n0~)B;R%)STNZ=%q$86#kRUg;n{Q|_Th5i{<@dX=977I zP<8{yYtL6tO!)HgWg;)l7)-2* zM`WD<@M>}v5kMNd3x2((gzA?d37FgYR&|1z3;oR3UgEHRsYR&=4aoa7xRMA!~xRb-ca$ zW6~t5<2z8e&D2%g4^h2Wua-EwYUw(hO}|UTNKi4>0P55IvBdyW=|a40#$#CoQUOcn zuW+@!;7b)SbHR%`OMFlCE1~5S&tiY5m?MY6zdkAbLu>fZ4iL1+ zkC=o208FBk%%iaGdxw5s@P!$E1`~<2i!hh+h#-MSQPJWe1opduTvme;j=lcMYh!k?yeKgB@DGzvoOhk{p->rNs{T$_~| zw+PMVwJn^-FAG(<<+d2P41*Git`!bbT|0AB<3@7Pj7n4q`-3R{VjkL3wG}5g!QWymig^&Qwez%4iQiMv;t_w#Xhw6$tKBrp=b$1%wJ?7Ghgl;%~V#;6$NOEE22Q+|c` zl(Y(3l=PiiKY@v;vg42^g8))Mt-psJ4?}7mljL<#$&1%BLAAzrGsz%l^}M3jyXfP; z+xaP#SSbo@3m32jAgwe)Nx9#Qe=r*~u_3h{wmWSuEU25a?(RV^mts`4zD#;`gvPj9 z4E3Te6%7-Q>M;ssUSi`DIaOe_hstkHuKIqRRz2*%LBKlh9Orks*t28_+%OR)(LPK; z+t=Ilcyv@7>dV~)W4-sM`}ihd&_O(5O|H=oVd~|B(vHD^n>6y!MIrR#52qtnKBGMu zOQ76HbVF(+zJoFt+-LhxK-LgK%a|6O|r1xNnpXK1IOx0saCfMQFl*zKi}0e zQLVz$k!2FOqu~jPOcFK)a4B(j2~@8Ws~n5os>#d=<>vgAXdKCQFkz)&K<#QmlDAFO zm!gqoQ(nyltmjR~mJ8_zd=|KDP8!wtN7|QH+d@<9D;S=GcczMc_D*zkix`c*s+C*X z-WP>NJZrSGQR+~KiU&&3L8MvpCelS#`SYDnD=cFfbijdSwYi(#1T@~t9 zt|4#a(GZ4`TenE3eg=eH_7}IKWJM-*HtO{;z!xt>_>Sdv%&>ART+dHo0ZWWNZb@Da zQu2Qb)O_>v)az_Xs*N@@B**1#@!Icj8xW0r1$UokLDpbz5`b->_``&Wyv+SWv$pDj zl_35lm*;B^v(HZ1$4k^$YzAH{o6ia}P5QZdZN;w3pT10#m$`6Y>Qy-U1WT;pJp9h| zG!*qVDhH0YPJ~YB( zOo>H2L18aTPH*9!vGCS#?}wXKSU)W5p*lB z`_>d|r}ps?=gFr3`6_5}R}%>-EQ;sLeN*qDSKZaXMpP5tz zKjB}#gzt`9%L6)9zD${S1;xsvfQ(cW1>GqajAa(T%BCr`lhk$B&}2S6+HtD6dYluBa!Pjb=jKFDm=bVcG} z@4)xdyh3RsE%`}ei3r5^XENZt2k9?CQ;T86lX_E}nZlBV;PJI&Z#C*qqr*vAZqqE9T1Qz^jg!*Z5j!M?WsAYA*ApjwgjTWnjQuJ5A z;&D;W6!E5nqSX5X1RhVMjT`g3Sec$1l@$~ zk#Ec6u!;1G@PjTh;K5bO{?Mn2ma2Faborf$<4TaG&|81~C*Pm;=X5^xpU3lgQCzPX z2C6Bw=u+owP`=&S8=EtO5>zK+RGy}K_okW==_&=e*(>mJV`XE9Mp1r$A;CMQ%UeERh$_ZhJ(*8QDjS7 z$dVI=r8wLSkPax+Dsi-zIK&s5co6_$smp|(bCvD9-2~|a-Wx|rq!a+>X?a@yl{oIO z8<8QzEL3%?bO={K@9p3$LJ=w&Vgik$k@$M zyHW2*Jq7jHTN>G{V=S9Mr0aT;lSE%=s7Eq+4q})_X`x?OwdQ^!*r9PY*HGZc2AD?@ z1Fb)B+PFrdC^pxK8s^aPrP=t@n#p-uBjI7{8UYG83}zMK|2NRR#aWOJziSZyf-q~0 zRDW4~Zv&gC8$AW=<4>H|mBH>>B+lC(Mk~1Zgov?iwPT9V133g?2%vePatX)uNJlW9 zpceQDn#szy&_kqwcE+71WS;D&J=0{%?(2F?ug~hQX)m^(6~+vxnK7s-#MG8XP6Sj& z{*t}f^CD$JX`xDU|CYchBOLQG!!|eshU%64^R6pV2=(0=1DTKVY=HIq2%I)wxJ`iq zHeLx0y*$xFc!lxmFS%vnH{%w=^#|}pQ8ycYtXnEN(QgSlBSOu#qneUOXAIcSRML^S z^WX$yFpD`n!Mue6(w;XW9pgoNX(-XE*?)>2VvFUdD_W4eaO% z5Z6~9ehq1kGP(~*2(@pb78C%HFt=dyLom|LNL$!9sEvdT5<_{_tV*O*l#mGQ_owj5 z_tNyBj&U%#kB;>G&J+OnyJXz&N%u|{u23fYJ0Ax$)bLSA^KpN)G!4nYL@2;XYGSME zi)1Uco!4d=W%RNO!k2Ful;EaqgJKM=`%9W@<(Ju03&s)5hbqK?6)2X6MJImJR~e`q z`{SiALv_#NA=_6}KxNwasuJMOkP2>Y@MyR(EGwT= ziVpThg)A?k#$IorkfOyhME$Su;2FqbuD7V=_Mp8prYHoY{Xh8G)zGu0Ed(>_1|s$7 zWK9b-ZD(sB7Lc%Q#VD!XQ20HaNyj1qL>bnQ!-jZ!?;_miLZ#!z61bCD+AF@qm9hLumzp`k4(a6IOD*H-N5C&#o^Nlf1sJkq@JAm10NI66u*Fog0!hHTEV7H zm^I$7ckIQ0c;PeOCTi*;-{~~hL|xjO{c1@qZylshL3RfeUQiETNORCA*N8CQKzL?- z{fft@N3*+U6&_)$WZ8nl=l~vCHo1OHe{?@X7a%1>0%IZk1-2Ta3)TZ8l1}W;M*a~! zqenEfDa9vQ(q!=`wRx#R)xXq`mcH1GtpbP=v_SSgLBuwC=6z$J{fLWy$4<${R`a|Y z=Isc~6^(}t>m-8%e=3LWFF2ozClZ_rJ}|k0G9J7q42-GAw4os-b;=!}MLCDe?83N+ zG#p_Bs?QrDPaOmw8BiQihWw%bnv>Fn8hqb4qEVQ)k+^r|+LKv?qy{Q0y zWu?Jg75S8^Vlg*ySXlx^Rj^Y~`f1nC9@g2`6=-I1?q36X8oBtf&WP<>9c*T6Lefa> zN-fTTzL2R&aDsFWT^QGg2bI%@-yTdEewuKBzQ)V7*4QR!Cy02q&)Wquf83+kNsk1= zoUQFMCA|p;clXhuw#5uMtR-OB=Ip8ef1YJ^CfM^<)}I*8*o@tskpWn)@++dDhVA9= zA~Un54q-);Ibp?vtb>hD(!y~Iz5x7gP`r9U#Cgsb%tqaRWcU2)#dMsJXhXsuFf;wUSN)K)a_wR^c71Cm zHs#V_aBe0~zpATCtgGSEi#p6w3E5E*ZsEN|*IvCVha~J;P2&d<9kBhyqZ7V#XsxOz zXU}`Cq>}MsJK_|%ob}H3z4q5%7uCXQWm0I$>>2Gl;z?T_n8nC9Xvh2FquK6QP?ORs zJEfa(pEDch;V)ZeW0?Nto(MNI)C>W=X&%LdFrMG&na8 zOSI-pV;c>T@@Elr8`6e7O!KpvWbF}}N_S11k5iV3&?C{r!QcDwegH^=3senk2R~sH z4K>r=jYF(%b`I90dPM?o66g05;_}B~vZQqShM!zzz8DmBZW@7r9#e9d5344Gon5|6 z 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 F|Jh-;o+khR literal 0 HcmV?d00001 diff --git a/examples/demos/osmbuildings/main.py b/examples/demos/osmbuildings/main.py new file mode 100644 index 00000000..313e3a0f --- /dev/null +++ b/examples/demos/osmbuildings/main.py @@ -0,0 +1,24 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from pathlib import Path + +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtGui import QGuiApplication +from PySide6.QtCore import QCoreApplication + +from manager import OSMManager, CustomTextureData # noqa: F401 + + +if __name__ == "__main__": + app = QGuiApplication(sys.argv) + engine = QQmlApplicationEngine() + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("OSMBuildings", "Main") + if not engine.rootObjects(): + sys.exit(-1) + + exit_code = QCoreApplication.exec() + del engine + sys.exit(exit_code) diff --git a/examples/demos/osmbuildings/manager.py b/examples/demos/osmbuildings/manager.py new file mode 100644 index 00000000..6ff2d204 --- /dev/null +++ b/examples/demos/osmbuildings/manager.py @@ -0,0 +1,104 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtQuick3D import QQuick3DTextureData +from PySide6.QtQml import QmlElement +from PySide6.QtGui import QImage, QVector3D +from PySide6.QtCore import QByteArray, QObject, Property, Slot, Signal + +from request import OSMTileData, OSMRequest + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "OSMBuildings" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class OSMManager(QObject): + + mapsDataReady = Signal(QByteArray, int, int, int) + + def __init__(self, parent=None): + super().__init__(parent) + self.m_request = OSMRequest(self) + self.m_startBuildingTileX = 17605 + self.m_startBuildingTileY = 10746 + self.m_tileSizeX = 37 + self.m_tileSizeY = 37 + self.m_request.mapsDataReady.connect(self._slotMapsDataReady) + + def tileSizeX(self): + return self.m_tileSizeX + + def tileSizeY(self): + return self.m_tileSizeY + + @Slot(QByteArray, int, int, int) + def _slotMapsDataReady(self, mapData, tileX, tileY, zoomLevel): + self.mapsDataReady.emit(mapData, tileX - self.m_startBuildingTileX, + tileY - self.m_startBuildingTileY, zoomLevel) + + @Slot(QVector3D, QVector3D, float, float, float, float, float, float) + def setCameraProperties(self, position, right, + cameraZoom, minimumZoom, maximumZoom, + cameraTilt, minimumTilt, maximumTilt): + + tiltFactor = (cameraTilt - minimumTilt) / max(maximumTilt - minimumTilt, 1.0) + zoomFactor = (cameraZoom - minimumZoom) / max(maximumZoom - minimumZoom, 1.0) + + # Forward vector align to the XY plane + forwardVector = QVector3D.crossProduct(right, QVector3D(0.0, 0.0, -1.0)).normalized() + projectionOfForwardOnXY = position + forwardVector * tiltFactor * zoomFactor * 50.0 + + queue = [] + for forwardIndex in range(-20, 21): + for sidewardIndex in range(-20, 21): + vx = float(self.m_tileSizeX * sidewardIndex) + vy = float(self.m_tileSizeY * forwardIndex) + transferredPosition = projectionOfForwardOnXY + QVector3D(vx, vy, 0) + tile_x = self.m_startBuildingTileX + int(transferredPosition.x() / self.m_tileSizeX) + tile_y = self.m_startBuildingTileY - int(transferredPosition.y() / self.m_tileSizeY) + self.addBuildingRequestToQueue(queue, tile_x, tile_y) + + projectedTileX = (self.m_startBuildingTileX + int(projectionOfForwardOnXY.x() + / self.m_tileSizeX)) + projectedTileY = (self.m_startBuildingTileY - int(projectionOfForwardOnXY.y() + / self.m_tileSizeY)) + + def tile_sort_key(tile_data): + return tile_data.distanceTo(projectedTileX, projectedTileY) + + queue.sort(key=tile_sort_key) + + self.m_request.getMapsData(queue.copy()) + + def addBuildingRequestToQueue(self, queue, tileX, tileY, zoomLevel=15): + queue.append(OSMTileData(tileX, tileY, zoomLevel)) + + @Slot(result=bool) + def isDemoToken(self): + return self.m_request.isDemoToken() + + @Slot(str) + def setToken(self, token): + self.m_request.setToken(token) + + @Slot(result=str) + def token(self): + return self.m_request.token() + + tileSizeX = Property(int, tileSizeX, constant=True) + tileSizeY = Property(int, tileSizeY, constant=True) + + +@QmlElement +class CustomTextureData(QQuick3DTextureData): + + @Slot(QByteArray) + def setImageData(self, data): + image = QImage.fromData(data).convertToFormat(QImage.Format.Format_RGBA8888) + self.setTextureData(QByteArray(bytearray(image.constBits()))) + self.setSize(image.size()) + self.setHasTransparency(False) + self.setFormat(QQuick3DTextureData.Format.RGBA8) diff --git a/examples/demos/osmbuildings/osmbuildings.pyproject b/examples/demos/osmbuildings/osmbuildings.pyproject new file mode 100644 index 00000000..2d023522 --- /dev/null +++ b/examples/demos/osmbuildings/osmbuildings.pyproject @@ -0,0 +1,5 @@ +{ + "files": ["main.py", "manager.py", "request.py", + "OSMBuildings/Main.qml", "OSMBuildings/OSMCameraController.qml", + "OSMBuildings/customshadertiles.frag"] +} diff --git a/examples/demos/osmbuildings/request.py b/examples/demos/osmbuildings/request.py new file mode 100644 index 00000000..a845bc29 --- /dev/null +++ b/examples/demos/osmbuildings/request.py @@ -0,0 +1,128 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import math +import sys +from dataclasses import dataclass +from functools import partial + +from PySide6.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest +from PySide6.QtCore import (QByteArray, QTimer, QFile, QFileInfo, + QObject, QUrl, Signal, Slot) + +# %1 = zoom level(is dynamic), %2 = x tile number, %3 = y tile number +URL_OSMB_MAP = "https://tile-a.openstreetmap.fr/hot/{}/{}/{}.png" + + +@dataclass +class OSMTileData: + TileX: int = 0 + TileY: int = 0 + ZoomLevel: int = 1 + + def distanceTo(self, x, y): + deltaX = float(self.TileX) - float(x) + deltaY = float(self.TileY) - float(y) + return math.sqrt(deltaX * deltaX + deltaY * deltaY) + + def __eq__(self, rhs): + return self._equals(rhs) + + def __ne__(self, rhs): + return not self._equals(rhs) + + def __hash__(self): + return hash((self.TileX, self.TileY, self.ZoomLevel)) + + def _equals(self, rhs): + return (self.TileX == rhs.TileX and self.TileY == rhs.TileY + and self.ZoomLevel == rhs.ZoomLevel) + + +def tileKey(tile): + return f"{tile.ZoomLevel},{tile.TileX},{tile.TileY}" + + +class OSMRequest(QObject): + + mapsDataReady = Signal(QByteArray, int, int, int) + + def __init__(self, parent): + super().__init__(parent) + + self.m_mapsNumberOfRequestsInFlight = 0 + self.m_queuesTimer = QTimer() + self.m_queuesTimer.setInterval(0) + self.m_buildingsQueue = [] + self.m_mapsQueue = [] + self.m_networkAccessManager = QNetworkAccessManager() + self.m_token = "" + + self.m_queuesTimer.timeout.connect(self._slotTimeOut) + self.m_queuesTimer.setInterval(0) + self.m_lastBuildingsDataError = "" + self.m_lastMapsDataError = "" + + @Slot() + def _slotTimeOut(self): + if not self.m_buildingsQueue and not self.m_mapsQueue: + self.m_queuesTimer.stop() + else: + numConcurrentRequests = 6 + if self.m_mapsQueue and self.m_mapsNumberOfRequestsInFlight < numConcurrentRequests: + self.getMapsDataRequest(self.m_mapsQueue[0]) + del self.m_mapsQueue[0] + + self.m_mapsNumberOfRequestsInFlight += 1 + + def isDemoToken(self): + return not self.m_token + + def token(self): + return self.m_token + + def setToken(self, token): + self.m_token = token + + def getBuildingsData(self, buildingsQueue): + if not buildingsQueue: + return + self.m_buildingsQueue = buildingsQueue + if not self.m_queuesTimer.isActive(): + self.m_queuesTimer.start() + + def getMapsData(self, mapsQueue): + if not mapsQueue: + return + self.m_mapsQueue = mapsQueue + if not self.m_queuesTimer.isActive(): + self.m_queuesTimer.start() + + def getMapsDataRequest(self, tile): + fileName = "data/" + tileKey(tile) + ".png" + if QFileInfo.exists(fileName): + file = QFile(fileName) + if file.open(QFile.OpenModeFlag.ReadOnly): + data = file.readAll() + file.close() + self.mapsDataReady.emit(data, tile.TileX, tile.TileY, tile.ZoomLevel) + self.m_mapsNumberOfRequestsInFlight -= 1 + return + + url = QUrl(URL_OSMB_MAP.format(tile.ZoomLevel, tile.TileX, tile.TileY)) + reply = self.m_networkAccessManager.get(QNetworkRequest(url)) + reply.finished.connect(partial(self._mapsDataReceived, reply, tile)) + + @Slot(OSMTileData) + def _mapsDataReceived(self, reply, tile): + reply.deleteLater() + if reply.error() == QNetworkReply.NetworkError.NoError: + data = reply.readAll() + self.mapsDataReady.emit(data, tile.TileX, tile.TileY, tile.ZoomLevel) + else: + message = reply.readAll().data().decode('utf-8') + if message != self.m_lastMapsDataError: + self.m_lastMapsDataError = message + print("OSMRequest.getMapsDataRequest", reply.error(), + reply.url(), message, file=sys.stderr) + self.m_mapsNumberOfRequestsInFlight -= 1 diff --git a/examples/designer/taskmenuextension/doc/taskmenuextension.rst b/examples/designer/taskmenuextension/doc/taskmenuextension.rst index dd7fe167..cbd893af 100644 --- a/examples/designer/taskmenuextension/doc/taskmenuextension.rst +++ b/examples/designer/taskmenuextension/doc/taskmenuextension.rst @@ -1,5 +1,3 @@ -.. _task-menu-extension-example: - Task Menu Extension Example =========================== diff --git a/examples/designer/taskmenuextension/main.py b/examples/designer/taskmenuextension/main.py index ffde89d2..ff2cda53 100644 --- a/examples/designer/taskmenuextension/main.py +++ b/examples/designer/taskmenuextension/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt Designer taskmenuextension example from Qt v6.x""" diff --git a/examples/designer/taskmenuextension/registertictactoe.py b/examples/designer/taskmenuextension/registertictactoe.py index 2b014c79..85335558 100644 --- a/examples/designer/taskmenuextension/registertictactoe.py +++ b/examples/designer/taskmenuextension/registertictactoe.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from tictactoe import TicTacToe # noqa: F401 from tictactoeplugin import TicTacToePlugin diff --git a/examples/designer/taskmenuextension/tictactoe.py b/examples/designer/taskmenuextension/tictactoe.py index aa1c3158..f334563a 100644 --- a/examples/designer/taskmenuextension/tictactoe.py +++ b/examples/designer/taskmenuextension/tictactoe.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt, QPoint, QRect, QSize, Property, Slot from PySide6.QtGui import QPainter, QPen @@ -63,9 +64,9 @@ class TicTacToe(QWidget): def paintEvent(self, event): with QPainter(self) as painter: - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) - painter.setPen(QPen(Qt.darkGreen, 1)) + painter.setPen(QPen(Qt.GlobalColor.darkGreen, 1)) painter.drawLine(self._cell_width(), 0, self._cell_width(), self.height()) painter.drawLine(2 * self._cell_width(), 0, @@ -75,7 +76,7 @@ class TicTacToe(QWidget): painter.drawLine(0, 2 * self._cell_height(), self.width(), 2 * self._cell_height()) - painter.setPen(QPen(Qt.darkBlue, 2)) + painter.setPen(QPen(Qt.GlobalColor.darkBlue, 2)) for position in range(9): cell = self._cell_rect(position) @@ -85,7 +86,7 @@ class TicTacToe(QWidget): elif self._state[position] == NOUGHT: painter.drawEllipse(cell) - painter.setPen(QPen(Qt.yellow, 3)) + painter.setPen(QPen(Qt.GlobalColor.yellow, 3)) for position in range(0, 8, 3): if (self._state[position] != EMPTY diff --git a/examples/designer/taskmenuextension/tictactoeplugin.py b/examples/designer/taskmenuextension/tictactoeplugin.py index f9c92513..93a40a85 100644 --- a/examples/designer/taskmenuextension/tictactoeplugin.py +++ b/examples/designer/taskmenuextension/tictactoeplugin.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from tictactoe import TicTacToe from tictactoetaskmenu import TicTacToeTaskMenuFactory diff --git a/examples/designer/taskmenuextension/tictactoetaskmenu.py b/examples/designer/taskmenuextension/tictactoetaskmenu.py index 71826dc7..a169a4f5 100644 --- a/examples/designer/taskmenuextension/tictactoetaskmenu.py +++ b/examples/designer/taskmenuextension/tictactoetaskmenu.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from tictactoe import TicTacToe @@ -15,12 +16,12 @@ class TicTacToeDialog(QDialog): layout = QVBoxLayout(self) self._ticTacToe = TicTacToe(self) layout.addWidget(self._ticTacToe) - button_box = QDialogButtonBox(QDialogButtonBox.Ok - | QDialogButtonBox.Cancel - | QDialogButtonBox.Reset) + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel + | QDialogButtonBox.StandardButton.Reset) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) - reset_button = button_box.button(QDialogButtonBox.Reset) + reset_button = button_box.button(QDialogButtonBox.StandardButton.Reset) reset_button.clicked.connect(self._ticTacToe.clear_board) layout.addWidget(button_box) @@ -48,7 +49,7 @@ class TicTacToeTaskMenu(QPyDesignerTaskMenuExtension): def _edit_state(self): dialog = TicTacToeDialog(self._ticTacToe) dialog.set_state(self._ticTacToe.state) - if dialog.exec() == QDialog.Accepted: + if dialog.exec() == QDialog.DialogCode.Accepted: self._ticTacToe.state = dialog.state() diff --git a/examples/external/matplotlib/widget3d/widget3d.py b/examples/external/matplotlib/widget3d/widget3d.py index 8bfcc4ca..0da07b40 100644 --- a/examples/external/matplotlib/widget3d/widget3d.py +++ b/examples/external/matplotlib/widget3d/widget3d.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -36,7 +37,8 @@ class ApplicationWindow(QMainWindow): self.menu_file.addAction(exit) self.menu_about = self.menu.addMenu("&About") - about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), + about = QAction("About Qt", self, + shortcut=QKeySequence(QKeySequence.StandardKey.HelpContents), triggered=qApp.aboutQt) # noqa: F821 self.menu_about.addAction(about) @@ -47,8 +49,8 @@ class ApplicationWindow(QMainWindow): # Sliders (Left) min = 0 max = 360 - self.slider_azim = QSlider(minimum=min, maximum=max, orientation=Qt.Horizontal) - self.slider_elev = QSlider(minimum=min, maximum=max, orientation=Qt.Horizontal) + self.slider_azim = QSlider(minimum=min, maximum=max, orientation=Qt.Orientation.Horizontal) + self.slider_elev = QSlider(minimum=min, maximum=max, orientation=Qt.Orientation.Horizontal) self.slider_azim_layout = QHBoxLayout() self.slider_azim_layout.addWidget(QLabel(f"{min}")) @@ -63,7 +65,7 @@ class ApplicationWindow(QMainWindow): # Table (Right) self.table = QTableWidget() header = self.table.horizontalHeader() - header.setSectionResizeMode(QHeaderView.Stretch) + header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # ComboBox (Right) self.combo = QComboBox() diff --git a/examples/external/matplotlib/widget_gaussian/widget_gaussian.py b/examples/external/matplotlib/widget_gaussian/widget_gaussian.py index 2423e496..22669d1a 100644 --- a/examples/external/matplotlib/widget_gaussian/widget_gaussian.py +++ b/examples/external/matplotlib/widget_gaussian/widget_gaussian.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/external/networkx/main.py b/examples/external/networkx/main.py index 8cd7e790..696a3f4f 100644 --- a/examples/external/networkx/main.py +++ b/examples/external/networkx/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math @@ -33,9 +34,9 @@ class Node(QGraphicsObject): self._radius = 30 self._rect = QRectF(0, 0, self._radius * 2, self._radius * 2) - self.setFlag(QGraphicsItem.ItemIsMovable) - self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) - self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges) + self.setCacheMode(QGraphicsItem.CacheMode.DeviceCoordinateCache) def boundingRect(self) -> QRectF: """Override from QGraphicsItem @@ -54,20 +55,20 @@ class Node(QGraphicsObject): painter (QPainter) option (QStyleOptionGraphicsItem) """ - painter.setRenderHints(QPainter.Antialiasing) + painter.setRenderHints(QPainter.RenderHint.Antialiasing) painter.setPen( QPen( QColor(self._color).darker(), 2, - Qt.SolidLine, - Qt.RoundCap, - Qt.RoundJoin, + Qt.PenStyle.SolidLine, + Qt.PenCapStyle.RoundCap, + Qt.PenJoinStyle.RoundJoin, ) ) painter.setBrush(QBrush(QColor(self._color))) painter.drawEllipse(self.boundingRect()) painter.setPen(QPen(QColor("white"))) - painter.drawText(self.boundingRect(), Qt.AlignCenter, self._name) + painter.drawText(self.boundingRect(), Qt.AlignmentFlag.AlignCenter, self._name) def add_edge(self, edge): """Add an edge to this node @@ -87,7 +88,7 @@ class Node(QGraphicsObject): Returns: Any """ - if change == QGraphicsItem.ItemPositionHasChanged: + if change == QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged: for edge in self._edges: edge.adjust() @@ -204,15 +205,15 @@ class Edge(QGraphicsItem): """ if self._source and self._dest: - painter.setRenderHints(QPainter.Antialiasing) + painter.setRenderHints(QPainter.RenderHint.Antialiasing) painter.setPen( QPen( QColor(self._color), self._tickness, - Qt.SolidLine, - Qt.RoundCap, - Qt.RoundJoin, + Qt.PenStyle.SolidLine, + Qt.PenCapStyle.RoundCap, + Qt.PenJoinStyle.RoundJoin, ) ) painter.drawLine(self._line) @@ -285,7 +286,7 @@ class GraphView(QGraphicsView): animation = QPropertyAnimation(item, b"pos") animation.setDuration(1000) animation.setEndValue(QPointF(x, y)) - animation.setEasingCurve(QEasingCurve.OutExpo) + animation.setEasingCurve(QEasingCurve.Type.OutExpo) self.animations.addAnimation(animation) self.animations.start() diff --git a/examples/external/opencv/webcam_pattern_detection.py b/examples/external/opencv/webcam_pattern_detection.py index 0c55a133..dfc59397 100644 --- a/examples/external/opencv/webcam_pattern_detection.py +++ b/examples/external/opencv/webcam_pattern_detection.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os import sys @@ -56,8 +57,8 @@ class Thread(QThread): # Creating and scaling QImage h, w, ch = color_frame.shape - img = QImage(color_frame.data, w, h, ch * w, QImage.Format_RGB888) - scaled_img = img.scaled(640, 480, Qt.KeepAspectRatio) + img = QImage(color_frame.data, w, h, ch * w, QImage.Format.Format_RGB888) + scaled_img = img.scaled(640, 480, Qt.AspectRatioMode.KeepAspectRatio) # Emit signal self.updateFrame.emit(scaled_img) @@ -78,7 +79,8 @@ class Window(QMainWindow): self.menu_file.addAction(exit) self.menu_about = self.menu.addMenu("&About") - about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents), + about = QAction("About Qt", self, + shortcut=QKeySequence(QKeySequence.StandardKey.HelpContents), triggered=qApp.aboutQt) # noqa: F821 self.menu_about.addAction(about) @@ -93,7 +95,7 @@ class Window(QMainWindow): # Model group self.group_model = QGroupBox("Trained model") - self.group_model.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.group_model.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) model_layout = QHBoxLayout() self.combobox = QComboBox() @@ -109,8 +111,8 @@ class Window(QMainWindow): buttons_layout = QHBoxLayout() self.button1 = QPushButton("Start") self.button2 = QPushButton("Stop/Close") - self.button1.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) - self.button2.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.button1.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) + self.button2.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) buttons_layout.addWidget(self.button2) buttons_layout.addWidget(self.button1) diff --git a/examples/external/pandas/dataframe_model.py b/examples/external/pandas/dataframe_model.py index b3d9e81f..bd741f97 100644 --- a/examples/external/pandas/dataframe_model.py +++ b/examples/external/pandas/dataframe_model.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import pandas as pd @@ -42,7 +43,7 @@ class PandasModel(QAbstractTableModel): if not index.isValid(): return None - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return str(self._dataframe.iloc[index.row(), index.column()]) return None @@ -54,8 +55,8 @@ class PandasModel(QAbstractTableModel): Return dataframe index as vertical header data and columns as horizontal header data. """ - if role == Qt.DisplayRole: - if orientation == Qt.Horizontal: + if role == Qt.ItemDataRole.DisplayRole: + if orientation == Qt.Orientation.Horizontal: return str(self._dataframe.columns[section]) if orientation == Qt.Vertical: diff --git a/examples/external/scikit/staining_colors_separation.py b/examples/external/scikit/staining_colors_separation.py index 94fdc3bd..ce886a8e 100644 --- a/examples/external/scikit/staining_colors_separation.py +++ b/examples/external/scikit/staining_colors_separation.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/graphs/2d/hellographs/HelloGraphs/Main.qml b/examples/graphs/2d/hellographs/HelloGraphs/Main.qml index b1844aec..815e365e 100644 --- a/examples/graphs/2d/hellographs/HelloGraphs/Main.qml +++ b/examples/graphs/2d/hellographs/HelloGraphs/Main.qml @@ -30,23 +30,24 @@ Item { GraphsView { anchors.fill: parent anchors.margins: 16 - theme: GraphTheme { - colorTheme: GraphTheme.ColorThemeDark + axisX: BarCategoryAxis { + categories: [2024, 2025, 2026] + gridVisible: false + subGridVisible: false + } + axisY: ValueAxis { + min: 20 + max: 100 + tickInterval: 10 + subTickCount: 9 + } + theme: GraphsTheme { + colorScheme: Qt.Dark + theme: GraphsTheme.Theme.QtGreen } //! [bargraph] //! [barseries] BarSeries { - axisX: BarCategoryAxis { - categories: [2024, 2025, 2026] - gridVisible: false - minorGridVisible: false - } - axisY: ValueAxis { - min: 20 - max: 100 - tickInterval: 10 - minorTickCount: 9 - } //! [barseries] //! [barset] BarSet { @@ -72,19 +73,32 @@ Item { GraphsView { anchors.fill: parent anchors.margins: 16 - theme: GraphTheme { + theme: GraphsTheme { readonly property color c1: "#DBEB00" readonly property color c2: "#373F26" readonly property color c3: Qt.lighter(c2, 1.5) - colorTheme: GraphTheme.ColorThemeDark - gridMajorBarsColor: c3 - gridMinorBarsColor: c2 - axisXMajorColor: c3 - axisYMajorColor: c3 - axisXMinorColor: c2 - axisYMinorColor: c2 - axisXLabelsColor: c1 - axisYLabelsColor: c1 + colorScheme: Qt.Dark + seriesColors: ["#2CDE85", "#DBEB00"] + grid.mainColor: c3 + grid.subColor: c2 + axisX.mainColor: c3 + axisY.mainColor: c3 + axisX.subColor: c2 + axisY.subColor: c2 + axisX.labelTextColor: c1 + axisY.labelTextColor: c1 + } + axisX: ValueAxis { + max: 5 + tickInterval: 1 + subTickCount: 9 + labelDecimals: 1 + } + axisY: ValueAxis { + max: 10 + tickInterval: 1 + subTickCount: 4 + labelDecimals: 1 } //! [linegraph] @@ -99,31 +113,11 @@ Item { } //! [linemarker] - //! [lineseriestheme] - SeriesTheme { - id: seriesTheme - colors: ["#2CDE85", "#DBEB00"] - } - //! [lineseriestheme] - //! [lineseries1] LineSeries { id: lineSeries1 - theme: seriesTheme - axisX: ValueAxis { - max: 5 - tickInterval: 1 - minorTickCount: 9 - labelDecimals: 1 - } - axisY: ValueAxis { - max: 10 - tickInterval: 1 - minorTickCount: 4 - labelDecimals: 1 - } width: 4 - pointMarker: Marker { } + pointDelegate: Marker { } XYPoint { x: 0; y: 0 } XYPoint { x: 1; y: 2.1 } XYPoint { x: 2; y: 3.3 } @@ -136,9 +130,8 @@ Item { //! [lineseries2] LineSeries { id: lineSeries2 - theme: seriesTheme width: 4 - pointMarker: Marker { } + pointDelegate: Marker { } XYPoint { x: 0; y: 5.0 } XYPoint { x: 1; y: 3.3 } XYPoint { x: 2; y: 7.1 } diff --git a/examples/graphs/2d/hellographs/main.py b/examples/graphs/2d/hellographs/main.py index acc349be..aff9d129 100644 --- a/examples/graphs/2d/hellographs/main.py +++ b/examples/graphs/2d/hellographs/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt Hello Graphs example from Qt v6.x""" diff --git a/examples/graphs/3d/bars/Bars/Axes.qml b/examples/graphs/3d/bars/Bars/Axes.qml new file mode 100644 index 00000000..db87a741 --- /dev/null +++ b/examples/graphs/3d/bars/Bars/Axes.qml @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtGraphs + +Item { + property alias column: columnAxis + property alias row: rowAxis + property alias value: valueAxis + property alias total: totalAxis + + // Custom labels for columns, since the data contains abbreviated month names. + //! [0] + Category3DAxis { + id: columnAxis + labels: ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"] + labelAutoAngle: 30 + } + //! [0] + Category3DAxis { + id: totalAxis + labels: ["Yearly total"] + labelAutoAngle: 30 + } + Category3DAxis { + // For row labels we can use row labels from data proxy, no labels defined for rows. + id: rowAxis + labelAutoAngle: 30 + } + + Value3DAxis { + id: valueAxis + min: 0 + max: 35 + labelFormat: "%.2f M\u20AC" + title: "Monthly income" + labelAutoAngle: 90 + } +} diff --git a/examples/graphs/3d/bars/Bars/Data.qml b/examples/graphs/3d/bars/Bars/Data.qml new file mode 100644 index 00000000..b088d4bb --- /dev/null +++ b/examples/graphs/3d/bars/Bars/Data.qml @@ -0,0 +1,118 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQml.Models + +Item { + property alias model: dataModel + + property var modelAsJsArray: { + var arr = []; + for (var i = 0; i < dataModel.count; i++) { + var row = dataModel.get(i); + arr.push({ + timestamp: row.timestamp, + expenses: row.expenses, + income: row.income + }); + } + return arr; + } + + //! [0] + ListModel { + id: dataModel + ListElement{ timestamp: "2016-01"; expenses: "-4"; income: "5" } + ListElement{ timestamp: "2016-02"; expenses: "-5"; income: "6" } + ListElement{ timestamp: "2016-03"; expenses: "-7"; income: "4" } + //! [0] + ListElement{ timestamp: "2016-04"; expenses: "-3"; income: "2" } + ListElement{ timestamp: "2016-05"; expenses: "-4"; income: "1" } + ListElement{ timestamp: "2016-06"; expenses: "-2"; income: "2" } + ListElement{ timestamp: "2016-07"; expenses: "-1"; income: "3" } + ListElement{ timestamp: "2016-08"; expenses: "-5"; income: "1" } + ListElement{ timestamp: "2016-09"; expenses: "-2"; income: "3" } + ListElement{ timestamp: "2016-10"; expenses: "-5"; income: "2" } + ListElement{ timestamp: "2016-11"; expenses: "-8"; income: "5" } + ListElement{ timestamp: "2016-12"; expenses: "-3"; income: "3" } + + ListElement{ timestamp: "2017-01"; expenses: "-3"; income: "1" } + ListElement{ timestamp: "2017-02"; expenses: "-4"; income: "2" } + ListElement{ timestamp: "2017-03"; expenses: "-12"; income: "4" } + ListElement{ timestamp: "2017-04"; expenses: "-13"; income: "6" } + ListElement{ timestamp: "2017-05"; expenses: "-14"; income: "11" } + ListElement{ timestamp: "2017-06"; expenses: "-7"; income: "7" } + ListElement{ timestamp: "2017-07"; expenses: "-6"; income: "4" } + ListElement{ timestamp: "2017-08"; expenses: "-4"; income: "15" } + ListElement{ timestamp: "2017-09"; expenses: "-2"; income: "18" } + ListElement{ timestamp: "2017-10"; expenses: "-29"; income: "25" } + ListElement{ timestamp: "2017-11"; expenses: "-23"; income: "29" } + ListElement{ timestamp: "2017-12"; expenses: "-5"; income: "9" } + + ListElement{ timestamp: "2018-01"; expenses: "-3"; income: "8" } + ListElement{ timestamp: "2018-02"; expenses: "-8"; income: "14" } + ListElement{ timestamp: "2018-03"; expenses: "-10"; income: "20" } + ListElement{ timestamp: "2018-04"; expenses: "-12"; income: "24" } + ListElement{ timestamp: "2018-05"; expenses: "-10"; income: "19" } + ListElement{ timestamp: "2018-06"; expenses: "-5"; income: "8" } + ListElement{ timestamp: "2018-07"; expenses: "-1"; income: "4" } + ListElement{ timestamp: "2018-08"; expenses: "-7"; income: "12" } + ListElement{ timestamp: "2018-09"; expenses: "-4"; income: "16" } + ListElement{ timestamp: "2018-10"; expenses: "-22"; income: "33" } + ListElement{ timestamp: "2018-11"; expenses: "-16"; income: "25" } + ListElement{ timestamp: "2018-12"; expenses: "-2"; income: "7" } + + ListElement{ timestamp: "2019-01"; expenses: "-4"; income: "5" } + ListElement{ timestamp: "2019-02"; expenses: "-4"; income: "7" } + ListElement{ timestamp: "2019-03"; expenses: "-11"; income: "14" } + ListElement{ timestamp: "2019-04"; expenses: "-16"; income: "22" } + ListElement{ timestamp: "2019-05"; expenses: "-3"; income: "5" } + ListElement{ timestamp: "2019-06"; expenses: "-4"; income: "8" } + ListElement{ timestamp: "2019-07"; expenses: "-7"; income: "9" } + ListElement{ timestamp: "2019-08"; expenses: "-9"; income: "13" } + ListElement{ timestamp: "2019-09"; expenses: "-1"; income: "6" } + ListElement{ timestamp: "2019-10"; expenses: "-14"; income: "25" } + ListElement{ timestamp: "2019-11"; expenses: "-19"; income: "29" } + ListElement{ timestamp: "2019-12"; expenses: "-5"; income: "7" } + + ListElement{ timestamp: "2020-01"; expenses: "-14"; income: "22" } + ListElement{ timestamp: "2020-02"; expenses: "-5"; income: "7" } + ListElement{ timestamp: "2020-03"; expenses: "-1"; income: "9" } + ListElement{ timestamp: "2020-04"; expenses: "-1"; income: "12" } + ListElement{ timestamp: "2020-05"; expenses: "-5"; income: "9" } + ListElement{ timestamp: "2020-06"; expenses: "-5"; income: "8" } + ListElement{ timestamp: "2020-07"; expenses: "-3"; income: "7" } + ListElement{ timestamp: "2020-08"; expenses: "-1"; income: "5" } + ListElement{ timestamp: "2020-09"; expenses: "-2"; income: "4" } + ListElement{ timestamp: "2020-10"; expenses: "-10"; income: "13" } + ListElement{ timestamp: "2020-11"; expenses: "-12"; income: "17" } + ListElement{ timestamp: "2020-12"; expenses: "-6"; income: "9" } + + ListElement{ timestamp: "2021-01"; expenses: "-2"; income: "6" } + ListElement{ timestamp: "2021-02"; expenses: "-4"; income: "8" } + ListElement{ timestamp: "2021-03"; expenses: "-7"; income: "12" } + ListElement{ timestamp: "2021-04"; expenses: "-9"; income: "15" } + ListElement{ timestamp: "2021-05"; expenses: "-7"; income: "19" } + ListElement{ timestamp: "2021-06"; expenses: "-9"; income: "18" } + ListElement{ timestamp: "2021-07"; expenses: "-13"; income: "17" } + ListElement{ timestamp: "2021-08"; expenses: "-5"; income: "9" } + ListElement{ timestamp: "2021-09"; expenses: "-3"; income: "8" } + ListElement{ timestamp: "2021-10"; expenses: "-13"; income: "15" } + ListElement{ timestamp: "2021-11"; expenses: "-8"; income: "17" } + ListElement{ timestamp: "2021-12"; expenses: "-7"; income: "10" } + + ListElement{ timestamp: "2022-01"; expenses: "-12"; income: "16" } + ListElement{ timestamp: "2022-02"; expenses: "-24"; income: "28" } + ListElement{ timestamp: "2022-03"; expenses: "-27"; income: "22" } + ListElement{ timestamp: "2022-04"; expenses: "-29"; income: "25" } + ListElement{ timestamp: "2022-05"; expenses: "-27"; income: "29" } + ListElement{ timestamp: "2022-06"; expenses: "-19"; income: "18" } + ListElement{ timestamp: "2022-07"; expenses: "-13"; income: "17" } + ListElement{ timestamp: "2022-08"; expenses: "-15"; income: "19" } + ListElement{ timestamp: "2022-09"; expenses: "-3"; income: "8" } + ListElement{ timestamp: "2022-10"; expenses: "-3"; income: "6" } + ListElement{ timestamp: "2022-11"; expenses: "-4"; income: "8" } + ListElement{ timestamp: "2022-12"; expenses: "-5"; income: "9" } + } +} diff --git a/examples/graphs/3d/bars/Bars/Main.qml b/examples/graphs/3d/bars/Bars/Main.qml new file mode 100644 index 00000000..e839fc39 --- /dev/null +++ b/examples/graphs/3d/bars/Bars/Main.qml @@ -0,0 +1,484 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls.Basic +import QtQuick.Layouts +import QtGraphs +import Qt.labs.qmlmodels + +pragma ComponentBehavior: Bound + +Item { + id: mainview + width: 1280 + height: 1024 + + property int buttonLayoutHeight: 180 + property int currentRow + state: Screen.width < Screen.height ? "portrait" : "landscape" + + Data { + id: graphData + } + + Axes { + id: graphAxes + } + + property Bar3DSeries selectedSeries + selectedSeries: barSeries + + function handleSelectionChange(series, position) { + if (position !== series.invalidSelectionPosition) + selectedSeries = series + + // Set tableView current row to selected bar + var rowRole = series.rowLabels[position.x] + var colRole + if (barGraph.columnAxis == graphAxes.total) + colRole = "01" + else + colRole = series.columnLabels[position.y] + var checkTimestamp = rowRole + "-" + colRole + + if (currentRow === -1 || checkTimestamp !== graphData.model.get(currentRow).timestamp) { + var totalRows = tableView.rows + for (var i = 0; i < totalRows; i++) { + var modelTimestamp = graphData.model.get(i).timestamp + if (modelTimestamp === checkTimestamp) { + currentRow = i + break + } + } + } + } + + ColumnLayout { + id: tableViewLayout + + anchors.top: parent.top + anchors.left: parent.left + + HorizontalHeaderView { + id: headerView + readonly property var columnNames: ["Month", "Expenses", "Income"] + + syncView: tableView + Layout.fillWidth: true + delegate: Text { + required property int index + padding: 3 + text: headerView.columnNames[index] + color: barGraph.theme.labelTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + } + + TableView { + id: tableView + Layout.fillWidth: true + Layout.fillHeight: true + + reuseItems: false + clip: true + + model: TableModel { + id: tableModel + TableModelColumn { display: "timestamp" } + TableModelColumn { display: "expenses" } + TableModelColumn { display: "income" } + + rows: graphData.modelAsJsArray + } + + delegate: Rectangle { + id: delegateRoot + required property int row + required property int column + required property string display + implicitHeight: 30 + implicitWidth: column === 0 ? tableView.width / 2 : tableView.width / 4 + color: row === mainview.currentRow ? barGraph.theme.grid.mainColor + : barGraph.theme.backgroundColor + border.color: row === mainview.currentRow ? barGraph.theme.labelTextColor + : barGraph.theme.grid.mainColor + border.width: 1 + MouseArea { + anchors.fill: parent + onClicked: { + mainview.currentRow = delegateRoot.row + + //! [2] + var timestamp = graphData.model.get(mainview.currentRow).timestamp + var pattern = /(\d\d\d\d)-(\d\d)/ + var matches = pattern.exec(timestamp) + var rowIndex = modelProxy.rowCategoryIndex(matches[1]) + var colIndex + + if (barGraph.columnAxis == graphAxes.total) + colIndex = 0 // Just one column when showing yearly totals + else + colIndex = modelProxy.columnCategoryIndex(matches[2]) + + if (selectedSeries.visible) + mainview.selectedSeries.selectedBar = Qt.point(rowIndex, colIndex) + else if (barSeries.visible) + barSeries.selectedBar = Qt.point(rowIndex, colIndex) + else + secondarySeries.selectedBar = Qt.point(rowIndex, colIndex) + //! [2] + } + } + + Text { + id: delegateText + anchors.verticalCenter: parent.verticalCenter + width: parent.width + anchors.leftMargin: 4 + anchors.left: parent.left + anchors.right: parent.right + text: formattedText + property string formattedText: { + if (delegateRoot.column === 0) { + if (delegateRoot.display !== "") { + var pattern = /(\d\d\d\d)-(\d\d)/ + var matches = pattern.exec(delegateRoot.display) + var colIndex = parseInt(matches[2], 10) - 1 + return matches[1] + " - " + graphAxes.column.labels[colIndex] + } + } else { + return delegateRoot.display + } + } + color: delegateRoot.row === mainview.currentRow ? barGraph.theme.backgroundColor + : barGraph.theme.labelTextColor + horizontalAlignment: delegateRoot.column === 0 ? Text.AlignLeft + : Text.AlignHCenter + elide: Text.ElideRight + } + } + } + } + + ColumnLayout { + id: controlLayout + spacing: 0 + + Button { + id: changeDataButton + Layout.fillWidth: true + Layout.fillHeight: true + text: "Show 2020 - 2022" + clip: true + //! [1] + onClicked: { + if (text === "Show yearly totals") { + modelProxy.autoRowCategories = true + secondaryProxy.autoRowCategories = true + modelProxy.columnRolePattern = /^.*$/ + secondaryProxy.columnRolePattern = /^.*$/ + graphAxes.value.autoAdjustRange = true + barGraph.columnAxis = graphAxes.total + text = "Show all years" + } else if (text === "Show all years") { + modelProxy.autoRowCategories = true + secondaryProxy.autoRowCategories = true + modelProxy.columnRolePattern = /^.*-(\d\d)$/ + secondaryProxy.columnRolePattern = /^.*-(\d\d)$/ + graphAxes.value.min = 0 + graphAxes.value.max = 35 + barGraph.columnAxis = graphAxes.column + text = "Show 2020 - 2022" + } else { // text === "Show 2020 - 2022" + // Explicitly defining row categories, since we do not want to show data for + // all years in the model, just for the selected ones. + modelProxy.autoRowCategories = false + secondaryProxy.autoRowCategories = false + modelProxy.rowCategories = ["2020", "2021", "2022"] + secondaryProxy.rowCategories = ["2020", "2021", "2022"] + text = "Show yearly totals" + } + } + //! [1] + + contentItem: Text { + text: changeDataButton.text + opacity: changeDataButton.enabled ? 1.0 : 0.3 + color: barGraph.theme.labelTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + opacity: changeDataButton.enabled ? 1 : 0.3 + color: changeDataButton.down ? barGraph.theme.grid.mainColor : barGraph.theme.backgroundColor + border.color: changeDataButton.down ? barGraph.theme.labelTextColor : barGraph.theme.grid.mainColor + border.width: 1 + radius: 2 + } + } + + Button { + id: shadowToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Hide Shadows" + clip: true + onClicked: { + if (barGraph.shadowQuality == Graphs3D.ShadowQuality.None) { + barGraph.shadowQuality = Graphs3D.ShadowQuality.SoftHigh + text = "Hide Shadows" + } else { + barGraph.shadowQuality = Graphs3D.ShadowQuality.None + text = "Show Shadows" + } + } + contentItem: Text { + text: shadowToggle.text + opacity: shadowToggle.enabled ? 1.0 : 0.3 + color: barGraph.theme.labelTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + opacity: shadowToggle.enabled ? 1 : 0.3 + color: shadowToggle.down ? barGraph.theme.grid.mainColor : barGraph.theme.backgroundColor + border.color: shadowToggle.down ? barGraph.theme.labelTextColor : barGraph.theme.grid.mainColor + border.width: 1 + radius: 2 + } + } + + Button { + id: seriesToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Show Expenses" + clip: true + //! [0] + onClicked: { + if (text === "Show Expenses") { + barSeries.visible = false + secondarySeries.visible = true + barGraph.valueAxis.labelFormat = "-%.2f M\u20AC" + secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: @valueLabel" + text = "Show Both" + } else if (text === "Show Both") { + barSeries.visible = true + barGraph.valueAxis.labelFormat = "%.2f M\u20AC" + secondarySeries.itemLabelFormat = "Expenses, @colLabel, @rowLabel: -@valueLabel" + text = "Show Income" + } else { // text === "Show Income" + secondarySeries.visible = false + text = "Show Expenses" + } + } + //! [0] + contentItem: Text { + text: seriesToggle.text + opacity: seriesToggle.enabled ? 1.0 : 0.3 + color: barGraph.theme.labelTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + opacity: seriesToggle.enabled ? 1 : 0.3 + color: seriesToggle.down ? barGraph.theme.grid.mainColor : barGraph.theme.backgroundColor + border.color: seriesToggle.down ? barGraph.theme.labelTextColor : barGraph.theme.grid.mainColor + border.width: 1 + radius: 2 + } + } + + Button { + id: marginToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Use Margin" + clip: true + + onClicked: { + if (text === "Use Margin") { + barGraph.barSeriesMargin = Qt.size(0.2, 0.2) + barGraph.barSpacing = Qt.size(0.0, 0.0) + text = "Use Spacing" + } else if (text === "Use Spacing") { + barGraph.barSeriesMargin = Qt.size(0.0, 0.0) + barGraph.barSpacing = Qt.size(0.5, 0.5) + text = "Use Margin" + } + } + contentItem: Text { + text: marginToggle.text + opacity: marginToggle.enabled ? 1.0 : 0.3 + color: barGraph.theme.labelTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + opacity: marginToggle.enabled ? 1 : 0.3 + color: marginToggle.down ? barGraph.theme.grid.mainColor : barGraph.theme.backgroundColor + border.color: marginToggle.down ? barGraph.theme.labelTextColor : barGraph.theme.grid.mainColor + border.width: 1 + radius: 2 + } + } + } + + Item { + id: dataView + anchors.right: mainview.right + anchors.bottom: mainview.bottom + + Bars3D { + id: barGraph + anchors.fill: parent + shadowQuality: Graphs3D.ShadowQuality.SoftHigh + selectionMode: Graphs3D.SelectionFlag.Item + theme: GraphsTheme { + colorScheme: GraphsTheme.ColorScheme.Dark + labelBorderVisible: true + labelFont.pointSize: 35 + labelBackgroundVisible: true + colorStyle: GraphsTheme.ColorStyle.RangeGradient + singleHighlightGradient: customGradient + + Gradient { + id: customGradient + GradientStop { position: 1.0; color: "#FFFF00" } + GradientStop { position: 0.0; color: "#808000" } + } + } + barThickness: 0.7 + barSpacing: Qt.size(0.5, 0.5) + barSpacingRelative: false + cameraPreset: Graphs3D.CameraPreset.IsometricLeftHigh + columnAxis: graphAxes.column + rowAxis: graphAxes.row + valueAxis: graphAxes.value + + //! [4] + Bar3DSeries { + id: secondarySeries + visible: false + itemLabelFormat: "Expenses, @colLabel, @rowLabel: -@valueLabel" + baseGradient: secondaryGradient + + ItemModelBarDataProxy { + id: secondaryProxy + itemModel: graphData.model + rowRole: "timestamp" + columnRole: "timestamp" + valueRole: "expenses" + rowRolePattern: /^(\d\d\d\d).*$/ + columnRolePattern: /^.*-(\d\d)$/ + valueRolePattern: /-/ + rowRoleReplace: "\\1" + columnRoleReplace: "\\1" + multiMatchBehavior: ItemModelBarDataProxy.MultiMatchBehavior.Cumulative + } + //! [4] + + Gradient { + id: secondaryGradient + GradientStop { position: 1.0; color: "#FF0000" } + GradientStop { position: 0.0; color: "#600000" } + } + + onSelectedBarChanged: (position) => mainview.handleSelectionChange(secondarySeries, + position) + } + + //! [3] + Bar3DSeries { + id: barSeries + itemLabelFormat: "Income, @colLabel, @rowLabel: @valueLabel" + baseGradient: barGradient + + ItemModelBarDataProxy { + id: modelProxy + itemModel: graphData.model + rowRole: "timestamp" + columnRole: "timestamp" + valueRole: "income" + rowRolePattern: /^(\d\d\d\d).*$/ + columnRolePattern: /^.*-(\d\d)$/ + rowRoleReplace: "\\1" + columnRoleReplace: "\\1" + multiMatchBehavior: ItemModelBarDataProxy.MultiMatchBehavior.Cumulative + } + //! [3] + + Gradient { + id: barGradient + GradientStop { position: 1.0; color: "#00FF00" } + GradientStop { position: 0.0; color: "#006000" } + } + + onSelectedBarChanged: (position) => mainview.handleSelectionChange(barSeries, + position) + } + } + } + + states: [ + State { + name: "landscape" + PropertyChanges { + target: dataView + width: mainview.width / 4 * 3 + height: mainview.height + } + PropertyChanges { + target: tableViewLayout + height: mainview.height - buttonLayoutHeight + anchors.right: dataView.left + anchors.left: mainview.left + anchors.bottom: undefined + } + PropertyChanges { + target: controlLayout + width: mainview.width / 4 + height: buttonLayoutHeight + anchors.top: tableViewLayout.bottom + anchors.bottom: mainview.bottom + anchors.left: mainview.left + anchors.right: dataView.left + } + }, + State { + name: "portrait" + PropertyChanges { + target: dataView + width: mainview.width + height: mainview.width + } + PropertyChanges { + target: tableViewLayout + height: mainview.width + anchors.right: controlLayout.left + anchors.left: mainview.left + anchors.bottom: dataView.top + } + PropertyChanges { + target: controlLayout + width: mainview.height / 4 + height: mainview.width / 4 + anchors.top: mainview.top + anchors.bottom: dataView.top + anchors.left: undefined + anchors.right: mainview.right + } + } + ] +} diff --git a/examples/graphs/3d/bars/Bars/qmldir b/examples/graphs/3d/bars/Bars/qmldir new file mode 100644 index 00000000..d6ace351 --- /dev/null +++ b/examples/graphs/3d/bars/Bars/qmldir @@ -0,0 +1,4 @@ +module Bars +Main 1.0 Main.qml +Axes 1.0 Axes.qml +Data 1.0 Data.qml diff --git a/examples/graphs/3d/bars/bars.pyproject b/examples/graphs/3d/bars/bars.pyproject new file mode 100644 index 00000000..86628474 --- /dev/null +++ b/examples/graphs/3d/bars/bars.pyproject @@ -0,0 +1,9 @@ +{ + "files": [ + "main.py", + "Bars/Main.qml", + "Bars/Data.qml", + "Bars/Axes.qml", + "Bars/qmldir" + ] +} diff --git a/examples/graphs/3d/bars/doc/bars-example.webp b/examples/graphs/3d/bars/doc/bars-example.webp new file mode 100644 index 0000000000000000000000000000000000000000..8d55d57a50e6e5248e5e5c88869dc17bb425d245 GIT binary patch literal 48706 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 self._areaMaxValue: - dist = self._axisXMaxValue - self._axisXMinValue - self._axisXMaxValue = self._areaMaxValue - self._axisXMinValue = self._axisXMaxValue - dist - - self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue) - elif self._state == InputState.StateDraggingZ: - distance = (move.x() * zMulX + move.y() * zMulY) * self._speedModifier - self._axisZMinValue += distance - self._axisZMaxValue += distance - if self._axisZMinValue < self._areaMinValue: - dist = self._axisZMaxValue - self._axisZMinValue - self._axisZMinValue = self._areaMinValue - self._axisZMaxValue = self._axisZMinValue + dist - - if self._axisZMaxValue > self._areaMaxValue: - dist = self._axisZMaxValue - self._axisZMinValue - self._axisZMaxValue = self._areaMaxValue - self._axisZMinValue = self._axisZMaxValue - dist - - self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue) - - def checkConstraints(self): - if self._axisXMinValue < self._areaMinValue: - self._axisXMinValue = self._areaMinValue - if self._axisXMaxValue > self._areaMaxValue: - self._axisXMaxValue = self._areaMaxValue - # Don't allow too much zoom in - range = self._axisXMaxValue - self._axisXMinValue - if range < self._axisXMinRange: - adjust = (self._axisXMinRange - range) / 2.0 - self._axisXMinValue -= adjust - self._axisXMaxValue += adjust - - if self._axisZMinValue < self._areaMinValue: - self._axisZMinValue = self._areaMinValue - if self._axisZMaxValue > self._areaMaxValue: - self._axisZMaxValue = self._areaMaxValue - # Don't allow too much zoom in - range = self._axisZMaxValue - self._axisZMinValue - if range < self._axisZMinRange: - adjust = (self._axisZMinRange - range) / 2.0 - self._axisZMinValue -= adjust - self._axisZMaxValue += adjust diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst b/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst deleted file mode 100644 index 1470001d..00000000 --- a/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst +++ /dev/null @@ -1,11 +0,0 @@ -Widget Gallery -============== - - -Widget Gallery demonstrates all three graph types and some of their special -features. The graphs have their own tabs in the application. - - -.. image:: widgetgallery.webp - :width: 400 - :alt: Widget Screenshot diff --git a/examples/graphs/3d/widgetgallery/main.py b/examples/graphs/3d/widgetgallery/main.py deleted file mode 100644 index 7bb2238a..00000000 --- a/examples/graphs/3d/widgetgallery/main.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -"""PySide6 port of the Qt Graphs widgetgallery example from Qt v6.x""" - -import sys - -from PySide6.QtCore import QSize -from PySide6.QtWidgets import QApplication, QTabWidget - -from bargraph import BarGraph -from scattergraph import ScatterGraph -from surfacegraph import SurfaceGraph - - -if __name__ == "__main__": - app = QApplication(sys.argv) - - # Create a tab widget for creating own tabs for Q3DBars, Q3DScatter, and Q3DSurface - tabWidget = QTabWidget() - tabWidget.setWindowTitle("Widget Gallery") - - screen_size = tabWidget.screen().size() - minimum_graph_size = QSize(screen_size.width() / 2, screen_size.height() / 1.75) - - # Create bar graph - bars = BarGraph(minimum_graph_size, screen_size) - # Create scatter graph - scatter = ScatterGraph(minimum_graph_size, screen_size) - # Create surface graph - surface = SurfaceGraph(minimum_graph_size, screen_size) - - # Add bars widget - tabWidget.addTab(bars.barsWidget(), "Bar Graph") - # Add scatter widget - tabWidget.addTab(scatter.scatterWidget(), "Scatter Graph") - # Add surface widget - tabWidget.addTab(surface.surfaceWidget(), "Surface Graph") - - tabWidget.show() - sys.exit(app.exec()) diff --git a/examples/graphs/3d/widgetgallery/scatterdatamodifier.py b/examples/graphs/3d/widgetgallery/scatterdatamodifier.py deleted file mode 100644 index 15064b41..00000000 --- a/examples/graphs/3d/widgetgallery/scatterdatamodifier.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from math import cos, degrees, sqrt - -from PySide6.QtCore import QObject, Signal, Slot, Qt -from PySide6.QtGui import QVector3D -from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, - QScatterDataItem, QScatterDataProxy, - QScatter3DSeries, Q3DTheme) - -from axesinputhandler import AxesInputHandler - - -NUMBER_OF_ITEMS = 10000 -CURVE_DIVIDER = 7.5 -LOWER_NUMBER_OF_ITEMS = 900 -LOWER_CURVE_DIVIDER = 0.75 - - -class ScatterDataModifier(QObject): - - backgroundEnabledChanged = Signal(bool) - gridEnabledChanged = Signal(bool) - shadowQualityChanged = Signal(int) - - def __init__(self, scatter, parent): - super().__init__(parent) - - self._graph = scatter - - self._style = QAbstract3DSeries.Mesh.Sphere - self._smooth = True - self._inputHandler = AxesInputHandler(scatter) - self._autoAdjust = True - self._itemCount = LOWER_NUMBER_OF_ITEMS - self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER - self._inputHandler = AxesInputHandler(scatter) - - self._graph.activeTheme().setType(Q3DTheme.Theme.StoneMoss) - self._graph.setShadowQuality(QAbstract3DGraph.ShadowQuality.SoftHigh) - self._graph.setCameraPreset(QAbstract3DGraph.CameraPreset.Front) - self._graph.setCameraZoomLevel(80.0) - - self._proxy = QScatterDataProxy() - self._series = QScatter3DSeries(self._proxy) - self._series.setItemLabelFormat("@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel") - self._series.setMeshSmooth(self._smooth) - self._graph.addSeries(self._series) - self._preset = QAbstract3DGraph.CameraPreset.FrontLow.value - - # Give ownership of the handler to the graph and make it the active - # handler - self._graph.setActiveInputHandler(self._inputHandler) - - # Give our axes to the input handler - self._inputHandler.setAxes(self._graph.axisX(), self._graph.axisZ(), - self._graph.axisY()) - - self.addData() - - def addData(self): - # Configure the axes according to the data - self._graph.axisX().setTitle("X") - self._graph.axisY().setTitle("Y") - self._graph.axisZ().setTitle("Z") - - dataArray = [] - limit = int(sqrt(self._itemCount) / 2.0) - for i in range(-limit, limit): - for j in range(-limit, limit): - x = float(i) + 0.5 - y = cos(degrees(float(i * j) / self._CURVE_DIVIDER)) - z = float(j) + 0.5 - dataArray.append(QScatterDataItem(QVector3D(x, y, z))) - - self._graph.seriesList()[0].dataProxy().resetArray(dataArray) - - @Slot(int) - def changeStyle(self, style): - comboBox = self.sender() - if comboBox: - self._style = comboBox.itemData(style) - if self._graph.seriesList(): - self._graph.seriesList()[0].setMesh(self._style) - - @Slot(int) - def setSmoothDots(self, smooth): - self._smooth = smooth == Qt.Checked.value - series = self._graph.seriesList()[0] - series.setMeshSmooth(self._smooth) - - @Slot(int) - def changeTheme(self, theme): - currentTheme = self._graph.activeTheme() - currentTheme.setType(Q3DTheme.Theme(theme)) - self.backgroundEnabledChanged.emit(currentTheme.isBackgroundEnabled()) - self.gridEnabledChanged.emit(currentTheme.isGridEnabled()) - - @Slot() - def changePresetCamera(self): - camera = self._graph.scene().activeCamera() - camera.setCameraPreset(QAbstract3DGraph.CameraPreset(self._preset)) - - self._preset += 1 - if self._preset > QAbstract3DGraph.CameraPreset.DirectlyBelow.value: - self._preset = QAbstract3DGraph.CameraPreset.FrontLow.value - - @Slot(QAbstract3DGraph.ShadowQuality) - def shadowQualityUpdatedByVisual(self, sq): - self.shadowQualityChanged.emit(sq.value) - - @Slot(int) - def changeShadowQuality(self, quality): - sq = QAbstract3DGraph.ShadowQuality(quality) - self._graph.setShadowQuality(sq) - - @Slot(int) - def setBackgroundEnabled(self, enabled): - self._graph.activeTheme().setBackgroundEnabled(enabled == Qt.Checked.value) - - @Slot(int) - def setGridEnabled(self, enabled): - self._graph.activeTheme().setGridEnabled(enabled == Qt.Checked.value) - - @Slot() - def toggleItemCount(self): - if self._itemCount == NUMBER_OF_ITEMS: - self._itemCount = LOWER_NUMBER_OF_ITEMS - self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER - else: - self._itemCount = NUMBER_OF_ITEMS - self._CURVE_DIVIDER = CURVE_DIVIDER - - self._graph.seriesList()[0].dataProxy().resetArray([]) - self.addData() - - @Slot() - def toggleRanges(self): - if not self._autoAdjust: - self._graph.axisX().setAutoAdjustRange(True) - self._graph.axisZ().setAutoAdjustRange(True) - self._inputHandler.setDragSpeedModifier(1.5) - self._autoAdjust = True - else: - self._graph.axisX().setRange(-10.0, 10.0) - self._graph.axisZ().setRange(-10.0, 10.0) - self._inputHandler.setDragSpeedModifier(15.0) - self._autoAdjust = False diff --git a/examples/graphs/3d/widgetgallery/bargraph.py b/examples/graphs/3d/widgetgraphgallery/bargraph.py similarity index 55% rename from examples/graphs/3d/widgetgallery/bargraph.py rename to examples/graphs/3d/widgetgraphgallery/bargraph.py index 822acb4a..3259e8a3 100644 --- a/examples/graphs/3d/widgetgallery/bargraph.py +++ b/examples/graphs/3d/widgetgraphgallery/bargraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from graphmodifier import GraphModifier @@ -9,35 +10,38 @@ from PySide6.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QFontComboBox QLabel, QPushButton, QHBoxLayout, QSizePolicy, QRadioButton, QSlider, QVBoxLayout, QWidget) from PySide6.QtQuickWidgets import QQuickWidget -from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, Q3DBars) +from PySide6.QtGraphs import QAbstract3DSeries, QtGraphs3D +from PySide6.QtGraphsWidgets import Q3DBarsWidgetItem class BarGraph(QObject): def __init__(self, minimum_graph_size, maximum_graph_size): super().__init__() - self._barsGraph = Q3DBars() + + barsGraph = Q3DBarsWidgetItem() + barsGraphWidget = QQuickWidget() + barsGraph.setWidget(barsGraphWidget) self._barsWidget = QWidget() hLayout = QHBoxLayout(self._barsWidget) - self._barsGraph.setMinimumSize(minimum_graph_size) - self._barsGraph.setMaximumSize(maximum_graph_size) - self._barsGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self._barsGraph.setFocusPolicy(Qt.StrongFocus) - self._barsGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) - hLayout.addWidget(self._barsGraph, 1) + barsGraphWidget.setMinimumSize(minimum_graph_size) + barsGraphWidget.setMaximumSize(maximum_graph_size) + barsGraphWidget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + barsGraphWidget.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + hLayout.addWidget(barsGraphWidget, 1) vLayout = QVBoxLayout() hLayout.addLayout(vLayout) themeList = QComboBox(self._barsWidget) - themeList.addItem("Qt") - themeList.addItem("Primary Colors") - themeList.addItem("Digia") - themeList.addItem("Stone Moss") - themeList.addItem("Army Blue") - themeList.addItem("Retro") - themeList.addItem("Ebony") - themeList.addItem("Isabelle") + themeList.addItem("QtGreen") + themeList.addItem("QtGreenNeon") + themeList.addItem("MixSeries") + themeList.addItem("OrangeSeries") + themeList.addItem("YellowSeries") + themeList.addItem("BlueSeries") + themeList.addItem("PurpleSeries") + themeList.addItem("GreySeries") themeList.setCurrentIndex(0) labelButton = QPushButton(self._barsWidget) @@ -63,37 +67,37 @@ class BarGraph(QObject): zoomToSelectedButton.setText("Zoom to selected bar") selectionModeList = QComboBox(self._barsWidget) - selectionModeList.addItem("None", QAbstract3DGraph.SelectionNone) - selectionModeList.addItem("Bar", QAbstract3DGraph.SelectionItem) - selectionModeList.addItem("Row", QAbstract3DGraph.SelectionRow) - sel = QAbstract3DGraph.SelectionItemAndRow + selectionModeList.addItem("None", QtGraphs3D.SelectionFlag.None_) + selectionModeList.addItem("Bar", QtGraphs3D.SelectionFlag.Item) + selectionModeList.addItem("Row", QtGraphs3D.SelectionFlag.Row) + sel = QtGraphs3D.SelectionFlag.ItemAndRow selectionModeList.addItem("Bar and Row", sel) - selectionModeList.addItem("Column", QAbstract3DGraph.SelectionColumn) - sel = QAbstract3DGraph.SelectionItemAndColumn + selectionModeList.addItem("Column", QtGraphs3D.SelectionFlag.Column) + sel = QtGraphs3D.SelectionFlag.ItemAndColumn selectionModeList.addItem("Bar and Column", sel) - sel = QAbstract3DGraph.SelectionRowAndColumn + sel = QtGraphs3D.SelectionFlag.RowAndColumn selectionModeList.addItem("Row and Column", sel) - sel = QAbstract3DGraph.SelectionItemRowAndColumn + sel = QtGraphs3D.SelectionFlag.RowAndColumn selectionModeList.addItem("Bar, Row and Column", sel) - sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionRow + sel = QtGraphs3D.SelectionFlag.Slice | QtGraphs3D.SelectionFlag.Row selectionModeList.addItem("Slice into Row", sel) - sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndRow + sel = QtGraphs3D.SelectionFlag.Slice | QtGraphs3D.SelectionFlag.ItemAndRow selectionModeList.addItem("Slice into Row and Item", sel) - sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionColumn + sel = QtGraphs3D.SelectionFlag.Slice | QtGraphs3D.SelectionFlag.Column selectionModeList.addItem("Slice into Column", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndColumn) + sel = (QtGraphs3D.SelectionFlag.Slice + | QtGraphs3D.SelectionFlag.ItemAndColumn) selectionModeList.addItem("Slice into Column and Item", sel) - sel = (QAbstract3DGraph.SelectionItemRowAndColumn - | QAbstract3DGraph.SelectionMultiSeries) + sel = (QtGraphs3D.SelectionFlag.ItemRowAndColumn + | QtGraphs3D.SelectionFlag.MultiSeries) selectionModeList.addItem("Multi: Bar, Row, Col", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndRow - | QAbstract3DGraph.SelectionMultiSeries) + sel = (QtGraphs3D.SelectionFlag.Slice + | QtGraphs3D.SelectionFlag.ItemAndRow + | QtGraphs3D.SelectionFlag.MultiSeries) selectionModeList.addItem("Multi, Slice: Row, Item", sel) - sel = (QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionItemAndColumn - | QAbstract3DGraph.SelectionMultiSeries) + sel = (QtGraphs3D.SelectionFlag.Slice + | QtGraphs3D.SelectionFlag.ItemAndColumn + | QtGraphs3D.SelectionFlag.MultiSeries) selectionModeList.addItem("Multi, Slice: Col, Item", sel) selectionModeList.setCurrentIndex(1) @@ -113,26 +117,22 @@ class BarGraph(QObject): reverseValueAxisCheckBox.setText("Reverse value axis") reverseValueAxisCheckBox.setChecked(False) - reflectionCheckBox = QCheckBox(self._barsWidget) - reflectionCheckBox.setText("Show reflections") - reflectionCheckBox.setChecked(False) - - rotationSliderX = QSlider(Qt.Horizontal, self._barsWidget) + rotationSliderX = QSlider(Qt.Orientation.Horizontal, self._barsWidget) rotationSliderX.setTickInterval(30) - rotationSliderX.setTickPosition(QSlider.TicksBelow) + rotationSliderX.setTickPosition(QSlider.TickPosition.TicksBelow) rotationSliderX.setMinimum(-180) rotationSliderX.setValue(0) rotationSliderX.setMaximum(180) - rotationSliderY = QSlider(Qt.Horizontal, self._barsWidget) + rotationSliderY = QSlider(Qt.Orientation.Horizontal, self._barsWidget) rotationSliderY.setTickInterval(15) - rotationSliderY.setTickPosition(QSlider.TicksAbove) + rotationSliderY.setTickPosition(QSlider.TickPosition.TicksAbove) rotationSliderY.setMinimum(-90) rotationSliderY.setValue(0) rotationSliderY.setMaximum(90) - fontSizeSlider = QSlider(Qt.Horizontal, self._barsWidget) + fontSizeSlider = QSlider(Qt.Orientation.Horizontal, self._barsWidget) fontSizeSlider.setTickInterval(10) - fontSizeSlider.setTickPosition(QSlider.TicksBelow) + fontSizeSlider.setTickPosition(QSlider.TickPosition.TicksBelow) fontSizeSlider.setMinimum(1) fontSizeSlider.setValue(30) fontSizeSlider.setMaximum(100) @@ -170,9 +170,9 @@ class BarGraph(QObject): axisTitlesFixedCB.setText("Axis titles fixed") axisTitlesFixedCB.setChecked(True) - axisLabelRotationSlider = QSlider(Qt.Horizontal, self._barsWidget) + axisLabelRotationSlider = QSlider(Qt.Orientation.Horizontal, self._barsWidget) axisLabelRotationSlider.setTickInterval(10) - axisLabelRotationSlider.setTickPosition(QSlider.TicksBelow) + axisLabelRotationSlider.setTickPosition(QSlider.TickPosition.TicksBelow) axisLabelRotationSlider.setMinimum(0) axisLabelRotationSlider.setValue(30) axisLabelRotationSlider.setMaximum(90) @@ -185,16 +185,15 @@ class BarGraph(QObject): modeGroup.addButton(modeCustomProxy) vLayout.addWidget(QLabel("Rotate horizontally")) - vLayout.addWidget(rotationSliderX, 0, Qt.AlignTop) + vLayout.addWidget(rotationSliderX, 0, Qt.AlignmentFlag.AlignTop) vLayout.addWidget(QLabel("Rotate vertically")) - vLayout.addWidget(rotationSliderY, 0, Qt.AlignTop) - vLayout.addWidget(labelButton, 0, Qt.AlignTop) - vLayout.addWidget(cameraButton, 0, Qt.AlignTop) - vLayout.addWidget(zoomToSelectedButton, 0, Qt.AlignTop) + vLayout.addWidget(rotationSliderY, 0, Qt.AlignmentFlag.AlignTop) + vLayout.addWidget(labelButton, 0, Qt.AlignmentFlag.AlignTop) + vLayout.addWidget(cameraButton, 0, Qt.AlignmentFlag.AlignTop) + vLayout.addWidget(zoomToSelectedButton, 0, Qt.AlignmentFlag.AlignTop) vLayout.addWidget(backgroundCheckBox) vLayout.addWidget(gridCheckBox) vLayout.addWidget(smoothCheckBox) - vLayout.addWidget(reflectionCheckBox) vLayout.addWidget(seriesCheckBox) vLayout.addWidget(reverseValueAxisCheckBox) vLayout.addWidget(axisTitlesVisibleCB) @@ -214,54 +213,54 @@ class BarGraph(QObject): vLayout.addWidget(QLabel("Adjust font size")) vLayout.addWidget(fontSizeSlider) vLayout.addWidget(QLabel("Axis label rotation")) - vLayout.addWidget(axisLabelRotationSlider, 0, Qt.AlignTop) - vLayout.addWidget(modeWeather, 0, Qt.AlignTop) - vLayout.addWidget(modeCustomProxy, 1, Qt.AlignTop) + vLayout.addWidget(axisLabelRotationSlider, 0, Qt.AlignmentFlag.AlignTop) + vLayout.addWidget(modeWeather, 0, Qt.AlignmentFlag.AlignTop) + vLayout.addWidget(modeCustomProxy, 1, Qt.AlignmentFlag.AlignTop) - self._modifier = GraphModifier(self._barsGraph, self) + modifier = GraphModifier(barsGraph, self) + modifier.changeTheme(themeList.currentIndex()) - rotationSliderX.valueChanged.connect(self._modifier.rotateX) - rotationSliderY.valueChanged.connect(self._modifier.rotateY) + rotationSliderX.valueChanged.connect(modifier.rotateX) + rotationSliderY.valueChanged.connect(modifier.rotateY) - labelButton.clicked.connect(self._modifier.changeLabelBackground) - cameraButton.clicked.connect(self._modifier.changePresetCamera) - zoomToSelectedButton.clicked.connect(self._modifier.zoomToSelectedBar) + labelButton.clicked.connect(modifier.changeLabelBackground) + cameraButton.clicked.connect(modifier.changePresetCamera) + zoomToSelectedButton.clicked.connect(modifier.zoomToSelectedBar) - backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled) - gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled) - smoothCheckBox.stateChanged.connect(self._modifier.setSmoothBars) - seriesCheckBox.stateChanged.connect(self._modifier.setSeriesVisibility) - reverseValueAxisCheckBox.stateChanged.connect(self._modifier.setReverseValueAxis) - reflectionCheckBox.stateChanged.connect(self._modifier.setReflection) + backgroundCheckBox.checkStateChanged.connect(modifier.setPlotAreaBackgroundVisible) + gridCheckBox.checkStateChanged.connect(modifier.setGridVisible) + smoothCheckBox.checkStateChanged.connect(modifier.setSmoothBars) + seriesCheckBox.checkStateChanged.connect(modifier.setSeriesVisibility) + reverseValueAxisCheckBox.checkStateChanged.connect(modifier.setReverseValueAxis) - self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) - self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked) + modifier.backgroundVisibleChanged.connect(backgroundCheckBox.setChecked) + modifier.gridVisibleChanged.connect(gridCheckBox.setChecked) - rangeList.currentIndexChanged.connect(self._modifier.changeRange) + rangeList.currentIndexChanged.connect(modifier.changeRange) - barStyleList.currentIndexChanged.connect(self._modifier.changeStyle) + barStyleList.currentIndexChanged.connect(modifier.changeStyle) - selectionModeList.currentIndexChanged.connect(self._modifier.changeSelectionMode) + selectionModeList.currentIndexChanged.connect(modifier.changeSelectionMode) - themeList.currentIndexChanged.connect(self._modifier.changeTheme) + themeList.currentIndexChanged.connect(modifier.changeTheme) - shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality) + shadowQuality.currentIndexChanged.connect(modifier.changeShadowQuality) - self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) - self._barsGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual) + modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) + barsGraph.shadowQualityChanged.connect(modifier.shadowQualityUpdatedByVisual) - fontSizeSlider.valueChanged.connect(self._modifier.changeFontSize) - fontList.currentFontChanged.connect(self._modifier.changeFont) + fontSizeSlider.valueChanged.connect(modifier.changeFontSize) + fontList.currentFontChanged.connect(modifier.changeFont) - self._modifier.fontSizeChanged.connect(fontSizeSlider.setValue) - self._modifier.fontChanged.connect(fontList.setCurrentFont) + modifier.fontSizeChanged.connect(fontSizeSlider.setValue) + modifier.fontChanged.connect(fontList.setCurrentFont) - axisTitlesVisibleCB.stateChanged.connect(self._modifier.setAxisTitleVisibility) - axisTitlesFixedCB.stateChanged.connect(self._modifier.setAxisTitleFixed) - axisLabelRotationSlider.valueChanged.connect(self._modifier.changeLabelRotation) + axisTitlesVisibleCB.checkStateChanged.connect(modifier.setAxisTitleVisibility) + axisTitlesFixedCB.checkStateChanged.connect(modifier.setAxisTitleFixed) + axisLabelRotationSlider.valueChanged.connect(modifier.changeLabelRotation) - modeWeather.toggled.connect(self._modifier.setDataModeToWeather) - modeCustomProxy.toggled.connect(self._modifier.setDataModeToCustom) + modeWeather.toggled.connect(modifier.setDataModeToWeather) + modeCustomProxy.toggled.connect(modifier.setDataModeToCustom) modeWeather.toggled.connect(seriesCheckBox.setEnabled) modeWeather.toggled.connect(rangeList.setEnabled) modeWeather.toggled.connect(axisTitlesVisibleCB.setEnabled) diff --git a/examples/graphs/3d/widgetgallery/data/layer_1.png b/examples/graphs/3d/widgetgraphgallery/data/layer_1.png similarity index 100% rename from examples/graphs/3d/widgetgallery/data/layer_1.png rename to examples/graphs/3d/widgetgraphgallery/data/layer_1.png diff --git a/examples/graphs/3d/widgetgallery/data/layer_2.png b/examples/graphs/3d/widgetgraphgallery/data/layer_2.png similarity index 100% rename from examples/graphs/3d/widgetgallery/data/layer_2.png rename to examples/graphs/3d/widgetgraphgallery/data/layer_2.png diff --git a/examples/graphs/3d/widgetgallery/data/layer_3.png b/examples/graphs/3d/widgetgraphgallery/data/layer_3.png similarity index 100% rename from examples/graphs/3d/widgetgallery/data/layer_3.png rename to examples/graphs/3d/widgetgraphgallery/data/layer_3.png diff --git a/examples/graphs/3d/widgetgallery/data/license.txt b/examples/graphs/3d/widgetgraphgallery/data/license.txt similarity index 100% rename from examples/graphs/3d/widgetgallery/data/license.txt rename to examples/graphs/3d/widgetgraphgallery/data/license.txt diff --git a/examples/graphs/3d/widgetgallery/data/maptexture.jpg b/examples/graphs/3d/widgetgraphgallery/data/maptexture.jpg similarity index 100% rename from examples/graphs/3d/widgetgallery/data/maptexture.jpg rename to examples/graphs/3d/widgetgraphgallery/data/maptexture.jpg diff --git a/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh b/examples/graphs/3d/widgetgraphgallery/data/narrowarrow.mesh similarity index 100% rename from examples/graphs/3d/widgetgallery/data/narrowarrow.mesh rename to examples/graphs/3d/widgetgraphgallery/data/narrowarrow.mesh diff --git a/examples/graphs/3d/widgetgallery/data/oilrig.mesh b/examples/graphs/3d/widgetgraphgallery/data/oilrig.mesh similarity index 100% rename from examples/graphs/3d/widgetgallery/data/oilrig.mesh rename to examples/graphs/3d/widgetgraphgallery/data/oilrig.mesh diff --git a/examples/graphs/3d/widgetgallery/data/pipe.mesh b/examples/graphs/3d/widgetgraphgallery/data/pipe.mesh similarity index 100% rename from examples/graphs/3d/widgetgallery/data/pipe.mesh rename to examples/graphs/3d/widgetgraphgallery/data/pipe.mesh diff --git a/examples/graphs/3d/widgetgallery/data/raindata.txt b/examples/graphs/3d/widgetgraphgallery/data/raindata.txt similarity index 100% rename from examples/graphs/3d/widgetgallery/data/raindata.txt rename to examples/graphs/3d/widgetgraphgallery/data/raindata.txt diff --git a/examples/graphs/3d/widgetgallery/data/refinery.mesh b/examples/graphs/3d/widgetgraphgallery/data/refinery.mesh similarity index 100% rename from examples/graphs/3d/widgetgallery/data/refinery.mesh rename to examples/graphs/3d/widgetgraphgallery/data/refinery.mesh diff --git a/examples/graphs/3d/widgetgallery/data/topography.png b/examples/graphs/3d/widgetgraphgallery/data/topography.png similarity index 100% rename from examples/graphs/3d/widgetgallery/data/topography.png rename to examples/graphs/3d/widgetgraphgallery/data/topography.png diff --git a/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst new file mode 100644 index 00000000..33a4da15 --- /dev/null +++ b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.rst @@ -0,0 +1,11 @@ +Widget Graph Gallery +==================== + + +Widget Graph Gallery demonstrates all three graph types and some of their +special features. The graphs have their own tabs in the application. + + +.. image:: widgetgraphgallery.webp + :width: 400 + :alt: Widget Screenshot diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp b/examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.webp similarity index 100% rename from examples/graphs/3d/widgetgallery/doc/widgetgallery.webp rename to examples/graphs/3d/widgetgraphgallery/doc/widgetgraphgallery.webp diff --git a/examples/graphs/3d/widgetgallery/graphmodifier.py b/examples/graphs/3d/widgetgraphgallery/graphmodifier.py similarity index 82% rename from examples/graphs/3d/widgetgallery/graphmodifier.py rename to examples/graphs/3d/widgetgraphgallery/graphmodifier.py index 2eaafa79..5c4d23c9 100644 --- a/examples/graphs/3d/widgetgallery/graphmodifier.py +++ b/examples/graphs/3d/widgetgraphgallery/graphmodifier.py @@ -1,15 +1,16 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from math import atan, degrees import numpy as np -from PySide6.QtCore import QObject, QPropertyAnimation, Signal, Slot +from PySide6.QtCore import QObject, QPropertyAnimation, Qt, Signal, Slot from PySide6.QtGui import QFont, QVector3D -from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries, +from PySide6.QtGraphs import (QAbstract3DSeries, QBarDataItem, QBar3DSeries, QCategory3DAxis, - QValue3DAxis, Q3DTheme) + QValue3DAxis, QtGraphs3D, QGraphsTheme) from rainfalldata import RainfallData @@ -41,8 +42,8 @@ TEMP_HELSINKI = np.array([ class GraphModifier(QObject): shadowQualityChanged = Signal(int) - backgroundEnabledChanged = Signal(bool) - gridEnabledChanged = Signal(bool) + backgroundVisibleChanged = Signal(bool) + gridVisibleChanged = Signal(bool) fontChanged = Signal(QFont) fontSizeChanged = Signal(int) @@ -75,11 +76,11 @@ class GraphModifier(QObject): self._defaultTarget = [] self._customData = None - self._graph.setShadowQuality(QAbstract3DGraph.ShadowQuality.SoftMedium) + self._graph.setShadowQuality(QtGraphs3D.ShadowQuality.SoftMedium) theme = self._graph.activeTheme() - theme.setBackgroundEnabled(False) - theme.setFont(QFont("Times New Roman", self._fontSize)) - theme.setLabelBackgroundEnabled(True) + theme.setPlotAreaBackgroundVisible(False) + theme.setLabelFont(QFont("Times New Roman", self._fontSize)) + theme.setLabelBackgroundVisible(True) self._graph.setMultiSeriesUniform(True) self._months = ["January", "February", "March", "April", "May", "June", @@ -93,14 +94,14 @@ class GraphModifier(QObject): self._temperatureAxis.setSubSegmentCount(self._subSegments) self._temperatureAxis.setRange(self._minval, self._maxval) self._temperatureAxis.setLabelFormat("%.1f " + self._celsiusString) - self._temperatureAxis.setLabelAutoRotation(30.0) + self._temperatureAxis.setLabelAutoAngle(30.0) self._temperatureAxis.setTitleVisible(True) self._yearAxis.setTitle("Year") - self._yearAxis.setLabelAutoRotation(30.0) + self._yearAxis.setLabelAutoAngle(30.0) self._yearAxis.setTitleVisible(True) self._monthAxis.setTitle("Month") - self._monthAxis.setLabelAutoRotation(30.0) + self._monthAxis.setLabelAutoAngle(30.0) self._monthAxis.setTitleVisible(True) self._graph.setValueAxis(self._temperatureAxis) @@ -207,63 +208,65 @@ class GraphModifier(QObject): # Restore camera target in case animation has changed it self._graph.setCameraTargetPosition(QVector3D(0.0, 0.0, 0.0)) - self._preset = QAbstract3DGraph.CameraPreset.Front.value + self._preset = QtGraphs3D.CameraPreset.Front.value - self._graph.setCameraPreset(QAbstract3DGraph.CameraPreset(self._preset)) + self._graph.setCameraPreset(QtGraphs3D.CameraPreset(self._preset)) self._preset += 1 - if self._preset > QAbstract3DGraph.CameraPreset.DirectlyBelow.value: - self._preset = QAbstract3DGraph.CameraPreset.FrontLow.value + if self._preset > QtGraphs3D.CameraPreset.DirectlyBelow.value: + self._preset = QtGraphs3D.CameraPreset.FrontLow.value @Slot(int) def changeTheme(self, theme): currentTheme = self._graph.activeTheme() - currentTheme.setType(Q3DTheme.Theme(theme)) - self.backgroundEnabledChanged.emit(currentTheme.isBackgroundEnabled()) - self.gridEnabledChanged.emit(currentTheme.isGridEnabled()) - self.fontChanged.emit(currentTheme.font()) - self.fontSizeChanged.emit(currentTheme.font().pointSize()) + currentTheme.setTheme(QGraphsTheme.Theme(theme)) + self.backgroundVisibleChanged.emit(currentTheme.isBackgroundVisible()) + self.gridVisibleChanged.emit(currentTheme.isGridVisible()) + self.fontChanged.emit(currentTheme.labelFont()) + self.fontSizeChanged.emit(currentTheme.labelFont().pointSize()) def changeLabelBackground(self): theme = self._graph.activeTheme() - theme.setLabelBackgroundEnabled(not theme.isLabelBackgroundEnabled()) + theme.setLabelBackgroundVisible(not theme.isLabelBackgroundVisible()) @Slot(int) def changeSelectionMode(self, selectionMode): comboBox = self.sender() if comboBox: flags = comboBox.itemData(selectionMode) - self._graph.setSelectionMode(QAbstract3DGraph.SelectionFlags(flags)) + self._graph.setSelectionMode(QtGraphs3D.SelectionFlags(flags)) def changeFont(self, font): newFont = font - self._graph.activeTheme().setFont(newFont) + self._graph.activeTheme().setLabelFont(newFont) def changeFontSize(self, fontsize): self._fontSize = fontsize - font = self._graph.activeTheme().font() + font = self._graph.activeTheme().labelFont() font.setPointSize(self._fontSize) - self._graph.activeTheme().setFont(font) + self._graph.activeTheme().setLabelFont(font) - @Slot(QAbstract3DGraph.ShadowQuality) + @Slot(QtGraphs3D.ShadowQuality) def shadowQualityUpdatedByVisual(self, sq): # Updates the UI component to show correct shadow quality self.shadowQualityChanged.emit(sq.value) @Slot(int) def changeLabelRotation(self, rotation): - self._temperatureAxis.setLabelAutoRotation(float(rotation)) - self._monthAxis.setLabelAutoRotation(float(rotation)) - self._yearAxis.setLabelAutoRotation(float(rotation)) + self._temperatureAxis.setLabelAutoAngle(float(rotation)) + self._monthAxis.setLabelAutoAngle(float(rotation)) + self._yearAxis.setLabelAutoAngle(float(rotation)) @Slot(bool) - def setAxisTitleVisibility(self, enabled): + def setAxisTitleVisibility(self, state): + enabled = state == Qt.CheckState.Checked self._temperatureAxis.setTitleVisible(enabled) self._monthAxis.setTitleVisible(enabled) self._yearAxis.setTitleVisible(enabled) @Slot(bool) - def setAxisTitleFixed(self, enabled): + def setAxisTitleFixed(self, state): + enabled = state == Qt.CheckState.Checked self._temperatureAxis.setTitleFixed(enabled) self._monthAxis.setTitleFixed(enabled) self._yearAxis.setTitleFixed(enabled) @@ -338,40 +341,36 @@ class GraphModifier(QObject): self.changeDataMode(True) def changeShadowQuality(self, quality): - sq = QAbstract3DGraph.ShadowQuality(quality) + sq = QtGraphs3D.ShadowQuality(quality) self._graph.setShadowQuality(sq) self.shadowQualityChanged.emit(quality) def rotateX(self, rotation): self._xRotation = rotation - camera = self._graph.scene().activeCamera() - camera.setCameraPosition(self._xRotation, self._yRotation) + self._graph.setCameraPosition(self._xRotation, self._yRotation) def rotateY(self, rotation): self._yRotation = rotation - camera = self._graph.scene().activeCamera() - camera.setCameraPosition(self._xRotation, self._yRotation) + self._graph.setCameraPosition(self._xRotation, self._yRotation) - def setBackgroundEnabled(self, enabled): - self._graph.activeTheme().setBackgroundEnabled(bool(enabled)) + def setPlotAreaBackgroundVisible(self, state): + enabled = state == Qt.CheckState.Checked + self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled) - def setGridEnabled(self, enabled): - self._graph.activeTheme().setGridEnabled(bool(enabled)) + def setGridVisible(self, state): + self._graph.activeTheme().setGridVisible(state == Qt.CheckState.Checked) - def setSmoothBars(self, smooth): - self._smooth = bool(smooth) + def setSmoothBars(self, state): + self._smooth = state == Qt.CheckState.Checked self._primarySeries.setMeshSmooth(self._smooth) self._secondarySeries.setMeshSmooth(self._smooth) self._customData.customSeries().setMeshSmooth(self._smooth) - def setSeriesVisibility(self, enabled): - self._secondarySeries.setVisible(bool(enabled)) + def setSeriesVisibility(self, state): + self._secondarySeries.setVisible(state == Qt.CheckState.Checked) - def setReverseValueAxis(self, enabled): - self._graph.valueAxis().setReversed(enabled) - - def setReflection(self, enabled): - self._graph.setReflection(enabled) + def setReverseValueAxis(self, state): + self._graph.valueAxis().setReversed(state == Qt.CheckState.Checked) def changeDataMode(self, customData): # Change between weather data and data from custom proxy diff --git a/examples/graphs/3d/widgetgallery/highlightseries.py b/examples/graphs/3d/widgetgraphgallery/highlightseries.py similarity index 75% rename from examples/graphs/3d/widgetgallery/highlightseries.py rename to examples/graphs/3d/widgetgraphgallery/highlightseries.py index 8c7b9163..27dad470 100644 --- a/examples/graphs/3d/widgetgallery/highlightseries.py +++ b/examples/graphs/3d/widgetgraphgallery/highlightseries.py @@ -1,9 +1,11 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QPoint, Qt, Slot from PySide6.QtGui import QLinearGradient, QVector3D -from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem, Q3DTheme) +from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem, + QGraphsTheme) DARK_RED_POS = 1.0 @@ -24,13 +26,13 @@ class HighlightSeries(QSurface3DSeries): self._position = {} self._topographicSeries = None self._minHeight = 0.0 - self.setDrawMode(QSurface3DSeries.DrawSurface) - self.setFlatShadingEnabled(True) + self.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface) + self.setShading(QSurface3DSeries.Shading.Flat) self.setVisible(False) def setTopographicSeries(self, series): self._topographicSeries = series - array = self._topographicSeries.dataProxy().array() + array = self._topographicSeries.dataArray() self._srcWidth = len(array[0]) self._srcHeight = len(array) self._topographicSeries.selectedPointChanged.connect(self.handlePositionChange) @@ -62,8 +64,7 @@ class HighlightSeries(QSurface3DSeries): if endZ > (self._srcHeight - 1): endZ = self._srcHeight - 1 - srcProxy = self._topographicSeries.dataProxy() - srcArray = srcProxy.array() + srcArray = self._topographicSeries.dataArray() dataArray = [] for i in range(int(startZ), int(endZ)): @@ -83,12 +84,12 @@ class HighlightSeries(QSurface3DSeries): ratio = self._minHeight / value gr = QLinearGradient() - gr.setColorAt(0.0, Qt.black) - gr.setColorAt(DARK_GREEN_POS * ratio, Qt.darkGreen) - gr.setColorAt(GREEN_POS * ratio, Qt.green) - gr.setColorAt(YELLOW_POS * ratio, Qt.yellow) - gr.setColorAt(RED_POS * ratio, Qt.red) - gr.setColorAt(DARK_RED_POS * ratio, Qt.darkRed) + gr.setColorAt(0.0, Qt.GlobalColor.black) + gr.setColorAt(DARK_GREEN_POS * ratio, Qt.GlobalColor.darkGreen) + gr.setColorAt(GREEN_POS * ratio, Qt.GlobalColor.green) + gr.setColorAt(YELLOW_POS * ratio, Qt.GlobalColor.yellow) + gr.setColorAt(RED_POS * ratio, Qt.GlobalColor.red) + gr.setColorAt(DARK_RED_POS * ratio, Qt.GlobalColor.darkRed) self.setBaseGradient(gr) - self.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + self.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient) diff --git a/examples/graphs/3d/widgetgraphgallery/main.py b/examples/graphs/3d/widgetgraphgallery/main.py new file mode 100644 index 00000000..7a5bfe45 --- /dev/null +++ b/examples/graphs/3d/widgetgraphgallery/main.py @@ -0,0 +1,50 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations + +"""PySide6 port of the Qt Graphs widgetgallery example from Qt v6.x""" + +import sys + +from PySide6.QtCore import QSize +from PySide6.QtWidgets import QApplication, QTabWidget + +from bargraph import BarGraph +from scattergraph import ScatterGraph +from surfacegraph import SurfaceGraph + + +class MainWidget(QTabWidget): + """Tab widget for creating own tabs for Q3DBars, Q3DScatter, and Q3DSurface""" + + def __init__(self, p=None): + super().__init__(p) + + screen_size = self.screen().size() + minimum_graph_size = QSize(screen_size.width() / 2, screen_size.height() / 1.75) + + # Create bar graph + self._bars = BarGraph(minimum_graph_size, screen_size) + # Create scatter graph + self._scatter = ScatterGraph(minimum_graph_size, screen_size) + # Create surface graph + self._surface = SurfaceGraph(minimum_graph_size, screen_size) + + # Add bars widget + self.addTab(self._bars.barsWidget(), "Bar Graph") + # Add scatter widget + self.addTab(self._scatter.scatterWidget(), "Scatter Graph") + # Add surface widget + self.addTab(self._surface.surfaceWidget(), "Surface Graph") + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + tabWidget = MainWidget() + tabWidget.setWindowTitle("Widget Gallery") + + tabWidget.show() + exit_code = app.exec() + del tabWidget + sys.exit(exit_code) diff --git a/examples/graphs/3d/widgetgallery/rainfalldata.py b/examples/graphs/3d/widgetgraphgallery/rainfalldata.py similarity index 97% rename from examples/graphs/3d/widgetgallery/rainfalldata.py rename to examples/graphs/3d/widgetgraphgallery/rainfalldata.py index d74f45a8..a5339672 100644 --- a/examples/graphs/3d/widgetgallery/rainfalldata.py +++ b/examples/graphs/3d/widgetgraphgallery/rainfalldata.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -95,7 +96,7 @@ class RainfallData(QObject): # Read data from a data file into the data item list file_path = Path(__file__).resolve().parent / "data" / "raindata.txt" dataFile = QFile(file_path) - if dataFile.open(QIODevice.ReadOnly | QIODevice.Text): + if dataFile.open(QIODevice.OpenModeFlag.ReadOnly | QIODevice.OpenModeFlag.Text): data = dataFile.readAll().data().decode("utf8") for line in data.split("\n"): if line and not line.startswith("#"): # Ignore comments diff --git a/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py b/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py new file mode 100644 index 00000000..946c9626 --- /dev/null +++ b/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py @@ -0,0 +1,199 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations + +from enum import Enum +from math import sin, cos, degrees, sqrt + +from PySide6.QtCore import QObject, Signal, Slot, Qt +from PySide6.QtGui import QVector2D, QVector3D +from PySide6.QtGraphs import (QAbstract3DSeries, + QScatterDataItem, QScatterDataProxy, + QScatter3DSeries, QtGraphs3D, QGraphsTheme) + + +NUMBER_OF_ITEMS = 10000 +CURVE_DIVIDER = 7.5 +LOWER_NUMBER_OF_ITEMS = 900 +LOWER_CURVE_DIVIDER = 0.75 + + +class InputState(Enum): + StateNormal = 0 + StateDraggingX = 1 + StateDraggingZ = 2 + StateDraggingY = 3 + + +class ScatterDataModifier(QObject): + + backgroundEnabledChanged = Signal(bool) + gridVisibleChanged = Signal(bool) + shadowQualityChanged = Signal(int) + + def __init__(self, scatter, parent): + super().__init__(parent) + + self._graph = scatter + + self._style = QAbstract3DSeries.Mesh.Sphere + self._smooth = True + self._autoAdjust = True + self._itemCount = LOWER_NUMBER_OF_ITEMS + self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER + + self._graph.activeTheme().setTheme(QGraphsTheme.Theme.MixSeries) + self._graph.activeTheme().setColorScheme(QGraphsTheme.ColorScheme.Dark) + self._graph.setShadowQuality(QtGraphs3D.ShadowQuality.SoftHigh) + self._graph.setCameraPreset(QtGraphs3D.CameraPreset.Front) + self._graph.setCameraZoomLevel(80.0) + + self._proxy = QScatterDataProxy() + self._series = QScatter3DSeries(self._proxy) + self._series.setItemLabelFormat("@xTitle: @xLabel @yTitle: @yLabel @zTitle: @zLabel") + self._series.setMeshSmooth(self._smooth) + self._graph.addSeries(self._series) + self._preset = QtGraphs3D.CameraPreset.FrontLow.value + + self._state = InputState.StateNormal + self._dragSpeedModifier = float(15) + + self._graph.selectedElementChanged.connect(self.handleElementSelected) + self._graph.dragged.connect(self.handleAxisDragging) + self._graph.setDragButton(Qt.MouseButton.LeftButton) + + self.addData() + + def addData(self): + # Configure the axes according to the data + self._graph.axisX().setTitle("X") + self._graph.axisY().setTitle("Y") + self._graph.axisZ().setTitle("Z") + + dataArray = [] + limit = int(sqrt(self._itemCount) / 2.0) + for i in range(-limit, limit): + for j in range(-limit, limit): + x = float(i) + 0.5 + y = cos(degrees(float(i * j) / self._CURVE_DIVIDER)) + z = float(j) + 0.5 + dataArray.append(QScatterDataItem(QVector3D(x, y, z))) + + self._graph.seriesList()[0].dataProxy().resetArray(dataArray) + + @Slot(int) + def changeStyle(self, style): + comboBox = self.sender() + if comboBox: + self._style = comboBox.itemData(style) + if self._graph.seriesList(): + self._graph.seriesList()[0].setMesh(self._style) + + @Slot(int) + def setSmoothDots(self, smooth): + self._smooth = smooth == Qt.CheckState.Checked + series = self._graph.seriesList()[0] + series.setMeshSmooth(self._smooth) + + @Slot(int) + def changeTheme(self, theme): + currentTheme = self._graph.activeTheme() + currentTheme.setTheme(QGraphsTheme.Theme(theme)) + self.backgroundEnabledChanged.emit(currentTheme.isPlotAreaBackgroundVisible()) + self.gridVisibleChanged.emit(currentTheme.isGridVisible()) + + @Slot() + def changePresetCamera(self): + self._graph.setCameraPreset(QtGraphs3D.CameraPreset(self._preset)) + + self._preset += 1 + if self._preset > QtGraphs3D.CameraPreset.DirectlyBelow.value: + self._preset = QtGraphs3D.CameraPreset.FrontLow.value + + @Slot(QtGraphs3D.ShadowQuality) + def shadowQualityUpdatedByVisual(self, sq): + self.shadowQualityChanged.emit(sq.value) + + @Slot(int) + def changeShadowQuality(self, quality): + sq = QtGraphs3D.ShadowQuality(quality) + self._graph.setShadowQuality(sq) + + @Slot(int) + def setPlotAreaBackgroundVisible(self, state): + enabled = state == Qt.CheckState.Checked + self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled) + + @Slot(int) + def setGridVisible(self, state): + self._graph.activeTheme().setGridVisible(state == Qt.Checked.value) + + @Slot() + def toggleItemCount(self): + if self._itemCount == NUMBER_OF_ITEMS: + self._itemCount = LOWER_NUMBER_OF_ITEMS + self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER + else: + self._itemCount = NUMBER_OF_ITEMS + self._CURVE_DIVIDER = CURVE_DIVIDER + + self._graph.seriesList()[0].dataProxy().resetArray([]) + self.addData() + + @Slot() + def toggleRanges(self): + if not self._autoAdjust: + self._graph.axisX().setAutoAdjustRange(True) + self._graph.axisZ().setAutoAdjustRange(True) + self._dragSpeedModifier = 1.5 + self._autoAdjust = True + else: + self._graph.axisX().setRange(-10.0, 10.0) + self._graph.axisZ().setRange(-10.0, 10.0) + self._dragSpeedModifier = float(15) + self._autoAdjust = False + + @Slot(QtGraphs3D.ElementType) + def handleElementSelected(self, type): + if type == QtGraphs3D.ElementType.AxisXLabel: + self._state = InputState.StateDraggingX + elif type == QtGraphs3D.ElementType.AxisYLabel: + self._state = InputState.StateDraggingY + elif type == QtGraphs3D.ElementType.AxisZLabel: + self._state = InputState.StateDraggingZ + else: + self._state = InputState.StateNormal + + @Slot(QVector2D) + def handleAxisDragging(self, delta): + distance = 0.0 + # Get scene orientation from active camera + xRotation = self._graph.cameraXRotation() + yRotation = self._graph.cameraYRotation() + + # Calculate directional drag multipliers based on rotation + xMulX = cos(degrees(xRotation)) + xMulY = sin(degrees(xRotation)) + zMulX = sin(degrees(xRotation)) + zMulY = cos(degrees(xRotation)) + + # Get the drag amount + move = delta.toPoint() + + # Flip the effect of y movement if we're viewing from below + yMove = -move.y() if yRotation < 0 else move.y() + + # Adjust axes + if self._state == InputState.StateDraggingX: + axis = self._graph.axisX() + distance = (move.x() * xMulX - yMove * xMulY) / self._dragSpeedModifier + axis.setRange(axis.min() - distance, axis.max() - distance) + elif self._state == InputState.StateDraggingZ: + axis = self._graph.axisZ() + distance = (move.x() * zMulX + yMove * zMulY) / self._dragSpeedModifier + axis.setRange(axis.min() + distance, axis.max() + distance) + elif self._state == InputState.StateDraggingY: + axis = self._graph.axisY() + # No need to use adjusted y move here + distance = move.y() / self._dragSpeedModifier + axis.setRange(axis.min() + distance, axis.max() + distance) diff --git a/examples/graphs/3d/widgetgallery/scattergraph.py b/examples/graphs/3d/widgetgraphgallery/scattergraph.py similarity index 66% rename from examples/graphs/3d/widgetgallery/scattergraph.py rename to examples/graphs/3d/widgetgraphgallery/scattergraph.py index 79e8933e..1b5c507a 100644 --- a/examples/graphs/3d/widgetgallery/scattergraph.py +++ b/examples/graphs/3d/widgetgraphgallery/scattergraph.py @@ -1,12 +1,14 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, QSize, Qt from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton, QLabel, QHBoxLayout, QSizePolicy, QVBoxLayout, QWidget, ) from PySide6.QtQuickWidgets import QQuickWidget -from PySide6.QtGraphs import (QAbstract3DSeries, Q3DScatter) +from PySide6.QtGraphs import QAbstract3DSeries +from PySide6.QtGraphsWidgets import Q3DScatterWidgetItem from scatterdatamodifier import ScatterDataModifier @@ -15,15 +17,17 @@ class ScatterGraph(QObject): def __init__(self, minimum_graph_size, maximum_graph_size): super().__init__() - self._scatterGraph = Q3DScatter() + + scatterGraph = Q3DScatterWidgetItem() + scatterGraphWidget = QQuickWidget() + scatterGraph.setWidget(scatterGraphWidget) self._scatterWidget = QWidget() hLayout = QHBoxLayout(self._scatterWidget) - self._scatterGraph.setMinimumSize(minimum_graph_size) - self._scatterGraph.setMaximumSize(maximum_graph_size) - self._scatterGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self._scatterGraph.setFocusPolicy(Qt.StrongFocus) - self._scatterGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) - hLayout.addWidget(self._scatterGraph, 1) + scatterGraphWidget.setMinimumSize(minimum_graph_size) + scatterGraphWidget.setMaximumSize(maximum_graph_size) + scatterGraphWidget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + scatterGraphWidget.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + hLayout.addWidget(scatterGraphWidget, 1) vLayout = QVBoxLayout() hLayout.addLayout(vLayout) @@ -44,7 +48,7 @@ class ScatterGraph(QObject): rangeButton.setIconSize(QSize(0, 0)) backgroundCheckBox = QCheckBox(self._scatterWidget) - backgroundCheckBox.setText("Show background") + backgroundCheckBox.setText("Show graph background") backgroundCheckBox.setChecked(True) gridCheckBox = QCheckBox(self._scatterWidget) @@ -94,28 +98,28 @@ class ScatterGraph(QObject): vLayout.addWidget(QLabel("Change theme")) vLayout.addWidget(themeList) vLayout.addWidget(QLabel("Adjust shadow quality")) - vLayout.addWidget(shadowQuality, 1, Qt.AlignTop) + vLayout.addWidget(shadowQuality, 1, Qt.AlignmentFlag.AlignTop) - self._modifier = ScatterDataModifier(self._scatterGraph, self) + modifier = ScatterDataModifier(scatterGraph, self) - cameraButton.clicked.connect(self._modifier.changePresetCamera) - itemCountButton.clicked.connect(self._modifier.toggleItemCount) - rangeButton.clicked.connect(self._modifier.toggleRanges) + cameraButton.clicked.connect(modifier.changePresetCamera) + itemCountButton.clicked.connect(modifier.toggleItemCount) + rangeButton.clicked.connect(modifier.toggleRanges) - backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled) - gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled) - smoothCheckBox.stateChanged.connect(self._modifier.setSmoothDots) + backgroundCheckBox.checkStateChanged.connect(modifier.setPlotAreaBackgroundVisible) + gridCheckBox.checkStateChanged.connect(modifier.setGridVisible) + smoothCheckBox.checkStateChanged.connect(modifier.setSmoothDots) - self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) - self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked) - itemStyleList.currentIndexChanged.connect(self._modifier.changeStyle) + modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked) + modifier.gridVisibleChanged.connect(gridCheckBox.setChecked) + itemStyleList.currentIndexChanged.connect(modifier.changeStyle) - themeList.currentIndexChanged.connect(self._modifier.changeTheme) + themeList.currentIndexChanged.connect(modifier.changeTheme) - shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality) + shadowQuality.currentIndexChanged.connect(modifier.changeShadowQuality) - self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) - self._scatterGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual) + modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex) + scatterGraph.shadowQualityChanged.connect(modifier.shadowQualityUpdatedByVisual) def scatterWidget(self): return self._scatterWidget diff --git a/examples/graphs/3d/widgetgallery/surfacegraph.py b/examples/graphs/3d/widgetgraphgallery/surfacegraph.py similarity index 79% rename from examples/graphs/3d/widgetgallery/surfacegraph.py rename to examples/graphs/3d/widgetgraphgallery/surfacegraph.py index 4052da82..8d565f9e 100644 --- a/examples/graphs/3d/widgetgallery/surfacegraph.py +++ b/examples/graphs/3d/widgetgraphgallery/surfacegraph.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from surfacegraphmodifier import SurfaceGraphModifier @@ -9,33 +10,33 @@ from PySide6.QtWidgets import (QGroupBox, QCheckBox, QLabel, QHBoxLayout, QPushButton, QRadioButton, QSizePolicy, QSlider, QVBoxLayout, QWidget) from PySide6.QtQuickWidgets import QQuickWidget -from PySide6.QtGraphs import Q3DSurface +from PySide6.QtGraphsWidgets import Q3DSurfaceWidgetItem def gradientBtoYPB_Pixmap(): grBtoY = QLinearGradient(0, 0, 1, 100) - grBtoY.setColorAt(1.0, Qt.black) - grBtoY.setColorAt(0.67, Qt.blue) - grBtoY.setColorAt(0.33, Qt.red) - grBtoY.setColorAt(0.0, Qt.yellow) + grBtoY.setColorAt(1.0, Qt.GlobalColor.black) + grBtoY.setColorAt(0.67, Qt.GlobalColor.blue) + grBtoY.setColorAt(0.33, Qt.GlobalColor.red) + grBtoY.setColorAt(0.0, Qt.GlobalColor.yellow) pm = QPixmap(24, 100) with QPainter(pm) as pmp: pmp.setBrush(QBrush(grBtoY)) - pmp.setPen(Qt.NoPen) + pmp.setPen(Qt.PenStyle.NoPen) pmp.drawRect(0, 0, 24, 100) return pm def gradientGtoRPB_Pixmap(): grGtoR = QLinearGradient(0, 0, 1, 100) - grGtoR.setColorAt(1.0, Qt.darkGreen) - grGtoR.setColorAt(0.5, Qt.yellow) - grGtoR.setColorAt(0.2, Qt.red) - grGtoR.setColorAt(0.0, Qt.darkRed) + grGtoR.setColorAt(1.0, Qt.GlobalColor.darkGreen) + grGtoR.setColorAt(0.5, Qt.GlobalColor.yellow) + grGtoR.setColorAt(0.2, Qt.GlobalColor.red) + grGtoR.setColorAt(0.0, Qt.GlobalColor.darkRed) pm = QPixmap(24, 100) with QPainter(pm) as pmp: pmp.setBrush(QBrush(grGtoR)) - pmp.setPen(Qt.NoPen) + pmp.setPen(Qt.PenStyle.NoPen) pmp.drawRect(0, 0, 24, 100) return pm @@ -45,19 +46,19 @@ def highlightPixmap(): WIDTH = 110 BORDER = 10 gr = QLinearGradient(0, 0, 1, HEIGHT - 2 * BORDER) - gr.setColorAt(1.0, Qt.black) - gr.setColorAt(0.8, Qt.darkGreen) - gr.setColorAt(0.6, Qt.green) - gr.setColorAt(0.4, Qt.yellow) - gr.setColorAt(0.2, Qt.red) - gr.setColorAt(0.0, Qt.darkRed) + gr.setColorAt(1.0, Qt.GlobalColor.black) + gr.setColorAt(0.8, Qt.GlobalColor.darkGreen) + gr.setColorAt(0.6, Qt.GlobalColor.green) + gr.setColorAt(0.4, Qt.GlobalColor.yellow) + gr.setColorAt(0.2, Qt.GlobalColor.red) + gr.setColorAt(0.0, Qt.GlobalColor.darkRed) pmHighlight = QPixmap(WIDTH, HEIGHT) - pmHighlight.fill(Qt.transparent) + pmHighlight.fill(Qt.GlobalColor.transparent) with QPainter(pmHighlight) as pmpHighlight: pmpHighlight.setBrush(QBrush(gr)) - pmpHighlight.setPen(Qt.NoPen) + pmpHighlight.setPen(Qt.PenStyle.NoPen) pmpHighlight.drawRect(BORDER, BORDER, 35, HEIGHT - 2 * BORDER) - pmpHighlight.setPen(Qt.black) + pmpHighlight.setPen(Qt.GlobalColor.black) step = (HEIGHT - 2 * BORDER) / 5 for i in range(0, 6): yPos = i * step + BORDER @@ -71,18 +72,21 @@ class SurfaceGraph(QObject): def __init__(self, minimum_graph_size, maximum_graph_size): super().__init__() - self._surfaceGraph = Q3DSurface() + + surfaceGraphWidget = QQuickWidget() + surfaceGraph = Q3DSurfaceWidgetItem() + surfaceGraph.setWidget(surfaceGraphWidget) self._surfaceWidget = QWidget() hLayout = QHBoxLayout(self._surfaceWidget) - self._surfaceGraph.setMinimumSize(minimum_graph_size) - self._surfaceGraph.setMaximumSize(maximum_graph_size) - self._surfaceGraph.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self._surfaceGraph.setFocusPolicy(Qt.StrongFocus) - self._surfaceGraph.setResizeMode(QQuickWidget.SizeRootObjectToView) - hLayout.addWidget(self._surfaceGraph, 1) + surfaceGraphWidget.setMinimumSize(minimum_graph_size) + surfaceGraphWidget.setMaximumSize(maximum_graph_size) + surfaceGraphWidget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + surfaceGraphWidget.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + surfaceGraphWidget.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView) + hLayout.addWidget(surfaceGraphWidget, 1) vLayout = QVBoxLayout() hLayout.addLayout(vLayout) - vLayout.setAlignment(Qt.AlignTop) + vLayout.setAlignment(Qt.AlignmentFlag.AlignTop) # Create control widgets modelGroupBox = QGroupBox("Model") sqrtSinModelRB = QRadioButton(self._surfaceWidget) @@ -119,19 +123,19 @@ class SurfaceGraph(QObject): selectionVBox.addWidget(modeSliceColumnRB) selectionGroupBox.setLayout(selectionVBox) axisGroupBox = QGroupBox("Axis ranges") - axisMinSliderX = QSlider(Qt.Horizontal) + axisMinSliderX = QSlider(Qt.Orientation.Horizontal) axisMinSliderX.setMinimum(0) axisMinSliderX.setTickInterval(1) axisMinSliderX.setEnabled(True) - axisMaxSliderX = QSlider(Qt.Horizontal) + axisMaxSliderX = QSlider(Qt.Orientation.Horizontal) axisMaxSliderX.setMinimum(1) axisMaxSliderX.setTickInterval(1) axisMaxSliderX.setEnabled(True) - axisMinSliderZ = QSlider(Qt.Horizontal) + axisMinSliderZ = QSlider(Qt.Orientation.Horizontal) axisMinSliderZ.setMinimum(0) axisMinSliderZ.setTickInterval(1) axisMinSliderZ.setEnabled(True) - axisMaxSliderZ = QSlider(Qt.Horizontal) + axisMaxSliderZ = QSlider(Qt.Orientation.Horizontal) axisMaxSliderZ.setMinimum(1) axisMaxSliderZ.setTickInterval(1) axisMaxSliderZ.setEnabled(True) @@ -213,7 +217,7 @@ class SurfaceGraph(QObject): vLayout.addWidget(heightMapGroupBox) vLayout.addWidget(enableTexture) # Create the controller - modifier = SurfaceGraphModifier(self._surfaceGraph, labelSelectedItem, self) + modifier = SurfaceGraphModifier(surfaceGraph, labelSelectedItem, self) # Connect widget controls to controller heightMapModelRB.toggled.connect(modifier.enableHeightMapModel) sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel) @@ -229,13 +233,13 @@ class SurfaceGraph(QObject): # Mode dependent connections gradientBtoYPB.pressed.connect(modifier.setBlackToYellowGradient) gradientGtoRPB.pressed.connect(modifier.setGreenToRedGradient) - checkboxShowOilRigOne.stateChanged.connect(modifier.toggleItemOne) - checkboxShowOilRigTwo.stateChanged.connect(modifier.toggleItemTwo) - checkboxShowRefinery.stateChanged.connect(modifier.toggleItemThree) - checkboxVisualsSeeThrough.stateChanged.connect(modifier.toggleSeeThrough) - checkboxHighlightOil.stateChanged.connect(modifier.toggleOilHighlight) - checkboxShowShadows.stateChanged.connect(modifier.toggleShadows) - enableTexture.stateChanged.connect(modifier.toggleSurfaceTexture) + checkboxShowOilRigOne.toggled.connect(modifier.toggleItemOne) + checkboxShowOilRigTwo.toggled.connect(modifier.toggleItemTwo) + checkboxShowRefinery.toggled.connect(modifier.toggleItemThree) + checkboxVisualsSeeThrough.toggled.connect(modifier.toggleSeeThrough) + checkboxHighlightOil.toggled.connect(modifier.toggleOilHighlight) + checkboxShowShadows.toggled.connect(modifier.toggleShadows) + enableTexture.toggled.connect(modifier.toggleSurfaceTexture) # Connections to disable features depending on mode sqrtSinModelRB.toggled.connect(colorGroupBox.setVisible) heightMapModelRB.toggled.connect(showGroupBox.setVisible) diff --git a/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py b/examples/graphs/3d/widgetgraphgallery/surfacegraphmodifier.py similarity index 71% rename from examples/graphs/3d/widgetgallery/surfacegraphmodifier.py rename to examples/graphs/3d/widgetgraphgallery/surfacegraphmodifier.py index b2706c6f..d9b2126f 100644 --- a/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py +++ b/examples/graphs/3d/widgetgraphgallery/surfacegraphmodifier.py @@ -1,23 +1,31 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os -from math import sqrt, sin +from enum import Enum +from math import sqrt, sin, cos, degrees from pathlib import Path from PySide6.QtCore import QObject, QPropertyAnimation, Qt, Slot from PySide6.QtGui import (QColor, QFont, QImage, QLinearGradient, - QQuaternion, QVector3D) -from PySide6.QtGraphs import (QAbstract3DGraph, QCustom3DItem, + QQuaternion, QVector2D, QVector3D, QWheelEvent) +from PySide6.QtGraphs import (QCustom3DItem, QCustom3DLabel, QHeightMapSurfaceDataProxy, QValue3DAxis, QSurfaceDataItem, QSurfaceDataProxy, QSurface3DSeries, - Q3DInputHandler, Q3DTheme) + QtGraphs3D, QGraphsTheme) from highlightseries import HighlightSeries from topographicseries import TopographicSeries -from custominputhandler import CustomInputHandler + + +class InputState(Enum): + StateNormal = 0 + StateDraggingX = 1 + StateDraggingZ = 2 + StateDraggingY = 3 SAMPLE_COUNT_X = 150 @@ -26,6 +34,7 @@ HEIGHTMAP_GRID_STEP_X = 6 HEIGHTMAP_GRID_STEP_Z = 6 SAMPLE_MIN = -8.0 SAMPLE_MAX = 8.0 +SPEED_MODIFIER = 20.0 AREA_WIDTH = 8000.0 AREA_HEIGHT = 8000.0 @@ -37,6 +46,7 @@ class SurfaceGraphModifier(QObject): def __init__(self, surface, label, parent): super().__init__(parent) + self._state = InputState.StateNormal self._data_path = Path(__file__).resolve().parent / "data" self._graph = surface self._textField = label @@ -60,6 +70,15 @@ class SurfaceGraphModifier(QObject): self._heightMapWidth = 0 self._heightMapHeight = 0 + self._axisXMinValue = 0.0 + self._axisXMaxValue = 0.0 + self._axisXMinRange = 0.0 + self._axisZMinValue = 0.0 + self._axisZMaxValue = 0.0 + self._axisZMinRange = 0.0 + self._areaMinValue = 0.0 + self._areaMaxValue = 0.0 + self._selectionAnimation = None self._titleLabel = None self._previouslyAnimatedItem = None @@ -70,12 +89,12 @@ class SurfaceGraphModifier(QObject): self._highlightWidth = 0 self._highlightHeight = 0 - self._customInputHandler = None - self._defaultInputHandler = Q3DInputHandler() - self._graph.setCameraZoomLevel(85.0) - self._graph.setCameraPreset(QAbstract3DGraph.CameraPreset.IsometricRight) - self._graph.activeTheme().setType(Q3DTheme.Theme.Retro) + self._graph.setCameraPreset(QtGraphs3D.CameraPreset.IsometricRight) + theme = self._graph.activeTheme() + theme.setTheme(QGraphsTheme.Theme.MixSeries) + theme.setLabelBackgroundVisible(False) + theme.setLabelBorderVisible(False) self._x_axis = QValue3DAxis() self._y_axis = QValue3DAxis() @@ -121,25 +140,25 @@ class SurfaceGraphModifier(QObject): # Set the gradients for multi-surface layers grOne = QLinearGradient() - grOne.setColorAt(0.0, Qt.black) - grOne.setColorAt(0.38, Qt.darkYellow) - grOne.setColorAt(0.39, Qt.darkGreen) - grOne.setColorAt(0.5, Qt.darkGray) - grOne.setColorAt(1.0, Qt.gray) + grOne.setColorAt(0.0, Qt.GlobalColor.black) + grOne.setColorAt(0.38, Qt.GlobalColor.darkYellow) + grOne.setColorAt(0.39, Qt.GlobalColor.darkGreen) + grOne.setColorAt(0.5, Qt.GlobalColor.darkGray) + grOne.setColorAt(1.0, Qt.GlobalColor.gray) self._heightMapSeriesOne.setBaseGradient(grOne) - self._heightMapSeriesOne.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + self._heightMapSeriesOne.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient) grTwo = QLinearGradient() - grTwo.setColorAt(0.39, Qt.blue) - grTwo.setColorAt(0.4, Qt.white) + grTwo.setColorAt(0.39, Qt.GlobalColor.blue) + grTwo.setColorAt(0.4, Qt.GlobalColor.white) self._heightMapSeriesTwo.setBaseGradient(grTwo) - self._heightMapSeriesTwo.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + self._heightMapSeriesTwo.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient) grThree = QLinearGradient() - grThree.setColorAt(0.0, Qt.white) - grThree.setColorAt(0.05, Qt.black) + grThree.setColorAt(0.0, Qt.GlobalColor.white) + grThree.setColorAt(0.05, Qt.GlobalColor.black) self._heightMapSeriesThree.setBaseGradient(grThree) - self._heightMapSeriesThree.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + self._heightMapSeriesThree.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient) # Custom items and label self._graph.selectedElementChanged.connect(self.handleElementSelected) @@ -179,11 +198,8 @@ class SurfaceGraphModifier(QObject): self._highlight.handleGradientChange(AREA_WIDTH * ASPECT_RATIO) self._graph.axisY().maxChanged.connect(self._highlight.handleGradientChange) - self._customInputHandler = CustomInputHandler(self._graph) - self._customInputHandler.setHighlightSeries(self._highlight) - self._customInputHandler.setAxes(self._x_axis, self._y_axis, self._z_axis) - self._customInputHandler.setLimits(0.0, AREA_WIDTH, MIN_RANGE) - self._customInputHandler.setAspectRatio(ASPECT_RATIO) + self._graph.wheel.connect(self.onWheel) + self._graph.dragged.connect(self.handleAxisDragging) def fillSqrtSinProxy(self): stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1) @@ -207,17 +223,17 @@ class SurfaceGraphModifier(QObject): @Slot(bool) def enableSqrtSinModel(self, enable): if enable: - self._sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe) - self._sqrtSinSeries.setFlatShadingEnabled(True) + self._sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurfaceAndWireframe) + self._sqrtSinSeries.setShading(QSurface3DSeries.Shading.Flat) self._graph.axisX().setLabelFormat("%.2f") self._graph.axisZ().setLabelFormat("%.2f") self._graph.axisX().setRange(SAMPLE_MIN, SAMPLE_MAX) self._graph.axisY().setRange(0.0, 2.0) self._graph.axisZ().setRange(SAMPLE_MIN, SAMPLE_MAX) - self._graph.axisX().setLabelAutoRotation(30.0) - self._graph.axisY().setLabelAutoRotation(90.0) - self._graph.axisZ().setLabelAutoRotation(30.0) + self._graph.axisX().setLabelAutoAngle(30.0) + self._graph.axisY().setLabelAutoAngle(90.0) + self._graph.axisZ().setLabelAutoAngle(30.0) self._graph.removeSeries(self._heightMapSeriesOne) self._graph.removeSeries(self._heightMapSeriesTwo) @@ -236,8 +252,6 @@ class SurfaceGraphModifier(QObject): self._graph.axisY().setTitle("") self._graph.axisZ().setTitle("") - self._graph.setActiveInputHandler(self._defaultInputHandler) - # Reset range sliders for Sqrt & Sin self._rangeMinX = SAMPLE_MIN self._rangeMinZ = SAMPLE_MIN @@ -256,15 +270,17 @@ class SurfaceGraphModifier(QObject): self._axisMaxSliderZ.setMaximum(SAMPLE_COUNT_Z - 1) self._axisMaxSliderZ.setValue(SAMPLE_COUNT_Z - 1) + self._graph.setZoomEnabled(True) + @Slot(bool) def enableHeightMapModel(self, enable): if enable: - self._heightMapSeriesOne.setDrawMode(QSurface3DSeries.DrawSurface) - self._heightMapSeriesOne.setFlatShadingEnabled(False) - self._heightMapSeriesTwo.setDrawMode(QSurface3DSeries.DrawSurface) - self._heightMapSeriesTwo.setFlatShadingEnabled(False) - self._heightMapSeriesThree.setDrawMode(QSurface3DSeries.DrawSurface) - self._heightMapSeriesThree.setFlatShadingEnabled(False) + self._heightMapSeriesOne.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface) + self._heightMapSeriesOne.setShading(QSurface3DSeries.Shading.Flat) + self._heightMapSeriesTwo.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface) + self._heightMapSeriesTwo.setShading(QSurface3DSeries.Shading.Flat) + self._heightMapSeriesThree.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface) + self._heightMapSeriesThree.setShading(QSurface3DSeries.Shading.Flat) self._graph.axisX().setLabelFormat("%.1f N") self._graph.axisZ().setLabelFormat("%.1f E") @@ -283,8 +299,6 @@ class SurfaceGraphModifier(QObject): self._graph.addSeries(self._heightMapSeriesTwo) self._graph.addSeries(self._heightMapSeriesThree) - self._graph.setActiveInputHandler(self._defaultInputHandler) - self._titleLabel.setVisible(True) self._graph.axisX().setTitleVisible(True) self._graph.axisY().setTitleVisible(True) @@ -310,6 +324,11 @@ class SurfaceGraphModifier(QObject): self._axisMaxSliderZ.setMaximum(mapGridCountZ - 1) self._axisMaxSliderZ.setValue(mapGridCountZ - 1) + self._graph.wheel.disconnect(self.onWheel) + self._graph.dragged.disconnect(self.handleAxisDragging) + self._graph.setDefaultInputHandler() + self._graph.setZoomEnabled(True) + @Slot(bool) def enableTopographyModel(self, enable): if enable: @@ -318,9 +337,9 @@ class SurfaceGraphModifier(QObject): self._graph.axisX().setRange(0.0, AREA_WIDTH) self._graph.axisY().setRange(100.0, AREA_WIDTH * ASPECT_RATIO) self._graph.axisZ().setRange(0.0, AREA_HEIGHT) - self._graph.axisX().setLabelAutoRotation(30.0) - self._graph.axisY().setLabelAutoRotation(90.0) - self._graph.axisZ().setLabelAutoRotation(30.0) + self._graph.axisX().setLabelAutoAngle(30.0) + self._graph.axisY().setLabelAutoAngle(90.0) + self._graph.axisZ().setLabelAutoAngle(30.0) self._graph.removeSeries(self._heightMapSeriesOne) self._graph.removeSeries(self._heightMapSeriesTwo) @@ -337,8 +356,6 @@ class SurfaceGraphModifier(QObject): self._graph.axisY().setTitle("") self._graph.axisZ().setTitle("") - self._graph.setActiveInputHandler(self._customInputHandler) - # Reset range sliders for topography map self._rangeMinX = 0.0 self._rangeMinZ = 0.0 @@ -357,6 +374,19 @@ class SurfaceGraphModifier(QObject): self._axisMaxSliderZ.setMaximum(AREA_HEIGHT) self._axisMaxSliderZ.setValue(AREA_HEIGHT) + self._areaMinValue = 0 + self._areaMaxValue = AREA_WIDTH + self._axisXMinValue = self._areaMinValue + self._axisXMaxValue = self._areaMaxValue + self._axisZMinValue = self._areaMinValue + self._axisZMaxValue = self._areaMaxValue + self._axisXMinRange = MIN_RANGE + self._axisZMinRange = MIN_RANGE + + self._graph.wheel.connect(self.onWheel) + self._graph.dragged.connect(self.handleAxisDragging) + self._graph.setZoomEnabled(False) + def adjustXMin(self, min): minX = self._stepX * float(min) + self._rangeMinX @@ -413,13 +443,13 @@ class SurfaceGraphModifier(QObject): def setBlackToYellowGradient(self): gr = QLinearGradient() - gr.setColorAt(0.0, Qt.black) + gr.setColorAt(0.0, Qt.GlobalColor.black) gr.setColorAt(0.33, Qt.blue) gr.setColorAt(0.67, Qt.red) gr.setColorAt(1.0, Qt.yellow) self._sqrtSinSeries.setBaseGradient(gr) - self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + self._sqrtSinSeries.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient) def setGreenToRedGradient(self): gr = QLinearGradient() @@ -429,7 +459,7 @@ class SurfaceGraphModifier(QObject): gr.setColorAt(1.0, Qt.darkRed) self._sqrtSinSeries.setBaseGradient(gr) - self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyle.RangeGradient) + self._sqrtSinSeries.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient) @Slot(bool) def toggleItemOne(self, show): @@ -437,8 +467,8 @@ class SurfaceGraphModifier(QObject): positionOnePipe = QVector3D(39.0, 45.0, 19.2) positionOneLabel = QVector3D(39.0, 107.0, 19.2) if show: - color = QImage(2, 2, QImage.Format_RGB32) - color.fill(Qt.red) + color = QImage(2, 2, QImage.Format.Format_RGB32) + color.fill(Qt.GlobalColor.red) file_name = os.fspath(self._data_path / "oilrig.mesh") item = QCustom3DItem(file_name, positionOne, QVector3D(0.025, 0.025, 0.025), @@ -469,8 +499,8 @@ class SurfaceGraphModifier(QObject): positionTwoPipe = QVector3D(34.5, 45.0, 23.4) positionTwoLabel = QVector3D(34.5, 107.0, 23.4) if show: - color = QImage(2, 2, QImage.Format_RGB32) - color.fill(Qt.red) + color = QImage(2, 2, QImage.Format.Format_RGB32) + color.fill(Qt.GlobalColor.red) item = QCustom3DItem() file_name = os.fspath(self._data_path / "oilrig.mesh") item.setMeshFile(file_name) @@ -502,7 +532,7 @@ class SurfaceGraphModifier(QObject): positionThree = QVector3D(34.5, 86.0, 19.1) positionThreeLabel = QVector3D(34.5, 116.0, 19.1) if show: - color = QImage(2, 2, QImage.Format_RGB32) + color = QImage(2, 2, QImage.Format.Format_RGB32) color.fill(Qt.darkMagenta) item = QCustom3DItem() file_name = os.fspath(self._data_path / "refinery.mesh") @@ -539,19 +569,19 @@ class SurfaceGraphModifier(QObject): s2 = self._graph.seriesList()[2] if highlight: grThree = QLinearGradient() - grThree.setColorAt(0.0, Qt.black) + grThree.setColorAt(0.0, Qt.GlobalColor.black) grThree.setColorAt(0.05, Qt.red) s2.setBaseGradient(grThree) else: grThree = QLinearGradient() - grThree.setColorAt(0.0, Qt.white) - grThree.setColorAt(0.05, Qt.black) + grThree.setColorAt(0.0, Qt.GlobalColor.white) + grThree.setColorAt(0.05, Qt.GlobalColor.black) s2.setBaseGradient(grThree) @Slot(bool) def toggleShadows(self, shadows): - sq = (QAbstract3DGraph.ShadowQualityMedium - if shadows else QAbstract3DGraph.ShadowQualityNone) + sq = (QtGraphs3D.ShadowQuality.Medium + if shadows else QtGraphs3D.ShadowQuality.None_) self._graph.setShadowQuality(sq) @Slot(bool) @@ -564,7 +594,7 @@ class SurfaceGraphModifier(QObject): def handleElementSelected(self, type): self.resetSelection() - if type == QAbstract3DGraph.ElementCustomItem: + if type == QtGraphs3D.ElementType.CustomItem: item = self._graph.selectedCustomItem() text = "" if isinstance(item, QCustom3DItem): @@ -581,7 +611,7 @@ class SurfaceGraphModifier(QObject): self._selectionAnimation.setStartValue(item.scaling()) self._selectionAnimation.setEndValue(item.scaling() * 1.5) self._selectionAnimation.start() - elif type == QAbstract3DGraph.ElementSeries: + elif type == QtGraphs3D.ElementType.Series: text = "Surface (" series = self._graph.selectedSeries() if series: @@ -589,16 +619,19 @@ class SurfaceGraphModifier(QObject): text += f"{point.x()}, {point.y()}" text += ")" self._textField.setText(text) - elif (type.value > QAbstract3DGraph.ElementSeries.value - and type < QAbstract3DGraph.ElementCustomItem.value): + elif (type.value > QtGraphs3D.ElementType.Series.value + and type.value < QtGraphs3D.ElementType.CustomItem.value): index = self._graph.selectedLabelIndex() text = "" - if type == QAbstract3DGraph.ElementAxisXLabel: + if type == QtGraphs3D.ElementType.AxisXLabel: text += "Axis X label: " - elif type == QAbstract3DGraph.ElementAxisYLabel: + self._state = InputState.StateDraggingX + elif type == QtGraphs3D.ElementType.AxisYLabel: text += "Axis Y label: " + self._state = InputState.StateDraggingY else: text += "Axis Z label: " + self._state = InputState.StateDraggingZ text += str(index) self._textField.setText(text) else: @@ -611,21 +644,21 @@ class SurfaceGraphModifier(QObject): self._previouslyAnimatedItem = None def toggleModeNone(self): - self._graph.setSelectionMode(QAbstract3DGraph.SelectionNone) + self._graph.setSelectionMode(QtGraphs3D.SelectionFlag.None_) def toggleModeItem(self): - self._graph.setSelectionMode(QAbstract3DGraph.SelectionItem) + self._graph.setSelectionMode(QtGraphs3D.SelectionFlag.Item) def toggleModeSliceRow(self): - sm = (QAbstract3DGraph.SelectionItemAndRow - | QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionMultiSeries) + sm = (QtGraphs3D.SelectionFlag.ItemAndRow + | QtGraphs3D.SelectionFlag.Slice + | QtGraphs3D.SelectionFlag.MultiSeries) self._graph.setSelectionMode(sm) def toggleModeSliceColumn(self): - sm = (QAbstract3DGraph.SelectionItemAndColumn - | QAbstract3DGraph.SelectionSlice - | QAbstract3DGraph.SelectionMultiSeries) + sm = (QtGraphs3D.SelectionFlag.ItemAndColumn + | QtGraphs3D.SelectionFlag.Slice + | QtGraphs3D.SelectionFlag.MultiSeries) self._graph.setSelectionMode(sm) def setAxisMinSliderX(self, slider): @@ -639,3 +672,91 @@ class SurfaceGraphModifier(QObject): def setAxisMaxSliderZ(self, slider): self._axisMaxSliderZ = slider + + def checkConstraints(self): + if self._axisXMinValue < self._areaMinValue: + self._axisXMinValue = self._areaMinValue + if self._axisXMaxValue > self._areaMaxValue: + self._axisXMaxValue = self._areaMaxValue + # Don't allow too much zoom in + range = self._axisXMaxValue - self._axisXMinValue + if range < self._axisXMinRange: + adjust = (self._axisXMinRange - range) / 2.0 + self._axisXMinValue -= adjust + self._axisXMaxValue += adjust + + if self._axisZMinValue < self._areaMinValue: + self._axisZMinValue = self._areaMinValue + if self._axisZMaxValue > self._areaMaxValue: + self._axisZMaxValue = self._areaMaxValue + # Don't allow too much zoom in + range = self._axisZMaxValue - self._axisZMinValue + if range < self._axisZMinRange: + adjust = (self._axisZMinRange - range) / 2.0 + self._axisZMinValue -= adjust + self._axisZMaxValue += adjust + + @Slot(QVector2D) + def handleAxisDragging(self, delta): + + distance = float(0) + + # Get scene orientation from active camera + xRotation = self._graph.cameraXRotation() + + # Calculate directional drag multipliers based on rotation + xMulX = cos(degrees(xRotation)) + xMulY = sin(degrees(xRotation)) + zMulX = sin(degrees(xRotation)) + zMulY = cos(degrees(xRotation)) + + # Get the drag amount + move = delta.toPoint() + + # Adjust axes + if self._state == InputState.StateDraggingX: + distance = (move.x() * xMulX - move.y() * xMulY) * SPEED_MODIFIER + self._axisXMinValue -= distance + self._axisXMaxValue -= distance + if self._axisXMinValue < self._areaMinValue: + dist = self._axisXMaxValue - self._axisXMinValue + self._axisXMinValue = self._areaMinValue + self._axisXMaxValue = self._axisXMinValue + dist + + if self._axisXMaxValue > self._areaMaxValue: + dist = self._axisXMaxValue - self._axisXMinValue + self._axisXMaxValue = self._areaMaxValue + self._axisXMinValue = self._axisXMaxValue - dist + + self._graph.axisX().setRange(self._axisXMinValue, self._axisXMaxValue) + elif self._state == InputState.StateDraggingZ: + distance = (move.x() * zMulX + move.y() * zMulY) * SPEED_MODIFIER + self._axisZMinValue += distance + self._axisZMaxValue += distance + if self._axisZMinValue < self._areaMinValue: + dist = self._axisZMaxValue - self._axisZMinValue + self._axisZMinValue = self._areaMinValue + self._axisZMaxValue = self._axisZMinValue + dist + + if self._axisZMaxValue > self._areaMaxValue: + dist = self._axisZMaxValue - self._axisZMinValue + self._axisZMaxValue = self._areaMaxValue + self._axisZMinValue = self._axisZMaxValue - dist + + self._graph.axisZ().setRange(self._axisZMinValue, self._axisZMaxValue) + + @Slot(QWheelEvent) + def onWheel(self, event): + delta = float(event.angleDelta().y()) + + self._axisXMinValue += delta + self._axisXMaxValue -= delta + self._axisZMinValue += delta + self._axisZMaxValue -= delta + self.checkConstraints() + + y = (self._axisXMaxValue - self._axisXMinValue) * ASPECT_RATIO + + self._graph.axisX().setRange(self._axisXMinValue, self._axisXMaxValue) + self._graph.axisY().setRange(100.0, y) + self._graph.axisZ().setRange(self._axisZMinValue, self._axisZMaxValue) diff --git a/examples/graphs/3d/widgetgallery/topographicseries.py b/examples/graphs/3d/widgetgraphgallery/topographicseries.py similarity index 89% rename from examples/graphs/3d/widgetgallery/topographicseries.py rename to examples/graphs/3d/widgetgraphgallery/topographicseries.py index 4f286a22..41cc35a3 100644 --- a/examples/graphs/3d/widgetgallery/topographicseries.py +++ b/examples/graphs/3d/widgetgraphgallery/topographicseries.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtGui import QImage, QVector3D @@ -16,9 +17,9 @@ class TopographicSeries(QSurface3DSeries): super().__init__() self._sampleCountX = 0.0 self._sampleCountZ = 0.0 - self.setDrawMode(QSurface3DSeries.DrawSurface) - self.setFlatShadingEnabled(True) - self.setBaseColor(Qt.white) + self.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface) + self.setShading(QSurface3DSeries.Shading.Flat) + self.setBaseColor(Qt.GlobalColor.white) def sampleCountX(self): return self._sampleCountX diff --git a/examples/graphs/3d/widgetgallery/variantbardatamapping.py b/examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py similarity index 98% rename from examples/graphs/3d/widgetgallery/variantbardatamapping.py rename to examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py index 50bdefa6..5b1986b8 100644 --- a/examples/graphs/3d/widgetgallery/variantbardatamapping.py +++ b/examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Signal diff --git a/examples/graphs/3d/widgetgallery/variantbardataproxy.py b/examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py similarity index 98% rename from examples/graphs/3d/widgetgallery/variantbardataproxy.py rename to examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py index 5ab2a2cd..9cd71b1a 100644 --- a/examples/graphs/3d/widgetgallery/variantbardataproxy.py +++ b/examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtGraphs import QBarDataProxy, QBarDataItem diff --git a/examples/graphs/3d/widgetgallery/variantdataset.py b/examples/graphs/3d/widgetgraphgallery/variantdataset.py similarity index 96% rename from examples/graphs/3d/widgetgallery/variantdataset.py rename to examples/graphs/3d/widgetgraphgallery/variantdataset.py index 752bc388..c9b8ab1a 100644 --- a/examples/graphs/3d/widgetgallery/variantdataset.py +++ b/examples/graphs/3d/widgetgraphgallery/variantdataset.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Signal diff --git a/examples/graphs/3d/widgetgallery/widgetgallery.pyproject b/examples/graphs/3d/widgetgraphgallery/widgetgraphgallery.pyproject similarity index 91% rename from examples/graphs/3d/widgetgallery/widgetgallery.pyproject rename to examples/graphs/3d/widgetgraphgallery/widgetgraphgallery.pyproject index 581b2148..ebc680f6 100644 --- a/examples/graphs/3d/widgetgallery/widgetgallery.pyproject +++ b/examples/graphs/3d/widgetgraphgallery/widgetgraphgallery.pyproject @@ -1,8 +1,6 @@ { "files": ["main.py", - "axesinputhandler.py", "bargraph.py", - "custominputhandler.py", "graphmodifier.py", "highlightseries.py", "rainfalldata.py", diff --git a/examples/gui/analogclock/main.py b/examples/gui/analogclock/main.py index c0e00dd9..28ce3454 100644 --- a/examples/gui/analogclock/main.py +++ b/examples/gui/analogclock/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -28,10 +29,10 @@ class AnalogClockWindow(QRasterWindow): QPoint(-1, -89), QPoint(1, -89)]) palette = qApp.palette() # noqa: F821 - self._background_color = palette.color(QPalette.Window) - self._hour_color = palette.color(QPalette.Text) - self._minute_color = palette.color(QPalette.Text) - self._seconds_color = palette.color(QPalette.Accent) + self._background_color = palette.color(QPalette.ColorRole.Window) + self._hour_color = palette.color(QPalette.ColorRole.Text) + self._minute_color = palette.color(QPalette.ColorRole.Text) + self._seconds_color = palette.color(QPalette.ColorRole.Accent) def paintEvent(self, e): with QPainter(self) as painter: @@ -44,13 +45,13 @@ class AnalogClockWindow(QRasterWindow): side = min(width, height) painter.fillRect(0, 0, width, height, self._background_color) - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.translate(width / 2, height / 2) painter.scale(side / 200.0, side / 200.0) time = QTime.currentTime() - painter.setPen(Qt.NoPen) + painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(self._hour_color) painter.save() diff --git a/examples/gui/rhiwindow/doc/rhiwindow.rst b/examples/gui/rhiwindow/doc/rhiwindow.rst index defbc46e..9d312080 100644 --- a/examples/gui/rhiwindow/doc/rhiwindow.rst +++ b/examples/gui/rhiwindow/doc/rhiwindow.rst @@ -1,5 +1,3 @@ -.. _rhi-window-example: - RHI Window Example ================== diff --git a/examples/gui/rhiwindow/main.py b/examples/gui/rhiwindow/main.py index cfb73b10..6c0f313b 100644 --- a/examples/gui/rhiwindow/main.py +++ b/examples/gui/rhiwindow/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from argparse import ArgumentParser, RawDescriptionHelpFormatter import sys @@ -14,11 +15,11 @@ if __name__ == "__main__": app = QGuiApplication(sys.argv) # Use platform-specific defaults when no command-line arguments given. - graphicsApi = QRhi.OpenGLES2 + graphicsApi = QRhi.Implementation.OpenGLES2 if sys.platform == "win32": - graphicsApi = QRhi.D3D11 + graphicsApi = QRhi.Implementation.D3D11 elif sys.platform == "darwin": - graphicsApi = QRhi.Metal + graphicsApi = QRhi.Implementation.Metal parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, description="QRhi render example") @@ -32,15 +33,15 @@ if __name__ == "__main__": help="Metal") args = parser.parse_args() if args.null: - graphicsApi = QRhi.Null + graphicsApi = QRhi.Implementation.Null elif args.opengl: - graphicsApi = QRhi.OpenGLES2 + graphicsApi = QRhi.Implementation.OpenGLES2 elif args.d3d11: - graphicsApi = QRhi.D3D11 + graphicsApi = QRhi.Implementation.D3D11 elif args.d3d12: - graphicsApi = QRhi.D3D12 + graphicsApi = QRhi.Implementation.D3D12 elif args.metal: - graphicsApi = QRhi.Metal + graphicsApi = QRhi.Implementation.Metal # graphicsApi = QRhi.Vulkan? detect? needs QVulkanInstance @@ -56,7 +57,7 @@ if __name__ == "__main__": # we get on macOS is not sufficient. if sys.platform == "darwin": fmt.setVersion(4, 1) - fmt.setProfile(QSurfaceFormat.CoreProfile) + fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.CoreProfile) QSurfaceFormat.setDefaultFormat(fmt) window = HelloWindow(graphicsApi) diff --git a/examples/gui/rhiwindow/rhiwindow.py b/examples/gui/rhiwindow/rhiwindow.py index fe054af4..d4522e51 100644 --- a/examples/gui/rhiwindow/rhiwindow.py +++ b/examples/gui/rhiwindow/rhiwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import numpy import sys @@ -37,7 +38,7 @@ UBUF_SIZE = 68 def getShader(name): f = QFile(name) - if f.open(QIODevice.ReadOnly): + if f.open(QIODevice.OpenModeFlag.ReadOnly): result = QShader.fromSerialized(f.readAll()) f.close() return result @@ -48,7 +49,7 @@ class RhiWindow(QWindow): def __init__(self, graphicsApi): super().__init__() - self.m_graphicsApi = QRhi.Null + self.m_graphicsApi = QRhi.Implementation.Null self.m_initialized = False self.m_notExposed = False self.m_newlyExposed = False @@ -63,15 +64,15 @@ class RhiWindow(QWindow): self.m_graphicsApi = graphicsApi - if graphicsApi == QRhi.OpenGLES2: + if graphicsApi == QRhi.Implementation.OpenGLES2: self.setSurfaceType(QSurface.SurfaceType.OpenGLSurface) - elif graphicsApi == QRhi.Vulkan: + elif graphicsApi == QRhi.Implementation.Vulkan: self.setSurfaceType(QSurface.SurfaceType.VulkanSurface) - elif graphicsApi == QRhi.D3D11 or graphicsApi == QRhi.D3D12: + elif graphicsApi == QRhi.Implementation.D3D11 or graphicsApi == QRhi.Implementation.D3D12: self.setSurfaceType(QSurface.SurfaceType.Direct3DSurface) - elif graphicsApi == QRhi.Metal: + elif graphicsApi == QRhi.Implementation.Metal: self.setSurfaceType(QSurface.SurfaceType.MetalSurface) - elif graphicsApi == QRhi.Null: + elif graphicsApi == QRhi.Implementation.Null: pass # RasterSurface def __del__(self): @@ -91,17 +92,17 @@ class RhiWindow(QWindow): self.m_fallbackSurface = None def graphicsApiName(self): - if self.m_graphicsApi == QRhi.Null: + if self.m_graphicsApi == QRhi.Implementation.Null: return "Null (no output)" - if self.m_graphicsApi == QRhi.OpenGLES2: + if self.m_graphicsApi == QRhi.Implementation.OpenGLES2: return "OpenGL" - if self.m_graphicsApi == QRhi.Vulkan: + if self.m_graphicsApi == QRhi.Implementation.Vulkan: return "Vulkan" - if self.m_graphicsApi == QRhi.D3D11: + if self.m_graphicsApi == QRhi.Implementation.D3D11: return "Direct3D 11" - if self.m_graphicsApi == QRhi.D3D12: + if self.m_graphicsApi == QRhi.Implementation.D3D12: return "Direct3D 12" - if self.m_graphicsApi == QRhi.Metal: + if self.m_graphicsApi == QRhi.Implementation.Metal: return "Metal" return "" @@ -140,51 +141,51 @@ class RhiWindow(QWindow): self.render() def event(self, e): - if e.type() == QEvent.UpdateRequest: + if e.type() == QEvent.Type.UpdateRequest: self.render() - elif e.type() == QEvent.PlatformSurface: + elif e.type() == QEvent.Type.PlatformSurface: # this is the proper time to tear down the swapchain (while # the native window and surface are still around) - if e.surfaceEventType() == QPlatformSurfaceEvent.SurfaceAboutToBeDestroyed: + if e.surfaceEventType() == QPlatformSurfaceEvent.SurfaceEventType.SurfaceAboutToBeDestroyed: # noqa: E501 self.releaseSwapChain() return super().event(e) def init(self): - if self.m_graphicsApi == QRhi.Null: + if self.m_graphicsApi == QRhi.Implementation.Null: params = QRhiNullInitParams() - self.m_rhi = QRhi.create(QRhi.Null, params) + self.m_rhi = QRhi.create(QRhi.Implementation.Null, params) - if self.m_graphicsApi == QRhi.OpenGLES2: + if self.m_graphicsApi == QRhi.Implementation.OpenGLES2: self.m_fallbackSurface = QRhiGles2InitParams.newFallbackSurface() params = QRhiGles2InitParams() params.fallbackSurface = self.m_fallbackSurface params.window = self - self.m_rhi = QRhi.create(QRhi.OpenGLES2, params) - elif self.m_graphicsApi == QRhi.D3D11: + self.m_rhi = QRhi.create(QRhi.Implementation.OpenGLES2, params) + elif self.m_graphicsApi == QRhi.Implementation.D3D11: params = QRhiD3D11InitParams() # Enable the debug layer, if available. This is optional # and should be avoided in production builds. params.enableDebugLayer = True - self.m_rhi = QRhi.create(QRhi.D3D11, params) - elif self.m_graphicsApi == QRhi.D3D12: + self.m_rhi = QRhi.create(QRhi.Implementation.D3D11, params) + elif self.m_graphicsApi == QRhi.Implementation.D3D12: params = QRhiD3D12InitParams() # Enable the debug layer, if available. This is optional # and should be avoided in production builds. params.enableDebugLayer = True - self.m_rhi = QRhi.create(QRhi.D3D12, params) - elif self.m_graphicsApi == QRhi.Metal: + self.m_rhi = QRhi.create(QRhi.Implementation.D3D12, params) + elif self.m_graphicsApi == QRhi.Implementation.Metal: params = QRhiMetalInitParams() - self.m_rhi.reset(QRhi.create(QRhi.Metal, params)) + self.m_rhi.reset(QRhi.create(QRhi.Implementation.Metal, params)) if not self.m_rhi: qFatal("Failed to create RHI backend") self.m_sc = self.m_rhi.newSwapChain() # no need to set the size here, due to UsedWithSwapChainOnly - self.m_ds = self.m_rhi.newRenderBuffer(QRhiRenderBuffer.DepthStencil, + self.m_ds = self.m_rhi.newRenderBuffer(QRhiRenderBuffer.Type.DepthStencil, QSize(), 1, - QRhiRenderBuffer.UsedWithSwapChainOnly) + QRhiRenderBuffer.Flag.UsedWithSwapChainOnly) self.m_sc.setWindow(self) self.m_sc.setDepthStencil(self.m_ds) self.m_rp = self.m_sc.newCompatibleRenderPassDescriptor() @@ -223,13 +224,13 @@ class RhiWindow(QWindow): self.m_newlyExposed = False result = self.m_rhi.beginFrame(self.m_sc) - if result == QRhi.FrameOpSwapChainOutOfDate: + if result == QRhi.FrameOpResult.FrameOpSwapChainOutOfDate: self.resizeSwapChain() if not self.m_hasSwapChain: return result = self.m_rhi.beginFrame(self.m_sc) - if result != QRhi.FrameOpSuccess: + if result != QRhi.FrameOpResult.FrameOpSuccess: qWarning(f"beginFrame failed with {result}, will retry") self.requestUpdate() return @@ -268,19 +269,19 @@ class HelloWindow(RhiWindow): return if not self.m_texture: - self.m_texture = self.m_rhi.newTexture(QRhiTexture.RGBA8, pixelSize) + self.m_texture = self.m_rhi.newTexture(QRhiTexture.Format.RGBA8, pixelSize) else: self.m_texture.setPixelSize(pixelSize) self.m_texture.create() - image = QImage(pixelSize, QImage.Format_RGBA8888_Premultiplied) + image = QImage(pixelSize, QImage.Format.Format_RGBA8888_Premultiplied) with QPainter(image) as painter: painter.fillRect(QRectF(QPointF(0, 0), pixelSize), QColor.fromRgbF(0.4, 0.7, 0.0, 1.0)) - painter.setPen(Qt.transparent) - painter.setBrush(QGradient(QGradient.DeepBlue)) + painter.setPen(Qt.GlobalColor.transparent) + painter.setBrush(QGradient(QGradient.Preset.DeepBlue)) painter.drawRoundedRect(QRectF(QPointF(20, 20), pixelSize - QSize(40, 40)), 16, 16) - painter.setPen(Qt.black) + painter.setPen(Qt.GlobalColor.black) font = QFont() font.setPixelSize(0.05 * min(pixelSize.width(), pixelSize.height())) painter.setFont(font) @@ -298,26 +299,29 @@ class HelloWindow(RhiWindow): self.m_initialUpdates = self.m_rhi.nextResourceUpdateBatch() vertex_size = 4 * VERTEX_DATA.size - self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Immutable, QRhiBuffer.VertexBuffer, + self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Type.Immutable, + QRhiBuffer.UsageFlag.VertexBuffer, vertex_size) self.m_vbuf.create() self.m_initialUpdates.uploadStaticBuffer(self.m_vbuf, VoidPtr(VERTEX_DATA.tobytes(), vertex_size)) - self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Dynamic, - QRhiBuffer.UniformBuffer, UBUF_SIZE) + self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Type.Dynamic, + QRhiBuffer.UsageFlag.UniformBuffer, UBUF_SIZE) self.m_ubuf.create() self.ensureFullscreenTexture(self.m_sc.surfacePixelSize(), self.m_initialUpdates) - self.m_sampler = self.m_rhi.newSampler(QRhiSampler.Linear, QRhiSampler.Linear, - QRhiSampler.None_, - QRhiSampler.ClampToEdge, QRhiSampler.ClampToEdge) + self.m_sampler = self.m_rhi.newSampler(QRhiSampler.Filter.Linear, + QRhiSampler.Filter.Linear, + QRhiSampler.Filter.None_, + QRhiSampler.AddressMode.ClampToEdge, + QRhiSampler.AddressMode.ClampToEdge) self.m_sampler.create() self.m_colorTriSrb = self.m_rhi.newShaderResourceBindings() - visibility = (QRhiShaderResourceBinding.VertexStage - | QRhiShaderResourceBinding.FragmentStage) + visibility = (QRhiShaderResourceBinding.StageFlag.VertexStage + | QRhiShaderResourceBinding.StageFlag.FragmentStage) bindings = [ QRhiShaderResourceBinding.uniformBuffer(0, visibility, self.m_ubuf) ] @@ -334,16 +338,17 @@ class HelloWindow(RhiWindow): premulAlphaBlend.enable = True self.m_colorPipeline.setTargetBlends([premulAlphaBlend]) stages = [ - QRhiShaderStage(QRhiShaderStage.Vertex, getShader(":/color.vert.qsb")), - QRhiShaderStage(QRhiShaderStage.Fragment, getShader(":/color.frag.qsb")) + QRhiShaderStage(QRhiShaderStage.Type.Vertex, getShader(":/color.vert.qsb")), + QRhiShaderStage(QRhiShaderStage.Type.Fragment, getShader(":/color.frag.qsb")) ] self.m_colorPipeline.setShaderStages(stages) inputLayout = QRhiVertexInputLayout() input_bindings = [QRhiVertexInputBinding(5 * 4)] # sizeof(float) inputLayout.setBindings(input_bindings) attributes = [ - QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Float2, 0), - QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Float3, 2 * 4)] # sizeof(float) + QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Format.Float2, 0), + # sizeof(float) + QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Format.Float3, 2 * 4)] inputLayout.setAttributes(attributes) self.m_colorPipeline.setVertexInputLayout(inputLayout) self.m_colorPipeline.setShaderResourceBindings(self.m_colorTriSrb) @@ -352,7 +357,7 @@ class HelloWindow(RhiWindow): self.m_fullscreenQuadSrb = self.m_rhi.newShaderResourceBindings() bindings = [ - QRhiShaderResourceBinding.sampledTexture(0, QRhiShaderResourceBinding.FragmentStage, + QRhiShaderResourceBinding.sampledTexture(0, QRhiShaderResourceBinding.StageFlag.FragmentStage, # noqa: E501 self.m_texture, self.m_sampler) ] self.m_fullscreenQuadSrb.setBindings(bindings) @@ -360,8 +365,8 @@ class HelloWindow(RhiWindow): self.m_fullscreenQuadPipeline = self.m_rhi.newGraphicsPipeline() stages = [ - QRhiShaderStage(QRhiShaderStage.Vertex, getShader(":/quad.vert.qsb")), - QRhiShaderStage(QRhiShaderStage.Fragment, getShader(":/quad.frag.qsb")) + QRhiShaderStage(QRhiShaderStage.Type.Vertex, getShader(":/quad.vert.qsb")), + QRhiShaderStage(QRhiShaderStage.Type.Fragment, getShader(":/quad.frag.qsb")) ] self.m_fullscreenQuadPipeline.setShaderStages(stages) layout = QRhiVertexInputLayout() @@ -402,7 +407,7 @@ class HelloWindow(RhiWindow): self.ensureFullscreenTexture(outputSizeInPixels, resourceUpdates) cv = QRhiDepthStencilClearValue(1.0, 0) - cb.beginPass(self.m_sc.currentFrameRenderTarget(), Qt.black, + cb.beginPass(self.m_sc.currentFrameRenderTarget(), Qt.GlobalColor.black, cv, resourceUpdates) cb.setGraphicsPipeline(self.m_fullscreenQuadPipeline) diff --git a/examples/httpserver/afterrequest/doc/afterrequest.rst b/examples/httpserver/afterrequest/doc/afterrequest.rst deleted file mode 100644 index 0e81a670..00000000 --- a/examples/httpserver/afterrequest/doc/afterrequest.rst +++ /dev/null @@ -1,5 +0,0 @@ -HTTP Server After Request Example -================================= - -A Python application that demonstrates the analogous example in C++ -`AfterRequest Example `_ diff --git a/examples/httpserver/simplehttpserver/doc/simplehttpserver.rst b/examples/httpserver/simplehttpserver/doc/simplehttpserver.rst new file mode 100644 index 00000000..f23998eb --- /dev/null +++ b/examples/httpserver/simplehttpserver/doc/simplehttpserver.rst @@ -0,0 +1,5 @@ +Simple HTTP Server Example +========================== + +A simplified version of the C++ example +`Simple HTTP Server Example `_ diff --git a/examples/httpserver/afterrequest/main.py b/examples/httpserver/simplehttpserver/main.py similarity index 57% rename from examples/httpserver/afterrequest/main.py rename to examples/httpserver/simplehttpserver/main.py index e68c992e..eb3f5694 100644 --- a/examples/httpserver/afterrequest/main.py +++ b/examples/httpserver/simplehttpserver/main.py @@ -1,13 +1,14 @@ # Copyright (C) 2020 Mikhail Svetkin # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the /httpserver/afterrequest from from Qt""" import sys from PySide6.QtCore import QCoreApplication -from PySide6.QtNetwork import QHostAddress +from PySide6.QtNetwork import QHttpHeaders, QTcpServer from PySide6.QtHttpServer import QHttpServer @@ -15,8 +16,11 @@ def route(request): return "Hello world" -def after_request(response, request): - response.setHeader(b"Server", b"Super server!") +def after_request(request, response): + headers = response.headers() + headers.append(QHttpHeaders.WellKnownHeader.WWWAuthenticate, + 'Basic realm="Simple example", charset="UTF-8"') + response.setHeaders(headers) if __name__ == '__main__': @@ -24,12 +28,13 @@ if __name__ == '__main__': httpServer = QHttpServer() httpServer.route("/", route) - httpServer.afterRequest(after_request) + httpServer.addAfterRequestHandler(httpServer, after_request) - port = httpServer.listen(QHostAddress.Any) - if port == 0: + tcpServer = QTcpServer() + if not tcpServer.listen() or not httpServer.bind(tcpServer): print("Server failed to listen on a port.", file=sys.stderr) sys.exit(-1) + port = tcpServer.serverPort() print(f"Running on http://127.0.0.1:{port}/ (Press CTRL+\\ to quit)") diff --git a/examples/httpserver/simplehttpserver/simplehttpserver.pyproject b/examples/httpserver/simplehttpserver/simplehttpserver.pyproject new file mode 100644 index 00000000..cc7a74a3 --- /dev/null +++ b/examples/httpserver/simplehttpserver/simplehttpserver.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py"] +} diff --git a/examples/installer_test/hello.py b/examples/installer_test/hello.py index 3921b5d5..162327c4 100644 --- a/examples/installer_test/hello.py +++ b/examples/installer_test/hello.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """ hello.py @@ -35,7 +36,7 @@ class MyWidget(QWidget): self.button = QPushButton("Click me!") self.text = QLabel(f"Hello World auto_quit={auto_quit}") - self.text.setAlignment(Qt.AlignCenter) + self.text.setAlignment(Qt.AlignmentFlag.AlignCenter) self.layout = QVBoxLayout() self.layout.addWidget(self.text) diff --git a/examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml index 426c7275..f2206111 100644 --- a/examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml +++ b/examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml @@ -66,4 +66,3 @@ Item { } } } - diff --git a/examples/location/mapviewer/main.py b/examples/location/mapviewer/main.py index 24ae1623..1eefb497 100644 --- a/examples/location/mapviewer/main.py +++ b/examples/location/mapviewer/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the location/mapviewer example from Qt v6.x""" @@ -70,6 +71,6 @@ if __name__ == "__main__": QMetaObject.invokeMethod(items[0], "initializeProviders", Q_ARG("QVariant", parameters)) - ex = application.exec() + exit_code = application.exec() del engine - sys.exit(ex) + sys.exit(exit_code) diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py index 06d52f68..b0ab567d 100644 --- a/examples/multimedia/audiooutput/audiooutput.py +++ b/examples/multimedia/audiooutput/audiooutput.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the multimedia/audiooutput example from Qt v5.x, originating from PyQt""" @@ -28,7 +29,7 @@ class Generator(QIODevice): self.generate_data(format, durationUs, sampleRate) def start(self): - self.open(QIODevice.ReadOnly) + self.open(QIODevice.OpenModeFlag.ReadOnly) def stop(self): self.m_pos = 0 @@ -39,18 +40,18 @@ class Generator(QIODevice): sample_size = fmt.bytesPerSample() * 8 if sample_size == 8: - if fmt.sampleFormat() == QAudioFormat.UInt8: + if fmt.sampleFormat() == QAudioFormat.SampleFormat.UInt8: scaler = lambda x: ((1.0 + x) / 2 * 255) # noqa: E731 pack_format = 'B' - elif fmt.sampleFormat() == QAudioFormat.Int16: + elif fmt.sampleFormat() == QAudioFormat.SampleFormat.Int16: scaler = lambda x: x * 127 # noqa: E731 pack_format = 'b' elif sample_size == 16: - little_endian = QSysInfo.ByteOrder == QSysInfo.LittleEndian - if fmt.sampleFormat() == QAudioFormat.UInt8: + little_endian = QSysInfo.Endian.ByteOrder == QSysInfo.Endian.LittleEndian + if fmt.sampleFormat() == QAudioFormat.SampleFormat.UInt8: scaler = lambda x: (1.0 + x) / 2 * 65535 # noqa: E731 pack_format = 'H' - elif fmt.sampleFormat() == QAudioFormat.Int16: + elif fmt.sampleFormat() == QAudioFormat.SampleFormat.Int16: scaler = lambda x: x * 32767 # noqa: E731 pack_format = 'h' @@ -139,7 +140,8 @@ class AudioTest(QMainWindow): volume_box = QHBoxLayout() volume_label = QLabel("Volume:") - self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10) + self.m_volumeSlider = QSlider(Qt.Orientation.Horizontal, + minimum=0, maximum=100, singleStep=10) self.m_volumeSlider.valueChanged.connect(self.volume_changed) volume_box.addWidget(volume_label) @@ -157,7 +159,7 @@ class AudioTest(QMainWindow): self.m_format = QAudioFormat() self.m_format.setSampleRate(self.DATA_SAMPLE_RATE_HZ) self.m_format.setChannelCount(1) - self.m_format.setSampleFormat(QAudioFormat.Int16) + self.m_format.setSampleFormat(QAudioFormat.SampleFormat.Int16) info = self.m_devices[0] if not info.isFormatSupported(self.m_format): @@ -209,7 +211,7 @@ class AudioTest(QMainWindow): @Slot() def pull_timer_expired(self): - if self.m_audioSink is not None and self.m_audioSink.state() != QAudio.StoppedState: + if self.m_audioSink is not None and self.m_audioSink.state() != QAudio.State.StoppedState: bytes_free = self.m_audioSink.bytesFree() data = self.m_generator.read(bytes_free) if data: @@ -234,26 +236,26 @@ class AudioTest(QMainWindow): @Slot() def toggle_suspend_resume(self): - if self.m_audioSink.state() == QAudio.SuspendedState: + if self.m_audioSink.state() == QAudio.State.SuspendedState: qWarning("status: Suspended, resume()") self.m_audioSink.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) - elif self.m_audioSink.state() == QAudio.ActiveState: + elif self.m_audioSink.state() == QAudio.State.ActiveState: qWarning("status: Active, suspend()") self.m_audioSink.suspend() self.m_suspendResumeButton.setText(self.RESUME_LABEL) - elif self.m_audioSink.state() == QAudio.StoppedState: + elif self.m_audioSink.state() == QAudio.State.StoppedState: qWarning("status: Stopped, resume()") self.m_audioSink.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) - elif self.m_audioSink.state() == QAudio.IdleState: + elif self.m_audioSink.state() == QAudio.State.IdleState: qWarning("status: IdleState") state_map = { - QAudio.ActiveState: "ActiveState", - QAudio.SuspendedState: "SuspendedState", - QAudio.StoppedState: "StoppedState", - QAudio.IdleState: "IdleState"} + QAudio.State.ActiveState: "ActiveState", + QAudio.State.SuspendedState: "SuspendedState", + QAudio.State.StoppedState: "StoppedState", + QAudio.State.IdleState: "IdleState"} @Slot("QAudio::State") def handle_state_changed(self, state): diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py index a78beb58..1c0d9841 100644 --- a/examples/multimedia/audiosource/audiosource.py +++ b/examples/multimedia/audiosource/audiosource.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """ PySide6 port of Qt6 example examples/multimedia/audiosources @@ -14,7 +15,6 @@ QIODevice have python bindings that needs to be fixed. """ import os import sys -from typing import Optional import PySide6 from PySide6.QtCore import QByteArray, QMargins, Qt, Slot, qWarning @@ -56,10 +56,10 @@ class AudioInfo: class RenderArea(QWidget): - def __init__(self, parent: Optional[PySide6.QtWidgets.QWidget] = None) -> None: + def __init__(self, parent: PySide6.QtWidgets.QWidget | None = None) -> None: super().__init__(parent=parent) self.m_level = 0 - self.setBackgroundRole(QPalette.Base) + self.setBackgroundRole(QPalette.ColorRole.Base) self.setAutoFillBackground(True) self.setMinimumHeight(30) self.setMinimumWidth(200) @@ -70,7 +70,7 @@ class RenderArea(QWidget): def paintEvent(self, event: PySide6.QtGui.QPaintEvent) -> None: with QPainter(self) as painter: - painter.setPen(Qt.black) + painter.setPen(Qt.GlobalColor.black) frame = painter.viewport() - QMargins(10, 10, 10, 10) painter.drawRect(frame) @@ -79,9 +79,8 @@ class RenderArea(QWidget): return pos: int = round((frame.width() - 1) * self.m_level) - painter.fillRect( - frame.left() + 1, frame.top() + 1, pos, frame.height() - 1, Qt.red - ) + painter.fillRect(frame.left() + 1, frame.top() + 1, pos, frame.height() - 1, + Qt.GlobalColor.red) class InputTest(QWidget): @@ -134,7 +133,7 @@ class InputTest(QWidget): self.m_device_box.activated[int].connect(self.device_changed) self.layout.addWidget(self.m_device_box) - self.m_volume_slider = QSlider(Qt.Horizontal, self) + self.m_volume_slider = QSlider(Qt.Orientation.Horizontal, self) self.m_volume_slider.setRange(0, 100) self.m_volume_slider.setValue(100) self.m_volume_slider.valueChanged.connect(self.slider_changed) @@ -152,22 +151,22 @@ class InputTest(QWidget): self.layout = QVBoxLayout(self) error_label = QLabel(self.tr("Microphone permission is not granted!")) error_label.setWordWrap(True) - error_label.setAlignment(Qt.AlignCenter) + error_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.layout.addWidget(error_label) def initialize_audio(self, device_info: QAudioDevice): format = QAudioFormat() format.setSampleRate(8000) format.setChannelCount(1) - format.setSampleFormat(QAudioFormat.Int16) + format.setSampleFormat(QAudioFormat.SampleFormat.Int16) self.m_audio_info = AudioInfo(format) self.m_audio_input = QAudioSource(device_info, format) initial_volume = QAudio.convertVolume( self.m_audio_input.volume(), - QAudio.LinearVolumeScale, - QAudio.LogarithmicVolumeScale, + QAudio.VolumeScale.LinearVolumeScale, + QAudio.VolumeScale.LogarithmicVolumeScale, ) self.m_volume_slider.setValue(int(round(initial_volume * 100))) self.toggle_mode() @@ -196,10 +195,10 @@ class InputTest(QWidget): def toggle_suspend(self): # toggle suspend/resume state = self.m_audio_input.state() - if (state == QAudio.SuspendedState) or (state == QAudio.StoppedState): + if (state == QAudio.State.SuspendedState) or (state == QAudio.State.StoppedState): self.m_audio_input.resume() self.m_suspend_resume_button.setText("Suspend recording") - elif state == QAudio.ActiveState: + elif state == QAudio.State.ActiveState: self.m_audio_input.suspend() self.m_suspend_resume_button.setText("Resume recording") # else no-op @@ -212,10 +211,9 @@ class InputTest(QWidget): @Slot(int) def slider_changed(self, value): - linearVolume = QAudio.convertVolume( - value / float(100), QAudio.LogarithmicVolumeScale, QAudio.LinearVolumeScale - ) - + linearVolume = QAudio.convertVolume(value / float(100), + QAudio.VolumeScale.LogarithmicVolumeScale, + QAudio.VolumeScale.LinearVolumeScale) self.m_audio_input.setVolume(linearVolume) diff --git a/examples/multimedia/audiosource/doc/audiosource.rst b/examples/multimedia/audiosource/doc/audiosource.rst index 3a247c50..9b0d98bf 100644 --- a/examples/multimedia/audiosource/doc/audiosource.rst +++ b/examples/multimedia/audiosource/doc/audiosource.rst @@ -4,10 +4,9 @@ Audio Source Example .. tags:: Android A Python application that demonstrates the analogous example in C++ -`Audio Source Example `_ +`Audio Source Example `_ .. image:: audiosource.png :width: 400 :alt: audiosource example - diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py index fa379c80..ecc8f701 100644 --- a/examples/multimedia/camera/camera.py +++ b/examples/multimedia/camera/camera.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os import sys @@ -152,10 +153,10 @@ class Camera(QMainWindow): return key = event.key() - if key == Qt.Key_CameraFocus: + if key == Qt.Key.Key_CameraFocus: self.displayViewfinder() event.accept() - elif key == Qt.Key_Camera: + elif key == Qt.Key.Key_Camera: if self.m_doImageCapture: self.takeImage() else: @@ -175,8 +176,9 @@ class Camera(QMainWindow): @Slot(int, QImage) def processCapturedImage(self, requestId, img): - scaled_image = img.scaled(self._ui.viewfinder.size(), Qt.KeepAspectRatio, - Qt.SmoothTransformation) + scaled_image = img.scaled(self._ui.viewfinder.size(), + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation) self._ui.lastImagePreviewLabel.setPixmap(QPixmap.fromImage(scaled_image)) @@ -260,17 +262,17 @@ class Camera(QMainWindow): @Slot(QMediaRecorder.RecorderState) def updateRecorderState(self, state): - if state == QMediaRecorder.StoppedState: + if state == QMediaRecorder.RecorderState.StoppedState: self._ui.recordButton.setEnabled(True) self._ui.pauseButton.setEnabled(True) self._ui.stopButton.setEnabled(False) self._ui.metaDataButton.setEnabled(True) - elif state == QMediaRecorder.PausedState: + elif state == QMediaRecorder.RecorderState.PausedState: self._ui.recordButton.setEnabled(True) self._ui.pauseButton.setEnabled(False) self._ui.stopButton.setEnabled(True) self._ui.metaDataButton.setEnabled(False) - elif state == QMediaRecorder.RecordingState: + elif state == QMediaRecorder.RecorderState.RecordingState: self._ui.recordButton.setEnabled(False) self._ui.pauseButton.setEnabled(True) self._ui.stopButton.setEnabled(True) @@ -343,8 +345,8 @@ class Camera(QMainWindow): def showMetaDataDialog(self): if not self.m_metaDataDialog: self.m_metaDataDialog = MetaDataDialog(self) - self.m_metaDataDialog.setAttribute(Qt.WA_DeleteOnClose, False) - if self.m_metaDataDialog.exec() == QDialog.Accepted: + self.m_metaDataDialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False) + if self.m_metaDataDialog.exec() == QDialog.DialogCode.Accepted: self.saveMetaData() @Slot() @@ -354,13 +356,13 @@ class Camera(QMainWindow): val = self.m_metaDataDialog.m_metaDataFields[i].text() if val: key = QMediaMetaData.Key(i) - if key == QMediaMetaData.CoverArtImage: + if key == QMediaMetaData.Key.CoverArtImage: cover_art = QImage(val) data.insert(key, cover_art) - elif key == QMediaMetaData.ThumbnailImage: + elif key == QMediaMetaData.Key.ThumbnailImage: thumbnail = QImage(val) data.insert(key, thumbnail) - elif key == QMediaMetaData.Date: + elif key == QMediaMetaData.Key.Date: date = QDateTime.fromString(val) data.insert(key, date) else: diff --git a/examples/multimedia/camera/camera.ui b/examples/multimedia/camera/camera.ui index 4584e909..a338fb51 100644 --- a/examples/multimedia/camera/camera.ui +++ b/examples/multimedia/camera/camera.ui @@ -28,7 +28,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -60,10 +60,10 @@ 2 - Qt::Horizontal + Qt::Orientation::Horizontal - QSlider::TicksAbove + QSlider::TickPosition::TicksAbove @@ -105,7 +105,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -232,7 +232,7 @@ - QFrame::Box + QFrame::Shape::Box @@ -251,7 +251,7 @@ 0 0 668 - 19 + 26 diff --git a/examples/multimedia/camera/camera_mobile.ui b/examples/multimedia/camera/camera_mobile.ui index 7f269b17..f36722ff 100644 --- a/examples/multimedia/camera/camera_mobile.ui +++ b/examples/multimedia/camera/camera_mobile.ui @@ -43,10 +43,10 @@ 2 - Qt::Horizontal + Qt::Orientation::Horizontal - QSlider::TicksAbove + QSlider::TickPosition::TicksAbove @@ -116,7 +116,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -254,7 +254,7 @@ - QFrame::Box + QFrame::Shape::Box @@ -273,7 +273,7 @@ 0 0 668 - 22 + 26 diff --git a/examples/multimedia/camera/imagesettings.py b/examples/multimedia/camera/imagesettings.py index 9ca1d92c..392681f0 100644 --- a/examples/multimedia/camera/imagesettings.py +++ b/examples/multimedia/camera/imagesettings.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtMultimedia import QImageCapture from PySide6.QtWidgets import QDialog diff --git a/examples/multimedia/camera/imagesettings.ui b/examples/multimedia/camera/imagesettings.ui index 8c59ca01..6eb37adf 100644 --- a/examples/multimedia/camera/imagesettings.ui +++ b/examples/multimedia/camera/imagesettings.ui @@ -53,7 +53,7 @@ 4 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -63,7 +63,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -76,10 +76,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/examples/multimedia/camera/main.py b/examples/multimedia/camera/main.py index fd4dd32e..004f8fac 100644 --- a/examples/multimedia/camera/main.py +++ b/examples/multimedia/camera/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the QtMultiMedia camera example from Qt v6.x""" diff --git a/examples/multimedia/camera/metadatadialog.py b/examples/multimedia/camera/metadatadialog.py index 97d0a36e..16fbde10 100644 --- a/examples/multimedia/camera/metadatadialog.py +++ b/examples/multimedia/camera/metadatadialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtMultimedia import QMediaMetaData from PySide6.QtWidgets import (QDialog, QDialogButtonBox, QFileDialog, @@ -12,11 +13,11 @@ IMAGE_FILTER = "Image Files (*.png *.jpg *.bmp)" def default_value(key): - if key == QMediaMetaData.Title: + if key == QMediaMetaData.Key.Title: return "Qt Camera Example" - if key == QMediaMetaData.Author: + if key == QMediaMetaData.Key.Author: return "The Qt Company" - if key == QMediaMetaData.Date: + if key == QMediaMetaData.Key.Date: return QDateTime.currentDateTime().toString() return "" @@ -34,14 +35,14 @@ class MetaDataDialog(QDialog): line_edit = QLineEdit(default_value(key)) line_edit.setClearButtonEnabled(True) self.m_metaDataFields.append(line_edit) - if key == QMediaMetaData.ThumbnailImage: + if key == QMediaMetaData.Key.ThumbnailImage: open_thumbnail = QPushButton("Open") open_thumbnail.clicked.connect(self.open_thumbnail_image) layout = QHBoxLayout() layout.addWidget(line_edit) layout.addWidget(open_thumbnail) meta_data_layout.addRow(label, layout) - elif key == QMediaMetaData.CoverArtImage: + elif key == QMediaMetaData.Key.CoverArtImage: open_cover_art = QPushButton("Open") open_cover_art.clicked.connect(self.open_cover_art_image) layout = QHBoxLayout() @@ -58,7 +59,8 @@ class MetaDataDialog(QDialog): dialog_layout = QVBoxLayout(self) dialog_layout.addWidget(scroll_area) - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) dialog_layout.addWidget(button_box) self.setWindowTitle("Set Metadata") diff --git a/examples/multimedia/camera/ui_camera.py b/examples/multimedia/camera/ui_camera.py index 690cf335..db78ecf5 100644 --- a/examples/multimedia/camera/ui_camera.py +++ b/examples/multimedia/camera/ui_camera.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'camera.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -62,8 +62,8 @@ class Ui_Camera(object): self.exposureCompensation.setMinimum(-4) self.exposureCompensation.setMaximum(4) self.exposureCompensation.setPageStep(2) - self.exposureCompensation.setOrientation(Qt.Horizontal) - self.exposureCompensation.setTickPosition(QSlider.TicksAbove) + self.exposureCompensation.setOrientation(Qt.Orientation.Horizontal) + self.exposureCompensation.setTickPosition(QSlider.TickPosition.TicksAbove) self.gridLayout.addWidget(self.exposureCompensation, 5, 0, 1, 1) @@ -152,7 +152,7 @@ class Ui_Camera(object): sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth()) self.lastImagePreviewLabel.setSizePolicy(sizePolicy1) - self.lastImagePreviewLabel.setFrameShape(QFrame.Box) + self.lastImagePreviewLabel.setFrameShape(QFrame.Shape.Box) self.gridLayout_4.addWidget(self.lastImagePreviewLabel, 0, 0, 1, 1) @@ -163,7 +163,7 @@ class Ui_Camera(object): Camera.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(Camera) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 668, 19)) + self.menubar.setGeometry(QRect(0, 0, 668, 26)) self.menuFile = QMenu(self.menubar) self.menuFile.setObjectName(u"menuFile") self.menuDevices = QMenu(self.menubar) diff --git a/examples/multimedia/camera/ui_camera_mobile.py b/examples/multimedia/camera/ui_camera_mobile.py index 5cdd81f1..a9bd4d09 100644 --- a/examples/multimedia/camera/ui_camera_mobile.py +++ b/examples/multimedia/camera/ui_camera_mobile.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'camera_mobile.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -56,8 +56,8 @@ class Ui_Camera(object): self.exposureCompensation.setMinimum(-4) self.exposureCompensation.setMaximum(4) self.exposureCompensation.setPageStep(2) - self.exposureCompensation.setOrientation(Qt.Horizontal) - self.exposureCompensation.setTickPosition(QSlider.TicksAbove) + self.exposureCompensation.setOrientation(Qt.Orientation.Horizontal) + self.exposureCompensation.setTickPosition(QSlider.TickPosition.TicksAbove) self.gridLayout.addWidget(self.exposureCompensation, 4, 0, 1, 1) @@ -75,7 +75,7 @@ class Ui_Camera(object): self.takeImageButton.setObjectName(u"takeImageButton") self.takeImageButton.setEnabled(False) icon = QIcon() - icon.addFile(u":/images/shutter.svg", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(u":/images/shutter.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.takeImageButton.setIcon(icon) self.gridLayout.addWidget(self.takeImageButton, 0, 0, 1, 1) @@ -180,7 +180,7 @@ class Ui_Camera(object): sizePolicy4.setVerticalStretch(0) sizePolicy4.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth()) self.lastImagePreviewLabel.setSizePolicy(sizePolicy4) - self.lastImagePreviewLabel.setFrameShape(QFrame.Box) + self.lastImagePreviewLabel.setFrameShape(QFrame.Shape.Box) self.gridLayout_4.addWidget(self.lastImagePreviewLabel, 0, 0, 1, 1) @@ -191,7 +191,7 @@ class Ui_Camera(object): Camera.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(Camera) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 668, 22)) + self.menubar.setGeometry(QRect(0, 0, 668, 26)) self.menuFile = QMenu(self.menubar) self.menuFile.setObjectName(u"menuFile") self.menuDevices = QMenu(self.menubar) diff --git a/examples/multimedia/camera/ui_imagesettings.py b/examples/multimedia/camera/ui_imagesettings.py index a3fba778..53fdc86b 100644 --- a/examples/multimedia/camera/ui_imagesettings.py +++ b/examples/multimedia/camera/ui_imagesettings.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'imagesettings.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -58,7 +58,7 @@ class Ui_ImageSettingsUi(object): self.imageQualitySlider = QSlider(self.groupBox_2) self.imageQualitySlider.setObjectName(u"imageQualitySlider") self.imageQualitySlider.setMaximum(4) - self.imageQualitySlider.setOrientation(Qt.Horizontal) + self.imageQualitySlider.setOrientation(Qt.Orientation.Horizontal) self.gridLayout_2.addWidget(self.imageQualitySlider, 4, 1, 1, 1) @@ -71,8 +71,8 @@ class Ui_ImageSettingsUi(object): self.buttonBox = QDialogButtonBox(ImageSettingsUi) self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + self.buttonBox.setOrientation(Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok) self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 1) diff --git a/examples/multimedia/camera/ui_videosettings.py b/examples/multimedia/camera/ui_videosettings.py index eec626f2..8a443b52 100644 --- a/examples/multimedia/camera/ui_videosettings.py +++ b/examples/multimedia/camera/ui_videosettings.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'videosettings.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -29,8 +29,8 @@ class Ui_VideoSettingsUi(object): self.gridLayout_3.setObjectName(u"gridLayout_3") self.buttonBox = QDialogButtonBox(VideoSettingsUi) self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + self.buttonBox.setOrientation(Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok) self.gridLayout_3.addWidget(self.buttonBox, 4, 1, 1, 1) @@ -72,7 +72,7 @@ class Ui_VideoSettingsUi(object): self.fpsSlider = QSlider(self.groupBox_2) self.fpsSlider.setObjectName(u"fpsSlider") - self.fpsSlider.setOrientation(Qt.Horizontal) + self.fpsSlider.setOrientation(Qt.Orientation.Horizontal) self.horizontalLayout.addWidget(self.fpsSlider) @@ -131,7 +131,7 @@ class Ui_VideoSettingsUi(object): self.qualitySlider = QSlider(self.groupBox) self.qualitySlider.setObjectName(u"qualitySlider") self.qualitySlider.setMaximum(4) - self.qualitySlider.setOrientation(Qt.Horizontal) + self.qualitySlider.setOrientation(Qt.Orientation.Horizontal) self.verticalLayout.addWidget(self.qualitySlider) diff --git a/examples/multimedia/camera/ui_videosettings_mobile.py b/examples/multimedia/camera/ui_videosettings_mobile.py index 50fb8e08..ed9b01f7 100644 --- a/examples/multimedia/camera/ui_videosettings_mobile.py +++ b/examples/multimedia/camera/ui_videosettings_mobile.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'videosettings_mobile.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -24,7 +24,7 @@ class Ui_VideoSettingsUi(object): def setupUi(self, VideoSettingsUi): if not VideoSettingsUi.objectName(): VideoSettingsUi.setObjectName(u"VideoSettingsUi") - VideoSettingsUi.resize(329, 591) + VideoSettingsUi.resize(329, 610) self.gridLayout_3 = QGridLayout(VideoSettingsUi) self.gridLayout_3.setObjectName(u"gridLayout_3") self.widget = QWidget(VideoSettingsUi) @@ -76,7 +76,7 @@ class Ui_VideoSettingsUi(object): self.qualitySlider = QSlider(self.groupBox) self.qualitySlider.setObjectName(u"qualitySlider") self.qualitySlider.setMaximum(4) - self.qualitySlider.setOrientation(Qt.Horizontal) + self.qualitySlider.setOrientation(Qt.Orientation.Horizontal) self.verticalLayout.addWidget(self.qualitySlider) @@ -127,8 +127,8 @@ class Ui_VideoSettingsUi(object): self.buttonBox = QDialogButtonBox(self.groupBox_2) self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + self.buttonBox.setOrientation(Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok) self.gridLayout_2.addWidget(self.buttonBox, 7, 0, 1, 1) @@ -143,7 +143,7 @@ class Ui_VideoSettingsUi(object): self.fpsSlider = QSlider(self.groupBox_2) self.fpsSlider.setObjectName(u"fpsSlider") - self.fpsSlider.setOrientation(Qt.Horizontal) + self.fpsSlider.setOrientation(Qt.Orientation.Horizontal) self.horizontalLayout.addWidget(self.fpsSlider) diff --git a/examples/multimedia/camera/videosettings.py b/examples/multimedia/camera/videosettings.py index a88cb39e..d0485023 100644 --- a/examples/multimedia/camera/videosettings.py +++ b/examples/multimedia/camera/videosettings.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from PySide6.QtMultimedia import (QCameraFormat, QMediaFormat, QMediaRecorder, diff --git a/examples/multimedia/camera/videosettings.ui b/examples/multimedia/camera/videosettings.ui index 3c1f71f1..ec35e957 100644 --- a/examples/multimedia/camera/videosettings.ui +++ b/examples/multimedia/camera/videosettings.ui @@ -17,10 +17,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok @@ -65,7 +65,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -140,7 +140,7 @@ 4 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -163,7 +163,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/examples/multimedia/camera/videosettings_mobile.ui b/examples/multimedia/camera/videosettings_mobile.ui index 6584f07f..06d20684 100644 --- a/examples/multimedia/camera/videosettings_mobile.ui +++ b/examples/multimedia/camera/videosettings_mobile.ui @@ -7,7 +7,7 @@ 0 0 329 - 591 + 610 @@ -80,7 +80,7 @@ 4 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -136,10 +136,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok @@ -158,7 +158,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py index d28f2887..23fdbb4c 100644 --- a/examples/multimedia/player/player.py +++ b/examples/multimedia/player/player.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 Multimedia player example""" @@ -9,7 +10,7 @@ from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, QMainWindow, QSlider, QStyle, QToolBar) from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat, - QMediaPlayer) + QMediaPlayer, QAudio) from PySide6.QtMultimediaWidgets import QVideoWidget @@ -87,16 +88,16 @@ class MainWindow(QMainWindow): play_menu.addAction(self._stop_action) self._volume_slider = QSlider() - self._volume_slider.setOrientation(Qt.Horizontal) + self._volume_slider.setOrientation(Qt.Orientation.Horizontal) self._volume_slider.setMinimum(0) self._volume_slider.setMaximum(100) available_width = self.screen().availableGeometry().width() self._volume_slider.setFixedWidth(available_width / 10) - self._volume_slider.setValue(self._audio_output.volume()) + self._volume_slider.setValue(self._audio_output.volume() * 100) self._volume_slider.setTickInterval(10) self._volume_slider.setTickPosition(QSlider.TicksBelow) self._volume_slider.setToolTip("Volume") - self._volume_slider.valueChanged.connect(self._audio_output.setVolume) + self._volume_slider.valueChanged.connect(self.setVolume) tool_bar.addWidget(self._volume_slider) icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout) @@ -183,6 +184,13 @@ class MainWindow(QMainWindow): print(error_string, file=sys.stderr) self.show_status_message(error_string) + @Slot() + def setVolume(self): + self.volumeValue = QAudio.convertVolume(self._volume_slider.value() / 100.0, + QAudio.VolumeScale.LogarithmicVolumeScale, + QAudio.VolumeScale.LinearVolumeScale) + self._audio_output.setVolume(self.volumeValue) + if __name__ == '__main__': app = QApplication(sys.argv) diff --git a/examples/multimedia/screencapture/main.py b/examples/multimedia/screencapture/main.py index f445bac0..df639216 100644 --- a/examples/multimedia/screencapture/main.py +++ b/examples/multimedia/screencapture/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the QtMultiMedia Screen Capture Example from Qt v6.x""" diff --git a/examples/multimedia/screencapture/screencapturepreview.py b/examples/multimedia/screencapture/screencapturepreview.py index c7e0c596..498803f6 100644 --- a/examples/multimedia/screencapture/screencapturepreview.py +++ b/examples/multimedia/screencapture/screencapturepreview.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import Enum, auto @@ -56,7 +57,7 @@ class ScreenCapturePreview(QWidget): update_action = QAction("Update windows List", self) update_action.triggered.connect(self._window_list_model.populate) self._window_list_view.addAction(update_action) - self._window_list_view.setContextMenuPolicy(Qt.ActionsContextMenu) + self._window_list_view.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) grid_layout = QGridLayout(self) grid_layout.addWidget(self._screen_label, 0, 0) @@ -81,9 +82,9 @@ class ScreenCapturePreview(QWidget): self._start_stop_button.clicked.connect(self.on_start_stop_button_clicked) self._screen_capture.errorOccurred.connect(self.on_screen_capture_error_occured, - Qt.QueuedConnection) + Qt.ConnectionType.QueuedConnection) self._window_capture.errorOccurred.connect(self.on_window_capture_error_occured, - Qt.QueuedConnection) + Qt.ConnectionType.QueuedConnection) self.update_active(SourceType.Screen, True) @Slot(QItemSelection) diff --git a/examples/multimedia/screencapture/screenlistmodel.py b/examples/multimedia/screencapture/screenlistmodel.py index 72bb306e..7e92c3a3 100644 --- a/examples/multimedia/screencapture/screenlistmodel.py +++ b/examples/multimedia/screencapture/screenlistmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtGui import QGuiApplication from PySide6.QtCore import QAbstractListModel, Qt, Slot @@ -20,7 +21,7 @@ class ScreenListModel(QAbstractListModel): def data(self, index, role): screen_list = QGuiApplication.screens() - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: screen = screen_list[index.row()] w = screen.size().width() h = screen.size().height() diff --git a/examples/multimedia/screencapture/windowlistmodel.py b/examples/multimedia/screencapture/windowlistmodel.py index 079040ec..4f622331 100644 --- a/examples/multimedia/screencapture/windowlistmodel.py +++ b/examples/multimedia/screencapture/windowlistmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QAbstractListModel, Qt, Slot from PySide6.QtMultimedia import QWindowCapture @@ -15,7 +16,7 @@ class WindowListModel(QAbstractListModel): return len(self._window_list) def data(self, index, role): - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: window = self._window_list[index.row()] return window.description() return None diff --git a/examples/network/blockingfortuneclient/blockingfortuneclient.py b/examples/network/blockingfortuneclient/blockingfortuneclient.py index d0dd7e0a..73476d47 100644 --- a/examples/network/blockingfortuneclient/blockingfortuneclient.py +++ b/examples/network/blockingfortuneclient/blockingfortuneclient.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the network/blockingfortunclient example from Qt v5.x, originating from PyQt""" @@ -95,10 +96,11 @@ class BlockingClient(QWidget): port_label = QLabel("S&erver port:") for ip_address in QNetworkInterface.allAddresses(): - if ip_address != QHostAddress.LocalHost and ip_address.toIPv4Address() != 0: + if (ip_address != QHostAddress.SpecialAddress.LocalHost + and ip_address.toIPv4Address() != 0): break else: - ip_address = QHostAddress(QHostAddress.LocalHost) + ip_address = QHostAddress(QHostAddress.SpecialAddress.LocalHost) ip_address = ip_address.toString() @@ -120,8 +122,8 @@ class BlockingClient(QWidget): quit_button = QPushButton("Quit") button_box = QDialogButtonBox() - button_box.addButton(self._get_fortune_button, QDialogButtonBox.ActionRole) - button_box.addButton(quit_button, QDialogButtonBox.RejectRole) + button_box.addButton(self._get_fortune_button, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(quit_button, QDialogButtonBox.ButtonRole.RejectRole) self._get_fortune_button.clicked.connect(self.request_new_fortune) quit_button.clicked.connect(self.close) @@ -157,11 +159,11 @@ class BlockingClient(QWidget): self._get_fortune_button.setEnabled(True) def display_error(self, socketError, message): - if socketError == QAbstractSocket.HostNotFoundError: + if socketError == QAbstractSocket.SocketError.HostNotFoundError: QMessageBox.information(self, "Blocking Fortune Client", "The host was not found. Please check the host and port " "settings.") - elif socketError == QAbstractSocket.ConnectionRefusedError: + elif socketError == QAbstractSocket.SocketError.ConnectionRefusedError: QMessageBox.information(self, "Blocking Fortune Client", "The connection was refused by the peer. Make sure the " "fortune server is running, and check that the host name " diff --git a/examples/network/blockingfortuneclient/doc/blockingfortuneclient.png b/examples/network/blockingfortuneclient/doc/blockingfortuneclient.png new file mode 100644 index 0000000000000000000000000000000000000000..ab918e791df0243b2e8f200f20e83e501cf86983 GIT binary patch literal 7013 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 k5+Ca=B7Q{w%pa2bDC!Ny&L8Wk literal 0 HcmV?d00001 diff --git a/examples/network/blockingfortuneclient/doc/blockingfortuneclient.rst b/examples/network/blockingfortuneclient/doc/blockingfortuneclient.rst new file mode 100644 index 00000000..661153db --- /dev/null +++ b/examples/network/blockingfortuneclient/doc/blockingfortuneclient.rst @@ -0,0 +1,11 @@ +Blocking Fortune Client Example +=============================== + +Demonstrates how to create a client for a network service. It corresponds to the +Qt example `Blocking Fortune Client Example +`_. + +.. image:: blockingfortuneclient.png + :align: center + :alt: blockingfortuneclient screenshot + :width: 400 diff --git a/examples/network/downloader/downloader.py b/examples/network/downloader/downloader.py index fba0cb98..97e0e3e2 100644 --- a/examples/network/downloader/downloader.py +++ b/examples/network/downloader/downloader.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import ( QWidget, @@ -35,8 +36,8 @@ class DownloaderWidget(QWidget): self.link_box.setPlaceholderText("Download Link ...") self._open_folder_action = self.dest_box.addAction( - qApp.style().standardIcon(QStyle.SP_DirOpenIcon), # noqa: F821 - QLineEdit.TrailingPosition + qApp.style().standardIcon(QStyle.StandardPixmap.SP_DirOpenIcon), # noqa: F821 + QLineEdit.ActionPosition.TrailingPosition ) self._open_folder_action.triggered.connect(self.on_open_folder) @@ -53,7 +54,7 @@ class DownloaderWidget(QWidget): #  Default destination dir self.dest_box.setText( QDir.fromNativeSeparators( - QStandardPaths.writableLocation(QStandardPaths.DownloadLocation) + QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation) ) ) @@ -104,7 +105,7 @@ class DownloaderWidget(QWidget): # Create the file in write mode to append bytes self.file = QSaveFile(dest_file) - if self.file.open(QIODevice.WriteOnly): + if self.file.open(QIODevice.OpenModeFlag.WriteOnly): # Start a GET HTTP request self.reply = self.manager.get(QNetworkRequest(url_file)) @@ -132,7 +133,7 @@ class DownloaderWidget(QWidget): def on_ready_read(self): """ Get available bytes and store them into the file""" if self.reply: - if self.reply.error() == QNetworkReply.NoError: + if self.reply.error() == QNetworkReply.NetworkError.NoError: self.file.write(self.reply.readAll()) @Slot() @@ -162,7 +163,7 @@ class DownloaderWidget(QWidget): def on_open_folder(self): dir_path = QFileDialog.getExistingDirectory( - self, "Open Directory", QDir.homePath(), QFileDialog.ShowDirsOnly + self, "Open Directory", QDir.homePath(), QFileDialog.Option.ShowDirsOnly ) if dir_path: diff --git a/examples/network/fortuneclient/doc/fortuneclient.png b/examples/network/fortuneclient/doc/fortuneclient.png new file mode 100644 index 0000000000000000000000000000000000000000..cae0b066dce86fd5bd35660eeb19cec902eece9a GIT binary patch literal 7814 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. + +.. image:: fortuneclient.png + :align: center + :alt: fortuneclient screenshot + :width: 400 diff --git a/examples/network/fortuneclient/fortuneclient.py b/examples/network/fortuneclient/fortuneclient.py index e88e5e35..54ce71f4 100644 --- a/examples/network/fortuneclient/fortuneclient.py +++ b/examples/network/fortuneclient/fortuneclient.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the network/fortuneclient example from Qt v5.x""" @@ -40,8 +41,8 @@ class Client(QDialog): quit_button = QPushButton("Quit") button_box = QDialogButtonBox() - button_box.addButton(self._get_fortune_button, QDialogButtonBox.ActionRole) - button_box.addButton(quit_button, QDialogButtonBox.RejectRole) + button_box.addButton(self._get_fortune_button, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(quit_button, QDialogButtonBox.ButtonRole.RejectRole) self._tcp_socket = QTcpSocket(self) @@ -72,7 +73,7 @@ class Client(QDialog): def read_fortune(self): instr = QDataStream(self._tcp_socket) - instr.setVersion(QDataStream.Qt_4_0) + instr.setVersion(QDataStream.Version.Qt_4_0) if self._block_size == 0: if self._tcp_socket.bytesAvailable() < 2: @@ -94,13 +95,13 @@ class Client(QDialog): self._get_fortune_button.setEnabled(True) def display_error(self, socketError): - if socketError == QAbstractSocket.RemoteHostClosedError: + if socketError == QAbstractSocket.SocketError.RemoteHostClosedError: pass - elif socketError == QAbstractSocket.HostNotFoundError: + elif socketError == QAbstractSocket.SocketError.HostNotFoundError: QMessageBox.information(self, "Fortune Client", "The host was not found. Please check the host name and " "port settings.") - elif socketError == QAbstractSocket.ConnectionRefusedError: + elif socketError == QAbstractSocket.SocketError.ConnectionRefusedError: QMessageBox.information(self, "Fortune Client", "The connection was refused by the peer. Make sure the " "fortune server is running, and check that the host name " diff --git a/examples/network/fortuneserver/doc/fortuneserver.png b/examples/network/fortuneserver/doc/fortuneserver.png new file mode 100644 index 0000000000000000000000000000000000000000..55ea8b1c29e4ed35592abd5734016623c1e892fd GIT binary patch literal 6167 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 E0X|0=uK)l5 literal 0 HcmV?d00001 diff --git a/examples/network/fortuneserver/doc/fortuneserver.rst b/examples/network/fortuneserver/doc/fortuneserver.rst new file mode 100644 index 00000000..fd21340c --- /dev/null +++ b/examples/network/fortuneserver/doc/fortuneserver.rst @@ -0,0 +1,11 @@ +Fortune Server Example +====================== + +Demonstrates how to create a server for a network service. It corresponds to the +Qt example `Fortune Server Example +`_. + +.. image:: fortuneserver.png + :align: center + :alt: fortuneserver screenshot + :width: 400 diff --git a/examples/network/fortuneserver/fortuneserver.py b/examples/network/fortuneserver/fortuneserver.py index a94a49f4..232485ee 100644 --- a/examples/network/fortuneserver/fortuneserver.py +++ b/examples/network/fortuneserver/fortuneserver.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the network/fortuneserver example from Qt v5.x""" @@ -19,7 +20,7 @@ class Server(QDialog): super().__init__(parent) status_label = QLabel() - status_label.setTextInteractionFlags(Qt.TextBrowserInteraction) + status_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) quit_button = QPushButton("Quit") quit_button.setAutoDefault(False) @@ -59,8 +60,8 @@ class Server(QDialog): def send_fortune(self): block = QByteArray() - out = QDataStream(block, QIODevice.WriteOnly) - out.setVersion(QDataStream.Qt_4_0) + out = QDataStream(block, QIODevice.OpenModeFlag.WriteOnly) + out.setVersion(QDataStream.Version.Qt_4_0) out.writeUInt16(0) fortune = self.fortunes[random.randint(0, len(self.fortunes) - 1)] diff --git a/examples/network/googlesuggest/googlesuggest.py b/examples/network/googlesuggest/googlesuggest.py index d3fc67c0..bb19647e 100644 --- a/examples/network/googlesuggest/googlesuggest.py +++ b/examples/network/googlesuggest/googlesuggest.py @@ -1,14 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from typing import List +from __future__ import annotations from PySide6.QtCore import (QByteArray, QEvent, QObject, QPoint, Qt, QTimer, QXmlStreamReader, Slot) from PySide6.QtGui import QPalette from PySide6.QtNetwork import (QNetworkAccessManager, QNetworkReply, QNetworkRequest) -from PySide6.QtWidgets import QFrame, QTreeWidget, QTreeWidgetItem +from PySide6.QtWidgets import QAbstractItemView, QFrame, QTreeWidget, QTreeWidgetItem class GSuggestCompletion(QObject): @@ -16,18 +15,18 @@ class GSuggestCompletion(QObject): super().__init__(parent) self.editor = parent self.popup = QTreeWidget() - self.popup.setWindowFlags(Qt.Popup) - self.popup.setFocusPolicy(Qt.NoFocus) + self.popup.setWindowFlags(Qt.WindowType.Popup) + self.popup.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.popup.setFocusProxy(parent) self.popup.setMouseTracking(True) self.popup.setColumnCount(1) self.popup.setUniformRowHeights(True) self.popup.setRootIsDecorated(False) - self.popup.setEditTriggers(QTreeWidget.NoEditTriggers) - self.popup.setSelectionBehavior(QTreeWidget.SelectRows) - self.popup.setFrameStyle(QFrame.Box | QFrame.Plain) - self.popup.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.popup.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + self.popup.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.popup.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Plain) + self.popup.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.popup.header().hide() self.popup.installEventFilter(self) @@ -46,28 +45,28 @@ class GSuggestCompletion(QObject): def eventFilter(self, obj: QObject, ev: QEvent): if obj is not self.popup: return False - if ev.type() == QEvent.MouseButtonPress: + if ev.type() == QEvent.Type.MouseButtonPress: self.popup.hide() self.editor.setFocus() return True - if ev.type() == QEvent.KeyPress: + if ev.type() == QEvent.Type.KeyPress: consumed = False key = ev.key() - if key in (Qt.Key_Enter, Qt.Key_Return): + if key in (Qt.Key.Key_Enter, Qt.Key.Key_Return): self.done_completion() consumed = True - elif key == Qt.Key_Escape: + elif key == Qt.Key.Key_Escape: self.editor.setFocus() self.popup.hide() consumed = True elif key in ( - Qt.Key_Up, - Qt.Key_Down, - Qt.Key_Home, - Qt.Key_End, - Qt.Key_PageUp, - Qt.Key_PageDown, + Qt.Key.Key_Up, + Qt.Key.Key_Down, + Qt.Key.Key_Home, + Qt.Key.Key_End, + Qt.Key.Key_PageUp, + Qt.Key.Key_PageDown, ): pass else: @@ -77,11 +76,11 @@ class GSuggestCompletion(QObject): return consumed return False - def show_completion(self, choices: List[str]): + def show_completion(self, choices: list[str]): if not choices: return pal = self.editor.palette() - color = pal.color(QPalette.Disabled, QPalette.WindowText) + color = pal.color(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText) self.popup.setUpdatesEnabled(False) self.popup.clear() @@ -120,14 +119,14 @@ class GSuggestCompletion(QObject): @Slot(QNetworkReply) def handle_network_data(self, network_reply: QNetworkReply): - if network_reply.error() == QNetworkReply.NoError: - choices: List[str] = [] + if network_reply.error() == QNetworkReply.NetworkError.NoError: + choices: list[str] = [] response: QByteArray = network_reply.readAll() xml = QXmlStreamReader(str(response)) while not xml.atEnd(): xml.readNext() - if xml.tokenType() == QXmlStreamReader.StartElement: + if xml.tokenType() == QXmlStreamReader.TokenType.StartElement: if xml.name() == "suggestion": s = xml.attributes().value("data") choices.append(s) diff --git a/examples/network/googlesuggest/main.py b/examples/network/googlesuggest/main.py index 1efda08e..50510421 100644 --- a/examples/network/googlesuggest/main.py +++ b/examples/network/googlesuggest/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/network/googlesuggest/searchbox.py b/examples/network/googlesuggest/searchbox.py index 9cbe20b2..a713a623 100644 --- a/examples/network/googlesuggest/searchbox.py +++ b/examples/network/googlesuggest/searchbox.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtGui import QDesktopServices diff --git a/examples/network/loopback/dialog.py b/examples/network/loopback/dialog.py index 673afce2..0790ff5c 100644 --- a/examples/network/loopback/dialog.py +++ b/examples/network/loopback/dialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QByteArray, Qt from PySide6.QtGui import QGuiApplication @@ -29,8 +30,8 @@ class Dialog(QDialog): self.quit_button = QPushButton("&Quit") self.button_box = QDialogButtonBox() - self.button_box.addButton(self.start_button, QDialogButtonBox.ActionRole) - self.button_box.addButton(self.quit_button, QDialogButtonBox.RejectRole) + self.button_box.addButton(self.start_button, QDialogButtonBox.ButtonRole.ActionRole) + self.button_box.addButton(self.quit_button, QDialogButtonBox.ButtonRole.RejectRole) self.start_button.clicked.connect(self.start) self.quit_button.clicked.connect(self.close) @@ -58,7 +59,7 @@ class Dialog(QDialog): self.start_button.setEnabled(False) - QGuiApplication.setOverrideCursor(Qt.WaitCursor) + QGuiApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) self.bytes_written = 0 self.bytes_received = 0 @@ -68,14 +69,15 @@ class Dialog(QDialog): self, "Loopback", f"Unable to start the test {self.tcp_server.errorString()}", - QMessageBox.Retry | QMessageBox.Cancel, + QMessageBox.StandardButton.Retry | QMessageBox.StandardButton.Cancel, ) - if ret == QMessageBox.Cancel: + if ret == QMessageBox.StandardButton.Cancel: return self.server_status_label.setText("Listening") self.client_status_label.setText("Connecting") - self.tcp_client.connectToHost(QHostAddress.LocalHost, self.tcp_server.serverPort()) + self.tcp_client.connectToHost(QHostAddress.SpecialAddress.LocalHost, + self.tcp_server.serverPort()) def accept_connection(self): @@ -131,7 +133,7 @@ class Dialog(QDialog): self.client_status_label.setText(f"Sent {self.bytes_written / (1024 ** 2)} MB") def display_error(self, socket_error: QAbstractSocket.SocketError): - if socket_error == QAbstractSocket.RemoteHostClosedError: + if socket_error == QAbstractSocket.SocketError.RemoteHostClosedError: return QMessageBox.information( diff --git a/examples/network/loopback/main.py b/examples/network/loopback/main.py index d684b6ba..046a8fba 100644 --- a/examples/network/loopback/main.py +++ b/examples/network/loopback/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/network/threadedfortuneserver/doc/threadedfortuneserver.png b/examples/network/threadedfortuneserver/doc/threadedfortuneserver.png new file mode 100644 index 0000000000000000000000000000000000000000..72315e0b79c474be90442a3aab3c40ca09cea58f GIT binary patch literal 5558 zcmZ`-2UHVVw~iNu9}J>Wq@yANQUgjrA&5v55JjmWR1u_@AT4x79j;1Iq^Jmp2zUua zIzj+tfPnNGYC@z35<(4CUbz2zZ{2tA|JM4>o;`c^DSOS#`S#h*Zd^Cu<`U+DKp@;k zhF8rYkORi7#{I9~SQgH{u1ePBfUmj16-Ysc=p5_jpqq}V4g^vZcX-F?(7q4G(8d=6 z5s=z%2aL?mFdz_qW237&x3R;tRL__E{i)v;lk>+O7=OEF0XdvErjQ@0#5iy^ad^yVYVTYP5^PEKc#B{s~toRGtN329N*kygG z6un^gvEp6%9e<_pkAW$A3#Wzb4O(2W*tK&~#e!OQEBnSQrbKIQ3WLaD#^q(3lDmJX z1r1^DeQ5ox;frpvfD#yL7<&t1<9soN_pHf{us}BSFL;47_L2}{@S1KKz_UY*uvn}b zM7_J{pG(y+xIKxMp1EX+zVMmkUsQcQY|C#G7q~0zb#*x)9uIOaqUx^W1*(lz-8vHL z0%$?p{4o4Y)^KCnBle@JC+%O1N%ghvh3UuGqc-N_BZiW!AaI8%soo; zu&IcrQ26e)?m-OuFeL_N$DS2nwDwgh>GsXl)W>y93Hz~a%RAQP*^7^Tb9~-p9I$Hc zLbChEtY)9oes`k)Zm*PhZ5e&*;IAHXgbh0s{gBehgTFZUL(bT!SyROgH>oXBPGxgPC@#L?VM{SbvC+Zo2WUT<&Pj83qrGa@m`N6KPS-9_9Ho6eI z1N6s@l?H&zV$}xv9fDAYt)9BPFp?_pqK&z#*<}tB-CPlxlp!M)Ii9VpZypXm<6C|M zg<37RcE2C_OW~RV3R<^Gahp*b`IVYzMqX|%Uo032o(I~=Azr=tDQbx2D|OwN?(L5D zk&$`^l-Tl8GlgQ=5`pdfIiO zJIo`4#;W+WE~UYR6@&LMGXgf{jojKVD1vF3lWPN)swsQrbB!Xw5_?fm#GQ#{gW4*O zej;(zzAk(dt|pIK9`)4+^%nKC$=21BU|-bm%qzXVzLTUM(C=*#Yw!q~`?`VJ<#?{M zE#z>RsJ5?rlohm2vS?(~30ow^BRdAEDG>=DE9Q2v=?U~+q!uEIm*+jL?Dl4h(ZBp! zDkK(dwo1hIX9(dK7;vv-%nnHR3BoLsEj99#vIID%Pv||~Yz(bI<(qL$5_70-+*kZi z+daL)h{4E3|C`Pcl*M1=2ZG(;J94dCk?%I)zdVO3#6va!Q=YNXP3cyL&?fiF*|C*Y z{tLq5C)7Vz5Ty9-HTI9^JfC^QSZc6{xojLDZz(NAn)JxmZmbhb`@y)J_VL>Ny3V|h z6+6g=dCCt=&D~%5FtX!uU|WSMF2u6~4`P_lnXseh_X)4(%N zM3I||#0sMOxrry9nQ*@+&UE8`es5DLKHjNIqkXFNt5*VYl<^mwEA3V5mRdNby=u_( z-o4Gv&R>KdV=V_n_ zH(I0S1=cLZdTYonSzo{7DsatoXZ4TQ(iRFRi&nM@5j6H>(B+1J3+8<>(&8o-OL$vj z0;5XXB%5xWUN>%VJj?s>tar82u66gKu{QZPW>(#9isaPu;B1{F?^ihn_t5Yeo9?#G zB>n-0cKCyJUtVpccxePuEuy>rnbr0C#T1S>2~>HunS8sZp`m7Q_?xI27~VnH&!o4EMj3nF|jHzz7>-1fwgKevFfbtfZ_2e9#KP=-HC#=g*kHfRe_R64=E59 zZrlLndRNzj?j)Tnc>O3z{|vt|*VLgqwcS%y!2=DRxfJgK;)hN_Q+Ee*DQoh{Rzzd( z`AG*En?TFh?3_YdGdaC$fV^qmL{xzKdAIriolAoD&-oO+vwx_RKU3td# zC4(u7%*_!-`gsjlU%3u~o_r9{^Jxm%g%o@q6% zP=4v;vSuCeG zR<6msQ~joS2!w^E&T&OL>_y8kAej!lKqSY6^4J!4Nnk55=#&WOFsqK!G`kz zM+~YSy6moz0MFJe?r@*X6i2MQami{{RD?5jMp)d_c;v^;$_%ergz6_p{$7FCUUEkF zDm#B!_w=1eT7zfiC?xTA)4qrWuXKNBtiwcy1?VOX7kXi7bCN$d;?~*!flZ$!X(k%{pp+ z@b+#B(LTKdUU<4{H^Y>eeFvqt(K{MU?T!oBc1`nFf>1pwEfwYT;Ce98HJRe8u3pLo z@3Yl(H{HJI$F04!IwtRxaIW;g`p_tDa7e5C-g-_EkE&pa=pGyBU?~oH}v>HBf{`k6*Ks!_!N5o8n75 z7i^#E+$JEbPh2nJdI$)M7kGsb%@hFS@dto5PUk+-1g5!mZpV$0x9+IUmzR{*&A`5k zeG+;QUJ0Y-l1!ekPEE)&4JAa^>QPcIQqny*~&l28i1;}pq z#B9Z6Ihoayk8n^Bhbsa(z5-x|BjC1R%tPiDxOY(6VOqs2)+gg-8Rd{< z%abZfsMRS+XXYRJ0wu2Y?`k0KMqqwe;F1Vst(KZ_U!5MtlP?|YJ?hE-N}+7@Lds7r zR=ko>4#@w-Hw@&jm^}jq2Bgq`C!#svS~01+QVZ*WI5x-1$I!Uj(Nw`T`PGxr6i(PY zGUd2KJ$EjvD*8R67pkPBY5ASI z!Zx!I7Golg(p13c{^Tcu={Xcm8fGSB zK!G=P-^>U0s}~B|OM|aTkqe=nRW8m|=#1Y=QI|BgVD+B@u&L0vLc2iKZDuu!&7v}&(VZj+_nzu*6MIsVL2&UBJbZvSc(7n880$5* z^)7YMCa~_w*jvTR9HoExpFbe}K_Gu4n0+d8`Hyt;cPiTV{3~e@C?ica5Atws5<(e8 zg*iCy7dL5u)dT6PlC5UpK=Q5UT|ETNa_!6zUKPrafnye!L}}Y!@H7(mvc3npv$}#fj4LteT)XwBA!rFb-M(qTK1WRcWnT z2MInZ5^tVJ3JynholZQ~UkavjzA7KL&gS6t782|v50aj$_7`?-B-k5&e>NTJ%KLHi zCBaBK0@O8VZe>{e6eZtufyVK_DJqM4{la8bP1Br9Qj0s$`}mbs{n)rTipBz9CjUTC z{F*wxIpdb@Cq61cCe#qzoOW=Jf6!=wIXz{X z_>jRqZH{ZMQ_?E7K>&pJ{=lP3%GNpBRHK1QZ`vEK!nD|>PYI2T;lRwZpv&QT$@th? zk(7>7LU`q^?v*}2z-O{jr+1L#Ds!H0XC$NE&DPyNCWg?R>h)@d10q}9TAp)hS$&ye z;w`${PHWMWY4eBvEC@D@$~id9yoSXu$wnM4YwblOQ-H|+=+f{Gwr9kd;p zEE7z##^km#{R{$(MQ)mappY=UDq##W;bcbr&efTI%OUuR^er!?pZXypX&!JOrlK^F zAVuoEJDX1($kB+qb!F=NtMdGm)~+m63Z%q1$92qOlrbD?-7uB-QnGuuL*2>E8V9j}c{RYSooEmYCqtp#9;D=G^&Xji7Z z;I{1ZT=Y%DKqYCRY;0Auw!_X>VJtvn)UtcEsy_Wju-GAjOc=hTY7SuIOFI&N@ECaD zPj24lXIA@P=Kg;O`+<|Y`|P(zY}?Gmy$g4Kc4g?sNAaD3Dt#vXTrYAVDB9z(oIR48 z0XrK$ExN2k_w?zdnanuEl|Er1QPG6pqm7*4;i%gxN_LeGKVCQ;B;)k{=M#$FdDf{@ z=RrJ66^x}k+!un^16cX2i~64eU}I_j7CDYgT1K2uSP|2a+Ssf3c;7c>+;Q=Q542~;TW$NN8V|EUTd**B4f7RYt!g~I)@lcxX|>@f|@__y)#lg|g5mnH~2 zVi&La)HCWcY8B-VB^ah@1h>k@7an%OW=KVB(<(UF+3llN7luk^@+(MJ(0@`aYvCM! zrPgMU!76LDL>R=1o-%+8v%_rvEI{3Dds(-&z1_n1uv{lvpspVh z_tDHoPgeQL^#|XkCkY>PhwJjlN&Oc@=fHbA8%wOSZjwX@lP=`|8HV`SuzC|>M69Y7 zidze6w>&nr+&V;y**|a}O>T5wvRzOVQT&J#Uz=)8etij|5M!Me+^tl%vwRbL`ixgP z7w2Ypq>CDS*tyg^qz~gn?fb*y-4pEaBt(I@Zkgz-V|N5Eu4Cz@hZt9JHGPJNR+V^@>Js`_. + +.. image:: threadedfortuneserver.png + :align: center + :alt: threadedfortuneserver screenshot + :width: 400 diff --git a/examples/network/threadedfortuneserver/threadedfortuneserver.py b/examples/network/threadedfortuneserver/threadedfortuneserver.py index c75e2bc5..01ae0621 100644 --- a/examples/network/threadedfortuneserver/threadedfortuneserver.py +++ b/examples/network/threadedfortuneserver/threadedfortuneserver.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the network/threadedfortuneserver example from Qt v5.x, originating from PyQt""" @@ -30,8 +31,8 @@ class FortuneThread(QThread): return block = QByteArray() - outstr = QDataStream(block, QIODevice.WriteOnly) - outstr.setVersion(QDataStream.Qt_4_0) + outstr = QDataStream(block, QIODevice.OpenModeFlag.WriteOnly) + outstr.setVersion(QDataStream.Version.Qt_4_0) outstr.writeUInt16(0) outstr.writeQString(self.text) outstr.device().seek(0) @@ -67,7 +68,7 @@ class Dialog(QDialog): self.server = FortuneServer() status_label = QLabel() - status_label.setTextInteractionFlags(Qt.TextBrowserInteraction) + status_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) status_label.setWordWrap(True) quit_button = QPushButton("Quit") quit_button.setAutoDefault(False) @@ -80,10 +81,11 @@ class Dialog(QDialog): return for ip_address in QNetworkInterface.allAddresses(): - if ip_address != QHostAddress.LocalHost and ip_address.toIPv4Address() != 0: + if (ip_address != QHostAddress.SpecialAddress.LocalHost + and ip_address.toIPv4Address() != 0): break else: - ip_address = QHostAddress(QHostAddress.LocalHost) + ip_address = QHostAddress(QHostAddress.SpecialAddress.LocalHost) ip_address = ip_address.toString() port = self.server.serverPort() diff --git a/examples/networkauth/redditclient/main.py b/examples/networkauth/redditclient/main.py index e88e32cc..c3e3694d 100644 --- a/examples/networkauth/redditclient/main.py +++ b/examples/networkauth/redditclient/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the networkauth redditclient example from Qt v6.x""" diff --git a/examples/networkauth/redditclient/redditmodel.py b/examples/networkauth/redditclient/redditmodel.py index fd6f9bc9..f2503f19 100644 --- a/examples/networkauth/redditclient/redditmodel.py +++ b/examples/networkauth/redditclient/redditmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import functools from PySide6.QtCore import (QAbstractTableModel, QJsonDocument, QModelIndex, @@ -30,7 +31,7 @@ class RedditModel(QAbstractTableModel): def data(self, index, role): if not index.isValid(): return None - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: children_object = self._threads[index.row()] data_object = children_object["data"] return data_object["title"] diff --git a/examples/networkauth/redditclient/redditwrapper.py b/examples/networkauth/redditclient/redditwrapper.py index ed4c3d2c..80dfb944 100644 --- a/examples/networkauth/redditclient/redditwrapper.py +++ b/examples/networkauth/redditclient/redditwrapper.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import functools diff --git a/examples/opengl/contextinfo/contextinfo.py b/examples/opengl/contextinfo/contextinfo.py index 311d5b76..9770be63 100644 --- a/examples/opengl/contextinfo/contextinfo.py +++ b/examples/opengl/contextinfo/contextinfo.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the opengl/contextinfo example from Qt v5.x""" @@ -73,7 +74,7 @@ colors = numpy.array([1, 0, 0, 0, 1, 0, 0, 0, 1], dtype=numpy.float32) def print_surface_format(surface_format): - if surface_format.profile() == QSurfaceFormat.CoreProfile: + if surface_format.profile() == QSurfaceFormat.OpenGLContextProfile.CoreProfile: profile_name = 'core' else: profile_name = 'compatibility' @@ -85,7 +86,7 @@ def print_surface_format(surface_format): class RenderWindow(QWindow): def __init__(self, fmt): super().__init__() - self.setSurfaceType(QWindow.OpenGLSurface) + self.setSurfaceType(QWindow.SurfaceType.OpenGLSurface) self.setFormat(fmt) self.context = QOpenGLContext(self) self.context.setFormat(self.requestedFormat()) @@ -101,22 +102,22 @@ class RenderWindow(QWindow): self.vbo = QOpenGLBuffer() fmt = self.context.format() - use_new_style_shader = fmt.profile() == QSurfaceFormat.CoreProfile + use_new_style_shader = fmt.profile() == QSurfaceFormat.OpenGLContextProfile.CoreProfile # Try to handle 3.0 & 3.1 that do not have the core/compatibility profile # concept 3.2+ has. This may still fail since version 150 (3.2) is # specified in the sources but it's worth a try. - if (fmt.renderableType() == QSurfaceFormat.OpenGL and fmt.majorVersion() == 3 + if (fmt.renderableType() == QSurfaceFormat.RenderableType.OpenGL and fmt.majorVersion() == 3 and fmt.minorVersion() <= 1): - use_new_style_shader = not fmt.testOption(QSurfaceFormat.DeprecatedFunctions) + use_new_style_shader = not fmt.testOption(QSurfaceFormat.FormatOption.DeprecatedFunctions) # noqa: E501 vertex_shader = vertex_shader_source if use_new_style_shader else vertex_shader_source_110 fragment_shader = (fragment_shader_source if use_new_style_shader else fragment_shader_source_110) - if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertex_shader): + if not self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, vertex_shader): # noqa: E501 log = self.program.log() raise Exception("Vertex shader could not be added: {log} ({vertexShader})") - if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragment_shader): + if not self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, fragment_shader): # noqa: E501 log = self.program.log() raise Exception(f"Fragment shader could not be added: {log} ({fragment_shader})") if not self.program.link(): @@ -253,11 +254,11 @@ if __name__ == '__main__': help='Use Desktop OpenGL') options = parser.parse_args() if options.gles: - QCoreApplication.setAttribute(Qt.AA_UseOpenGLES) + QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseOpenGLES) elif options.software: - QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) + QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL) elif options.desktop: - QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL) + QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) main_window = MainWindow() diff --git a/examples/opengl/contextinfo/requirements.txt b/examples/opengl/contextinfo/requirements.txt new file mode 100644 index 00000000..37272131 --- /dev/null +++ b/examples/opengl/contextinfo/requirements.txt @@ -0,0 +1 @@ +PyOpenGL diff --git a/examples/opengl/hellogl2/glwidget.py b/examples/opengl/hellogl2/glwidget.py index bbf200a6..9660f873 100644 --- a/examples/opengl/hellogl2/glwidget.py +++ b/examples/opengl/hellogl2/glwidget.py @@ -1,6 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # Copyright (C) 2013 Riverbank Computing Limited. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import ctypes from PySide6.QtCore import Signal, Slot, Qt, QSize, QPointF @@ -81,7 +82,7 @@ class GLWidget(QOpenGLWidget, QOpenGLFunctions): QOpenGLWidget.__init__(self, parent) QOpenGLFunctions.__init__(self) - self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.CoreProfile + self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.OpenGLContextProfile.CoreProfile # noqa: E501 self._x_rot = 0 self._y_rot = 0 @@ -179,9 +180,9 @@ class GLWidget(QOpenGLWidget, QOpenGLFunctions): self._vertex_shader = VERTEX_SHADER_SOURCE self._fragment_shader = FRAGMENT_SHADER_SOURCE - self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, + self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, self._vertex_shader) - self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, + self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, self._fragment_shader) self.program.bindAttributeLocation("vertex", 0) self.program.bindAttributeLocation("normal", 1) @@ -262,7 +263,7 @@ class GLWidget(QOpenGLWidget, QOpenGLFunctions): dx = pos.x() - self._last_pos.x() dy = pos.y() - self._last_pos.y() - if event.buttons() & Qt.LeftButton: + if event.buttons() & Qt.MouseButton.LeftButton: self.set_xrotation(self._x_rot + 8 * dy) self.set_yrotation(self._y_rot + 8 * dx) elif event.buttons() & Qt.RightButton: diff --git a/examples/opengl/hellogl2/logo.py b/examples/opengl/hellogl2/logo.py index c236a1ec..3be1b5bd 100644 --- a/examples/opengl/hellogl2/logo.py +++ b/examples/opengl/hellogl2/logo.py @@ -1,6 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # Copyright (C) 2013 Riverbank Computing Limited. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math diff --git a/examples/opengl/hellogl2/main.py b/examples/opengl/hellogl2/main.py index c7eb78a8..6bce36b0 100644 --- a/examples/opengl/hellogl2/main.py +++ b/examples/opengl/hellogl2/main.py @@ -1,6 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # Copyright (C) 2013 Riverbank Computing Limited. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the opengl/hellogl2 example from Qt v6.x""" @@ -16,9 +17,9 @@ try: from glwidget import GLWidget except ImportError: app = QApplication(sys.argv) - message_box = QMessageBox(QMessageBox.Critical, "OpenGL hellogl", + message_box = QMessageBox(QMessageBox.Icon.Critical, "OpenGL hellogl", "PyOpenGL must be installed to run this example.", - QMessageBox.Close) + QMessageBox.StandardButton.Close) message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") message_box.exec() sys.exit(1) @@ -42,15 +43,15 @@ if __name__ == '__main__': fmt.setSamples(4) if options.coreprofile: fmt.setVersion(3, 2) - fmt.setProfile(QSurfaceFormat.CoreProfile) + fmt.setProfile(QSurfaceFormat.OpenGLContextProfile.CoreProfile) QSurfaceFormat.setDefaultFormat(fmt) GLWidget.set_transparent(options.transparent) main_window = MainWindow() if options.transparent: - main_window.setAttribute(Qt.WA_TranslucentBackground) - main_window.setAttribute(Qt.WA_NoSystemBackground, False) + main_window.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) + main_window.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground, False) main_window.show() diff --git a/examples/opengl/hellogl2/mainwindow.py b/examples/opengl/hellogl2/mainwindow.py index 69b9b66f..ce00b66f 100644 --- a/examples/opengl/hellogl2/mainwindow.py +++ b/examples/opengl/hellogl2/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot, Qt from PySide6.QtGui import QKeySequence @@ -13,9 +14,9 @@ class MainWindow(QMainWindow): def __init__(self): super().__init__() menuWindow = self.menuBar().addMenu("Window") - menuWindow.addAction("Add new", QKeySequence(Qt.CTRL | Qt.Key_N), + menuWindow.addAction("Add new", QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_N), self.onAddNew) - menuWindow.addAction("Quit", QKeySequence(Qt.CTRL | Qt.Key_Q), + menuWindow.addAction("Quit", QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Q), qApp.closeAllWindows) # noqa: F821 self.onAddNew() diff --git a/examples/opengl/hellogl2/requirements.txt b/examples/opengl/hellogl2/requirements.txt new file mode 100644 index 00000000..37272131 --- /dev/null +++ b/examples/opengl/hellogl2/requirements.txt @@ -0,0 +1 @@ +PyOpenGL diff --git a/examples/opengl/hellogl2/window.py b/examples/opengl/hellogl2/window.py index ad61d2f9..5d2c22ff 100644 --- a/examples/opengl/hellogl2/window.py +++ b/examples/opengl/hellogl2/window.py @@ -1,6 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # Copyright (C) 2013 Riverbank Computing Limited. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot, Qt from PySide6.QtWidgets import (QHBoxLayout, QMainWindow, @@ -58,13 +59,13 @@ class Window(QWidget): self.setWindowTitle(self.tr("Hello GL")) def create_slider(self): - slider = QSlider(Qt.Vertical) + slider = QSlider(Qt.Orientation.Vertical) slider.setRange(0, 360 * 16) slider.setSingleStep(16) slider.setPageStep(15 * 16) slider.setTickInterval(15 * 16) - slider.setTickPosition(QSlider.TicksRight) + slider.setTickPosition(QSlider.TickPosition.TicksRight) return slider def closeEvent(self, event): @@ -72,7 +73,7 @@ class Window(QWidget): event.accept() def keyPressEvent(self, event): - if self.isWindow() and event.key() == Qt.Key_Escape: + if self.isWindow() and event.key() == Qt.Key.Key_Escape: self.close() else: super().keyPressEvent(event) @@ -95,13 +96,13 @@ class Window(QWidget): "Main window already occupied") return - self.setAttribute(Qt.WA_DeleteOnClose, False) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False) self._dock_btn.setText("Undock") mainWindow.setCentralWidget(self) def undock(self): self.setParent(None) - self.setAttribute(Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) geometry = self.screen().availableGeometry() x = geometry.x() + (geometry.width() - self.width()) / 2 y = geometry.y() + (geometry.height() - self.height()) / 2 diff --git a/examples/opengl/textures/requirements.txt b/examples/opengl/textures/requirements.txt new file mode 100644 index 00000000..37272131 --- /dev/null +++ b/examples/opengl/textures/requirements.txt @@ -0,0 +1 @@ +PyOpenGL diff --git a/examples/opengl/textures/textures.py b/examples/opengl/textures/textures.py index 87c1164b..5df84ad4 100644 --- a/examples/opengl/textures/textures.py +++ b/examples/opengl/textures/textures.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the opengl/textures example from Qt v6.x showing the use of legacy OpenGL functions with QOpenGLVersionFunctionsFactory.""" @@ -45,7 +46,7 @@ class GLWidget(QOpenGLWidget): def __init__(self, parent): super().__init__(parent) - self.clearColor = Qt.black + self.clearColor = Qt.GlobalColor.black self.xRot = 0 self.yRot = 0 self.zRot = 0 @@ -78,7 +79,7 @@ class GLWidget(QOpenGLWidget): def initializeGL(self): profile = QOpenGLVersionProfile() profile.setVersion(3, 2) - profile.setProfile(QSurfaceFormat.CompatibilityProfile) + profile.setProfile(QSurfaceFormat.OpenGLContextProfile.CompatibilityProfile) self.funcs = QOpenGLVersionFunctionsFactory.get(profile) self.funcs.initializeOpenGLFunctions() @@ -124,7 +125,7 @@ class GLWidget(QOpenGLWidget): dx = pos.x() - self.lastPos.x() dy = pos.y() - self.lastPos.y() - if event.buttons() & Qt.LeftButton: + if event.buttons() & Qt.MouseButton.LeftButton: self.rotateBy(8 * dy, 8 * dx, 0) elif event.buttons() & Qt.RightButton: self.rotateBy(8 * dy, 0, 8 * dx) diff --git a/examples/opengl/threadedqopenglwidget/glwidget.py b/examples/opengl/threadedqopenglwidget/glwidget.py index edb88e77..70efc281 100644 --- a/examples/opengl/threadedqopenglwidget/glwidget.py +++ b/examples/opengl/threadedqopenglwidget/glwidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtOpenGLWidgets import QOpenGLWidget diff --git a/examples/opengl/threadedqopenglwidget/main.py b/examples/opengl/threadedqopenglwidget/main.py index 2a379da2..302ac438 100644 --- a/examples/opengl/threadedqopenglwidget/main.py +++ b/examples/opengl/threadedqopenglwidget/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Threaded QOpenGLWidget Example from Qt v6.x""" @@ -16,9 +17,9 @@ try: except ImportError: app = QApplication(sys.argv) message = "PyOpenGL must be installed to run this example." - message_box = QMessageBox(QMessageBox.Critical, + message_box = QMessageBox(QMessageBox.Icon.Critical, "Threaded QOpenGLWidget Example", - message, QMessageBox.Close) + message, QMessageBox.StandardButton.Close) detail = "Run:\npip install PyOpenGL PyOpenGL_accelerate" message_box.setDetailedText(detail) message_box.exec() @@ -69,9 +70,9 @@ if __name__ == "__main__": top_gl_widget.setToolTip(tool_tip) print(tool_tip) - close_shortcut = QShortcut(Qt.CTRL | Qt.Key_Q, top_gl_widget) + close_shortcut = QShortcut(Qt.Modifier.CTRL | Qt.Key.Key_Q, top_gl_widget) close_shortcut.activated.connect(QApplication.closeAllWindows) - close_shortcut.setContext(Qt.ApplicationShortcut) + close_shortcut.setContext(Qt.ShortcutContext.ApplicationShortcut) mw1 = None mw2 = None diff --git a/examples/opengl/threadedqopenglwidget/mainwindow.py b/examples/opengl/threadedqopenglwidget/mainwindow.py index 4999b799..cf6dbeec 100644 --- a/examples/opengl/threadedqopenglwidget/mainwindow.py +++ b/examples/opengl/threadedqopenglwidget/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QWidget, QHBoxLayout diff --git a/examples/opengl/threadedqopenglwidget/renderer.py b/examples/opengl/threadedqopenglwidget/renderer.py index 81ec63cb..b20cbe1e 100644 --- a/examples/opengl/threadedqopenglwidget/renderer.py +++ b/examples/opengl/threadedqopenglwidget/renderer.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import ctypes import math @@ -145,10 +146,10 @@ class Renderer(QObject, QOpenGLFunctions): QMetaObject.invokeMethod(self._glwidget, "update") def _init_gl(self): - vshader = QOpenGLShader(QOpenGLShader.Vertex, self) + vshader = QOpenGLShader(QOpenGLShader.ShaderTypeBit.Vertex, self) vshader.compileSourceCode(VERTEX_SHADER) - fshader = QOpenGLShader(QOpenGLShader.Fragment, self) + fshader = QOpenGLShader(QOpenGLShader.ShaderTypeBit.Fragment, self) fshader.compileSourceCode(FRAGMENT_SHADER) self._program.addShader(vshader) diff --git a/examples/opengl/threadedqopenglwidget/requirements.txt b/examples/opengl/threadedqopenglwidget/requirements.txt new file mode 100644 index 00000000..37272131 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/requirements.txt @@ -0,0 +1 @@ +PyOpenGL diff --git a/examples/pdf/quickpdfviewer/main.py b/examples/pdf/quickpdfviewer/main.py index ee7b882c..388c590c 100644 --- a/examples/pdf/quickpdfviewer/main.py +++ b/examples/pdf/quickpdfviewer/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os import sys diff --git a/examples/pdfwidgets/pdfviewer/main.py b/examples/pdfwidgets/pdfviewer/main.py index c4765fdd..55ac454e 100644 --- a/examples/pdfwidgets/pdfviewer/main.py +++ b/examples/pdfwidgets/pdfviewer/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from argparse import ArgumentParser, RawTextHelpFormatter diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.py b/examples/pdfwidgets/pdfviewer/mainwindow.py index d37475ac..154c8770 100644 --- a/examples/pdfwidgets/pdfviewer/mainwindow.py +++ b/examples/pdfwidgets/pdfviewer/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math import sys diff --git a/examples/pdfwidgets/pdfviewer/mainwindow.ui b/examples/pdfwidgets/pdfviewer/mainwindow.ui index 7cfcde4b..3bf46887 100644 --- a/examples/pdfwidgets/pdfviewer/mainwindow.ui +++ b/examples/pdfwidgets/pdfviewer/mainwindow.ui @@ -54,7 +54,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -64,7 +64,7 @@ - QTabWidget::West + QTabWidget::TabPosition::West 0 @@ -134,7 +134,7 @@ 0 0 700 - 23 + 26 diff --git a/examples/pdfwidgets/pdfviewer/ui_mainwindow.py b/examples/pdfwidgets/pdfviewer/ui_mainwindow.py index 9ed525db..c31da6ff 100644 --- a/examples/pdfwidgets/pdfviewer/ui_mainwindow.py +++ b/examples/pdfwidgets/pdfviewer/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -36,7 +36,7 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon = QIcon.fromTheme(iconThemeName) else: - icon.addFile(u":/icons/images/document-open.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(u":/icons/images/document-open.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionOpen.setIcon(icon) self.actionQuit = QAction(MainWindow) @@ -56,7 +56,7 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon3 = QIcon.fromTheme(iconThemeName) else: - icon3.addFile(u":/icons/images/zoom-in.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon3.addFile(u":/icons/images/zoom-in.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionZoom_In.setIcon(icon3) self.actionZoom_Out = QAction(MainWindow) @@ -66,7 +66,7 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon4 = QIcon.fromTheme(iconThemeName) else: - icon4.addFile(u":/icons/images/zoom-out.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon4.addFile(u":/icons/images/zoom-out.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionZoom_Out.setIcon(icon4) self.actionPrevious_Page = QAction(MainWindow) @@ -76,7 +76,7 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon5 = QIcon.fromTheme(iconThemeName) else: - icon5.addFile(u":/icons/images/go-previous-view-page.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon5.addFile(u":/icons/images/go-previous-view-page.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionPrevious_Page.setIcon(icon5) self.actionNext_Page = QAction(MainWindow) @@ -86,7 +86,7 @@ class Ui_MainWindow(object): if QIcon.hasThemeIcon(iconThemeName): icon6 = QIcon.fromTheme(iconThemeName) else: - icon6.addFile(u":/icons/images/go-next-view-page.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon6.addFile(u":/icons/images/go-next-view-page.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionNext_Page.setIcon(icon6) self.actionContinuous = QAction(MainWindow) @@ -96,13 +96,13 @@ class Ui_MainWindow(object): self.actionBack.setObjectName(u"actionBack") self.actionBack.setEnabled(False) icon7 = QIcon() - icon7.addFile(u":/icons/images/go-previous-view.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon7.addFile(u":/icons/images/go-previous-view.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionBack.setIcon(icon7) self.actionForward = QAction(MainWindow) self.actionForward.setObjectName(u"actionForward") self.actionForward.setEnabled(False) icon8 = QIcon() - icon8.addFile(u":/icons/images/go-next-view.svgz", QSize(), QIcon.Normal, QIcon.Off) + icon8.addFile(u":/icons/images/go-next-view.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionForward.setIcon(icon8) self.centralWidget = QWidget(MainWindow) self.centralWidget.setObjectName(u"centralWidget") @@ -120,7 +120,7 @@ class Ui_MainWindow(object): self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.splitter = QSplitter(self.widget) self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Horizontal) + self.splitter.setOrientation(Qt.Orientation.Horizontal) self.tabWidget = QTabWidget(self.splitter) self.tabWidget.setObjectName(u"tabWidget") sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) @@ -128,7 +128,7 @@ class Ui_MainWindow(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth()) self.tabWidget.setSizePolicy(sizePolicy) - self.tabWidget.setTabPosition(QTabWidget.West) + self.tabWidget.setTabPosition(QTabWidget.TabPosition.West) self.tabWidget.setDocumentMode(False) self.bookmarkTab = QWidget() self.bookmarkTab.setObjectName(u"bookmarkTab") @@ -167,7 +167,7 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralWidget) self.menuBar = QMenuBar(MainWindow) self.menuBar.setObjectName(u"menuBar") - self.menuBar.setGeometry(QRect(0, 0, 700, 23)) + self.menuBar.setGeometry(QRect(0, 0, 700, 26)) self.menuFile = QMenu(self.menuBar) self.menuFile.setObjectName(u"menuFile") self.menuHelp = QMenu(self.menuBar) diff --git a/examples/pdfwidgets/pdfviewer/zoomselector.py b/examples/pdfwidgets/pdfviewer/zoomselector.py index 6f9f972e..3fa64cd3 100644 --- a/examples/pdfwidgets/pdfviewer/zoomselector.py +++ b/examples/pdfwidgets/pdfviewer/zoomselector.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtPdfWidgets import QPdfView from PySide6.QtWidgets import QComboBox diff --git a/examples/qml/editingmodel/main.py b/examples/qml/editingmodel/main.py index 5240a9de..cccbd2b0 100644 --- a/examples/qml/editingmodel/main.py +++ b/examples/qml/editingmodel/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from pathlib import Path @@ -15,7 +16,9 @@ if __name__ == "__main__": engine = QQmlApplicationEngine() qml_file = Path(__file__).parent / "main.qml" engine.load(QUrl.fromLocalFile(qml_file)) - if not engine.rootObjects(): sys.exit(-1) - sys.exit(app.exec()) + + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/qml/editingmodel/model.py b/examples/qml/editingmodel/model.py index 02a1e571..c89aea7a 100644 --- a/examples/qml/editingmodel/model.py +++ b/examples/qml/editingmodel/model.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, Qt, Slot @@ -15,7 +16,7 @@ QML_IMPORT_MAJOR_VERSION = 1 @QmlElement class BaseModel(QAbstractListModel): - RatioRole = Qt.UserRole + 1 + RatioRole = Qt.ItemDataRole.UserRole + 1 def __init__(self, parent=None): super().__init__(parent=parent) @@ -27,7 +28,7 @@ class BaseModel(QAbstractListModel): def roleNames(self): default = super().roleNames() default[self.RatioRole] = QByteArray(b"ratio") - default[Qt.BackgroundRole] = QByteArray(b"backgroundColor") + default[Qt.ItemDataRole.BackgroundRole] = QByteArray(b"backgroundColor") return default def data(self, index, role: int): @@ -35,9 +36,9 @@ class BaseModel(QAbstractListModel): ret = None elif not index.isValid(): ret = None - elif role == Qt.DisplayRole: + elif role == Qt.ItemDataRole.DisplayRole: ret = self.db[index.row()]["text"] - elif role == Qt.BackgroundRole: + elif role == Qt.ItemDataRole.BackgroundRole: ret = self.db[index.row()]["bgColor"] elif role == self.RatioRole: ret = self.db[index.row()]["ratio"] @@ -48,7 +49,7 @@ class BaseModel(QAbstractListModel): def setData(self, index, value, role): if not index.isValid(): return False - if role == Qt.EditRole: + if role == Qt.ItemDataRole.EditRole: self.db[index.row()]["text"] = value return True diff --git a/examples/qml/signals/pytoqml1/main.py b/examples/qml/signals/pytoqml1/main.py deleted file mode 100644 index 5bc27a52..00000000 --- a/examples/qml/signals/pytoqml1/main.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import os -from pathlib import Path -import sys -from PySide6.QtCore import QTimer, QUrl -from PySide6.QtGui import QGuiApplication -from PySide6.QtQuick import QQuickView - -if __name__ == '__main__': - app = QGuiApplication(sys.argv) - - timer = QTimer() - timer.start(2000) - - view = QQuickView() - qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') - view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: - sys.exit(-1) - root = view.rootObject() - - timer.timeout.connect(root.updateRotater) - - view.show() - res = app.exec() - # Deleting the view before it goes out of scope is required to make sure all child QML instances - # are destroyed in the correct order. - del view - sys.exit(res) diff --git a/examples/qml/signals/pytoqml1/pytoqml1.pyproject b/examples/qml/signals/pytoqml1/pytoqml1.pyproject deleted file mode 100644 index e6f087cc..00000000 --- a/examples/qml/signals/pytoqml1/pytoqml1.pyproject +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files": ["main.py", "view.qml"] -} diff --git a/examples/qml/signals/pytoqml1/view.qml b/examples/qml/signals/pytoqml1/view.qml deleted file mode 100644 index af2d966b..00000000 --- a/examples/qml/signals/pytoqml1/view.qml +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -Rectangle { - id: page - - function updateRotater() { - rotater.angle = rotater.angle + 45 - } - - width: 500; height: 200 - color: "lightgray" - - Rectangle { - id: rotater - property real angle : 0 - x: 240 - width: 100; height: 10 - color: "black" - y: 95 - - transform: Rotation { - origin.x: 10; origin.y: 5 - angle: rotater.angle - Behavior on angle { - SpringAnimation { - spring: 1.4 - damping: .05 - } - } - } - } - -} diff --git a/examples/qml/signals/pytoqml2/main.py b/examples/qml/signals/pytoqml2/main.py deleted file mode 100644 index 3a3650ab..00000000 --- a/examples/qml/signals/pytoqml2/main.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import os -from pathlib import Path -import sys -from PySide6.QtCore import QObject, QTimer, QUrl, Signal, Slot -from PySide6.QtGui import QGuiApplication -from PySide6.QtQuick import QQuickView -from PySide6.QtQml import QmlElement - - -# To be used on the @QmlElement decorator -# (QML_IMPORT_MINOR_VERSION is optional) -QML_IMPORT_NAME = "examples.signals.pytoqml2" -QML_IMPORT_MAJOR_VERSION = 1 - - -@QmlElement -class RotateValue(QObject): - valueChanged = Signal(int, arguments=['val']) - - def __init__(self): - super().__init__() - self.r = 0 - - @Slot() - def increment(self): - self.r = self.r + 10 - self.valueChanged.emit(self.r) - - -if __name__ == '__main__': - app = QGuiApplication(sys.argv) - view = QQuickView() - - rotatevalue = RotateValue() - timer = QTimer() - timer.start(2000) - view.setInitialProperties({"rotatevalue": rotatevalue}) - - qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') - view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: - sys.exit(-1) - - timer.timeout.connect(rotatevalue.increment) - - view.show() - res = app.exec() - # Deleting the view before it goes out of scope is required to make - # sure all child QML instances are destroyed in the correct order. - del view - sys.exit(res) diff --git a/examples/qml/signals/pytoqml2/pytoqml2.pyproject b/examples/qml/signals/pytoqml2/pytoqml2.pyproject deleted file mode 100644 index e6f087cc..00000000 --- a/examples/qml/signals/pytoqml2/pytoqml2.pyproject +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files": ["main.py", "view.qml"] -} diff --git a/examples/qml/signals/pytoqml2/view.qml b/examples/qml/signals/pytoqml2/view.qml deleted file mode 100644 index 2e9128f5..00000000 --- a/examples/qml/signals/pytoqml2/view.qml +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQml - -import examples.signals.pytoqml2 1.0 - -Rectangle { - id: page - - width: 500; height: 200 - color: "lightgray" - required property RotateValue rotatevalue - - Text { - id: helloText - text: "Hello world!" - anchors.horizontalCenter: page.horizontalCenter - y: 30 - font.pointSize: 24; font.bold: true - } - - Connections { - target: rotatevalue - function onValueChanged(val) { - helloText.rotation = val - } - } -} diff --git a/examples/qml/signals/qmltopy1/doc/qmltopy1.rst b/examples/qml/signals/qmltopy1/doc/qmltopy1.rst new file mode 100644 index 00000000..f5d8ece4 --- /dev/null +++ b/examples/qml/signals/qmltopy1/doc/qmltopy1.rst @@ -0,0 +1,13 @@ +Calling Python Methods from QML +=============================== + +Introduce how to invoke Python methods (slots) from QML. + +**Key Features:** + +- **Python Class with Slots:** Defines a Console class in Python with multiple slots using the + :deco:`~PySide6.QtCore.Slot` decorator. +- **Exposing Python Class to QML:** Uses :deco:`~PySide6.QtQml.QmlElement` to make the Console class + available in QML. +- **Calling Slots from QML:** In QML, instantiates Console and calls its methods in response to user + interactions. diff --git a/examples/qml/signals/qmltopy1/main.py b/examples/qml/signals/qmltopy1/main.py index 83966903..0644e4c9 100644 --- a/examples/qml/signals/qmltopy1/main.py +++ b/examples/qml/signals/qmltopy1/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from pathlib import Path @@ -40,7 +41,7 @@ if __name__ == '__main__': qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/signals/qmltopy2/doc/qmltopy2.rst b/examples/qml/signals/qmltopy2/doc/qmltopy2.rst new file mode 100644 index 00000000..3a2518db --- /dev/null +++ b/examples/qml/signals/qmltopy2/doc/qmltopy2.rst @@ -0,0 +1,12 @@ +Receiving return values from Python in QML +========================================== + +Demonstrate how to call Python methods from QML that return values. + +**Key Features:** + +- **Python Class with Returning Slot:** Defines a `RotateValue` class with a slot that returns an + integer. +- **Exposing Class to QML:** Uses :deco:`~PySide6.QtQml.QmlElement` to expose RotateValue to QML. +- **Using Return Values in QML:** Calls the Python method from QML and uses the returned value to + update the UI. diff --git a/examples/qml/signals/qmltopy2/main.py b/examples/qml/signals/qmltopy2/main.py index bc2e8d3e..aeb8b3b7 100644 --- a/examples/qml/signals/qmltopy2/main.py +++ b/examples/qml/signals/qmltopy2/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from pathlib import Path @@ -35,7 +36,7 @@ if __name__ == '__main__': qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/signals/qmltopy3/doc/qmltopy3.rst b/examples/qml/signals/qmltopy3/doc/qmltopy3.rst new file mode 100644 index 00000000..0c94b4b6 --- /dev/null +++ b/examples/qml/signals/qmltopy3/doc/qmltopy3.rst @@ -0,0 +1,10 @@ +Handling QML Signals in Python +============================== + +Show how to respond to signals emitted from QML in Python. + +**Key Features:** + +- **Defining Signals in QML:** Defines custom signals like `buttonClicked` and `textRotationChanged` +- **Connecting Signals to Python Functions:** In Python, connects these signals to functions. +- **Handling Signals in Python:** Implements the `sayThis` function to handle the signals. diff --git a/examples/qml/signals/qmltopy3/main.py b/examples/qml/signals/qmltopy3/main.py index 6a555484..7656154d 100644 --- a/examples/qml/signals/qmltopy3/main.py +++ b/examples/qml/signals/qmltopy3/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from pathlib import Path @@ -18,7 +19,7 @@ if __name__ == '__main__': view = QQuickView() qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) root = view.rootObject() diff --git a/examples/qml/signals/qmltopy4/doc/qmltopy4.rst b/examples/qml/signals/qmltopy4/doc/qmltopy4.rst new file mode 100644 index 00000000..70f0082b --- /dev/null +++ b/examples/qml/signals/qmltopy4/doc/qmltopy4.rst @@ -0,0 +1,11 @@ +Directly Connecting QML Component Signals to Python Functions +============================================================= + +Teach how to connect signals of specific QML components to Python functions using object names. + +**Key Features:** + +- **Assigning `objectName` in QML:** Sets `objectName` properties to identify QML objects. +- **Finding QML Objects in Python:** Uses `findChild` to get references to QML objects. +- **Connecting Component Signals to Python Functions:** Connects signals directly to Python + functions. diff --git a/examples/qml/signals/qmltopy4/main.py b/examples/qml/signals/qmltopy4/main.py index 8a56073d..a6bd1e06 100644 --- a/examples/qml/signals/qmltopy4/main.py +++ b/examples/qml/signals/qmltopy4/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from pathlib import Path @@ -18,7 +19,7 @@ if __name__ == '__main__': view = QQuickView() qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) root = view.rootObject() diff --git a/examples/qml/textproperties/main.py b/examples/qml/textproperties/main.py index 4e6afc9f..802e9f94 100644 --- a/examples/qml/textproperties/main.py +++ b/examples/qml/textproperties/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from pathlib import Path @@ -73,4 +74,6 @@ if __name__ == '__main__': if not engine.rootObjects(): sys.exit(-1) - sys.exit(app.exec()) + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst b/examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst index 4c1b3bda..a18acb4b 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst +++ b/examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst @@ -1,5 +1,3 @@ -.. _qml-adding-types-example: - Extending QML - Adding Types Example ==================================== diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/main.py b/examples/qml/tutorials/extending-qml-advanced/adding/main.py index ec703dbf..4c556c63 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/adding/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/referenceexamples/adding example from Qt v6.x""" diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/person.py b/examples/qml/tutorials/extending-qml-advanced/adding/person.py index 526eae71..90e82082 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/adding/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property from PySide6.QtQml import QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/birthdayparty.py index 76481517..875e98a2 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/doc/advanced1-Base-project.rst b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/doc/advanced1-Base-project.rst index 90a73b78..878b122c 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/doc/advanced1-Base-project.rst +++ b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/doc/advanced1-Base-project.rst @@ -1,5 +1,3 @@ -.. _qml-advanced1-base-project: - Extending QML (advanced) - BirthdayParty Base Project ===================================================== diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py index 560db660..eccb9ff8 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project example from Qt v6.x""" diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/person.py b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/person.py index 60dc9d88..89a66925 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/birthdayparty.py index 76481517..875e98a2 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/doc/advanced2-Inheritance-and-coercion.rst b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/doc/advanced2-Inheritance-and-coercion.rst index 16c0dbc9..e55705d5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/doc/advanced2-Inheritance-and-coercion.rst +++ b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/doc/advanced2-Inheritance-and-coercion.rst @@ -1,5 +1,3 @@ -.. _qml-advanced2-inheritance-and-coercion: - Extending QML (advanced) - Inheritance and Coercion =================================================== @@ -25,7 +23,7 @@ To do this, the ``Boy`` and ``Girl`` classes are introduced, both inheriting fro The ``Person`` class remains unaltered and the ``Boy`` and ``Girl`` classes are trivial extensions of it. The types and their QML name are registered with the -QML engine with ``@QmlElement``. +QML engine with :deco:`~PySide6.QtQml.QmlElement`. Notice that the ``host`` and ``guests`` properties in ``BirthdayParty`` still take instances of ``Person``. @@ -49,8 +47,9 @@ explicit ``Boy`` or ``Girl`` should be instantiated instead. While we want to disallow instantiating ``Person`` from within QML, it still needs to be registered with the QML engine so that it can be used as a property -type and other types can be coerced to it. This is what the ``@QmlUncreatable`` -macro does. As all three types, ``Person``, ``Boy`` and ``Girl``, have been +type and other types can be coerced to it. This is what +:deco:`~PySide6.QtQml.QmlUncreatable` does. +As all three types, ``Person``, ``Boy`` and ``Girl``, have been registered with the QML system, on assignment, QML automatically (and type-safely) converts the ``Boy`` and ``Girl`` objects into a ``Person``. diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py index cc77e2b4..ba9063e2 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion example diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/person.py b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/person.py index 57e73e6f..3d7e0271 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlElement, QmlUncreatable diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/birthdayparty.py index 3f6102c6..1ef5ae51 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, ClassInfo, Property, Signal from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/doc/advanced3-Default-properties.rst b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/doc/advanced3-Default-properties.rst index 0857f9d0..c2cb5ecc 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/doc/advanced3-Default-properties.rst +++ b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/doc/advanced3-Default-properties.rst @@ -1,5 +1,3 @@ -.. _qml-advanced3-default-properties: - Extending QML (advanced) - Default Properties ============================================= diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py index 020974c9..977a44c4 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/default advanced3-Default-properties example diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/person.py b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/person.py index 503aaf65..8deb7d0b 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlAnonymous, QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/birthdayparty.py index 3f6102c6..1ef5ae51 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, ClassInfo, Property, Signal from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/doc/advanced4-Grouped-properties.rst b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/doc/advanced4-Grouped-properties.rst index 7748d318..ff5dbf6b 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/doc/advanced4-Grouped-properties.rst +++ b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/doc/advanced4-Grouped-properties.rst @@ -1,5 +1,3 @@ -.. _qml-advanced-advanced4-grouped-properties: - Extending QML (advanced) - Grouped Properties ============================================= diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py index 9757b6da..f1562054 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties example diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/person.py b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/person.py index ccd439e8..db3b8d5b 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtGui import QColor diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/birthdayparty.py index f38bfd30..8e6628f7 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QDate, QObject, ClassInfo, Property, Signal from PySide6.QtQml import QmlAnonymous, QmlAttached, QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/doc/advanced5-Attached-properties.rst b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/doc/advanced5-Attached-properties.rst index 14b4bddb..c81d02b5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/doc/advanced5-Attached-properties.rst +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/doc/advanced5-Attached-properties.rst @@ -1,5 +1,3 @@ -.. _qml-advanced-advanced5-attached-properties: - Extending QML (advanced) - Attached Properties ============================================== diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py index 9a92afeb..c914fe7d 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties example diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/person.py b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/person.py index 503aaf65..8deb7d0b 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlAnonymous, QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/birthdayparty.py index eacb5201..e946242f 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QDate, QObject, ClassInfo, Property, QTime, Signal from PySide6.QtQml import QmlAnonymous, QmlAttached, QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/doc/advanced6-Property-value-source.rst b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/doc/advanced6-Property-value-source.rst index 4e1dc393..17e13d49 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/doc/advanced6-Property-value-source.rst +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/doc/advanced6-Property-value-source.rst @@ -1,5 +1,3 @@ -.. _qml-advanced/advanced6-property-value-source: - Extending QML (advanced) - Property Value Source ================================================ @@ -16,7 +14,7 @@ the song over time. :lines: 13-49 The class ``HappyBirthdaySong`` is added as a value source. It must inherit -from ``QQmlPropertyValueSource`` and implement its interface. The +from :class:`~PySide6.QtQml.QQmlPropertyValueSource` and implement its interface. The ``setTarget()`` function is used to define which property this source acts upon. In this case, the value source writes to the ``announcement`` property of the ``BirthdayParty`` to display the lyrics over time. It has an internal timer diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/happybirthdaysong.py b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/happybirthdaysong.py index c35f9bff..b76d06ad 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/happybirthdaysong.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/happybirthdaysong.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QTimer, Property, Signal, Slot from PySide6.QtQml import QmlElement, QPyQmlPropertyValueSource diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py index ea412a54..eb693134 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source example diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/person.py b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/person.py index 503aaf65..8deb7d0b 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlAnonymous, QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/binding/birthdayparty.py index a337d4a1..958a3523 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/binding/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QDate, QObject, ClassInfo, Property, QTime, Signal from PySide6.QtQml import QmlAnonymous, QmlAttached, QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/doc/binding.rst b/examples/qml/tutorials/extending-qml-advanced/binding/doc/binding.rst index 5c0ed21b..8ffffa79 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/doc/binding.rst +++ b/examples/qml/tutorials/extending-qml-advanced/binding/doc/binding.rst @@ -1,14 +1,12 @@ -.. _qml-binding-example: - Extending QML - Binding Example =============================== -This example builds on the :ref:`qml-adding-types-example`, -the :ref:`qml-attached-properties-example`, -the :ref:`qml-default-property-example`, -the :ref:`qml-inheritance-and-coercion-example` -the :ref:`qml-object-and-list-property-types-example` -and the :ref:`qml-valuesource-example`. +This example builds on the :ref:`example_qml_tutorials_extending-qml-advanced_adding`, +the :ref:`example_qml_tutorials_extending-qml-advanced_advanced5-attached-properties`, +the :ref:`example_qml_tutorials_extending-qml-advanced_advanced3-default-properties`, +the :ref:`example_qml_tutorials_extending-qml-advanced_advanced2-inheritance-and-coercion` +the :ref:`example_qml_tutorials_extending-qml-advanced_properties` +and the :ref:`example_qml_tutorials_extending-qml-advanced_advanced6-property-value-source`. Running the Example ------------------- diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/happybirthdaysong.py b/examples/qml/tutorials/extending-qml-advanced/binding/happybirthdaysong.py index 59ebfe4c..31d477e8 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/happybirthdaysong.py +++ b/examples/qml/tutorials/extending-qml-advanced/binding/happybirthdaysong.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QTimer, Property, Slot from PySide6.QtQml import QmlElement, QPyQmlPropertyValueSource diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/main.py b/examples/qml/tutorials/extending-qml-advanced/binding/main.py index 64929a80..45d0fd3f 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/binding/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/referenceexamples/binding example from Qt v6.x""" diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/person.py b/examples/qml/tutorials/extending-qml-advanced/binding/person.py index a6942763..69ae48c8 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/binding/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal from PySide6.QtQml import QmlAnonymous, QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/extended/doc/extended.rst b/examples/qml/tutorials/extending-qml-advanced/extended/doc/extended.rst index 74596053..f901de9a 100644 --- a/examples/qml/tutorials/extending-qml-advanced/extended/doc/extended.rst +++ b/examples/qml/tutorials/extending-qml-advanced/extended/doc/extended.rst @@ -1,16 +1,14 @@ -.. _qml-extension-objects-example: - Extending QML - Extension Objects Example ========================================= -This example builds on the the :ref:`qml-adding-types-example`. +This example builds on the the :ref:`example_qml_tutorials_extending-qml-advanced_adding`. -Shows how to use QmlExtended decorator to provide an extension object to a +Shows how to use :deco:`~PySide6.QtQml.QmlExtended` to provide an extension object to a QLineEdit without modifying or subclassing it. Firstly, the LineEditExtension class is registered with the QML system as an -extension of QLineEdit. We declare a foreign type to do this as we cannot -modify Qt's internal QLineEdit class. +extension of :class:`~PySide6.QtWidgets.QLineEdit`. We declare a foreign type to do +this as we cannot modify Qt's internal QLineEdit class. .. code-block:: python @@ -20,7 +18,8 @@ modify Qt's internal QLineEdit class. class LineEditForeign(QObject): -Note the usage of ``QmlNamedElement()`` instead of ``QmlElement()``. +Note the usage of :deco:`~PySide6.QtQml.QmlNamedElement` instead of +:deco:`~PySide6.QtQml.QmlElement`. ``QmlElement()`` uses the name of the containing type by default, ``LineEditExtension`` in this case. As the class being an extension class is an implementation detail, we choose the more natural name ``QLineEdit`` diff --git a/examples/qml/tutorials/extending-qml-advanced/extended/main.py b/examples/qml/tutorials/extending-qml-advanced/extended/main.py index 6ee38640..694a042f 100644 --- a/examples/qml/tutorials/extending-qml-advanced/extended/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/extended/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/referenceexamples/extended example from Qt v6.x""" diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/methods/birthdayparty.py index a3942b67..a0034767 100644 --- a/examples/qml/tutorials/extending-qml-advanced/methods/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/methods/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Slot from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/doc/methods.rst b/examples/qml/tutorials/extending-qml-advanced/methods/doc/methods.rst index bda2ede5..fe7469bd 100644 --- a/examples/qml/tutorials/extending-qml-advanced/methods/doc/methods.rst +++ b/examples/qml/tutorials/extending-qml-advanced/methods/doc/methods.rst @@ -1,14 +1,12 @@ -.. _qml-methods-example: - Extending QML - Methods Example =============================== -This example builds on the :ref:`qml-adding-types-example`, -the :ref:`qml-object-and-list-property-types-example` and -the :ref:`qml-inheritance-and-coercion-example`. +This example builds on the :ref:`example_qml_tutorials_extending-qml-advanced_adding`, +the :ref:`example_qml_tutorials_extending-qml-advanced_properties` and +the :ref:`example_qml_tutorials_extending-qml-advanced_advanced2-inheritance-and-coercion`. The Methods Example has an additional method in the ``BirthdayParty`` class: -``invite()``. ``invite()`` is decorated with ``@Slot`` so that it can be +``invite()``. ``invite()`` is decorated with :deco:`~PySide6.QtCore.Slot` so that it can be called from QML. In ``example.qml``, the ``invite()`` method is called diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/main.py b/examples/qml/tutorials/extending-qml-advanced/methods/main.py index fb656f26..e23476b4 100644 --- a/examples/qml/tutorials/extending-qml-advanced/methods/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/methods/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/referenceexamples/methods example from Qt v6.x""" diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/person.py b/examples/qml/tutorials/extending-qml-advanced/methods/person.py index 526eae71..90e82082 100644 --- a/examples/qml/tutorials/extending-qml-advanced/methods/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/methods/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property from PySide6.QtQml import QmlElement diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/birthdayparty.py b/examples/qml/tutorials/extending-qml-advanced/properties/birthdayparty.py index 47dddc85..be7a2143 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/birthdayparty.py +++ b/examples/qml/tutorials/extending-qml-advanced/properties/birthdayparty.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst index 16924cdc..6655187e 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst +++ b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst @@ -1,11 +1,9 @@ -.. _qml-object-and-list-property-types-example: - Extending QML - Object and List Property Types Example ====================================================== Exporting C++ Properties. -This example builds on :ref:`qml-adding-types-example`. +This example builds on :ref:`example_qml_tutorials_extending-qml-advanced_adding`. The Object and List Property Types example shows how to add object and list properties in QML. This example adds a BirthdayParty type that specifies a diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/main.py b/examples/qml/tutorials/extending-qml-advanced/properties/main.py index 11757d5f..f481be16 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/main.py +++ b/examples/qml/tutorials/extending-qml-advanced/properties/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/examples/qml/referenceexamples/properties example from Qt v6.x""" diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/person.py b/examples/qml/tutorials/extending-qml-advanced/properties/person.py index 526eae71..90e82082 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/person.py +++ b/examples/qml/tutorials/extending-qml-advanced/properties/person.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property from PySide6.QtQml import QmlElement diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py b/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py index 47d0a0e0..322bef95 100644 --- a/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/basics.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/tutorials/extending-qml/chapter1-basics example from Qt v5.x""" @@ -31,7 +32,7 @@ class PieChart (QQuickPaintedItem): def paint(self, painter): pen = QPen(self.color, 2) painter.setPen(pen) - painter.setRenderHints(QPainter.Antialiasing, True) + painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) @Property(QColor, final=True) @@ -55,10 +56,10 @@ if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst b/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst index 38233aae..d9bc18cc 100644 --- a/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst @@ -1,5 +1,3 @@ -.. _qml-chapter1-basics: - Extending QML - Creating a New Type =================================== @@ -48,8 +46,8 @@ To do this, we need a C++ class that encapsulates this ``PieChart`` type and its two properties. Since QML makes extensive use of Qt's Meta-Object System this new class must: -* Inherit from ``QObject`` -* Declare its properties using the ``Property`` decorator +* Inherit from :class:`~PySide6.QtCore.QObject` +* Declare its properties using the :class:`~PySide6.QtCore.Property` decorator Class Implementation -------------------- @@ -60,21 +58,22 @@ Here is our ``PieChart`` class, defined in ``basics.py``: :lineno-start: 21 :lines: 21-51 -The class inherits from ``QQuickPaintedItem`` because we want to override -``QQuickPaintedItem.paint()`` to perform drawing operations with the -``QPainter`` API. If the class just represented some data type and was not an +The class inherits from :class:`~PySide6.QtQuick.QQuickPaintedItem` +because we want to override :meth:`~PySide6.QtQuick.QQuickPaintedItem.paint` +to perform drawing operations with the :class:`~PySide6.QtGui.QPainter` API. +If the class just represented some data type and was not an item that actually needed to be displayed, it could simply inherit from -``QObject``. Or, if we want to extend the functionality of an existing +:class:`~PySide6.QtCore.QObject`. Or, if we want to extend the functionality of an existing ``QObject``-based class, it could inherit from that class instead. Alternatively, if we want to create a visual item that doesn't need to perform drawing operations with the ``QPainter`` API, we can just subclass -``QQuickItem``. +:class:`~PySide6.QtQuick.QQuickItem`. The ``PieChart`` class defines the two properties, ``name`` and ``color``, with the ``Property`` decorator, and overrides ``QQuickPaintedItem.paint()``. The -``PieChart`` class is registered using the ``QmlElement`` decorator, to allow -it to be used from QML. If you don't register the class, ``app.qml`` won't be -able to create a ``PieChart``. +``PieChart`` class is registered using the :deco:`~PySide6.QtQml.QmlElement` +decorator, to allow it to be used from QML. If you don't register the class, ``app.qml`` +won't be able to create a ``PieChart``. QML Usage --------- @@ -88,13 +87,13 @@ using a standard QML ``Text`` item: :lines: 7-26 Notice that although the color is specified as a string in QML, it is -automatically converted to a ``QColor`` object for the PieChart ``color`` -property. Automatic conversions are provided for various other QML value types. +automatically converted to a :class:`~PySide6.QtGui.QColor` object for the PieChart +``color`` property. Automatic conversions are provided for various other QML value types. For example, a string like "640x480" can be automatically converted to a ``QSize`` value. -We'll also create a main function that uses a ``QQuickView`` to run and display -``app.qml``. Here is the application ``basics.py``: +We'll also create a main function that uses a :class:`~PySide6.QtQuick.QQuickView` +to run and display ``app.qml``. Here is the application ``basics.py``: .. literalinclude:: basics.py :lineno-start: 54 diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst b/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst index 245d0ddb..c192fb9b 100644 --- a/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst @@ -1,5 +1,3 @@ -.. _qml-chapter2-methods: - Extending QML - Connecting to C++ Methods and Signals ===================================================== @@ -21,7 +19,7 @@ to our C++ class: :lineno-start: 54 :lines: 54-58 -The use of the ``Slot`` decorator makes the ``clearChart()`` method available +The use of :deco:`~PySide6.QtCore.Slot` makes the ``clearChart()`` method available to the Qt Meta-Object system, and in turn, to QML. The method simply changes the color to ``Qt::transparent``, repaints the chart, then emits the ``chartCleared()`` signal: diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py b/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py index d455c317..238225fd 100644 --- a/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/methods.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/tutorials/extending-qml/chapter2-methods example from Qt v5.x""" @@ -32,7 +33,7 @@ class PieChart(QQuickPaintedItem): def paint(self, painter): pen = QPen(self.color, 2) painter.setPen(pen) - painter.setRenderHints(QPainter.Antialiasing, True) + painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) @Property(QColor, final=True) @@ -62,10 +63,10 @@ if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py b/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py index a9b61e7f..ed332cba 100644 --- a/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/tutorials/extending-qml/chapter3-bindings example from Qt v5.x""" @@ -33,7 +34,7 @@ class PieChart (QQuickPaintedItem): def paint(self, painter): pen = QPen(self._color, 2) painter.setPen(pen) - painter.setRenderHints(QPainter.Antialiasing, True) + painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) @Property(QColor, notify=colorChanged, final=True) @@ -66,10 +67,10 @@ if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst b/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst index 3b719119..8d29c07a 100644 --- a/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst @@ -1,5 +1,3 @@ -.. _qml-chapter3-bindings: - Extending QML - Adding Property Bindings ======================================== @@ -24,8 +22,8 @@ clicked, the ``onClicked`` handler in the ``MouseArea`` changes the color of ``chartA`` , thereby changing both charts to the color blue. It's easy to enable property binding for the ``color`` property. We add a -``notify`` parameter to its ``Property`` decorator to indicate that a -``colorChanged`` signal is emitted whenever the value changes. +``notify`` parameter to its :class:`~PySide6.QtCore.Property` decorator to +indicate that a ``colorChanged`` signal is emitted whenever the value changes. .. literalinclude:: bindings.py :lineno-start: 39 diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py index 659850f3..bf24ec3c 100644 --- a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example from Qt v5.x""" @@ -37,7 +38,7 @@ class PieSlice (QQuickPaintedItem): def paint(self, painter): pen = QPen(self._color, 2) painter.setPen(pen) - painter.setRenderHints(QPainter.Antialiasing, True) + painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16) @@ -70,10 +71,10 @@ if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst index f7c3efb1..394f8261 100644 --- a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst @@ -1,5 +1,3 @@ -.. _qml-chapter4-custompropertytypes: - Extending QML - Using Custom Property Types =========================================== @@ -32,7 +30,8 @@ int-type property to store an identifier for each chart: } Aside from ``int``, we could use various other property types. Many of the Qt -data types such as ``QColor``, ``QSize`` and ``QRect`` are automatically +data types such as :class:`~PySide6.QtGui.QColor`, :class:`~PySide6.QtCore.QSize` +and :class:`~PySide6.QtCore.QRect` are automatically supported from QML. If we want to create a property whose type is not supported by QML by default, @@ -47,8 +46,8 @@ we assign an ``PieSlice`` value which itself contains a ``color``: :lines: 4-22 Like ``PieChart``, this new ``PieSlice`` type inherits from -``QQuickPaintedItem``, is exposed via the ``QmlElement`` decorator and declares -its properties with the ``Property`` decorator: +``QQuickPaintedItem``, is exposed via :deco:`~PySide6.QtQml.QmlElement` and declares +its properties with the :class:`~PySide6.QtCore.Property` decorator: .. literalinclude:: customPropertyTypes.py :lineno-start: 21 @@ -63,7 +62,7 @@ and associated method signatures: There is one thing to be aware of when implementing ``setPieSlice()``. The ``PieSlice`` is a visual item, so it must be set as a child of the ``PieChart`` -using ``QQuickItem.setParentItem()`` so that the ``PieChart`` knows to paint +using :meth:`~PySide6.QtQuick.QQuickItem.setParentItem` so that the ``PieChart`` knows to paint this child item when its contents are drawn. As with ``PieChart``, we add the ``Charts`` type namespace, version 1.0: diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst index a98f18c8..ece79e9d 100644 --- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst @@ -1,5 +1,3 @@ -.. _qml-chapter5-listproperties: - Extending QML - Using List Property Types ========================================= diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py index 98952cef..67d7482e 100644 --- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the qml/tutorials/extending-qml/chapter5-listproperties example from Qt v5.x""" @@ -53,7 +54,7 @@ class PieSlice (QQuickPaintedItem): def paint(self, painter): pen = QPen(self._color, 2) painter.setPen(pen) - painter.setRenderHints(QPainter.Antialiasing, True) + painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) painter.drawPie( self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16) @@ -84,10 +85,10 @@ if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() res = app.exec() diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py index 3ab8bcc0..d406fd75 100644 --- a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Property from PySide6.QtQml import QmlElement, ListProperty diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py index 6f82f1f1..6a4a6973 100644 --- a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Property, QRectF from PySide6.QtGui import QColor, QPainter, QPen diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst b/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst index a9d10081..2320a8fa 100644 --- a/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst +++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst @@ -1,5 +1,3 @@ -.. _qml-chapter6-plugins-example: - Extending QML - Plugins Example =============================== @@ -9,9 +7,9 @@ about extending QML with Python. This example refers to the Python version of using a QML plugin in Python. The idea of plugins in Python is non-existent because Python modules are dynamically loaded anyway. We use this idea and our QML type registration -decorators - ``QmlELement``/``QmlNamedElement`` - to register the QML modules as they -are imported. The ``pyside6-qml`` tool does this for you by simply pointing to the -``.qml`` file. +decorators - :deco:`~PySide6.QtQml.QmlElement` / :deco:`~PySide6.QtQml.QmlNamedElement` - +to register the QML modules as they are imported. +The :ref:`pyside6-qml` tool does this for you by simply pointing to the ``.qml`` file. .. image:: plugins.png :width: 400 diff --git a/examples/qml/usingmodel/usingmodel.py b/examples/qml/usingmodel/usingmodel.py index 008a1b94..e73eafef 100644 --- a/examples/qml/usingmodel/usingmodel.py +++ b/examples/qml/usingmodel/usingmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from dataclasses import dataclass @@ -24,7 +25,7 @@ class Person: @QmlElement @QmlSingleton class PersonModel (QAbstractListModel): - MyRole = Qt.UserRole + 1 + MyRole = Qt.ItemDataRole.UserRole + 1 def __init__(self, data, parent=None): super().__init__(parent) @@ -33,7 +34,7 @@ class PersonModel (QAbstractListModel): def roleNames(self): roles = { PersonModel.MyRole: QByteArray(b'myrole'), - Qt.DisplayRole: QByteArray(b'display') + Qt.ItemDataRole.DisplayRole: QByteArray(b'display') } return roles @@ -42,10 +43,10 @@ class PersonModel (QAbstractListModel): def data(self, index, role): d = self._data[index.row()] - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return d.name - if role == Qt.DecorationRole: - return Qt.black + if role == Qt.ItemDataRole.DecorationRole: + return Qt.GlobalColor.black if role == PersonModel.MyRole: return d.myrole return None @@ -59,11 +60,11 @@ class PersonModel (QAbstractListModel): if __name__ == '__main__': app = QGuiApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml') view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() diff --git a/examples/quick/models/objectlistmodel/objectlistmodel.py b/examples/quick/models/objectlistmodel/objectlistmodel.py index 968761e5..0f0da3e3 100644 --- a/examples/quick/models/objectlistmodel/objectlistmodel.py +++ b/examples/quick/models/objectlistmodel/objectlistmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from pathlib import Path import sys @@ -49,7 +50,7 @@ if __name__ == '__main__': DataObject("Item 4", "yellow")] view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) view.setInitialProperties({"model": dataList}) qml_file = Path(__file__).parent / "view.qml" diff --git a/examples/quick/models/stringlistmodel/stringlistmodel.py b/examples/quick/models/stringlistmodel/stringlistmodel.py index 3982b1ff..a1614b41 100644 --- a/examples/quick/models/stringlistmodel/stringlistmodel.py +++ b/examples/quick/models/stringlistmodel/stringlistmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from pathlib import Path import sys diff --git a/examples/quick/painteditem/painteditem.py b/examples/quick/painteditem/painteditem.py index cf5a05ad..95390b6e 100644 --- a/examples/quick/painteditem/painteditem.py +++ b/examples/quick/painteditem/painteditem.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from argparse import ArgumentParser, RawTextHelpFormatter from pathlib import Path @@ -39,8 +40,8 @@ class TextBalloon(QQuickPaintedItem): brush = QBrush(QColor("#007430")) painter.setBrush(brush) - painter.setPen(Qt.NoPen) - painter.setRenderHint(QPainter.Antialiasing) + painter.setPen(Qt.PenStyle.NoPen) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) itemSize = self.size() @@ -72,11 +73,11 @@ if __name__ == "__main__": QQmlDebuggingEnabler.enableDebugging(True) app = QApplication(sys.argv) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = Path(__file__).parent / "main.qml" view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py b/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py index 69e7321f..862c38d7 100644 --- a/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py +++ b/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import ctypes import numpy @@ -102,9 +103,9 @@ class CubeRenderer(): w.height() * w.devicePixelRatio()) self.m_program = QOpenGLShaderProgram() - self.m_program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, + self.m_program.addCacheableShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, VERTEXSHADER_SOURCE) - self.m_program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, + self.m_program.addCacheableShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, FRAGMENTSHADER_SOURCE) self.m_program.bindAttributeLocation("vertex", 0) self.m_program.bindAttributeLocation("coord", 1) diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/main.py b/examples/quick/rendercontrol/rendercontrol_opengl/main.py index ee885ae6..340d35a9 100644 --- a/examples/quick/rendercontrol/rendercontrol_opengl/main.py +++ b/examples/quick/rendercontrol/rendercontrol_opengl/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtGui import QGuiApplication @@ -11,7 +12,7 @@ from window_singlethreaded import WindowSingleThreaded if __name__ == "__main__": app = QGuiApplication(sys.argv) # only functional when Qt Quick is also using OpenGL - QQuickWindow.setGraphicsApi(QSGRendererInterface.OpenGLRhi) + QQuickWindow.setGraphicsApi(QSGRendererInterface.GraphicsApi.OpenGLRhi) window = WindowSingleThreaded() window.resize(1024, 768) window.show() diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/requirements.txt b/examples/quick/rendercontrol/rendercontrol_opengl/requirements.txt new file mode 100644 index 00000000..37272131 --- /dev/null +++ b/examples/quick/rendercontrol/rendercontrol_opengl/requirements.txt @@ -0,0 +1 @@ +PyOpenGL diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py b/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py index 6f1e61f9..a40be7d3 100644 --- a/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py +++ b/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import numpy from pathlib import Path @@ -42,7 +43,7 @@ class WindowSingleThreaded(QWindow): self.m_quickReady = False self.m_dpr = 0 self.m_status_conn_id = None - self.setSurfaceType(QSurface.OpenGLSurface) + self.setSurfaceType(QSurface.SurfaceType.OpenGLSurface) format = QSurfaceFormat() # Qt Quick may need a depth and stencil buffer. Always make sure these diff --git a/examples/quick/scenegraph/openglunderqml/main.py b/examples/quick/scenegraph/openglunderqml/main.py index 0e24877b..a7ff6a22 100644 --- a/examples/quick/scenegraph/openglunderqml/main.py +++ b/examples/quick/scenegraph/openglunderqml/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from pathlib import Path @@ -13,14 +14,14 @@ from squircle import Squircle # noqa: F401 if __name__ == "__main__": app = QGuiApplication(sys.argv) - QQuickWindow.setGraphicsApi(QSGRendererInterface.OpenGL) + QQuickWindow.setGraphicsApi(QSGRendererInterface.GraphicsApi.OpenGL) view = QQuickView() - view.setResizeMode(QQuickView.SizeRootObjectToView) + view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView) qml_file = Path(__file__).parent / "main.qml" view.setSource(QUrl.fromLocalFile(qml_file)) - if view.status() == QQuickView.Error: + if view.status() == QQuickView.Status.Error: sys.exit(-1) view.show() diff --git a/examples/quick/scenegraph/openglunderqml/requirements.txt b/examples/quick/scenegraph/openglunderqml/requirements.txt new file mode 100644 index 00000000..37272131 --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/requirements.txt @@ -0,0 +1 @@ +PyOpenGL diff --git a/examples/quick/scenegraph/openglunderqml/squircle.py b/examples/quick/scenegraph/openglunderqml/squircle.py index d2900198..a1a15496 100644 --- a/examples/quick/scenegraph/openglunderqml/squircle.py +++ b/examples/quick/scenegraph/openglunderqml/squircle.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Property, QRunnable, Qt, Signal, Slot from PySide6.QtQml import QmlElement @@ -47,9 +48,9 @@ class Squircle(QQuickItem): @Slot(QQuickWindow) def handleWindowChanged(self, win): if win: - win.beforeSynchronizing.connect(self.sync, type=Qt.DirectConnection) - win.sceneGraphInvalidated.connect(self.cleanup, type=Qt.DirectConnection) - win.setColor(Qt.black) + win.beforeSynchronizing.connect(self.sync, type=Qt.ConnectionType.DirectConnection) + win.sceneGraphInvalidated.connect(self.cleanup, type=Qt.ConnectionType.DirectConnection) + win.setColor(Qt.GlobalColor.black) self.sync() @Slot() @@ -62,9 +63,9 @@ class Squircle(QQuickItem): window = self.window() if not self._renderer: self._renderer = SquircleRenderer() - window.beforeRendering.connect(self._renderer.init, Qt.DirectConnection) + window.beforeRendering.connect(self._renderer.init, Qt.ConnectionType.DirectConnection) window.beforeRenderPassRecording.connect( - self._renderer.paint, Qt.DirectConnection + self._renderer.paint, Qt.ConnectionType.DirectConnection ) self._renderer.setViewportSize(window.size() * window.devicePixelRatio()) self._renderer.setT(self._t) @@ -72,7 +73,7 @@ class Squircle(QQuickItem): def releaseResources(self): self.window().scheduleRenderJob( - CleanupJob(self._renderer), QQuickWindow.BeforeSynchronizingStage + CleanupJob(self._renderer), QQuickWindow.RenderStage.BeforeSynchronizingStage ) self._renderer = None diff --git a/examples/quick/scenegraph/openglunderqml/squirclerenderer.py b/examples/quick/scenegraph/openglunderqml/squirclerenderer.py index d824f96a..d9921997 100644 --- a/examples/quick/scenegraph/openglunderqml/squirclerenderer.py +++ b/examples/quick/scenegraph/openglunderqml/squirclerenderer.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from textwrap import dedent @@ -56,11 +57,13 @@ class SquircleRenderer(QOpenGLFunctions): def init(self): if not self._program: rif = self._window.rendererInterface() - assert (rif.graphicsApi() == QSGRendererInterface.OpenGL) + assert (rif.graphicsApi() == QSGRendererInterface.GraphicsApi.OpenGL) self.initializeOpenGLFunctions() self._program = QOpenGLShaderProgram() - self._program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, VERTEX_SHADER) - self._program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, FRAGMENT_SHADER) + self._program.addCacheableShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, + VERTEX_SHADER) + self._program.addCacheableShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, + FRAGMENT_SHADER) self._program.bindAttributeLocation("vertices", 0) self._program.link() diff --git a/examples/quick/scenegraph/scenegraph_customgeometry/main.py b/examples/quick/scenegraph/scenegraph_customgeometry/main.py index 60a90406..5c588b96 100644 --- a/examples/quick/scenegraph/scenegraph_customgeometry/main.py +++ b/examples/quick/scenegraph/scenegraph_customgeometry/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt Quick customgeometry example from Qt v6.x""" @@ -37,7 +38,7 @@ class BezierCurve(QQuickItem): self._node = None self._geometry = None - self.setFlag(QQuickItem.Flags.ItemHasContents, True) + self.setFlag(QQuickItem.Flag.ItemHasContents, True) def p1(self): return self._p1 @@ -99,11 +100,11 @@ class BezierCurve(QQuickItem): self._node = QSGGeometryNode() self._node.setGeometry(self._geometry) - self._node.setFlag(QSGNode.Flags.OwnsGeometry) + self._node.setFlag(QSGNode.Flag.OwnsGeometry) self._material = QSGFlatColorMaterial() self._material.setColor(QColor(255, 0, 0)) self._node.setMaterial(self._material) - self._node.setFlag(QSGNode.Flags.OwnsMaterial) + self._node.setFlag(QSGNode.Flag.OwnsMaterial) else: self._geometry = self._node.geometry() self._geometry.allocate(self._segmentCount) @@ -123,7 +124,7 @@ class BezierCurve(QQuickItem): self._geometry.setVertexDataAsPoint2D(vertices) - self._node.markDirty(QSGNode.DirtyGeometry) + self._node.markDirty(QSGNode.DirtyStateBit.DirtyGeometry) return self._node p1 = Property(QPointF, p1, setP1, notify=p1Changed) diff --git a/examples/quick/shared/FlickrRssModel.qml b/examples/quick/shared/FlickrRssModel.qml index e78d1ffa..84402b60 100644 --- a/examples/quick/shared/FlickrRssModel.qml +++ b/examples/quick/shared/FlickrRssModel.qml @@ -42,4 +42,3 @@ ListModel { fetchImages("json"); } } - diff --git a/examples/quick/window/main.py b/examples/quick/window/main.py index 62ba6a5e..f12fa88d 100644 --- a/examples/quick/window/main.py +++ b/examples/quick/window/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from pathlib import Path diff --git a/examples/quick3d/customgeometry/examplepoint.py b/examples/quick3d/customgeometry/examplepoint.py index 0360806a..3b498422 100644 --- a/examples/quick3d/customgeometry/examplepoint.py +++ b/examples/quick3d/customgeometry/examplepoint.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import random @@ -47,5 +48,6 @@ class ExamplePointGeometry(QQuick3DGeometry): self.setPrimitiveType(QQuick3DGeometry.PrimitiveType.Points) self.addAttribute( - QQuick3DGeometry.Attribute.PositionSemantic, 0, QQuick3DGeometry.Attribute.F32Type + QQuick3DGeometry.Attribute.Semantic.PositionSemantic, 0, + QQuick3DGeometry.Attribute.ComponentType.F32Type ) diff --git a/examples/quick3d/customgeometry/exampletriangle.py b/examples/quick3d/customgeometry/exampletriangle.py index 854430c4..996a9f85 100644 --- a/examples/quick3d/customgeometry/exampletriangle.py +++ b/examples/quick3d/customgeometry/exampletriangle.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import numpy as np from PySide6.QtCore import Property, Signal @@ -170,14 +171,15 @@ class ExampleTriangleGeometry(QQuick3DGeometry): self.setBounds(QVector3D(-1.0, -1.0, 0.0), QVector3D(+1.0, +1.0, 0.0)) self.setPrimitiveType(QQuick3DGeometry.PrimitiveType.Triangles) self.addAttribute( - QQuick3DGeometry.Attribute.PositionSemantic, 0, QQuick3DGeometry.Attribute.F32Type + QQuick3DGeometry.Attribute.Semantic.PositionSemantic, 0, + QQuick3DGeometry.Attribute.ComponentType.F32Type ) if self._hasNormals: self.addAttribute( - QQuick3DGeometry.Attribute.NormalSemantic, + QQuick3DGeometry.Attribute.Semantic.NormalSemantic, 3 * FLOAT_SIZE, - QQuick3DGeometry.Attribute.F32Type, + QQuick3DGeometry.Attribute.ComponentType.F32Type, ) if self._hasUV: diff --git a/examples/quick3d/customgeometry/main.py b/examples/quick3d/customgeometry/main.py index 148330d9..bff6b4a9 100644 --- a/examples/quick3d/customgeometry/main.py +++ b/examples/quick3d/customgeometry/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os @@ -26,4 +27,6 @@ if __name__ == "__main__": if not engine.rootObjects(): sys.exit(-1) - sys.exit(app.exec()) + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/quick3d/intro/main.py b/examples/quick3d/intro/main.py index 82743403..bb0c95d3 100644 --- a/examples/quick3d/intro/main.py +++ b/examples/quick3d/intro/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os import sys @@ -21,4 +22,6 @@ if __name__ == "__main__": if not engine.rootObjects(): sys.exit(-1) - sys.exit(app.exec()) + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/quick3d/proceduraltexture/gradienttexture.py b/examples/quick3d/proceduraltexture/gradienttexture.py index a577f7eb..22df990c 100644 --- a/examples/quick3d/proceduraltexture/gradienttexture.py +++ b/examples/quick3d/proceduraltexture/gradienttexture.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Signal, Property, QSize from PySide6.QtGui import QColor @@ -76,7 +77,7 @@ class GradientTexture(QQuick3DTextureData): def updateTexture(self): self.setSize(QSize(self._width, self._height)) - self.setFormat(QQuick3DTextureData.RGBA8) + self.setFormat(QQuick3DTextureData.Format.RGBA8) self.setHasTransparency(False) self.setTextureData(self.generate_texture()) diff --git a/examples/quick3d/proceduraltexture/main.py b/examples/quick3d/proceduraltexture/main.py index a732cc01..05ff4937 100644 --- a/examples/quick3d/proceduraltexture/main.py +++ b/examples/quick3d/proceduraltexture/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtGui import QGuiApplication from PySide6.QtQml import QQmlApplicationEngine @@ -24,7 +25,6 @@ if __name__ == "__main__": if not engine.rootObjects(): sys.exit(-1) - ex = app.exec() + exit_code = app.exec() del engine - - sys.exit(ex) + sys.exit(exit_code) diff --git a/examples/quickcontrols/contactslist/contactmodel.py b/examples/quickcontrols/contactslist/contactmodel.py index 5d2746c2..9f17786c 100644 --- a/examples/quickcontrols/contactslist/contactmodel.py +++ b/examples/quickcontrols/contactslist/contactmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import bisect from dataclasses import dataclass @@ -18,10 +19,10 @@ class ContactModel(QAbstractListModel): @QEnum class ContactRole(IntEnum): - FullNameRole = Qt.DisplayRole - AddressRole = Qt.UserRole - CityRole = Qt.UserRole + 1 - NumberRole = Qt.UserRole + 2 + FullNameRole = Qt.ItemDataRole.DisplayRole + AddressRole = Qt.ItemDataRole.UserRole + CityRole = Qt.ItemDataRole.UserRole + 1 + NumberRole = Qt.ItemDataRole.UserRole + 2 @dataclass class Contact: diff --git a/examples/quickcontrols/contactslist/main.py b/examples/quickcontrols/contactslist/main.py index d501bbdb..c9ce11e5 100644 --- a/examples/quickcontrols/contactslist/main.py +++ b/examples/quickcontrols/contactslist/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """ PySide6 port of Qt Quick Controls Contact List example from Qt v6.x @@ -23,6 +24,6 @@ if __name__ == '__main__': if not engine.rootObjects(): sys.exit(-1) - ex = app.exec() + exit_code = app.exec() del engine - sys.exit(ex) + sys.exit(exit_code) diff --git a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst index b46cbec7..98034ba4 100644 --- a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst +++ b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst @@ -1,5 +1,3 @@ -.. _filesystemexplorer_example: - Qt Quick Controls - Filesystem Explorer ======================================= diff --git a/examples/quickcontrols/filesystemexplorer/editormodels.py b/examples/quickcontrols/filesystemexplorer/editormodels.py index 68814772..f317fb48 100644 --- a/examples/quickcontrols/filesystemexplorer/editormodels.py +++ b/examples/quickcontrols/filesystemexplorer/editormodels.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QFileSystemModel from PySide6.QtQuick import QQuickTextDocument diff --git a/examples/quickcontrols/filesystemexplorer/main.py b/examples/quickcontrols/filesystemexplorer/main.py index 8fad951c..9c63ec38 100644 --- a/examples/quickcontrols/filesystemexplorer/main.py +++ b/examples/quickcontrols/filesystemexplorer/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """ This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer. @@ -45,4 +46,6 @@ if __name__ == '__main__': fsm = engine.singletonInstance("FileSystemModule", "FileSystemModel") fsm.setInitialDirectory(args[0]) - sys.exit(app.exec()) + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/quickcontrols/gallery/gallery.py b/examples/quickcontrols/gallery/gallery.py index 6c2a3612..d454cf53 100644 --- a/examples/quickcontrols/gallery/gallery.py +++ b/examples/quickcontrols/gallery/gallery.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """ The gallery example is a simple application with a drawer menu that contains @@ -49,4 +50,6 @@ if __name__ == "__main__": window = rootObjects[0] window.setIcon(QIcon(':/qt-project.org/logos/pysidelogo.png')) - sys.exit(app.exec()) + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/remoteobjects/modelview/doc/modelview.rst b/examples/remoteobjects/modelview/doc/modelview.rst new file mode 100644 index 00000000..8fc6780e --- /dev/null +++ b/examples/remoteobjects/modelview/doc/modelview.rst @@ -0,0 +1,12 @@ +Model-View Server Example +========================= + +Developing a simple server program that displays and makes changes to a +QTreeView which is made available on a Remote Objects network. It +corresponds to the Qt example `Model-View Server Example +`_. + +.. image:: modelview.png + :align: center + :alt: modelview screenshot + :width: 400 diff --git a/examples/remoteobjects/modelview/modelviewclient.py b/examples/remoteobjects/modelview/modelviewclient.py index 38409c99..c311d1fe 100644 --- a/examples/remoteobjects/modelview/modelviewclient.py +++ b/examples/remoteobjects/modelview/modelviewclient.py @@ -1,6 +1,7 @@ # Copyright (C) 2017 Ford Motor Company # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the remoteobjects/modelviewclient example from Qt v5.x""" diff --git a/examples/remoteobjects/modelview/modelviewserver.py b/examples/remoteobjects/modelview/modelviewserver.py index 0e213fb5..ec85e521 100644 --- a/examples/remoteobjects/modelview/modelviewserver.py +++ b/examples/remoteobjects/modelview/modelviewserver.py @@ -1,6 +1,7 @@ # Copyright (C) 2017 Ford Motor Company # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the remoteobjects/modelviewserver example from Qt v5.x""" @@ -20,15 +21,17 @@ class TimerHandler(QObject): def change_data(self): for i in range(10, 50): self._model.setData(self._model.index(i, 1), - QColor(Qt.blue), Qt.BackgroundRole) + QColor(Qt.GlobalColor.blue), + Qt.ItemDataRole.BackgroundRole) def insert_data(self): self._model.insertRows(2, 9) for i in range(2, 11): self._model.setData(self._model.index(i, 1), - QColor(Qt.green), Qt.BackgroundRole) + QColor(Qt.GlobalColor.green), + Qt.ItemDataRole.BackgroundRole) self._model.setData(self._model.index(i, 1), - "InsertedRow", Qt.DisplayRole) + "InsertedRow", Qt.ItemDataRole.DisplayRole) def remove_data(self): self._model.removeRows(2, 4) @@ -37,7 +40,7 @@ class TimerHandler(QObject): item = self._model.item(0, 0) item.setEnabled(False) item = item.child(0, 0) - item.setFlags(item.flags() & Qt.ItemIsSelectable) + item.setFlags(item.flags() & Qt.ItemFlag.ItemIsSelectable) def move_data(self): self._model.moveRows(QModelIndex(), 2, 4, QModelIndex(), 10) @@ -69,19 +72,19 @@ if __name__ == '__main__': first_item.appendRow(add_child(2, 2)) second_item = QStandardItem(f"FancyRow2TextNumber {i}") if i % 2 == 0: - first_item.setBackground(Qt.red) + first_item.setBackground(Qt.GlobalColor.red) row = [first_item, second_item] source_model.invisibleRootItem().appendRow(row) data_list.append(f"FancyTextNumber {i}") # Needed by QMLModelViewClient role_names = { - Qt.DisplayRole: QByteArray(b'_text'), - Qt.BackgroundRole: QByteArray(b'_color') + Qt.ItemDataRole.DisplayRole: QByteArray(b'_text'), + Qt.ItemDataRole.BackgroundRole: QByteArray(b'_color') } source_model.setItemRoleNames(role_names) - roles = [Qt.DisplayRole, Qt.BackgroundRole] + roles = [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.BackgroundRole] print("Creating registry host") node = QRemoteObjectRegistryHost(QUrl("local:registry")) diff --git a/examples/samplebinding/doc/samplebinding.pyproject b/examples/samplebinding/doc/samplebinding.pyproject index b0786355..883c74c0 100644 --- a/examples/samplebinding/doc/samplebinding.pyproject +++ b/examples/samplebinding/doc/samplebinding.pyproject @@ -7,5 +7,6 @@ "../main.py", "../truck.cpp", "../truck.h", - "../CMakeLists.txt"] + "../CMakeLists.txt", + "../../utils/pyside_config.py"] } diff --git a/examples/samplebinding/doc/samplebinding.rst b/examples/samplebinding/doc/samplebinding.rst index 51b6b4c2..930405c5 100644 --- a/examples/samplebinding/doc/samplebinding.rst +++ b/examples/samplebinding/doc/samplebinding.rst @@ -48,7 +48,7 @@ placed in separate ``.cpp`` files named after each C++ type. The code is then compiled and linked into a shared library. The shared library is a CPython extension module, which is loaded by the Python interpreter. -Beacuse the C++ language has different semantics to Python, shiboken +Because the C++ language has different semantics to Python, shiboken needs help in figuring out how to generate the bindings code. This is done by specifying a special XML file called a typesystem file. @@ -186,6 +186,7 @@ Run CMake on Windows: To build: .. code-block:: bash + ninja ninja install cd .. diff --git a/examples/samplebinding/main.py b/examples/samplebinding/main.py index cb9f116e..125073d2 100644 --- a/examples/samplebinding/main.py +++ b/examples/samplebinding/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """An example showcasing how to use bindings for a custom non-Qt C++ library""" diff --git a/examples/scriptableapplication/doc/scriptableapplication.pyproject b/examples/scriptableapplication/doc/scriptableapplication.pyproject index eee54112..1b1545c7 100644 --- a/examples/scriptableapplication/doc/scriptableapplication.pyproject +++ b/examples/scriptableapplication/doc/scriptableapplication.pyproject @@ -1,9 +1,11 @@ { "files": ["../main.cpp", + "../scriptableapplication.xml", "../mainwindow.cpp", "../mainwindow.h", "../pythonutils.cpp", "../pythonutils.h", "../wrappedclasses.h", - "../CMakeLists.txt"] + "../CMakeLists.txt", + "../../utils/pyside_config.py"] } diff --git a/examples/scriptableapplication/mainwindow.cpp b/examples/scriptableapplication/mainwindow.cpp index ece7989e..5dc6fd7f 100644 --- a/examples/scriptableapplication/mainwindow.cpp +++ b/examples/scriptableapplication/mainwindow.cpp @@ -5,6 +5,7 @@ #include "pythonutils.h" #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include @@ -69,6 +71,11 @@ MainWindow::MainWindow() m_scriptEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); setCentralWidget(m_scriptEdit); + m_lineNumberLabel = new QLabel; + statusBar()->addPermanentWidget(m_lineNumberLabel, 0); + connect(m_scriptEdit, &QPlainTextEdit::cursorPositionChanged, + this, &MainWindow::slotCursorChanged); + if (!PythonUtils::bindAppObject("__main__"_L1, "mainWindow"_L1, PythonUtils::MainWindowType, this)) { statusBar()->showMessage(tr("Error loading the application module")); @@ -105,3 +112,10 @@ void MainWindow::testFunction1() qDebug().noquote() << message; statusBar()->showMessage(message); } + +void MainWindow::slotCursorChanged() +{ + auto cursor = m_scriptEdit->textCursor(); + const int line = cursor.blockNumber() + 1; + m_lineNumberLabel->setText("Line: "_L1 + QString::number(line)); +} diff --git a/examples/scriptableapplication/mainwindow.h b/examples/scriptableapplication/mainwindow.h index e72f5ca7..98af12eb 100644 --- a/examples/scriptableapplication/mainwindow.h +++ b/examples/scriptableapplication/mainwindow.h @@ -6,6 +6,7 @@ #include +QT_FORWARD_DECLARE_CLASS(QLabel) QT_FORWARD_DECLARE_CLASS(QPlainTextEdit) class MainWindow : public QMainWindow @@ -19,6 +20,7 @@ public: static constexpr auto TEST = QLatin1StringView("test"); private Q_SLOTS: + void slotCursorChanged(); void slotRunScript(); void slotPrintDiagnostics(); @@ -26,6 +28,7 @@ private: void runScript(const QString &); QPlainTextEdit *m_scriptEdit; + QLabel *m_lineNumberLabel; }; #endif // MAINWINDOW_H diff --git a/examples/serialbus/can/bitratebox.py b/examples/serialbus/can/bitratebox.py index 68c70baa..952967c1 100644 --- a/examples/serialbus/can/bitratebox.py +++ b/examples/serialbus/can/bitratebox.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QComboBox from PySide6.QtGui import QIntValidator diff --git a/examples/serialbus/can/canbusdeviceinfobox.py b/examples/serialbus/can/canbusdeviceinfobox.py index 5e604926..15f3cc2b 100644 --- a/examples/serialbus/can/canbusdeviceinfobox.py +++ b/examples/serialbus/can/canbusdeviceinfobox.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import QGroupBox @@ -8,8 +9,8 @@ from ui_canbusdeviceinfobox import Ui_CanBusDeviceInfoBox def _set_readonly_and_compact(box): - box.setAttribute(Qt.WA_TransparentForMouseEvents) - box.setFocusPolicy(Qt.NoFocus) + box.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) + box.setFocusPolicy(Qt.FocusPolicy.NoFocus) box.setStyleSheet("margin-top:0; margin-bottom:0;") diff --git a/examples/serialbus/can/canbusdeviceinfobox.ui b/examples/serialbus/can/canbusdeviceinfobox.ui index d4dd84fb..39e2dd88 100644 --- a/examples/serialbus/can/canbusdeviceinfobox.ui +++ b/examples/serialbus/can/canbusdeviceinfobox.ui @@ -7,7 +7,7 @@ 0 0 319 - 217 + 257 diff --git a/examples/serialbus/can/canbusdeviceinfodialog.py b/examples/serialbus/can/canbusdeviceinfodialog.py index dfb4e602..ced446b0 100644 --- a/examples/serialbus/can/canbusdeviceinfodialog.py +++ b/examples/serialbus/can/canbusdeviceinfodialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QDialog diff --git a/examples/serialbus/can/canbusdeviceinfodialog.ui b/examples/serialbus/can/canbusdeviceinfodialog.ui index 6328a06d..fc3f6353 100644 --- a/examples/serialbus/can/canbusdeviceinfodialog.ui +++ b/examples/serialbus/can/canbusdeviceinfodialog.ui @@ -29,7 +29,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/examples/serialbus/can/connectdialog.py b/examples/serialbus/can/connectdialog.py index a6d5ac37..bd612ba7 100644 --- a/examples/serialbus/can/connectdialog.py +++ b/examples/serialbus/can/connectdialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QSettings, Qt, Slot from PySide6.QtGui import QIntValidator @@ -50,7 +51,7 @@ class ConnectDialog(QDialog): self.m_ui.useConfigurationBox.toggled.connect(self.m_ui.configurationBox.setEnabled) self.m_ui.pluginListBox.currentTextChanged.connect(self.plugin_changed) self.m_ui.interfaceListBox.currentTextChanged.connect(self.interface_changed) - self.m_ui.ringBufferBox.stateChanged.connect(self._ring_buffer_changed) + self.m_ui.ringBufferBox.checkStateChanged.connect(self._ring_buffer_changed) self.m_ui.rawFilterEdit.hide() self.m_ui.rawFilterLabel.hide() @@ -61,7 +62,7 @@ class ConnectDialog(QDialog): @Slot(int) def _ring_buffer_changed(self, state): - self.m_ui.ringBufferLimitBox.setEnabled(state == Qt.CheckState.Checked.value) + self.m_ui.ringBufferLimitBox.setEnabled(state == Qt.CheckState.Checked) def settings(self): return self.m_currentSettings @@ -149,7 +150,8 @@ class ConnectDialog(QDialog): if k == key: result = v break - if not result and (key == QCanBusDevice.LoopbackKey or key == QCanBusDevice.ReceiveOwnKey): + if (not result and (key == QCanBusDevice.ConfigurationKey.LoopbackKey + or key == QCanBusDevice.ConfigurationKey.ReceiveOwnKey)): return "unspecified" return str(result) @@ -162,22 +164,22 @@ class ConnectDialog(QDialog): self.m_ui.ringBufferLimitBox.setValue(self.m_currentSettings.model_ring_buffer_size) self.m_ui.autoscrollBox.setChecked(self.m_currentSettings.use_autoscroll) - value = self.configuration_value(QCanBusDevice.LoopbackKey) + value = self.configuration_value(QCanBusDevice.ConfigurationKey.LoopbackKey) self.m_ui.loopbackBox.setCurrentText(value) - value = self.configuration_value(QCanBusDevice.ReceiveOwnKey) + value = self.configuration_value(QCanBusDevice.ConfigurationKey.ReceiveOwnKey) self.m_ui.receiveOwnBox.setCurrentText(value) - value = self.configuration_value(QCanBusDevice.ErrorFilterKey) + value = self.configuration_value(QCanBusDevice.ConfigurationKey.ErrorFilterKey) self.m_ui.errorFilterEdit.setText(value) - value = self.configuration_value(QCanBusDevice.BitRateKey) + value = self.configuration_value(QCanBusDevice.ConfigurationKey.BitRateKey) self.m_ui.bitrateBox.setCurrentText(value) - value = self.configuration_value(QCanBusDevice.CanFdKey) + value = self.configuration_value(QCanBusDevice.ConfigurationKey.CanFdKey) self.m_ui.canFdBox.setCurrentText(value) - value = self.configuration_value(QCanBusDevice.DataBitRateKey) + value = self.configuration_value(QCanBusDevice.ConfigurationKey.DataBitRateKey) self.m_ui.dataBitrateBox.setCurrentText(value) def update_settings(self): diff --git a/examples/serialbus/can/connectdialog.ui b/examples/serialbus/can/connectdialog.ui index 97e6e005..beda14b3 100644 --- a/examples/serialbus/can/connectdialog.ui +++ b/examples/serialbus/can/connectdialog.ui @@ -66,7 +66,7 @@ 10 - QAbstractSpinBox::AdaptiveDecimalStepType + QAbstractSpinBox::StepType::AdaptiveDecimalStepType 1000 @@ -116,7 +116,7 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -130,7 +130,7 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter FrameError bits @@ -195,7 +195,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/examples/serialbus/can/main.py b/examples/serialbus/can/main.py index 97cdcc90..0e4aefd3 100644 --- a/examples/serialbus/can/main.py +++ b/examples/serialbus/can/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/serialbus/can/mainwindow.py b/examples/serialbus/can/mainwindow.py index ae2a08f1..5800af54 100644 --- a/examples/serialbus/can/mainwindow.py +++ b/examples/serialbus/can/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QTimer, QUrl, Slot from PySide6.QtGui import QDesktopServices @@ -135,11 +136,11 @@ class MainWindow(QMainWindow): self.m_ui.actionDisconnect.setEnabled(True) self.m_ui.actionDeviceInformation.setEnabled(True) self.m_ui.sendFrameBox.setEnabled(True) - config_bit_rate = self.m_can_device.configurationParameter(QCanBusDevice.BitRateKey) - if config_bit_rate > 0: - is_can_fd = bool(self.m_can_device.configurationParameter(QCanBusDevice.CanFdKey)) + config_bit_rate = self.m_can_device.configurationParameter(QCanBusDevice.ConfigurationKey.BitRateKey) # noqa: E501 + if config_bit_rate is not None and config_bit_rate > 0: + is_can_fd = bool(self.m_can_device.configurationParameter(QCanBusDevice.ConfigurationKey.CanFdKey)) # noqa: E501 config_data_bit_rate = self.m_can_device.configurationParameter( - QCanBusDevice.DataBitRateKey) + QCanBusDevice.ConfigurationKey.DataBitRateKey) bit_rate = config_bit_rate / 1000 if is_can_fd and config_data_bit_rate > 0: data_bit_rate = config_data_bit_rate / 1000 diff --git a/examples/serialbus/can/mainwindow.ui b/examples/serialbus/can/mainwindow.ui index 61c7760e..8c7ef841 100644 --- a/examples/serialbus/can/mainwindow.ui +++ b/examples/serialbus/can/mainwindow.ui @@ -33,7 +33,7 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false @@ -42,7 +42,7 @@ false - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows @@ -58,7 +58,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -83,7 +83,7 @@ 0 0 634 - 22 + 26 diff --git a/examples/serialbus/can/receivedframesmodel.py b/examples/serialbus/can/receivedframesmodel.py index 7953f375..d18354e0 100644 --- a/examples/serialbus/can/receivedframesmodel.py +++ b/examples/serialbus/can/receivedframesmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import IntEnum @@ -16,12 +17,15 @@ class ReceivedFramesModelColumns(IntEnum): count = 6 -clipboard_text_role = Qt.UserRole + 1 +clipboard_text_role = Qt.ItemDataRole.UserRole + 1 -column_alignment = [Qt.AlignRight | Qt.AlignVCenter, Qt.AlignRight | Qt.AlignVCenter, - Qt.AlignCenter, Qt.AlignRight | Qt.AlignVCenter, - Qt.AlignRight | Qt.AlignVCenter, Qt.AlignLeft | Qt.AlignVCenter] +column_alignment = [Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, + Qt.AlignmentFlag.AlignCenter, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, + Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter] class ReceivedFramesModel(QAbstractTableModel): @@ -39,7 +43,7 @@ class ReceivedFramesModel(QAbstractTableModel): return True def headerData(self, section, orientation, role): - if role == Qt.DisplayRole and orientation == Qt.Horizontal: + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: if section == ReceivedFramesModelColumns.number: return "#" if section == ReceivedFramesModelColumns.timestamp: @@ -53,7 +57,7 @@ class ReceivedFramesModel(QAbstractTableModel): if section == ReceivedFramesModelColumns.data: return "Data" - if role == Qt.SizeHintRole and orientation == Qt.Horizontal: + if role == Qt.ItemDataRole.SizeHintRole and orientation == Qt.Orientation.Horizontal: if section == ReceivedFramesModelColumns.number: return QSize(80, 25) if section == ReceivedFramesModelColumns.timestamp: @@ -73,9 +77,9 @@ class ReceivedFramesModel(QAbstractTableModel): return None row = index.row() column = index.column() - if role == Qt.TextAlignmentRole: + if role == Qt.ItemDataRole.TextAlignmentRole: return column_alignment[index.column()] - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.AlignmentFlag.DisplayRole: return self.m_framesQueue[row][column] if role == clipboard_text_role: f = self.m_framesQueue[row][column] diff --git a/examples/serialbus/can/receivedframesview.py b/examples/serialbus/can/receivedframesview.py index 0f99a1e7..c4a7f665 100644 --- a/examples/serialbus/can/receivedframesview.py +++ b/examples/serialbus/can/receivedframesview.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QPoint, Qt, Slot from PySide6.QtGui import QAction, QKeySequence @@ -12,7 +13,7 @@ class ReceivedFramesView(QTableView): def __init__(self, parent): super().__init__(parent) - self.setContextMenuPolicy(Qt.CustomContextMenu) + self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self._context_menu) @Slot(QPoint) @@ -31,7 +32,7 @@ class ReceivedFramesView(QTableView): def set_model(self, model): super().setModel(model) for i in range(0, model.columnCount()): - size = model.headerData(i, Qt.Horizontal, Qt.SizeHintRole) + size = model.headerData(i, Qt.Orientation.Horizontal, Qt.ItemDataRole.SizeHintRole) self.setColumnWidth(i, size.width()) def keyPressEvent(self, event): diff --git a/examples/serialbus/can/sendframebox.py b/examples/serialbus/can/sendframebox.py index 6472fc47..b151b79a 100644 --- a/examples/serialbus/can/sendframebox.py +++ b/examples/serialbus/can/sendframebox.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import re diff --git a/examples/serialbus/can/sendframebox.ui b/examples/serialbus/can/sendframebox.ui index d913e334..110c7e73 100644 --- a/examples/serialbus/can/sendframebox.ui +++ b/examples/serialbus/can/sendframebox.ui @@ -6,8 +6,8 @@ 0 0 - 485 - 206 + 505 + 219 @@ -18,7 +18,7 @@ - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize @@ -122,7 +122,7 @@ - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize diff --git a/examples/serialbus/can/ui_canbusdeviceinfobox.py b/examples/serialbus/can/ui_canbusdeviceinfobox.py index d2f0e74a..c4d4fc56 100644 --- a/examples/serialbus/can/ui_canbusdeviceinfobox.py +++ b/examples/serialbus/can/ui_canbusdeviceinfobox.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'canbusdeviceinfobox.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -22,7 +22,7 @@ class Ui_CanBusDeviceInfoBox(object): def setupUi(self, CanBusDeviceInfoBox): if not CanBusDeviceInfoBox.objectName(): CanBusDeviceInfoBox.setObjectName(u"CanBusDeviceInfoBox") - CanBusDeviceInfoBox.resize(319, 217) + CanBusDeviceInfoBox.resize(319, 257) self.verticalLayout = QVBoxLayout(CanBusDeviceInfoBox) self.verticalLayout.setObjectName(u"verticalLayout") self.pluginLabel = QLabel(CanBusDeviceInfoBox) diff --git a/examples/serialbus/can/ui_canbusdeviceinfodialog.py b/examples/serialbus/can/ui_canbusdeviceinfodialog.py index c32e9ca2..808e8ae9 100644 --- a/examples/serialbus/can/ui_canbusdeviceinfodialog.py +++ b/examples/serialbus/can/ui_canbusdeviceinfodialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'canbusdeviceinfodialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/serialbus/can/ui_connectdialog.py b/examples/serialbus/can/ui_connectdialog.py index f01adc4e..d69f2876 100644 --- a/examples/serialbus/can/ui_connectdialog.py +++ b/examples/serialbus/can/ui_connectdialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'connectdialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -63,7 +63,7 @@ class Ui_ConnectDialog(object): self.ringBufferLimitBox.setMinimum(10) self.ringBufferLimitBox.setMaximum(10000000) self.ringBufferLimitBox.setSingleStep(10) - self.ringBufferLimitBox.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType) + self.ringBufferLimitBox.setStepType(QAbstractSpinBox.StepType.AdaptiveDecimalStepType) self.ringBufferLimitBox.setValue(1000) self.horizontalLayout_2.addWidget(self.ringBufferLimitBox) @@ -99,7 +99,7 @@ class Ui_ConnectDialog(object): self.rawFilterEdit = QLineEdit(self.configurationBox) self.rawFilterEdit.setObjectName(u"rawFilterEdit") - self.rawFilterEdit.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.rawFilterEdit.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout_4.addWidget(self.rawFilterEdit, 0, 1, 1, 1) @@ -110,7 +110,7 @@ class Ui_ConnectDialog(object): self.errorFilterEdit = QLineEdit(self.configurationBox) self.errorFilterEdit.setObjectName(u"errorFilterEdit") - self.errorFilterEdit.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.errorFilterEdit.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout_4.addWidget(self.errorFilterEdit, 1, 1, 1, 1) diff --git a/examples/serialbus/can/ui_mainwindow.py b/examples/serialbus/can/ui_mainwindow.py index 4ba4dd30..e08c093a 100644 --- a/examples/serialbus/can/ui_mainwindow.py +++ b/examples/serialbus/can/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -33,24 +33,24 @@ class Ui_MainWindow(object): self.actionConnect = QAction(MainWindow) self.actionConnect.setObjectName(u"actionConnect") icon = QIcon() - icon.addFile(u":/images/connect.png", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(u":/images/connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionConnect.setIcon(icon) self.actionDisconnect = QAction(MainWindow) self.actionDisconnect.setObjectName(u"actionDisconnect") icon1 = QIcon() - icon1.addFile(u":/images/disconnect.png", QSize(), QIcon.Normal, QIcon.Off) + icon1.addFile(u":/images/disconnect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionDisconnect.setIcon(icon1) self.actionQuit = QAction(MainWindow) self.actionQuit.setObjectName(u"actionQuit") icon2 = QIcon() - icon2.addFile(u":/images/application-exit.png", QSize(), QIcon.Normal, QIcon.Off) + icon2.addFile(u":/images/application-exit.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionQuit.setIcon(icon2) self.actionAboutQt = QAction(MainWindow) self.actionAboutQt.setObjectName(u"actionAboutQt") self.actionClearLog = QAction(MainWindow) self.actionClearLog.setObjectName(u"actionClearLog") icon3 = QIcon() - icon3.addFile(u":/images/clear.png", QSize(), QIcon.Normal, QIcon.Off) + icon3.addFile(u":/images/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionClearLog.setIcon(icon3) self.actionPluginDocumentation = QAction(MainWindow) self.actionPluginDocumentation.setObjectName(u"actionPluginDocumentation") @@ -80,10 +80,10 @@ class Ui_MainWindow(object): self.verticalLayout_2.setObjectName(u"verticalLayout_2") self.receivedFramesView = ReceivedFramesView(self.receivedMessagesBox) self.receivedFramesView.setObjectName(u"receivedFramesView") - self.receivedFramesView.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.receivedFramesView.setProperty("showDropIndicator", False) + self.receivedFramesView.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + self.receivedFramesView.setProperty(u"showDropIndicator", False) self.receivedFramesView.setDragDropOverwriteMode(False) - self.receivedFramesView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.receivedFramesView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.verticalLayout_2.addWidget(self.receivedFramesView) @@ -111,7 +111,7 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralWidget) self.menuBar = QMenuBar(MainWindow) self.menuBar.setObjectName(u"menuBar") - self.menuBar.setGeometry(QRect(0, 0, 634, 22)) + self.menuBar.setGeometry(QRect(0, 0, 634, 26)) self.menuCalls = QMenu(self.menuBar) self.menuCalls.setObjectName(u"menuCalls") self.menuHelp = QMenu(self.menuBar) diff --git a/examples/serialbus/can/ui_sendframebox.py b/examples/serialbus/can/ui_sendframebox.py index 79f013c8..204defec 100644 --- a/examples/serialbus/can/ui_sendframebox.py +++ b/examples/serialbus/can/ui_sendframebox.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'sendframebox.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -23,10 +23,10 @@ class Ui_SendFrameBox(object): def setupUi(self, SendFrameBox): if not SendFrameBox.objectName(): SendFrameBox.setObjectName(u"SendFrameBox") - SendFrameBox.resize(485, 206) + SendFrameBox.resize(505, 219) self.verticalLayout_4 = QVBoxLayout(SendFrameBox) self.verticalLayout_4.setObjectName(u"verticalLayout_4") - self.verticalLayout_4.setSizeConstraint(QLayout.SetMinimumSize) + self.verticalLayout_4.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) self.frameTypeBox = QGroupBox(SendFrameBox) self.frameTypeBox.setObjectName(u"frameTypeBox") self.frameTypeBox.setCheckable(False) @@ -78,7 +78,7 @@ class Ui_SendFrameBox(object): self.horizontalLayout_3 = QHBoxLayout() self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") - self.horizontalLayout_3.setSizeConstraint(QLayout.SetMinimumSize) + self.horizontalLayout_3.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) self.verticalLayout = QVBoxLayout() self.verticalLayout.setObjectName(u"verticalLayout") self.frameIdLabel = QLabel(SendFrameBox) diff --git a/examples/serialbus/modbus/modbusclient/main.py b/examples/serialbus/modbus/modbusclient/main.py index 1bad75c4..de3e946b 100644 --- a/examples/serialbus/modbus/modbusclient/main.py +++ b/examples/serialbus/modbus/modbusclient/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the examples/serialbus/modbus/client example from Qt v6.x""" diff --git a/examples/serialbus/modbus/modbusclient/mainwindow.py b/examples/serialbus/modbus/modbusclient/mainwindow.py index 02f9d478..97de08d1 100644 --- a/examples/serialbus/modbus/modbusclient/mainwindow.py +++ b/examples/serialbus/modbus/modbusclient/mainwindow.py @@ -1,14 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations -import functools from enum import IntEnum from PySide6.QtCore import QUrl, Slot from PySide6.QtGui import QStandardItemModel, QStandardItem from PySide6.QtWidgets import QMainWindow -from PySide6.QtSerialBus import (QModbusDataUnit, - QModbusDevice, QModbusReply, +from PySide6.QtSerialBus import (QModbusDataUnit, QModbusDevice, QModbusRtuSerialClient, QModbusTcpClient) from ui_mainwindow import Ui_MainWindow @@ -43,10 +42,11 @@ class MainWindow(QMainWindow): vp = self.ui.writeValueTable.viewport() self._write_model.update_viewport.connect(vp.update) - self.ui.writeTable.addItem("Coils", QModbusDataUnit.Coils) - self.ui.writeTable.addItem("Discrete Inputs", QModbusDataUnit.DiscreteInputs) - self.ui.writeTable.addItem("Input Registers", QModbusDataUnit.InputRegisters) - self.ui.writeTable.addItem("Holding Registers", QModbusDataUnit.HoldingRegisters) + self.ui.writeTable.addItem("Coils", QModbusDataUnit.RegisterType.Coils) + self.ui.writeTable.addItem("Discrete Inputs", QModbusDataUnit.RegisterType.DiscreteInputs) + self.ui.writeTable.addItem("Input Registers", QModbusDataUnit.RegisterType.InputRegisters) + self.ui.writeTable.addItem("Holding Registers", + QModbusDataUnit.RegisterType.HoldingRegisters) self.ui.connectType.setCurrentIndex(0) self.onConnectTypeChanged(0) @@ -111,7 +111,7 @@ class MainWindow(QMainWindow): elif index == ModbusConnection.TCP: self._modbus_device = QModbusTcpClient(self) if not self.ui.portEdit.text(): - self.ui.portEdit.setText("127.0.0.1:502") + self.ui.portEdit.setText("127.0.0.1:50200") self._modbus_device.errorOccurred.connect(self._show_device_errorstring) @@ -133,24 +133,24 @@ class MainWindow(QMainWindow): self.statusBar().clearMessage() md = self._modbus_device - if md.state() != QModbusDevice.ConnectedState: + if md.state() != QModbusDevice.State.ConnectedState: settings = self._settings_dialog.settings() if self.ui.connectType.currentIndex() == ModbusConnection.SERIAL: - md.setConnectionParameter(QModbusDevice.SerialPortNameParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.SerialPortNameParameter, self.ui.portEdit.text()) - md.setConnectionParameter(QModbusDevice.SerialParityParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.SerialParityParameter, settings.parity) - md.setConnectionParameter(QModbusDevice.SerialBaudRateParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.SerialBaudRateParameter, settings.baud) - md.setConnectionParameter(QModbusDevice.SerialDataBitsParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.SerialDataBitsParameter, settings.data_bits) - md.setConnectionParameter(QModbusDevice.SerialStopBitsParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.SerialStopBitsParameter, settings.stop_bits) else: url = QUrl.fromUserInput(self.ui.portEdit.text()) - md.setConnectionParameter(QModbusDevice.NetworkPortParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.NetworkPortParameter, url.port()) - md.setConnectionParameter(QModbusDevice.NetworkAddressParameter, + md.setConnectionParameter(QModbusDevice.ConnectionParameter.NetworkAddressParameter, url.host()) md.setTimeout(settings.response_time) @@ -169,13 +169,13 @@ class MainWindow(QMainWindow): @Slot(int) def onModbusStateChanged(self, state): - connected = (state != QModbusDevice.UnconnectedState) + connected = (state != QModbusDevice.State.UnconnectedState) self.ui.actionConnect.setEnabled(not connected) self.ui.actionDisconnect.setEnabled(connected) - if state == QModbusDevice.UnconnectedState: + if state == QModbusDevice.State.UnconnectedState: self.ui.connectButton.setText("Connect") - elif state == QModbusDevice.ConnectedState: + elif state == QModbusDevice.State.ConnectedState: self.ui.connectButton.setText("Disconnect") @Slot() @@ -188,7 +188,7 @@ class MainWindow(QMainWindow): self.ui.serverEdit.value()) if reply: if not reply.isFinished(): - reply.finished.connect(functools.partial(self.onReadReady, reply)) + reply.finished.connect(self.onReadReady) else: del reply # broadcast replies return immediately else: @@ -196,7 +196,8 @@ class MainWindow(QMainWindow): self.statusBar().showMessage(message, 5000) @Slot() - def onReadReady(self, reply): + def onReadReady(self): + reply = self.sender() if not reply: return @@ -236,7 +237,7 @@ class MainWindow(QMainWindow): table = write_unit.registerType() for i in range(0, total): addr = i + write_unit.startAddress() - if table == QModbusDataUnit.Coils: + if table == QModbusDataUnit.RegisterType.Coils: write_unit.setValue(i, self._write_model.m_coils[addr]) else: write_unit.setValue(i, self._write_model.m_holdingRegisters[addr]) @@ -248,13 +249,16 @@ class MainWindow(QMainWindow): # broadcast replies return immediately reply.deleteLater() else: - reply.finished.connect(functools.partial(self._write_finished, reply)) + reply.finished.connect(self._write_finished) else: message = "Write error: " + self._modbus_device.errorString() self.statusBar().showMessage(message, 5000) - @Slot(QModbusReply) - def _write_finished(self, reply): + @Slot() + def _write_finished(self): + reply = self.sender() + if not reply: + return error = reply.error() if error == QModbusDevice.ProtocolError: e = reply.errorString() @@ -289,7 +293,7 @@ class MainWindow(QMainWindow): self.ui.serverEdit.value()) if reply: if not reply.isFinished(): - reply.finished.connect(functools.partial(self.onReadReady, reply)) + reply.finished.connect(self.onReadReady) else: del reply # broadcast replies return immediately else: diff --git a/examples/serialbus/modbus/modbusclient/mainwindow.ui b/examples/serialbus/modbus/modbusclient/mainwindow.ui index 1de38197..0681b99e 100644 --- a/examples/serialbus/modbus/modbusclient/mainwindow.ui +++ b/examples/serialbus/modbus/modbusclient/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 516 + 601 378 @@ -61,7 +61,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -387,7 +387,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -437,8 +437,8 @@ 0 0 - 516 - 21 + 601 + 26 diff --git a/examples/serialbus/modbus/modbusclient/settingsdialog.py b/examples/serialbus/modbus/modbusclient/settingsdialog.py index a5e4a37c..d86ea05d 100644 --- a/examples/serialbus/modbus/modbusclient/settingsdialog.py +++ b/examples/serialbus/modbus/modbusclient/settingsdialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtWidgets import QDialog @@ -10,10 +11,10 @@ from ui_settingsdialog import Ui_SettingsDialog class Settings: def __init__(self): - self.parity = QSerialPort.EvenParity - self.baud = QSerialPort.Baud19200 - self.data_bits = QSerialPort.Data8 - self.stop_bits = QSerialPort.OneStop + self.parity = QSerialPort.Parity.EvenParity + self.baud = QSerialPort.BaudRate.Baud19200 + self.data_bits = QSerialPort.DataBits.Data8 + self.stop_bits = QSerialPort.StopBits.OneStop self.response_time = 1000 self.number_of_retries = 3 diff --git a/examples/serialbus/modbus/modbusclient/settingsdialog.ui b/examples/serialbus/modbus/modbusclient/settingsdialog.ui index fb594f4d..68122a1e 100644 --- a/examples/serialbus/modbus/modbusclient/settingsdialog.ui +++ b/examples/serialbus/modbus/modbusclient/settingsdialog.ui @@ -6,8 +6,8 @@ 0 0 - 239 - 256 + 259 + 321 @@ -17,7 +17,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py index 1535fc8d..a23e5fc6 100644 --- a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py +++ b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -28,27 +28,27 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(516, 378) + MainWindow.resize(601, 378) MainWindow.setMaximumSize(QSize(16777215, 1000)) self.actionConnect = QAction(MainWindow) self.actionConnect.setObjectName(u"actionConnect") icon = QIcon() - icon.addFile(u":/images/connect.png", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(u":/images/connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionConnect.setIcon(icon) self.actionDisconnect = QAction(MainWindow) self.actionDisconnect.setObjectName(u"actionDisconnect") icon1 = QIcon() - icon1.addFile(u":/images/disconnect.png", QSize(), QIcon.Normal, QIcon.Off) + icon1.addFile(u":/images/disconnect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionDisconnect.setIcon(icon1) self.actionExit = QAction(MainWindow) self.actionExit.setObjectName(u"actionExit") icon2 = QIcon() - icon2.addFile(u":/images/application-exit.png", QSize(), QIcon.Normal, QIcon.Off) + icon2.addFile(u":/images/application-exit.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionExit.setIcon(icon2) self.actionOptions = QAction(MainWindow) self.actionOptions.setObjectName(u"actionOptions") icon3 = QIcon() - icon3.addFile(u":/images/settings.png", QSize(), QIcon.Normal, QIcon.Off) + icon3.addFile(u":/images/settings.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionOptions.setIcon(icon3) self.centralWidget = QWidget(MainWindow) self.centralWidget.setObjectName(u"centralWidget") @@ -195,7 +195,7 @@ class Ui_MainWindow(object): self.writeValueTable = QTreeView(self.writeGroupBox) self.writeValueTable.setObjectName(u"writeValueTable") - self.writeValueTable.setProperty("showDropIndicator", True) + self.writeValueTable.setProperty(u"showDropIndicator", True) self.writeValueTable.setAlternatingRowColors(True) self.writeValueTable.setRootIsDecorated(False) self.writeValueTable.setUniformRowHeights(True) @@ -286,7 +286,7 @@ class Ui_MainWindow(object): MainWindow.setStatusBar(self.statusBar) self.menuBar = QMenuBar(MainWindow) self.menuBar.setObjectName(u"menuBar") - self.menuBar.setGeometry(QRect(0, 0, 516, 21)) + self.menuBar.setGeometry(QRect(0, 0, 601, 26)) self.menuDevice = QMenu(self.menuBar) self.menuDevice.setObjectName(u"menuDevice") self.menuToo_ls = QMenu(self.menuBar) diff --git a/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py b/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py index af3a0665..0dfda7a0 100644 --- a/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py +++ b/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'settingsdialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -23,7 +23,7 @@ class Ui_SettingsDialog(object): def setupUi(self, SettingsDialog): if not SettingsDialog.objectName(): SettingsDialog.setObjectName(u"SettingsDialog") - SettingsDialog.resize(239, 256) + SettingsDialog.resize(259, 321) self.gridLayout = QGridLayout(SettingsDialog) self.gridLayout.setObjectName(u"gridLayout") self.verticalSpacer = QSpacerItem(20, 43, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) diff --git a/examples/serialbus/modbus/modbusclient/writeregistermodel.py b/examples/serialbus/modbus/modbusclient/writeregistermodel.py index 07b1226d..17b61bca 100644 --- a/examples/serialbus/modbus/modbusclient/writeregistermodel.py +++ b/examples/serialbus/modbus/modbusclient/writeregistermodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import IntEnum @@ -40,23 +41,23 @@ class WriteRegisterModel(QAbstractTableModel): assert self.m_coils.size() == Column.ROW_COUNT assert len(self.m_holdingRegisters) == Column.ROW_COUNT - if column == Column.NUM_COLUMN and role == Qt.DisplayRole: + if column == Column.NUM_COLUMN and role == Qt.ItemDataRole.DisplayRole: return f"{row}" - if column == Column.COILS_COLUMN and role == Qt.CheckStateRole: # coils - return Qt.Checked if self.m_coils[row] else Qt.Unchecked + if column == Column.COILS_COLUMN and role == Qt.ItemDataRole.CheckStateRole: # coils + return Qt.CheckState.Checked if self.m_coils[row] else Qt.CheckState.Unchecked # holding registers - if column == Column.HOLDING_COLUMN and role == Qt.DisplayRole: + if column == Column.HOLDING_COLUMN and role == Qt.ItemDataRole.DisplayRole: reg = self.m_holdingRegisters[row] return f"0x{reg:x}" return None def headerData(self, section, orientation, role): - if role != Qt.DisplayRole: + if role != Qt.ItemDataRole.DisplayRole: return None - if orientation == Qt.Horizontal: + if orientation == Qt.Orientation.Horizontal: if section == Column.NUM_COLUMN: return "#" if section == Column.COILS_COLUMN: @@ -74,16 +75,17 @@ class WriteRegisterModel(QAbstractTableModel): assert self.m_coils.size() == Column.ROW_COUNT assert len(self.m_holdingRegisters) == Column.ROW_COUNT - if column == Column.COILS_COLUMN and role == Qt.CheckStateRole: # coils + if column == Column.COILS_COLUMN and role == Qt.ItemDataRole.CheckStateRole: # coils s = Qt.CheckState(int(value)) - if s == Qt.Checked: + if s == Qt.CheckState.Checked: self.m_coils.setBit(row) else: self.m_coils.clearBit(row) self.dataChanged.emit(index, index) return True - if column == Column.HOLDING_COLUMN and role == Qt.EditRole: # holding registers + if column == Column.HOLDING_COLUMN and role == Qt.ItemDataRole.EditRole: + # holding registers base = 16 if value.startswith("0x") else 10 self.m_holdingRegisters[row] = int(value, base=base) self.dataChanged.emit(index, index) @@ -99,12 +101,12 @@ class WriteRegisterModel(QAbstractTableModel): return flags if row < self.m_address or row >= (self.m_address + self.m_number): - flags &= ~Qt.ItemIsEnabled + flags &= ~Qt.ItemFlag.ItemIsEnabled if column == Column.COILS_COLUMN: # coils - return flags | Qt.ItemIsUserCheckable + return flags | Qt.ItemFlag.ItemIsUserCheckable if column == Column.HOLDING_COLUMN: # holding registers - return flags | Qt.ItemIsEditable + return flags | Qt.ItemFlag.ItemIsEditable return flags @Slot(int) diff --git a/examples/serialport/terminal/console.py b/examples/serialport/terminal/console.py index 12595137..682711f4 100644 --- a/examples/serialport/terminal/console.py +++ b/examples/serialport/terminal/console.py @@ -1,13 +1,14 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt, Signal, Slot from PySide6.QtGui import QPalette from PySide6.QtWidgets import QPlainTextEdit -UNHANDLED_KEYS = [Qt.Key_Backspace, Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, - Qt.Key_Down] +UNHANDLED_KEYS = [Qt.Key.Key_Backspace, Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, + Qt.Key.Key_Down] class Console(QPlainTextEdit): @@ -19,8 +20,8 @@ class Console(QPlainTextEdit): self.m_localEchoEnabled = False self.document().setMaximumBlockCount(100) p = self.palette() - p.setColor(QPalette.Base, Qt.black) - p.setColor(QPalette.Text, Qt.green) + p.setColor(QPalette.ColorRole.Base, Qt.GlobalColor.black) + p.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.green) self.setPalette(p) @Slot(bytearray) diff --git a/examples/serialport/terminal/main.py b/examples/serialport/terminal/main.py index 0d2ce0a0..feff5da1 100644 --- a/examples/serialport/terminal/main.py +++ b/examples/serialport/terminal/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/serialport/terminal/mainwindow.py b/examples/serialport/terminal/mainwindow.py index bdfb0fb0..557821a7 100644 --- a/examples/serialport/terminal/mainwindow.py +++ b/examples/serialport/terminal/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QIODeviceBase, Slot from PySide6.QtWidgets import QLabel, QMainWindow, QMessageBox @@ -62,7 +63,7 @@ class MainWindow(QMainWindow): self.m_serial.setParity(s.parity) self.m_serial.setStopBits(s.stop_bits) self.m_serial.setFlowControl(s.flow_control) - if self.m_serial.open(QIODeviceBase.ReadWrite): + if self.m_serial.open(QIODeviceBase.OpenModeFlag.ReadWrite): self.m_console.setEnabled(True) self.m_console.set_local_echo_enabled(s.local_echo_enabled) self.m_ui.actionConnect.setEnabled(False) @@ -98,7 +99,7 @@ class MainWindow(QMainWindow): @Slot(QSerialPort.SerialPortError) def handle_error(self, error): - if error == QSerialPort.ResourceError: + if error == QSerialPort.SerialPortError.ResourceError: QMessageBox.critical(self, "Critical Error", self.m_serial.errorString()) self.close_serial_port() diff --git a/examples/serialport/terminal/mainwindow.ui b/examples/serialport/terminal/mainwindow.ui index 452fdd53..858736e3 100644 --- a/examples/serialport/terminal/mainwindow.ui +++ b/examples/serialport/terminal/mainwindow.ui @@ -22,7 +22,7 @@ 0 0 400 - 19 + 26 diff --git a/examples/serialport/terminal/settingsdialog.py b/examples/serialport/terminal/settingsdialog.py index c9373d5b..e950c81c 100644 --- a/examples/serialport/terminal/settingsdialog.py +++ b/examples/serialport/terminal/settingsdialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -25,13 +26,13 @@ class Settings(): self.name = "" self.baud_rate = 0 self.string_baud_rate = "" - self.data_bits = QSerialPort.Data8 + self.data_bits = QSerialPort.DataBits.Data8 self.string_data_bits = "" - self.parity = QSerialPort.NoParity + self.parity = QSerialPort.Parity.NoParity self.string_parity = "" - self.stop_bits = QSerialPort.OneStop + self.stop_bits = QSerialPort.StopBits.OneStop self.string_stop_bits = "" - self.flow_control = QSerialPort.SoftwareControl + self.flow_control = QSerialPort.FlowControl.SoftwareControl self.string_flow_control = "" self.local_echo_enabled = False @@ -47,7 +48,7 @@ class SettingsDialog(QDialog): self.m_currentSettings = Settings() self.m_intValidator = QIntValidator(0, 4000000, self) - self.m_ui.baudRateBox.setInsertPolicy(QComboBox.NoInsert) + self.m_ui.baudRateBox.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) self.m_ui.applyButton.clicked.connect(self.apply) self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(self.show_port_info) @@ -105,33 +106,33 @@ class SettingsDialog(QDialog): self.m_ui.serialPortInfoListBox.clearEditText() def fill_ports_parameters(self): - self.m_ui.baudRateBox.addItem("9600", QSerialPort.Baud9600) - self.m_ui.baudRateBox.addItem("19200", QSerialPort.Baud19200) - self.m_ui.baudRateBox.addItem("38400", QSerialPort.Baud38400) - self.m_ui.baudRateBox.addItem("115200", QSerialPort.Baud115200) + self.m_ui.baudRateBox.addItem("9600", QSerialPort.BaudRate.Baud9600) + self.m_ui.baudRateBox.addItem("19200", QSerialPort.BaudRate.Baud19200) + self.m_ui.baudRateBox.addItem("38400", QSerialPort.BaudRate.Baud38400) + self.m_ui.baudRateBox.addItem("115200", QSerialPort.BaudRate.Baud115200) self.m_ui.baudRateBox.addItem("Custom") - self.m_ui.dataBitsBox.addItem("5", QSerialPort.Data5) - self.m_ui.dataBitsBox.addItem("6", QSerialPort.Data6) - self.m_ui.dataBitsBox.addItem("7", QSerialPort.Data7) - self.m_ui.dataBitsBox.addItem("8", QSerialPort.Data8) + self.m_ui.dataBitsBox.addItem("5", QSerialPort.DataBits.Data5) + self.m_ui.dataBitsBox.addItem("6", QSerialPort.DataBits.Data6) + self.m_ui.dataBitsBox.addItem("7", QSerialPort.DataBits.Data7) + self.m_ui.dataBitsBox.addItem("8", QSerialPort.DataBits.Data8) self.m_ui.dataBitsBox.setCurrentIndex(3) - self.m_ui.parityBox.addItem("None", QSerialPort.NoParity) - self.m_ui.parityBox.addItem("Even", QSerialPort.EvenParity) - self.m_ui.parityBox.addItem("Odd", QSerialPort.OddParity) - self.m_ui.parityBox.addItem("Mark", QSerialPort.MarkParity) - self.m_ui.parityBox.addItem("Space", QSerialPort.SpaceParity) + self.m_ui.parityBox.addItem("None", QSerialPort.Parity.NoParity) + self.m_ui.parityBox.addItem("Even", QSerialPort.Parity.EvenParity) + self.m_ui.parityBox.addItem("Odd", QSerialPort.Parity.OddParity) + self.m_ui.parityBox.addItem("Mark", QSerialPort.Parity.MarkParity) + self.m_ui.parityBox.addItem("Space", QSerialPort.Parity.SpaceParity) - self.m_ui.stopBitsBox.addItem("1", QSerialPort.OneStop) + self.m_ui.stopBitsBox.addItem("1", QSerialPort.StopBits.OneStop) if sys.platform == "win32": - self.m_ui.stopBitsBox.addItem("1.5", QSerialPort.OneAndHalfStop) + self.m_ui.stopBitsBox.addItem("1.5", QSerialPort.StopBits.OneAndHalfStop) - self.m_ui.stopBitsBox.addItem("2", QSerialPort.TwoStop) + self.m_ui.stopBitsBox.addItem("2", QSerialPort.StopBits.TwoStop) - self.m_ui.flowControlBox.addItem("None", QSerialPort.NoFlowControl) - self.m_ui.flowControlBox.addItem("RTS/CTS", QSerialPort.HardwareControl) - self.m_ui.flowControlBox.addItem("XON/XOFF", QSerialPort.SoftwareControl) + self.m_ui.flowControlBox.addItem("None", QSerialPort.FlowControl.NoFlowControl) + self.m_ui.flowControlBox.addItem("RTS/CTS", QSerialPort.FlowControl.HardwareControl) + self.m_ui.flowControlBox.addItem("XON/XOFF", QSerialPort.FlowControl.SoftwareControl) def fill_ports_info(self): self.m_ui.serialPortInfoListBox.clear() diff --git a/examples/serialport/terminal/settingsdialog.ui b/examples/serialport/terminal/settingsdialog.ui index 8f15b1a5..eedd4ccc 100644 --- a/examples/serialport/terminal/settingsdialog.ui +++ b/examples/serialport/terminal/settingsdialog.ui @@ -6,8 +6,8 @@ 0 0 - 281 - 262 + 392 + 386 @@ -132,7 +132,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/examples/serialport/terminal/ui_mainwindow.py b/examples/serialport/terminal/ui_mainwindow.py index 21adbd5c..e9f7cd97 100644 --- a/examples/serialport/terminal/ui_mainwindow.py +++ b/examples/serialport/terminal/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -33,27 +33,27 @@ class Ui_MainWindow(object): self.actionConnect = QAction(MainWindow) self.actionConnect.setObjectName(u"actionConnect") icon = QIcon() - icon.addFile(u":/images/connect.png", QSize(), QIcon.Normal, QIcon.Off) + icon.addFile(u":/images/connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionConnect.setIcon(icon) self.actionDisconnect = QAction(MainWindow) self.actionDisconnect.setObjectName(u"actionDisconnect") icon1 = QIcon() - icon1.addFile(u":/images/disconnect.png", QSize(), QIcon.Normal, QIcon.Off) + icon1.addFile(u":/images/disconnect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionDisconnect.setIcon(icon1) self.actionConfigure = QAction(MainWindow) self.actionConfigure.setObjectName(u"actionConfigure") icon2 = QIcon() - icon2.addFile(u":/images/settings.png", QSize(), QIcon.Normal, QIcon.Off) + icon2.addFile(u":/images/settings.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionConfigure.setIcon(icon2) self.actionClear = QAction(MainWindow) self.actionClear.setObjectName(u"actionClear") icon3 = QIcon() - icon3.addFile(u":/images/clear.png", QSize(), QIcon.Normal, QIcon.Off) + icon3.addFile(u":/images/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionClear.setIcon(icon3) self.actionQuit = QAction(MainWindow) self.actionQuit.setObjectName(u"actionQuit") icon4 = QIcon() - icon4.addFile(u":/images/application-exit.png", QSize(), QIcon.Normal, QIcon.Off) + icon4.addFile(u":/images/application-exit.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) self.actionQuit.setIcon(icon4) self.centralWidget = QWidget(MainWindow) self.centralWidget.setObjectName(u"centralWidget") @@ -64,7 +64,7 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralWidget) self.menuBar = QMenuBar(MainWindow) self.menuBar.setObjectName(u"menuBar") - self.menuBar.setGeometry(QRect(0, 0, 400, 19)) + self.menuBar.setGeometry(QRect(0, 0, 400, 26)) self.menuCalls = QMenu(self.menuBar) self.menuCalls.setObjectName(u"menuCalls") self.menuTools = QMenu(self.menuBar) diff --git a/examples/serialport/terminal/ui_settingsdialog.py b/examples/serialport/terminal/ui_settingsdialog.py index 6c5a417c..4429322b 100644 --- a/examples/serialport/terminal/ui_settingsdialog.py +++ b/examples/serialport/terminal/ui_settingsdialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'settingsdialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -24,7 +24,7 @@ class Ui_SettingsDialog(object): def setupUi(self, SettingsDialog): if not SettingsDialog.objectName(): SettingsDialog.setObjectName(u"SettingsDialog") - SettingsDialog.resize(281, 262) + SettingsDialog.resize(392, 386) self.gridLayout_3 = QGridLayout(SettingsDialog) self.gridLayout_3.setObjectName(u"gridLayout_3") self.parametersBox = QGroupBox(SettingsDialog) diff --git a/examples/spatialaudio/audiopanning/main.py b/examples/spatialaudio/audiopanning/main.py index 19b6c3bb..558451e8 100644 --- a/examples/spatialaudio/audiopanning/main.py +++ b/examples/spatialaudio/audiopanning/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math import sys @@ -34,42 +35,42 @@ class AudioWidget(QWidget): file_layout.addWidget(self._file_dialog_button) form.addRow(file_layout) - self._azimuth = QSlider(Qt.Horizontal) + self._azimuth = QSlider(Qt.Orientation.Horizontal) self._azimuth.setRange(-180, 180) form.addRow("Azimuth (-180 - 180 degree):", self._azimuth) - self._elevation = QSlider(Qt.Horizontal) + self._elevation = QSlider(Qt.Orientation.Horizontal) self._elevation.setRange(-90, 90) form.addRow("Elevation (-90 - 90 degree)", self._elevation) - self._distance = QSlider(Qt.Horizontal) + self._distance = QSlider(Qt.Orientation.Horizontal) self._distance.setRange(0, 1000) self._distance.setValue(100) form.addRow("Distance (0 - 10 meter):", self._distance) - self._occlusion = QSlider(Qt.Horizontal) + self._occlusion = QSlider(Qt.Orientation.Horizontal) self._occlusion.setRange(0, 400) form.addRow("Occlusion (0 - 4):", self._occlusion) - self._room_dimension = QSlider(Qt.Horizontal) + self._room_dimension = QSlider(Qt.Orientation.Horizontal) self._room_dimension.setRange(0, 10000) self._room_dimension.setValue(1000) form.addRow("Room dimension (0 - 100 meter):", self._room_dimension) - self._reverb_gain = QSlider(Qt.Horizontal) + self._reverb_gain = QSlider(Qt.Orientation.Horizontal) self._reverb_gain.setRange(0, 500) self._reverb_gain.setValue(0) form.addRow("Reverb gain (0-5):", self._reverb_gain) - self._reflection_gain = QSlider(Qt.Horizontal) + self._reflection_gain = QSlider(Qt.Orientation.Horizontal) self._reflection_gain.setRange(0, 500) self._reflection_gain.setValue(0) form.addRow("Reflection gain (0-5):", self._reflection_gain) self._mode = QComboBox() - self._mode.addItem("Surround", QAudioEngine.Surround) - self._mode.addItem("Stereo", QAudioEngine.Stereo) - self._mode.addItem("Headphone", QAudioEngine.Headphone) + self._mode.addItem("Surround", QAudioEngine.OutputMode.Surround) + self._mode.addItem("Stereo", QAudioEngine.OutputMode.Stereo) + self._mode.addItem("Headphone", QAudioEngine.OutputMode.Headphone) form.addRow("Output mode:", self._mode) @@ -92,12 +93,12 @@ class AudioWidget(QWidget): self._engine = QAudioEngine() self._room = QAudioRoom(self._engine) - self._room.setWallMaterial(QAudioRoom.BackWall, QAudioRoom.BrickBare) - self._room.setWallMaterial(QAudioRoom.FrontWall, QAudioRoom.BrickBare) - self._room.setWallMaterial(QAudioRoom.LeftWall, QAudioRoom.BrickBare) - self._room.setWallMaterial(QAudioRoom.RightWall, QAudioRoom.BrickBare) - self._room.setWallMaterial(QAudioRoom.Floor, QAudioRoom.Marble) - self._room.setWallMaterial(QAudioRoom.Ceiling, QAudioRoom.WoodCeiling) + self._room.setWallMaterial(QAudioRoom.Wall.BackWall, QAudioRoom.Material.BrickBare) + self._room.setWallMaterial(QAudioRoom.Wall.FrontWall, QAudioRoom.Material.BrickBare) + self._room.setWallMaterial(QAudioRoom.Wall.LeftWall, QAudioRoom.Material.BrickBare) + self._room.setWallMaterial(QAudioRoom.Wall.RightWall, QAudioRoom.Material.BrickBare) + self._room.setWallMaterial(QAudioRoom.Wall.Floor, QAudioRoom.Material.Marble) + self._room.setWallMaterial(QAudioRoom.Wall.Ceiling, QAudioRoom.Material.WoodCeiling) self.update_room() self._listener = QAudioListener(self._engine) @@ -140,20 +141,20 @@ class AudioWidget(QWidget): def file_changed(self, file): self._sound.setSource(QUrl.fromLocalFile(file)) self._sound.setSize(5) - self._sound.setLoops(QSpatialSound.Infinite) + self._sound.setLoops(QSpatialSound.Loops.Infinite) @Slot() def open_file_dialog(self): if not self._file_dialog: - directory = QStandardPaths.writableLocation(QStandardPaths.MusicLocation) + directory = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.MusicLocation) # noqa: E501 self._file_dialog = QFileDialog(self, "Open Audio File", directory) - self._file_dialog.setAcceptMode(QFileDialog.AcceptOpen) + self._file_dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) mime_types = ["audio/mpeg", "audio/aac", "audio/x-ms-wma", "audio/x-flac+ogg", "audio/x-wav"] self._file_dialog.setMimeTypeFilters(mime_types) self._file_dialog.selectMimeTypeFilter(mime_types[0]) - if self._file_dialog.exec() == QDialog.Accepted: + if self._file_dialog.exec() == QDialog.DialogCode.Accepted: self._file_edit.setText(self._file_dialog.selectedFiles()[0]) @Slot() diff --git a/examples/speech/hello_speak/doc/hello_speak.rst b/examples/speech/hello_speak/doc/hello_speak.rst index b7c17c35..8a2fa88b 100644 --- a/examples/speech/hello_speak/doc/hello_speak.rst +++ b/examples/speech/hello_speak/doc/hello_speak.rst @@ -13,4 +13,3 @@ voice. .. image:: hello_speak.webp :width: 400 :alt: Hello Speak Screenshot - diff --git a/examples/speech/hello_speak/main.py b/examples/speech/hello_speak/main.py index b025dd6c..309e7ef1 100644 --- a/examples/speech/hello_speak/main.py +++ b/examples/speech/hello_speak/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the hello_speak example from Qt v6.x""" diff --git a/examples/speech/hello_speak/mainwindow.py b/examples/speech/hello_speak/mainwindow.py index 7790fec8..cb46b55f 100644 --- a/examples/speech/hello_speak/mainwindow.py +++ b/examples/speech/hello_speak/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QLocale, QSignalBlocker, Slot from PySide6.QtWidgets import QMainWindow diff --git a/examples/speech/hello_speak/mainwindow.ui b/examples/speech/hello_speak/mainwindow.ui index 6f3accf6..6c328bcc 100644 --- a/examples/speech/hello_speak/mainwindow.ui +++ b/examples/speech/hello_speak/mainwindow.ui @@ -46,7 +46,7 @@ Done, over and out. Engine - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -62,7 +62,7 @@ Done, over and out. Pitch: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -78,7 +78,7 @@ Done, over and out. &Language: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter language @@ -97,7 +97,7 @@ Done, over and out. 1 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -107,7 +107,7 @@ Done, over and out. Voice name: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -126,7 +126,7 @@ Done, over and out. 70 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -155,7 +155,7 @@ Done, over and out. Rate: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -171,7 +171,7 @@ Done, over and out. Volume: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -184,7 +184,7 @@ Done, over and out. 10 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -241,7 +241,7 @@ Done, over and out. - Qt::Vertical + Qt::Orientation::Vertical diff --git a/examples/speech/hello_speak/ui_mainwindow.py b/examples/speech/hello_speak/ui_mainwindow.py index ab132e7f..c7c4cb62 100644 --- a/examples/speech/hello_speak/ui_mainwindow.py +++ b/examples/speech/hello_speak/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -48,7 +48,7 @@ class Ui_MainWindow(object): sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) self.label_5.setSizePolicy(sizePolicy1) - self.label_5.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.label_5.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1) @@ -56,7 +56,7 @@ class Ui_MainWindow(object): self.label_3.setObjectName(u"label_3") sizePolicy1.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) self.label_3.setSizePolicy(sizePolicy1) - self.label_3.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.label_3.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1) @@ -64,7 +64,7 @@ class Ui_MainWindow(object): self.label_4.setObjectName(u"label_4") sizePolicy1.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) self.label_4.setSizePolicy(sizePolicy1) - self.label_4.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.label_4.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout.addWidget(self.label_4, 5, 0, 1, 1) @@ -73,13 +73,13 @@ class Ui_MainWindow(object): self.pitch.setMinimum(-10) self.pitch.setMaximum(10) self.pitch.setSingleStep(1) - self.pitch.setOrientation(Qt.Horizontal) + self.pitch.setOrientation(Qt.Orientation.Horizontal) self.gridLayout.addWidget(self.pitch, 3, 2, 1, 1) self.label_6 = QLabel(self.centralwidget) self.label_6.setObjectName(u"label_6") - self.label_6.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.label_6.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout.addWidget(self.label_6, 6, 0, 1, 1) @@ -89,7 +89,7 @@ class Ui_MainWindow(object): self.volume.setSingleStep(5) self.volume.setPageStep(20) self.volume.setValue(70) - self.volume.setOrientation(Qt.Horizontal) + self.volume.setOrientation(Qt.Orientation.Horizontal) self.gridLayout.addWidget(self.volume, 1, 2, 1, 1) @@ -112,7 +112,7 @@ class Ui_MainWindow(object): self.label.setObjectName(u"label") sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) self.label.setSizePolicy(sizePolicy1) - self.label.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.label.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout.addWidget(self.label, 2, 0, 1, 1) @@ -120,7 +120,7 @@ class Ui_MainWindow(object): self.label_2.setObjectName(u"label_2") sizePolicy1.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) self.label_2.setSizePolicy(sizePolicy1) - self.label_2.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.label_2.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignTrailing|Qt.AlignmentFlag.AlignVCenter) self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) @@ -128,7 +128,7 @@ class Ui_MainWindow(object): self.rate.setObjectName(u"rate") self.rate.setMinimum(-10) self.rate.setMaximum(10) - self.rate.setOrientation(Qt.Horizontal) + self.rate.setOrientation(Qt.Orientation.Horizontal) self.gridLayout.addWidget(self.rate, 2, 2, 1, 1) diff --git a/examples/sql/books/bookdelegate.py b/examples/sql/books/bookdelegate.py index 78295adf..897d0a63 100644 --- a/examples/sql/books/bookdelegate.py +++ b/examples/sql/books/bookdelegate.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import copy from PySide6.QtSql import QSqlRelationalDelegate @@ -45,7 +46,7 @@ class BookDelegate(QSqlRelationalDelegate): if option.state & QStyle.State_Selected: painter.fillRect(option.rect, option.palette.color(color_group, QPalette.Highlight)) - rating = model.data(index, Qt.DisplayRole) + rating = model.data(index, Qt.ItemDataRole.DisplayRole) width = self.star.width() height = self.star.height() x = option.rect.x() diff --git a/examples/sql/books/bookwindow.py b/examples/sql/books/bookwindow.py index 7f9e0f94..e913a228 100644 --- a/examples/sql/books/bookwindow.py +++ b/examples/sql/books/bookwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper, QHeaderView, QMainWindow, QMessageBox) @@ -34,11 +35,12 @@ class BookWindow(QMainWindow, Ui_BookWindow): 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")) + model.setHeaderData(author_idx, Qt.Orientation.Horizontal, self.tr("Author Name")) + model.setHeaderData(genre_idx, Qt.Orientation.Horizontal, self.tr("Genre")) + model.setHeaderData(model.fieldIndex("title"), Qt.Orientation.Horizontal, self.tr("Title")) + model.setHeaderData(model.fieldIndex("year"), Qt.Orientation.Horizontal, self.tr("Year")) + model.setHeaderData(model.fieldIndex("rating"), + Qt.Orientation.Horizontal, self.tr("Rating")) if not model.select(): print(model.lastError()) diff --git a/examples/sql/books/bookwindow.ui b/examples/sql/books/bookwindow.ui index ce8f9f93..ecd458ab 100644 --- a/examples/sql/books/bookwindow.ui +++ b/examples/sql/books/bookwindow.ui @@ -54,7 +54,7 @@ - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows diff --git a/examples/sql/books/createdb.py b/examples/sql/books/createdb.py index 5ee0f148..471148c6 100644 --- a/examples/sql/books/createdb.py +++ b/examples/sql/books/createdb.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtSql import QSqlDatabase, QSqlQuery from datetime import date diff --git a/examples/sql/books/main.py b/examples/sql/books/main.py index 025b5588..4b4aee1a 100644 --- a/examples/sql/books/main.py +++ b/examples/sql/books/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtWidgets import QApplication diff --git a/examples/sql/books/ui_bookwindow.py b/examples/sql/books/ui_bookwindow.py index 52795217..2244dd7d 100644 --- a/examples/sql/books/ui_bookwindow.py +++ b/examples/sql/books/ui_bookwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'bookwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -39,7 +39,7 @@ class Ui_BookWindow(object): self.vboxLayout1.setContentsMargins(9, 9, 9, 9) self.bookTable = QTableView(self.groupBox) self.bookTable.setObjectName(u"bookTable") - self.bookTable.setSelectionBehavior(QAbstractItemView.SelectRows) + self.bookTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.vboxLayout1.addWidget(self.bookTable) diff --git a/examples/sql/relationaltablemodel/connection.py b/examples/sql/relationaltablemodel/connection.py index 6bfc828c..61c154ba 100644 --- a/examples/sql/relationaltablemodel/connection.py +++ b/examples/sql/relationaltablemodel/connection.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the relationaltablemodel example from Qt v6.x""" diff --git a/examples/sql/relationaltablemodel/relationaltablemodel.py b/examples/sql/relationaltablemodel/relationaltablemodel.py index d2efac2d..47328e72 100644 --- a/examples/sql/relationaltablemodel/relationaltablemodel.py +++ b/examples/sql/relationaltablemodel/relationaltablemodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the relationaltablemodel example from Qt v6.x""" @@ -19,11 +20,11 @@ def initializeModel(model): model.setEditStrategy(QSqlRelationalTableModel.OnManualSubmit) model.setRelation(2, QSqlRelation("city", "id", "name")) model.setRelation(3, QSqlRelation("country", "id", "name")) - model.setHeaderData(0, Qt.Horizontal, QObject().tr("ID")) + model.setHeaderData(0, Qt.Orientation.Horizontal, QObject().tr("ID")) - model.setHeaderData(1, Qt.Horizontal, QObject().tr("Name")) - model.setHeaderData(2, Qt.Horizontal, QObject().tr("City")) - model.setHeaderData(3, Qt.Horizontal, QObject().tr("Country")) + model.setHeaderData(1, Qt.Orientation.Horizontal, QObject().tr("Name")) + model.setHeaderData(2, Qt.Orientation.Horizontal, QObject().tr("City")) + model.setHeaderData(3, Qt.Orientation.Horizontal, QObject().tr("Country")) model.select() diff --git a/examples/statemachine/moveblocks/moveblocks.py b/examples/statemachine/moveblocks/moveblocks.py index 0d52c5b0..146fd739 100644 --- a/examples/statemachine/moveblocks/moveblocks.py +++ b/examples/statemachine/moveblocks/moveblocks.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the examples/statemachine/moveblocks example from Qt v6.x""" @@ -34,7 +35,7 @@ class QGraphicsRectWidget(QGraphicsWidget): def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: QWidget | None = None): - painter.fillRect(self.rect(), Qt.blue) + painter.fillRect(self.rect(), Qt.GlobalColor.blue) class StateSwitchTransition(QAbstractTransition): @@ -109,7 +110,7 @@ if __name__ == '__main__': button4.setZValue(3) scene = QGraphicsScene(0, 0, 300, 300) - scene.setBackgroundBrush(Qt.black) + scene.setBackgroundBrush(Qt.GlobalColor.black) scene.addItem(button1) scene.addItem(button2) scene.addItem(button3) @@ -117,9 +118,9 @@ if __name__ == '__main__': window = GraphicsView(scene) window.setFrameStyle(0) - window.setAlignment(Qt.AlignLeft | Qt.AlignTop) - window.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - window.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + window.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) + window.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + window.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) machine = QStateMachine() @@ -177,28 +178,28 @@ if __name__ == '__main__': anim = QPropertyAnimation(button4, b"geometry") anim.setDuration(1000) - anim.setEasingCurve(QEasingCurve.OutElastic) + anim.setEasingCurve(QEasingCurve.Type.OutElastic) animation_group.addAnimation(anim) sub_group = QSequentialAnimationGroup(animation_group) sub_group.addPause(100) anim = QPropertyAnimation(button3, b"geometry") anim.setDuration(1000) - anim.setEasingCurve(QEasingCurve.OutElastic) + anim.setEasingCurve(QEasingCurve.Type.OutElastic) sub_group.addAnimation(anim) sub_group = QSequentialAnimationGroup(animation_group) sub_group.addPause(150) anim = QPropertyAnimation(button2, b"geometry") anim.setDuration(1000) - anim.setEasingCurve(QEasingCurve.OutElastic) + anim.setEasingCurve(QEasingCurve.Type.OutElastic) sub_group.addAnimation(anim) sub_group = QSequentialAnimationGroup(animation_group) sub_group.addPause(200) anim = QPropertyAnimation(button1, b"geometry") anim.setDuration(1000) - anim.setEasingCurve(QEasingCurve.OutElastic) + anim.setEasingCurve(QEasingCurve.Type.OutElastic) sub_group.addAnimation(anim) state_switcher = StateSwitcher(machine) diff --git a/examples/statemachine/ping_pong/doc/ping_pong.rst b/examples/statemachine/ping_pong/doc/ping_pong.rst new file mode 100644 index 00000000..020f9d9a --- /dev/null +++ b/examples/statemachine/ping_pong/doc/ping_pong.rst @@ -0,0 +1,12 @@ +StateMachine Ping Pong Example +============================== + +The Ping Pong States example shows how to use parallel states together +with custom events and transitions in The State Machine Framework. +It corresponds to the Qt example `Ping Pong States Example +`_. + +.. image:: pingpong.png + :align: center + :alt: ping_pong screenshot + :width: 400 diff --git a/examples/statemachine/ping_pong/doc/pingpong.png b/examples/statemachine/ping_pong/doc/pingpong.png new file mode 100644 index 0000000000000000000000000000000000000000..af707e4633fd2fcb96c8d145b6d5aa5998674a47 GIT binary patch literal 7843 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 # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/statemachine/rogue/doc/rogue.png b/examples/statemachine/rogue/doc/rogue.png new file mode 100644 index 0000000000000000000000000000000000000000..7aeb0e54f91344c0d7a350ad46293683510142f9 GIT binary patch literal 10364 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 B0^|Sy literal 0 HcmV?d00001 diff --git a/examples/statemachine/rogue/doc/rogue.rst b/examples/statemachine/rogue/doc/rogue.rst new file mode 100644 index 00000000..e8d20312 --- /dev/null +++ b/examples/statemachine/rogue/doc/rogue.rst @@ -0,0 +1,11 @@ +StateMachine Rogue Example +========================== + +The Rogue example shows how to use the Qt state machine for event handling +It corresponds to the Qt example `Rogue Example +`_. + +.. image:: rogue.png + :align: center + :alt: rogue screenshot + :width: 400 diff --git a/examples/statemachine/rogue/rogue.py b/examples/statemachine/rogue/rogue.py index f0ce9c28..20376b3c 100644 --- a/examples/statemachine/rogue/rogue.py +++ b/examples/statemachine/rogue/rogue.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 velociraptor Genjix # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -14,26 +15,26 @@ from PySide6.QtStateMachine import (QEventTransition, QFinalState, class MovementTransition(QEventTransition): def __init__(self, window): - super().__init__(window, QEvent.KeyPress) + super().__init__(window, QEvent.Type.KeyPress) self.window = window def eventTest(self, event): - if (event.type() == QEvent.StateMachineWrapped - and event.event().type() == QEvent.KeyPress): + if (event.type() == QEvent.Type.StateMachineWrapped + and event.event().type() == QEvent.Type.KeyPress): key = event.event().key() - return (key == Qt.Key_2 or key == Qt.Key_8 - or key == Qt.Key_6 or key == Qt.Key_4) + return (key == Qt.Key.Key_2 or key == Qt.Key.Key_8 + or key == Qt.Key.Key_6 or key == Qt.Key.Key_4) return False def onTransition(self, event): key = event.event().key() - if key == Qt.Key_4: + if key == Qt.Key.Key_4: self.window.move_player(self.window.left) - if key == Qt.Key_8: + if key == Qt.Key.Key_8: self.window.move_player(self.window.Up) - if key == Qt.Key_6: + if key == Qt.Key.Key_6: self.window.move_player(self.window.right) - if key == Qt.Key_2: + if key == Qt.Key.Key_2: self.window.move_player(self.window.down) @@ -99,16 +100,16 @@ class MainWindow(QMainWindow): quit_state = QState(machine) quit_state.assignProperty(self, 'status', 'Really quit(y/n)?') - yes_transition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Y) + yes_transition = QKeyEventTransition(self, QEvent.Type.KeyPress, Qt.Key.Key_Y) self._final_state = QFinalState(machine) yes_transition.setTargetState(self._final_state) quit_state.addTransition(yes_transition) - no_transition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_N) + no_transition = QKeyEventTransition(self, QEvent.Type.KeyPress, Qt.Key.Key_N) no_transition.setTargetState(input_state) quit_state.addTransition(no_transition) - quit_transition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Q) + quit_transition = QKeyEventTransition(self, QEvent.Type.KeyPress, Qt.Key.Key_Q) quit_transition.setTargetState(quit_state) input_state.addTransition(quit_transition) @@ -127,8 +128,8 @@ class MainWindow(QMainWindow): font_height = metrics.height() font_width = metrics.horizontalAdvance('X') - painter.fillRect(self.rect(), Qt.black) - painter.setPen(Qt.white) + painter.fillRect(self.rect(), Qt.GlobalColor.black) + painter.setPen(Qt.GlobalColor.white) y_pos = font_height painter.drawText(QPoint(0, y_pos), self.status) diff --git a/examples/statemachine/trafficlight/trafficlight.py b/examples/statemachine/trafficlight/trafficlight.py index 1e58384f..c5c7f1da 100644 --- a/examples/statemachine/trafficlight/trafficlight.py +++ b/examples/statemachine/trafficlight/trafficlight.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 velociraptor Genjix # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -37,7 +38,7 @@ class LightWidget(QWidget): if not self._on_val: return with QPainter(self) as painter: - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.setBrush(self.color) painter.drawEllipse(0, 0, self.width(), self.height()) @@ -48,14 +49,14 @@ class TrafficLightWidget(QWidget): def __init__(self): super().__init__() vbox = QVBoxLayout(self) - self._red_light = LightWidget(Qt.red) + self._red_light = LightWidget(Qt.GlobalColor.red) vbox.addWidget(self._red_light) - self._yellow_light = LightWidget(Qt.yellow) + self._yellow_light = LightWidget(Qt.GlobalColor.yellow) vbox.addWidget(self._yellow_light) - self._green_light = LightWidget(Qt.green) + self._green_light = LightWidget(Qt.GlobalColor.green) vbox.addWidget(self._green_light) pal = QPalette() - pal.setColor(QPalette.Window, Qt.black) + pal.setColor(QPalette.ColorRole.Window, Qt.GlobalColor.black) self.setPalette(pal) self.setAutoFillBackground(True) diff --git a/examples/tutorials/finance_manager/part1/Finance/AddDialog.qml b/examples/tutorials/finance_manager/part1/Finance/AddDialog.qml new file mode 100644 index 00000000..eea14f54 --- /dev/null +++ b/examples/tutorials/finance_manager/part1/Finance/AddDialog.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Dialog { + id: dialog + + signal finished(string itemName, string category, real cost, string date) + + contentItem: ColumnLayout { + id: form + spacing: 10 + property alias itemName: itemName + property alias category: category + property alias cost: cost + property alias date: date + + GridLayout { + columns: 2 + columnSpacing: 20 + rowSpacing: 10 + Layout.fillWidth: true + + Label { + text: qsTr("Item Name:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: itemName + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + Label { + text: qsTr("Category:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: category + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + Label { + text: qsTr("Cost:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: cost + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: qsTr("€") + inputMethodHints: Qt.ImhFormattedNumbersOnly + } + + Label { + text: qsTr("Date:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: date + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + // placeholderText: qsTr("dd-mm-yyyy") + validator: RegularExpressionValidator { regularExpression: /^[0-3]?\d-[01]?\d-\d{4}$/ } + // code to add the - automatically + onTextChanged: { + if (date.text.length === 2 || date.text.length === 5) { + date.text += "-" + } + } + Component.onCompleted: { + var today = new Date(); + var day = String(today.getDate()).padStart(2, '0'); + var month = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based + var year = today.getFullYear(); + date.placeholderText = day + "-" + month + "-" + year; + } + } + } + } + + function createEntry() { + form.itemName.clear() + form.category.clear() + form.cost.clear() + form.date.clear() + dialog.title = qsTr("Add Finance Item") + dialog.open() + } + + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + + focus: true + modal: true + title: qsTr("Add Finance Item") + standardButtons: Dialog.Ok | Dialog.Cancel + + Component.onCompleted: { + dialog.visible = false + Qt.inputMethod.visibleChanged.connect(adjustDialogPosition) + } + + function adjustDialogPosition() { + if (Qt.inputMethod.visible) { + // If the keyboard is visible, move the dialog up + dialog.y = parent.height / 4 - height / 2 + } else { + // If the keyboard is not visible, center the dialog + dialog.y = parent.height / 2 - height / 2 + } + } + + onAccepted: { + finished(form.itemName.text, form.category.text, parseFloat(form.cost.text), form.date.text) + } +} diff --git a/examples/tutorials/finance_manager/part1/Finance/FinanceDelegate.qml b/examples/tutorials/finance_manager/part1/Finance/FinanceDelegate.qml new file mode 100644 index 00000000..abea906e --- /dev/null +++ b/examples/tutorials/finance_manager/part1/Finance/FinanceDelegate.qml @@ -0,0 +1,86 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material + +ItemDelegate { + id: delegate + checkable: true + width: parent.width + height: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.15 : + Math.min(window.width, window.height) * 0.1 + + contentItem: + RowLayout { + Label { + id: dateLabel + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + text: date + elide: Text.ElideRight + Layout.fillWidth: true + Layout.preferredWidth: 1 + color: Material.primaryTextColor + } + + ColumnLayout { + spacing: 5 + Layout.fillWidth: true + Layout.preferredWidth: 1 + + Label { + text: item_name + color: "#5c8540" + font.bold: true + elide: Text.ElideRight + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + Layout.fillWidth: true + } + + Label { + text: category + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + } + + Item { + Layout.fillWidth: true // This item will take up the remaining space + } + + ColumnLayout { + spacing: 5 + Layout.fillWidth: true + Layout.preferredWidth: 1 + + Label { + text: "you spent:" + color: "#5c8540" + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + + Label { + text: cost + "€" + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + } + } +} diff --git a/examples/tutorials/finance_manager/part1/Finance/FinancePieChart.qml b/examples/tutorials/finance_manager/part1/Finance/FinancePieChart.qml new file mode 100644 index 00000000..d05a49c0 --- /dev/null +++ b/examples/tutorials/finance_manager/part1/Finance/FinancePieChart.qml @@ -0,0 +1,48 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma ComponentBehavior: Bound +import QtQuick +import QtGraphs +import QtQuick.Controls.Material + +Item { + width: Screen.width + height: Screen.height + + GraphsView { + id: chart + anchors.fill: parent + antialiasing: true + + theme: GraphsTheme { + colorScheme: Qt.Dark + theme: GraphsTheme.Theme.QtGreenNeon + } + + PieSeries { + id: pieSeries + } + } + + Text { + id: chartTitle + text: "Total Expenses Breakdown by Category" + color: "#5c8540" + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.03 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 20 + } + + function updateChart(data) { + pieSeries.clear() + for (var category in data) { + var slice = pieSeries.append(category, data[category]) + slice.label = category + ": " + data[category] + "€" + slice.labelVisible = true + } + } +} diff --git a/examples/tutorials/finance_manager/part1/Finance/FinanceView.qml b/examples/tutorials/finance_manager/part1/Finance/FinanceView.qml new file mode 100644 index 00000000..44d6c209 --- /dev/null +++ b/examples/tutorials/finance_manager/part1/Finance/FinanceView.qml @@ -0,0 +1,47 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +ListView { + id: listView + anchors.fill: parent + height: parent.height + property var financeModel + + delegate: FinanceDelegate { + id: delegate + width: listView.width + } + + model: financeModel + + section.property: "month" // Group items by the "month" property + section.criteria: ViewSection.FullString + section.delegate: Component { + id: sectionHeading + Rectangle { + width: listView.width + height: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.05 : + Math.min(window.width, window.height) * 0.03 + color: "#5c8540" + + required property string section + + Text { + text: parent.section + font.bold: true + // depending on the screen density, adjust the font size + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + color: Material.primaryTextColor + } + } + } + + ScrollBar.vertical: ScrollBar { } +} diff --git a/examples/tutorials/finance_manager/part1/Finance/Main.qml b/examples/tutorials/finance_manager/part1/Finance/Main.qml new file mode 100644 index 00000000..b06b3f53 --- /dev/null +++ b/examples/tutorials/finance_manager/part1/Finance/Main.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Controls.Material +import Finance + +ApplicationWindow { + id: window + Material.theme: Material.Dark + Material.accent: Material.Gray + width: Screen.width * 0.3 + height: Screen.height * 0.5 + visible: true + title: qsTr("Finance Manager") + + // Add a toolbar for the application, only visible on mobile + header: ToolBar { + Material.primary: "#5c8540" + visible: Qt.platform.os == "android" + RowLayout { + anchors.fill: parent + Label { + text: qsTr("Finance Manager") + font.pixelSize: 20 + Layout.alignment: Qt.AlignCenter + } + } + } + + ColumnLayout { + anchors.fill: parent + + TabBar { + id: tabBar + Layout.fillWidth: true + + TabButton { + text: qsTr("Expenses") + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + onClicked: stackView.currentIndex = 0 + } + + TabButton { + text: qsTr("Charts") + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + onClicked: stackView.currentIndex = 1 + } + } + + StackLayout { + id: stackView + Layout.fillWidth: true + Layout.fillHeight: true + + Item { + id: expensesView + Layout.fillWidth: true + Layout.fillHeight: true + + FinanceView { + id: financeView + anchors.fill: parent + financeModel: finance_model + } + } + + Item { + id: chartsView + Layout.fillWidth: true + Layout.fillHeight: true + + FinancePieChart { + id: financePieChart + anchors.fill: parent + Component.onCompleted: { + var categoryData = finance_model.getCategoryData() + updateChart(categoryData) + } + } + } + } + } + + // Model to store the finance data. Created from Python. + FinanceModel { + id: finance_model + } + + // Add a dialog to add new entries + AddDialog { + id: addDialog + onFinished: function(item_name, category, cost, date) { + finance_model.append(item_name, category, cost, date) + var categoryData = finance_model.getCategoryData() + financePieChart.updateChart(categoryData) + } + } + + // Add a button to open the dialog + ToolButton { + id: roundButton + text: qsTr("+") + highlighted: true + Material.elevation: 6 + width: Qt.platform.os === "android" ? + Math.min(parent.width * 0.2, Screen.width * 0.15) : + Math.min(parent.width * 0.060, Screen.width * 0.05) + height: width // Keep the button circular + anchors.margins: 10 + anchors.right: parent.right + anchors.bottom: parent.bottom + background: Rectangle { + color: "#5c8540" + radius: roundButton.width / 2 + } + font.pixelSize: width * 0.4 + onClicked: { + addDialog.createEntry() + } + } +} diff --git a/examples/tutorials/finance_manager/part1/Finance/qmldir b/examples/tutorials/finance_manager/part1/Finance/qmldir new file mode 100644 index 00000000..5362013c --- /dev/null +++ b/examples/tutorials/finance_manager/part1/Finance/qmldir @@ -0,0 +1,6 @@ +module Finance +Main 1.0 Main.qml +FinanceView 1.0 FinanceView.qml +FinancePieChart 1.0 FinancePieChart.qml +FinanceDelegate 1.0 FinanceDelegate.qml +AddDialog 1.0 AddDialog.qml diff --git a/examples/tutorials/finance_manager/part1/doc/part1.md b/examples/tutorials/finance_manager/part1/doc/part1.md new file mode 100644 index 00000000..0c8d9fa7 --- /dev/null +++ b/examples/tutorials/finance_manager/part1/doc/part1.md @@ -0,0 +1,11 @@ +# Finance Manager Example - Part 1 + +This example represents the part one of the tutorial series on creating a simple Finance Manager +that allows users to manage their expenses and visualize them using a pie chart, using PySide6, +[SQLAlchemy], [FastAPI], and [Pydantic]. + +For more details, see the [Finance Manager Tutorial - Part 1](tutorial_financemanager_partone). + +[SQLalchemy]: https://www.sqlalchemy.org/ +[FastApi]: https://fastapi.tiangolo.com/ +[PyDantic]: https://pydantic-docs.helpmanual.io/ diff --git a/examples/tutorials/finance_manager/part1/financemodel.py b/examples/tutorials/finance_manager/part1/financemodel.py new file mode 100644 index 00000000..9ab60fef --- /dev/null +++ b/examples/tutorials/finance_manager/part1/financemodel.py @@ -0,0 +1,94 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from datetime import datetime +from dataclasses import dataclass +from enum import IntEnum +from collections import defaultdict + +from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot, + QByteArray) +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "Finance" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class FinanceModel(QAbstractListModel): + + @QEnum + class FinanceRole(IntEnum): + ItemNameRole = Qt.ItemDataRole.DisplayRole + CategoryRole = Qt.ItemDataRole.UserRole + CostRole = Qt.ItemDataRole.UserRole + 1 + DateRole = Qt.ItemDataRole.UserRole + 2 + MonthRole = Qt.ItemDataRole.UserRole + 3 + + @dataclass + class Finance: + item_name: str + category: str + cost: float + date: str + + @property + def month(self): + return datetime.strptime(self.date, "%d-%m-%Y").strftime("%B %Y") + + def __init__(self, parent=None) -> None: + super().__init__(parent) + self.m_finances = [] + self.m_finances.append(self.Finance("Mobile Prepaid", "Electronics", 20.00, "15-02-2024")) + self.m_finances.append(self.Finance("Groceries-Feb-Week1", "Groceries", 60.75, + "16-01-2024")) + self.m_finances.append(self.Finance("Bus Ticket", "Transport", 5.50, "17-01-2024")) + self.m_finances.append(self.Finance("Book", "Education", 25.00, "18-01-2024")) + + def rowCount(self, parent=QModelIndex()): + return len(self.m_finances) + + def data(self, index: QModelIndex, role: int): + row = index.row() + if row < self.rowCount(): + finance = self.m_finances[row] + if role == FinanceModel.FinanceRole.ItemNameRole: + return finance.item_name + if role == FinanceModel.FinanceRole.CategoryRole: + return finance.category + if role == FinanceModel.FinanceRole.CostRole: + return finance.cost + if role == FinanceModel.FinanceRole.DateRole: + return finance.date + if role == FinanceModel.FinanceRole.MonthRole: + return finance.month + return None + + @Slot(result=dict) + def getCategoryData(self): + category_data = defaultdict(float) + for finance in self.m_finances: + category_data[finance.category] += finance.cost + return dict(category_data) + + def roleNames(self): + roles = super().roleNames() + roles[FinanceModel.FinanceRole.ItemNameRole] = QByteArray(b"item_name") + roles[FinanceModel.FinanceRole.CategoryRole] = QByteArray(b"category") + roles[FinanceModel.FinanceRole.CostRole] = QByteArray(b"cost") + roles[FinanceModel.FinanceRole.DateRole] = QByteArray(b"date") + roles[FinanceModel.FinanceRole.MonthRole] = QByteArray(b"month") + return roles + + @Slot(int, result='QVariantMap') + def get(self, row: int): + finance = self.m_finances[row] + return {"item_name": finance.item_name, "category": finance.category, + "cost": finance.cost, "date": finance.date} + + @Slot(str, str, float, str) + def append(self, item_name: str, category: str, cost: float, date: str): + finance = self.Finance(item_name, category, cost, date) + self.beginInsertRows(QModelIndex(), 0, 0) # Insert at the front + self.m_finances.insert(0, finance) # Insert at the front of the list + self.endInsertRows() diff --git a/examples/tutorials/finance_manager/part1/main.py b/examples/tutorials/finance_manager/part1/main.py new file mode 100644 index 00000000..7a794db0 --- /dev/null +++ b/examples/tutorials/finance_manager/part1/main.py @@ -0,0 +1,25 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from pathlib import Path +from PySide6.QtWidgets import QApplication +from PySide6.QtQml import QQmlApplicationEngine + +from financemodel import FinanceModel # noqa: F401 + +if __name__ == '__main__': + app = QApplication(sys.argv) + QApplication.setOrganizationName("QtProject") + QApplication.setApplicationName("Finance Manager") + engine = QQmlApplicationEngine() + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("Finance", "Main") + + if not engine.rootObjects(): + sys.exit(-1) + + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/tutorials/finance_manager/part1/part1.pyproject b/examples/tutorials/finance_manager/part1/part1.pyproject new file mode 100644 index 00000000..e02b93bb --- /dev/null +++ b/examples/tutorials/finance_manager/part1/part1.pyproject @@ -0,0 +1,12 @@ +{ + "files": [ + "main.py", + "financemodel.py", + "Finance/AddDialog.qml", + "Finance/FinanceDelegate.qml", + "Finance/FinancePieChart.qml", + "Finance/FinanceView.qml", + "Finance/Main.qml", + "Finance/qmldir" + ] +} diff --git a/examples/tutorials/finance_manager/part2/Finance/AddDialog.qml b/examples/tutorials/finance_manager/part2/Finance/AddDialog.qml new file mode 100644 index 00000000..b61c7f93 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/Finance/AddDialog.qml @@ -0,0 +1,129 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Dialog { + id: dialog + + signal finished(string itemName, string category, real cost, string date) + + contentItem: ColumnLayout { + id: form + spacing: 10 + property alias itemName: itemName + property alias category: category + property alias cost: cost + property alias date: date + + GridLayout { + columns: 2 + columnSpacing: 20 + rowSpacing: 10 + Layout.fillWidth: true + + Label { + text: qsTr("Item Name:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: itemName + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + Label { + text: qsTr("Category:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: category + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + Label { + text: qsTr("Cost:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: cost + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: qsTr("€") + inputMethodHints: Qt.ImhFormattedNumbersOnly + } + + Label { + text: qsTr("Date:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: date + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: qsTr("dd-mm-yyyy") + validator: RegularExpressionValidator { regularExpression: /^[0-3]?\d-[01]?\d-\d{4}$/ } + // code to add the - automatically + onTextChanged: { + if (date.text.length === 2 || date.text.length === 5) { + date.text += "-" + } + } + Component.onCompleted: { + var today = new Date(); + var day = String(today.getDate()).padStart(2, '0'); + var month = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based + var year = today.getFullYear(); + date.placeholderText = day + "-" + month + "-" + year; + } + } + } + } + + function createEntry() { + form.itemName.clear() + form.category.clear() + form.cost.clear() + form.date.clear() + dialog.title = qsTr("Add Finance Item") + dialog.open() + } + + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + + focus: true + modal: true + title: qsTr("Add Finance Item") + standardButtons: Dialog.Ok | Dialog.Cancel + + Component.onCompleted: { + dialog.visible = false + Qt.inputMethod.visibleChanged.connect(adjustDialogPosition) + } + + function adjustDialogPosition() { + if (Qt.inputMethod.visible) { + // If the keyboard is visible, move the dialog up + dialog.y = parent.height / 4 - height / 2 + } else { + // If the keyboard is not visible, center the dialog + dialog.y = parent.height / 2 - height / 2 + } + } + + onAccepted: { + finished(form.itemName.text, form.category.text, parseFloat(form.cost.text), form.date.text) + } +} diff --git a/examples/tutorials/finance_manager/part2/Finance/FinanceDelegate.qml b/examples/tutorials/finance_manager/part2/Finance/FinanceDelegate.qml new file mode 100644 index 00000000..267d57d5 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/Finance/FinanceDelegate.qml @@ -0,0 +1,86 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material + +ItemDelegate { + id: delegate + checkable: true + width: parent.width + height: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.15 : + Math.min(window.width, window.height) * 0.1 + + contentItem: + RowLayout { + Label { + id: dateLabel + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + text: date + elide: Text.ElideRight + Layout.fillWidth: true + Layout.preferredWidth: 1 + color: Material.primaryTextColor + } + + ColumnLayout { + spacing: 5 + Layout.fillWidth: true + Layout.preferredWidth: 1 + + Label { + text: item_name + color: "#5c8540" + font.bold: true + elide: Text.ElideRight + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + Layout.fillWidth: true + } + + Label { + text: category + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + } + + Item { + Layout.fillWidth: true // This item will take up the remaining space + } + + ColumnLayout { + spacing: 5 + Layout.fillWidth: true + Layout.preferredWidth: 1 + + Label { + text: "you spent:" + color: "#5c8540" + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + + Label { + text: cost + "€" + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + } + } +} diff --git a/examples/tutorials/finance_manager/part2/Finance/FinancePieChart.qml b/examples/tutorials/finance_manager/part2/Finance/FinancePieChart.qml new file mode 100644 index 00000000..f5386326 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/Finance/FinancePieChart.qml @@ -0,0 +1,47 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtGraphs +import QtQuick.Controls.Material + +Item { + width: Screen.width + height: Screen.height + + GraphsView { + id: chart + anchors.fill: parent + antialiasing: true + + theme: GraphsTheme { + colorScheme: Qt.Dark + theme: GraphsTheme.Theme.QtGreenNeon + } + + PieSeries { + id: pieSeries + } + } + + Text { + id: chartTitle + text: "Total Expenses Breakdown by Category" + color: "#5c8540" + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.03 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 20 + } + + function updateChart(data) { + pieSeries.clear() + for (var category in data) { + var slice = pieSeries.append(category, data[category]) + slice.label = category + ": " + data[category] + "€" + slice.labelVisible = true + } + } +} diff --git a/examples/tutorials/finance_manager/part2/Finance/FinanceView.qml b/examples/tutorials/finance_manager/part2/Finance/FinanceView.qml new file mode 100644 index 00000000..8a943e04 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/Finance/FinanceView.qml @@ -0,0 +1,46 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +ListView { + id: listView + anchors.fill: parent + height: parent.height + property var financeModel + + delegate: FinanceDelegate { + id: delegate + width: listView.width + } + + model: financeModel + + section.property: "month" // Group items by the "month" property + section.criteria: ViewSection.FullString + section.delegate: Component { + id: sectionHeading + Rectangle { + width: listView.width + height: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.05 : + Math.min(window.width, window.height) * 0.03 + color: "#5c8540" + + required property string section + + Text { + text: parent.section + font.bold: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + color: Material.primaryTextColor + } + } + } + + ScrollBar.vertical: ScrollBar { } +} diff --git a/examples/tutorials/finance_manager/part2/Finance/Main.qml b/examples/tutorials/finance_manager/part2/Finance/Main.qml new file mode 100644 index 00000000..b06b3f53 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/Finance/Main.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Controls.Material +import Finance + +ApplicationWindow { + id: window + Material.theme: Material.Dark + Material.accent: Material.Gray + width: Screen.width * 0.3 + height: Screen.height * 0.5 + visible: true + title: qsTr("Finance Manager") + + // Add a toolbar for the application, only visible on mobile + header: ToolBar { + Material.primary: "#5c8540" + visible: Qt.platform.os == "android" + RowLayout { + anchors.fill: parent + Label { + text: qsTr("Finance Manager") + font.pixelSize: 20 + Layout.alignment: Qt.AlignCenter + } + } + } + + ColumnLayout { + anchors.fill: parent + + TabBar { + id: tabBar + Layout.fillWidth: true + + TabButton { + text: qsTr("Expenses") + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + onClicked: stackView.currentIndex = 0 + } + + TabButton { + text: qsTr("Charts") + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + onClicked: stackView.currentIndex = 1 + } + } + + StackLayout { + id: stackView + Layout.fillWidth: true + Layout.fillHeight: true + + Item { + id: expensesView + Layout.fillWidth: true + Layout.fillHeight: true + + FinanceView { + id: financeView + anchors.fill: parent + financeModel: finance_model + } + } + + Item { + id: chartsView + Layout.fillWidth: true + Layout.fillHeight: true + + FinancePieChart { + id: financePieChart + anchors.fill: parent + Component.onCompleted: { + var categoryData = finance_model.getCategoryData() + updateChart(categoryData) + } + } + } + } + } + + // Model to store the finance data. Created from Python. + FinanceModel { + id: finance_model + } + + // Add a dialog to add new entries + AddDialog { + id: addDialog + onFinished: function(item_name, category, cost, date) { + finance_model.append(item_name, category, cost, date) + var categoryData = finance_model.getCategoryData() + financePieChart.updateChart(categoryData) + } + } + + // Add a button to open the dialog + ToolButton { + id: roundButton + text: qsTr("+") + highlighted: true + Material.elevation: 6 + width: Qt.platform.os === "android" ? + Math.min(parent.width * 0.2, Screen.width * 0.15) : + Math.min(parent.width * 0.060, Screen.width * 0.05) + height: width // Keep the button circular + anchors.margins: 10 + anchors.right: parent.right + anchors.bottom: parent.bottom + background: Rectangle { + color: "#5c8540" + radius: roundButton.width / 2 + } + font.pixelSize: width * 0.4 + onClicked: { + addDialog.createEntry() + } + } +} diff --git a/examples/tutorials/finance_manager/part2/Finance/qmldir b/examples/tutorials/finance_manager/part2/Finance/qmldir new file mode 100644 index 00000000..5362013c --- /dev/null +++ b/examples/tutorials/finance_manager/part2/Finance/qmldir @@ -0,0 +1,6 @@ +module Finance +Main 1.0 Main.qml +FinanceView 1.0 FinanceView.qml +FinancePieChart 1.0 FinancePieChart.qml +FinanceDelegate 1.0 FinanceDelegate.qml +AddDialog 1.0 AddDialog.qml diff --git a/examples/tutorials/finance_manager/part2/database.py b/examples/tutorials/finance_manager/part2/database.py new file mode 100644 index 00000000..08cbb62c --- /dev/null +++ b/examples/tutorials/finance_manager/part2/database.py @@ -0,0 +1,68 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from sqlalchemy import create_engine, Column, Integer, String, Float +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import os +import platform +from pathlib import Path + +Base = declarative_base() + + +class Finance(Base): + __tablename__ = 'finances' + id = Column(Integer, primary_key=True) + item_name = Column(String) + category = Column(String) + cost = Column(Float) + date = Column(String) + + +# Check for an environment variable for the database path +env_db_path = os.getenv('FINANCE_MANAGER_DB_PATH') + +if env_db_path: + db_path = Path(env_db_path) +else: + # Determine the application data directory based on the operating system using pathlib + if platform.system() == 'Windows': + app_data_location = Path(os.getenv('APPDATA')) / 'FinanceManager' + elif platform.system() == 'Darwin': # macOS + app_data_location = Path.home() / 'Library' / 'Application Support' / 'FinanceManager' + else: # Linux and other Unix-like systems + app_data_location = Path.home() / '.local' / 'share' / 'FinanceManager' + + db_path = app_data_location / 'finances.db' + +DATABASE_URL = f'sqlite:///{db_path}' +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) + +# Default data to be added to the database +default_data = [ + {"item_name": "Mobile Prepaid", "category": "Electronics", "cost": 20.00, "date": "15-02-2024"}, + {"item_name": "Groceries-Feb-Week1", "category": "Groceries", "cost": 60.75, + "date": "16-01-2024"}, + {"item_name": "Bus Ticket", "category": "Transport", "cost": 5.50, "date": "17-01-2024"}, + {"item_name": "Book", "category": "Education", "cost": 25.00, "date": "18-01-2024"}, +] + + +def initialize_database(): + if db_path.exists(): + print(f"Database '{db_path}' already exists.") + return + + app_data_location.mkdir(parents=True, exist_ok=True) + Base.metadata.create_all(engine) + print(f"Database '{db_path}' created successfully.") + session = Session() + + for data in default_data: + finance = Finance(**data) + session.add(finance) + + session.commit() + print("Default data has been added to the database.") diff --git a/examples/tutorials/finance_manager/part2/doc/part2.md b/examples/tutorials/finance_manager/part2/doc/part2.md new file mode 100644 index 00000000..1454d235 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/doc/part2.md @@ -0,0 +1,11 @@ +# Finance Manager Example - Part 2 + +This example represents the part two of the tutorial series on creating a simple Finance Manager +that allows users to manage their expenses and visualize them using a pie chart, using PySide6, +[SQLAlchemy], [FastAPI], and [Pydantic]. + +For more details, see the [Finance Manager Tutorial - Part 2](tutorial_financemanager_parttwo). + +[SQLalchemy]: https://www.sqlalchemy.org/ +[FastApi]: https://fastapi.tiangolo.com/ +[PyDantic]: https://pydantic-docs.helpmanual.io/ diff --git a/examples/tutorials/finance_manager/part2/financemodel.py b/examples/tutorials/finance_manager/part2/financemodel.py new file mode 100644 index 00000000..7e48c2a6 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/financemodel.py @@ -0,0 +1,101 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from datetime import datetime +from dataclasses import dataclass +from enum import IntEnum +from collections import defaultdict + +from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot, + QByteArray) +from PySide6.QtQml import QmlElement +import database + +QML_IMPORT_NAME = "Finance" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class FinanceModel(QAbstractListModel): + + @QEnum + class FinanceRole(IntEnum): + ItemNameRole = Qt.ItemDataRole.DisplayRole + CategoryRole = Qt.ItemDataRole.UserRole + CostRole = Qt.ItemDataRole.UserRole + 1 + DateRole = Qt.ItemDataRole.UserRole + 2 + MonthRole = Qt.ItemDataRole.UserRole + 3 + + @dataclass + class Finance: + item_name: str + category: str + cost: float + date: str + + @property + def month(self): + return datetime.strptime(self.date, "%d-%m-%Y").strftime("%B %Y") + + def __init__(self, parent=None) -> None: + super().__init__(parent) + self.session = database.Session() + self.m_finances = self.load_finances() + + def load_finances(self): + finances = [] + for finance in self.session.query(database.Finance).all(): + finances.append(self.Finance(finance.item_name, finance.category, finance.cost, + finance.date)) + return finances + + def rowCount(self, parent=QModelIndex()): + return len(self.m_finances) + + def data(self, index: QModelIndex, role: int): + row = index.row() + if row < self.rowCount(): + finance = self.m_finances[row] + if role == FinanceModel.FinanceRole.ItemNameRole: + return finance.item_name + if role == FinanceModel.FinanceRole.CategoryRole: + return finance.category + if role == FinanceModel.FinanceRole.CostRole: + return finance.cost + if role == FinanceModel.FinanceRole.DateRole: + return finance.date + if role == FinanceModel.FinanceRole.MonthRole: + return finance.month + return None + + @Slot(result=dict) + def getCategoryData(self): + category_data = defaultdict(float) + for finance in self.m_finances: + category_data[finance.category] += finance.cost + return dict(category_data) + + def roleNames(self): + roles = super().roleNames() + roles[FinanceModel.FinanceRole.ItemNameRole] = QByteArray(b"item_name") + roles[FinanceModel.FinanceRole.CategoryRole] = QByteArray(b"category") + roles[FinanceModel.FinanceRole.CostRole] = QByteArray(b"cost") + roles[FinanceModel.FinanceRole.DateRole] = QByteArray(b"date") + roles[FinanceModel.FinanceRole.MonthRole] = QByteArray(b"month") + return roles + + @Slot(int, result='QVariantMap') + def get(self, row: int): + finance = self.m_finances[row] + return {"item_name": finance.item_name, "category": finance.category, + "cost": finance.cost, "date": finance.date} + + @Slot(str, str, float, str) + def append(self, item_name: str, category: str, cost: float, date: str): + finance = self.Finance(item_name, category, cost, date) + self.session.add(database.Finance(item_name=item_name, category=category, cost=cost, + date=date)) + self.beginInsertRows(QModelIndex(), 0, 0) # Insert at the front + self.m_finances.insert(0, finance) # Insert at the front of the list + self.endInsertRows() + self.session.commit() diff --git a/examples/tutorials/finance_manager/part2/main.py b/examples/tutorials/finance_manager/part2/main.py new file mode 100644 index 00000000..bff29760 --- /dev/null +++ b/examples/tutorials/finance_manager/part2/main.py @@ -0,0 +1,30 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from pathlib import Path + +from PySide6.QtWidgets import QApplication +from PySide6.QtQml import QQmlApplicationEngine + +from financemodel import FinanceModel # noqa: F401 +from database import initialize_database + +if __name__ == '__main__': + # Initialize the database if it does not exist + initialize_database() + + app = QApplication(sys.argv) + QApplication.setOrganizationName("QtProject") + QApplication.setApplicationName("Finance Manager") + engine = QQmlApplicationEngine() + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("Finance", "Main") + + if not engine.rootObjects(): + sys.exit(-1) + + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/tutorials/finance_manager/part2/part2.pyproject b/examples/tutorials/finance_manager/part2/part2.pyproject new file mode 100644 index 00000000..74b88adc --- /dev/null +++ b/examples/tutorials/finance_manager/part2/part2.pyproject @@ -0,0 +1,14 @@ +{ + "files": [ + "database.py", + "main.py", + "financemodel.py", + "Finance/AddDialog.qml", + "Finance/FinanceDelegate.qml", + "Finance/FinancePieChart.qml", + "Finance/FinanceView.qml", + "Finance/Main.qml", + "Finance/qmldir", + "requirements.txt" + ] +} diff --git a/examples/tutorials/finance_manager/part2/requirements.txt b/examples/tutorials/finance_manager/part2/requirements.txt new file mode 100644 index 00000000..39fb2bef --- /dev/null +++ b/examples/tutorials/finance_manager/part2/requirements.txt @@ -0,0 +1 @@ +sqlalchemy diff --git a/examples/tutorials/finance_manager/part3/Backend/database.py b/examples/tutorials/finance_manager/part3/Backend/database.py new file mode 100644 index 00000000..39bcd886 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Backend/database.py @@ -0,0 +1,62 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from sqlalchemy import create_engine, Column, Integer, String, Float +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import os +import platform +from pathlib import Path + +Base = declarative_base() + + +class Finance(Base): + __tablename__ = 'finances' + id = Column(Integer, primary_key=True) + item_name = Column(String) + category = Column(String) + cost = Column(Float) + date = Column(String) + + +# Determine the application data directory based on the operating system using pathlib +if platform.system() == 'Windows': + app_data_location = Path(os.getenv('APPDATA')) / 'FinanceManager' +elif platform.system() == 'Darwin': # macOS + app_data_location = Path.home() / 'Library' / 'Application Support' / 'FinanceManager' +else: # Linux and other Unix-like systems + app_data_location = Path.home() / '.local' / 'share' / 'FinanceManager' + +db_path = app_data_location / 'finances.db' + +DATABASE_URL = f'sqlite:///{db_path}' +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) + +# Default data to be added to the database +default_data = [ + {"item_name": "Mobile Prepaid", "category": "Electronics", "cost": 20.00, "date": "15-02-2024"}, + {"item_name": "Groceries-Feb-Week1", "category": "Groceries", "cost": 60.75, + "date": "16-01-2024"}, + {"item_name": "Bus Ticket", "category": "Transport", "cost": 5.50, "date": "17-01-2024"}, + {"item_name": "Book", "category": "Education", "cost": 25.00, "date": "18-01-2024"}, +] + + +def initialize_database(): + if db_path.exists(): + print(f"Database '{db_path}' already exists.") + return + + app_data_location.mkdir(parents=True, exist_ok=True) + Base.metadata.create_all(engine) + print(f"Database '{db_path}' created successfully.") + session = Session() + + for data in default_data: + finance = Finance(**data) + session.add(finance) + + session.commit() + print("Default data has been added to the database.") diff --git a/examples/tutorials/finance_manager/part3/Backend/main.py b/examples/tutorials/finance_manager/part3/Backend/main.py new file mode 100644 index 00000000..1ab0bd12 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Backend/main.py @@ -0,0 +1,16 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import uvicorn +from database import initialize_database + + +def main(): + # Initialize the database + initialize_database() + # Start the FastAPI endpoint + uvicorn.run("rest_api:app", host="127.0.0.1", port=8000, reload=True) + + +if __name__ == "__main__": + main() diff --git a/examples/tutorials/finance_manager/part3/Backend/rest_api.py b/examples/tutorials/finance_manager/part3/Backend/rest_api.py new file mode 100644 index 00000000..b2a25f8a --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Backend/rest_api.py @@ -0,0 +1,58 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import logging +from fastapi import FastAPI, Depends, HTTPException +from pydantic import BaseModel +from typing import Dict, Any +from sqlalchemy import orm +from database import Session, Finance + +app = FastAPI() + + +class FinanceCreate(BaseModel): + item_name: str + category: str + cost: float + date: str + + +class FinanceRead(FinanceCreate): + class Config: + from_attributes = True + + +def get_db(): + db = Session() + try: + yield db + finally: + db.close() + + +@app.post("/finances/", response_model=FinanceRead) +def create_finance(finance: FinanceCreate, db: orm.Session = Depends(get_db)): + print(f"Adding finance item: {finance}") + db_finance = Finance(**finance.model_dump()) + db.add(db_finance) + db.commit() + db.refresh(db_finance) + return db_finance + + +@app.get("/finances/", response_model=Dict[str, Any]) +def read_finances(skip: int = 0, limit: int = 10, db: orm.Session = Depends(get_db)): + try: + total = db.query(Finance).count() + finances = db.query(Finance).offset(skip).limit(limit).all() + response = { + "total": total, + # Convert the list of Finance objects to a list of FinanceRead objects + "items": [FinanceRead.from_orm(finance) for finance in finances] + } + logging.info(f"Response: {response}") + return response + except Exception as e: + logging.error(f"Error occurred: {e}") + raise HTTPException(status_code=500, detail="Internal Server Error") diff --git a/examples/tutorials/finance_manager/part3/Frontend/Finance/AddDialog.qml b/examples/tutorials/finance_manager/part3/Frontend/Finance/AddDialog.qml new file mode 100644 index 00000000..b61c7f93 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/Finance/AddDialog.qml @@ -0,0 +1,129 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Dialog { + id: dialog + + signal finished(string itemName, string category, real cost, string date) + + contentItem: ColumnLayout { + id: form + spacing: 10 + property alias itemName: itemName + property alias category: category + property alias cost: cost + property alias date: date + + GridLayout { + columns: 2 + columnSpacing: 20 + rowSpacing: 10 + Layout.fillWidth: true + + Label { + text: qsTr("Item Name:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: itemName + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + Label { + text: qsTr("Category:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: category + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + Label { + text: qsTr("Cost:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: cost + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: qsTr("€") + inputMethodHints: Qt.ImhFormattedNumbersOnly + } + + Label { + text: qsTr("Date:") + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + + TextField { + id: date + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + placeholderText: qsTr("dd-mm-yyyy") + validator: RegularExpressionValidator { regularExpression: /^[0-3]?\d-[01]?\d-\d{4}$/ } + // code to add the - automatically + onTextChanged: { + if (date.text.length === 2 || date.text.length === 5) { + date.text += "-" + } + } + Component.onCompleted: { + var today = new Date(); + var day = String(today.getDate()).padStart(2, '0'); + var month = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based + var year = today.getFullYear(); + date.placeholderText = day + "-" + month + "-" + year; + } + } + } + } + + function createEntry() { + form.itemName.clear() + form.category.clear() + form.cost.clear() + form.date.clear() + dialog.title = qsTr("Add Finance Item") + dialog.open() + } + + x: parent.width / 2 - width / 2 + y: parent.height / 2 - height / 2 + + focus: true + modal: true + title: qsTr("Add Finance Item") + standardButtons: Dialog.Ok | Dialog.Cancel + + Component.onCompleted: { + dialog.visible = false + Qt.inputMethod.visibleChanged.connect(adjustDialogPosition) + } + + function adjustDialogPosition() { + if (Qt.inputMethod.visible) { + // If the keyboard is visible, move the dialog up + dialog.y = parent.height / 4 - height / 2 + } else { + // If the keyboard is not visible, center the dialog + dialog.y = parent.height / 2 - height / 2 + } + } + + onAccepted: { + finished(form.itemName.text, form.category.text, parseFloat(form.cost.text), form.date.text) + } +} diff --git a/examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceDelegate.qml b/examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceDelegate.qml new file mode 100644 index 00000000..267d57d5 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceDelegate.qml @@ -0,0 +1,86 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material + +ItemDelegate { + id: delegate + checkable: true + width: parent.width + height: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.15 : + Math.min(window.width, window.height) * 0.1 + + contentItem: + RowLayout { + Label { + id: dateLabel + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + text: date + elide: Text.ElideRight + Layout.fillWidth: true + Layout.preferredWidth: 1 + color: Material.primaryTextColor + } + + ColumnLayout { + spacing: 5 + Layout.fillWidth: true + Layout.preferredWidth: 1 + + Label { + text: item_name + color: "#5c8540" + font.bold: true + elide: Text.ElideRight + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + Layout.fillWidth: true + } + + Label { + text: category + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + } + + Item { + Layout.fillWidth: true // This item will take up the remaining space + } + + ColumnLayout { + spacing: 5 + Layout.fillWidth: true + Layout.preferredWidth: 1 + + Label { + text: "you spent:" + color: "#5c8540" + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + + Label { + text: cost + "€" + elide: Text.ElideRight + Layout.fillWidth: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + } + } + } +} diff --git a/examples/tutorials/finance_manager/part3/Frontend/Finance/FinancePieChart.qml b/examples/tutorials/finance_manager/part3/Frontend/Finance/FinancePieChart.qml new file mode 100644 index 00000000..f5386326 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/Finance/FinancePieChart.qml @@ -0,0 +1,47 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtGraphs +import QtQuick.Controls.Material + +Item { + width: Screen.width + height: Screen.height + + GraphsView { + id: chart + anchors.fill: parent + antialiasing: true + + theme: GraphsTheme { + colorScheme: Qt.Dark + theme: GraphsTheme.Theme.QtGreenNeon + } + + PieSeries { + id: pieSeries + } + } + + Text { + id: chartTitle + text: "Total Expenses Breakdown by Category" + color: "#5c8540" + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.03 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 20 + } + + function updateChart(data) { + pieSeries.clear() + for (var category in data) { + var slice = pieSeries.append(category, data[category]) + slice.label = category + ": " + data[category] + "€" + slice.labelVisible = true + } + } +} diff --git a/examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceView.qml b/examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceView.qml new file mode 100644 index 00000000..8a943e04 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/Finance/FinanceView.qml @@ -0,0 +1,46 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +ListView { + id: listView + anchors.fill: parent + height: parent.height + property var financeModel + + delegate: FinanceDelegate { + id: delegate + width: listView.width + } + + model: financeModel + + section.property: "month" // Group items by the "month" property + section.criteria: ViewSection.FullString + section.delegate: Component { + id: sectionHeading + Rectangle { + width: listView.width + height: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.05 : + Math.min(window.width, window.height) * 0.03 + color: "#5c8540" + + required property string section + + Text { + text: parent.section + font.bold: true + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.03 : + Math.min(window.width, window.height) * 0.02 + color: Material.primaryTextColor + } + } + } + + ScrollBar.vertical: ScrollBar { } +} diff --git a/examples/tutorials/finance_manager/part3/Frontend/Finance/Main.qml b/examples/tutorials/finance_manager/part3/Frontend/Finance/Main.qml new file mode 100644 index 00000000..b06b3f53 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/Finance/Main.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Controls.Material +import Finance + +ApplicationWindow { + id: window + Material.theme: Material.Dark + Material.accent: Material.Gray + width: Screen.width * 0.3 + height: Screen.height * 0.5 + visible: true + title: qsTr("Finance Manager") + + // Add a toolbar for the application, only visible on mobile + header: ToolBar { + Material.primary: "#5c8540" + visible: Qt.platform.os == "android" + RowLayout { + anchors.fill: parent + Label { + text: qsTr("Finance Manager") + font.pixelSize: 20 + Layout.alignment: Qt.AlignCenter + } + } + } + + ColumnLayout { + anchors.fill: parent + + TabBar { + id: tabBar + Layout.fillWidth: true + + TabButton { + text: qsTr("Expenses") + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + onClicked: stackView.currentIndex = 0 + } + + TabButton { + text: qsTr("Charts") + font.pixelSize: Qt.platform.os == "android" ? + Math.min(window.width, window.height) * 0.04 : + Math.min(window.width, window.height) * 0.02 + onClicked: stackView.currentIndex = 1 + } + } + + StackLayout { + id: stackView + Layout.fillWidth: true + Layout.fillHeight: true + + Item { + id: expensesView + Layout.fillWidth: true + Layout.fillHeight: true + + FinanceView { + id: financeView + anchors.fill: parent + financeModel: finance_model + } + } + + Item { + id: chartsView + Layout.fillWidth: true + Layout.fillHeight: true + + FinancePieChart { + id: financePieChart + anchors.fill: parent + Component.onCompleted: { + var categoryData = finance_model.getCategoryData() + updateChart(categoryData) + } + } + } + } + } + + // Model to store the finance data. Created from Python. + FinanceModel { + id: finance_model + } + + // Add a dialog to add new entries + AddDialog { + id: addDialog + onFinished: function(item_name, category, cost, date) { + finance_model.append(item_name, category, cost, date) + var categoryData = finance_model.getCategoryData() + financePieChart.updateChart(categoryData) + } + } + + // Add a button to open the dialog + ToolButton { + id: roundButton + text: qsTr("+") + highlighted: true + Material.elevation: 6 + width: Qt.platform.os === "android" ? + Math.min(parent.width * 0.2, Screen.width * 0.15) : + Math.min(parent.width * 0.060, Screen.width * 0.05) + height: width // Keep the button circular + anchors.margins: 10 + anchors.right: parent.right + anchors.bottom: parent.bottom + background: Rectangle { + color: "#5c8540" + radius: roundButton.width / 2 + } + font.pixelSize: width * 0.4 + onClicked: { + addDialog.createEntry() + } + } +} diff --git a/examples/tutorials/finance_manager/part3/Frontend/Finance/qmldir b/examples/tutorials/finance_manager/part3/Frontend/Finance/qmldir new file mode 100644 index 00000000..5362013c --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/Finance/qmldir @@ -0,0 +1,6 @@ +module Finance +Main 1.0 Main.qml +FinanceView 1.0 FinanceView.qml +FinancePieChart 1.0 FinancePieChart.qml +FinanceDelegate 1.0 FinanceDelegate.qml +AddDialog 1.0 AddDialog.qml diff --git a/examples/tutorials/finance_manager/part3/Frontend/financemodel.py b/examples/tutorials/finance_manager/part3/Frontend/financemodel.py new file mode 100644 index 00000000..0e4bbef2 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/financemodel.py @@ -0,0 +1,109 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import requests +from datetime import datetime +from dataclasses import dataclass +from enum import IntEnum +from collections import defaultdict + +from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot, + QByteArray) +from PySide6.QtQml import QmlElement + +QML_IMPORT_NAME = "Finance" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class FinanceModel(QAbstractListModel): + + @QEnum + class FinanceRole(IntEnum): + ItemNameRole = Qt.ItemDataRole.DisplayRole + CategoryRole = Qt.ItemDataRole.UserRole + CostRole = Qt.ItemDataRole.UserRole + 1 + DateRole = Qt.ItemDataRole.UserRole + 2 + MonthRole = Qt.ItemDataRole.UserRole + 3 + + @dataclass + class Finance: + item_name: str + category: str + cost: float + date: str + + @property + def month(self): + return datetime.strptime(self.date, "%d-%m-%Y").strftime("%B %Y") + + def __init__(self, parent=None) -> None: + super().__init__(parent) + self.m_finances = [] + self.fetchAllData() + + def fetchAllData(self): + response = requests.get("http://127.0.0.1:8000/finances/") + try: + data = response.json() + except requests.exceptions.JSONDecodeError: + print("Failed to decode JSON response") + return + self.beginInsertRows(QModelIndex(), 0, len(data["items"]) - 1) + self.m_finances.extend([self.Finance(**item) for item in data["items"]]) + self.endInsertRows() + + def rowCount(self, parent=QModelIndex()): + return len(self.m_finances) + + def data(self, index: QModelIndex, role: int): + if not index.isValid() or index.row() >= self.rowCount(): + return None + row = index.row() + if row < self.rowCount(): + finance = self.m_finances[row] + if role == FinanceModel.FinanceRole.ItemNameRole: + return finance.item_name + if role == FinanceModel.FinanceRole.CategoryRole: + return finance.category + if role == FinanceModel.FinanceRole.CostRole: + return finance.cost + if role == FinanceModel.FinanceRole.DateRole: + return finance.date + if role == FinanceModel.FinanceRole.MonthRole: + return finance.month + return None + + def roleNames(self): + roles = super().roleNames() + roles[FinanceModel.FinanceRole.ItemNameRole] = QByteArray(b"item_name") + roles[FinanceModel.FinanceRole.CategoryRole] = QByteArray(b"category") + roles[FinanceModel.FinanceRole.CostRole] = QByteArray(b"cost") + roles[FinanceModel.FinanceRole.DateRole] = QByteArray(b"date") + roles[FinanceModel.FinanceRole.MonthRole] = QByteArray(b"month") + return roles + + @Slot(int, result='QVariantMap') + def get(self, row: int): + finance = self.m_finances[row] + return {"item_name": finance.item_name, "category": finance.category, + "cost": finance.cost, "date": finance.date} + + @Slot(str, str, float, str) + def append(self, item_name: str, category: str, cost: float, date: str): + finance = {"item_name": item_name, "category": category, "cost": cost, "date": date} + response = requests.post("http://127.0.0.1:8000/finances/", json=finance) + if response.status_code == 200: + finance = response.json() + self.beginInsertRows(QModelIndex(), 0, 0) + self.m_finances.insert(0, self.Finance(**finance)) + self.endInsertRows() + else: + print("Failed to add finance item") + + @Slot(result=dict) + def getCategoryData(self): + category_data = defaultdict(float) + for finance in self.m_finances: + category_data[finance.category] += finance.cost + return dict(category_data) diff --git a/examples/tutorials/finance_manager/part3/Frontend/main.py b/examples/tutorials/finance_manager/part3/Frontend/main.py new file mode 100644 index 00000000..bb90d00e --- /dev/null +++ b/examples/tutorials/finance_manager/part3/Frontend/main.py @@ -0,0 +1,26 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from pathlib import Path + +from PySide6.QtWidgets import QApplication +from PySide6.QtQml import QQmlApplicationEngine + +from financemodel import FinanceModel # noqa: F401 + +if __name__ == '__main__': + app = QApplication(sys.argv) + QApplication.setOrganizationName("QtProject") + QApplication.setApplicationName("Finance Manager") + engine = QQmlApplicationEngine() + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("Finance", "Main") + + if not engine.rootObjects(): + sys.exit(-1) + + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/tutorials/finance_manager/part3/doc/part3.md b/examples/tutorials/finance_manager/part3/doc/part3.md new file mode 100644 index 00000000..08768f3a --- /dev/null +++ b/examples/tutorials/finance_manager/part3/doc/part3.md @@ -0,0 +1,11 @@ +# Finance Manager Example - Part 3 + +This example represents the final part of the tutorial series on creating a simple Finance Manager +that allows users to manage their expenses and visualize them using a pie chart, using PySide6, +[SQLAlchemy], [FastAPI], and [Pydantic]. + +For more details, see the [Finance Manager Tutorial - Part 3](tutorial_financemanager_partthree). + +[SQLalchemy]: https://www.sqlalchemy.org/ +[FastApi]: https://fastapi.tiangolo.com/ +[PyDantic]: https://pydantic-docs.helpmanual.io/ diff --git a/examples/tutorials/finance_manager/part3/part3.pyproject b/examples/tutorials/finance_manager/part3/part3.pyproject new file mode 100644 index 00000000..c51cb912 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/part3.pyproject @@ -0,0 +1,16 @@ +{ + "files": [ + "Backend/database.py", + "Backend/main.py", + "Backend/rest_api.py", + "Frontend/Finance/AddDialog.qml", + "Frontend/Finance/FinanceDelegate.qml", + "Frontend/Finance/FinancePieChart.qml", + "Frontend/Finance/FinanceView.qml", + "Frontend/Finance/Main.qml", + "Frontend/Finance/qmldir", + "Frontend/financemodel.py", + "Frontend/main.py", + "requirements.txt" + ] +} diff --git a/examples/tutorials/finance_manager/part3/requirements.txt b/examples/tutorials/finance_manager/part3/requirements.txt new file mode 100644 index 00000000..1970bea0 --- /dev/null +++ b/examples/tutorials/finance_manager/part3/requirements.txt @@ -0,0 +1,3 @@ +sqlalchemy +uvicorn +fastapi diff --git a/examples/uitools/uiloader/doc/uiloader.rst b/examples/uitools/uiloader/doc/uiloader.rst new file mode 100644 index 00000000..df02c9b9 --- /dev/null +++ b/examples/uitools/uiloader/doc/uiloader.rst @@ -0,0 +1,7 @@ +UILoader Example +================ + +This example demonstrates how to dynamically load and display a user interface +designed with Qt Designer using PySide6's `QUiLoader`. It shows how to load a +`.ui` file at runtime, allowing for flexible and dynamic UI design and +modification without recompiling the application. diff --git a/examples/uitools/uiloader/uiloader.py b/examples/uitools/uiloader/uiloader.py index 265c9383..01530d65 100644 --- a/examples/uitools/uiloader/uiloader.py +++ b/examples/uitools/uiloader/uiloader.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """QUiLoader example, showing how to dynamically load a Qt Designer form from a UI file.""" @@ -21,7 +22,7 @@ if __name__ == '__main__': app = QApplication(sys.argv) ui_file = QFile(ui_file_name) - if not ui_file.open(QIODevice.ReadOnly): + if not ui_file.open(QIODevice.OpenModeFlag.ReadOnly): reason = ui_file.errorString() print(f"Cannot open {ui_file_name}: {reason}") sys.exit(-1) diff --git a/examples/utils/pyside_config.py b/examples/utils/pyside_config.py index d3067eec..c4bb873e 100644 --- a/examples/utils/pyside_config.py +++ b/examples/utils/pyside_config.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sysconfig from enum import Enum @@ -202,7 +203,10 @@ def python_version(): def get_python_include_path(): - return sysconfig.get_path('include') + if sys.platform == 'win32': + return sysconfig.get_path('include') + else: + return sysconfig.get_path('include', scheme="posix_prefix") def python_link_flags_qmake(): diff --git a/examples/webchannel/standalone/core.py b/examples/webchannel/standalone/core.py index 2d0cb028..987a4ee1 100644 --- a/examples/webchannel/standalone/core.py +++ b/examples/webchannel/standalone/core.py @@ -1,6 +1,7 @@ # Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Signal, Slot diff --git a/examples/webchannel/standalone/dialog.py b/examples/webchannel/standalone/dialog.py index 7f2413e6..27cef86a 100644 --- a/examples/webchannel/standalone/dialog.py +++ b/examples/webchannel/standalone/dialog.py @@ -1,6 +1,7 @@ # Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Signal, Slot diff --git a/examples/webchannel/standalone/main.py b/examples/webchannel/standalone/main.py index 8c04aa64..b344ec79 100644 --- a/examples/webchannel/standalone/main.py +++ b/examples/webchannel/standalone/main.py @@ -1,6 +1,8 @@ -# Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff +# Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, +# author Milian Wolff # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os @@ -31,8 +33,8 @@ if __name__ == '__main__': # setup the QWebSocketServer server = QWebSocketServer("QWebChannel Standalone Example Server", - QWebSocketServer.NonSecureMode) - if not server.listen(QHostAddress.LocalHost, 12345): + QWebSocketServer.SslMode.NonSecureMode) + if not server.listen(QHostAddress.SpecialAddress.LocalHost, 12345): print("Failed to open web socket server.") sys.exit(-1) diff --git a/examples/webchannel/standalone/ui_dialog.py b/examples/webchannel/standalone/ui_dialog.py index 36c2fe40..4543c4c8 100644 --- a/examples/webchannel/standalone/ui_dialog.py +++ b/examples/webchannel/standalone/ui_dialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'dialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/examples/webchannel/standalone/websocketclientwrapper.py b/examples/webchannel/standalone/websocketclientwrapper.py index 00b41089..a14672f2 100644 --- a/examples/webchannel/standalone/websocketclientwrapper.py +++ b/examples/webchannel/standalone/websocketclientwrapper.py @@ -1,6 +1,7 @@ # Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Signal, Slot diff --git a/examples/webchannel/standalone/websockettransport.py b/examples/webchannel/standalone/websockettransport.py index ab60e844..96e9b822 100644 --- a/examples/webchannel/standalone/websockettransport.py +++ b/examples/webchannel/standalone/websockettransport.py @@ -1,6 +1,7 @@ # Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWebChannel import QWebChannelAbstractTransport from PySide6.QtCore import QByteArray, QJsonDocument, Slot diff --git a/examples/webenginequick/nanobrowser/quicknanobrowser.py b/examples/webenginequick/nanobrowser/quicknanobrowser.py index aee79c2a..e5c667c5 100644 --- a/examples/webenginequick/nanobrowser/quicknanobrowser.py +++ b/examples/webenginequick/nanobrowser/quicknanobrowser.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 WebEngine QtQuick 2 Example""" @@ -67,4 +68,6 @@ if __name__ == '__main__': QMetaObject.invokeMethod(engine.rootObjects()[0], "load", Q_ARG("QVariant", url)) - app.exec() + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/webenginewidgets/markdowneditor/doc/markdowneditor.png b/examples/webenginewidgets/markdowneditor/doc/markdowneditor.png new file mode 100644 index 0000000000000000000000000000000000000000..9f456c4db2dd9a948fbca9e832cb1c80af8a2fb3 GIT binary patch literal 41883 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 VMEb7a1DfziS>dgGxvcre{{X~aq$dCX literal 0 HcmV?d00001 diff --git a/examples/webenginewidgets/markdowneditor/doc/markdowneditor.rst b/examples/webenginewidgets/markdowneditor/doc/markdowneditor.rst new file mode 100644 index 00000000..d54aafaf --- /dev/null +++ b/examples/webenginewidgets/markdowneditor/doc/markdowneditor.rst @@ -0,0 +1,11 @@ +WebEngine Markdown Editor Example +================================= + +Demonstrates how to integrate a web engine in a hybrid desktop application. +It corresponds to the Qt example `WebEngine Markdown Editor Example +`_. + +.. image:: markdowneditor.png + :align: center + :alt: markdowneditor screenshot + :width: 400 diff --git a/examples/webenginewidgets/markdowneditor/document.py b/examples/webenginewidgets/markdowneditor/document.py index 331fbc0c..10ed5752 100644 --- a/examples/webenginewidgets/markdowneditor/document.py +++ b/examples/webenginewidgets/markdowneditor/document.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QObject, Property, Signal diff --git a/examples/webenginewidgets/markdowneditor/main.py b/examples/webenginewidgets/markdowneditor/main.py index 4d787f0f..8a92abf8 100644 --- a/examples/webenginewidgets/markdowneditor/main.py +++ b/examples/webenginewidgets/markdowneditor/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 Markdown Editor Example""" diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.py b/examples/webenginewidgets/markdowneditor/mainwindow.py index 6f74cf93..208e6fa0 100644 --- a/examples/webenginewidgets/markdowneditor/mainwindow.py +++ b/examples/webenginewidgets/markdowneditor/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QDir, QFile, QIODevice, QUrl, Qt, Slot @@ -20,9 +21,9 @@ class MainWindow(QMainWindow): self.m_content = Document() self._ui = Ui_MainWindow() self._ui.setupUi(self) - font = QFontDatabase.systemFont(QFontDatabase.FixedFont) + font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont) self._ui.editor.setFont(font) - self._ui.preview.setContextMenuPolicy(Qt.NoContextMenu) + self._ui.preview.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) self._page = PreviewPage(self) self._ui.preview.setPage(self._page) @@ -43,7 +44,7 @@ class MainWindow(QMainWindow): self._ui.editor.document().modificationChanged.connect(self._ui.actionSave.setEnabled) defaultTextFile = QFile(":/default.md") - defaultTextFile.open(QIODevice.ReadOnly) + defaultTextFile.open(QIODevice.OpenModeFlag.ReadOnly) data = defaultTextFile.readAll() self._ui.editor.setPlainText(data.data().decode('utf8')) @@ -55,7 +56,7 @@ class MainWindow(QMainWindow): def openFile(self, path): f = QFile(path) name = QDir.toNativeSeparators(path) - if not f.open(QIODevice.ReadOnly): + if not f.open(QIODevice.OpenModeFlag.ReadOnly): error = f.errorString() QMessageBox.warning(self, self.windowTitle(), f"Could not open file {name}: {error}") @@ -73,7 +74,7 @@ class MainWindow(QMainWindow): if self.isModified(): m = "You have unsaved changes. Do you want to create a new document anyway?" button = QMessageBox.question(self, self.windowTitle(), m) - if button != QMessageBox.Yes: + if button != QMessageBox.StandardButton.Yes: return self.m_file_path = '' @@ -85,13 +86,13 @@ class MainWindow(QMainWindow): if self.isModified(): m = "You have unsaved changes. Do you want to open a new document anyway?" button = QMessageBox.question(self, self.windowTitle(), m) - if button != QMessageBox.Yes: + if button != QMessageBox.StandardButton.Yes: return dialog = QFileDialog(self) dialog.setWindowTitle("Open MarkDown File") dialog.setMimeTypeFilters(["text/markdown"]) - dialog.setAcceptMode(QFileDialog.AcceptOpen) - if dialog.exec() == QDialog.Accepted: + dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) + if dialog.exec() == QDialog.DialogCode.Accepted: self.openFile(dialog.selectedFiles()[0]) @Slot() @@ -103,7 +104,7 @@ class MainWindow(QMainWindow): f = QFile(self.m_file_path) name = QDir.toNativeSeparators(self.m_file_path) - if not f.open(QIODevice.WriteOnly | QIODevice.Text): + if not f.open(QIODevice.OpenModeFlag.WriteOnly | QIODevice.OpenModeFlag.Text): error = f.errorString() QMessageBox.warning(self, self.windowTitle(), f"Could not write to file {name}: {error}") @@ -119,9 +120,9 @@ class MainWindow(QMainWindow): dialog = QFileDialog(self) dialog.setWindowTitle("Save MarkDown File") dialog.setMimeTypeFilters(["text/markdown"]) - dialog.setAcceptMode(QFileDialog.AcceptSave) + dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) dialog.setDefaultSuffix("md") - if dialog.exec() != QDialog.Accepted: + if dialog.exec() != QDialog.DialogCode.Accepted: return path = dialog.selectedFiles()[0] self.m_file_path = path @@ -131,7 +132,7 @@ class MainWindow(QMainWindow): if self.isModified(): m = "You have unsaved changes. Do you want to exit anyway?" button = QMessageBox.question(self, self.windowTitle(), m) - if button != QMessageBox.Yes: + if button != QMessageBox.StandardButton.Yes: event.ignore() else: event.accept() diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.ui b/examples/webenginewidgets/markdowneditor/mainwindow.ui index f4e29ad9..846f2d70 100644 --- a/examples/webenginewidgets/markdowneditor/mainwindow.ui +++ b/examples/webenginewidgets/markdowneditor/mainwindow.ui @@ -18,7 +18,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -102,6 +102,13 @@ + + + QWebEngineView + QWidget +
qwebengineview.h
+
+
diff --git a/examples/webenginewidgets/markdowneditor/previewpage.py b/examples/webenginewidgets/markdowneditor/previewpage.py index 35ac80be..75c8e317 100644 --- a/examples/webenginewidgets/markdowneditor/previewpage.py +++ b/examples/webenginewidgets/markdowneditor/previewpage.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtGui import QDesktopServices from PySide6.QtWebEngineCore import QWebEnginePage diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html index c8e30b49..2d3fce90 100644 --- a/examples/webenginewidgets/markdowneditor/resources/index.html +++ b/examples/webenginewidgets/markdowneditor/resources/index.html @@ -27,6 +27,3 @@ - - - diff --git a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py index 0be76911..d538824f 100644 --- a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py +++ b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -42,7 +42,7 @@ class Ui_MainWindow(object): self.horizontalLayout.setObjectName(u"horizontalLayout") self.splitter = QSplitter(self.centralwidget) self.splitter.setObjectName(u"splitter") - self.splitter.setOrientation(Qt.Horizontal) + self.splitter.setOrientation(Qt.Orientation.Horizontal) self.editor = QPlainTextEdit(self.splitter) self.editor.setObjectName(u"editor") self.splitter.addWidget(self.editor) diff --git a/examples/webenginewidgets/notifications/main.py b/examples/webenginewidgets/notifications/main.py index b59aead9..6a96c360 100644 --- a/examples/webenginewidgets/notifications/main.py +++ b/examples/webenginewidgets/notifications/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 WebEngineWidgets Notifications Example""" @@ -37,10 +38,11 @@ if __name__ == '__main__': view.setPage(WebEnginePage(view)) def set_feature_permission(origin: QUrl, feature: QWebEnginePage.Feature): - if feature != QWebEnginePage.Notifications: + if feature != QWebEnginePage.Feature.Notifications: return - view.page().setFeaturePermission(origin, feature, QWebEnginePage.PermissionGrantedByUser) + view.page().setFeaturePermission(origin, feature, + QWebEnginePage.PermissionPolicy.PermissionGrantedByUser) view.page().featurePermissionRequested.connect(set_feature_permission) profile = view.page().profile() diff --git a/examples/webenginewidgets/notifications/notificationpopup.py b/examples/webenginewidgets/notifications/notificationpopup.py index e68ce3d6..86ff5c8e 100644 --- a/examples/webenginewidgets/notifications/notificationpopup.py +++ b/examples/webenginewidgets/notifications/notificationpopup.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt, QTimer, QPoint, Slot from PySide6.QtWidgets import (QWidget, QHBoxLayout, QLabel, QVBoxLayout, QSpacerItem, QSizePolicy, @@ -13,7 +14,7 @@ class NotificationPopup(QWidget): super().__init__(parent) self.notification = None self.m_icon, self.m_title, self.m_message = QLabel(), QLabel(), QLabel() - self.setWindowFlags(Qt.ToolTip) + self.setWindowFlags(Qt.WindowType.ToolTip) rootLayout = QHBoxLayout(self) rootLayout.addWidget(self.m_icon) @@ -25,7 +26,7 @@ class NotificationPopup(QWidget): bodyLayout.addLayout(titleLayout) titleLayout.addWidget(self.m_title) - titleLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding)) + titleLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Policy.Expanding)) close = QPushButton("Close") titleLayout.addWidget(close) @@ -63,6 +64,6 @@ class NotificationPopup(QWidget): def mouseReleaseEvent(self, event: QMouseEvent) -> None: QWidget.mouseReleaseEvent(event) - if self.notification and event.button() == Qt.LeftButton: + if self.notification and event.button() == Qt.MouseButton.LeftButton: self.notification.click() self.onClosed() diff --git a/examples/webenginewidgets/simplebrowser/browser.py b/examples/webenginewidgets/simplebrowser/browser.py index a124ea08..1bf458e7 100644 --- a/examples/webenginewidgets/simplebrowser/browser.py +++ b/examples/webenginewidgets/simplebrowser/browser.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWebEngineCore import (qWebEngineChromiumVersion, QWebEngineProfile, QWebEngineSettings) @@ -19,7 +20,7 @@ class Browser(QObject): # Quit application if the download manager window is the only # remaining window - self._download_manager_widget.setAttribute(Qt.WA_QuitOnClose, False) + self._download_manager_widget.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False) dp = QWebEngineProfile.defaultProfile() dp.downloadRequested.connect(self._download_manager_widget.download_requested) @@ -29,10 +30,10 @@ class Browser(QObject): name = "simplebrowser." + qWebEngineChromiumVersion() self._profile = QWebEngineProfile(name) s = self._profile.settings() - s.setAttribute(QWebEngineSettings.PluginsEnabled, True) - s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True) - s.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True) - s.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, False) + s.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True) + s.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True) + s.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True) + s.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, False) self._profile.downloadRequested.connect( self._download_manager_widget.download_requested) diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py index 43b81120..ad28304e 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.py +++ b/examples/webenginewidgets/simplebrowser/browserwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -17,7 +18,7 @@ def remove_backspace(keys): result = keys.copy() # Chromium already handles navigate on backspace when appropriate. for i, key in enumerate(result): - if (key[0].key() & Qt.Key_unknown) == Qt.Key_Backspace: + if (key[0].key() & Qt.Key.Key_unknown) == Qt.Key.Key_Backspace: del result[i] break return result @@ -50,8 +51,8 @@ class BrowserWindow(QMainWindow): self._reload_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh, QIcon(":view-refresh.png")) - self.setAttribute(Qt.WA_DeleteOnClose, True) - self.setFocusPolicy(Qt.ClickFocus) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True) + self.setFocusPolicy(Qt.FocusPolicy.ClickFocus) if not forDevTools: self._progress_bar = QProgressBar(self) @@ -96,7 +97,7 @@ class BrowserWindow(QMainWindow): focus_url_line_edit_action = QAction(self) self.addAction(focus_url_line_edit_action) - focus_url_line_edit_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_L)) + focus_url_line_edit_action.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_L)) focus_url_line_edit_action.triggered.connect(self._focus_url_lineEdit) self.handle_web_view_title_changed("") @@ -139,27 +140,27 @@ class BrowserWindow(QMainWindow): def create_file_menu(self, tabWidget): file_menu = QMenu("File") - file_menu.addAction("&New Window", QKeySequence.New, + file_menu.addAction("&New Window", QKeySequence.StandardKey.New, self.handle_new_window_triggered) file_menu.addAction("New &Incognito Window", self.handle_new_incognito_window_triggered) new_tab_action = QAction("New Tab", self) - new_tab_action.setShortcuts(QKeySequence.AddTab) + new_tab_action.setShortcuts(QKeySequence.StandardKey.AddTab) new_tab_action.triggered.connect(self._new_tab) file_menu.addAction(new_tab_action) - file_menu.addAction("&Open File...", QKeySequence.Open, + file_menu.addAction("&Open File...", QKeySequence.StandardKey.Open, self.handle_file_open_triggered) file_menu.addSeparator() close_tab_action = QAction("Close Tab", self) - close_tab_action.setShortcuts(QKeySequence.Close) + close_tab_action.setShortcuts(QKeySequence.StandardKey.Close) close_tab_action.triggered.connect(self._close_current_tab) file_menu.addAction(close_tab_action) self._close_action = QAction("Quit", self) - self._close_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Q)) + self._close_action.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Q)) self._close_action.triggered.connect(self.close) file_menu.addAction(self._close_action) @@ -181,15 +182,15 @@ class BrowserWindow(QMainWindow): def create_edit_menu(self): edit_menu = QMenu("Edit") find_action = edit_menu.addAction("Find") - find_action.setShortcuts(QKeySequence.Find) + find_action.setShortcuts(QKeySequence.StandardKey.Find) find_action.triggered.connect(self.handle_find_action_triggered) find_next_action = edit_menu.addAction("Find Next") - find_next_action.setShortcut(QKeySequence.FindNext) + find_next_action.setShortcut(QKeySequence.StandardKey.FindNext) find_next_action.triggered.connect(self._find_next) find_previous_action = edit_menu.addAction("Find Previous") - find_previous_action.setShortcut(QKeySequence.FindPrevious) + find_previous_action.setShortcut(QKeySequence.StandardKey.FindPrevious) find_previous_action.triggered.connect(self._find_previous) return edit_menu @@ -242,25 +243,25 @@ class BrowserWindow(QMainWindow): view_menu = QMenu("View") self._stop_action = view_menu.addAction("Stop") shortcuts = [] - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Period)) - shortcuts.append(QKeySequence(Qt.Key_Escape)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Period)) + shortcuts.append(QKeySequence(Qt.Key.Key_Escape)) self._stop_action.setShortcuts(shortcuts) self._stop_action.triggered.connect(self._stop) self._reload_action = view_menu.addAction("Reload Page") - self._reload_action.setShortcuts(QKeySequence.Refresh) + self._reload_action.setShortcuts(QKeySequence.StandardKey.Refresh) self._reload_action.triggered.connect(self._reload) zoom_in = view_menu.addAction("Zoom In") - zoom_in.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Plus)) + zoom_in.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Plus)) zoom_in.triggered.connect(self._zoom_in) zoom_out = view_menu.addAction("Zoom Out") - zoom_out.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_Minus)) + zoom_out.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Minus)) zoom_out.triggered.connect(self._zoom_out) reset_zoom = view_menu.addAction("Reset Zoom") - reset_zoom.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_0)) + reset_zoom.setShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_0)) reset_zoom.triggered.connect(self._reset_zoom) view_menu.addSeparator() @@ -285,25 +286,25 @@ class BrowserWindow(QMainWindow): menu = QMenu("Window") self._next_tab_action = QAction("Show Next Tab", self) shortcuts = [] - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceRight)) - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageDown)) - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketRight)) - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Less)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_BraceRight)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_PageDown)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_BracketRight)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Less)) self._next_tab_action.setShortcuts(shortcuts) self._next_tab_action.triggered.connect(tabWidget.next_tab) self._previous_tab_action = QAction("Show Previous Tab", self) shortcuts.clear() - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BraceLeft)) - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_PageUp)) - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_BracketLeft)) - shortcuts.append(QKeySequence(Qt.CTRL | Qt.Key_Greater)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_BraceLeft)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_PageUp)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_BracketLeft)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Greater)) self._previous_tab_action.setShortcuts(shortcuts) self._previous_tab_action.triggered.connect(tabWidget.previous_tab) self._inspector_action = QAction("Open inspector in window", self) shortcuts.clear() - shortcuts.append(QKeySequence(Qt.CTRL | Qt.SHIFT | Qt.Key_I)) + shortcuts.append(QKeySequence(Qt.Modifier.CTRL | Qt.Modifier.SHIFT | Qt.Key.Key_I)) self._inspector_action.setShortcuts(shortcuts) self._inspector_action.triggered.connect(self._emit_dev_tools_requested) self._window_menu = menu @@ -336,11 +337,11 @@ class BrowserWindow(QMainWindow): @Slot() def _back(self): - self._tab_widget.trigger_web_page_action(QWebEnginePage.Back) + self._tab_widget.trigger_web_page_action(QWebEnginePage.WebAction.Back) @Slot() def _forward(self): - self._tab_widget.trigger_web_page_action(QWebEnginePage.Forward) + self._tab_widget.trigger_web_page_action(QWebEnginePage.WebAction.Forward) @Slot() def _stop_reload(self): @@ -353,10 +354,10 @@ class BrowserWindow(QMainWindow): navigation_bar.toggleViewAction().setEnabled(False) self._history_back_action = QAction(self) - back_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.Back)) + back_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.StandardKey.Back)) # For some reason Qt doesn't bind the dedicated Back key to Back. - back_shortcuts.append(QKeySequence(Qt.Key_Back)) + back_shortcuts.append(QKeySequence(Qt.Key.Key_Back)) self._history_back_action.setShortcuts(back_shortcuts) self._history_back_action.setIconVisibleInMenu(False) back_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoPrevious, @@ -367,8 +368,8 @@ class BrowserWindow(QMainWindow): navigation_bar.addAction(self._history_back_action) self._history_forward_action = QAction(self) - fwd_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.Forward)) - fwd_shortcuts.append(QKeySequence(Qt.Key_Forward)) + fwd_shortcuts = remove_backspace(QKeySequence.keyBindings(QKeySequence.StandardKey.Forward)) + fwd_shortcuts.append(QKeySequence(Qt.Key.Key_Forward)) self._history_forward_action.setShortcuts(fwd_shortcuts) self._history_forward_action.setIconVisibleInMenu(False) next_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoNext, @@ -384,7 +385,7 @@ class BrowserWindow(QMainWindow): self._url_line_edit = QLineEdit(self) self._fav_action = QAction(self) - self._url_line_edit.addAction(self._fav_action, QLineEdit.LeadingPosition) + self._url_line_edit.addAction(self._fav_action, QLineEdit.ActionPosition.LeadingPosition) self._url_line_edit.setClearButtonEnabled(True) navigation_bar.addWidget(self._url_line_edit) @@ -398,13 +399,13 @@ class BrowserWindow(QMainWindow): return navigation_bar def handle_web_action_enabled_changed(self, action, enabled): - if action == QWebEnginePage.Back: + if action == QWebEnginePage.WebAction.Back: self._history_back_action.setEnabled(enabled) - elif action == QWebEnginePage.Forward: + elif action == QWebEnginePage.WebAction.Forward: self._history_forward_action.setEnabled(enabled) - elif action == QWebEnginePage.Reload: + elif action == QWebEnginePage.WebAction.Reload: self._reload_action.setEnabled(enabled) - elif action == QWebEnginePage.Stop: + elif action == QWebEnginePage.WebAction.Stop: self._stop_action.setEnabled(enabled) else: print("Unhandled webActionChanged signal", file=sys.stderr) @@ -436,7 +437,7 @@ class BrowserWindow(QMainWindow): if not self.current_tab(): return search, ok = QInputDialog.getText(self, "Find", "Find:", - QLineEdit.Normal, self._last_search) + QLineEdit.EchoMode.Normal, self._last_search) if ok and search: self._last_search = search self.current_tab().findText(self._last_search) @@ -464,12 +465,12 @@ class BrowserWindow(QMainWindow): def handle_web_view_load_progress(self, progress): if 0 < progress and progress < 100: - self._stop_reload_action.setData(QWebEnginePage.Stop) + self._stop_reload_action.setData(QWebEnginePage.WebAction.Stop) self._stop_reload_action.setIcon(self._stop_icon) self._stop_reload_action.setToolTip("Stop loading the current page") self._progress_bar.setValue(progress) else: - self._stop_reload_action.setData(QWebEnginePage.Reload) + self._stop_reload_action.setData(QWebEnginePage.WebAction.Reload) self._stop_reload_action.setIcon(self._reload_icon) self._stop_reload_action.setToolTip("Reload the current page") self._progress_bar.setValue(0) @@ -485,12 +486,12 @@ class BrowserWindow(QMainWindow): def handle_dev_tools_requested(self, source): page = self._browser.create_dev_tools_window().current_tab().page() source.setDevToolsPage(page) - source.triggerAction(QWebEnginePage.InspectElement) + source.triggerAction(QWebEnginePage.WebAction.InspectElement) def handle_find_text_finished(self, result): sb = self.statusBar() if result.numberOfMatches() == 0: - sb.showMessage(f'"{self._lastSearch}" not found.') + sb.showMessage(f'"{self._last_search}" not found.') else: active = result.activeMatch() number = result.numberOfMatches() diff --git a/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui b/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui index a97f25b6..7e8c3607 100644 --- a/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui +++ b/examples/webenginewidgets/simplebrowser/certificateerrordialog.ui @@ -6,8 +6,8 @@ 0 0 - 370 - 141 + 689 + 204 @@ -26,7 +26,7 @@ Icon - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter
@@ -42,7 +42,7 @@ Error - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter true @@ -63,7 +63,7 @@ Do you wish to override the security check and continue ? - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter true @@ -73,7 +73,7 @@ Do you wish to override the security check and continue ? - Qt::Vertical + Qt::Orientation::Vertical @@ -86,10 +86,10 @@ Do you wish to override the security check and continue ? - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::No|QDialogButtonBox::Yes + QDialogButtonBox::StandardButton::No|QDialogButtonBox::StandardButton::Yes diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py index 7096b8b5..09dc0061 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWebEngineCore import QWebEngineDownloadRequest from PySide6.QtWidgets import QWidget, QFileDialog diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui index b7544ac1..aa3b7da2 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.ui @@ -20,7 +20,7 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint 0 @@ -43,18 +43,26 @@ } - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff true - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + 0 + 0 + 382 + 208 + + #m_items {background: palette(mid)} @@ -89,7 +97,7 @@ No downloads - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.py b/examples/webenginewidgets/simplebrowser/downloadwidget.py index 3b4973cb..39a11053 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.py +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from ui_downloadwidget import Ui_DownloadWidget diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.ui b/examples/webenginewidgets/simplebrowser/downloadwidget.ui index 47f62148..9017f204 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.ui +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.ui @@ -2,6 +2,14 @@ DownloadWidget + + + 0 + 0 + 144 + 103 + + #DownloadWidget { background: palette(button); @@ -11,7 +19,7 @@ - QLayout::SetMinAndMaxSize + QLayout::SizeConstraint::SetMinAndMaxSize @@ -27,7 +35,10 @@ - + + 0 + 0 + QPushButton { diff --git a/examples/webenginewidgets/simplebrowser/main.py b/examples/webenginewidgets/simplebrowser/main.py index 781ec29e..2602c5db 100644 --- a/examples/webenginewidgets/simplebrowser/main.py +++ b/examples/webenginewidgets/simplebrowser/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt WebEngineWidgets Simple Browser example from Qt v6.x""" @@ -33,8 +34,8 @@ if __name__ == "__main__": QLoggingCategory.setFilterRules("qt.webenginecontext.debug=true") s = QWebEngineProfile.defaultProfile().settings() - s.setAttribute(QWebEngineSettings.PluginsEnabled, True) - s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True) + s.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True) + s.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True) browser = Browser() window = browser.create_hidden_window() diff --git a/examples/webenginewidgets/simplebrowser/passworddialog.ui b/examples/webenginewidgets/simplebrowser/passworddialog.ui index bbf5004f..623dea57 100644 --- a/examples/webenginewidgets/simplebrowser/passworddialog.ui +++ b/examples/webenginewidgets/simplebrowser/passworddialog.ui @@ -20,7 +20,7 @@ Icon - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -60,17 +60,17 @@ - QLineEdit::Password + QLineEdit::EchoMode::Password - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.py b/examples/webenginewidgets/simplebrowser/tabwidget.py index bda321ac..4a5a933b 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.py +++ b/examples/webenginewidgets/simplebrowser/tabwidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from functools import partial @@ -27,14 +28,14 @@ class TabWidget(QTabWidget): self._profile = profile tab_bar = self.tabBar() tab_bar.setTabsClosable(True) - tab_bar.setSelectionBehaviorOnRemove(QTabBar.SelectPreviousTab) + tab_bar.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectPreviousTab) tab_bar.setMovable(True) - tab_bar.setContextMenuPolicy(Qt.CustomContextMenu) + tab_bar.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) tab_bar.customContextMenuRequested.connect(self.handle_context_menu_requested) tab_bar.tabCloseRequested.connect(self.close_tab) tab_bar.tabBarDoubleClicked.connect(self._tabbar_double_clicked) self.setDocumentMode(True) - self.setElideMode(Qt.ElideRight) + self.setElideMode(Qt.TextElideMode.ElideRight) self.currentChanged.connect(self.handle_current_changed) @@ -59,14 +60,14 @@ class TabWidget(QTabWidget): self.load_progress.emit(view.load_progress()) self.url_changed.emit(view.url()) self.fav_icon_changed.emit(view.fav_icon()) - e = view.is_web_action_enabled(QWebEnginePage.Back) - self.web_action_enabled_changed.emit(QWebEnginePage.Back, e) - e = view.is_web_action_enabled(QWebEnginePage.Forward) - self.web_action_enabled_changed.emit(QWebEnginePage.Forward, e) - e = view.is_web_action_enabled(QWebEnginePage.Stop) - self.web_action_enabled_changed.emit(QWebEnginePage.Stop, e) - e = view.is_web_action_enabled(QWebEnginePage.Reload) - self.web_action_enabled_changed.emit(QWebEnginePage.Reload, e) + e = view.is_web_action_enabled(QWebEnginePage.WebAction.Back) + self.web_action_enabled_changed.emit(QWebEnginePage.WebAction.Back, e) + e = view.is_web_action_enabled(QWebEnginePage.WebAction.Forward) + self.web_action_enabled_changed.emit(QWebEnginePage.WebAction.Forward, e) + e = view.is_web_action_enabled(QWebEnginePage.WebAction.Stop) + self.web_action_enabled_changed.emit(QWebEnginePage.WebAction.Stop, e) + e = view.is_web_action_enabled(QWebEnginePage.WebAction.Reload) + self.web_action_enabled_changed.emit(QWebEnginePage.WebAction.Reload, e) else: self.title_changed.emit("") self.load_progress.emit(0) diff --git a/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py index a963f0ac..3bbdcacb 100644 --- a/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py +++ b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'certificateerrordialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -23,13 +23,13 @@ class Ui_CertificateErrorDialog(object): def setupUi(self, CertificateErrorDialog): if not CertificateErrorDialog.objectName(): CertificateErrorDialog.setObjectName(u"CertificateErrorDialog") - CertificateErrorDialog.resize(370, 141) + CertificateErrorDialog.resize(689, 204) self.verticalLayout = QVBoxLayout(CertificateErrorDialog) self.verticalLayout.setObjectName(u"verticalLayout") self.verticalLayout.setContentsMargins(20, -1, 20, -1) self.m_iconLabel = QLabel(CertificateErrorDialog) self.m_iconLabel.setObjectName(u"m_iconLabel") - self.m_iconLabel.setAlignment(Qt.AlignCenter) + self.m_iconLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) self.verticalLayout.addWidget(self.m_iconLabel) @@ -40,7 +40,7 @@ class Ui_CertificateErrorDialog(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.m_errorLabel.sizePolicy().hasHeightForWidth()) self.m_errorLabel.setSizePolicy(sizePolicy) - self.m_errorLabel.setAlignment(Qt.AlignCenter) + self.m_errorLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) self.m_errorLabel.setWordWrap(True) self.verticalLayout.addWidget(self.m_errorLabel) @@ -52,7 +52,7 @@ class Ui_CertificateErrorDialog(object): sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth()) self.m_infoLabel.setSizePolicy(sizePolicy1) - self.m_infoLabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.m_infoLabel.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter) self.m_infoLabel.setWordWrap(True) self.verticalLayout.addWidget(self.m_infoLabel) @@ -63,8 +63,8 @@ class Ui_CertificateErrorDialog(object): self.buttonBox = QDialogButtonBox(CertificateErrorDialog) self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.No|QDialogButtonBox.Yes) + self.buttonBox.setOrientation(Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.No|QDialogButtonBox.StandardButton.Yes) self.verticalLayout.addWidget(self.buttonBox) diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py index f0f61aa7..51f46e08 100644 --- a/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py +++ b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'downloadmanagerwidget.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -28,7 +28,7 @@ class Ui_DownloadManagerWidget(object): "}") self.m_topLevelLayout = QVBoxLayout(DownloadManagerWidget) self.m_topLevelLayout.setObjectName(u"m_topLevelLayout") - self.m_topLevelLayout.setSizeConstraint(QLayout.SetNoConstraint) + self.m_topLevelLayout.setSizeConstraint(QLayout.SizeConstraint.SetNoConstraint) self.m_topLevelLayout.setContentsMargins(0, 0, 0, 0) self.m_scrollArea = QScrollArea(DownloadManagerWidget) self.m_scrollArea.setObjectName(u"m_scrollArea") @@ -36,12 +36,13 @@ class Ui_DownloadManagerWidget(object): " margin: 2px;\n" " border: none;\n" "}") - self.m_scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self.m_scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.m_scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) + self.m_scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.m_scrollArea.setWidgetResizable(True) - self.m_scrollArea.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop) + self.m_scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignTop) self.m_items = QWidget() self.m_items.setObjectName(u"m_items") + self.m_items.setGeometry(QRect(0, 0, 382, 208)) self.m_items.setStyleSheet(u"#m_items {background: palette(mid)}") self.m_itemsLayout = QVBoxLayout(self.m_items) self.m_itemsLayout.setSpacing(2) @@ -55,7 +56,7 @@ class Ui_DownloadManagerWidget(object): sizePolicy.setHeightForWidth(self.m_zeroItemsLabel.sizePolicy().hasHeightForWidth()) self.m_zeroItemsLabel.setSizePolicy(sizePolicy) self.m_zeroItemsLabel.setStyleSheet(u"color: palette(shadow)") - self.m_zeroItemsLabel.setAlignment(Qt.AlignCenter) + self.m_zeroItemsLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) self.m_itemsLayout.addWidget(self.m_zeroItemsLabel) diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py index 58c32fdf..5204b688 100644 --- a/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py +++ b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'downloadwidget.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -23,6 +23,7 @@ class Ui_DownloadWidget(object): def setupUi(self, DownloadWidget): if not DownloadWidget.objectName(): DownloadWidget.setObjectName(u"DownloadWidget") + DownloadWidget.resize(144, 103) DownloadWidget.setStyleSheet(u"#DownloadWidget {\n" " background: palette(button);\n" " border: 1px solid palette(dark);\n" @@ -30,7 +31,7 @@ class Ui_DownloadWidget(object): "}") self.m_topLevelLayout = QGridLayout(DownloadWidget) self.m_topLevelLayout.setObjectName(u"m_topLevelLayout") - self.m_topLevelLayout.setSizeConstraint(QLayout.SetMinAndMaxSize) + self.m_topLevelLayout.setSizeConstraint(QLayout.SizeConstraint.SetMinAndMaxSize) self.m_dstName = QLabel(DownloadWidget) self.m_dstName.setObjectName(u"m_dstName") self.m_dstName.setStyleSheet(u"font-weight: bold\n" diff --git a/examples/webenginewidgets/simplebrowser/ui_passworddialog.py b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py index 11e0c4a2..05bec7b5 100644 --- a/examples/webenginewidgets/simplebrowser/ui_passworddialog.py +++ b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'passworddialog.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -28,7 +28,7 @@ class Ui_PasswordDialog(object): self.gridLayout.setObjectName(u"gridLayout") self.m_iconLabel = QLabel(PasswordDialog) self.m_iconLabel.setObjectName(u"m_iconLabel") - self.m_iconLabel.setAlignment(Qt.AlignCenter) + self.m_iconLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) self.gridLayout.addWidget(self.m_iconLabel, 0, 0, 1, 1) @@ -60,14 +60,14 @@ class Ui_PasswordDialog(object): self.m_passwordLineEdit = QLineEdit(PasswordDialog) self.m_passwordLineEdit.setObjectName(u"m_passwordLineEdit") - self.m_passwordLineEdit.setEchoMode(QLineEdit.Password) + self.m_passwordLineEdit.setEchoMode(QLineEdit.EchoMode.Password) self.gridLayout.addWidget(self.m_passwordLineEdit, 2, 1, 1, 1) self.buttonBox = QDialogButtonBox(PasswordDialog) self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) + self.buttonBox.setOrientation(Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok) self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 2) diff --git a/examples/webenginewidgets/simplebrowser/webpage.py b/examples/webenginewidgets/simplebrowser/webpage.py index 2f2800a1..e5d9bc6a 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.py +++ b/examples/webenginewidgets/simplebrowser/webpage.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from functools import partial diff --git a/examples/webenginewidgets/simplebrowser/webpopupwindow.py b/examples/webenginewidgets/simplebrowser/webpopupwindow.py index fac27a61..fd82ed9f 100644 --- a/examples/webenginewidgets/simplebrowser/webpopupwindow.py +++ b/examples/webenginewidgets/simplebrowser/webpopupwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QLineEdit, QSizePolicy, QWidget, QVBoxLayout from PySide6.QtGui import QAction @@ -17,8 +18,8 @@ class WebPopupWindow(QWidget): self._fav_action = QAction(self) self._view = view - self.setAttribute(Qt.WA_DeleteOnClose) - self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -29,7 +30,7 @@ class WebPopupWindow(QWidget): self._view.setFocus() self._url_line_edit.setReadOnly(True) - self._url_line_edit.addAction(self._fav_action, QLineEdit.LeadingPosition) + self._url_line_edit.addAction(self._fav_action, QLineEdit.ActionPosition.LeadingPosition) self._view.titleChanged.connect(self.setWindowTitle) self._view.urlChanged.connect(self._url_changed) diff --git a/examples/webenginewidgets/simplebrowser/webview.py b/examples/webenginewidgets/simplebrowser/webview.py index e1282c1d..c7e57717 100644 --- a/examples/webenginewidgets/simplebrowser/webview.py +++ b/examples/webenginewidgets/simplebrowser/webview.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from functools import partial @@ -106,10 +107,10 @@ class WebView(QWebEngineView): self.handle_register_protocol_handler_requested) old_page.fileSystemAccessRequested.disconnect(self.handle_file_system_access_requested) - self.create_web_action_trigger(page, QWebEnginePage.Forward) - self.create_web_action_trigger(page, QWebEnginePage.Back) - self.create_web_action_trigger(page, QWebEnginePage.Reload) - self.create_web_action_trigger(page, QWebEnginePage.Stop) + self.create_web_action_trigger(page, QWebEnginePage.WebAction.Forward) + self.create_web_action_trigger(page, QWebEnginePage.WebAction.Back) + self.create_web_action_trigger(page, QWebEnginePage.WebAction.Reload) + self.create_web_action_trigger(page, QWebEnginePage.WebAction.Stop) super().setPage(page) page.create_certificate_error_dialog.connect(self.handle_certificate_error) page.authenticationRequired.connect(self.handle_authentication_required) diff --git a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py index 2db86599..b8477112 100644 --- a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py +++ b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 WebEngineWidgets Example""" diff --git a/examples/webview/minibrowser/doc/minibrowser.rst b/examples/webview/minibrowser/doc/minibrowser.rst new file mode 100644 index 00000000..334b95b9 --- /dev/null +++ b/examples/webview/minibrowser/doc/minibrowser.rst @@ -0,0 +1,8 @@ +Minibrowser Example +=================== + +Simple application that demonstrates how to use a QWebView modules with Qt Quick. + +.. image:: minibrowser.webp + :width: 800 + :alt: Minibrowser screenshot diff --git a/examples/webview/minibrowser/doc/minibrowser.webp b/examples/webview/minibrowser/doc/minibrowser.webp new file mode 100644 index 0000000000000000000000000000000000000000..b7aca890a775ebf5ccd687f0d26d1c67ca0570e8 GIT binary patch literal 125798 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
JmS=|wvRf*tX*v8$5;wIkMaU{BzW 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 P)RNgj=Kupx6NkU2Nljk)Gquzo< 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ⓈZy*Gr6x!@q6OtR{oC{~E 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 aH|D#sfB*mh003n$HN&>S;c9d6OLX%9 literal 0 HcmV?d00001 diff --git a/examples/webview/minibrowser/images/left-32.png b/examples/webview/minibrowser/images/left-32.png new file mode 100644 index 0000000000000000000000000000000000000000..28e4dda7a3fa89dc072348cc53ddd586d3529271 GIT binary patch literal 831 zcmV-F1Hk-=P)gtXXRv=BS}BdjnX0~#iBv!59r6HAZR-f z6PCu{N;eH;xELJ7vCU)3Q6zF)@7mwagRnhupou@Gfd?SY9)K31qgzdnEdf zpidmhN()HbQ1&6-*@qQ$VG5babX4KrMyV);HGp^ew<<;X6RVR0%|Y9(}~~tGOv?YjTi~5{+$Jp#gL#kaB4fI@ljontTjA8(GfOvTMPhDGiJ8&XX zWO6l*-B!fKQ#EP+)5Q+TiZ75@nn04aEp408rOfJ`Z$en=!u{r^t4g^Cz7~ z5+#;N+>3{of9WrysC-pswea*?B6C9qzpJ8wZqF-hg57MN7(I9m$$qtDhNe^w=-UJE zK0SEOcfl@aq9YUd!G4&?V%){< zwmGWw#w=mkTxPPwU8o6%CL53_ao`-{1}HcWoP=6ffpX}(3;T87*B1DgOY}{eG|&6I z&;S2Ehxd6tc+5*$i*jvaSvIg2s01p2yx`Ul&<->MZ7zG~@c%}jwy{hCtUx_btkG^S za0GC>?49C26R2%0D+1200XQ0w8kfDZe>nx+3g8ETzDEEjc(yNSOS=bZ8_Ny>C&J~a zrYuTxtrTVMBs)1DQ(Ovy0Mm0G?s^8ee(xe(LoLiL&O|eN*k$iLxhjDm+aHv&tmHgi zF0G?vV+A@X(rL5vKD@KzNRp2DSQ9`0afX)O?^p^fhO55fvUi?YmOwD(egAGsRF=F& z_4b3r>J2JO{)JiE`_FRm#yReJ1`s^VHJubge)={xT8g+l)P&FP4JWZ#nG;f2w)e;} zmyme#*-zMASPg&>0!fk(LNGYq!`E#eGwB;yF#$}5WPJ19Xo*=OSBKJ{R%OD~>^w~Q zrYeLG2q8iw`bK``cvIb~;6OBRIWeu#s-Rx}vngW-*5X%@BuVx2&Q5UZmk(JC%xf+K zgFYdexL!pd_`-#5|5EAO#OPu|H`=K~2*Lc~9G7mM*9@MM`V_U+&oL*YQn&95Hdu?sdQp7W}?KD3FPhD@*4I%M=9B`N9BHb6=sxT-*XgO@f*v6o8wb)*qm5Q z8LH}$ZLLep#!=}cG0uYANkdg|>E;D;)3%b4l%oQd4P~d~lb2SqVjh$iR55hFhv5f< zDqz{xsP`{!-M>b8!3z=1Xy|Xh2WPW`N#97+5O{oJI9nV{&rL2%pr!X)8as|LFxDN_ zM$g?28as~Bdi}IKLxc#l19)aesc&{LH8Y{|!qm(J&K8HJ#|I$YyfH#1gkWsywo=rt zB+x8N$ER=OY<&lxf9m0D=AYuE`#nadZfOFKjftZmeS66D$`JI8TvnDftK?hm*<`KRXkFE6OVppTD`4~$(2zeHPjGrAT&d>Hfz%rDMr1~ugtue2rh!Cu>#n^0-0}1?IQ}~Uu@o9s)>{hM zy{Vdtq8j326I3(9;?e@=uAZW~=bO;5vf@Cp(nuDg(_<@so#&rEz}$ilAp#^Pq+^Ur zj3~8$2+(t+}*_mf!%lh4HuoPmBw;-Xz=YPPkcaZ+kF1l~I zmwPX?4W%o2;t^KF&7b@a6M*2s62KO%LRJB`1&7c6C;H>`v9Essp6CN;Uj8?+00000 LNkvXXu0mjfXhCIP literal 0 HcmV?d00001 diff --git a/examples/webview/minibrowser/images/right-32.png b/examples/webview/minibrowser/images/right-32.png new file mode 100644 index 0000000000000000000000000000000000000000..00ccf43b57f5a4f253bb8af1a680818003a19d7d GIT binary patch literal 822 zcmV-61Ihe}P)eo} zK~z}7&6mGVQ&AMhKlinZlO}4E5TOGaqNSD?yGW~|7#TFM$fQ+Zuz~askT@yEnZ(o} z5{eT{#2*165THyA5lTs8$|4w|gCisKI{04m%6ol35CS*p(0k53-|su;d(XWW4tWSS z&aq^u0tf?5Km*`YO+SDIUs@n*-v&Od^_=dnOP|hRy?H zE`ZI5v?QYGwQ>%M1^5FX{~zF}Yx8O>=LBNOP!}+=k6Ig1W7?NMWjk^}qHjw?)1zex zs5Q@9MX0R^;jg@AnE$i2$?W%0CnPTDH6eDXebuh!itx}8c}cM9rolF~`hl^fH%^4< z*LH_q1C34uSCBglrk<18m^0X#{10iV?{Xr{Ml%uiIW6Q)VVn1<9x@wq2q8*-cj_K- zzdmLcqKM_ub-34V)Ffqr<)5=uo~YsMabJn8=1dSpc**}>EVumh)z-@X#j(vTEd$}b zNP4HR&A|LKG8=P9S=#WL15daYd}_5k4B*wur?=Es-*QFd+xj%sr!H~)*vXRlyLFF| zvcTs|zuB@TU{oW}ptp2JdJ#e(r9=o}l78cSIp5|$JAfI927Ly65-*BSUxM+K!Ez#8&=Z&fIyKAE`m|$kkK&~>@~UJ9Xy6msp>p6FX0ZgRz0^+w z51fk_Y!lfbJ}tksTAnHrQ15U#x8NP&UM^Q%H?-rgt4TZXoOW;2?}E5n@L=@~gKaW9 z%*UmKU5L2ZVKn0;3ySd29)4i}mr^pB9d>#g&m6rA$zff|44qJQ=sp1O?%m*)?}B~K zM4K7-(SDeym$LXPuzh_7hv=ejUk^lez07*qoM6N<$f@cbL AEdT%j literal 0 HcmV?d00001 diff --git a/examples/webview/minibrowser/images/settings-32.png b/examples/webview/minibrowser/images/settings-32.png new file mode 100644 index 0000000000000000000000000000000000000000..948d90e10a5eca169c2c6ec34339272c8f616674 GIT binary patch literal 5947 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 Gxc>k<3{nvQ literal 0 HcmV?d00001 diff --git a/examples/webview/minibrowser/images/stop-32.png b/examples/webview/minibrowser/images/stop-32.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5fb8b61f1ea06f7d077e267ea63bc2257b0239 GIT binary patch literal 639 zcmV-_0)YLAP)v+9`S<_;0t`t+ zK~z}7&6lxDBS93#zj?cmg`}{N1B-}PSllTr*jTMLW(ioUm2w0*u+j+r5u!=pKoAkJ z5cG-zD=`O0*y=z6DZDEzgrpGBHo`6>GZyZI_1thqH-5Rs-{xGD$#(xq`@XPSnty3hNaP>>@J!U%9={i#?b(rm9~ag0whkPuS~JPA zD0Z{#Y7;CK%n26E_K#)Jc-NV~GBK|wm@cbBm(LB4WziI&a54-`7dtbiluCiSZfIf} z9?JsQdR7HPmwZ!I_Y1N}cDSxv|Bnm%YnIs(V0dgv^*=x|2>=oglvXW3GM@xg8>7kv z_)L;#qWnMXGpX0}0LkW{oa{3R0P4f~m1@3ZJ_(5PGvxw&CINsO6Njn=NG1V5Cht^~ z=79zw=f+kQ+>FtNDt?fq2Q0}!L+IeiT^GMBE3)X_n8>}D375AWUNf29?dWSJvxAfJ zvytrdclFwy9T`a6iOFujZ^x;y_|4hrtD!Ws6))_T=5NQT5M|_H@V>a1^!L?F#pSf; Z>k$W1?IS-DM-Bi0002ovPDHLkV1hfE8iD`- literal 0 HcmV?d00001 diff --git a/examples/webview/minibrowser/main.py b/examples/webview/minibrowser/main.py new file mode 100644 index 00000000..24ca3e84 --- /dev/null +++ b/examples/webview/minibrowser/main.py @@ -0,0 +1,60 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from pathlib import Path + +from PySide6.QtCore import QCoreApplication, QUrl, QRect, QPoint +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtWebView import QtWebView +import argparse + +import rc_qml # noqa: F401 + + +class Utils: + @staticmethod + def fromUserInput(userInput): + if not userInput: + return QUrl.fromUserInput("about:blank") + result = QUrl.fromUserInput(userInput) + return result if result.isValid() else QUrl.fromUserInput("about:blank") + + +if __name__ == "__main__": + QtWebView.initialize() + app = QGuiApplication(sys.argv) + QGuiApplication.setApplicationDisplayName(QCoreApplication + .translate("main", "QtWebView Example")) + + parser = argparse.ArgumentParser(description=QGuiApplication.applicationDisplayName()) + parser.add_argument("--url", nargs="?", + default="https://www.qt.io", + help="The initial URL to open.") + args = parser.parse_args() + initialUrl = args.url + + engine = QQmlApplicationEngine() + context = engine.rootContext() + context.setContextProperty("utils", Utils()) + context.setContextProperty("initialUrl", Utils.fromUserInput(initialUrl)) + + geometry = QGuiApplication.primaryScreen().availableGeometry() + if not QGuiApplication.styleHints().showIsFullScreen(): + size = geometry.size() * 4 / 5 + offset = (geometry.size() - size) / 2 + pos = geometry.topLeft() + QPoint(offset.width(), offset.height()) + geometry = QRect(pos, size) + + engine.setInitialProperties({"x": geometry.x(), "y": geometry.y(), + "width": geometry.width(), "height": geometry.height()}) + qml_file = Path(__file__).parent / "main.qml" + engine.load(QUrl.fromLocalFile(qml_file)) + + if not engine.rootObjects(): + sys.exit(-1) + + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/webview/minibrowser/main.qml b/examples/webview/minibrowser/main.qml new file mode 100644 index 00000000..f647c626 --- /dev/null +++ b/examples/webview/minibrowser/main.qml @@ -0,0 +1,153 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtWebView +import QtQuick.Layouts + + +ApplicationWindow { + id: window + visible: true + title: webView.title + + menuBar: ToolBar { + id: navigationBar + RowLayout { + anchors.fill: parent + spacing: 0 + + ToolButton { + id: backButton + icon.source: "qrc:/left-32.png" + onClicked: webView.goBack() + enabled: webView.canGoBack + Layout.preferredWidth: navigationBar.height + } + + ToolButton { + id: forwardButton + icon.source: "qrc:/right-32.png" + onClicked: webView.goForward() + enabled: webView.canGoForward + Layout.preferredWidth: navigationBar.height + } + + ToolButton { + id: reloadButton + icon.source: webView.loading ? "qrc:/stop-32.png" : "qrc:/refresh-32.png" + onClicked: webView.loading ? webView.stop() : webView.reload() + Layout.preferredWidth: navigationBar.height + } + + Item { Layout.preferredWidth: 5 } + + TextField { + Layout.fillWidth: true + id: urlField + inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhPreferLowercase + text: webView.url + onAccepted: webView.url = utils.fromUserInput(text) + } + + Item { Layout.preferredWidth: 5 } + + ToolButton { + id: goButton + text: qsTr("Go") + onClicked: { + Qt.inputMethod.commit() + Qt.inputMethod.hide() + webView.url = utils.fromUserInput(urlField.text) + } + } + + ToolButton { + id: settingsButton + icon.source: "qrc:/settings-32.png" + onClicked: { + settingsDrawer.width = (settingsDrawer.width > 0) ? 0 : window.width * 1/4 + } + Layout.preferredWidth: navigationBar.height + } + + Item { Layout.preferredWidth: 10 } + } + ProgressBar { + id: progress + anchors { + left: parent.left + top: parent.bottom + right: parent.right + leftMargin: parent.leftMargin + rightMargin: parent.rightMargin + } + height:3 + z: Qt.platform.os === "android" ? -1 : -2 + background: Item {} + visible: Qt.platform.os !== "ios" && Qt.platform.os !== "winrt" + from: 0 + to: 100 + value: webView.loadProgress < 100 ? webView.loadProgress : 0 + } + } + + Item { + id: settingsDrawer + anchors.right: parent.right + ColumnLayout { + Label { + text: "JavaScript" + } + CheckBox { + id: javaScriptEnabledCheckBox + text: "enabled" + onCheckStateChanged: webView.settings.javaScriptEnabled = (checkState == Qt.Checked) + } + Label { + text: "Local storage" + } + CheckBox { + id: localStorageEnabledCheckBox + text: "enabled" + onCheckStateChanged: webView.settings.localStorageEnabled = (checkState == Qt.Checked) + } + Label { + text: "Allow file access" + } + CheckBox { + id: allowFileAccessEnabledCheckBox + text: "enabled" + onCheckStateChanged: webView.settings.allowFileAccess = (checkState == Qt.Checked) + } + Label { + text: "Local content can access file URLs" + } + CheckBox { + id: localContentCanAccessFileUrlsEnabledCheckBox + text: "enabled" + onCheckStateChanged: webView.settings.localContentCanAccessFileUrls = (checkState == Qt.Checked) + } + } + } + + WebView { + id: webView + url: initialUrl + anchors.right: settingsDrawer.left + anchors.left: parent.left + height: parent.height + onLoadingChanged: function(loadRequest) { + if (loadRequest.errorString) + console.error(loadRequest.errorString); + } + + Component.onCompleted: { + javaScriptEnabledCheckBox.checkState = settings.javaScriptEnabled ? Qt.Checked : Qt.Unchecked + localStorageEnabledCheckBox.checkState = settings.localStorageEnabled ? Qt.Checked : Qt.Unchecked + allowFileAccessEnabledCheckBox.checkState = settings.allowFileAccess ? Qt.Checked : Qt.Unchecked + localContentCanAccessFileUrlsEnabledCheckBox.checkState = settings.localContentCanAccessFileUrls ? Qt.Checked : Qt.Unchecked + } + } +} diff --git a/examples/webview/minibrowser/minibrowser.pyproject b/examples/webview/minibrowser/minibrowser.pyproject new file mode 100644 index 00000000..46617061 --- /dev/null +++ b/examples/webview/minibrowser/minibrowser.pyproject @@ -0,0 +1,4 @@ +{ + "files": ["main.py", "main.qml", "qml.qrc", "images/left-32.png", "images/right-32.png", + "images/refresh-32.png", "images/settings-32.png", "images/stop-32.png"] +} diff --git a/examples/webview/minibrowser/qml.qrc b/examples/webview/minibrowser/qml.qrc new file mode 100644 index 00000000..f3604716 --- /dev/null +++ b/examples/webview/minibrowser/qml.qrc @@ -0,0 +1,9 @@ + + + main.qml + images/left-32.png + images/stop-32.png + images/refresh-32.png + images/right-32.png + + diff --git a/examples/webview/minibrowser/rc_qml.py b/examples/webview/minibrowser/rc_qml.py new file mode 100644 index 00000000..104eadba --- /dev/null +++ b/examples/webview/minibrowser/rc_qml.py @@ -0,0 +1,368 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.7.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x02\x7f\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\ +\x00\x00\x00\x06bKGD\x00i\x00\xa1\x006za\ +\x0c\x8d\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\ +\xdf\x01\x1a\x09+7\xecd\xf9\xf8\x00\x00\x02\x0cID\ +ATX\xc3\xcd\x97\xb1K#A\x14\xc6\xbfy\xbb\x90\ +\x85\xa4\xb0\x91\x03\x8b\x88WX\xdc),\xd8X\xad6\ +f\x09X\xab\x95r\x049\xb0\xd2\x08\xfe\x11\xa2I\xe0\ +@\x10\x11\xb1\x10\xf4\x8a\x03+1\x07G\xd8\xea@\x02\ +)\xbc+,\x84\xa4\x10\xd16\xc2.$3\x16\xee\x84\ +\xf5\xdcpF7\x93L\xb9;\xcb\xef{\xdf\xbc\xf7\xf6\ +\x0d\xd0\xe3\xc5:\xd9\xbc\xbff\x99\x86\xc7\x96\x12\x0f\x94\ +NT\x1b\xa3h\xfa\xdfk\x10\xf5a\xfd\xaa\x1e\xe7g\ +nL\x1cf\xf2N%2\x01{\xeb\x16\x19\x1e\xcb\x0e\ +]\xf2\xcd\x16\xf0\x7fK\x83\xb8\x19\xa3\x0d7&r\xcb\ +\xdb\x0e\x7f\xb3\x80\x83Uk&Y\x11\xe7\xaf\x06\x87\x08\ +\xa9\x99\xcc\xfeRp~\xb6\xdbB\xed^\x1c\xadL\xe5\ +\x93eQ|3\x1c\x00\x9a`\xc9\xb2(\x1e\xadL\xe5\ +;r\xe0\xf8\xeb\xf4\xc9\xe0\xdf\xe6\x5c\x94\xc9v\xffI\ +\xfb\xbe\xb0[\x9a\x7fiRH\xe4\x1f\xfe\xf0L\xd4\xd9\ +\x1e\xbf\x17\x9f\xed\xd9\x91\x81\x1f\x17\xd5\xf3\xb6\x0e\x1c\xac\ +Z3\xc9\xb2(v\xb3\xecj\x13,\x15\xcc\x09\x16\xcc\ +\xf6\x8fe\xd1x\xd7\x99\xbf21\xaf'\x98.\xabC\ +\x97\xcf\x0d\x8fe\xd1\x14\xa1p2\x0cL\x17;3\xa6\ +\x94J\x81\xbbnhb\x1a\x1e\xcb\x02\xd8zV\x05C\ +\x97|SU\xf7\x0b\xb2Hv\xb8\xae[\xff\x8f\x0b\xfb\ +k\x96\xd9\x12`xlI\xf5?@2\x09\x00\x12\x0f\ +\x94V-@2\x9f\x04T\x1b\xa3\xca\x05\xf8L\x92g\ +\xa2\xfc?\xec3\xa9\xd7\xf3\x00\xc9\xe6\xa0\x9c\xec3\x09\ +\x00\xea\xc3\xfa\x95j\xbed>\x09\x88\xf33\xe5\x02|\ +&\x01\x80\x1b\x13\x87\xaa\x05H&\x01@&\xefT\x94\ +\xe6\x81\x06!\xe7\xc6V\x15\xdc\x8c\xd1\x86*~\x90\xa5\ +\x07,\xc9AC\xe8\xe0\xc9]\x17\xbf,+\xb2\xe8\xdd\ +\x98\xc8\xbd\x98\x09\x97\xb7\x1d^3\x99\xdd\xed\xe8k&\ +\xb3\x83\x93\xf2\xb3\x91\xec\xf4w\xf5\xda\x9e\x1d\x19H\xdc\ +\x89\xc9n\xc0o\xc7\xa9\xb0\xf8\xcd\xd9\xe9\xab\xa14\xb4\ +\x15/\xec\x96\xe6o\xc7\xa9\x10e\xe4a\xf0\xbe\xb8\x98\ +\xf4\xf7\xd5L\xc5\xe5\xb4\xe7\xeb\x11\x07R\xed#?\x12\ +G\x0e\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x05\x15\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\ +\x00\x00\x00\x06bKGD\x00\xd9\x00M\x00M\x0d\x89\ +\x85\xe9\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\ +\xdf\x01\x17\x0f!*\x83j\xd9\xc4\x00\x00\x04\xa2ID\ +ATX\xc3\xcd\x97[L\x93g\x18\xc7\x7f_)\x05\ +Z\x0a\x149\xaa\x84\x83\xe0&\xa2\x11g\xc0L\xdc\x92\ +ZL\x06\xd9\xc6\x95\xcb\x8e\xbd\xf1b\xa0u\xa7\xbb\x1d\ +b\xdc\xc5\xee\xb69\xaa\xf4\xc6,a\xd9\x5cf\xb2\xc4\ +]\xa8\x09\x87&\x1b\x90(q\xe0\x1c\xe2\x06(8\x0f\ +\x9c\x84ZX\x81r\xe8\xbb\x0b\xfbu\xdf\xd7\x16\xf8\x98\ +K\xf4M\x9a4\xcf\xfb\xbc\xcf\xff\xff>\x87\xf7y>\ +x\xccKZ\x8brmcY6\xb0\x17\xa8\x04*\x80\ +\xbc\xe0\xd6\x10\xd0\x0e4\x03m.{\xe7\xf0\xffF\xa0\ +\xb6\xb1L\x02\xac@=P\xac\xd1n/p\x04p\xbb\ +\xec\x9d\xe2?\x13\xa8m,+\x02\xce\xae\x018\x1a\x91\ +\x1a\x97\xbd\xb3\x7f9\x05\xdd\x0a\xe0\x07\x80\xbeG\x00'\ +x\xb6/hK\xbb\x07j\x1b\xcb\x0e\x01'\xc2\xe5\xa9\ +\xa6,Js\xad\x14e\xee$;9\x1fS\x5c\x0a\x82\ +\x00\xd3s\x1e\xeez\x06\xb8~\xef\x22]C-\xcc-\ +\xceD3{\xd8e\xef<\xb9*\x81 \xdb\x1f\x94\xb2\ +\xac\xe4<^.\xad\xa3dc\x05:)\xd2i\xb3\xf3\ +>\xbc\xb3\xe3H\x92\x8e\xf8X\x13?\xffq\x86\x96\xde\ +\xefX\x0a,\x86\xab\xbe\xe2\xb2w\x9eY\x96@0\xe6\ +}\xffnJT\x96\xbcE\xf5\xf6\x83\xc4\xea\x0d*K\ +\xfe\x85Y\xda\xfb\xcfr\xf1\xc69\xeey\x06\x10<\xcc\ +5\x9d\x14C~\xfa66Z\x8a\xb8<\xd4\x84\xcf\xef\ +\x0d'\xb1Y\x99\x13RX\xb6\xf7\xc81\x97\x90x\xf3\ +\xd9O\xd8]X\x0d\x80\x10\x02I\x92\x10B08\xde\ +\xc3\xd7m\x1f3\xe9\x1bY1\x01L\x86d|\xf3\xde\ +h\x89Y\x22W\x87\xd2\x9fVe\xc2\xd5\xec!\x9a\xb8^E \xf8\xc2\x85n\ +\xffR\xe9\xdb\xc4\xe8bB7\x97o\x7f\xfe\xb7S\xd1\ +b\xba\xec*H\xdf\xce\xa75?\x92\x9f\xb6-\xc2\x0b\ +A\xcc\x90\x07\xf6\xca;\x99I\xb9\x14f\xecP\xb9]\ +\x08\xc1\xfc\xe2\x1c\x97n\x9e\xd7\x0c\x9e\x93\xfa\x14\xb5\xd6\ +\xcf1'\xa4R\xb7\xef\x0b6X\x8a\xc2U\xf6*\x09\ +T\x86R4kWT\x837\xc7\xaf\xb2\xb0\xe4\xd7\x04\ +\x9e\x99\x94\xcb!\xeb\x97\x18\x0df\x00\x8c\x063\x8e}\ +_\x91n\xceQ\xaaU*\x09T\xc8\xd2\x8c\xa4\x9c\x88\ +\xd8K\x92\xc4\xf8\xf4\x1dM\xe0\xa9\xa6,\x1c6'I\ +\xc6u\xaa\xf3I\xc6u\x1c\xa9tb1f\xa2\xc4\x94\ +\x09\xe4\xed\xdb\xf2\x1a\xf6=G)\xd9\xb0G\xe5~y\ +\x15f\x94b\xdfs\x14[\xf1\x1b\xcb\x82\x9b\xe3Sq\ +\xd8\x9cXL\x19\xaa\xea\x91m\xadK\xcc\xc6Q\xe9$\ +1\xce\x82\xdcICUp\xe9\xe6\x05r\xd3\xb6\x92\x91\ +\x94\xa3\x02\x97\x0de\xa7\xe4\x93\x97V\xb2b\x1e\x94\x17\ +T1t\xbf\x87\xe1\x07\x83*`\xd9\xd6\xa8\xf7/n\ +\xdd\xbfFy\xc1\x0b\x11\xcdh\xe8o\xbf\x07g\xb3\x83\ +I\xdfH\xd4\x10x|c8[\x1cL\xcfM.K\ +\xa0\xa5\xf7[\x1a;\x8e10\xd6\x1d\xd5F\xcf\xdd\x0e\ +\x1a;\x8e\xd1z\xfd\xb4|\x18\xf4\x09\xcc/\xcej\ +\x067\xc5%\xf3\xd1\x8b\xa7I1\xa6\xa9\xe4K\x81%\ +\x8e7\xd5qc\xecJ\xa8!\xb9\xec\x9d[\xc3{\xc1\ +\x91p\x83k\x01\xd7\xeb\x0c\x1c|\xee3R\x8ci\x11\ +\xaew\xf7~\xaf\x04Wa)\x09\xb8\x83\xad2\xa2\xa5\ +jy|\xde\xdd\xdf\xc0\xe6\xacgT\xb5\x0fpq\xe0\ +\x1cg\xbbN\x84\xb7c\xb7\xa6\x81Dv\xeb\xae\xbc\xfd\ +\xdc\xf1\xf438\xfe;\x01\xb1\x14\x1aV\xd6[\x0a\xd9\ +\xbd\xa9\x9a\x8a\xa2\x1a\xe2b\x13T3\xc3\xe2\xd2\x02\xe7\ +\xae\x9e\xa2\xb9\xe7\x9b\xd0\xb0\xb2\xe2@\xb2\xd2H\x16\xa3\ +\xd3c+~\x9d\xe7\x9f>\xc0\xdc\x82\x0f!\x02$'\ +\xa4c\x8cK\x8c(\xb5\x80\x08\xd0s\xa7\x9d\x9f\xba\x1b\ +\x18\xf1\x0e\xadm$[m(\x8d\xd7\x1b\xd9\x99gc\ +\xcb\xfa\xddl\xb0\x14b\x8e\xb7 \xa1\xc3\xe7\x7f\xc0\xb0\ +w\x90\xfe\xd1.\xbao\xb9\x97{/\xb4\x0d\xa5+y\ +\xe2\x11V\xc4\xcd\x9f\xfc\x0f\x13\x80\xe0\xc1\x12\xc0\x16\xad\ +BV\x01\xb6\x05\x87\xcf\xfe'\xfa\xe3\xf4\xb1\xaf\x7f\x00\ +\x9e\xe8\x03h^\xfe7\xb1\x00\x00\x00\x00IEND\ +\xaeB`\x82\ +\x00\x00\x03?\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\ +\x00\x00\x00\x06bKGD\x00i\x00\xa1\x006za\ +\x0c\x8d\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\ +\xdf\x01\x17\x0f)\x18\x83d\x02L\x00\x00\x02\xccID\ +ATX\xc3\xcd\x97\xcfKTQ\x14\xc7?o&\x89\ +\x22P\xc8~)\xd9\xd0/$tF\x03g\xe5d\x98\ +#I?\xd0\x8d\xd2\x22'\xc5\xa2A\xe9\x0f\xe8\xc7\xa6\ + h;\x10\x13\x96\xc6\xe0J7\x0dd\xb8\x18\x1c\xc4\ +\xb1\xcdc\xcaQ$r\x5c\xe4 \xa6\x1b\x17B\xe0F\ +}-z\xef\xf1\xe6\xbd\xf9\xf1\xde\xa8\xe9Y\xbdw\xee\ +\xb9\xf7\xfb\xe5\x9c\xef\xbd\xf7\x5c\xd8g\x13\xac\x04\xfbC\ +\xee3\x80\x07\xf0\x02\xf5\x80C\x1eZ\x04\xa6\x80\x08\x10\ +\x0b\xfa\xc4\x95]#\xe0\x0f\xb9\x05\xa0\x11\x08\x00WL\ +\xae\xfb\x03x\x02D\x83>Q*\x98\x80?\xe4\xbe\x04\ +\x84-\x00g\x22\xd2\x1a\xf4\x89\x0b\xd9\x02l9\xc0\xdb\ +\x81\xe4\x0e\xc0\x91\xe7&\xe5\xb5\xcc\x13\xf0\x87\xdc\xbd\xc0\ +\xf0.jmX^3\x7f\x09d\xb6\xc3{$\xfa\x8e\ +\xa0O\x1c\xc9J@\xaey2\xdf*\xd7+\xdb)=\ +Vn\xf0\xcf-\x7f\xe5\xe7\x8a\x98o\xfae\xad&\x04\ +\x9d\xda\xe7\xf2\xd5\xbc\xb5\xb6\x17o\xd5}\x04!=y\ +\xd3\xa9(\x03\x93\xcf\xd9\x96\xb6\xcc\x08\xb3J\xd9\x1dZ\ +\x0d4\x9a\x01o\xae\xee4\x80'R\x13\x0c\xc6^\x98\ +\x01W\x84\xd9\x98I\x84\x81\x5c\xb3\xee\xd6\xfai\xae\xee\ +4\xf8\x13\xa9\x09\x06b\xcf\xd8\xda\xde\xb4\xa2\x85@Z\ +\x09\xe4\x13\xeew\xb6\xe8;5\x8f\xb9Y\xfd\xc0\xe0\x9f\ +]\x9a\xe4\xc3\xe4S\xab\xe0\x8a\x95\x05}\xe2\x8a\x92\x01\ +\xcf\x7f\x06W1\x0f\xc9?\xdeL\x11\xb7]\x8fTp\ +A\x10\x90\xa4\x7f\xa7\xea\xfa\xc6\x1a\xdfS\xe38\xcf6\ +XB\x9cN\x8dk\x7f\xbd\xc0\x88B\xa0>\x13x\x8b\ +\xb3;Mp\xcaw\xc9\xd1R\xba\x1am\x9a\xfb\x5c\xb5/3\xef\x19\ +\x9b\x1dTA\x14 \xedw6Bfc\x15L%\x03\ +S@\xa5vtt\xa6\x1f@-\x85\xd6\xd67\xd6\xf8\ +\xf4-\xc0f\xe1;@\xc1T\x09D\x80\x1e}\xc4\xe8\ +L?\x82`\xa3\xc5\xd9\x95\xe6/>r\x9c\xab\xe7n\ +\xect\x1bF\xb4%\x88e\x8b\xfa\x9cx\xc7\xd8\xecG\ +C\xaa]\x15\x0d\xf4\x5c{\x8d\xddV\xb0\x8cb\x00v\ +\x80xx\xf9O][y;p\x22Sdr5\x8e\ +\xddV\xc4\xc5S5i\xfe\xd3\xc5\x0e\xca\x8a/\x90X\ +\x9a@\x92\xb6-uJA\x9f\xf8F%\x00P\xd7V\ +>\x0ftf\x9b1\xbf\x1a\xa7\xc8v\x98\xf3'\x9dF\ +\x12%\x96I\xdc\x8b\x87\x97\x7f\xe9/\xa3\xa8|Uf\ +\xb5\xf0\xf4[\x22sC\x06\xbf\xab\xa2\x81n\xcf+l\ +\x82\xddl\x9f\x18=x\x0d\xc9~\xb5d\x86\xa6T\x0e\ +\xe8\xdb\x03\xf0>=x\xcew\xc1.g\xa2#\x13\xf8\ +\xc1~\x98\xc8\xe5X\x00\xaa\x80\xa6|;$\x03p\x93\ +\xdc|.\x1c\xe8\xc7\xe9\xbe\xdb_E\x87\x0e'\xe81\ +\xfc\xef\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x04\xcf\ +\x00\ +\x00\x141x\xda\xc5X\xddS\xe36\x10\x7f\xf7_\xa1\ +\xe6\xe1&\xe9L\x1c>\xae\xd3\x99\xb4\xf4\x06B\xb9\xa3\ +\x93\x9b\x03\x02\xe5\xfa(l%VQ$#\xc9\x04z\ +\xbd\xff\xbd+\xc9vl\xd9\x0e\xa1\xf4\xa8\x9f\xa2\xdd\xf5\ +\xee\xea\xb7\x9f\xceh\x84&\x22}\x94t\x91h\xd4\x9f\ +\x0c\xd0\xde\xce\xee\x8f\xe82!\xe8\x5c\x03g\x99b\xfe\ +\x88\xa6:\x0e\x83\xd1\x08\xcd\xce\x8e?\x0f\xa74\x22\x5c\ +\x91\xe1iL\xb8\xa6sJ\xe4\x18\xe5\xb4\x0b2\x1f\x9e\ +\xeb!\xbc\xb6$2\xa2\x98\xa1O\x17\xe8hv<\xdc\ +\x1fN\x18\xce\x14\x09\x02\xbaL\x85\xd4\xa0\xfc<\xa3\xd1\ +\xadw\x0c'\x82k)\x98Z\xd3\xaf\xc9\xcd\xef\x94\xac\ +|\xc1)~\x14\x99VA\x10\x1c\xa6)\xa3\x11\xd6T\ +\xf0k\xcac\xb1B_\x02\x04\x0f\x8d\xc7he\x09\xf6\ +xO\x15\xbdad\x8c\xb4\xcc\x88\xa5<\x8c\x11\xe5T\ +\x83\x97\x9f\xed\xf9\xb1<\xffa\xcf+\x1a\xeb\xa4\xa4]\ +\x9b\x93\xa5'\xc4`U2>\xd8\xa3\xe5h\xaa\x8d\x85\ +\x95s9\xb4\xc7\xc0r\x96\x84gG\x18\x80\xba\x14\x82\ +\xc1\x8f\xdc\xc7\xc2O\x8e\xef\xe9\xc2^\x01x%\xe7B\ +\xac\xdc5+\xd2\xe6\xc1\x95\xaf\xd0o\xdas\xd5]\xedN]\ +\xca~\xef\xbd\xe8\x0d6\xa5\x5cS\xb5y\x00\xc2\x0a\xe2\ +a\x04\x03\x8f\xea\x96dk\x91MhL:$\x9f\x06\ +\xb1\x88\x7f\xd8\x82\xa6\x05\xf4%\xf5\xad\x88\xd6PUj\ +\xfb\xa6W\xbc\xb1M\xed\xb6\x03Yh8\x96\x18\x121\ +\xb43\x17\xee\xdeo\xa5\xff\x82v\x06P\xf3;\xa8\x98\ +\xec9\xfd{\xb4;z\xfb\x04\x18\xafV\xfa\xbb;U\ +\xcb\x95\x9fgR,\xa0\xd1\xa9\xfa\xf8/\xd1Osv\ +\x9d\x93\xcf\xfa\x16\xf4\xcc\xf4-\xa6\x7fh\x0e\xcd4\x17\ +i)p# \xa6\xcb\x86\x88t\xabL.$\x1b7\ +/\x0c}\xc4rAy\xcd\x9c#\xb5k\xf4\xc4+\xb4\ +`S\x88\xf2\xcdj\xbfN\xfd\xcb\xf6\xb2\x94a\x0dC\ +y\x19\x0a\x85\x0e\x0e\x0eP\x0f\xf3X\x0a\x1a\xf7 !\ +\x86\xbb\x90\x11\xc3\xbd\xfakf\x97YH\x91q\xc0\xd6\ +E\xcc\xb3Vn\x84\x9e\xf6\xef\x8cv*T\x0f\xbdy\ +\xd3\xca\x83\xdc\x93\xda\xcbuS\xa6f\xdf\xf2\x02`\xd2\ +\xc1#\xdec\x96y#\xb2\xc8\x0c\xf4\xb3\x11\xafL\xb5\ +\x1a\xb3\xaa\xde]%\xcfKw\xbb\xa0\xad\x94]\xf9\x04\ +\xfe\xe6\xb8)\xec\x13\xc1\xb2%o]<\xa7\xf8\x86\xb0\ +\x96Tt\xfd\xb4\xf7\x1b\xbe\xc7\xb3H\xd2\xd4\x03\xa7\x8e\ +\xfb$!\xb0d\x8a\x87\x8e&\xf4g\xa9\xe4W\xb7\x04\ +\x15\xf2]V\xf3]\xa9\xb5\xf7\x98Wg\x1ak\x023\ +\x90/\xaas\xaa\x00(l\xd83\xdd'*_\x84\x5c\ +39`5\x91x\xb0\xe1^O\x803\x15\x11|\x0e\ +\xc1\x9a\x22\xf1\x82\xbc\x00\x1ff\xf4\xcc\x9c\x9a\xd7A\xa8\ +\xc5\xe27\xc2\xe8\x901\xf8|\x83=\x88 \x0c\x9b\x85\ +R/\xc0\x09\x1b]'\xa0\xea\xd0jz\x1d\xa8<\xa3\ +\xdf4\x95`\x1ak\xa8^\x04\x9f\x069Z\x0e\xb9\xab\ +\x8b\xa9zi~M\x9c\xee\x09\xe6\xee\x22\xe6J\xb0I\ +\xaaWL\xb8.\x17\xfe\x0d\xa6\xb5n\x99\xff\xa1\xe05\ +\xcc\xdc\x89\x92\x06\x8bV\xf9\x85\x7fU\xd9}\xbd\x0e\xea\ +\xad)\xb51\x5c\x88v\x0f\xea\xe2\x9f\x84\x9c\xe7m\x1e\ +\x82O\xddgN\x89\xd4<\xe3\x91YS\xfaf.\x5c\ +\x90\xbb\x8c(=\xf0\xe2H\xe7\xa8\xca\x0ea5\x11r\ +\xa6%\xe8i\xee\x8b\x90CJ0\xe2\x84:_\xfb)\ +hY\x84\xcc_C\x82\x1b\xb7!\xa8\xf0\x9b\x11\xdd\xdc\ +\xf2:\x9byX\x0d!\xda\xd0\x8c\xdfU\xc2\x8b\xec\xa8\ +\xbe\xe2\x91;\xd6,mh\x8b\x1d\xb6\xda\xda\xda\xb6\xd6\ +67\x97\x0e\x83~sx\xd6\xd5\xb6\xac\xc8Mw\xed\ +\xac\xa8m\x1c)J\xe8k\xf0\x0f\x96\x1c\x9e\xf1\ +\x00\x00\x036\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\ +\x00\x00\x00\x06bKGD\x00i\x00\xa1\x006za\ +\x0c\x8d\x00\x00\x00\x09pHYs\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07tIME\x07\ +\xdf\x01\x17\x0f(,\xbb\xcb\xc7\xb8\x00\x00\x02\xc3ID\ +ATX\xc3\xcd\x97\xbfOSQ\x14\xc7?\xf7\xb5\x8b\ +\x93&j\x94\x10\xa1\x03\x1a\xa2\xa5\x96\x18\xbbH\xab\xa2\ +\x18\x194\xb0\xc8\xa4U`\xb0\x81\xf4\x0f\x908)\xc6\ +\x99\xc4\xd4 \x12\x8a\x13L\xc4\x1f\x01!\x10\xa0L\x0d\ +\x11JIc\xca\x22\x18\xa2\x83##\xf4:\xf8^\xf3\ +\xca{}?\x10\x027\xe9\xd0{\xce=\xdf\xef;\xe7\ +{\xcf\xbd\x17\x0ey\x087\xce\xb1d\xa8\x02\x08\x03M\ +@\x03\xe0SM?\x80\x05`\x0aH%\xa2\xe9_\xfb\ +F \x96\x0c\x09\xa0\x11\xe8\x03.:\x8c\x9b\x03\xe2\xc0\ +L\x22\x9a\x96{&\x10K\x86\xce\x03c.\x80\xcd\x88\ +\xb4$\xa2\xe9\xb5r\x0e\x8a\x05\xf8\x03 \xff\x1f\xe0\xa8\ +k\xf3j,\xe7\x04b\xc9P\x170\xb2\x8fZ\x1bQ\ +c\xda\x97@e;r@\xa2oKD\xd3\xa3e\x09\ +\xa85\xcf[E\xa8\xad\x08\xe1\xaf\xbcf\x98\xff\xb3\xb5\ +\xc9\xec\xf7Q'$.\xe85!v\xa9}\xd5\xae\xe6\ +\x8a\xf0\xd0\x11yI\xb0\xea\xa6\xc16\xb5\xfa\x81\xb1\xa5\ +7N\x84\xe9\xd7v\x87^\x03\x8dN\x04W\x90;\x0c\ +\xa6\x9e\x93\xd9\x983\xd8\x9a\xfc\x0fi\xa9\xefr\x22\xcc\ +F3\x11\xf69-\xe4Na\x9b\xf7\xa9\x1e2\x1bs\ +\x08!J~w\xea\x1eq\xbf>f\x17\xa2\x88\xe5\xd1\ +u\xb8\xd7n\xd4$e\x81\xe5\x9f\xb3T\x9e\xa8\xe1\xec\ +q_\x89\xad\xe6L\x10Ex\xc9\xff^,\xb7\xfc\xf4\ +\xd5\xd6\xca\xfe\xc5\xb1\xcd--\x03\xe1\xbdHz\xa7\xb0\ +\xcd\xc0\xfc32\x1bsHY\xda\xf0\x9a\x03O\xb8\x17\ +|j\xb5<\x0c\xe0\xd5\xca\xa7\xb7\xd4W\xdfrE\xe4\ +\xdb\xfa4\xd5\xa7.q\xfc\xd8\xc9\x92\xf9\xbbu\x8f\x91\ +\xb2\xc0\xe7L\xbf\xd9\xb2&`T#\xd0\xa0\xb7tF\ +z\x11B \xa5D\x08a\x92~\xe3|9\xdf\xe6@\ +;\x80\x19\x89\x06}\x06|\xe5k\xfd/\xb5\x1a!k\ +]\x98\xfb6\x07\xda\x91H\xbed\xde\xe9\xdd}\x96g\ +\x813!J[RvC\xd1\x9d\xe7\xc6>\xad\xdb^\ +V\xff\xedl\xe3+\x83\xbb\xbf\xbe\x88\xa9\x95`\x01\xa8\ +\xd5,\x03\xf3=\xae\xbe\xc2\xabxi\xbd\x12/\x8aP\ +_\x82\xf1\x95\xc1r\x22\x5c\xd0\x13\x98\x02:5\xcb\xd2\ +\xfa\xb4cp\x8f\xe2\xa53\xf2\xaad\x07h\xe0\x13\xd9\ +\xa1r\xe0\x1af\xb1\x04\xa9\xbd\xd4O\x03\x0f\x9c\x8b\x18\ +l\x13\xd9!>-\xbf\xb5Z\x9e*\x12P\xefp9\ +\xb7\xe0\x1d\xe1^.W]7\xb4\xe3\xaf\xabI;\xf0\ +\x9cvo\xd4\xef\x82\xb8[\xf0`\xf5\x0d\x83m2;\ +\xcc\xc7\xa5\x84]\x88\xb8\xd9a4\xe3$\x0b\x8a\xf0\xd0\ +\x1e~a\x00\x97R2\x99\x1dvz\x1c\xcf\x1c\xbd\x0b\ +\xc9a]\xc9\x0c\x9dPu\xe8>\x00\xf0\xee\xdd\xe0\x96\ +\xef\x82}\xceD\x9b\x19\xf8\xd1~\x98\xa8\xe5X\x03\xfc\ +\xc0m\x97}\x22\xa7\xae\xf1[\x81\x1f\x89\xc7\xe9\xa1\x8f\ +\xbfgH\x11\xb4\x13s\xe2\x92\x00\x00\x00\x00IEN\ +D\xaeB`\x82\ +" + +qt_resource_name = b"\ +\x00\x0b\ +\x00\x90;'\ +\x00s\ +\x00t\x00o\x00p\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x0e\ +\x07\xd4\xda\x07\ +\x00r\ +\x00e\x00f\x00r\x00e\x00s\x00h\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x0b\ +\x00\x80-\xa7\ +\x00l\ +\x00e\x00f\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x08\ +\x08\x01Z\x5c\ +\x00m\ +\x00a\x00i\x00n\x00.\x00q\x00m\x00l\ +\x00\x0c\ +\x0fz\xe9\xa7\ +\x00r\ +\x00i\x00g\x00h\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00>\x00\x00\x00\x00\x00\x01\x00\x00\x07\x9c\ +\x00\x00\x01\x91\x08\xc1\x9ai\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x91\x08\xc1\x9ai\ +\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x02\x83\ +\x00\x00\x01\x91\x08\xc1\x9ai\ +\x00\x00\x00Z\x00\x01\x00\x00\x00\x01\x00\x00\x0a\xdf\ +\x00\x00\x01\x91\x08\xc1\x9ai\ +\x00\x00\x00p\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xb2\ +\x00\x00\x01\x91\x08\xc1\x9ai\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/widgetbinding/dialog.py b/examples/widgetbinding/dialog.py index 1bd12780..b7bd74ce 100644 --- a/examples/widgetbinding/dialog.py +++ b/examples/widgetbinding/dialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QDialog, QLineEdit, QVBoxLayout diff --git a/examples/widgetbinding/doc/widgetbinding.pyproject b/examples/widgetbinding/doc/widgetbinding.pyproject index e423ea34..da4219d8 100644 --- a/examples/widgetbinding/doc/widgetbinding.pyproject +++ b/examples/widgetbinding/doc/widgetbinding.pyproject @@ -1,10 +1,13 @@ { "files": ["../bindings.h", + "../bindings.xml", "../dialog.py", "../macros.h", "../main.py", "../registerwigglywidget.py", "../wigglywidget.cpp", "../wigglywidget.h", - "../wigglywidget.py"] + "../wigglywidget.py", + "../CMakeLists.txt", + "../../utils/pyside_config.py"] } diff --git a/examples/widgetbinding/main.py b/examples/widgetbinding/main.py index 2b24018b..3876b4c6 100644 --- a/examples/widgetbinding/main.py +++ b/examples/widgetbinding/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgetbinding/registerwigglywidget.py b/examples/widgetbinding/registerwigglywidget.py index c5560ba6..f172a94c 100644 --- a/examples/widgetbinding/registerwigglywidget.py +++ b/examples/widgetbinding/registerwigglywidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection from wigglywidget import WigglyWidget diff --git a/examples/widgetbinding/wigglywidget.py b/examples/widgetbinding/wigglywidget.py index 3362eab6..08c64a28 100644 --- a/examples/widgetbinding/wigglywidget.py +++ b/examples/widgetbinding/wigglywidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QBasicTimer, Property from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPalette diff --git a/examples/widgets/animation/animatedtiles/animatedtiles.py b/examples/widgets/animation/animatedtiles/animatedtiles.py index 02fc75bf..a014a2f4 100644 --- a/examples/widgets/animation/animatedtiles/animatedtiles.py +++ b/examples/widgets/animation/animatedtiles/animatedtiles.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys import math @@ -25,7 +26,7 @@ class Pixmap(QObject): super().__init__() self.pixmap_item = QGraphicsPixmapItem(pix) - self.pixmap_item.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.pixmap_item.setCacheMode(QGraphicsItem.CacheMode.DeviceCoordinateCache) def set_pos(self, pos): self.pixmap_item.setPos(pos) @@ -45,7 +46,7 @@ class Button(QGraphicsWidget): self._pix = pixmap self.setAcceptHoverEvents(True) - self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.setCacheMode(QGraphicsItem.CacheMode.DeviceCoordinateCache) def boundingRect(self): return QRectF(-65, -65, 130, 130) @@ -57,16 +58,16 @@ class Button(QGraphicsWidget): return path def paint(self, painter, option, widget): - down = option.state & QStyle.State_Sunken + down = option.state & QStyle.StateFlag.State_Sunken r = self.boundingRect() grad = QLinearGradient(r.topLeft(), r.bottomRight()) - if option.state & QStyle.State_MouseOver: - color_0 = Qt.white + if option.state & QStyle.StateFlag.State_MouseOver: + color_0 = Qt.GlobalColor.white else: - color_0 = Qt.lightGray + color_0 = Qt.GlobalColor.lightGray - color_1 = Qt.darkGray + color_1 = Qt.GlobalColor.darkGray if down: color_0, color_1 = color_1, color_0 @@ -74,12 +75,12 @@ class Button(QGraphicsWidget): grad.setColorAt(0, color_0) grad.setColorAt(1, color_1) - painter.setPen(Qt.darkGray) + painter.setPen(Qt.GlobalColor.darkGray) painter.setBrush(grad) painter.drawEllipse(r) - color_0 = Qt.darkGray - color_1 = Qt.lightGray + color_0 = Qt.GlobalColor.darkGray + color_1 = Qt.GlobalColor.lightGray if down: color_0, color_1 = color_1, color_0 @@ -87,7 +88,7 @@ class Button(QGraphicsWidget): grad.setColorAt(0, color_0) grad.setColorAt(1, color_1) - painter.setPen(Qt.NoPen) + painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(grad) if down: @@ -108,7 +109,7 @@ class Button(QGraphicsWidget): class View(QGraphicsView): def resizeEvent(self, event): super(View, self).resizeEvent(event) - self.fitInView(self.sceneRect(), Qt.KeepAspectRatio) + self.fitInView(self.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio) if __name__ == '__main__': @@ -187,10 +188,11 @@ if __name__ == '__main__': # Ui. view = View(scene) view.setWindowTitle("Animated Tiles") - view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) + view.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.BoundingRectViewportUpdate) view.setBackgroundBrush(QBrush(bg_pix)) - view.setCacheMode(QGraphicsView.CacheBackground) - view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) + view.setCacheMode(QGraphicsView.CacheModeFlag.CacheBackground) + view.setRenderHints(QPainter.RenderHint.Antialiasing + | QPainter.RenderHint.SmoothPixmapTransform) view.show() states = QStateMachine() @@ -202,7 +204,7 @@ if __name__ == '__main__': for i, item in enumerate(items): anim = QPropertyAnimation(item, b'pos') anim.setDuration(750 + i * 25) - anim.setEasingCurve(QEasingCurve.InOutBack) + anim.setEasingCurve(QEasingCurve.Type.InOutBack) group.addAnimation(anim) trans = root_state.addTransition(ellipse_button.pressed, ellipse_state) diff --git a/examples/widgets/animation/appchooser/appchooser.py b/examples/widgets/animation/appchooser/appchooser.py index 61005021..8386909f 100644 --- a/examples/widgets/animation/appchooser/appchooser.py +++ b/examples/widgets/animation/appchooser/appchooser.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -65,7 +66,7 @@ if __name__ == '__main__': p4.setGeometry(QRectF(0.0, 236.0, 64.0, 64.0)) scene = QGraphicsScene(0, 0, 300, 300) - scene.setBackgroundBrush(Qt.white) + scene.setBackgroundBrush(Qt.GlobalColor.white) scene.addItem(p1) scene.addItem(p2) scene.addItem(p3) @@ -73,12 +74,12 @@ if __name__ == '__main__': window = QGraphicsView(scene) window.setFrameStyle(0) - window.setAlignment(Qt.AlignLeft | Qt.AlignTop) - window.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - window.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + window.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) + window.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + window.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) machine = QStateMachine() - machine.setGlobalRestorePolicy(QStateMachine.RestoreProperties) + machine.setGlobalRestorePolicy(QStateMachine.RestorePolicy.RestoreProperties) group = QState(machine) selected_rect = QRect(86, 86, 128, 128) diff --git a/examples/widgets/animation/easing/easing.py b/examples/widgets/animation/easing/easing.py index ba503245..5ae9c0be 100644 --- a/examples/widgets/animation/easing/easing.py +++ b/examples/widgets/animation/easing/easing.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from enum import IntEnum import sys @@ -64,7 +65,7 @@ class Pixmap(QObject): super().__init__() self.pixmap_item = QGraphicsPixmapItem(pix) - self.pixmap_item.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.pixmap_item.setCacheMode(QGraphicsItem.CacheMode.DeviceCoordinateCache) def set_pos(self, pos): self.pixmap_item.setPos(pos) @@ -109,7 +110,7 @@ class Window(QWidget): self._ui.graphicsView.setScene(self._scene) self._anim = Animation(self._item, b'pos') - self._anim.setEasingCurve(QEasingCurve.OutBounce) + self._anim.setEasingCurve(QEasingCurve.Type.OutBounce) self._ui.easingCurvePicker.setCurrentRow(0) self.start_animation() @@ -139,15 +140,15 @@ class Window(QWidget): curve_scale = self._iconSize.height() / 2.0 - painter.setPen(Qt.NoPen) + painter.setPen(Qt.PenStyle.NoPen) # Start point. - painter.setBrush(Qt.red) + painter.setBrush(Qt.GlobalColor.red) start = QPoint(y_axis, x_axis - curve_scale * curve.valueForProgress(0)) painter.drawRect(start.x() - 1, start.y() - 1, 3, 3) # End point. - painter.setBrush(Qt.blue) + painter.setBrush(Qt.GlobalColor.blue) end = QPoint(y_axis + curve_scale, x_axis - curve_scale * curve.valueForProgress(1)) painter.drawRect(end.x() - 1, end.y() - 1, 3, 3) @@ -161,9 +162,9 @@ class Window(QWidget): curve_path.lineTo(to) t += 1.0 / curve_scale - painter.setRenderHint(QPainter.Antialiasing, True) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) painter.strokePath(curve_path, QColor(32, 32, 32)) - painter.setRenderHint(QPainter.Antialiasing, False) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) item = QListWidgetItem() item.setIcon(QIcon(pix)) @@ -182,15 +183,16 @@ class Window(QWidget): self._anim.setEasingCurve(curve_type) self._anim.setCurrentTime(0) - is_elastic = (curve_type.value >= QEasingCurve.InElastic.value - and curve_type.value <= QEasingCurve.OutInElastic.value) - is_bounce = (curve_type.value >= QEasingCurve.InBounce.value - and curve_type.value <= QEasingCurve.OutInBounce.value) + is_elastic = (curve_type.value >= QEasingCurve.Type.InElastic.value + and curve_type.value <= QEasingCurve.Type.OutInElastic.value) + is_bounce = (curve_type.value >= QEasingCurve.Type.InBounce.value + and curve_type.value <= QEasingCurve.Type.OutInBounce.value) self._ui.periodSpinBox.setEnabled(is_elastic) self._ui.amplitudeSpinBox.setEnabled(is_elastic or is_bounce) - self._ui.overshootSpinBox.setEnabled(curve_type.value >= QEasingCurve.InBack.value - and curve_type.value <= QEasingCurve.OutInBack.value) + overshoot = (curve_type.value >= QEasingCurve.Type.InBack.value + and curve_type.value <= QEasingCurve.Type.OutInBack.value) + self._ui.overshootSpinBox.setEnabled(overshoot) def path_changed(self, index): self._anim.set_path_type(index) diff --git a/examples/widgets/animation/easing/form.ui b/examples/widgets/animation/easing/form.ui index 364aebed..1a6340a9 100644 --- a/examples/widgets/animation/easing/form.ui +++ b/examples/widgets/animation/easing/form.ui @@ -29,16 +29,16 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QListView::Static + QListView::Movement::Static false - QListView::IconMode + QListView::ViewMode::IconMode false @@ -68,7 +68,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight Line @@ -77,7 +77,7 @@ true - buttonGroup + buttonGroup @@ -93,7 +93,7 @@ Circle - buttonGroup + buttonGroup @@ -113,7 +113,7 @@ - QFormLayout::AllNonFixedFieldsGrow + QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow @@ -238,7 +238,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/examples/widgets/animation/easing/ui_form.py b/examples/widgets/animation/easing/ui_form.py index 2925cbae..c879bb59 100644 --- a/examples/widgets/animation/easing/ui_form.py +++ b/examples/widgets/animation/easing/ui_form.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'form.ui' ## -## Created by: Qt User Interface Compiler version 6.7.0 +## Created by: Qt User Interface Compiler version 6.8.1 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -35,10 +35,10 @@ class Ui_Form(object): sizePolicy.setHeightForWidth(self.easingCurvePicker.sizePolicy().hasHeightForWidth()) self.easingCurvePicker.setSizePolicy(sizePolicy) self.easingCurvePicker.setMaximumSize(QSize(16777215, 120)) - self.easingCurvePicker.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.easingCurvePicker.setMovement(QListView.Static) - self.easingCurvePicker.setProperty("isWrapping", False) - self.easingCurvePicker.setViewMode(QListView.IconMode) + self.easingCurvePicker.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.easingCurvePicker.setMovement(QListView.Movement.Static) + self.easingCurvePicker.setProperty(u"isWrapping", False) + self.easingCurvePicker.setViewMode(QListView.ViewMode.IconMode) self.easingCurvePicker.setSelectionRectVisible(False) self.gridLayout.addWidget(self.easingCurvePicker, 0, 0, 1, 2) @@ -56,7 +56,7 @@ class Ui_Form(object): self.buttonGroup.addButton(self.lineRadio) self.lineRadio.setObjectName(u"lineRadio") self.lineRadio.setMaximumSize(QSize(16777215, 40)) - self.lineRadio.setLayoutDirection(Qt.LeftToRight) + self.lineRadio.setLayoutDirection(Qt.LayoutDirection.LeftToRight) self.lineRadio.setChecked(True) self.gridLayout_2.addWidget(self.lineRadio, 0, 0, 1, 1) @@ -80,7 +80,7 @@ class Ui_Form(object): self.groupBox.setSizePolicy(sizePolicy1) self.formLayout = QFormLayout(self.groupBox) self.formLayout.setObjectName(u"formLayout") - self.formLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) self.label = QLabel(self.groupBox) self.label.setObjectName(u"label") sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) @@ -90,7 +90,7 @@ class Ui_Form(object): self.label.setSizePolicy(sizePolicy2) self.label.setMinimumSize(QSize(0, 30)) - self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label) + self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.label) self.periodSpinBox = QDoubleSpinBox(self.groupBox) self.periodSpinBox.setObjectName(u"periodSpinBox") @@ -105,7 +105,7 @@ class Ui_Form(object): self.periodSpinBox.setSingleStep(0.100000000000000) self.periodSpinBox.setValue(-1.000000000000000) - self.formLayout.setWidget(0, QFormLayout.FieldRole, self.periodSpinBox) + self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.periodSpinBox) self.amplitudeSpinBox = QDoubleSpinBox(self.groupBox) self.amplitudeSpinBox.setObjectName(u"amplitudeSpinBox") @@ -115,13 +115,13 @@ class Ui_Form(object): self.amplitudeSpinBox.setSingleStep(0.100000000000000) self.amplitudeSpinBox.setValue(-1.000000000000000) - self.formLayout.setWidget(2, QFormLayout.FieldRole, self.amplitudeSpinBox) + self.formLayout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.amplitudeSpinBox) self.label_3 = QLabel(self.groupBox) self.label_3.setObjectName(u"label_3") self.label_3.setMinimumSize(QSize(0, 30)) - self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_3) + self.formLayout.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label_3) self.overshootSpinBox = QDoubleSpinBox(self.groupBox) self.overshootSpinBox.setObjectName(u"overshootSpinBox") @@ -131,13 +131,13 @@ class Ui_Form(object): self.overshootSpinBox.setSingleStep(0.100000000000000) self.overshootSpinBox.setValue(-1.000000000000000) - self.formLayout.setWidget(4, QFormLayout.FieldRole, self.overshootSpinBox) + self.formLayout.setWidget(4, QFormLayout.ItemRole.FieldRole, self.overshootSpinBox) self.label_2 = QLabel(self.groupBox) self.label_2.setObjectName(u"label_2") self.label_2.setMinimumSize(QSize(0, 30)) - self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_2) + self.formLayout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.label_2) self.verticalLayout.addWidget(self.groupBox) diff --git a/examples/widgets/animation/states/states.py b/examples/widgets/animation/states/states.py index 509c3a7a..730aab1b 100644 --- a/examples/widgets/animation/states/states.py +++ b/examples/widgets/animation/states/states.py @@ -1,6 +1,7 @@ # Copyright (C) 2010 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -64,7 +65,7 @@ if __name__ == '__main__': # Parent widget. widget = QGraphicsWidget() - layout = QGraphicsLinearLayout(Qt.Vertical, widget) + layout = QGraphicsLinearLayout(Qt.Orientation.Vertical, widget) layout.addItem(edit_proxy) layout.addItem(button_proxy) widget.setLayout(layout) diff --git a/examples/widgets/animation/states/states_rc.py b/examples/widgets/animation/states/states_rc.py index 8b87b5e5..ea7cfa0b 100644 --- a/examples/widgets/animation/states/states_rc.py +++ b/examples/widgets/animation/states/states_rc.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # Resource object code # diff --git a/examples/widgets/desktop/screenshot/screenshot.py b/examples/widgets/desktop/screenshot/screenshot.py index a5688da7..fba1f71b 100644 --- a/examples/widgets/desktop/screenshot/screenshot.py +++ b/examples/widgets/desktop/screenshot/screenshot.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/desktop/screenshot example from Qt v6.x""" @@ -20,8 +21,9 @@ class Screenshot(QWidget): self.screenshot_label = QLabel(self) - self.screenshot_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.screenshot_label.setAlignment(Qt.AlignCenter) + self.screenshot_label.setSizePolicy(QSizePolicy.Policy.Expanding, + QSizePolicy.Policy.Expanding) + self.screenshot_label.setAlignment(Qt.AlignmentFlag.AlignCenter) screen_geometry: QRect = self.screen().geometry() self.screenshot_label.setMinimumSize( @@ -55,7 +57,7 @@ class Screenshot(QWidget): save_screenshot_button.clicked.connect(self.save_screenshot) buttons_layout.addWidget(save_screenshot_button) quit_screenshot_button = QPushButton("Quit", self) - quit_screenshot_button.setShortcut(Qt.CTRL | Qt.Key_Q) + quit_screenshot_button.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_Q) quit_screenshot_button.clicked.connect(self.close) buttons_layout.addWidget(quit_screenshot_button) buttons_layout.addStretch() @@ -69,7 +71,7 @@ class Screenshot(QWidget): def resizeEvent(self, event): scaled_size = self.original_pixmap.size() - scaled_size.scale(self.screenshot_label.size(), Qt.KeepAspectRatio) + scaled_size.scale(self.screenshot_label.size(), Qt.AspectRatioMode.KeepAspectRatio) if scaled_size != self.screenshot_label.pixmap().size(): self.update_screenshot_label() @@ -84,14 +86,14 @@ class Screenshot(QWidget): @Slot() def save_screenshot(self): fmt = "png" # In order to avoid shadowing built-in format - initial_path = QStandardPaths.writableLocation(QStandardPaths.PicturesLocation) + initial_path = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.PicturesLocation) # noqa: E501 if not initial_path: initial_path = QDir.currentPath() initial_path += f"/untitled.{fmt}" fileDialog = QFileDialog(self, "Save As", initial_path) - fileDialog.setAcceptMode(QFileDialog.AcceptSave) - fileDialog.setFileMode(QFileDialog.AnyFile) + fileDialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) + fileDialog.setFileMode(QFileDialog.FileMode.AnyFile) fileDialog.setDirectory(initial_path) mime_types = [] @@ -100,7 +102,7 @@ class Screenshot(QWidget): fileDialog.setMimeTypeFilters(mime_types) fileDialog.selectMimeTypeFilter("image/" + fmt) fileDialog.setDefaultSuffix(fmt) - if fileDialog.exec() != QDialog.Accepted: + if fileDialog.exec() != QDialog.DialogCode.Accepted: return file_name = fileDialog.selectedFiles()[0] @@ -142,8 +144,8 @@ class Screenshot(QWidget): self.screenshot_label.setPixmap( self.original_pixmap.scaled( self.screenshot_label.size(), - Qt.KeepAspectRatio, - Qt.SmoothTransformation, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation, ) ) diff --git a/examples/widgets/desktop/systray/doc/systray.png b/examples/widgets/desktop/systray/doc/systray.png new file mode 100644 index 0000000000000000000000000000000000000000..5537c09fde5facc16c2d2128c1c96ff90647ed05 GIT binary patch literal 16137 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�X? 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. + +.. image:: systray.png + :align: center diff --git a/examples/widgets/desktop/systray/main.py b/examples/widgets/desktop/systray/main.py index c52c5138..38e7fbb2 100644 --- a/examples/widgets/desktop/systray/main.py +++ b/examples/widgets/desktop/systray/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgets/desktop/systray/window.py b/examples/widgets/desktop/systray/window.py index 0b5bc39d..ec725239 100644 --- a/examples/widgets/desktop/systray/window.py +++ b/examples/widgets/desktop/systray/window.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtGui import QAction, QIcon @@ -156,21 +157,21 @@ class Window(QDialog): self._type_label = QLabel("Type:") self._type_combo_box = QComboBox() - self._type_combo_box.addItem("None", QSystemTrayIcon.NoIcon) + self._type_combo_box.addItem("None", QSystemTrayIcon.MessageIcon.NoIcon) self._type_combo_box.addItem( - self.style().standardIcon(QStyle.SP_MessageBoxInformation), + self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxInformation), "Information", - QSystemTrayIcon.Information, + QSystemTrayIcon.MessageIcon.Information, ) self._type_combo_box.addItem( - self.style().standardIcon(QStyle.SP_MessageBoxWarning), + self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxWarning), "Warning", - QSystemTrayIcon.Warning, + QSystemTrayIcon.MessageIcon.Warning, ) self._type_combo_box.addItem( - self.style().standardIcon(QStyle.SP_MessageBoxCritical), + self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxCritical), "Critical", - QSystemTrayIcon.Critical, + QSystemTrayIcon.MessageIcon.Critical, ) self._type_combo_box.addItem(QIcon(), "Custom icon", -1) self._type_combo_box.setCurrentIndex(1) diff --git a/examples/widgets/dialogs/classwizard/classwizard.py b/examples/widgets/dialogs/classwizard/classwizard.py index 0efbde69..d7014313 100644 --- a/examples/widgets/dialogs/classwizard/classwizard.py +++ b/examples/widgets/dialogs/classwizard/classwizard.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import os from pathlib import Path @@ -64,9 +65,9 @@ class ClassWizard(QWizard): self._output_index = self.addPage(OutputFilesPage()) self.addPage(ConclusionPage()) - self.setPixmap(QWizard.BannerPixmap, + self.setPixmap(QWizard.WizardPixmap.BannerPixmap, QPixmap(':/images/banner.png')) - self.setPixmap(QWizard.BackgroundPixmap, + self.setPixmap(QWizard.WizardPixmap.BackgroundPixmap, QPixmap(':/images/background.png')) self.setWindowTitle("Class Wizard") @@ -217,7 +218,7 @@ class IntroPage(QWizardPage): super().__init__(parent) self.setTitle("Introduction") - self.setPixmap(QWizard.WatermarkPixmap, + self.setPixmap(QWizard.WizardPixmap.WatermarkPixmap, QPixmap(':/images/watermark1.png')) label = QLabel(INTRODUCTION) @@ -234,7 +235,7 @@ class ClassInfoPage(QWizardPage): self.setTitle("Class Information") self.setSubTitle("Specify basic information about the class for " "which you want to generate a skeleton source code file.") - self.setPixmap(QWizard.LogoPixmap, + self.setPixmap(QWizard.WizardPixmap.LogoPixmap, QPixmap(':/qt-project.org/logos/pysidelogo.png')) class_name_line_edit = QLineEdit() @@ -277,7 +278,7 @@ class QObjectPage(QWizardPage): self.setTitle("QObject parameters") self.setSubTitle("Specify the signals, slots and properties.") - self.setPixmap(QWizard.LogoPixmap, + self.setPixmap(QWizard.WizardPixmap.LogoPixmap, QPixmap(':/qt-project.org/logos/pysidelogo.png')) layout = QVBoxLayout(self) self._properties_chooser = PropertyChooser() @@ -295,7 +296,7 @@ class OutputFilesPage(QWizardPage): self.setTitle("Output Files") self.setSubTitle("Specify where you want the wizard to put the " "generated skeleton code.") - self.setPixmap(QWizard.LogoPixmap, + self.setPixmap(QWizard.WizardPixmap.LogoPixmap, QPixmap(':/qt-project.org/logos/pysidelogo.png')) output_dir_label = QLabel("&Output directory:") @@ -353,7 +354,7 @@ class ConclusionPage(QWizardPage): super().__init__(parent) self.setTitle("Conclusion") - self.setPixmap(QWizard.WatermarkPixmap, + self.setPixmap(QWizard.WizardPixmap.WatermarkPixmap, QPixmap(':/images/watermark1.png')) self.label = QLabel() @@ -367,7 +368,7 @@ class ConclusionPage(QWizardPage): layout.addWidget(self._launch_check_box) def initializePage(self): - finish_text = self.wizard().buttonText(QWizard.FinishButton) + finish_text = self.wizard().buttonText(QWizard.WizardButton.FinishButton) finish_text = finish_text.replace('&', '') self.label.setText(f"Click {finish_text} to generate the class skeleton.") self._launch_check_box.setChecked(True) diff --git a/examples/widgets/dialogs/classwizard/listchooser.py b/examples/widgets/dialogs/classwizard/listchooser.py index 6bf47c6d..f7239ae3 100644 --- a/examples/widgets/dialogs/classwizard/listchooser.py +++ b/examples/widgets/dialogs/classwizard/listchooser.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QRegularExpression, Property, Slot from PySide6.QtGui import QRegularExpressionValidator @@ -33,7 +34,8 @@ class ValidatingInputDialog(QDialog): self._form_layout.addRow(label, self._lineedit) layout.addLayout(self._form_layout) - bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) layout.addWidget(bb) bb.rejected.connect(self.reject) bb.accepted.connect(self.accept) diff --git a/examples/widgets/dialogs/extension/doc/extension.png b/examples/widgets/dialogs/extension/doc/extension.png new file mode 100644 index 0000000000000000000000000000000000000000..407af27cabad504c929e7fdf0b76d4b72bee2193 GIT binary patch literal 13523 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/dialogs/extension/doc/extension.rst b/examples/widgets/dialogs/extension/doc/extension.rst new file mode 100644 index 00000000..d64db233 --- /dev/null +++ b/examples/widgets/dialogs/extension/doc/extension.rst @@ -0,0 +1,11 @@ +Extension Example +================= + +This example demonstrates a PySide6 application that creates a find dialog with +an extendable interface, showcasing how to add and manage additional options +dynamically within a dialog. It demonstrates the analogous Qt example +`Extension Example `_. + +.. image:: extension.png + :width: 400 + :alt: extension screenshot diff --git a/examples/widgets/dialogs/extension/extension.py b/examples/widgets/dialogs/extension/extension.py index b8919f38..bed9a1a3 100644 --- a/examples/widgets/dialogs/extension/extension.py +++ b/examples/widgets/dialogs/extension/extension.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/dialogs/extension example from Qt v5.x""" @@ -32,9 +33,9 @@ class FindDialog(QDialog): more_button.setCheckable(True) more_button.setAutoDefault(False) - button_box = QDialogButtonBox(Qt.Vertical) - button_box.addButton(find_button, QDialogButtonBox.ActionRole) - button_box.addButton(more_button, QDialogButtonBox.ActionRole) + button_box = QDialogButtonBox(Qt.Orientation.Vertical) + button_box.addButton(find_button, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(more_button, QDialogButtonBox.ButtonRole.ActionRole) extension = QWidget() @@ -62,7 +63,7 @@ class FindDialog(QDialog): left_layout.addStretch(1) main_layout = QGridLayout(self) - main_layout.setSizeConstraint(QLayout.SetFixedSize) + main_layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize) main_layout.addLayout(left_layout, 0, 0) main_layout.addWidget(button_box, 0, 1) main_layout.addWidget(extension, 1, 0, 1, 2) diff --git a/examples/widgets/dialogs/licensewizard/licensewizard.py b/examples/widgets/dialogs/licensewizard/licensewizard.py index 28667d17..0fdeffbd 100644 --- a/examples/widgets/dialogs/licensewizard/licensewizard.py +++ b/examples/widgets/dialogs/licensewizard/licensewizard.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from enum import IntEnum @@ -31,7 +32,8 @@ class IntroPage(QWizardPage): path = Path(__file__).resolve().parent - self.setPixmap(QWizard.WatermarkPixmap, QPixmap(path / "images" / "watermark.png")) + self.setPixmap(QWizard.WizardPixmap.WatermarkPixmap, + QPixmap(path / "images" / "watermark.png")) self.top_label = QLabel( "This wizard will help you register your copy of " "Super Product One™ or start " @@ -165,7 +167,8 @@ class ConclusionPage(QWizardPage): self.setTitle("Complete Your Registration") path = Path(__file__).resolve().parent - self.setPixmap(QWizard.WatermarkPixmap, QPixmap(path / "images" / "watermark.png")) + self.setPixmap(QWizard.WizardPixmap.WatermarkPixmap, + QPixmap(path / "images" / "watermark.png")) self.bottom_label = QLabel() self.bottom_label.setWordWrap(True) @@ -206,14 +209,14 @@ class ConclusionPage(QWizardPage): def setVisible(self, visible: bool): super().setVisible(visible) if visible: - self.wizard().setButtonText(QWizard.CustomButton1, "&Print") - self.wizard().setOption(QWizard.HaveCustomButton1, True) + self.wizard().setButtonText(QWizard.WizardButton.CustomButton1, "&Print") + self.wizard().setOption(QWizard.WizardOption.HaveCustomButton1, True) if not self.custom_button_clicked_signal_connected: self.custom_button_clicked_signal_connected = True self.wizard().customButtonClicked.connect(self.print_button_clicked) else: - self.wizard().setOption(QWizard.HaveCustomButton1, False) + self.wizard().setOption(QWizard.WizardOption.HaveCustomButton1, False) if self.custom_button_clicked_signal_connected: self.custom_button_clicked_signal_connected = False @@ -250,12 +253,12 @@ class LicenseWizard(QWizard): self.setStartId(Pages.Page_Intro) if sys.platform == 'darwin': - self.setWizardStyle(QWizard.ModernStyle) + self.setWizardStyle(QWizard.WizardStyle.ModernStyle) - self.setOption(QWizard.HaveHelpButton, True) + self.setOption(QWizard.WizardOption.HaveHelpButton, True) path = Path(__file__).resolve().parent - self.setPixmap(QWizard.LogoPixmap, QPixmap(path / "images" / "logo.png")) + self.setPixmap(QWizard.WizardPixmap.LogoPixmap, QPixmap(path / "images" / "logo.png")) self.helpRequested.connect(self.show_help) self.setWindowTitle("License Wizard") diff --git a/examples/widgets/dialogs/licensewizard/main.py b/examples/widgets/dialogs/licensewizard/main.py index 26537ddf..4e216734 100644 --- a/examples/widgets/dialogs/licensewizard/main.py +++ b/examples/widgets/dialogs/licensewizard/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgets/dialogs/standarddialogs/doc/standarddialogs.png b/examples/widgets/dialogs/standarddialogs/doc/standarddialogs.png new file mode 100644 index 0000000000000000000000000000000000000000..6598710f74bab09303d096b6151532ba03ddce49 GIT binary patch literal 28634 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�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 literal 0 HcmV?d00001 diff --git a/examples/widgets/dialogs/standarddialogs/doc/standarddialogs.rst b/examples/widgets/dialogs/standarddialogs/doc/standarddialogs.rst new file mode 100644 index 00000000..83bc6ca4 --- /dev/null +++ b/examples/widgets/dialogs/standarddialogs/doc/standarddialogs.rst @@ -0,0 +1,8 @@ +Standard Dialogs Example +======================== + +The Standard Dialogs example shows the standard dialogs that are provided by +Qt. + +.. image:: standarddialogs.png + :align: center diff --git a/examples/widgets/dialogs/standarddialogs/standarddialogs.py b/examples/widgets/dialogs/standarddialogs/standarddialogs.py index ef677d5a..7bd68620 100644 --- a/examples/widgets/dialogs/standarddialogs/standarddialogs.py +++ b/examples/widgets/dialogs/standarddialogs/standarddialogs.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/dialogs/standarddialogs example from Qt v5.x""" @@ -20,13 +21,14 @@ from PySide6.QtWidgets import (QApplication, QColorDialog, QCheckBox, QDialog, class DialogOptionsWidget(QGroupBox): """Widget displaying a number of check boxes representing the dialog options.""" - def __init__(self, parent=None): + def __init__(self, zero_value, parent=None): super().__init__(parent) + self._zero_value = zero_value self._layout = QVBoxLayout(self) self._mapping = {} def value(self): - result = 0 + result = self._zero_value for checkbox, value in self._mapping.items(): if checkbox.isChecked(): result |= value @@ -47,7 +49,7 @@ class Dialog(QDialog): self._error_message_dialog = QErrorMessage(self) - frame_style = QFrame.Sunken | QFrame.Panel + frame_style = QFrame.Shadow.Sunken | QFrame.Shape.Panel self._integer_label = QLabel() self._integer_label.setFrameStyle(frame_style) @@ -72,27 +74,27 @@ class Dialog(QDialog): self._color_label = QLabel() self._color_label.setFrameStyle(frame_style) self._color_button = QPushButton("QColorDialog.get&Color()") - self._color_options = DialogOptionsWidget() + self._color_options = DialogOptionsWidget(QColorDialog.ColorDialogOption(0)) self._color_options.add_checkbox("Show alpha channel", - QColorDialog.ShowAlphaChannel) + QColorDialog.ColorDialogOption.ShowAlphaChannel) self._color_options.add_checkbox("No buttons", - QColorDialog.NoButtons) + QColorDialog.ColorDialogOption.NoButtons) self._font_label = QLabel() self._font_label.setFrameStyle(frame_style) self._font_button = QPushButton("QFontDialog.get&Font()") - self._font_options = DialogOptionsWidget() + self._font_options = DialogOptionsWidget(QFontDialog.FontDialogOption(0)) self._font_options.add_checkbox("Do not use native dialog", - QFontDialog.DontUseNativeDialog) + QFontDialog.FontDialogOption.DontUseNativeDialog) self._font_options.add_checkbox("Show scalable fonts", - QFontDialog.ScalableFonts) + QFontDialog.FontDialogOption.ScalableFonts) self._font_options.add_checkbox("Show non-scalable fonts", - QFontDialog.NonScalableFonts) + QFontDialog.FontDialogOption.NonScalableFonts) self._font_options.add_checkbox("Show monospaced fonts", - QFontDialog.MonospacedFonts) + QFontDialog.FontDialogOption.MonospacedFonts) self._font_options.add_checkbox("Show proportional fonts", - QFontDialog.ProportionalFonts) - self._font_options.add_checkbox("No buttons", QFontDialog.NoButtons) + QFontDialog.FontDialogOption.ProportionalFonts) + self._font_options.add_checkbox("No buttons", QFontDialog.FontDialogOption.NoButtons) self._directory_label = QLabel() self._directory_label.setFrameStyle(frame_style) @@ -110,20 +112,20 @@ class Dialog(QDialog): self._save_file_name_label.setFrameStyle(frame_style) self._save_file_name_button = QPushButton("QFileDialog.get&SaveFileName()") - self._file_options = DialogOptionsWidget() + self._file_options = DialogOptionsWidget(QFileDialog.Option(0)) self._file_options.add_checkbox("Do not use native dialog", - QFileDialog.DontUseNativeDialog) + QFileDialog.Option.DontUseNativeDialog) self._file_options.add_checkbox("Show directories only", - QFileDialog.ShowDirsOnly) + QFileDialog.Option.ShowDirsOnly) self._file_options.add_checkbox("Do not resolve symlinks", - QFileDialog.DontResolveSymlinks) + QFileDialog.Option.DontResolveSymlinks) self._file_options.add_checkbox("Do not confirm overwrite", - QFileDialog.DontConfirmOverwrite) - self._file_options.add_checkbox("Readonly", QFileDialog.ReadOnly) + QFileDialog.Option.DontConfirmOverwrite) + self._file_options.add_checkbox("Readonly", QFileDialog.Option.ReadOnly) self._file_options.add_checkbox("Hide name filter details", - QFileDialog.HideNameFilterDetails) + QFileDialog.Option.HideNameFilterDetails) self._file_options.add_checkbox("Do not use custom directory icons (Windows)", - QFileDialog.DontUseCustomDirectoryIcons) + QFileDialog.Option.DontUseCustomDirectoryIcons) self._critical_label = QLabel() self._critical_label.setFrameStyle(frame_style) @@ -177,7 +179,7 @@ class Dialog(QDialog): layout.addWidget(self._text_label, 3, 1) layout.addWidget(self._multiline_text_label, 4, 1) layout.addWidget(self._multiline_text_button, 4, 0) - spacer = QSpacerItem(0, 0, QSizePolicy.Ignored, QSizePolicy.MinimumExpanding) + spacer = QSpacerItem(0, 0, QSizePolicy.Policy.Ignored, QSizePolicy.Policy.MinimumExpanding) layout.addItem(spacer, 5, 0) toolbox.addItem(page, "Input Dialogs") @@ -185,7 +187,7 @@ class Dialog(QDialog): layout = QGridLayout(page) layout.addWidget(self._color_button, 0, 0) layout.addWidget(self._color_label, 0, 1) - spacer = QSpacerItem(0, 0, QSizePolicy.Ignored, QSizePolicy.MinimumExpanding) + spacer = QSpacerItem(0, 0, QSizePolicy.Policy.Ignored, QSizePolicy.Policy.MinimumExpanding) layout.addItem(spacer, 1, 0) layout.addWidget(self._color_options, 2, 0, 1, 2) toolbox.addItem(page, "Color Dialog") @@ -194,7 +196,7 @@ class Dialog(QDialog): layout = QGridLayout(page) layout.addWidget(self._font_button, 0, 0) layout.addWidget(self._font_label, 0, 1) - spacer = QSpacerItem(0, 0, QSizePolicy.Ignored, QSizePolicy.MinimumExpanding) + spacer = QSpacerItem(0, 0, QSizePolicy.Policy.Ignored, QSizePolicy.Policy.MinimumExpanding) layout.addItem(spacer, 1, 0) layout.addWidget(self._font_options, 2, 0, 1, 2) toolbox.addItem(page, "Font Dialog") @@ -209,7 +211,7 @@ class Dialog(QDialog): layout.addWidget(self._open_file_names_label, 2, 1) layout.addWidget(self._save_file_name_button, 3, 0) layout.addWidget(self._save_file_name_label, 3, 1) - spacer = QSpacerItem(0, 0, QSizePolicy.Ignored, QSizePolicy.MinimumExpanding) + spacer = QSpacerItem(0, 0, QSizePolicy.Policy.Ignored, QSizePolicy.Policy.MinimumExpanding) layout.addItem(spacer, 4, 0) layout.addWidget(self._file_options, 5, 0, 1, 2) @@ -226,7 +228,7 @@ class Dialog(QDialog): layout.addWidget(self._warning_button, 3, 0) layout.addWidget(self._warning_label, 3, 1) layout.addWidget(self._error_button, 4, 0) - spacer = QSpacerItem(0, 0, QSizePolicy.Ignored, QSizePolicy.MinimumExpanding) + spacer = QSpacerItem(0, 0, QSizePolicy.Policy.Ignored, QSizePolicy.Policy.MinimumExpanding) layout.addItem(spacer, 5, 0) toolbox.addItem(page, "Message Boxes") @@ -271,8 +273,8 @@ class Dialog(QDialog): @Slot() def set_color(self): options_value = self._color_options.value() - options = QColorDialog.ColorDialogOptions(options_value) - color = QColorDialog.getColor(Qt.green, self, "Select Color", options) + options = QColorDialog.ColorDialogOption(options_value) + color = QColorDialog.getColor(Qt.GlobalColor.green, self, "Select Color", options) if color.isValid(): self._color_label.setText(color.name()) @@ -345,15 +347,15 @@ class Dialog(QDialog): Activating the liquid oxygen stirring fans caused an explosion in one of the tanks. Liquid oxygen levels are getting low. This may jeopardize the moon landing mission.""") - msg_box = QMessageBox(QMessageBox.Critical, "QMessageBox.critical()", + msg_box = QMessageBox(QMessageBox.Icon.Critical, "QMessageBox.critical()", "Houston, we have a problem", - QMessageBox.Abort | QMessageBox.Retry | QMessageBox.Ignore, - self) + QMessageBox.StandardButton.Abort | QMessageBox.StandardButton.Retry + | QMessageBox.StandardButton.Ignore, self) msg_box.setInformativeText(m) reply = msg_box.exec() - if reply == QMessageBox.Abort: + if reply == QMessageBox.StandardButton.Abort: self._critical_label.setText("Abort") - elif reply == QMessageBox.Retry: + elif reply == QMessageBox.StandardButton.Retry: self._critical_label.setText("Retry") else: self._critical_label.setText("Ignore") @@ -365,12 +367,12 @@ class Dialog(QDialog): the conclusion of Elvis Presley concerts in order to disperse audiences who lingered in hopes of an encore. It has since become a catchphrase and punchline.""") - msg_box = QMessageBox(QMessageBox.Information, "QMessageBox.information()", + msg_box = QMessageBox(QMessageBox.Icon.Information, "QMessageBox.information()", "Elvis has left the building.", - QMessageBox.Ok, self) + QMessageBox.StandardButton.Ok, self) msg_box.setInformativeText(m) reply = msg_box.exec() - if reply == QMessageBox.Ok: + if reply == QMessageBox.StandardButton.Ok: self._information_label.setText("OK") else: self._information_label.setText("Escape") @@ -383,29 +385,30 @@ class Dialog(QDialog): meat patty. The cheese is usually added to the cooking hamburger patty shortly before serving, which allows the cheese to melt.""") - msg_box = QMessageBox(QMessageBox.Question, "QMessageBox.question()", + msg_box = QMessageBox(QMessageBox.Icon.Question, "QMessageBox.question()", "Would you like cheese with that?", - QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + | QMessageBox.StandardButton.Cancel) msg_box.setInformativeText(m) reply = msg_box.exec() - if reply == QMessageBox.Yes: + if reply == QMessageBox.StandardButton.Yes: self._question_label.setText("Yes") - elif reply == QMessageBox.No: + elif reply == QMessageBox.StandardButton.No: self._question_label.setText("No") else: self._question_label.setText("Cancel") @Slot() def warning_message(self): - msg_box = QMessageBox(QMessageBox.Warning, "QMessageBox.warning()", + msg_box = QMessageBox(QMessageBox.Icon.Warning, "QMessageBox.warning()", "Delete the only copy of your movie manuscript?", - QMessageBox.NoButton, self) + QMessageBox.StandardButton.NoButton, self) m = "You've been working on this manuscript for 738 days now. Hang in there!" msg_box.setInformativeText(m) msg_box.setDetailedText('"A long time ago in a galaxy far, far away...."') - msg_box.addButton("&Keep", QMessageBox.AcceptRole) - msg_box.addButton("Delete", QMessageBox.RejectRole) - if msg_box.exec() == QMessageBox.AcceptRole: + msg_box.addButton("&Keep", QMessageBox.ButtonRole.AcceptRole) + msg_box.addButton("Delete", QMessageBox.ButtonRole.RejectRole) + if msg_box.exec() == QMessageBox.ButtonRole.AcceptRole: self._warning_label.setText("Keep") else: self._warning_label.setText("Delete") diff --git a/examples/widgets/dialogs/tabdialog/tabdialog.py b/examples/widgets/dialogs/tabdialog/tabdialog.py index 6be3d020..c8eeec6b 100644 --- a/examples/widgets/dialogs/tabdialog/tabdialog.py +++ b/examples/widgets/dialogs/tabdialog/tabdialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/dialogs/tabdialog example from Qt v6.x""" @@ -34,7 +35,7 @@ class TabDialog(QDialog): tab_widget.addTab(ApplicationsTab(file_info, self), "Applications") button_box = QDialogButtonBox( - QDialogButtonBox.Ok | QDialogButtonBox.Cancel + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) button_box.accepted.connect(self.accept) @@ -56,20 +57,20 @@ class GeneralTab(QWidget): path_label = QLabel("Path:") path_value_label = QLabel(file_info.absoluteFilePath()) - path_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken) + path_value_label.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) size_label = QLabel("Size:") size = file_info.size() / 1024 size_value_label = QLabel(f"{size} K") - size_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken) + size_value_label.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) last_read_label = QLabel("Last Read:") last_read_value_label = QLabel(file_info.lastRead().toString()) - last_read_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken) + last_read_value_label.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) last_mod_label = QLabel("Last Modified:") last_mod_value_label = QLabel(file_info.lastModified().toString()) - last_mod_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken) + last_mod_value_label.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) main_layout = QVBoxLayout() main_layout.addWidget(file_name_label) @@ -108,11 +109,11 @@ class PermissionsTab(QWidget): owner_label = QLabel("Owner") owner_value_label = QLabel(file_info.owner()) - owner_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken) + owner_value_label.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) group_label = QLabel("Group") group_value_label = QLabel(file_info.group()) - group_value_label.setFrameStyle(QFrame.Panel | QFrame.Sunken) + group_value_label.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) permissions_layout = QVBoxLayout() permissions_layout.addWidget(readable) diff --git a/examples/widgets/dialogs/trivialwizard/doc/trivialwizard.png b/examples/widgets/dialogs/trivialwizard/doc/trivialwizard.png new file mode 100644 index 0000000000000000000000000000000000000000..02291df7940fee595c6dedd00786858bdc7252e0 GIT binary patch literal 18190 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. + +.. image:: trivialwizard.png + :width: 400 + :alt: trivialwizard screenshot diff --git a/examples/widgets/dialogs/trivialwizard/trivialwizard.py b/examples/widgets/dialogs/trivialwizard/trivialwizard.py index 2e551ae1..25f4f962 100644 --- a/examples/widgets/dialogs/trivialwizard/trivialwizard.py +++ b/examples/widgets/dialogs/trivialwizard/trivialwizard.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/dialogs/trivialwizard example from Qt v5.x""" diff --git a/examples/widgets/draganddrop/draggableicons/draggableicons.py b/examples/widgets/draganddrop/draggableicons/draggableicons.py index b929bd5e..bbaa514a 100644 --- a/examples/widgets/draganddrop/draggableicons/draggableicons.py +++ b/examples/widgets/draganddrop/draggableicons/draggableicons.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from pathlib import Path import sys @@ -13,7 +14,7 @@ class DragWidget(QFrame): def __init__(self, parent: QWidget): super().__init__(parent) self.setMinimumSize(200, 200) - self.setFrameStyle(QFrame.Sunken | QFrame.StyledPanel) + self.setFrameStyle(QFrame.Shadow.Sunken | QFrame.Shape.StyledPanel) self.setAcceptDrops(True) path = Path(__file__).resolve().parent @@ -22,24 +23,24 @@ class DragWidget(QFrame): boat_icon.setPixmap(QPixmap(path / "images" / "boat.png")) boat_icon.move(10, 10) boat_icon.show() - boat_icon.setAttribute(Qt.WA_DeleteOnClose) + boat_icon.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) car_icon = QLabel(self) car_icon.setPixmap(QPixmap(path / "images" / "car.png")) car_icon.move(100, 10) car_icon.show() - car_icon.setAttribute(Qt.WA_DeleteOnClose) + car_icon.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) house_icon = QLabel(self) house_icon.setPixmap(QPixmap(path / "images" / "house.png")) house_icon.move(10, 80) house_icon.show() - house_icon.setAttribute(Qt.WA_DeleteOnClose) + house_icon.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) def dragEnterEvent(self, event): if event.mimeData().hasFormat("application/x-dnditem_data"): if event.source() == self: - event.setDropAction(Qt.MoveAction) + event.setDropAction(Qt.DropAction.MoveAction) event.accept() else: event.acceptProposedAction() @@ -49,7 +50,7 @@ class DragWidget(QFrame): def dragMoveEvent(self, event): if event.mimeData().hasFormat("application/x-dnditem_data"): if event.source() == self: - event.setDropAction(Qt.MoveAction) + event.setDropAction(Qt.DropAction.MoveAction) event.accept() else: event.acceptProposedAction() @@ -59,7 +60,7 @@ class DragWidget(QFrame): def dropEvent(self, event): if event.mimeData().hasFormat("application/x-dnditem_data"): item_data: QByteArray = event.mimeData().data("application/x-dnditem_data") - data_stream = QDataStream(item_data, QIODevice.ReadOnly) + data_stream = QDataStream(item_data, QIODevice.OpenModeFlag.ReadOnly) pixmap = QPixmap() offset = QPoint() @@ -70,10 +71,10 @@ class DragWidget(QFrame): new_icon.setPixmap(pixmap) new_icon.move(event.position().toPoint() - offset) new_icon.show() - new_icon.setAttribute(Qt.WA_DeleteOnClose) + new_icon.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) if event.source() == self: - event.setDropAction(Qt.MoveAction) + event.setDropAction(Qt.DropAction.MoveAction) event.accept() else: event.acceptProposedAction() @@ -88,7 +89,7 @@ class DragWidget(QFrame): pixmap = child.pixmap() item_data = QByteArray() - data_stream = QDataStream(item_data, QIODevice.WriteOnly) + data_stream = QDataStream(item_data, QIODevice.OpenModeFlag.WriteOnly) data_stream << pixmap << QPoint(event.position().toPoint() - child.pos()) @@ -107,7 +108,8 @@ class DragWidget(QFrame): child.setPixmap(temp_pixmap) - if drag.exec(Qt.CopyAction | Qt.MoveAction, Qt.CopyAction) == Qt.MoveAction: + if drag.exec(Qt.DropAction.CopyAction | Qt.DropAction.MoveAction, + Qt.DropAction.CopyAction) == Qt.DropAction.MoveAction: child.close() else: child.show() diff --git a/examples/widgets/draganddrop/draggabletext/doc/draggabletext.png b/examples/widgets/draganddrop/draggabletext/doc/draggabletext.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe5ae4b10d213f4b456752dc0d2c210e2dbc0e5 GIT binary patch literal 9329 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 Hjr{%(8S3aw literal 0 HcmV?d00001 diff --git a/examples/widgets/draganddrop/draggabletext/doc/draggabletext.rst b/examples/widgets/draganddrop/draggabletext/doc/draggabletext.rst new file mode 100644 index 00000000..2da12e51 --- /dev/null +++ b/examples/widgets/draganddrop/draggabletext/doc/draggabletext.rst @@ -0,0 +1,8 @@ +Draggable Text Example +====================== + +Illustrates how to drag and drop text between widgets. + +.. image:: draggabletext.png + :width: 400 + :alt: draggabletext screenshot diff --git a/examples/widgets/draganddrop/draggabletext/draggabletext.py b/examples/widgets/draganddrop/draggabletext/draggabletext.py index 6ffdbd70..504e6bd0 100644 --- a/examples/widgets/draganddrop/draggabletext/draggabletext.py +++ b/examples/widgets/draganddrop/draggabletext/draggabletext.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/draganddrop/draggabletext example from Qt v5.x, originating from PyQt""" @@ -17,8 +18,8 @@ class DragLabel(QLabel): super().__init__(text, parent) self.setAutoFillBackground(True) - self.setFrameShape(QFrame.Panel) - self.setFrameShadow(QFrame.Raised) + self.setFrameShape(QFrame.Shape.Panel) + self.setFrameShadow(QFrame.Shadow.Raised) def mousePressEvent(self, event): hot_spot = event.position().toPoint() @@ -37,9 +38,10 @@ class DragLabel(QLabel): drag.setPixmap(pixmap) drag.setHotSpot(hot_spot) - drop_action = drag.exec(Qt.CopyAction | Qt.MoveAction, Qt.CopyAction) + drop_action = drag.exec(Qt.DropAction.CopyAction | Qt.DropAction.MoveAction, + Qt.DropAction.CopyAction) - if drop_action == Qt.MoveAction: + if drop_action == Qt.DropAction.MoveAction: self.close() self.update() @@ -49,7 +51,7 @@ class DragWidget(QWidget): super().__init__(parent) dictionary_file = QFile(':/dictionary/words.txt') - dictionary_file.open(QIODevice.ReadOnly) + dictionary_file.open(QIODevice.OpenModeFlag.ReadOnly) x = 5 y = 5 @@ -64,7 +66,7 @@ class DragWidget(QWidget): y += word_label.height() + 2 new_palette = self.palette() - new_palette.setColor(QPalette.Window, Qt.white) + new_palette.setColor(QPalette.ColorRole.Window, Qt.GlobalColor.white) self.setPalette(new_palette) self.setAcceptDrops(True) @@ -74,7 +76,7 @@ class DragWidget(QWidget): def dragEnterEvent(self, event): if event.mimeData().hasText(): if event.source() in self.children(): - event.setDropAction(Qt.MoveAction) + event.setDropAction(Qt.DropAction.MoveAction) event.accept() else: event.acceptProposedAction() @@ -101,7 +103,7 @@ class DragWidget(QWidget): position += QPoint(new_label.width(), 0) if event.source() in self.children(): - event.setDropAction(Qt.MoveAction) + event.setDropAction(Qt.DropAction.MoveAction) event.accept() else: event.acceptProposedAction() diff --git a/examples/widgets/draganddrop/dropsite/droparea.py b/examples/widgets/draganddrop/dropsite/droparea.py index efc4614f..1c10a686 100644 --- a/examples/widgets/draganddrop/dropsite/droparea.py +++ b/examples/widgets/draganddrop/dropsite/droparea.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QMimeData, Qt, Slot, Signal from PySide6.QtGui import QPalette, QPixmap @@ -13,15 +14,15 @@ class DropArea(QLabel): def __init__(self, parent=None): super().__init__(parent) self.setMinimumSize(200, 200) - self.setFrameStyle(QFrame.Sunken | QFrame.StyledPanel) - self.setAlignment(Qt.AlignCenter) + self.setFrameStyle(QFrame.Shadow.Sunken | QFrame.Shape.StyledPanel) + self.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setAcceptDrops(True) self.setAutoFillBackground(True) self.clear() def dragEnterEvent(self, event): self.setText("") - self.setBackgroundRole(QPalette.Highlight) + self.setBackgroundRole(QPalette.ColorRole.Highlight) event.acceptProposedAction() self.changed.emit(event.mimeData()) @@ -36,13 +37,13 @@ class DropArea(QLabel): self.setPixmap(QPixmap(mime_data.imageData())) elif mime_data.hasFormat("text/markdown"): self.setText(mime_data.data("text/markdown")) - self.setTextFormat(Qt.MarkdownText) + self.setTextFormat(Qt.TextFormat.MarkdownText) elif mime_data.hasHtml(): self.setText(mime_data.html()) - self.setTextFormat(Qt.RichText) + self.setTextFormat(Qt.TextFormat.RichText) elif mime_data.hasText(): self.setText(mime_data.text()) - self.setTextFormat(Qt.PlainText) + self.setTextFormat(Qt.TextFormat.PlainText) elif mime_data.hasUrls(): url_list = mime_data.urls() text = "" @@ -52,7 +53,7 @@ class DropArea(QLabel): else: self.setText("Cannot display data") - self.setBackgroundRole(QPalette.Dark) + self.setBackgroundRole(QPalette.ColorRole.Dark) event.acceptProposedAction() def dragLeaveEvent(self, event): @@ -62,6 +63,6 @@ class DropArea(QLabel): @Slot() def clear(self): self.setText("") - self.setBackgroundRole(QPalette.Dark) + self.setBackgroundRole(QPalette.ColorRole.Dark) self.changed.emit(None) diff --git a/examples/widgets/draganddrop/dropsite/dropsitewindow.py b/examples/widgets/draganddrop/dropsite/dropsitewindow.py index 5427d520..0d6bbebf 100644 --- a/examples/widgets/draganddrop/dropsite/dropsitewindow.py +++ b/examples/widgets/draganddrop/dropsite/dropsitewindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import re from PySide6.QtCore import QMimeData, Qt, Slot @@ -40,7 +41,7 @@ class DropSiteWindow(QWidget): self._formats_table = QTableWidget() self._formats_table.setColumnCount(2) - self._formats_table.setEditTriggers(QAbstractItemView.NoEditTriggers) + self._formats_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self._formats_table.setHorizontalHeaderLabels(["Format", "Content"]) self._formats_table.horizontalHeader().setStretchLastSection(True) @@ -49,11 +50,11 @@ class DropSiteWindow(QWidget): quit_button = QPushButton("Quit") button_box = QDialogButtonBox() - button_box.addButton(clear_button, QDialogButtonBox.ActionRole) - button_box.addButton(self._copy_button, QDialogButtonBox.ActionRole) + button_box.addButton(clear_button, QDialogButtonBox.ButtonRole.ActionRole) + button_box.addButton(self._copy_button, QDialogButtonBox.ButtonRole.ActionRole) self._copy_button.setVisible(False) - button_box.addButton(quit_button, QDialogButtonBox.RejectRole) + button_box.addButton(quit_button, QDialogButtonBox.ButtonRole.RejectRole) quit_button.clicked.connect(self.close) clear_button.clicked.connect(drop_area.clear) @@ -77,8 +78,8 @@ class DropSiteWindow(QWidget): for format in mime_data.formats(): format_item = QTableWidgetItem(format) - format_item.setFlags(Qt.ItemIsEnabled) - format_item.setTextAlignment(Qt.AlignTop | Qt.AlignLeft) + format_item.setFlags(Qt.ItemFlag.ItemIsEnabled) + format_item.setTextAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft) if format == "text/plain": text = simplify_whitespace(mime_data.text()) diff --git a/examples/widgets/draganddrop/dropsite/main.py b/examples/widgets/draganddrop/dropsite/main.py index bce476a6..1c4365ed 100644 --- a/examples/widgets/draganddrop/dropsite/main.py +++ b/examples/widgets/draganddrop/dropsite/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgets/effects/blurpicker/blureffect.py b/examples/widgets/effects/blurpicker/blureffect.py index 16c14a75..cb9b2f34 100644 --- a/examples/widgets/effects/blurpicker/blureffect.py +++ b/examples/widgets/effects/blurpicker/blureffect.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QRect diff --git a/examples/widgets/effects/blurpicker/blurpicker.py b/examples/widgets/effects/blurpicker/blurpicker.py index 6ff11f21..c42a9569 100644 --- a/examples/widgets/effects/blurpicker/blurpicker.py +++ b/examples/widgets/effects/blurpicker/blurpicker.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from pathlib import Path @@ -34,10 +35,10 @@ class BlurPicker(QGraphicsView): self.index = 0 self._animation.setDuration(400) - self._animation.setEasingCurve(QEasingCurve.InOutSine) + self._animation.setEasingCurve(QEasingCurve.Type.InOutSine) - self.setRenderHint(QPainter.Antialiasing, True) - self.setFrameStyle(QFrame.NoFrame) + self.setRenderHint(QPainter.RenderHint.Antialiasing, True) + self.setFrameStyle(QFrame.Shape.NoFrame) @Property(float) def index(self) -> float: @@ -85,9 +86,9 @@ class BlurPicker(QGraphicsView): def keyPressEvent(self, event): delta = 0 - if event.key() == Qt.Key_Left: + if event.key() == Qt.Key.Key_Left: delta = -1 - elif event.key() == Qt.Key_Right: + elif event.key() == Qt.Key.Key_Right: delta = 1 if self._animation.state() == QAbstractAnimation.Stopped and delta: @@ -99,7 +100,7 @@ class BlurPicker(QGraphicsView): right = event.position().x() > (self.width() / 2) delta = 1 if right else -1 - if self._animation.state() == QAbstractAnimation.Stopped: + if self._animation.state() == QAbstractAnimation.State.Stopped: self._animation.setEndValue(self._index + delta) self._animation.start() event.accept() diff --git a/examples/widgets/effects/blurpicker/main.py b/examples/widgets/effects/blurpicker/main.py index 053ac60e..7f2df0db 100644 --- a/examples/widgets/effects/blurpicker/main.py +++ b/examples/widgets/effects/blurpicker/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/effects/blurpicker example from Qt v6.x""" diff --git a/examples/widgets/effects/lighting/doc/lighting.rst b/examples/widgets/effects/lighting/doc/lighting.rst new file mode 100644 index 00000000..95adb468 --- /dev/null +++ b/examples/widgets/effects/lighting/doc/lighting.rst @@ -0,0 +1,11 @@ +Lighting Example +================ + +This example demonstrates a PySide6 application that creates a dynamic scene with lighting and +shadow effects using `QGraphicsView` and `QGraphicsScene`. It features animated light sources and +graphical items with drop shadows that respond to the light, showcasing advanced rendering and +animation techniques. + +.. image:: lighting.webp + :width: 400 + :alt: lighting screenshot diff --git a/examples/widgets/effects/lighting/doc/lighting.webp b/examples/widgets/effects/lighting/doc/lighting.webp new file mode 100644 index 0000000000000000000000000000000000000000..fbae74d3bbca754db8af3a9cbb01a4b7d1d61029 GIT binary patch literal 9430 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/effects/lighting/lighting.py b/examples/widgets/effects/lighting/lighting.py index f074813c..2970d55a 100644 --- a/examples/widgets/effects/lighting/lighting.py +++ b/examples/widgets/effects/lighting/lighting.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math import sys @@ -31,8 +32,8 @@ class Lighting(QGraphicsView): timer.setInterval(30) timer.start() - self.setRenderHint(QPainter.Antialiasing) - self.setFrameStyle(QFrame.NoFrame) + self.setRenderHint(QPainter.RenderHint.Antialiasing) + self.setFrameStyle(QFrame.Shape.NoFrame) def setup_scene(self): self.m_scene.setSceneRect(-300, -200, 600, 460) @@ -43,15 +44,15 @@ class Lighting(QGraphicsView): self.setBackgroundBrush(linear_grad) radial_grad = QRadialGradient(30, 30, 30) - radial_grad.setColorAt(0, Qt.yellow) - radial_grad.setColorAt(0.2, Qt.yellow) - radial_grad.setColorAt(1, Qt.transparent) + radial_grad.setColorAt(0, Qt.GlobalColor.yellow) + radial_grad.setColorAt(0.2, Qt.GlobalColor.yellow) + radial_grad.setColorAt(1, Qt.GlobalColor.transparent) pixmap = QPixmap(60, 60) - pixmap.fill(Qt.transparent) + pixmap.fill(Qt.GlobalColor.transparent) with QPainter(pixmap) as painter: - painter.setPen(Qt.NoPen) + painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(radial_grad) painter.drawEllipse(0, 0, 60, 60) @@ -65,8 +66,8 @@ class Lighting(QGraphicsView): else: item = QGraphicsRectItem(0, 0, 50, 50) - item.setPen(QPen(Qt.black, 1)) - item.setBrush(QBrush(Qt.white)) + item.setPen(QPen(Qt.GlobalColor.black, 1)) + item.setBrush(QBrush(Qt.GlobalColor.white)) effect = QGraphicsDropShadowEffect(self) effect.setBlurRadius(8) diff --git a/examples/widgets/gettext/doc/gettext.rst b/examples/widgets/gettext/doc/gettext.rst index ea1127fe..9c00591f 100644 --- a/examples/widgets/gettext/doc/gettext.rst +++ b/examples/widgets/gettext/doc/gettext.rst @@ -1,5 +1,3 @@ -.. _gettext-example: - GNU gettext Example =================== diff --git a/examples/widgets/gettext/main.py b/examples/widgets/gettext/main.py index 85695728..6710a236 100644 --- a/examples/widgets/gettext/main.py +++ b/examples/widgets/gettext/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from pathlib import Path import gettext @@ -23,7 +24,7 @@ class Window(QMainWindow): quit_action.triggered.connect(self.close) self._list_widget = QListWidget() - self._list_widget.setSelectionMode(QAbstractItemView.MultiSelection) + self._list_widget.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) self._list_widget.selectionModel().selectionChanged.connect(self.selection_changed) self._list_widget.addItem("C++") self._list_widget.addItem("Java") diff --git a/examples/widgets/graphicsview/anchorlayout/anchorlayout.py b/examples/widgets/graphicsview/anchorlayout/anchorlayout.py index 9d9be041..33115312 100644 --- a/examples/widgets/graphicsview/anchorlayout/anchorlayout.py +++ b/examples/widgets/graphicsview/anchorlayout/anchorlayout.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -18,7 +19,7 @@ def create_item(minimum, preferred, maximum, name): w.setMinimumSize(minimum) w.setPreferredSize(preferred) w.setMaximumSize(maximum) - w.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + w.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) return w @@ -44,45 +45,45 @@ if __name__ == '__main__': l = QGraphicsAnchorLayout() # noqa: E741 l.setSpacing(0) - w = QGraphicsWidget(None, Qt.Window) + w = QGraphicsWidget(None, Qt.WindowType.Window) w.setPos(20, 20) w.setLayout(l) - # Vertical. - l.addAnchor(a, Qt.AnchorTop, l, Qt.AnchorTop) - l.addAnchor(b, Qt.AnchorTop, l, Qt.AnchorTop) + # Vertical + l.addAnchor(a, Qt.AnchorPoint.AnchorTop, l, Qt.AnchorPoint.AnchorTop) + l.addAnchor(b, Qt.AnchorPoint.AnchorTop, l, Qt.AnchorPoint.AnchorTop) - l.addAnchor(c, Qt.AnchorTop, a, Qt.AnchorBottom) - l.addAnchor(c, Qt.AnchorTop, b, Qt.AnchorBottom) - l.addAnchor(c, Qt.AnchorBottom, d, Qt.AnchorTop) - l.addAnchor(c, Qt.AnchorBottom, e, Qt.AnchorTop) + l.addAnchor(c, Qt.AnchorPoint.AnchorTop, a, Qt.AnchorPoint.AnchorBottom) + l.addAnchor(c, Qt.AnchorPoint.AnchorTop, b, Qt.AnchorPoint.AnchorBottom) + l.addAnchor(c, Qt.AnchorPoint.AnchorBottom, d, Qt.AnchorPoint.AnchorTop) + l.addAnchor(c, Qt.AnchorPoint.AnchorBottom, e, Qt.AnchorPoint.AnchorTop) - l.addAnchor(d, Qt.AnchorBottom, l, Qt.AnchorBottom) - l.addAnchor(e, Qt.AnchorBottom, l, Qt.AnchorBottom) + l.addAnchor(d, Qt.AnchorPoint.AnchorBottom, l, Qt.AnchorPoint.AnchorBottom) + l.addAnchor(e, Qt.AnchorPoint.AnchorBottom, l, Qt.AnchorPoint.AnchorBottom) - l.addAnchor(c, Qt.AnchorTop, f, Qt.AnchorTop) - l.addAnchor(c, Qt.AnchorVerticalCenter, f, Qt.AnchorBottom) - l.addAnchor(f, Qt.AnchorBottom, g, Qt.AnchorTop) - l.addAnchor(c, Qt.AnchorBottom, g, Qt.AnchorBottom) + l.addAnchor(c, Qt.AnchorPoint.AnchorTop, f, Qt.AnchorPoint.AnchorTop) + l.addAnchor(c, Qt.AnchorPoint.AnchorVerticalCenter, f, Qt.AnchorPoint.AnchorBottom) + l.addAnchor(f, Qt.AnchorPoint.AnchorBottom, g, Qt.AnchorPoint.AnchorTop) + l.addAnchor(c, Qt.AnchorPoint.AnchorBottom, g, Qt.AnchorPoint.AnchorBottom) # Horizontal. - l.addAnchor(l, Qt.AnchorLeft, a, Qt.AnchorLeft) - l.addAnchor(l, Qt.AnchorLeft, d, Qt.AnchorLeft) - l.addAnchor(a, Qt.AnchorRight, b, Qt.AnchorLeft) + l.addAnchor(l, Qt.AnchorPoint.AnchorLeft, a, Qt.AnchorPoint.AnchorLeft) + l.addAnchor(l, Qt.AnchorPoint.AnchorLeft, d, Qt.AnchorPoint.AnchorLeft) + l.addAnchor(a, Qt.AnchorPoint.AnchorRight, b, Qt.AnchorPoint.AnchorLeft) - l.addAnchor(a, Qt.AnchorRight, c, Qt.AnchorLeft) - l.addAnchor(c, Qt.AnchorRight, e, Qt.AnchorLeft) + l.addAnchor(a, Qt.AnchorPoint.AnchorRight, c, Qt.AnchorPoint.AnchorLeft) + l.addAnchor(c, Qt.AnchorPoint.AnchorRight, e, Qt.AnchorPoint.AnchorLeft) - l.addAnchor(b, Qt.AnchorRight, l, Qt.AnchorRight) - l.addAnchor(e, Qt.AnchorRight, l, Qt.AnchorRight) - l.addAnchor(d, Qt.AnchorRight, e, Qt.AnchorLeft) + l.addAnchor(b, Qt.AnchorPoint.AnchorRight, l, Qt.AnchorPoint.AnchorRight) + l.addAnchor(e, Qt.AnchorPoint.AnchorRight, l, Qt.AnchorPoint.AnchorRight) + l.addAnchor(d, Qt.AnchorPoint.AnchorRight, e, Qt.AnchorPoint.AnchorLeft) - l.addAnchor(l, Qt.AnchorLeft, f, Qt.AnchorLeft) - l.addAnchor(l, Qt.AnchorLeft, g, Qt.AnchorLeft) - l.addAnchor(f, Qt.AnchorRight, g, Qt.AnchorRight) + l.addAnchor(l, Qt.AnchorPoint.AnchorLeft, f, Qt.AnchorPoint.AnchorLeft) + l.addAnchor(l, Qt.AnchorPoint.AnchorLeft, g, Qt.AnchorPoint.AnchorLeft) + l.addAnchor(f, Qt.AnchorPoint.AnchorRight, g, Qt.AnchorPoint.AnchorRight) scene.addItem(w) - scene.setBackgroundBrush(Qt.darkGreen) + scene.setBackgroundBrush(Qt.GlobalColor.darkGreen) view = QGraphicsView(scene) view.show() diff --git a/examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.rst b/examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.rst new file mode 100644 index 00000000..8eef329c --- /dev/null +++ b/examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.rst @@ -0,0 +1,12 @@ +Anchor Layout Example +===================== + +This application demonstrates the use of `QGraphicsAnchorLayout` in a PySide6 Widget application. +It creates a graphical scene with several `QPushButton` widgets arranged using anchor constraints, +allowing for dynamic and flexible layout management within a `QGraphicsView`. It demonstrates +the analogous Qt example +`Anchor Layout Example `_ + +.. image:: anchorlayout.webp + :width: 400 + :alt: anchorlayout screenshot diff --git a/examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.webp b/examples/widgets/graphicsview/anchorlayout/doc/anchorlayout.webp new file mode 100644 index 0000000000000000000000000000000000000000..cfc89c667fc36bc405ee40f05736ca7a01f136bc GIT binary patch literal 4234 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 Y6c%mVM0TGfZ`~Uy| literal 0 HcmV?d00001 diff --git a/examples/widgets/graphicsview/collidingmice/collidingmice.py b/examples/widgets/graphicsview/collidingmice/collidingmice.py index fb24db3a..54d256b6 100644 --- a/examples/widgets/graphicsview/collidingmice/collidingmice.py +++ b/examples/widgets/graphicsview/collidingmice/collidingmice.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math import sys @@ -57,12 +58,12 @@ class Mouse(QGraphicsItem): painter.drawEllipse(-10, -20, 20, 40) # Eyes. - painter.setBrush(Qt.white) + painter.setBrush(Qt.GlobalColor.white) painter.drawEllipse(-10, -17, 8, 8) painter.drawEllipse(2, -17, 8, 8) # Nose. - painter.setBrush(Qt.black) + painter.setBrush(Qt.GlobalColor.black) painter.drawEllipse(QRectF(-2, -22, 4, 4)) # Pupils. @@ -71,9 +72,9 @@ class Mouse(QGraphicsItem): # Ears. if self.scene().collidingItems(self): - painter.setBrush(Qt.red) + painter.setBrush(Qt.GlobalColor.red) else: - painter.setBrush(Qt.darkYellow) + painter.setBrush(Qt.GlobalColor.darkYellow) painter.drawEllipse(-17, -12, 16, 16) painter.drawEllipse(1, -12, 16, 16) @@ -83,7 +84,7 @@ class Mouse(QGraphicsItem): path.cubicTo(-5, 22, -5, 22, 0, 25) path.cubicTo(5, 27, 5, 32, 0, 30) path.cubicTo(-5, 32, -5, 42, 0, 35) - painter.setBrush(Qt.NoBrush) + painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawPath(path) def advance(self, phase): @@ -154,7 +155,7 @@ if __name__ == '__main__': scene = QGraphicsScene() scene.setSceneRect(-300, -300, 600, 600) - scene.setItemIndexMethod(QGraphicsScene.NoIndex) + scene.setItemIndexMethod(QGraphicsScene.ItemIndexMethod.NoIndex) for i in range(MOUSE_COUNT): mouse = Mouse() @@ -163,11 +164,11 @@ if __name__ == '__main__': scene.addItem(mouse) view = QGraphicsView(scene) - view.setRenderHint(QPainter.Antialiasing) + view.setRenderHint(QPainter.RenderHint.Antialiasing) view.setBackgroundBrush(QBrush(QPixmap(':/images/cheese.jpg'))) - view.setCacheMode(QGraphicsView.CacheBackground) - view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) - view.setDragMode(QGraphicsView.ScrollHandDrag) + view.setCacheMode(QGraphicsView.CacheModeFlag.CacheBackground) + view.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.BoundingRectViewportUpdate) + view.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) view.setWindowTitle("Colliding Mice") view.resize(400, 300) view.show() diff --git a/examples/widgets/graphicsview/collidingmice/doc/collidingmice.rst b/examples/widgets/graphicsview/collidingmice/doc/collidingmice.rst new file mode 100644 index 00000000..44901592 --- /dev/null +++ b/examples/widgets/graphicsview/collidingmice/doc/collidingmice.rst @@ -0,0 +1,15 @@ +Colliding Mice Example +====================== + +This application, is a graphical simulation built using PySide6. It creates a +scene with multiple animated mouse objects that move around and interact with +each other within a `QGraphicsView`. The mice change direction to avoid +collisions and exhibit random movements, demonstrating the use of +`QGraphicsItem`, `QGraphicsScene`, and `QGraphicsView` for creating dynamic and +interactive graphics in a PySide6 application. This example demonstrates the +analogous Qt example `Colliding Mice Example +`_. + +.. image:: collidingmice.webp + :width: 400 + :alt: collidingmice screenshot diff --git a/examples/widgets/graphicsview/collidingmice/doc/collidingmice.webp b/examples/widgets/graphicsview/collidingmice/doc/collidingmice.webp new file mode 100644 index 0000000000000000000000000000000000000000..aafd2b15fcbe2e5b195147c223e01a3356a5d99d GIT binary patch literal 10604 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. + +.. image:: diagramscene.webp + :width: 400 + :alt: diagramscene screenshot diff --git a/examples/widgets/graphicsview/diagramscene/doc/diagramscene.webp b/examples/widgets/graphicsview/diagramscene/doc/diagramscene.webp new file mode 100644 index 0000000000000000000000000000000000000000..e94a8393c06f4ed67ce14b50a6c2abe77953f020 GIT binary patch literal 15208 zcmdUVV~{4z*6q`_J>Ao`ZQHi(?&)by+qP}nwlQsc+P3-JdCxiDRosa0_sxi^sI1zV zvG(3;?aYcuWhrrSLmmJ?LrhpvU6E50761Ss|Giki0JNY0Az4MqXHWnDA@tm96AV007xqpHmJX$w4V_2&>wbiFc^P9S`ZX)CC0X7Nl8K&q7ray6b$t6%t$T9 z1in$k>rF0U1m~Js;M;-#w-z=Bnh3TOVwUUFcZp(HavPo%ACK!nf-gl1<4sCP$nkX? z4Sq?c-!V@3?QfUQy*%ZzjP?K{#)kW>QOa;nea@RRjdOYO1|xVQ)2w$^wc8db{Z~hB zlzn1I@dOrvBKjO(Anr~%T2$5VyS`-A5uFwOuR*d(k%6&W9vLu(uJ=MTF~S7KhG%B$ z{eBsN_l1l0RSdLZ$d-UK}BLP!b%e4b1;-J zniQr-BJp+LCGg4jz4vqH>j$tExbgp47ufqCJP{ZthYf}q=Ep?CC=DA#8iIhKMT0~E z10w^1$#A{Z>@wGYdn@WXqPy_XQvul9B|**C~@PoJuPrlhQ~)2GWW;SD^I=AVZUkOF|n_ zsbmpLiopj(#R&nT(iz3#E3i?S4Y5H;!U7hD1%(M4SSj;{3XrAzz_sXvNF~+aQ3Gt% zp(V}W$3g&q6+tM>2>>uMfnC5!i``mj)uq|-=5%SsjUV4~&2bA*GfAjZI`3U`RdjM|D{3dYQ zxf8(VWo3C8PGG{j#yjB4|Kt2K^a*Gt z(CCx%OZ&6*i{gTy59?0h%=fH!99Rl${tWy(LSckq%Xb;*`k4(h_-BTpof+T&a2H5( zgYmKPy7Jz8(|Zhj_pSf?Y3I`SArLrvQ@iuGlkMx+d+C|u)Ai*D1b&&)zos~#)ITA! zN$+A)RcJu>%PF^s!&)Z~Fsi85paf(TJ4B%`(F7S5rqez}U4k_8yUu>ITJ_~@wF;sf z^+i^ESZQ;GrfS1Yc8wtT4v{iu<&-%D9|Y^(H*C_qT2dGU!QnJykRLgM>z~}fnwmP~ zFc54KwwAryT~-=`HdfcmmQ;N&Nmfkkl(sS`HI0p>$gi!g7oE;D>^4|wW?u^*R-v+2 zVgN!Vt(;W+pPL+(W!3K35D|1B_6Spy3HAuFuK8H`!vH<9hshZjdE>(Gw$!6_jE=26xp8(!hK=>I;V=EDbGo(nN=R89Fy@a&%CMQ6DYGTv)6uj zvR3MyGKqu>0Dd6Rn~Yg+$VW4(7Idy*jWC+}MK8Y*Wv??m_U5X+wV6d8eZ}}os=OJ9 zrB0cI{Bi&+yGuI5gouSM(M!gs36Rgm25)|cSv@{*bOMp?E|ha8BY>a!+TZ1Fl1K-& zg!Isxlg?wsTB$HDaYIyvtpHW{-W{}49Hj+A&9paf#<&~c{EH%PNConoQ)J6^TOw`a z5f01XSx2pN8s3a}v4XR|fXDdyrFWQ0A#s2&mz1Br6pjajS5HG+>SS8m*yCSZsh$*G zD5h+;)xZAX!T}q#|2Hqg*UxE-2F)(v_Nz!A^74jjiAbg*nF|ytjI}VD8b)Me$0a+C z*b^fpDQjfersTpF9GWFIaEuf!*`h6Eo+cZ)cs`}sN_Lk+)qzH_KAbl*zpg#EpSw3L zwy(UeJ*S*_xJe)wC;rm5L}ro+sk%2;4(e*yFW7y7st9hrKB7<37H}M>Ast07_~EV; z_6^JG8PvwyVpj&`&5WD#XTvl-tfmfAL==;*6;y1@PqqosCUm<1S+-fbPP|XamrJfM zVfCS9FfCOrO4W1XPfrWqcb5NR%II!f)A1{;O<5J2q{x~XGO!S3-~m}=p=`wVl-xV- zP}2{)9sxGcZuPH`w_{+~TgM?3by+#iqNnysp9BZ{RD1^551TO@+JLVU?QA{Az+|<5 znrduijiO^kM0~bHb2Sj|sa9q3$84^i+>c!Z+PaatQ6?P56`__3r;lKMIX~aH(iN;= zLi{jMw3h&~-=yo9uU-0WSY9Qej5MUZKwBNJE52WCkgKGFdXO9SGmEh?3%LhmwtY>H z)TaTKyiOu3>m6)_2%l2arBeAiT2`Yj0`>L_Ew_fov*URjXh^)z^%wuZuK1^w6KLq> z9jalFaK~DPF)q`uf@vdyU0Sv=dsnI1B$idYr7~ULH=z(|=o;1h+ikB*0FX$bh5=TP z4>>ql_~Qv_VWa)McF0NSb?|&Ye&(slr*X@XiOriS91^as0i^D!LZ#;meeXUw0iBB3BS1tMblS{bbB0`;1NC!gmfEXeX zp+UWD*55>2K9a|5lWNC7MaS*#7QCk4Z)f*ngth;Npvg|B&wnHQH(+h!)3*?7jvdEq ziGiUIXdfOup0$|43ICN<-;jEdzwB!lnv4ZD%*5GsT!U0Nh5l-X`{&(1<0~rKAKo|r zQbkQoxAe)Y)g*U+DHuunA@w;RB=0}+vW9*6w=&|cPkU%b)!E*O{=!u!ca9d^08g{Y z+i>Ojn)Qyi`WH)t0DS9=N&7=~g*S=tIU?;?w52PQT>p~G{{-Dmfw;5OhcQ9b?Y?~; zQHY(Ne{RHs_YeL4s_A&)`rrEc8TSdhU4uYbQ>Z2RG^05VwfAqa|FvsSKe?OuE7W8S z0$_Lb#lMa4hPlKD+nxSHR_ebKZR8eGGUxxzo2c}cDpQ1?LxQ-DX(>L5Ez_Np99$mz ze=)U!yhokE#Y;TO_X3dmYq`%Q5d*Tp>D14)F!#IEa_Z`DFo{TprH&>mfc1A238Fs9 zu%GfjUB0ocZPoajB5u5#%0t3z{F!s5L^hbC592J3)LT*Wk~B1~q}@dBlE;+t#?g-q zxjRpj4U(?;_p(?7WYOngSW>1%9|`Ee^e~36yl>Ul(kjba^@7G^QvLbwr4r{kGwECp zGew_yx^}7BMtixr+H~3(kH&2=`cT=*x zeBaWBNmEHcv-p_L#Un3+$!t-+qrjHN80R0cl7xw2T%>2G@XrIAX~bgBL2v*-)XrQw zMn0X&YkxiCn&sgVFith~ST;;|)mOs7N_`S-UE;&FyAM1`OKyp9d&SE~SFG54Tc%4Nr{NBN%Ma%8Dx7Ubjog_d z5EisqTDPI*A;flzYHE{>Q>ng#3>|Wns3<-(u^!E_{1t6H!sy^E>ATbQWEtMt*v%@` z80E9-G~>NwsWCee9mD@?yH`YEG942sLRya1$E)|3&JAtQ%?C@)A6Ws2NMRUSv!p%g z`BuW7F!gBE0?&!4V8Qy#N3lxr0a*WL%}U+7Di~|jA@i6P2<0U#3l{|yc6=K_Q^a~2 z#H~Co?zLo5jwvx}Hr3to0NewPt0Cgxb~hzPbnqvm0Nuy1z^ZpkG4N%A0*34WFu_KL zHdQ8^ORP~bx64r92(J`T=flB0;v);zGq7CVh2yN}w6u}ElQ~k@#tsLxUIvE_xgLe2 zn=i4jhCw3F-=41)L=IA4ExvtuJMf#@9D|`;;#6P17v#=Jyg~|14ipxI_To^%sbzka z$qPic2*Rp>Oo+dD#QFb53J<*ZvTq2$KLTFgfxzY*47ly&5kjnNvqi(SQKTV7scI(yN9>0) z{Xm@1$~lM{Le7mHM<+?6ea7;1D($Q53h?^cfO)-mzJk}oPAX{SyCnBHLhcf*b%UOf zY*bkas8Hza?ES>xK(G3*j!$cf*wJ!>)C&<{~F)tqF>L^f){i3||5-mcbd!Vf54Dy9ZqPl~sD z&o9TER9-C3tGMuk5*RR}8p|5OM8-QPcQ0;4jVV71!BdKyMqtu>TLna6BkZz3X$EL~ z4h-Y+!`a+ZWaHfjW~qhsW7^7?U!F@|mlZ^GAM_5A#3I3xGY*Z*qWs$#3hTKVw?pll zfALw;r0VHzCoiR^InF9cQWke>zeXBr@MfJW*mevu9ZwMQ*QUIIcAIMWD0EH=6POmm zg+7R?N)3qW==;cWyG%2*I)xo}o%3DFj9StCAMF4Gb?enLn0lk+yYe0RaOaXqcnkg7 zsDvKIfY#A|L%kgnJz%V*dE2KM^y&GUPjd=7MfG{bgah=#9NaRa8z0$lE~Z#O6Gl_o zt&2Y!4ZfuZ9Ec|4b}FL|Q|@ALQ$AsygakO6ca&pEmg&HN(P93zn<(tro43 zYb-T7s;G}PaLcwszhP6AJ5QV|4LNmEb~7o3)d%44x*|Dq!OSZ9OH-R~`6yY5C81`$ zwvYOb!{1k^lBl+6_qMz>uCiXHHd+#!r3A0x0~kH`PVGrK6jg0p{?seI2E8v2p0Boy z!R3Oco066>%i2jPxoeF0$ZT4J6%n>7sK<=A22SC>CvT;3Zw@Q=7|A<076_J=83@qpp&&AqiDjP=QEydG-D*MU=B zQx^J^M<+92^WlVFpg660uA850lj}UW?mQvXI6JrI3W!|gq!!+md*j~z)eiHh{9-oX z$dM7jGmUAS2jr*s>QFDQ6O$aT0o}f@hpjDC{fdXCOg^1}k(f&#KTD*Dni-Ui%()242;QF|0&tUFTi(rxAjgvaclEvEg_LE0^1!&_mZrE1aUN z`Ut0wr0V$RtkWA2*~snD8PgrYW9`QpRA!x9s~3R*vv*KB@3y(g2Uo?WzB7uC6G^tx z?IZKbVml3sCp?gGr~a8CBH0pBf9JS+R7x%5r98@Mstikg=Z#QFm!Y-6G0P6T`hasmGJp5PQK-iv zDf|kUY&_ZOhtG4~E+SC?8J3}H-KTp4MyPNWYcSjUMK_BU7{@thOgLDM0K9Tfelgt1 z&?^Fw?rD@hyXCD5W4mX$OXRPv>zf=~~Ks88{{=4Hcs}K~$|J;xM1w(5ho$ zBV2U4bik+vFGU36D5!%A_)oBhie+BcUP5l+??7stbYdekmv%%1neJAO+RvLUS1Qb< zBknKaGmKW(!NE@#;OJtbL*_lVrg{DIDb1hmD$PeigN{s|A4)o2RMJOY z>At8OT^$Sz1)dgSFU6MR)S_D6#J?R2Pn|3Wlg$=bY#b@g4_MiTkZmw1h zgTEx(f)nFnUC+X@qNbxk5_+{29OBM~OST!xFt~Mta;4aDjK685OIf5zd*Q^*W?sjD)N@`<{*}8p=wz zW17&ez4W;=nY1=GX*bI+Bb)0I?hsQD7#7H63%u~CJM4zl$I*2tZFn844O4I3!jZv} zXvh&2O2ePeLGCFXz|5J>rY37X1n(-n#xuC#!9(La=8-52tNn+X+M==y6g5qrO2YX^`LlSC}dP!udF*70vDKk55myb4F|hEy6PB^@I%7`f}8 zRIQ|h6F@efT8o{lS0zEMIoqK1lhJZj@Unk8IebKnZ=NxID2QjNU8$}{)Yf7kJ6l%? zlcR)t1t1&u7Hf0su=9XW!v?OB3+u&$5HBzEuq#cSw}jnV^)+oa4=&AIy$<^+m?NSb z?a3UCo|iHnd}P=$ql-KT({PoqGEsBi(^fSjPon1rYP6#wqJh^?wHat!sml&9hT+Eu zvsRpl>LiL;TehgFf&`4WBy@a$C*p%-ZML`h7`$*plRMLGasT0Hn;FXS^8yBa2`AE` z>!FBzPhs1}X`MZv?-;6J#o+r@E;5PXcR(~&WOR&%;G#XiVxA~#q3^om+;AzWsS{X1 zC1l@z)q9PDkm^dwVlIr{g&#L*gMRvy@p?HwS&xmE5C_Vm!owpAx?LZDojL^NJb z4J2FeGX}UMV)yh_Y=8fJnKl87?eJ7D3I)5%3>r}^=M)P9#mE|RPaFTE3rb3z)2`cT zkN-@QQcZ_gVtD0_LrWeM*AQ@DI=agKQRi}7QnK2=j3arlpMo=`BX%E{JrzV}bdx!~ zPNmD2OP!;I-|gEIVn`HwtlNZAy_-(Y!YQ4ic~&iJm{CmRVeX78Tz!v8g7=$T0Ck(1 z3KnWth)7Z=_tH>DyWbz@td`+!2Z5wt#&br!PbRZrwQz-F)>a3S)jRnS1xGS=* zZTp`1bevc>tGSt^V%T)DU?-8ctTlzeOoAehR|7h1a_2WzzL+2KMKqp$yE+Ro?^a3> z&Cv|Z^`yvZ-ZB*&qQkjm#1G*rr3A1oB#m~Fxp{~E;YH;;WifjUT`cdstqo7638!AA)r#X2AFEex`uxX7#m%%VT3f=gwUGnG(5 z#t8}G;$&QI#pGj9pY2NT?9nGQ|ClryZjxQk9j$c)$tYzlV+Ts$K{A9~rGJ?-p@s~R zN?%^)wm~`>Q0QexEmG(9AV0=kWc3ZD_O~rxL*#df_sM;~tKDLpv%W+L!9Qfxa?u;* zTKc{${dqB~WHASbeQ(OrfN6ljymNSzCqrU5ObQ@s@^5yivcKuQNiEq(%f+ac5;BXi zR)z2?YFD)?`FxY}7yINX{LcOP5b#r#C!kc$$()bN&a(40gZO}~*FNC8SJ->vQTG;n z=Ue{~dPz^{0yjO`Qzk!MjRZ$l%7)yskLE?A^E_>P=&_=I)k>2kM>FkXR4e{w>mvEG zheJ~*=Uq0D2nouKtRN1ry1IF7O%A3fyze5%U^nncuwrv?P<7|d>gLm2>-+`T4J7?z z$&F#8uz&fNJY^^5Y=E-fe_y zzTeu{QS{BKO>Cy8nW7c9fc*lwp%PK>S>-FVvJECTg}-}AXX|VPkY{a33ImYs$9z!S zPFe5@QcpwWhLhQdYq%Kp{LPC#I59=Xr*aMWW!k=G%WCogZH$ zhG{04Et>AJKh!@g;_&F;w?ewJseKsKhx)8`C@20*&Egd{ZZpuuwbck%@tWlfoU#|H zV$*&JV8~es%-C+=;p1s)VIU~uJZ$D>+ z4aKIkA(2%TiBeR${k$0icfsY>MkeALD{A<7j` zo(qAoGvzHxc~XQDc+fog!6^h&0SYw1cAq;zZx-i9CqRQ^bz!k7iBXPDg^!bGkHV zOo5|=i4YCiK9kWv9&~z;)gQqF>q_@{-8aV2$$0~>SKm47xL@w$BD1@eAoG8gj)d7# zgwsVa^AmyAC|G@nd>d@r>Mjq!{9qU?BjKtJF9|itR)IG5{OfowhF9L>r0%M(W|nbt z5>A7#V@<)NojKc*SpauGPIMWTsQuuz7Y(Y6=LVw-fmzf>g+FX>u{{4K+lLS)gf#P> zqoh}B3kGkBanzh_4vMYbcW{tni8PZv7R3CPM2k1Gv%}a${ezz3S4Plw&xT%ld-V$ri z!hbSHj-nr$WDalhKb-h~CXO_9-g`oP z-<{>Kj&-;XZ1ILVi%@iY%IV%+h3TYCE==E1TQF#9>s*#dg0FU<~B26 z$vlIB_-S+IaUPoo9`87bd5b^PO23CaOMQpk7JCZ%l0=R>dY&w~pYZfG_1oVW9@R}> zdBBE<{9^sLV_XE+6zQd>OC%+|{aP<=N-SY$(_4E)nJ(~#czAsft0?IXi4XwbfvfaW zgl3Q3WDLjsVa##q=@(ny^$5u6(U;BT$vUwJPGX|3fj z*}?E`kmN|YX3EZz+$(+rr2zM)1I#sZku2= zwD>C3ixrU>_^4U^RUY_I6@q(SKd5{Ri3AVqA8qH{}d z`kQ+qIJ|XCEehzU(1oIm@*;vS<&KxDvqzYmkC2QvMeunsgdydz^re3L%cpYhj0uhw zCWoyjJe0pqrvGfVHL11_JES-GlI|S zX#`+;HCNw03YGEMFk|J`T{EFm86X6o?djLNger(F%y?}z0HTew60?y)AQydg?MC?f zs+hJqyC|HOa1ptPRzg}3%}e>1U3a%l!85FuXD02iO1eKQg<0mS5M-v%J1U8^8hpBP z!w}K82NR9ry5jTmJwU`~iv70;) zXGIMU;!f@T&ql@QGok-z4j=W8frCqcoU}a!n`kt5wM=`%kST>cGlw`S@q+7$2@4+Y zFi7;Yo@lKoM~WOnK2N-LAsjP9UWq6RTcVg_w@n;c23tmOnXYpXf}`1I1xs5CH-%!r zy*m*l``7-d28^J%8(3F;H}tyGC?brXWZA?^i$WH}&B+N>1B|KMB| zYXnZ!#*NiyZ$b-R0JJq=8xN%=h<6_KNR{%PbA#wr!dq+x>T0?zkGVZFdelO3opDWU z5tlL)eoI}m+8X;*57ictFR;2WAWh z*rX^j1HR4+#zTAxQv1w}WJnk36^$M~tbwm7JfK$OZZ?nm@$5m!{w}})R0DMw6H`#6 zVZHPPW{G0E`gVA7l|{9Q=%aQ+CTBl;7IRm}hBpzBmy;Aa;h-llG++Zq-k@)kn%xd+ zMnlDWKc0@Sr8x{eqA;4J9TxlYcTgVzJ{Q}RR^VCVMZwM09J0A!gG2pdw@Un=MQFc% zO({NbW7k$NOQiv3tN>_8+wLl0IRJ)~PSzAp$aiiZe{ZVd-+GP+szxEK5k~B1YuEuf z{s7>tYPY+keHg*X?7YZnx2;=Irz;6pca8j@ga%O^3hox@O&zk*l z_-ojRxHmnshEav*c?@tr^W|GU(+k-yM8T|jRv=JmjHu2?Mg`5?K{01laFUd59|d*6G2qB7y3A zVFub+a$$TpSZAuoEc(0XRI?4#U-~pEU1^)`{0g`F;y^T26FHOx66k+BCQWOIy+kqp zj+-ckO8KWrglwN%4B{1}93RU6!5-4E7iGhhLV1NYCfk^%`i6(Mhd*X%tf}>RcyTN; zWYGux9&|~Ch`#E4ZmLwJU$scvlaXc_4V;IuG1rO?5Z$P}=k>iRy?$J4c4OY6cQb{^ zHR$#`jFcGRH>wUF;fF_^dp?C+Bw^yg* z7^+7^mm;@-l`>4Epz$l7zIbcn*}au(qE!u670`&(8LZU1$kSz>dsh{KJRn6n8kD5^ zW-*oh%T#-)IpunlnX;E);v0j%aEMi73)rP?+o6d@7ovXvM~)cK?CFnv+qC_h+SX=c z(Ai-k@X4U8-yd#cnKmth@7#@-JBNOqw`3+!ggy#<7KRwuda`X|e$^Z!wSVCyKyVC`h)0W4*JLX?j9-mhH-rKb8v&Z`ewL@s4m7ot}N>=gQ zgpALJM!~&z!H|9EZjNl`fEfjpAB^x^B&zcle5RpJh@2B@R*H6&GE$>YaKf38PoCH4 zS6esfFaU+;pCh?Dn~pD;)$QvWt+1L0>Jw!>Z|un=v%V6G8Sd2fPeG~ZEgiRgK-T%y z-C*XP*mBkz)l#`ui6-Wg=5uWgm&df5fZWO5qd6 zP+qwihbZPQ&27|7Y>Q@>G^jfly_y%|O}L>*rekebv@3DV$U`dH-Guf1Q$Xu(GvqrM zpZD&vd1oGM9S7-ici}|pQeW&cr+Fo#izOcj<{rs&U^Bb($%g4_x*XVu%f6nItUfA`1Dqj0b9B!vT|{tdI!*69)Z>%q(-CIzGp} z24ZS!By_2foOyQZL;O)A>*cRnwlm^>Tn&LR?1d0lMZ_@vt%0HR*c=9iL#$1da>`Z~ z%64X|b^wPJ^#3gm$@0mpe3ndK4x6}qc!?bls_P-rF6|G!%Dpv9r`+%Z8u65*$IEvP zSE%|q>l{!VG!bd>UBOutc7e)q%SM0Q$6+;iJ`ax8784VCg)2uY-lH!TXrrFwQq^H* zX|ounU~u%-9EBu=ab;F}3h#klBTwKYF*5;<*3u_Ork-58?1=u|wpUAHX{qGa?@yaI|^rEhk z1C6zs7-I0$mfG_VWF(^JM{OH)PF{C^2V{#JT{x9?ogcbRZSe|aGcT3$q8d(A0n-v% zM&KShM}^57mfw229G25&N`JSlUIg-6l!#d%IPqm_V|uwPwZ;)}A{=25-&P+${DP(o znYX$TUc%jw?x~ZHt~29SArx4rgk7xG_vjFq@(XRkhSZdy9qag@fAia*+nv6^52-aQ zxUVhAZ7`nTd_T6W`h}i{Sw|n7vA>jYf{&=f1FgKIDx^0wVOz7Aru&vulRxG7tpZ)Y z;745*0rrU%+ezM{r3m-t!#QujI)^%Ax^kQf!jSr9GXfjSC56uln1k#rRi(dxf=4x@ z0v7eC^V;blW?!4+JL>t4rfc5m$?G_k5`M3Ud$1AD>6$qrExNc_Q*Ok`j8Y%Zjp06{ z4f_bAWZ^h;gp2L|G~_n+ib0VgL>8c?aC4&?fX4N`%(1+`tvF&;HCrrbTOo6v{z zg#432x*O3(p1G{sq>10}NfwkI+<5ZG90-SzR|NNQ{#Yx|%aE$iSv=JrD$s_1&kG6k zhJ})s4hN};N2LlqUPU*DH!XYyh0F=E&&X)bpi}Li(@zaC>MM zLhzHAe!9~0*PThqD(!{QE^-i&9Ydh5xZO6`V(pzYuYnE}u!ene<+Fd#S6|l=B0(TNoG22NuBQVN#7Ee`$bwY+D=8W4^(3Zw?}Afa zW8B7I_&iyUcN++bmPyjuJC0+qCQ*+a&mRg)fovdOV{YHo{sm8tLt;b6Jk_u`ESNAz^DyO<`4232Qs9h z(f_L$2zq^_|4dcKWy@)Y$jx!QT*4mBe_;YuaoZS?{Cg%JlrQm8OXeKEXcx1~p$quV zl-OLBbpVI2CaeNz9ak)bIvF*5@hrQ!83I>5K-k$~ns{vy+d9;3%O)u%2O(m2vDYO*z>D&3$t# zm2kwiTcy6KxZelTk{LP{-u|G0`6ML%dc2+XGl27<|A~aQfDH*IBMN;<_&`h$h;yr5 z${Di6sC;JZjA9pcH<_`@@1&|g2;-tQjOY9qr$yX6$UG-b$EygTzS|yBtTe1yt#9-e zw63X*6}_K2k^$-$X;t%Qb{@|6gAIplSOjN=P|L zK`D09$Mj9541H3P_Rr!75ys|J z{-BGT2=*j#!BdNQbm+A6XwLz1)x+H+v%}-ItkWqp97$&IJh0q3Ul`w>D%V_PtgX63 zT&BQ?Z}GGaSta=3pT{LfZ)AP~2#a=e@sfWGI-3e5XY_4Qxi2sk`XZO?tC695@&7G9 zgX)zh<7dL|A(6=S{#ipjYC+pcH&LY!aP0J*Vk8lBk4$gwb{IiP(aDxxY5b7ikL+m$ zvZIM=_{Rd($a2zLf&%|Stv=;=(P6o3=S}D<DI6U z8*^5ITDZXY<}E%9u<*yhj;o7sJ7GiEdDOGV-6Mc254f8PQvQaIE5N&n-Ehlwku)%v zRK7h%yVsOv3d(ObbyInTv&=>O7{Y;lgF~=TGoXMXegXB+N|pX`ww}I~Du+i`TZGJ$ z+iT_&(sKteU;m8UpHV&)4&vR^RQ$?zMRkv0_JvypW1loH`fuQUrzo3?B}lbe8);e! znT!_{YfuVO>6KlQtZni;=qaO+CEr^J0r8uk1LcwALq#4OX7Y^F--AEf3U?H72Y1zD zb!h27EJ)Q*J=NZID-Xex;DE~qc0;#bMF8Fv62F>+*eDMHUnz$y0@*rjlMSbZ!Oai; zpL7%EzBD+_uD_vwiKHtK@3mYl73*}Tw5htOF(%`uhc2A4Q+~6c;(;bRm4hT}xj!cF z;!+AQw7{sVN77yE)|iF5Add6QBpRx#&HAbd;smG;jeRD+p#XlKJ2{zhrXcIB{HuVP z|8P=!z=KzCk^c-vnh-T4j& z%S}s`Yv$}k#1BGNHg?S*9~lb2m4jU*+{l3IQxDn!Q0|lCw+i)Tb~2Le-L+O8OCcLd zzy3+-fy=VEak?}n{oe(W{rkr&OVqL5zc6d({)dTb1F9b81PW!Vc~%9DdmNIFR zH`5ju8UJT7*|@vS|8rcI`O~K6i_t>W5$A^qijShW6xX>S3JdVhY@WWWA>!p#^jN@N zF%Ly+9|zBTC08V0kQ{Om|9v*W_vbvzV1LP+G)%2UY2ruvpiEYbW0#=&QqOI}Zy0qS zvAY>=J=e%tDc9`jta)5AMY!(qLeHTz6gVVki{>TPPD3i;Lt5bu3{+IBaunbO%q&o!lRL&H|1=aDnh6l0$$;rGucW-II53AW8aJoLIhCNQuFsk8pdDos4gjjNWZ zXkH?il>4i7)AeyTYwZUUyHFX`Q^Rj~u({$SX+CdpYw6-ige!FZ{^3kAB84~tyvRz0 zZ!dvX-I{6=hWNTeg<94QJ;7Ukn1}Z}bJ;nTI`%>#_EP0#L(S!P0ad)!RWvun z!~nx5NEx@^fL-0-Kzf^z#r;Z6v@Sn~#0r)}0T%v-5!`x1r=4*x)o2Jmmv~pDR%SA9>`8f_3w>#JAAa2LT*uF(`tu&JWbD5W!?cK&}Cy0f8`NK3?pfB(wR@9dqPcD6aEm@{;h1iKExy<*h^xN zz>Q()=StvVf8Th5_Xs}DL%ksPK`#4;fZT1^;!okvqh?sNyJ#nbG8_Sk`*m5W;-JZl zMG9bZk!EuL*X`Cun`ei>SfGkc5$gN(+pPM^TZkss?D-Eyi6`P)`Rrh2%tyMF>-bQd z%E_><5Zy4mOvN@&sl0lAkf22bvn@3pG}D<{y$DKSUZv7b4MsG=vLP71l-t-)rQZT^ zNBE#n6S7Rg^q@m1uR8%d4OJ7S89$VzQ>tb*(#1N;(NEXBtx!d6IS)`u9!bls7q;S7 z#b~r^>(0ZAPs9mj{ou*{F;|#y0~~tFJj&FUbApDc=Y5dw%kZGAs@`b*lBI+dOMf8+ za5AOa+SIp0X<@ls@u!tWp$zvjag5uix`oCparvsWpx(mMAU-8 z?ui@VoqpiQlYx+0S?y;2B3e?dS?LY;jHD;e@UQBG_keQ^M00I?d?l;WUr~9|6p!>B1gsh)8=t^d_M)TdM`NH0QEsm;*Dl$;6 zda{wqxlGPa9-Gz|L9eq-?=pW6dX41fHl4N-HN|Ek&T|4_cqyZn(>SL)6eL@;+NexM zEh%gO--L>#cQRz1zZB{?e}Ak%EdB{~3;AMip|*ugJl;N>E8Ka7Scm+xjX#ab5K>5A zRH8Y!qs1k@PE#l3tUv`zI!OIMKrz(kjxIP|Nc^ literal 0 HcmV?d00001 diff --git a/examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.png b/examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.png new file mode 100644 index 0000000000000000000000000000000000000000..b8eff6ff8825121fc023f9c934b12119e8cf7be3 GIT binary patch literal 26994 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 N^3p0&pCwG*{~vdxj2!>~ literal 0 HcmV?d00001 diff --git a/examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.rst b/examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.rst new file mode 100644 index 00000000..f2e6b7e7 --- /dev/null +++ b/examples/widgets/graphicsview/dragdroprobot/doc/dragdroprobot.rst @@ -0,0 +1,12 @@ +Drag and Drop Robot Example +=========================== + +This example demonstrates a PySide6 application where users can drag and drop +color items onto a robot graphic, showcasing interactive drag-and-drop +functionality within a `QGraphicsView`. It demonstrates the analogous Qt example +`Drag and Drop Robot Example +`_. + +.. image:: dragdroprobot.png + :width: 400 + :alt: dragdroprobot screenshot diff --git a/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py b/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py index 9ed92b26..c25de913 100644 --- a/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py +++ b/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys import math @@ -31,29 +32,29 @@ class ColorItem(QGraphicsItem): (r, g, b) = (self.color.red(), self.color.green(), self.color.blue()) self.setToolTip( f"QColor({r}, {g}, {b})\nClick and drag this color onto the robot!") - self.setCursor(Qt.OpenHandCursor) + self.setCursor(Qt.CursorShape.OpenHandCursor) self._start_drag_distance = QApplication.startDragDistance() def boundingRect(self): return QRectF(-15.5, -15.5, 34, 34) def paint(self, painter, option, widget): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.darkGray) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.darkGray) painter.drawEllipse(-12, -12, 30, 30) - painter.setPen(QPen(Qt.black, 1)) + painter.setPen(QPen(Qt.GlobalColor.black, 1)) painter.setBrush(QBrush(self.color)) painter.drawEllipse(-15, -15, 30, 30) def mousePressEvent(self, event): - if event.button() != Qt.LeftButton: + if event.button() != Qt.MouseButton.LeftButton: event.ignore() return - self.setCursor(Qt.ClosedHandCursor) + self.setCursor(Qt.CursorShape.ClosedHandCursor) def mouseMoveEvent(self, event): - start = QPointF(event.buttonDownScreenPos(Qt.LeftButton)) + start = QPointF(event.buttonDownScreenPos(Qt.MouseButton.LeftButton)) if QLineF(event.screenPos(), start).length() < self._start_drag_distance: return @@ -73,11 +74,11 @@ class ColorItem(QGraphicsItem): mime.setText(f"#{r:02x}{g:02x}{b:02x}") pixmap = QPixmap(34, 34) - pixmap.fill(Qt.white) + pixmap.fill(Qt.GlobalColor.white) with QPainter(pixmap) as painter: painter.translate(15, 15) - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) self.paint(painter, None, None) pixmap.setMask(pixmap.createHeuristicMask()) @@ -86,17 +87,17 @@ class ColorItem(QGraphicsItem): drag.setHotSpot(QPoint(15, 20)) drag.exec() - self.setCursor(Qt.OpenHandCursor) + self.setCursor(Qt.CursorShape.OpenHandCursor) def mouseReleaseEvent(self, event): - self.setCursor(Qt.OpenHandCursor) + self.setCursor(Qt.CursorShape.OpenHandCursor) class RobotPart(QGraphicsItem): def __init__(self, parent=None): super().__init__(parent) - self.color = QColor(Qt.lightGray) + self.color = QColor(Qt.GlobalColor.lightGray) self.pixmap = None self._drag_over = False @@ -132,15 +133,15 @@ class RobotHead(RobotPart): def paint(self, painter, option, widget=None): if not self.pixmap: painter.setBrush(self._drag_over and self.color.lighter(130) or self.color) - painter.drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt.RelativeSize) - painter.setBrush(Qt.white) + painter.drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt.SizeMode.RelativeSize) + painter.setBrush(Qt.GlobalColor.white) painter.drawEllipse(-7, -3 - 20, 7, 7) painter.drawEllipse(0, -3 - 20, 7, 7) - painter.setBrush(Qt.black) + painter.setBrush(Qt.GlobalColor.black) painter.drawEllipse(-5, -1 - 20, 2, 2) painter.drawEllipse(2, -1 - 20, 2, 2) - painter.setPen(QPen(Qt.black, 2)) - painter.setBrush(Qt.NoBrush) + painter.setPen(QPen(Qt.GlobalColor.black, 2)) + painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawArc(-6, -2 - 20, 12, 15, 190 * 16, 160 * 16) else: painter.scale(.2272, .2824) @@ -154,7 +155,7 @@ class RobotTorso(RobotPart): def paint(self, painter, option, widget=None): painter.setBrush(self._drag_over and self.color.lighter(130) or self.color) - painter.drawRoundedRect(-20, -20, 40, 60, 25, 25, Qt.RelativeSize) + painter.drawRoundedRect(-20, -20, 40, 60, 25, 25, Qt.SizeMode.RelativeSize) painter.drawEllipse(-25, -20, 20, 20) painter.drawEllipse(5, -20, 20, 20) painter.drawEllipse(-20, 22, 20, 20) @@ -168,7 +169,7 @@ class RobotLimb(RobotPart): def paint(self, painter, option, widget=None): painter.setBrush(self._drag_over and self.color.lighter(130) or self.color) painter.drawRoundedRect(self.boundingRect(), 50, 50, - Qt.RelativeSize) + Qt.SizeMode.RelativeSize) painter.drawEllipse(-5, -5, 10, 10) @@ -214,7 +215,7 @@ class Robot(RobotPart): self.animations[0].setScaleAt(1, 1.1, 1.1) self.timeline.setUpdateInterval(1000 / 25) - curve = QEasingCurve(QEasingCurve.SineCurve) + curve = QEasingCurve(QEasingCurve.Type.SineCurve) self.timeline.setEasingCurve(curve) self.timeline.setLoopCount(0) self.timeline.setDuration(2000) @@ -244,8 +245,8 @@ if __name__ == '__main__': scene.addItem(robot) view = QGraphicsView(scene) - view.setRenderHint(QPainter.Antialiasing) - view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) + view.setRenderHint(QPainter.RenderHint.Antialiasing) + view.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.BoundingRectViewportUpdate) view.setBackgroundBrush(QColor(230, 200, 167)) view.setWindowTitle("Drag and Drop Robot") view.show() diff --git a/examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.png b/examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.png new file mode 100644 index 0000000000000000000000000000000000000000..840f74fe1f0a4ae04765a598c9bae4e667f1d253 GIT binary patch literal 20101 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.rst b/examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.rst new file mode 100644 index 00000000..91afe0ba --- /dev/null +++ b/examples/widgets/graphicsview/elasticnodes/doc/elasticnodes.rst @@ -0,0 +1,15 @@ +Elastic Nodes Example +===================== + +This example demonstrates a PySide6 application that creates a dynamic and +interactive graphical scene with nodes connected by elastic edges. The nodes +and edges respond to user interactions and physics-based movements, showcasing +the use of `QGraphicsItem`, `QGraphicsScene`, and `QGraphicsView` for creating +complex animations and interactions. + +It demonstrates the analogous Qt example `Elastic Nodes Example +`_. + +.. image:: elasticnodes.png + :width: 400 + :alt: elasticnodes screenshot diff --git a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py index 90cb4962..3a051bd8 100644 --- a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py +++ b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys import weakref @@ -26,7 +27,7 @@ class Edge(QGraphicsItem): self._arrow_size = 10.0 self._source_point = QPointF() self._dest_point = QPointF() - self.setAcceptedMouseButtons(Qt.NoButton) + self.setAcceptedMouseButtons(Qt.MouseButton.NoButton) self.source = weakref.ref(sourceNode) self.dest = weakref.ref(destNode) self.source().add_edge(self) @@ -89,7 +90,8 @@ class Edge(QGraphicsItem): if line.length() == 0.0: return - painter.setPen(QPen(Qt.black, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) + painter.setPen(QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.SolidLine, + Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin)) painter.drawLine(line) # Draw the arrows if there's enough room. @@ -111,7 +113,7 @@ class Edge(QGraphicsItem): math.cos(angle - math.pi + math.pi / 3) * self._arrow_size) dest_arrow_p2 = self._dest_point + arrow_head2 - painter.setBrush(Qt.black) + painter.setBrush(Qt.GlobalColor.black) painter.drawPolygon(QPolygonF([line.p1(), source_arrow_p1, source_arrow_p2])) painter.drawPolygon(QPolygonF([line.p2(), dest_arrow_p1, dest_arrow_p2])) @@ -124,9 +126,9 @@ class Node(QGraphicsItem): self.graph = weakref.ref(graphWidget) self._edge_list = [] self._new_pos = QPointF() - self.setFlag(QGraphicsItem.ItemIsMovable) - self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) - self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges) + self.setCacheMode(QGraphicsItem.CacheMode.DeviceCoordinateCache) self.setZValue(-1) def item_type(self): @@ -197,26 +199,26 @@ class Node(QGraphicsItem): return path def paint(self, painter, option, widget): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.darkGray) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.darkGray) painter.drawEllipse(-7, -7, 20, 20) gradient = QRadialGradient(-3, -3, 10) - if option.state & QStyle.State_Sunken: + if option.state & QStyle.StateFlag.State_Sunken: gradient.setCenter(3, 3) gradient.setFocalPoint(3, 3) - gradient.setColorAt(1, QColor(Qt.yellow).lighter(120)) - gradient.setColorAt(0, QColor(Qt.darkYellow).lighter(120)) + gradient.setColorAt(1, QColor(Qt.GlobalColor.yellow).lighter(120)) + gradient.setColorAt(0, QColor(Qt.GlobalColor.darkYellow).lighter(120)) else: - gradient.setColorAt(0, Qt.yellow) - gradient.setColorAt(1, Qt.darkYellow) + gradient.setColorAt(0, Qt.GlobalColor.yellow) + gradient.setColorAt(1, Qt.GlobalColor.darkYellow) painter.setBrush(QBrush(gradient)) - painter.setPen(QPen(Qt.black, 0)) + painter.setPen(QPen(Qt.GlobalColor.black, 0)) painter.drawEllipse(-10, -10, 20, 20) def itemChange(self, change, value): - if change == QGraphicsItem.ItemPositionChange: + if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange: for edge in self._edge_list: edge().adjust() self.graph().item_moved() @@ -239,13 +241,13 @@ class GraphWidget(QGraphicsView): self._timer_id = 0 scene = QGraphicsScene(self) - scene.setItemIndexMethod(QGraphicsScene.NoIndex) + scene.setItemIndexMethod(QGraphicsScene.ItemIndexMethod.NoIndex) scene.setSceneRect(-200, -200, 400, 400) self.setScene(scene) - self.setCacheMode(QGraphicsView.CacheBackground) - self.setRenderHint(QPainter.Antialiasing) - self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) - self.setResizeAnchor(QGraphicsView.AnchorViewCenter) + self.setCacheMode(QGraphicsView.CacheModeFlag.CacheBackground) + self.setRenderHint(QPainter.RenderHint.Antialiasing) + self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) + self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter) node1 = Node(self) node2 = Node(self) @@ -299,19 +301,19 @@ class GraphWidget(QGraphicsView): def keyPressEvent(self, event): key = event.key() - if key == Qt.Key_Up: + if key == Qt.Key.Key_Up: self._center_node.moveBy(0, -20) - elif key == Qt.Key_Down: + elif key == Qt.Key.Key_Down: self._center_node.moveBy(0, 20) - elif key == Qt.Key_Left: + elif key == Qt.Key.Key_Left: self._center_node.moveBy(-20, 0) - elif key == Qt.Key_Right: + elif key == Qt.Key.Key_Right: self._center_node.moveBy(20, 0) - elif key == Qt.Key_Plus: + elif key == Qt.Key.Key_Plus: self.scale_view(1.2) - elif key == Qt.Key_Minus: + elif key == Qt.Key.Key_Minus: self.scale_view(1 / 1.2) - elif key == Qt.Key_Space or key == Qt.Key_Enter: + elif key == Qt.Key.Key_Space or key == Qt.Key.Key_Enter: for item in self.scene().items(): if isinstance(item, Node): item.setPos(-150 + random(300), -150 + random(300)) @@ -351,7 +353,7 @@ class GraphWidget(QGraphicsView): # Fill. gradient = QLinearGradient(scene_rect.topLeft(), scene_rect.bottomRight()) - gradient.setColorAt(0, Qt.white) + gradient.setColorAt(0, Qt.GlobalColor.white) gradient.setColorAt(1, Qt.lightGray) painter.fillRect(rect.intersected(scene_rect), QBrush(gradient)) painter.setBrush(Qt.NoBrush) @@ -369,7 +371,7 @@ class GraphWidget(QGraphicsView): painter.setFont(font) painter.setPen(Qt.lightGray) painter.drawText(text_rect.translated(2, 2), message) - painter.setPen(Qt.black) + painter.setPen(Qt.GlobalColor.black) painter.drawText(text_rect, message) def scale_view(self, scaleFactor): diff --git a/examples/widgets/imageviewer/doc/imageviewer.rst b/examples/widgets/imageviewer/doc/imageviewer.rst new file mode 100644 index 00000000..5a300173 --- /dev/null +++ b/examples/widgets/imageviewer/doc/imageviewer.rst @@ -0,0 +1,12 @@ +Image Viewer Example +==================== + +This example demonstrates an image viewer application built using PySide6, +featuring functionalities such as opening, saving, printing, copying, pasting, +and zooming images within a scrollable area. This example demonstrates the +analogous Qt example `Image Viewer Example +`_. + +.. image:: imageviewer.webp + :width: 400 + :alt: imageviewer screenshot diff --git a/examples/widgets/imageviewer/doc/imageviewer.webp b/examples/widgets/imageviewer/doc/imageviewer.webp new file mode 100644 index 0000000000000000000000000000000000000000..ecc2f6acc79d49b1ff26cb53a34052a1be40ebb3 GIT binary patch literal 49984 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/imageviewer/imageviewer.py b/examples/widgets/imageviewer/imageviewer.py index 93b1e87b..eb0e1788 100644 --- a/examples/widgets/imageviewer/imageviewer.py +++ b/examples/widgets/imageviewer/imageviewer.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtPrintSupport import QPrintDialog, QPrinter from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, QLabel, @@ -32,13 +33,13 @@ class ImageViewer(QMainWindow): self._scale_factor = 1.0 self._first_file_dialog = True self._image_label = QLabel() - self._image_label.setBackgroundRole(QPalette.Base) - self._image_label.setSizePolicy(QSizePolicy.Ignored, - QSizePolicy.Ignored) + self._image_label.setBackgroundRole(QPalette.ColorRole.Base) + self._image_label.setSizePolicy(QSizePolicy.Policy.Ignored, + QSizePolicy.Policy.Ignored) self._image_label.setScaledContents(True) self._scroll_area = QScrollArea() - self._scroll_area.setBackgroundRole(QPalette.Dark) + self._scroll_area.setBackgroundRole(QPalette.ColorRole.Dark) self._scroll_area.setWidget(self._image_label) self._scroll_area.setVisible(False) self.setCentralWidget(self._scroll_area) @@ -72,7 +73,8 @@ class ImageViewer(QMainWindow): def _set_image(self, new_image): self._image = new_image if self._image.colorSpace().isValid(): - self._image.convertToColorSpace(QColorSpace.SRgb) + color_space = QColorSpace(QColorSpace.NamedColorSpace.SRgb) + self._image.convertToColorSpace(color_space) self._image_label.setPixmap(QPixmap.fromImage(self._image)) self._scale_factor = 1.0 @@ -100,16 +102,16 @@ class ImageViewer(QMainWindow): @Slot() def _open(self): dialog = QFileDialog(self, "Open File") - self._initialize_image_filedialog(dialog, QFileDialog.AcceptOpen) - while (dialog.exec() == QDialog.Accepted + self._initialize_image_filedialog(dialog, QFileDialog.AcceptMode.AcceptOpen) + while (dialog.exec() == QDialog.DialogCode.Accepted and not self.load_file(dialog.selectedFiles()[0])): pass @Slot() def _save_as(self): dialog = QFileDialog(self, "Save File As") - self._initialize_image_filedialog(dialog, QFileDialog.AcceptSave) - while (dialog.exec() == QDialog.Accepted + self._initialize_image_filedialog(dialog, QFileDialog.AcceptMode.AcceptSave) + while (dialog.exec() == QDialog.DialogCode.Accepted and not self._save_file(dialog.selectedFiles()[0])): pass @@ -117,7 +119,7 @@ class ImageViewer(QMainWindow): def _print_(self): printer = QPrinter() dialog = QPrintDialog(printer, self) - if dialog.exec() == QDialog.Accepted: + if dialog.exec() == QDialog.DialogCode.Accepted: with QPainter(printer) as painter: pixmap = self._image_label.pixmap() rect = painter.viewport() @@ -175,7 +177,7 @@ class ImageViewer(QMainWindow): self._open_act = file_menu.addAction("&Open...") self._open_act.triggered.connect(self._open) - self._open_act.setShortcut(QKeySequence.Open) + self._open_act.setShortcut(QKeySequence.StandardKey.Open) self._save_as_act = file_menu.addAction("&Save As...") self._save_as_act.triggered.connect(self._save_as) @@ -183,7 +185,7 @@ class ImageViewer(QMainWindow): self._print_act = file_menu.addAction("&Print...") self._print_act.triggered.connect(self._print_) - self._print_act.setShortcut(QKeySequence.Print) + self._print_act.setShortcut(QKeySequence.StandardKey.Print) self._print_act.setEnabled(False) file_menu.addSeparator() @@ -196,23 +198,23 @@ class ImageViewer(QMainWindow): self._copy_act = edit_menu.addAction("&Copy") self._copy_act.triggered.connect(self._copy) - self._copy_act.setShortcut(QKeySequence.Copy) + self._copy_act.setShortcut(QKeySequence.StandardKey.Copy) self._copy_act.setEnabled(False) self._paste_act = edit_menu.addAction("&Paste") self._paste_act.triggered.connect(self._paste) - self._paste_act.setShortcut(QKeySequence.Paste) + self._paste_act.setShortcut(QKeySequence.StandardKey.Paste) view_menu = self.menuBar().addMenu("&View") self._zoom_in_act = view_menu.addAction("Zoom &In (25%)") - self._zoom_in_act.setShortcut(QKeySequence.ZoomIn) + self._zoom_in_act.setShortcut(QKeySequence.StandardKey.ZoomIn) self._zoom_in_act.triggered.connect(self._zoom_in) self._zoom_in_act.setEnabled(False) self._zoom_out_act = view_menu.addAction("Zoom &Out (25%)") self._zoom_out_act.triggered.connect(self._zoom_out) - self._zoom_out_act.setShortcut(QKeySequence.ZoomOut) + self._zoom_out_act.setShortcut(QKeySequence.StandardKey.ZoomOut) self._zoom_out_act.setEnabled(False) self._normal_size_act = view_menu.addAction("&Normal Size") @@ -263,7 +265,7 @@ class ImageViewer(QMainWindow): def _initialize_image_filedialog(self, dialog, acceptMode): if self._first_file_dialog: self._first_file_dialog = False - locations = QStandardPaths.standardLocations(QStandardPaths.PicturesLocation) + locations = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.PicturesLocation) # noqa: E501 directory = locations[-1] if locations else QDir.currentPath() dialog.setDirectory(directory) @@ -273,5 +275,5 @@ class ImageViewer(QMainWindow): dialog.setMimeTypeFilters(mime_types) dialog.selectMimeTypeFilter("image/jpeg") dialog.setAcceptMode(acceptMode) - if acceptMode == QFileDialog.AcceptSave: + if acceptMode == QFileDialog.AcceptMode.AcceptSave: dialog.setDefaultSuffix("jpg") diff --git a/examples/widgets/imageviewer/main.py b/examples/widgets/imageviewer/main.py index 949def62..2af5833a 100644 --- a/examples/widgets/imageviewer/main.py +++ b/examples/widgets/imageviewer/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/imageviewer example from Qt v6.0""" diff --git a/examples/widgets/itemviews/address_book/adddialogwidget.py b/examples/widgets/itemviews/address_book/adddialogwidget.py index ecb853e8..cd850bed 100644 --- a/examples/widgets/itemviews/address_book/adddialogwidget.py +++ b/examples/widgets/itemviews/address_book/adddialogwidget.py @@ -1,6 +1,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import (QDialog, QLabel, QTextEdit, QLineEdit, @@ -15,8 +16,8 @@ class AddDialogWidget(QDialog): name_label = QLabel("Name") address_label = QLabel("Address") - button_box = QDialogButtonBox(QDialogButtonBox.Ok - | QDialogButtonBox.Cancel) + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) self._name_text = QLineEdit() self._address_text = QTextEdit() @@ -25,8 +26,8 @@ class AddDialogWidget(QDialog): grid.setColumnStretch(1, 2) grid.addWidget(name_label, 0, 0) grid.addWidget(self._name_text, 0, 1) - grid.addWidget(address_label, 1, 0, Qt.AlignLeft | Qt.AlignTop) - grid.addWidget(self._address_text, 1, 1, Qt.AlignLeft) + grid.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) + grid.addWidget(self._address_text, 1, 1, Qt.AlignmentFlag.AlignLeft) layout = QVBoxLayout() layout.addLayout(grid) diff --git a/examples/widgets/itemviews/address_book/address_book.py b/examples/widgets/itemviews/address_book/address_book.py index af0cf3de..64732690 100644 --- a/examples/widgets/itemviews/address_book/address_book.py +++ b/examples/widgets/itemviews/address_book/address_book.py @@ -1,6 +1,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Slot from PySide6.QtGui import QAction diff --git a/examples/widgets/itemviews/address_book/addresswidget.py b/examples/widgets/itemviews/address_book/addresswidget.py index cb2f46ea..37bd06f8 100644 --- a/examples/widgets/itemviews/address_book/addresswidget.py +++ b/examples/widgets/itemviews/address_book/addresswidget.py @@ -1,6 +1,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations try: import cpickle as pickle @@ -67,11 +68,11 @@ class AddressWidget(QTabWidget): # Step 2: get the index of the newly created row and use it. # to set the name ix = self._table_model.index(0, 0, QModelIndex()) - self._table_model.setData(ix, address["name"], Qt.EditRole) + self._table_model.setData(ix, address["name"], Qt.ItemDataRole.EditRole) # Step 3: lather, rinse, repeat for the address. ix = self._table_model.index(0, 1, QModelIndex()) - self._table_model.setData(ix, address["address"], Qt.EditRole) + self._table_model.setData(ix, address["address"], Qt.ItemDataRole.EditRole) # Remove the newAddressTab, as we now have at least one # address in the model. @@ -98,9 +99,9 @@ class AddressWidget(QTabWidget): row = proxy_model.mapToSource(indexes[0]).row() ix = self._table_model.index(row, 0, QModelIndex()) - name = self._table_model.data(ix, Qt.DisplayRole) + name = self._table_model.data(ix, Qt.ItemDataRole.DisplayRole) ix = self._table_model.index(row, 1, QModelIndex()) - address = self._table_model.data(ix, Qt.DisplayRole) + address = self._table_model.data(ix, Qt.ItemDataRole.DisplayRole) # Open an addDialogWidget, and only allow the user to edit the address. add_dialog = AddDialogWidget() @@ -115,7 +116,7 @@ class AddressWidget(QTabWidget): new_address = add_dialog.address if new_address != address: ix = self._table_model.index(row, 1, QModelIndex()) - self._table_model.setData(ix, new_address, Qt.EditRole) + self._table_model.setData(ix, new_address, Qt.ItemDataRole.EditRole) @Slot() def remove_entry(self): @@ -148,11 +149,11 @@ class AddressWidget(QTabWidget): table_view = QTableView() table_view.setModel(proxy_model) table_view.setSortingEnabled(True) - table_view.setSelectionBehavior(QAbstractItemView.SelectRows) + table_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) table_view.horizontalHeader().setStretchLastSection(True) table_view.verticalHeader().hide() - table_view.setEditTriggers(QAbstractItemView.NoEditTriggers) - table_view.setSelectionMode(QAbstractItemView.SingleSelection) + table_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) + table_view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) # This here be the magic: we use the group name (e.g. "ABC") to # build the regex for the QSortFilterProxyModel for the group's @@ -161,10 +162,10 @@ class AddressWidget(QTabWidget): # "A", "B", or "C". Notice that we set it to be case-insensitive. re = QRegularExpression(f"^[{group}].*") assert re.isValid() - re.setPatternOptions(QRegularExpression.CaseInsensitiveOption) + re.setPatternOptions(QRegularExpression.PatternOption.CaseInsensitiveOption) proxy_model.setFilterRegularExpression(re) proxy_model.setFilterKeyColumn(0) # Filter on the "name" column - proxy_model.sort(0, Qt.AscendingOrder) + proxy_model.sort(0, Qt.SortOrder.AscendingOrder) # This prevents an application crash (see: # https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) # noqa: E501 diff --git a/examples/widgets/itemviews/address_book/newaddresstab.py b/examples/widgets/itemviews/address_book/newaddresstab.py index d3d037ad..41410176 100644 --- a/examples/widgets/itemviews/address_book/newaddresstab.py +++ b/examples/widgets/itemviews/address_book/newaddresstab.py @@ -1,6 +1,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import (Qt, Signal) from PySide6.QtWidgets import (QWidget, QLabel, QPushButton, QVBoxLayout) @@ -25,7 +26,7 @@ class NewAddressTab(QWidget): layout = QVBoxLayout() layout.addWidget(description_label) - layout.addWidget(add_button, 0, Qt.AlignCenter) + layout.addWidget(add_button, 0, Qt.AlignmentFlag.AlignCenter) self.setLayout(layout) diff --git a/examples/widgets/itemviews/address_book/tablemodel.py b/examples/widgets/itemviews/address_book/tablemodel.py index 3c1dbd4c..6654edfa 100644 --- a/examples/widgets/itemviews/address_book/tablemodel.py +++ b/examples/widgets/itemviews/address_book/tablemodel.py @@ -1,6 +1,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import (Qt, QAbstractTableModel, QModelIndex) @@ -23,7 +24,7 @@ class TableModel(QAbstractTableModel): """ Returns the number of columns the model holds. """ return 2 - def data(self, index, role=Qt.DisplayRole): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): """ Depending on the index and role given, return data. If not returning data, return None (PySide equivalent of QT's "invalid QVariant"). @@ -34,7 +35,7 @@ class TableModel(QAbstractTableModel): if not 0 <= index.row() < len(self.addresses): return None - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: name = self.addresses[index.row()]["name"] address = self.addresses[index.row()]["address"] @@ -45,12 +46,12 @@ class TableModel(QAbstractTableModel): return None - def headerData(self, section, orientation, role=Qt.DisplayRole): + def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole): """ Set the headers to be displayed. """ - if role != Qt.DisplayRole: + if role != Qt.ItemDataRole.DisplayRole: return None - if orientation == Qt.Horizontal: + if orientation == Qt.Orientation.Horizontal: if section == 0: return "Name" elif section == 1: @@ -77,11 +78,11 @@ class TableModel(QAbstractTableModel): self.endRemoveRows() return True - def setData(self, index, value, role=Qt.EditRole): + def setData(self, index, value, role=Qt.ItemDataRole.EditRole): """ Adjust the data (set it to ) depending on the given index and role. """ - if role != Qt.EditRole: + if role != Qt.ItemDataRole.EditRole: return False if index.isValid() and 0 <= index.row() < len(self.addresses): @@ -104,6 +105,6 @@ class TableModel(QAbstractTableModel): manually adjust each tableView to have NoEditTriggers. """ if not index.isValid(): - return Qt.ItemIsEnabled + return Qt.ItemFlag.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index) - | Qt.ItemIsEditable) + | Qt.ItemFlag.ItemIsEditable) diff --git a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py index a30b0abd..759f1ae1 100644 --- a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py +++ b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtCore import (QDate, QDateTime, QRegularExpression, @@ -91,7 +92,7 @@ class Window(QWidget): self.setWindowTitle("Basic Sort/Filter Model") self.resize(500, 450) - self._proxy_view.sortByColumn(1, Qt.AscendingOrder) + self._proxy_view.sortByColumn(1, Qt.SortOrder.AscendingOrder) self._filter_column_combo_box.setCurrentIndex(1) self._filter_pattern_line_edit.setText("Andy|Grace") @@ -114,7 +115,7 @@ class Window(QWidget): reg_exp = QRegularExpression(pattern) if not self._filter_case_sensitivity_check_box.isChecked(): options = reg_exp.patternOptions() - options |= QRegularExpression.CaseInsensitiveOption + options |= QRegularExpression.PatternOption.CaseInsensitiveOption reg_exp.setPatternOptions(options) self._proxy_model.setFilterRegularExpression(reg_exp) @@ -125,9 +126,9 @@ class Window(QWidget): @Slot() def sort_changed(self): if self._sort_case_sensitivity_check_box.isChecked(): - case_sensitivity = Qt.CaseSensitive + case_sensitivity = Qt.CaseSensitivity.CaseSensitive else: - case_sensitivity = Qt.CaseInsensitive + case_sensitivity = Qt.CaseSensitivity.CaseInsensitive self._proxy_model.setSortCaseSensitivity(case_sensitivity) @@ -142,9 +143,9 @@ def add_mail(model, subject, sender, date): def create_mail_model(parent): model = QStandardItemModel(0, 3, parent) - model.setHeaderData(0, Qt.Horizontal, "Subject") - model.setHeaderData(1, Qt.Horizontal, "Sender") - model.setHeaderData(2, Qt.Horizontal, "Date") + model.setHeaderData(0, Qt.Orientation.Horizontal, "Subject") + model.setHeaderData(1, Qt.Orientation.Horizontal, "Sender") + model.setHeaderData(2, Qt.Orientation.Horizontal, "Date") add_mail(model, "Happy New Year!", "Grace K. ", QDateTime(QDate(2006, 12, 31), QTime(17, 3))) diff --git a/examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.rst b/examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.rst new file mode 100644 index 00000000..a8aa5f33 --- /dev/null +++ b/examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.rst @@ -0,0 +1,11 @@ +Basic Sort/Filter Model Example +=============================== + +The Basic Sort/Filter Model example illustrates how to use `QSortFilterProxyModel` +to perform basic sorting and filtering. This example demonstrates the analogous +Qt example `Basic Sort/Filter Model Example +`_. + +.. image:: basicfiltermodel.webp + :width: 400 + :alt: basicsortfiltermodel screenshot diff --git a/examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.webp b/examples/widgets/itemviews/basicfiltermodel/doc/basicfiltermodel.webp new file mode 100644 index 0000000000000000000000000000000000000000..6d290cba3f54a6d84f478353c8fe412957b15e88 GIT binary patch literal 38116 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

;l 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 MP_Wkp(ZK)!09J`lNB{r; literal 0 HcmV?d00001 diff --git a/examples/widgets/itemviews/dirview/dirview.py b/examples/widgets/itemviews/dirview/dirview.py index d1be6958..5408f5c7 100644 --- a/examples/widgets/itemviews/dirview/dirview.py +++ b/examples/widgets/itemviews/dirview/dirview.py @@ -1,5 +1,6 @@ # Copyright (C) 2020 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from argparse import ArgumentParser, RawTextHelpFormatter diff --git a/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst b/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst index c936972a..b5c7c240 100644 --- a/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst +++ b/examples/widgets/itemviews/editabletreemodel/doc/editabletreemodel.rst @@ -7,4 +7,3 @@ A Python application that demonstrates the analogous example in C++ .. image:: editabletreemodel.png :width: 611 :alt: editabletreemodel screenshot - diff --git a/examples/widgets/itemviews/editabletreemodel/main.py b/examples/widgets/itemviews/editabletreemodel/main.py index 491baa68..dd33c542 100644 --- a/examples/widgets/itemviews/editabletreemodel/main.py +++ b/examples/widgets/itemviews/editabletreemodel/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgets/itemviews/editabletreemodel/mainwindow.py b/examples/widgets/itemviews/editabletreemodel/mainwindow.py index 1489bf28..e33220e8 100644 --- a/examples/widgets/itemviews/editabletreemodel/mainwindow.py +++ b/examples/widgets/itemviews/editabletreemodel/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from pathlib import Path @@ -20,8 +21,8 @@ class MainWindow(QMainWindow): self.view = QTreeView() self.view.setAlternatingRowColors(True) - self.view.setSelectionBehavior(QAbstractItemView.SelectItems) - self.view.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) + self.view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems) + self.view.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self.view.setAnimated(False) self.view.setAllColumnsShowFocus(True) self.setCentralWidget(self.view) @@ -90,13 +91,13 @@ class MainWindow(QMainWindow): for column in range(model.columnCount(index)): child: QModelIndex = model.index(0, column, index) - model.setData(child, "[No data]", Qt.EditRole) - if not model.headerData(column, Qt.Horizontal): - model.setHeaderData(column, Qt.Horizontal, "[No header]", - Qt.EditRole) + model.setData(child, "[No data]", Qt.ItemDataRole.EditRole) + if not model.headerData(column, Qt.Orientation.Horizontal): + model.setHeaderData(column, Qt.Orientation.Horizontal, "[No header]", + Qt.ItemDataRole.EditRole) selection_model.setCurrentIndex( - model.index(0, 0, index), QItemSelectionModel.ClearAndSelect + model.index(0, 0, index), QItemSelectionModel.SelectionFlag.ClearAndSelect ) self.update_actions() @@ -107,8 +108,8 @@ class MainWindow(QMainWindow): changed: bool = model.insertColumn(column + 1) if changed: - model.setHeaderData(column + 1, Qt.Horizontal, "[No header]", - Qt.EditRole) + model.setHeaderData(column + 1, Qt.Orientation.Horizontal, "[No header]", + Qt.ItemDataRole.EditRole) self.update_actions() @@ -125,7 +126,7 @@ class MainWindow(QMainWindow): for column in range(model.columnCount(parent)): child: QModelIndex = model.index(index.row() + 1, column, parent) - model.setData(child, "[No data]", Qt.EditRole) + model.setData(child, "[No data]", Qt.ItemDataRole.EditRole) @Slot() def remove_column(self) -> None: diff --git a/examples/widgets/itemviews/editabletreemodel/treeitem.py b/examples/widgets/itemviews/editabletreemodel/treeitem.py index 1a25b077..dfc98b3e 100644 --- a/examples/widgets/itemviews/editabletreemodel/treeitem.py +++ b/examples/widgets/itemviews/editabletreemodel/treeitem.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations class TreeItem: diff --git a/examples/widgets/itemviews/editabletreemodel/treemodel.py b/examples/widgets/itemviews/editabletreemodel/treemodel.py index a58572fc..98e4d475 100644 --- a/examples/widgets/itemviews/editabletreemodel/treemodel.py +++ b/examples/widgets/itemviews/editabletreemodel/treemodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QModelIndex, Qt, QAbstractItemModel @@ -22,7 +23,7 @@ class TreeModel(QAbstractItemModel): if not index.isValid(): return None - if role != Qt.DisplayRole and role != Qt.EditRole: + if role != Qt.ItemDataRole.DisplayRole and role != Qt.ItemDataRole.EditRole: return None item: TreeItem = self.get_item(index) @@ -31,9 +32,9 @@ class TreeModel(QAbstractItemModel): def flags(self, index: QModelIndex) -> Qt.ItemFlags: if not index.isValid(): - return Qt.NoItemFlags + return Qt.ItemFlag.NoItemFlags - return Qt.ItemIsEditable | QAbstractItemModel.flags(self, index) + return Qt.ItemFlag.ItemIsEditable | QAbstractItemModel.flags(self, index) def get_item(self, index: QModelIndex = QModelIndex()) -> TreeItem: if index.isValid(): @@ -44,8 +45,8 @@ class TreeModel(QAbstractItemModel): return self.root_item def headerData(self, section: int, orientation: Qt.Orientation, - role: int = Qt.DisplayRole): - if orientation == Qt.Horizontal and role == Qt.DisplayRole: + role: int = Qt.ItemDataRole.DisplayRole): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole: return self.root_item.data(section) return None @@ -132,20 +133,21 @@ class TreeModel(QAbstractItemModel): return parent_item.child_count() def setData(self, index: QModelIndex, value, role: int) -> bool: - if role != Qt.EditRole: + if role != Qt.ItemDataRole.EditRole: return False item: TreeItem = self.get_item(index) result: bool = item.set_data(index.column(), value) if result: - self.dataChanged.emit(index, index, [Qt.DisplayRole, Qt.EditRole]) + self.dataChanged.emit(index, index, + [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]) return result def setHeaderData(self, section: int, orientation: Qt.Orientation, value, role: int = None) -> bool: - if role != Qt.EditRole or orientation != Qt.Horizontal: + if role != Qt.ItemDataRole.EditRole or orientation != Qt.Orientation.Horizontal: return False result: bool = self.root_item.set_data(section, value) diff --git a/examples/widgets/itemviews/fetchmore/doc/fetchmore.png b/examples/widgets/itemviews/fetchmore/doc/fetchmore.png new file mode 100644 index 0000000000000000000000000000000000000000..e3b47b8a85019d4d5d5cdbba7daccd60b6e6f386 GIT binary patch literal 28481 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/itemviews/fetchmore/doc/fetchmore.rst b/examples/widgets/itemviews/fetchmore/doc/fetchmore.rst new file mode 100644 index 00000000..08fa60a0 --- /dev/null +++ b/examples/widgets/itemviews/fetchmore/doc/fetchmore.rst @@ -0,0 +1,10 @@ +Fetch More Example +================== + +The Fetch More example shows how to add items to an item view model on demand. +This example demonstrates the analogous Qt example `Fetch More Example +`_. + +.. image:: fetchmore.png + :width: 400 + :alt: fetchmore screenshot diff --git a/examples/widgets/itemviews/fetchmore/fetchmore.py b/examples/widgets/itemviews/fetchmore/fetchmore.py index 5150250e..8f41910a 100644 --- a/examples/widgets/itemviews/fetchmore/fetchmore.py +++ b/examples/widgets/itemviews/fetchmore/fetchmore.py @@ -2,6 +2,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the itemviews/fetchmore/fetchmore example from Qt v6.x @@ -36,7 +37,7 @@ class FileListModel(QAbstractListModel): def rowCount(self, parent=QModelIndex()): return self._file_count - def data(self, index, role=Qt.DisplayRole): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): if not index.isValid(): return None @@ -44,15 +45,15 @@ class FileListModel(QAbstractListModel): if row >= len(self._file_list) or row < 0: return None - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return self._file_list[row].fileName() - if role == Qt.BackgroundRole: + if role == Qt.ItemDataRole.BackgroundRole: batch = row // BATCH_SIZE palette = qApp.palette() # noqa: F821 return palette.base() if batch % 2 == 0 else palette.alternateBase() - if role == Qt.DecorationRole: + if role == Qt.ItemDataRole.DecorationRole: return self._icon_provider.icon(self._file_list[row]) return None @@ -80,8 +81,8 @@ class FileListModel(QAbstractListModel): directory = QDir(path) self.beginResetModel() - directory_filter = QDir.AllEntries | QDir.NoDot - self._file_list = directory.entryInfoList(directory_filter, QDir.Name) + directory_filter = QDir.Filter.AllEntries | QDir.Filter.NoDot + self._file_list = directory.entryInfoList(directory_filter, QDir.SortFlag.Name) self._file_count = 0 self.endResetModel() @@ -100,8 +101,8 @@ class Window(QWidget): self._view.setModel(self._model) self._log_viewer = QPlainTextEdit() - self._log_viewer.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, - QSizePolicy.Preferred)) + self._log_viewer.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Preferred, + QSizePolicy.Policy.Preferred)) self._model.number_populated.connect(self.update_log) self._view.activated.connect(self.activated) diff --git a/examples/widgets/itemviews/jsonmodel/jsonmodel.py b/examples/widgets/itemviews/jsonmodel/jsonmodel.py index 6e614c77..00ec1478 100644 --- a/examples/widgets/itemviews/jsonmodel/jsonmodel.py +++ b/examples/widgets/itemviews/jsonmodel/jsonmodel.py @@ -1,9 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import json import sys -from typing import Any, List, Dict, Union +from typing import Any from PySide6.QtWidgets import QTreeView, QApplication, QHeaderView from PySide6.QtCore import QAbstractItemModel, QModelIndex, QObject, Qt, QFileInfo @@ -71,7 +72,7 @@ class TreeItem: @classmethod def load( - cls, value: Union[List, Dict], parent: "TreeItem" = None, sort=True + cls, value: list | dict, parent: "TreeItem" = None, sort=True ) -> "TreeItem": """Create a 'root' TreeItem from a nested list or a nested dictonary @@ -155,14 +156,14 @@ class JsonModel(QAbstractItemModel): item = index.internalPointer() - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: if index.column() == 0: return item.key if index.column() == 1: return item.value - elif role == Qt.EditRole: + elif role == Qt.ItemDataRole.EditRole: if index.column() == 1: return item.value @@ -177,12 +178,12 @@ class JsonModel(QAbstractItemModel): role (Qt.ItemDataRole) """ - if role == Qt.EditRole: + if role == Qt.ItemDataRole.EditRole: if index.column() == 1: item = index.internalPointer() item.value = str(value) - self.dataChanged.emit(index, index, [Qt.EditRole]) + self.dataChanged.emit(index, index, [Qt.ItemDataRole.EditRole]) return True @@ -196,10 +197,10 @@ class JsonModel(QAbstractItemModel): For the JsonModel, it returns only data for columns (orientation = Horizontal) """ - if role != Qt.DisplayRole: + if role != Qt.ItemDataRole.DisplayRole: return None - if orientation == Qt.Horizontal: + if orientation == Qt.Orientation.Horizontal: return self._headers[section] def index(self, row: int, column: int, parent=QModelIndex()) -> QModelIndex: @@ -270,7 +271,7 @@ class JsonModel(QAbstractItemModel): flags = super(JsonModel, self).flags(index) if index.column() == 1: - return Qt.ItemIsEditable | flags + return Qt.ItemFlag.ItemIsEditable | flags else: return flags @@ -314,7 +315,7 @@ if __name__ == "__main__": model.load(document) view.show() - view.header().setSectionResizeMode(0, QHeaderView.Stretch) + view.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) view.setAlternatingRowColors(True) view.resize(500, 300) app.exec() diff --git a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py index 577f0faa..11bdeddb 100644 --- a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py +++ b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -32,7 +33,7 @@ class SpinBoxDelegate(QStyledItemDelegate): #! [2] def setEditorData(self, editor, index): - value = index.model().data(index, Qt.EditRole) + value = index.model().data(index, Qt.ItemDataRole.EditRole) editor.setValue(value) #! [2] @@ -40,7 +41,7 @@ class SpinBoxDelegate(QStyledItemDelegate): def setModelData(self, editor, model, index): editor.interpretText() value = editor.value() - model.setData(index, value, Qt.EditRole) + model.setData(index, value, Qt.ItemDataRole.EditRole) #! [3] #! [4] diff --git a/examples/widgets/itemviews/spreadsheet/main.py b/examples/widgets/itemviews/spreadsheet/main.py index 0ecc5ec2..e811f629 100644 --- a/examples/widgets/itemviews/spreadsheet/main.py +++ b/examples/widgets/itemviews/spreadsheet/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -14,6 +15,6 @@ if __name__ == "__main__": sheet = SpreadSheet(10, 6) sheet.setWindowIcon(QPixmap(":/images/interview.png")) sheet.show() - sheet.layout().setSizeConstraint(QLayout.SetFixedSize) + sheet.layout().setSizeConstraint(QLayout.SizeConstraint.SetFixedSize) sys.exit(app.exec()) diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheet.py b/examples/widgets/itemviews/spreadsheet/spreadsheet.py index 82ebe5eb..88357ae1 100644 --- a/examples/widgets/itemviews/spreadsheet/spreadsheet.py +++ b/examples/widgets/itemviews/spreadsheet/spreadsheet.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import QPoint, Qt, QCoreApplication, Slot from PySide6.QtGui import QAction, QBrush, QPixmap, QColor, QPainter @@ -11,12 +12,11 @@ from PySide6.QtWidgets import (QColorDialog, QComboBox, QDialog, QFontDialog, from spreadsheetdelegate import SpreadSheetDelegate from spreadsheetitem import SpreadSheetItem -from typing import Optional from numbers import Number class SpreadSheet(QMainWindow): - def __init__(self, rows: Number, cols: Number, parent: Optional[QWidget] = None) -> None: + def __init__(self, rows: Number, cols: Number, parent: QWidget | None = None) -> None: super().__init__(parent) self._tool_bar = QToolBar(self) @@ -76,30 +76,30 @@ class SpreadSheet(QMainWindow): self._cell_sum_action.triggered.connect(self.action_sum) self._cell_add_action = QAction("&Add", self) - self._cell_add_action.setShortcut(Qt.CTRL | Qt.Key_Plus) + self._cell_add_action.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_Plus) self._cell_add_action.triggered.connect(self.action_add) self._cell_sub_action = QAction("&Subtract", self) - self._cell_sub_action.setShortcut(Qt.CTRL | Qt.Key_Minus) + self._cell_sub_action.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_Minus) self._cell_sub_action.triggered.connect(self.action_subtract) self._cell_mul_action = QAction("&Multiply", self) - self._cell_mul_action.setShortcut(Qt.CTRL | Qt.Key_multiply) + self._cell_mul_action.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_multiply) self._cell_mul_action.triggered.connect(self.action_multiply) self._cell_div_action = QAction("&Divide", self) - self._cell_div_action.setShortcut(Qt.CTRL | Qt.Key_division) + self._cell_div_action.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_division) self._cell_div_action.triggered.connect(self.action_divide) self._font_action = QAction("Font...", self) - self._font_action.setShortcut(Qt.CTRL | Qt.Key_F) + self._font_action.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_F) self._font_action.triggered.connect(self.select_font) self._color_action = QAction(QPixmap(16, 16), "Background &Color...", self) self._color_action.triggered.connect(self.select_color) self._clear_action = QAction("Clear", self) - self._clear_action.setShortcut(Qt.Key_Delete) + self._clear_action.setShortcut(Qt.Key.Key_Delete) self._clear_action.triggered.connect(self.clear) self._about_spreadsheet = QAction("About Spreadsheet", self) @@ -137,7 +137,7 @@ class SpreadSheet(QMainWindow): @Slot(QTableWidgetItem) def update_status(self, item: QTableWidgetItem) -> None: if item and item == self._table.currentItem(): - self.statusBar().showMessage(str(item.data(Qt.StatusTipRole)), 1000) + self.statusBar().showMessage(str(item.data(Qt.ItemDataRole.StatusTipRole)), 1000) self._cell_label.setText( "Cell: ({})".format( SpreadSheetItem.encode_pos(self._table.row(item), self._table.column(item)) @@ -174,7 +174,7 @@ class SpreadSheet(QMainWindow): if item != self._table.currentItem(): return if item: - self._formula_input.setText(str(item.data(Qt.EditRole))) + self._formula_input.setText(str(item.data(Qt.ItemDataRole.EditRole))) else: self._formula_input.clear() @@ -187,7 +187,7 @@ class SpreadSheet(QMainWindow): if not item: self._table.setItem(row, col, SpreadSheetItem(text)) else: - item.setData(Qt.EditRole, text) + item.setData(Qt.ItemDataRole.EditRole, text) self._table.viewport().update() @Slot() @@ -248,7 +248,7 @@ class SpreadSheet(QMainWindow): cell1_col_input.setCurrentIndex(c1_col) operator_label = QLabel(opText, group) - operator_label.setAlignment(Qt.AlignHCenter) + operator_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) cell2_label = QLabel(c2Text, group) cell2_row_input = QComboBox(group) @@ -260,7 +260,7 @@ class SpreadSheet(QMainWindow): cell2_col_input.setCurrentIndex(c2_col) equals_label = QLabel("=", group) - equals_label.setAlignment(Qt.AlignHCenter) + equals_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) out_label = QLabel(outText, group) out_row_input = QComboBox(group) @@ -405,10 +405,10 @@ class SpreadSheet(QMainWindow): self.addAction(self._font_action) self.addAction(self._second_separator) self.addAction(self._clear_action) - self.setContextMenuPolicy(Qt.ActionsContextMenu) + self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) def setup_contents(self) -> None: - title_background = QBrush(Qt.lightGray) + title_background = QBrush(Qt.GlobalColor.lightGray) title_font = self._table.font() title_font.setBold(True) @@ -469,7 +469,7 @@ class SpreadSheet(QMainWindow): self._table.setItem(8, 2, SpreadSheetItem("1240")) self._table.setItem(9, 2, SpreadSheetItem()) - self._table.item(9, 2).setBackground(Qt.lightGray) + self._table.item(9, 2).setBackground(Qt.GlobalColor.lightGray) # column 3 self._table.setItem(0, 3, SpreadSheetItem("Currency")) @@ -487,7 +487,7 @@ class SpreadSheet(QMainWindow): self._table.setItem(8, 3, SpreadSheetItem("USD")) self._table.setItem(9, 3, SpreadSheetItem()) - self._table.item(9, 3).setBackground(Qt.lightGray) + self._table.item(9, 3).setBackground(Qt.GlobalColor.lightGray) # column 4 self._table.setItem(0, 4, SpreadSheetItem("Ex. Rate")) diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py b/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py index 57aba6f4..a6d81223 100644 --- a/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py +++ b/examples/widgets/itemviews/spreadsheet/spreadsheetdelegate.py @@ -1,16 +1,15 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import (QAbstractItemModel, QDate, QModelIndex, QObject, QStringListModel, Qt, Slot) from PySide6.QtWidgets import (QCompleter, QDateTimeEdit, QLineEdit, QStyleOptionViewItem, QStyledItemDelegate, QWidget) -from typing import Optional - class SpreadSheetDelegate(QStyledItemDelegate): - def __init__(self, parent: Optional[QObject] = None) -> None: + def __init__(self, parent: QObject | None = None) -> None: super().__init__(parent) def create_editor(self, parent: QWidget, @@ -27,7 +26,8 @@ class SpreadSheetDelegate(QStyledItemDelegate): # create a completer with the strings in the column as model allStrings = QStringListModel() for i in range(1, index.model().rowCount()): - strItem = str(index.model().data(index.sibling(i, index.column()), Qt.EditRole)) + strItem = str(index.model().data(index.sibling(i, index.column()), + Qt.ItemDataRole.EditRole)) if not allStrings.contains(strItem): allStrings.append(strItem) @@ -46,14 +46,14 @@ class SpreadSheetDelegate(QStyledItemDelegate): def set_editor_data(self, editor: QWidget, index: QModelIndex) -> None: edit = QLineEdit(editor) if edit: - edit.setText(str(index.model().data(index, Qt.EditRole))) + edit.setText(str(index.model().data(index, Qt.ItemDataRole.EditRole))) return dateEditor = QDateTimeEdit(editor) if dateEditor: dateEditor.setDate( QDate.fromString( - str(index.model().data(index, Qt.EditRole)), "d/M/yyyy")) + str(index.model().data(index, Qt.ItemDataRole.EditRole)), "d/M/yyyy")) def set_model_data(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex) -> None: diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py b/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py index dc70da88..8f1f9b42 100644 --- a/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py +++ b/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py @@ -1,7 +1,8 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations -from typing import Any, Tuple +from typing import Any from PySide6.QtCore import QMetaType, Qt from PySide6.QtWidgets import QTableWidget, QTableWidgetItem @@ -13,25 +14,25 @@ class SpreadSheetItem(QTableWidgetItem): return super().__init_subclass__() def data(self, role: int) -> Any: - if role == Qt.EditRole or role == Qt.StatusTipRole: + if role == Qt.ItemDataRole.EditRole or role == Qt.ItemDataRole.StatusTipRole: return self.formula() - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: return self.display() t = str(self.display()) - if role == Qt.ForegroundRole: + if role == Qt.ItemDataRole.ForegroundRole: try: number = int(t) - color = Qt.red if number < 0 else Qt.blue + color = Qt.GlobalColor.red if number < 0 else Qt.GlobalColor.blue except ValueError: - color = Qt.black + color = Qt.GlobalColor.black return color - if role == Qt.TextAlignmentRole: + if role == Qt.ItemDataRole.TextAlignmentRole: if t and (t[0].isdigit() or t[0] == '-'): - return int(Qt.AlignRight | Qt.AlignVCenter) + return int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) return super().data(role) @@ -51,7 +52,7 @@ class SpreadSheetItem(QTableWidgetItem): return result def formula(self) -> None: - return str(super().data(Qt.DisplayRole)) + return str(super().data(Qt.ItemDataRole.DisplayRole)) def compute_formula(self, formula: str, widget: QTableWidget, this) -> QMetaType.Type.QVariant: # check if the string is actually a formula or not @@ -106,7 +107,7 @@ class SpreadSheetItem(QTableWidgetItem): return result - def decode_pos(pos: str) -> Tuple[int, int]: + def decode_pos(pos: str) -> tuple[int, int]: if (not pos): col = -1 row = -1 diff --git a/examples/widgets/itemviews/stardelegate/stardelegate.py b/examples/widgets/itemviews/stardelegate/stardelegate.py index 973eb14f..93b09cdf 100644 --- a/examples/widgets/itemviews/stardelegate/stardelegate.py +++ b/examples/widgets/itemviews/stardelegate/stardelegate.py @@ -2,6 +2,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import QStyledItemDelegate, QStyle @@ -33,7 +34,7 @@ class StarDelegate(QStyledItemDelegate): # If the row is currently selected, we need to make sure we # paint the background accordingly. - if option.state & QStyle.State_Selected: + if option.state & QStyle.StateFlag.State_Selected: # The original C++ example used option.palette.foreground() to # get the brush for painting, but there are a couple of # problems with that: @@ -111,9 +112,9 @@ if __name__ == "__main__": # Create and populate the tableWidget table_widget = QTableWidget(4, 4) table_widget.setItemDelegate(StarDelegate()) - table_widget.setEditTriggers(QAbstractItemView.DoubleClicked - | QAbstractItemView.SelectedClicked) - table_widget.setSelectionBehavior(QAbstractItemView.SelectRows) + table_widget.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked + | QAbstractItemView.EditTrigger.SelectedClicked) + table_widget.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) table_widget.setHorizontalHeaderLabels(["Title", "Genre", "Artist", "Rating"]) data = [["Mass in B-Minor", "Baroque", "J.S. Bach", 5], diff --git a/examples/widgets/itemviews/stardelegate/stareditor.py b/examples/widgets/itemviews/stardelegate/stareditor.py index 296afa95..1fd7839e 100644 --- a/examples/widgets/itemviews/stardelegate/stareditor.py +++ b/examples/widgets/itemviews/stardelegate/stareditor.py @@ -2,6 +2,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import (QWidget) from PySide6.QtGui import (QPainter) diff --git a/examples/widgets/itemviews/stardelegate/starrating.py b/examples/widgets/itemviews/stardelegate/starrating.py index 28dbacd6..694bb43e 100644 --- a/examples/widgets/itemviews/stardelegate/starrating.py +++ b/examples/widgets/itemviews/stardelegate/starrating.py @@ -2,6 +2,7 @@ # Copyright (C) 2011 Arun Srinivasan # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from math import (cos, sin, pi) @@ -11,7 +12,7 @@ from PySide6.QtCore import (QPointF, QSize, Qt) PAINTING_SCALE_FACTOR = 20 -class StarRating(object): +class StarRating: """ Handle the actual painting of the stars themselves. """ def __init__(self, starCount=1, maxStarCount=5): @@ -40,8 +41,8 @@ class StarRating(object): """ Paint the stars (and/or diamonds if we're in editing mode). """ painter.save() - painter.setRenderHint(QPainter.Antialiasing, True) - painter.setPen(Qt.NoPen) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + painter.setPen(Qt.PenStyle.NoPen) if isEditable: painter.setBrush(palette.highlight()) @@ -54,7 +55,7 @@ class StarRating(object): for i in range(self.MAX_STAR_COUNT): if i < self.star_count: - painter.drawPolygon(self._star_polygon, Qt.WindingFill) + painter.drawPolygon(self._star_polygon, Qt.FillRule.WindingFill) elif isEditable: painter.drawPolygon(self._diamond_polygon, Qt.WindingFill) painter.translate(1.0, 0.0) diff --git a/examples/widgets/layouts/basiclayouts/basiclayouts.py b/examples/widgets/layouts/basiclayouts/basiclayouts.py index 827cb785..949adc67 100644 --- a/examples/widgets/layouts/basiclayouts/basiclayouts.py +++ b/examples/widgets/layouts/basiclayouts/basiclayouts.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/layouts/basiclayout example from Qt v5.x""" @@ -29,7 +30,8 @@ class Dialog(QDialog): big_editor.setPlainText("This widget takes up all the remaining space " "in the top-level layout.") - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) diff --git a/examples/widgets/layouts/borderlayout/borderlayout.py b/examples/widgets/layouts/borderlayout/borderlayout.py index 3268d703..e60c2465 100644 --- a/examples/widgets/layouts/borderlayout/borderlayout.py +++ b/examples/widgets/layouts/borderlayout/borderlayout.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/layouts/borderlayout example from Qt v5.x""" @@ -62,7 +63,7 @@ class BorderLayout(QLayout): self.add(QWidgetItem(widget), position) def expandingDirections(self) -> Qt.Orientations: - return Qt.Horizontal | Qt.Vertical + return Qt.Orientation.Horizontal | Qt.Orientation.Vertical def hasHeightForWidth(self) -> bool: return False @@ -237,7 +238,7 @@ class Window(QWidget): @staticmethod def create_label(text: str): label = QLabel(text) - label.setFrameStyle(QFrame.Box | QFrame.Raised) + label.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Raised) return label diff --git a/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py b/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py index c9dfcc73..bff66727 100644 --- a/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py +++ b/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/layouts/dynamiclayouts example from Qt v5.x""" @@ -25,7 +26,7 @@ class Dialog(QDialog): main_layout.addWidget(self._rotable_group_box, 0, 0) main_layout.addWidget(self._options_group_box, 1, 0) main_layout.addWidget(self._button_box, 2, 0) - main_layout.setSizeConstraint(QLayout.SetMinimumSize) + main_layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) self._main_layout = main_layout self.setLayout(self._main_layout) @@ -63,7 +64,7 @@ class Dialog(QDialog): self._button_box.setOrientation(orientation) new_size_hint = self._button_box.sizeHint() + QSize(spacing, spacing) - if orientation == Qt.Horizontal: + if orientation == Qt.Orientation.Horizontal: self._main_layout.addWidget(self._button_box, 2, 0) self.resize(self.size() + QSize(-old_size_hint.width(), new_size_hint.height())) else: @@ -100,8 +101,8 @@ class Dialog(QDialog): buttons_orientation_label = QLabel("Orientation of buttons:") buttons_orientation_combo_box = QComboBox() - buttons_orientation_combo_box.addItem("Horizontal", Qt.Horizontal) - buttons_orientation_combo_box.addItem("Vertical", Qt.Vertical) + buttons_orientation_combo_box.addItem("Horizontal", Qt.Orientation.Horizontal) + buttons_orientation_combo_box.addItem("Vertical", Qt.Orientation.Vertical) buttons_orientation_combo_box.currentIndexChanged[int].connect( self.buttons_orientation_changed) @@ -116,10 +117,10 @@ class Dialog(QDialog): def create_button_box(self): self._button_box = QDialogButtonBox() - close_button = self._button_box.addButton(QDialogButtonBox.Close) - help_button = self._button_box.addButton(QDialogButtonBox.Help) + close_button = self._button_box.addButton(QDialogButtonBox.StandardButton.Close) + help_button = self._button_box.addButton(QDialogButtonBox.StandardButton.Help) rotate_widgets_button = self._button_box.addButton( - "Rotate &Widgets", QDialogButtonBox.ActionRole) + "Rotate &Widgets", QDialogButtonBox.ButtonRole.ActionRole) rotate_widgets_button.clicked.connect(self.rotate_widgets) close_button.clicked.connect(self.close) diff --git a/examples/widgets/layouts/flowlayout/flowlayout.py b/examples/widgets/layouts/flowlayout/flowlayout.py index 3ab4318f..856d6420 100644 --- a/examples/widgets/layouts/flowlayout/flowlayout.py +++ b/examples/widgets/layouts/flowlayout/flowlayout.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x""" @@ -90,10 +91,12 @@ class FlowLayout(QLayout): for item in self._item_list: style = item.widget().style() layout_spacing_x = style.layoutSpacing( - QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal + QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, + Qt.Orientation.Horizontal ) layout_spacing_y = style.layoutSpacing( - QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical + QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, + Qt.Orientation.Vertical ) space_x = spacing + layout_spacing_x space_y = spacing + layout_spacing_y diff --git a/examples/widgets/linguist/doc/linguist.rst b/examples/widgets/linguist/doc/linguist.rst index 24a49a61..169688a4 100644 --- a/examples/widgets/linguist/doc/linguist.rst +++ b/examples/widgets/linguist/doc/linguist.rst @@ -1,5 +1,3 @@ -.. _qt-linguist-example: - Qt Linguist Example =================== diff --git a/examples/widgets/linguist/main.py b/examples/widgets/linguist/main.py index e4212e80..0feb0063 100644 --- a/examples/widgets/linguist/main.py +++ b/examples/widgets/linguist/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -24,7 +25,7 @@ class Window(QMainWindow): about_qt_action.triggered.connect(qApp.aboutQt) # noqa: F821 self._list_widget = QListWidget() - self._list_widget.setSelectionMode(QAbstractItemView.MultiSelection) + self._list_widget.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) self._list_widget.selectionModel().selectionChanged.connect(self.selection_changed) self._list_widget.addItem("C++") self._list_widget.addItem("Java") @@ -41,7 +42,7 @@ class Window(QMainWindow): if __name__ == '__main__': app = QApplication(sys.argv) - path = QLibraryInfo.path(QLibraryInfo.TranslationsPath) + path = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath) translator = QTranslator(app) if translator.load(QLocale.system(), 'qtbase', '_', path): app.installTranslator(translator) diff --git a/examples/widgets/mainwindows/application/application.py b/examples/widgets/mainwindows/application/application.py index f69eade2..c3ac90a4 100644 --- a/examples/widgets/mainwindows/application/application.py +++ b/examples/widgets/mainwindows/application/application.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from argparse import ArgumentParser, RawTextHelpFormatter import sys @@ -83,21 +84,22 @@ class MainWindow(QMainWindow): def create_actions(self): icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew, QIcon(':/images/new.png')) - self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.New, + self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.StandardKey.New, statusTip="Create a new file", triggered=self.new_file) icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen, QIcon(':/images/open.png')) self._open_act = QAction(icon, "&Open...", self, - shortcut=QKeySequence.Open, statusTip="Open an existing file", + shortcut=QKeySequence.StandardKey.Open, + statusTip="Open an existing file", triggered=self.open) icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave, QIcon(':/images/save.png')) self._save_act = QAction(icon, "&Save", self, - shortcut=QKeySequence.Save, + shortcut=QKeySequence.StandardKey.Save, statusTip="Save the document to disk", triggered=self.save) self._save_as_act = QAction("Save &As...", self, - shortcut=QKeySequence.SaveAs, + shortcut=QKeySequence.StandardKey.SaveAs, statusTip="Save the document under a new name", triggered=self.save_as) @@ -106,19 +108,19 @@ class MainWindow(QMainWindow): statusTip="Exit the application", triggered=self.close) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(':/images/cut.png')) - self._cut_act = QAction(icon, "Cu&t", self, shortcut=QKeySequence.Cut, + self._cut_act = QAction(icon, "Cu&t", self, shortcut=QKeySequence.StandardKey.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self._text_edit.cut) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(':/images/copy.png')) self._copy_act = QAction(icon, "&Copy", - self, shortcut=QKeySequence.Copy, + self, shortcut=QKeySequence.StandardKey.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self._text_edit.copy) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(':/images/paste.png')) self._paste_act = QAction(icon, "&Paste", - self, shortcut=QKeySequence.Paste, + self, shortcut=QKeySequence.StandardKey.Paste, statusTip="Paste the clipboard's contents into the current " "selection", triggered=self._text_edit.paste) @@ -186,22 +188,24 @@ class MainWindow(QMainWindow): ret = QMessageBox.warning(self, "Application", "The document has been modified.\nDo you want to save " "your changes?", - QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) - if ret == QMessageBox.Save: + QMessageBox.StandardButton.Save + | QMessageBox.StandardButton.Discard + | QMessageBox.StandardButton.Cancel) + if ret == QMessageBox.StandardButton.Save: return self.save() - elif ret == QMessageBox.Cancel: + elif ret == QMessageBox.StandardButton.Cancel: return False return True def load_file(self, fileName): file = QFile(fileName) - if not file.open(QFile.ReadOnly | QFile.Text): + if not file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): reason = file.errorString() QMessageBox.warning(self, "Application", f"Cannot read file {fileName}:\n{reason}.") return inf = QTextStream(file) - with QApplication.setOverrideCursor(Qt.WaitCursor): + with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor): self._text_edit.setPlainText(inf.readAll()) self.set_current_file(fileName) @@ -209,9 +213,9 @@ class MainWindow(QMainWindow): def save_file(self, fileName): error = None - with QApplication.setOverrideCursor(Qt.WaitCursor): + with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor): file = QSaveFile(fileName) - if file.open(QFile.WriteOnly | QFile.Text): + if file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text): outf = QTextStream(file) outf << self._text_edit.toPlainText() if not file.commit(): diff --git a/examples/widgets/mainwindows/application/doc/application.png b/examples/widgets/mainwindows/application/doc/application.png new file mode 100644 index 0000000000000000000000000000000000000000..b5fd4597dc7235b4550a8a17dcffe0ff2409c740 GIT binary patch literal 39984 zcmb@tbx>SE(>F>I5|&_#1$PJpg1d*sZ3*rWAh@%*CS-AUhv30|3GVLh?(S}P^E~fY zb*sMj{&%;w_RQ&-Ju}m%r@NVvb};fCMMgj%N01g1QE{C= zOmq3B(skXlv)sVgkh1J?()3Dtm+^wI~sw%s-{2Ha0fXW*nafKwsa&>mQ~2#ej(7 zilEHV#l^+tWj)F$M}g2)9(0OMdmU+MX=CHuh=_=q8qSOnB>sOK@ZsW+pP8A#!@=R< z<8ya)jijEVS0wJSCr*foF|x4O-`>s}Gpp8O9H{)p_OJgD|17$nU&wLZ+b-3MPdI-4 z8VSw-#2beM2fGTSXJ=Gc)^=OZkBmH8uBESATz{`;r8& z|ItDkTA{;qb9q@@SU4Vp8I_U2j1@$Ug-nMHuRXo#R*=b6DpB)*k4Ro#Ua0^l7S4Ty z+l39O?BKaev>j0EZ zT`Yx)qPe+e)VK0gRX<6F2G^Lq^h(U;Q3@~wf&GEB%zY(Y019zM8(j@w!7_+B7f6m_ zYY2@qSCccB+o3}e6|Lsz6!*nys)p$5xPl7@1h5}+Q_0Eq6uKdL16%}=b_7NEv zhAqWF9+H*ymDFV)yO*W^X+&pxdscnk_U_q6kZ1rUG+J4Sh>&n_XwY2?9rZQBmm^{1A?!u|uLeXY z@4xqUvJ14Y;EWm9#aTQRt;Yt5y0!oqpjt9A$qO0Ov999vr*nmt-1`KboZC80q9{69 zjnv<~R#!U9DSpA_gor35oK!g7Qy@1pQ-nSaorvvk{?-@x%WR{QnHcG(E72;A=42 zm94EWp$eO-$2T!RYKN#yKifH*OrxjGikZmt*cv!lS)D&UuNKZ~q!P)%E-HTvSs9_} z*;yzsT8bJzZ>d}*xSX*NE-LBGkMm(2zTWT%a7$7QoJN)oXd+WG{6vlkN=|N6;$%C! zsm!8lA6lTK%KliZa*S(M*y0%80XVzG`T5n-Q#^9JnN^RsCzy$(sI9n{w!0^^af}8! zq0Ef%SoK>fxW}@oAd4<~T`+L;2hS@7mhs4n4ayu&yc=SMuaQA1&$s617 zh5^$Osy~$-4Rve3WqXnGVt}MJ7Al2BhBbZi146i69Z*qfnCg{AFrC*Zu3u*r99YvH z^1gbpQn9!i+cc6N8mjloGf6r$JFmbD>W~-9B8_|V3&C|9b5`=wel0ui0PkG;_)I)T z!;LmSTHKQeLxI_r`QMjWk`=`G_AYFxnTKGaazTCgdmnIxaW;3Lkj#94qRhw2s_faT zDVOE(hYwkgvXWCj2c`XOVCUMliOCS#Tt%!(U@F4z03c;;BA_9-I=N_%#^z0=$^yJ&}3MULaeN z-p>u5HRHT$c#qYw3Dt{J%wbhIxtNORZO0_-B$=Y+3DVNbN2z?Z)5!QV5;@lpq3p9PrP7DG2uewD^B1|5N=R%KuXRJKz)u>Io-#Bl_>}5pUGg z)JoQ4dA=hg{!@Fd#8Lx)!@p@_{41lr`oA9Q%GMRcq{rC{n>p7x(qHzRtH@7#+RjjX z98&8H^N?(~|16{ID+(X%qw2aCp6ANS_;kgFaE7?KOuge$&gpC|bKgs-bDzSHgVUjo zc}v{e>NPH*tnStNwCu&#D-C(?j^O{jeOM)MBi_0{ysuH#)xw~KaxGKJ9O1@{&dkNX znC|6~uZ0|CbO$axq&A&T zApA7jElW6uR6%C0PwvJ^b*~1bdcqfUokd3U4BZ`^tdzZzb_Oh!hl!U@-M=gc6DWA) zO18s!iy(^MP%OFQMYwTkzMwB8%bde>J?)co_;_R2IVy3(lblt zX!GAKuyLM;WaZeXQLkG>$3H10++MV~NGwDG?&doi05Hd9@`~KJx*s`E6E&`B4A@R* z74gl`N!cCBar1=d`jxrx^2Yv0^g}JES4PDzsyB#qtbXKb?o%H2vn|4Y6_+v`TIkq*dL!BdVTxz>N3VzR-k@PIG@2 zkehsdXY-kh=ETd%AJZLe&-%{V-2_c=G8F?I3C?S}E@=f2;myC3UmeLJ)xwN4kFBrYyeF zCm9)xR`vhpUN1qkO+>%eZNl(Yx4=O>uS zkuQO5@xTN*)NWvCDMN6A+hX)E;~4e!`gGfTxV#YlmPUW|aq;r)~i_w9)@*j{VO1Ut?0* z|EkOX>fj#_uRQ)eyw&Iv7^86~XUrl*Mz-+40^eU8U=)f8!Z#9%pyi56l*!%RHVKDa zQ%l44$j&g>)iG;;Kd>mWLO_YyVnMoB}-%D|ii6H_)iEgnfTK9fMAXi7y*HOKVu z>V`Zwi%r5U&U8G4mUJkpG$NCNrMu4W?kWuJ=)|h|={(W9Y9!Y7@ws!o;yUBhRUnkL zdv5nw0A0lnuTWiX=T@RqT-;&(foV=cVxEANpfncy4JR~D}udEy6y`jJt;jKoLpR2QmHERs&-Pn zO%Zj`@z;^-lb$;=dv_UAj5PqNP`h8AUz8!)xw*LFik}DK)#Il)C(|Cxb4pRfOq}(e z^eY@r&^oXX5ClEIcoXB;>l+$c3(xJNeOk#B0AB;MP}Tt6P}XgpP}UxM4pGRwB}W?N z){b4W0QLfn;etA5)?}ElOfe0 zGv9mWA&HUY^DBhWs-0LMUI0t`?J>$PY~le*C~x>giHkvw$4?-LI_uMr0l2v&C`S1; z86;%}6ZCQV0*6z0`h$=BIV_OGXEO=EMjkYw1yz*RbMN8KHJKi69!pVg9$#+0c0PaG zeycblaZO^|h--0hKunzWolzdAYeP{WxM#yD{L`mkN~!2oo_HhN{tws{e=F ze{VEch^pZWRV=845@9J9C(<8yVIY&Ve74^jgX_dfo#mBOnMEwmMX-!so&9#Rt zr)Z&(EiGiAK;y&!&v)QC6=OnH70sB7E|IO+PZJ-)&v!2P2%OgrLL6+jJ#m z0&Q4ABhO-X=If(x?uzlo^kSse+_=+p{FJO5<5M;ks|jy-N8-d=)x-@Bi>{hKGWQ z{50Y;F-imNuT#FPu=4WAfi2>mD~?VB4GCJsSI^$Ma%0}+^+u%c;*Bx85!Vkp_cLaS zSsgWg$3%XigxYzO!j&dDShp@VJ{?HbGdh2bccVqvE>-(GVE#FOL61VH{E1YELX3 zjXK1`&wp{}srxv2hTx&?!C)zyoCn|QiyOb~D90cNcN4)#g2akd3V|RK6O&iZ&u+Cg z;?RISCgGOfER+IrMN@33%If+jo4d2@_J$fuek+YhMUsrK-^@q2;q$vBT89 z?XqP47+>vdhP{%d>q;(#4y%wod6yr3NdmJHOEQ5jR1K=)$HKW z0}Nv-(9)bvuh$}2Ue~9qd+D%>cxF*$#LOJ}RWZ)tVA~p}iyfzLD3Nn%2sCK0m@VbI z!Q)F!;GaoH`V#7^r;X6KNfkco>nPC^2xg>qZ*hoht8#>ELO>si#ykBwFQ z{hQOAx!gZq2`e4$?*~z1O-)T9Bl_;|@BjG|NP5i6dc-%`+%`>f5N(hcum~~Kd}n}8 z@tp~m5R(e>dEkG<&I4SnHnG*!^ugLO3ag%f4hs>4Z{HhCW$osPjXn`rB8mt~Y|{uU z_ccluH>n`5o>evPLzzkv!ab)`JKeDQJW)z#Hf)L2nc zIKb5m2s`)3kA;8#QVTJGKgPhP6x{zF0zrADKU*c355wQu9@6opZlF`vFbgd8Vb*E1 zGBpk1+f0z9#-gP)%I1hSYA|h0NTTjVE;`oz5jXpNH5hwm#4wa`kdgA2sDbC}R%}vU zr*((W!yvjOAK zJn7&_!V+SQqFnxMYyG~_7EMm8Qz^I}l!fSwIEvO5OHK^JiX*T$GPrQHNMs&rHeS6I zJ70{ljpfo;;BCpnID0Q0iBVo`3Rf=_07@P@7KQ_NDXDU$g6fJ2Ok_k}ZX0(G4|dcJ z`&4+;uFNtt6!pa1$PpQnTH4>=KaynELC)^$?}mI++BC2%yIv}fS5#zb`S{hO;B1Wx z|7-Ye445C@Sm1rQ9fJEJ_`t9jcPi!Z}iWbBkkrWiNYRYA=WMW^!2?T`+bYoN0+kW ziDm<;dnCDjRT4(CI$z#Qne6q8yZ*FHLAKZF}M^ptbRQ$aU_ zKj=Cm7dk2FGpcZ1d;<@V+K+Osu_%bihV0P zRDOOzTW9#1n*vJh9R4Xk#QnxCXf=tbrKZ(hqsW*qc;bnY)_=pjaYrmVN2>(Ab0a)M za}U#u@#4?%k+8^3K^)Obu&1)3dD~G6T4u0YV#KXy+b`-~0_#~O4Q*|D)o?erTHjy4 z`uqEzo;*=Pf~~Br93U5uo=x(dSxB?_<`M3qzQ5|B^3-#Lc$J@(H8dtCXD4Y|7H{|G zZ&LZPrYtZ)geCW8N)87x_VqiMJfWEU-&TLXjJ{nUBcxl|?>ks}-1YwxOo<|M9|Jp> zu_hEK9$gZh6Z7Y~OYk*jZ?w25Z)wmfA+Y^*kfh0S^6auwr6dsu-_l$QFV^5QTu&b} zrMtgL=%TjY*o@l z-Gd7=bn)Ql*WH|)5Tu2>0VH^)rX>H^NE;)0o#hu zbd^KqSGN`J;@j`rDJ(fl`mDe$x&g9SBveSNgEq@+eoS7; zN+JC~PIf6O!>zlcBm0R&0m*S~b!6G#=`qXy{1!BMuYzmAps$WO-lVVVkXtWxZ@-Y>Ww9EkiM; zFDe|?|BH_g{Hm;+{k7LgAEV7)$4Gg?UtT*8oNi@cVDsH5D?7VBKP)FFPeKC9%{}1Q zhTWB2TvFU=XnzwqaN8>#!DC(nporh_M_GWIp4H~TJsVc$$R16!NBa<#ZfRjNQOb)?70O#8v83UlU8GinyUe6m;SehT z98ctP?t(hg4NgU^-QDwjW2^`FP9^al0XU=|oHr!GbxQww?SNNqZ!bep>vTjEb7Hb% z(n@q~^z<^8n?kKnMSwU{S0R>+jEwNm89ZD!e=(q~wRw~Q+ex?)OT~xQ6UxvE;7I4? zrIRB^lS@aF14Xa^m;f}gC_of^YuR|+x_!K_tV5QTTHV6Jb7d#@Am_rZP{vr)_+)~g z-B7z*FAtz{fgb93;tqwSs+FkqFZg^8?l^k83bt3)Ud?&1brbohKn{akv*s~{Z)n7u z+r*o55Qa%8>*%OxsO)YW6`{<}KVP1mo{9lDxd+Vrw>8jj?9@A5JNA3)+$QGx?=2*{ z50lsj2M3#9)@Y%?fr>Fj%sMx>FnM{48jH575xc~ta{H>X2!-LHlqQcZp-^rX)L&BP z&39Yf`aC@qX{FdJVX1V%gsg^$;z~H;QPfj0WUH!F)6ZCWUI*osNT1`Ng{!Ub3TVgwbNRzNT(O5H-H#9Ju z*MrZVQtJG+0w^EQI zuEADf*8dL5()wXBNOJpxRK``S^6|JvB2U8+aP%eLvjnf7(FJF@{wZ-`6=|&`ML@W( zyjowcZyY5<8m*3rnYcPEE+4g{@^NNvS&eTBW9%sJ7T^eG{rHKV+F*Q-s)cnvO|YK+ z=dAl_r@BTwr$nlSg(YuzHeI2hLo~x?Ur)5n_V%Btl65h~*oKCNViD3~(!%n{ z$~w$8F3R7!XJSckW1f>+U0q*a-`^lPIpu(e(8$OL2zvGE)eKt|P-J#t4h50PB5Zs3 zXo2+D=DnX1C8qHAO~;i-2czI|vzn^y95bG`xrWiu(BLyfK|v7~))&$recG>A*4FOH{?j3e&RHuCKDL1mh>~snctjo( zaZYFtuJ5kNW@%S1EG-YGI3&()3DhtBC4O9H8oSJQ}`QK+S~)*8E*ua2L~OO zLp2daX8C7t=p5V;Sh}@J)N<7zr~`QK#XuAhJ(dw~;d^QePWrfHazN%+IvhZ6x=(~+ z!v67t8S5PH&O`HC=M#Q-yrIUc1Lt@eg7_1-t{8h(0}p)Jt}D5lfIzQLevaS)Zc9s< zFzm8eeaF11GZaqmFlMEwOu6~BwbQyiv9Gs`Tuy!~lf2OaN9Ra=AI~$Ppd@X^o)+m;e0-CkUuTe)|_M`+vh1KYR$wcm74+ z1mf2n&&G}a8(QP}f-I-iRp9gT{0$!1f}VXos*EiSZ*6S#RDW>`(iL8E>)che#CBtU z`}lv(0#tnqmJI&rQGg+QxIp<%?rgWa?0KU?;3C0zg{=J`>j$~(v`Tku&Mbf%HKSlpojf5|zqqEFK<#*;OkXhbM5SN_SE_?3GOsGUu znWreNXg0=k+{aSgB~qj1#{ib#D>- zl=1lH06NR)Jo;P1h4l=`bo0YcK!&a;()-tgpT^m<%>cp+;sNVcLwZ=Chk>lo<|D30 z_|d%s*siOvK%@zeq|~N~Kt<2BEj^>W<{CMg;g@JY5r56IZ(najI#?fOIa6XBWdJ%o zNiX?8M_**Nv&Nnt?7)2|sSjAvn71P2-q;h;%k$}@HY9wQ_D72lrNBU1aXRm}jv?b8 z5U5+f zzoNM$GfIM{GHPQO?)md>z19uv@bmDEHtoSzkBDUpgCpn$l^F$UTY$%}U zw60~l(9y3F4X-6-sb?ZpOsRGhNF;W-pc%oc_Wg4PEBT~mDA0VH^YX~cr5fL;{`NsC zWLNz|I5G=u_vw+2ie%khs!k3m^cXe>lLA5jl1YsrI;&E>lIEIgn zOuBUYis?gMpIJQye4bbQz0T4-?~A(@KX$Qw=@LUu?EoNo8jEtUP8m5hoQ`KL5i+r1 z<;)c~?LFT##x@cdl%}>n;PkL=me42Lv#$&HvkP9d4)K3CkBn))M!>xjFc9{+xz=Mb zcdHXsvWy@kPnYAby^NR)&FAzDALG(RvfE#y%UlV1}Xw1&FGD81GgQ!z)GI zachhjyLX11tFV7>_-$>*Nt$HFMAB5&T=$ekc!SD;;d&i5_tf~A4l1`8z$jl}tR<-; zdwrkq?q2gEvX_-^(C({zjCK~Fr}F0 zEvg$t!CqaMV^`o_M;G$28er6(+6j=>&HCt_t|e|1YD~MWr938#Ghr=i?*6vLQS zqJHT`ZO-cEV{KZ+wxul*SRiD1^5?x8)G-y)(1+3VtR+526bWt>ao_vBvR~i74#h$V zuqiW1iT;>E*}~<*2})&QEMs2e6EkCTkW_>p>d>w7SI%Q}lxS@2BYSK-=#3)L2bYU( zD@POZhRzkH88Hst`&0{890e4f9mrdkQDmAD?sh%+kgcU>S!5*xX5y?TbIk63#! z{s5xS>9iRZ_dW3?(*|q)J#%#SzOGv$>p`YV;~C?`yFTRe+_q42?@x9+?sa@@)tWf*#W1G2)fYk~xu;eOVBU$Si1;j%Q#uLUX= zj3KH7WVay#JH@;yKD5GDAtEMuK`H(Cp%s;@r`KMe5-Qo45AR%mAijO1)gCHH=^ts{ z5Wn+w%xuidgNd}6p@k^#!0hDSHc?7BDFrc%{*Z<1=)>1?ZS~`>a|? zbSHTcdAE$uPSt5MF`Y@YCw6HM?`-Fv=<>DV%nivF?ywXUEDNfTr`i3H$WSa8p4WzR_LM9SlGbQR`$lb`>DQv1EcHXtQV`7_vrY zBjn9>^+l;Pb>hP_kQPSL@aQ^xv>sAKJNG^-e#g8%IL3QtZ+qWrX^q#p*R$8>TCZ=U zq5SR&Aaw4=FaOhipwEq~QBOF5jc@LS%lG8&n9~WVg^kA>jbkM6EPSpo%uA|oRpBPJ z#h!t|c*vLhy<4+==G&?%eypy%4fII}zR@5jaq73dzpHWyyj5pkvU(lY$F`3rPLB;h zeb8(+i8&2-GZ-gs6@7j^T#6L0n5Xj|g0R(Fd_ti!E~0Nk>h7Pi<-fL?)*Bdw@)(4s zU_`J*fu-VWcx~WTPp@I#(jop%xjAET^xIKMnW*^M$_LmoEbfJHz(q4Q?{IP_j%dSe zd@N<2vSiwYVft#Fx5-(vU}s!qX8GzjD1OJXf_}r=MQ>AqkG5+tjgvLDV*n5L@7*j1 ziuXyzyiLibX?n%>US~meXZ#~jqz-<@#1L-P?9z80lJ*-6#?I~fpX-!M5_%RjNrn!# zixWR-6Lml|J#%ZqSLCvp{uFvq^f6bvV(5&iYKnYf@Q|pu zuSknJ1fQEk4ksBb#MF?XFm*yY$erdWH$BC=+kRq%TzF!;rAwpcbR@`3on-fS><5qe zg*xrXlEJsNV16S>*_2!4dQDmq(L#vVR+ zFb1%8_sf>?*3@b$#PEeocrhrM`W#Y?hP~a|@=G5!(hH1z=M|OTrD4BjOndg5QT6jN zHCRBGr}Sdg)cTJQ5xr)yjQ+aJ?dKtjn)3U5$>qk;IKNB5dv$M<<}}ydlc~?$^3pL! zUM53lQP)df+YA?7&3x;osy9uFhaM;+?GkF^KX-?gR zm6!U;P($_K46$1@R;dy_oTlNyhV+tC72ZVc7v$dA#Y2uj0dmW|EZcY&OpN z-pBE8Xd*0Z)1x&)kV`-V6jLRWI!@b^wn8F6L=oN*g>2)vO7PsboTc576?8$)g(b+S ztZ-@}$K<fwMAYTKktM?NJ)PA@2 zG`(N7!$4x_SxZULkmwM&P0z2ty+m_6n;A3I<%~B%r~l3=&%t)|a7HUC`vIx`GW}v} zZT*<{OLFA^uf<9JJD!?(Xr(;PY$JO=2rGfe_?!Y?9ud48Ya`pMy0Q}O4Xo3Q*I!h8#k;mB z{U~n!ChW+R&`=N9mH5m`&4YKPV^CG7FG*jjm5*VQ^lHj>N z2^NNdZ%liVDVk?eUz)yEna5VW{Gn;lH6KkWH4i0%glX>B3@GQ#DgWahi|byA&6{P7 z9g+QY$Q_RvDAlP*_m=5bKm&bcl?4HHSTgtY`TqUJ6tT9wqfz+5Wx+Fus=F+6Td>y^ z?=J?)CV5MgqByqNlfGOIxR_Tp!oDM^CO-#zTU@ICdv20OO4N{Xn2RYl?5sO(!2-&0 zh5J4;%yQka z?}pf%ceSd@nESf%FdF9gTfJ-sL&e!A`bWtu-zSS{z31K=wIiO?`WM|54-+Ya_%)X8 zdIhq-tao*ZtVJo6KzoYQw&@^{i|%@mO#Ed2Xv= ze~_2&s5tad=u}sD zP0pE#<;zk5MSu}De{UM4-7)VJh>aj#2TA?w9S=ksCf)ZKDeFsQ4JeEx6Z4LG|1>vK#U1nKNAQ9IcX(`6CB*OV z_dZIOb-QCc-^v*!cv@{N;=^ZCnbL405g=ms-0^kHi;duTjS6wxLwfv=+s5bdnT$W5 zg<)&;1EIt7-|Zd=EtY?Ux`>@a1)!SfRPAhQcTtbZmI~98=~|F^tMx@Y`RRq6>wZ6MTx(*f()Z;u@;d@M}b;ADE8eOiP@_Z@k!cOzcV_k(PmZ7g; z$*ow!TYf!?Pm%8{vP9fkOgW%TiZ(jlGCyaVma>koKi0D$SL~en6)E7Z+-CGmyTs=+ z@NWT}M$Qu%ChL)bq(2nQR$;ukN}f^|T`N zQ4xW<>xZ6Lkki@5KYMi?r|}mVnsg_lEJ}t~4a_&X?1YcE&UFxnDGBt`(=FDY4Mu6} zW>ow&eM!DaUP|DtFc_K69{k-W@^O7t`d0zF_O+FC^i4XE?HpIU>$D!?*9( zJGMtf>wL^L_;wpiLsgd;@HlNko_!-fAeHg<#*tNY_?H-|y%60_>z4m?JPU4qM1ppg z#KIX66y005F*xCU=e1D(Ie|d=5h@X;FFNEAc{a?s_*aVxInWVE4jtUk!)Fkxa-S)k z+WAa7f_{|VIB=7CzYXllQ;$z8-T{K7fT!J2!s(xUAib%=f6h)qUGnNj01B_&^J3x| zh$8yi6NH2X`Wvp4r%eo6axb_@KY82{1ciw~oOeo%)XplJ(uRs}opzi_cqk4@h>mDz z_`zKw*M<79Lj?#5t|lT>g8(c4_Z^BynX6Gd{>96~cV{x%ZLm$;QLEzG2NId~p#<;8 z<4~SPmsq4Imt8|NV`JJ}Acg%EXOR%>$u%#DVMJ)MhjlXZZYP5~)tRufq4`NT!*qh{ zrXIVB6n6PG;V*Yji(%zs9gp^14T(8gDh(*m7H_k=M8Agb)2aDEmT5tZ4k3{M!*&H{8qpRC(A4(V$>>-P&VHtfOsKjrf` zb&8vh9aAo+cQH(c3GS3O@J2Asa|+L8{JbQ3=Ek_E8DD-OAL1S{e-idA3}ts=IpA2p z{P1Q)cXiTbZ4zU!MH@)fyKp}p(xZcyk5OBnKX_5>*@Wmu{n6SG`BWkIEd;B)g?_H7^6GHmb zd_;HEu{kp-vhCAN+y?1KHzEFY6>bd*`yU98=USk~i-&rQ?92Mbtm|L@;!xnQGK#Ps zchsm{-g{3Yf&xoqo^7ndtsPJ7B(J8jlZHUHmWn%cgONloVL*3g_cre9iztPQW)B~w zoVYBy+A)t!z)EQZ>7EOzCf+A|!kgtymmWd>gAg=*Ih5aJ#ok*>hqGlam*Y!LQj|O4 zwYv{(lF)|-%X+hzwKK~2$Uz(k&!}V#rn-u zuv^2)@~H|l9ubR2Y~iOGyU+k$~&_2T|KDdgkr`70L(rL{0o?Mm}{ zBtNO13L;x>N2xnwq6r^wuH^||F@cmH&nR3t7FRcl+zAJ_ zr8tyw#w|vA;7bL5)o~q5$E&SNo;XVISs%c7fCX}`a`|IXqN?PuUqwmh0j}=0!jDJe zl`o1h-#_Y?!erwm6QF>pDefg##Uh)7@rgnu_}m~k6yMk9ZHIkb8+xZz9N_UbRucxU zYuYH+!&kc9?CK2+ia&ar540tV_qX@&-aH<4B7&fGD}OLB)JJu-OIN}gKEhwnUSZ5Y zeouW;R3M8>1pf1UDZgW_m0;6wwk>{P@O2}nz$o|!fIUJ8;w^QM_^XiTCHhT$%7>LZ z%(uTbX1-h;3opa>?`KL8nAwy^etYF4MXSVH%>YiqNO$$UplJud{sLRi8Hj+K`$ZLl!2R@ z-^0~9MylUL*kI>*OZo(;KPtpOjvK#3GCh8z|2EZWIroC>FIoRHop(f-2zHsT=glCQ zZ|Z|J&OWmf>lBvblpuArGBnoV6X~f4$A~ce;pd@SGmw=lU5?NqcvBwTJ(VSbt&1~M zirN%R;E>B>(x@N*%<^e6_nagG>^}&MtH9`{_1#tcN@LLSZ+XGj*Z&e%e)RZV3(~@K zc>e#&Yx!T&UjD!GWd0{B=6|c+|1^XYfHIVy+y7#ZrfyR9a5Cewm`T>)UogI6x7G8k zG)xKoD%+34J{BlCbi*$l(=-`Cc5RLMCi`%VpoPkDE^}*O+L+;mMl_mH8@t2BLa3TG0@D~nvUcDd!5i!qvX#77C zm%88JaMu=iSp1co>5NxqzT-!Ks0);MaVTf=SgPz%O8wD;S^sz%YAxGssquZtQULJR+j`uTe2t5D-*2XuHC1$jUZ$ zsWBzk*~f8Rf<{DYjqUe5Qa1PA2npebaFkE009E-}MERIFx|5ox(0NE;IF}{LdTUfB;G0LBr$aWD4L8dDuqrdn}m8T!Zzv( z8Gp%HndHOBp}WCUWJ0)xwB(4ewhbZqg5!Cs`qLCZQKIQKx?KVmG{|T+F;^Ow1EgEa zZMSsdb>}>ianhFeE`DY9^qky>NXTFvhx4Mg;Xz*RXs;~)>YRlZ8M`M^@q~nKiX1vg zVCw;(d?`yb4;!pSU&r)*85-MvE?%5f*gSpY^<&L5l6hwCEA5GPwb$`xkp}tgSN!*9)_AGPyI@sa&CT|c zno?g;Ded~35;0+vXmUV-_DgN}43Snx9!d*xTxVHmqY?;5x>VVuwNK-a1(pqTY#VVFeu3 z#4~6~yTY$cO*9A}L~6dgvz27Z&CuZ=Ku%JSwQ0v6U~bwI*Fe>)KUg2qPRfTdo=KW0 zD{Haj+1lb$h=Ub;Vq!HTAr4o>ww)om>G^F|clXVp0K5?fmTnC%uL2BEuY*Y;s!{0F z%<=_}*wa@zh|`0FtZN%{nVkVqlLwj1-5 zP+7(NRN+vM2NF8TWnfj)(>!bs(OhunQEy=UY5K6pE537M+(Vo49@`dhPW^w*0$^~h z*{~QBVdo0GxD|z)F4w#5tp>Bki|zJ<2sh$GWE-PcOwQsj>hAVD63|)1oiC#Id5arw z3u9Q0XV~5u4Ne`A82bW)Z%YHa;Xo*VfUMPasu^p)qvP4lS|Hi=;kjVKdF6HU_AlTX z_rXHWV0()6iR9PE=L0xMRO2Vy8D!nVB%BUuxLx0uhWY;4awGf&?t;TK164^rYNbUY zNwCo*ya!9(f&T@xm<5JbR$Pu+S<;)Rf}P~$t+RtU&EEfNp3hb0`~CN}^3A#HS?z}U zg#+`^-S@w^SQsxyTw$!!%hGcdw0Dm+2j-H^1h!#^8Nv)b8gXYZeyl5RucN4{d~}lf z`PM&WK8GR25zc>3U_fgr?D@C9Ro^P(%O1N)1%%gi)i;LCb;*oliyE1cDs2 zVYneZA30aNB%Q6Y5QB`iz3Odj$1~;0(AmU?Q6^$%e=Ho7#ahawolv8Dcv)(VrKJil zhXdz^q%BrY_2tGh-@p@a;fYFm>p7k0At6h5Z5bvX9=SC3AKME|z-eykseGdjhVgIX z%1h!8K7$1Yc=NS-V2^`UTq+r8aaKFY``6*oT0=Zc9f>829If<^b!Xvf3S{Q(iHt9d zdYjkXanl4Br4y(B4{vW7)Y#YVZ3c(N-QA&a4cfRvf|1v$YD1@KOmqLRt$96hMLx)o=b>Vo#XC35 zZI2YmmkT6s59`_ZXL-0Q`;0rV<2Z8gqEm7@DvU7O@bCT4L~`Z%MNL`dOZ#orCifv8pp82>OaAf{qAqL1GTyMj3_ojoi}gp>DzZ%=e7Mf z#)xVf?JS^fG0Rwkn2nN-4vx`e6ez3_0fj|~+(|( zK(ny_ZGSuQ-LY7UDXq1hC;hOELMI6GP_IFv>pXgZgDA^fEU@xx?tKim&QgW^7S~o% zO;+8xyldFI*)*GYM1l(Y&G)qy!OsAGOwbCm>^Wa}p)>hcs#IllzaS_sGeJd|@NT{1 zbgAMXb;hd~L-V8Z5OlSMnXpH0d*MnOQc^WY zx80%!n!IHSoXit0CAD0?CF2v_>Uo;reD+P9e)-{RBU5yrO6yN`Bp^oDprPZ$Ds#$3 zx7c<%Sn_2WE5dLnD6Q$k`rnivDlap55}{<>w?=YYjbKZW=>4XgX?QFi8Pg*5W{+@i zlP%lfkbKtW?$9KsOkw8(p@s`T?Xr1z1``DAs$!e>v@Oq9UDuLq_z)B{%@OcW=9>o zn1AOYof2YXIdSh57(n&%24u`%j)+V0DcV#B*Jv)!-;`Z|66f#pfZ|0&`7us|3PJT) zwIZSTE8KorM%&i;$o;gn=?poK`y^VVl{F@g?c_*nvrzwfqe-G3r^Ep*;kt&4c(}Mi zV2;{PmZenZizYskbbc{V3+|_%ArK>%mBCy*p z{Y=wOszA@v^ioDX{-$T@X*T3UUea1DbgiIdw!aS=dW9!Db6k6adJ+-Njv@Q0zrJY7 z&26Q0y5$^ZO1{?WF+}BO^lxgi3>GTmMLaBy8FcFL9&&rHRJSK6SY=sGd*+^4zf4El z$geuq^H`s%4%3Exu*{(~P(K{1>8{3`@>qC;E|)|h{gX=EAqYmIl6{v@paQXfNMXLE zxH)fW7a|5)!Fx}|KNBBa-e$pS7lOK4E{59Kuk$R~UV$k8L-fI_bF#pSgPH{s&U)VY zBsBgdAp6y0?jv!&F>FU7_RXk7_V-qzrY@BB2Tc|5a}9WXWR_P5YoInBFK}6~huNeI ziag#0Iz>=InRZZJD<1$j7r|=du2nGOQk}t@G?>z3V2JB7qJdr*q1FK41N~QxY8#_ z)UsTw(1VumnY8hGrL%Z^qyk5({qe$U;QOx(CfQo%=i{DR)6wM5Hn-0JCRnCEcW$Ki zLnc5!Z0b`e`j6bBR+}*Q$)uj!YT##+{Q;A`r+t>NK#0Oor{Mj4u}p+?6@vPcVgfoq zWp4iwK4OJX<_w<7kM*$oOSDGHJ=UXn^;+Zuu7)1-I~O;?pU&&yuP3TQ7FC zKUXVc3|g(f)d=n2JUU-9vQUUI&N?pcn{CQghY%8u_C;%=5-XEMa%$w@W+P7UAruUV z{m95RA>h*4Fu8SmZb9Ne`+Nh<0LRzpETzUXr>lVFC8v!hnR_SI!Wv>Opj0@pExOg~ zIBHit`@n2CXRU_0r6I)DSM-%PwK(J%JQcKir#I6YG2f0|`d9*2t+I&br(0 zy1}zvyc$US_?4dH#bKGgw*g_$Z8hEhewb>Chv2o2o9X&456;s<{EcTT64X))<5!M& zdGUK4P}A~_#F>o59N}h8eyfc#?s@Jfc^L_nlFwGt;az3m#GzCissdeDI9=W^J}J+; zbksmzHt|Y6{FJzq$FuGJb&r6&I04Gbx9h8kw}~U8@#G)2e|_ttB;(f#a9-qQ#zB|3 z6sie1fHGc#3+wIi>~Ce4EsywwoFunHEvr%-y+idvG4W!o#63Q40kgB{8zG@nf@8nD z{dH}g$C2toq5`TQn9sVGZ&V80Qn>?vG1oirlCCe_*>RHkIKO?dEzzuJRPEE}z7zn$s$L0d^CU}LE4>p!`ySzimpaDG?KZclm( zQT$Qqmrxg-&rjyEN1KS<>t53!!sg?C*VO|-8V7oNBJp)kT?JKxS>~Q81Un_pOsq}w zWs34t^S?|?LHs^x_Z=;8(kem5dYFnn#V{$z=H|8;EtMSZ+udJbtMKdW+fak@Yzozg zUEkL8*E9F%*A8J!WMJjwuNSs__#J>e_hMRSS<7CWB687?`rdECY(@9s1w*!Bu3@CK z@z|3GR(Q)^K0ARu<#1`53S>-Tu!0xGYfPGk+BFNdqbmdIgG@%q)0ud}*;wql88nPL z0<;7Pta={DM}7eeMqJb!J-#p8b9csB5&oXvJjV5Q9}IaZk0W)?AugUgCk3&R9_X0J z1>O#J$373CnRKv3o$_>qAiL>&PMLM+0CmB?)Cty?wHKeyV7dkZU>ECEc_`YAYzHgI z9B*9GA#!=tHDG%ffQB#tBPY@G;(f7e_@wnbh((I`C`~I^(#^Y@bv{=JcW@FKt*0r% z;$pfV-h=q{(-Ws=C6bU7MRCJ@vFk`m~6qkQ*IjIy9xPOI8_Iyiti!|?Sfc=3t(w7f-dtTGyFEjB93 zy!UkXew_mnf-;m}1=qRFuojAqyVYMwOJ_WQ((1)W{Y%;1rrt;pittp(t3SCl`w`#| zhi432c=_KZ?O^_`7{F22c0e1+o6++3FC;Bl-b@&6yk+RLp?`h3*Fp-qXvF?@-=pd( zIiT<0LG27)rX7`#%K%a{tIlHw4K7=BfU4dnn{na@07=<|-dl(zZnmsXw`sF|=;<@t zJmFzL&t7CFmsqkrX<$V~WfKlFqQ^q1Q_LreskNR7c&_=wTH@+@5*@{JS?G-zJegBs zruPf(2pS-E(IxZ7Q;U#T81_0i@|O=40-&Hl=kNj%Z-jj-du_Fp1l-oG%`>lFAORXc zLaX_FE?sdT0*jA^bp-yWbWc>cpwQtj?w z7oJe{6}qGhOZU_!sfJQwy;utqmbCgTJcsQ2C-8>+lbC<%@BKBrEXLPlYOaS065#>| zxyUscy!ET1b(wLPw6Z^d+_Lnx$c^g?+>fmA6L?d@DtMB zBcL$Ncia8cb@5Hmk8l#|j&rJ%bgySV7!BC6#iwUUN!!Ujd@jT`sbbBms;YjfJ3#Ja z_%|NIopkGHzfUVXbRbKmc?U4bOQgYFe?qsCfL~AQ%T_}Se-c6jJS3vO#tNYd0*)r( zK%MwdKo8fSAyzdzP_9t35a9mbY~^7gK@YimfuN(XiJ2(>@pKf<|H<;F{F`RjI#UI- zd7i|Ti~@J8vl_n^q57FP;lNOE$L;#?o;})LpZa)+!7d zby_fE4&jqD{S7${fmZZu4dts7iL|_+m4k&lpL)_6-pjS6cw$>i?j8Z2)Plg_Afpae@W@I z+K}yqN{1NI-5WaH8$&vQ7N0fJ1(B5G!tM(u6mJ(O7E$gQzveS@G-R zTpYAtk?DT@^Mz;cN_ge7?*#6wwmKH**J#0Yvz0GdA6)@CcxIY@c!eeoFzF&gB{N}? zx#Y>PCPTA4tN7#c9C=~gdgg*Uz+1RA3E4Hubv|hjAXm9@5JzZIGO1xyv0rZ$oWTeI z@TdkmwV?{JCNQWJhoUeUc2AMv{%5UtdgzDe^I!tLsnuQa-jv-^)$FvPXLfwRoz2gs$s2RNs#mH@U^N^}?;3#~32zop+{cJ2`&((b#g^E!-Qr6!*&v z5{s@aL}@5hK45beFfN?E@vuI*WZ5;(P3+F;IN<`AonEH+GZ@ydlJ#!){;HVq1Htu= zG&JeJCf+Ta&D`4-DbA9K=ADKaayUjr!@vQWlP05$t^`lcgpCCZOBOi^P@dl5_6V?~ zd#aRYclAq(v8vw}SsiN>6HlnumsREo3=0Ma@wDO>GzPgA5$eAeg2%_#R+5Ujt-hpS zg8y?Caq8+)tQ540b4DC58u{C*EgGW}&_esyTie|(+Xe!rlwXDMSuHb#L`|=SD+auQ z<>?6`QCrX&sl8u#Y+I70*%7cf;Dk}38NkaSkXleB zXubmJ3Az^4QFZa=p-(a38kdQJtH{ONKhn7oPHqTIpT(s72vdRa{R9iX8F)Ziv}Sv+ zKR!uyxgH*`XXQ-)bN8k|fz?4Dy0%SA&&FPmNG(Y*o2_kIXlz!zzA30k4CucYbwR>T^HH0(e} zM=!Wo_k`wQ%fw#2e~-!=f3Q=4hZQk+aAmNYkOb{_QuAhbQyy@Kul+I(NIF(ra5KZz zz_oM&>$e__3ypkha7<7E) zpSK*>7F;}4VlF`|;^dRjX@12jSG_Zfk56zz|Lbhe#&NVLBdi>;xxcP!B%^#CQ`QTb zyH!mbx){`~_}Y&_F?>KyWw05s$N73WLzr4#fh zPC*|%&F9*p)_#FohHCQ94&03R`?u?RzmM6TyA-P?{R}e_LF2ig)Pa_(5_LK5;wx9| zwz|vg)Xe5Vg@9o?<+F#qMi*Y7R0nF=!iO;9RHedNt^(^;+(wUKtwu#moRKk2z}^5; z_X$1In(b}nA*C%V)8r!gakad4aLxUKjVIcq!SS=i3e<|!68I0AfvSWA4Y0Oy7+}n^ zkl961!EjDy9@rk0=#saI#Cp+5-yeS!+G={Sq@*O(`^px#VZWN^6)N~rT|qs@p9i~@ zs?!Qc`RIb@Z48D;_OszE4UpyA3$Dd)LV=_MRnB4fp&>IpyBD6mN_Z%itAYXi~KV zGn-FlDdt(;#p(Bz?ZHQ6co#XhU|iqvh5%D(DV;6^b zCvuhY_n;rJD=T2Z1)Su4L(A87pgA?VWi3_@B;g)_xY_#LmR!_IURCn-4d82?p5a{M zpq$W;?~2VFfV*1o(C=P0lst196L;k)i@8fSj10Fqnq|@VzTW<;>fPOb9?X&+|w;%LUdd-4~wSofZO4 zg|l!cSH@G*%X4!E@AQ67oJ~EEmJ)FlmwS$0z2f919|NFPI<4GaM<_XTT(79e2dp#AQbnW1=SFmxokQ*dMdY4DVHFri}89|Emr?%?t3fkfST_Ho{r>+V~kS^@m}>Dq-# zfRUYajxncsTk5ExZ{>o9du=Nuy)AuO^<{w0(-q~?@_3aeHPNkQ-CUUp3<4X9&3p7H zZ)6>55}$kjMY7@O%SZR5>X++9_G?{ts!H)AYm9t2w#W#Pj|p3-%m8}QLKnx5`LG>` z8qbUQ-iR=SB6k1jeqIC`y?y;~b{}{cmpR$bq6Kk#-&3y+zCFh$$o|G!_jCEYnDjz= zs!SGySaXUBN@s7q>7J#7de@Nk@F-qjqI}}_(Z#Iswk=Ny10G3nk~=$9f`ld=T+JU& zw)N*35CYs`AB(#_3Ig-u>H9Ie+GSuFXcU_m;X`g7kuU1G zrMwOQ5-;Tu2g)l`O4U&-nyf~poS&t2jD{~VEO^N@V&*{n6qz>hzG$L(4W-YT?k64S zwp7@d>vt6nF#0BtGHq*Z5#(FDKr+>T&4erMg&jM+E#&8|`aK*Px2MKtm|g0<*a+9; z*o<0Qi~$s_pu~QUfB?|E_|#8q>2@S8#eNs&_60R;PV z?BsFo#^l7`L>~15jnkX%nzv) z1JN(2SFL@A;L?vCw#Z_0y@^^*T(v)rl--=AMZqS=RGSZBF7%x`No9T!V|BmD(K($H z&DO*Ohl;O z8>Wp%ZX)UQ=p zI@SKER4s=~q2ovTL6Ai6aS<;1WxF1Xf*V?Fd?rAb0QEh1P1#%wkzv$=o!?By=V0=# zO5khaA1Ku|UF_*L0w#}^PKr-5I;~|E`W++bK;+;W0X9DTRCgRNF=xgwb1vGe)rfez z>a*Mhu5o(+pmnKaMXfMpYMl>V>w?{^hE7Nc;DRCB!E3AxrVQkaN_vhRxT zgr~RCoz4$xm-48wyDQ5O)<*x%WB$4JMS1N$ADboS>-GGe@y29X|FIsqwS*G2%O=M)waL5u}2$cPG@V)#UB`|pv;5<1q zd(2)^MoeV~bqAK@>2maB%4ydZ?7S=#qJxw&B0<&93(yR8rGBm^4ivk$dP|b01QLCE z7w*MJrEZ*{X5NL5A=w`Gl%YHa3Fb$*X$#7s|AT}-&hVU!1IAL`et~Xt-4+tGF3kmHSQ;QyXTdEFbw{t2ZCP1^lmmbOHjN#Pl0YY#63tuyz z&$Ornp(c3Go|ARRS0vBja~jY8gBJ~Ah=#6@V1FSa34B{^_`6~Q9Zmr#;9%e{%0a<^ ztCO**AhIeGh5j~VH*#inC4{fgv{e00!x&g|4yCczFYSd)AS_;LsTEs`kOn2f8J9xMhU;!E+^}_ zmL+Ya`|TFFyzGZ(pl|i>WhV#NALYOR-QjNf*U=}bS+O0G*rP2*S-h3d)DA- z2PY@toIYYCAMiCdM)g3g2Ojo<@G1A#<5Jk@!_1Fx8n*YpmLNW~qX%|4*@-`myU2;x z(f9g_P4M-y{?*JeF_Q<}5B{d}Y^-_QJc&n?`bJ{>2kL*m3`^3yJ zuNj_ltQOBf(aBT(V^1u76%E(BzSnL#ItVx4$r}yRMTpIPT8msAJ3Z-m4u%+nY$6CC%$C~e<#!>am zL^F7KbxT5$870Rj$hTdhAl`6!+LjRbM)D_Pt~+Qkk6x(T$I*eVgWzs0DUX>2m=xrrkPkCTkOg$_MA|WQ?X5GTr=CuexER|&Wx;hyH)AI!(My4Hm>CbNT(7y@*4C*=V%Sckyc0^z8F! zQ<0cDU6p_Z^xzw%JhFW!`Lfrp+V@eQe7%ZpiKIJfzzY>3HtsdK2bY?8SWCtTSif1; z8(y|#W6lfsJ-ljcZ=4(K=}J+4JcOc&&`+}&2O^O^_q)G7s>G!+B8v=oEzC^e z>AizESFRJkQ`Vf+;cJ@6J2>7a$p^<$?3vwncegr-5|dNG^%u;a6@7eEfPE=Yy5#T~ zJnJnQQ$fftdDQXfoOEpejeBh!-VSO#W94OU+WXCsgEq3lO2Fy8p9dDuO=X^vlU0Nv zuEdPj@FE`8lBR9H6iVl~*$>@A%gygvqQeA4=*L;Dx}83(2Lr9~B&RL(q>jB&I!J{6 zO!<`UjrysM5Ja?098R+fySR70vR_u)P?t3vUU^*u;UDPq z-eP7j2gw^7?CVNF0g8zXKC499z37lxzrxpcphTiDLvd8+OnL{0U0r4EW)1Wqp~gq4 z9kklMunefrxvM+CwkE|?hlo+}jZ4Syv*=)tM0Px*V)IY8)7mG0`IGKN!^c8SzJ1U- zZr%>ijL*02!X&D1E?!poM+`N3kT_ssIl1N6)7gU2Eq0_I5bAbc z*#Q$ix2_?95Qp*`)g`l zPAWzaCUMFEra?FZ;uZ+2SnrE^W@!z@tK|<;OOvcvp`dug*-KFIEI@=Z6 zrSPGQ>jy)+LmB6iEfodN{TLT%&PzKta{wMuYeea>8R(sZ{;+;S3Tw6nn0tb&{NM0W zVfLG(<71O*#jzz zl;_zVA0a1yeycKi*(6$NQp%7wS}I^sz&>j3YxC-Tosn=@3ZBnkjlZzl_((->xcI6U zu^ODv+>fA~fvY)sYA5Sgus45kx!xxdjWcsT;h7!Ej8ezCbP92-7}r?y5M9ka`m9k~ zqx`?%)U7#T4To6<%cz%`fJB{sR>EA(X_}d@62%cyWEC1DMfcZ%U%#7qblg-V&i}=t z^p*RpR2_2uHuJ$6D9`dCKIin6+rysbdli}ttz#4A_Ii4bWyv}qKlfZ_KL}*x?@Hmm zA!EbL;*j^z1@?<>ocAV9iP*`EkvZ$PY)gr}f|ZLLE&dzR82p)%(4Uv^kYN!QgSUzCR$us{`zIb6S?v-I^Tk!D#3H8$B zNEV~RP&)2}1HlCU`qR60GS#ZJ*pGat2dxU#F@YXIVZx}gC3-k_`>$r)>kDGBBkoBIYF+cgU<{VFf0lb91+03#dDPwS( zeM=fGO@LV1z%MRgfA;CLGnPrum$pZ)91shLzV)c4{Cyrt)UhM1=l{EV$`e(Gg8CW? zb-nIL$5EU+<0l<`8q?IC;|igIW}-d(GEa$dYnmGIXI9+cS6IN`CF5e%QY0Kx5PS1J`?RJk1o(jQPUkFp{kgf~=T4e>kaNkhH zttB;5&2eE-iNKyPVb0R(^SX+;7|)RTLC=3ofgPJb<(DVT{0n`vB$x7cH*2-owW4LX zGSPp!o?FyX=B!=)78(d^bCg><({sLwqcmD)Xi&v)8XB|*8X|lkE`sGgUqg2j20ZcnP54C`fRaoOv!XAexWR-TxTnLv*yt2%=)D4dT(=IruKQ7zez8{^#%)FirB(CevHqnzuL|j-mc{}zD zkF0+F_F%ag2_?BkNBGEXM)3k(dd(fZ-n#uY6BgfSl6=?ifU z(&sl4C$reZuc?wg1dY#Tlkw=?n;uMj7w9)YFX7Ag(rWlM8oE~2A7FsC`#U&`X@w2i zN*O`N3^qF#J$n%je^E-Xk>TH>=gz|f0KmxMHp-5k66u2eP@(!I`UA2}7xQ4>UNOXU zM)y?&6TV6XGXW7~Y72e4(H;}r=;)SU%k&P#eVD)Rj|d+W9=W9XM&^x~$S2OhjnKcg zSvIK(Drqje`_$AlU*$KA#a>q}AuS_IPhUNtyEt^cse9pYF5VctMXc{M9;h662lZQfjx$xLC zwU8vIpBkqsYSPlItVFYIqnzmV1VzM25LgRuvsG=sY45c0^!q)_T8`rg^7>%t$U`I< zTJWpSbB4;%D*th1IF`#q_){v{@0`Aui7PAp0|OFD(@?Wjxi1|Lc@*pPyg%Ei4ON)%2h9Om;aI3Bz3X48?1=JVRa_WLIM)r&}=lMc)u%cjowOL-SS~Z>d5b{?>t1LMSNf+fcy{eo^R6$+!3j{z?^GRXR z>w0()pPI}~&O&tcF zU(6we+@YDB{I{Gvm#|_p3(3J&sL#>`v%`PsavmAzM~}0K)De)N?F1WcZ)RIU(Gr}y zVV*oaTb9#~N8$%ckIO6OG^wfJ8MCiE@VQ+!{`t$Cd%SE}!$z!1cjE0}XibF)0YFiK zo;+Owbxj;9R0fI*#w?P{z@`V~wDdOj;o%vpWN}Z#fB9Cfql7rV~ukrVhrW|tjf~vAz^*r5QP(WvdNyUlt z6!c}N6F)~Q<^9YU=YQ|4*WY>59Wt@h_@23ucm4W$$$K`z$l^b?0;k(n@s#ch2#)W1U^*qt0W2p*izmPDPOC zarU(bM~Tu6@8CnWS7{s`h_0^`+G%CO2G!g(w#ARXcFH|P=4oLJ zD@%o(8o?5nqwSP@~8X(iErXxq`EWw7hN)gr>CuCVf zhphWC_oMIP8RBkK_0oRMr}On|hUFg49!I*9UH|fC_@r311!6}q?#yUi_+D8-%@xeN zK=i{-_3>VExcEzA$L(-Gzv;315RtA=r~f4-f-D>2+gV}FFw#4&lXrP_O>}B)Y6aV~ zlVrpGh7yK`97FwLm_;hKP!ejC#){zzqDry>=+vf z!eR$tyqx=7GkT0m@)EkU0{qG@*ms|9#R-IApE(qe>+L(X8O!5HvvZfJB3`AkEpUv0w9|623sfsq*i=--;sfaQ0U5) zFrP)eVtjXI$erlVP}7bgUkKmZQ30F2`b%Fh2u`~D9m z+Ek;ADx$I!fvyDUqx!aJ(53<~Mue&6ZmDgqxLrYhLsGJjK+;IK<1(#nFT|Cy4(~=C z@o_Xuh$&3R03Z;yqk>6+e3kW^Cw_E7XJa@(!dxR^9rqa_ws6PxwJA0baHLkQln%Q8 z(M-#n&7fbOm-H+7$>MO^8x;IS!1a_gF!p~F<=!|JQU4juy`=$$d8j(_sqTG{pCbpK zeQug73I0uMZr11}tfQ$6s)Ncf5&`hg?Mug2hC=+I#~Aw476T3nwdSKLCh=~>6R}S# zaAj-{#0OBqQ@%HlfG-B#lhew3&O^4_af1G9W)Y;mA53BCSt6Q)QUei*97OvCubJH4 ze=C9C;Z%vYDJi)~s}#LL?RAOqEAj;1oZ!mVU&8}JiC_7sHM|RRR)rIu;*|+aQ(A*w z&tzkwMkhW71766BeKedWWo&<|?VT!LAl#7zKAFv?H_mh@Or&@0welR*5?`y|9`jK* zhz#hpg=^zF@=2NQ@$)98p?qpA1F=QMJ#6p9(Un5GF&nN=^M+q3d(T0YY{D z-`-N5K%8>(KLok!e*gbul;0hMLDn23_&?Hl=1L=4r^ zU%|SAbaY=evPbyx=Tpy}B(d`~7uI~#4|4uU?Uw0qtGn@WHTIG>#{~3>j%DoE$d0bv z8eAdNL93@r6|n!pvs(h-*Mj2J#JM8F%mJDH)7xHCuDHaO@iEXJF!6K9aNRuDl7DCR z(z9ua7mLrXIFbjMWwpk=9eGYATJncNZsq1-h?#aCe!4M5tYR!> zR=o1X(HWkZn^R1Q_O^V??AVQPHD105^}H7l{8--8P0Z#Xrz6@Sew|l#u^t`znY4h) zBpsp5$k>$o4lArU@9_xgO%zZ0A^4-#S_L)UelsMj$OZ*%L#%I$CB013*3**~;P>ma zCTz~D+3ijbc)A|?)Y>We$%?7Z8eKk}^~!h)-DqnenVdlFo=Rgd()e*}ce=TV5_up; zU{xyhVGx-29{}1HzHvJL3;4_&S22=0ZA&FTpNPQoqb21JP!osK9H?mrF2i`1@Vc(c^H_oRk~&+DXIVBa za;AZgOH}=fnbLAl3N|vc8i}FOiAw@<;rtHZN*Za69>}D@IVB~L?DsE#`|S@1F3Rs7 z7I)P3%R3?Ts~k`?HyB@Jof%Y8Mj40#fAw10COLqCfbD;yx>2>edze%M93T*FHQL_c zRntm`NKSY3m<3`z|7`vEKLE|@eoJGc2@Ta|!7{fn^h7wE;1xp8@yk38@`c{q1g5GgM0CM(Ba|%N6=uz7g?_(Vt_;O<1P;4&GxeW&rz)wy= z4L{x)Td*v|Pub4n;(NQU15PjtNN>);9#L^M@(WnVG|B&(M2jP};xbkn2qR(o&*hHU zTx3g!)|nds`r?U=5|g+lm7;yHH_$fq@}^-~RA@}hhm%nYB2oLGuf^i@QW;%3G&sS( zu_dsmsE{4X;`I8I<1q>5HremTQe|oZ0@=)y5>SKc*X?G?LZ0ptZ&n-ykW_E{jaZk$ z&p8H#3~V*)FX<_rW1#^CsUJfXYGIp{wBl{CDAjVny~{i%g#lwHorA5a3--(9^hU)T zk|Rt|ANiYzY$a*sT|CWW`Udj=C_0uIQU@4A9n`DMEhHSvVxeK?@V3+0)5{jLtYXjV z6p&0-iiWUMz%B#;=wbrpk_^#P2Txjm^`6kb8%I%36PbQh=ouL+`jxE5fYDCT%h29# z-ctAlDxXICt%1KO0EQw2dFNBe$;nAYl-mkf*ShO^c$j7Nmo6bj-I~!MEg1zM-cdYT^ohs zu$7HJ1i;IG6yx{JLMJ}61xQAF-e{2;n^ep|DLs6E^)|<6Q|f40g)747((Fdigie=6 zXAM!@Bhp$M)M`}nb(}8#^iIy~$arEV+tEa<)}hZDuw+=a2J>rL3J2}QmS;ndZ3Cbo zI^4XapbdE^>w%?tfz*leeR#7U2!g*RZb1>2-@asYGTGv10oq37K*p#REuW8C2IcLf z=-nr!oHsu&-o|sKrI?<^-KiIIihu^2@f-@|ajD;CC@tY9G#m()43Y-EwN+e|H}B5X zu@dS0u#h8bhfc90t@wXLv3;PCz|G}7b@DQmH}x8^;DU}N@+pmkxh@mjkd$Ua&mc*p zVZM~BVGFxx6QH2t097!}u7t)Tae+N!a#5F}G>Y4FztE8L@;|7}ms1f4Pqi=8FXbn& z=@o2laK*!^98z#PPZw2I2gtIRbpJwCaJ-!*e~elHhiuPkt&C4(LK9nmX+V6TxFuXc z{E8JZIY{Z5Qh*ajV{lr&@j#K@KYJTf2Va*^1=aFB^g_KH%-F3O0Km_b5*1PrI2OLzp=@a{1TCUV3OX`A9-g+=*4Ee2(tl(;P^k;lW+tBk zCXV%}A$)aJCzmE_{pM%-I8*l#?O-N`=Emk=yR7^kSn~;J!&%PtMfi0@_ zZ{&^A_abRaJ5+K6y)Ec1Enf-rr~2kRl^?87S3L{GQ$DNt4~S2he?7wQ)w%jNQYC%U zSKg7Oj9<%94S2n@vCKljj8U#x3ovfDFVx35NXfD%qx+|B1}$+ezD|-5$Hr}z#4vE) zgeTHJg&vaJ_H^8O$atI|D)S-B;l@>a#tI4Ji(%a72eL%ncWt+gUr<`#j83u#i;O3M zEO{c#fhc_^mJlPXMweXQG(0;6rFuUN&_Vr0l2X<~5xQ}Dz2_yO;){?vbztxf-;7H{ zF6SEy2LwBhd%n^%t5N`^P*VR5?P-M^W0h-`(hKTu%p#S{1X%Gvz;VlR`1Bk@$Z!Yx_%RGcs#V2sXon9)$Qa=KsO9VBLJYB2o_RVU(9^Ve^4n^uvldp@{3uytgfDJB?U;M)sD-I(e)45KJ+^W4Qq zqS~s)MzWD|RH422oz${?CG_SSul%?~?tV{<7v^xI(m};rDec_D3{hrBq`7I3h*-Ke ziqF}8wJlRso08SH#MFX|0WF6BN$~%de zWU2r%9gPU!bbOpG^|%ma)QwTNCkY57zegud*6C4S1!Qtm4BNEE&xOJt_|?U4at_LW zr!>R~Hc*LRcwP@iMkG@fs(5QrKRX;p7y-SEqv&XAu|}EtVK!-^u=6mpx1vU$Wd zQbXPRo48st-|g#5KQ$b%aP$~)_1BMucHQ?vyqJs%bsUfKRCvqyDGHg_3A@alZeXQZq4np$KXSiBhN6+ZNU!B;&&IOHh33gG!Qd{JfBm!DW9~1n|>|gm@w4 z{PFZ1Q`HkQ$j4FCk^@t5x;<>1sV5YtXt71)X^>VAx-(Y$B-DF>f&`$qav|vBEcTxS zU#@AnCW)wKf{LL31d)3BAf_AI(;0tmjLR@Q69_1pXf6L@ShU78f-K7`lDx;6Is~l# zaa4$DlWK;ONuERb;0l|{1qU#L|CW16p1=3zpT@6PY4og(!+T$Vo)+WRqZ|gLiTCkr zW@z|Jod^<+(`0z>_f&T?I`DbB$UIGgHv4cv*5Hom4@pGY9AbhewB%}^zieh0=Cokb zwvoMSo!K>mU`>J~As(ir`P6>jUF0lG&~Bsfv>? zGHQ08pQFYd1?WU&-I?!y_|R*}nO9p}XIYHYFtCD!{~M#TNc>+wE%*l(36i;-52P8C z6tMGFkiH6dq45`f>fHEk?Nd8+nTh?JEhqMX%?ENh`Ga5b<%(wR8~8pl^U&(2-@bhI zVW=@r>KH@K@m6q1K6ts*xZ5@+pg0dLB~2}oW^}7(%4&8ut`<#9J~kiT8e?h^RNzYUkCRcC8mfh~`@rbbk^>p%b-T54nKlf&pvv+hKS3aGJ@#(+eYBx1_WBe&>sQR(< z@!|sn-iN;}^n$rqsmn7*rmQ(iZ^9j`+DTq@V>Vk_6UtIrv3pB~TjSKmLp#ATF(Xks zryWroj(s8uLVDuLP+1dc$M%4pe$gk@q|JUNqz2O4=ZhyF8plRMB9f)Gh6tG{p>@HPV`6{p3($v_9DFH&Zl9Y?n< zOYXqi^l!mje-xJ*Pl;&YHCs@{hQU-2Kx66@a@aAYc*C2n-uE~26MrPK~W zixb}U9m?TVR0`Y0&d2|ZiOR#2S)n;)I1fhMmGt33S=_S?QRPR> zHlgokzY#Nd-C@>6n7t(6%Jtn0{% zaRx$X>csCY?x7bK?3IHZfn$lvB|@dOS#+h*6c%YDr$8UmRlqv~ra5^x!j{ty7$ow1 zk?3VdZ2D=q@f^jK8o4hY2s5eaWYBhb!sr(UF^;7m5N2Ii0W_E#VUX$LARuQ{vuEyK z61i|N{x>0VWM-XhEYwI1ph|Fkv!)g^(rNUTSHjrY*1WPg3ofCUIhLcKR)`i|voe?I zF?@B601!_&Y`t?DtW=a&vYWpu-q-d*UU93*Mplsr+-srPf_>NDCwo#|cKI5&>w!V| z!Mmx6EPOqU!gcs=`Oug5SHQMQ-u#d=xH-8(lfcP96XcMX;uyr zVcRp+m~lp7U$a}I+XxA24;XcY-e&^pxIioSJ_9Sq?C!L-M5^PjMFQ*bC%4E{-E4|=60(HUqEffOICs)T1z8-lZ&wMk#s6xF!DWK zxGxi#VB#0}nV#SL1a?C~dvlQTxhuPzA?zOfXh}hlD7z8&2{rk@C@9DJ?i9iVPShc%ns zVLK&UI)oOFgQ|xpy_gphJV%BjFM(5PQS;66SJHmWd0R9(j3Z3s-=p6zMhv8}0}5Rc zxu8&8mM#5ECqJ`WEHg~(ovPHg|4wt2q*S^dOZB^4yi6)5R6g?aM#4sn`)K&}#f;o$ zwFWmq)1=Jxo0CBRvFu0d!t50zTy*8yF5$P@5#fsEcdN!1P0FPG|FtF{DV&}@NV)|6 zxboQLVuki&TWaD>K4$T@j2&p>C3XLN z?VB}G7d@nnAqorr9eI8Ee__adMfL(h$tsvF1#ZFp6UPa*cA*aGc_By>=%9B}i`#p# z5ymp1$lU3I5342+YOx_u6^kJ2yu`F#~+_nLeHrjy1O#)W>jV@^AoLW`%jIk z+%~$y)Ea&+Zol|Lp&U8e`)7^OriSSmvTP6kZU)>$8vxlBU*&7fZ|oocS_Gv z1u^{a%IJ*lSJa7%mQ20|DT&2ezI;P7@e!UH>N9 zB2Iuzb{<2GhNr`-a?vuHJ$a4XbuUY#UETU4 z-#SCDYlDCiRR@2JzkS3~&8lCUsU)eyYrP3eh?Pf%Qe@n4gcEj~Bf?g&p&T>R?Edrc z9`dfNS%(c8{f3NI{~o8)@%Yfmn{^18hykbFW4>Y1(bNzCpkN6*|Ix2`m7m1Uxp7D~ zdzdPbrc!8%){VrXc4SHg`p))h{x)_zkf9(iz@uX4E&B<>qKF~RyM@awZXO>pNZ#hyh#wIocl=)w?Az$-@sxJe-&7)uk=!ms0B{3c7TQtB z5*n>CLS{Q zJzS$nZDW%R7E@eL_{UCw(qp>QKgmZWsV}ncz@oF(8u^;@Fj#}p=8&^jEJcBD=7OY6 zMZc|QVuuTo(faZAju#@*{7@o*TIZZ z&6U-}3w=Q9XOS`IgxW3*=YoJ}?35@@g{(sTYa@mUMs80SDOVSe%f|Qk+kkfFHvwQ1 zbhl1za(k-NX+^UnX>9RRfEZ{flg80>9-yUHU-TxXFz@|U|@Af?{ok1Ymv=+#CkR_Y>fYntSPG(A|by)+KobJ^^@0|qxJJ-Oq-*ywqp zKzSs2t#CU^iMsEH8`3Q`j;o8ad}GaK%0W**5k4E7b8!SUex)E)z1jaqp`O;M!!oRVuIF{+S>G4O=(TCnwmWGH8H;FeYZ2tM<08-oKC0*U+p0g z%^QZ8AyCGvDpt14dOS*GS1DGcmTG5A|tQ%$euv83_%0C}j z2)($P&x+O@@# zWeI0SBj#i==XKJ-iJPODNT$_R z5%xDN(ZyK>-|p1){?*eSmUn$SCA|Ld)4r}2+Qx#g!@&^zldF4pGr`;;6NMD*xW}B$ zU!KtpzBrc6b>xCsP|37fNPj8!*B464jg4@gpc`zD!yZ=WaYa~^DCyl*E;_nuX*N2^ zN_R&tDLFQ`-SByN{qj$4TAYg6ElqfoakI=P6Jk7PoRmr@ghm6%cg7PEsKQL{ zt#PDf*!{J1fk)!YF{osnh7#Aarp@~r^ z%05WTyv6n;>+lh=n5yqIRRP&>zY$V$DtVESiHySlsu+QHbU#qGkPO+4MW$pI$AvvTG` zf-l9F;8wa5)i|qoE@Pw+i0jgHi~(Q#i~IOd>k!fM5a3dY?AbBokyUIGw7rV}sCVU- zSh%3i>DS+v-G}-3>rEYO7{8}%Md{DMvzzhgoW*m?+2Dza@sdm;b~Wo7sB$a4_4|*Tk|d<$6KHFI){oY6hDJ)g_${Pon?Cj( z=xic(dWagzxM+2b?KeNOTr$rl@DAf-ovGoEsniBoytZp3p+oNA;S{^pF!3!GrGS^YuVn^3> za05>TA3M(+e1l;PqVV)B=A5eK(6fxE3ov_MAN^+&bsEU?CCGQ$_^4#-y%do&4Pu## z#UGIqSlG9X=)$+REr!WU{hn0p@40@c0cr*49~Xt%VhSqCwo1qH;-b)}Iu-{7`JCA$ zXr!9^bnuQ+U4+M55SJ9sih|ebSWc1Wjt~Uv-*S>1i|$7~slk)|HvA(mxD6?!O%^}= z;pA=L**B*Mg*!LhJTG&IY~aoj%4N?Anh!T=I;>~7Yo=f$y;^M62ixcEr_mZ+jZX$J zaT{!IS6*#bUy~Xga+)59XG`+xa|=!XArUaxHAOG_7SzsbW9*LMF<|m8oHH(&e-akT zx|Wex9I2B}9#n%4b+Gt?V{CQS$qSKPGZM=JQ0tAQ(5TM=2I>-5g8r)}jfWrpF*)Ba|e zl^$uxPMwI;W=VW@m3#k}r^z?NipTD`YA^%819&sDL5I*Omv0N9*CoQB{JP4I{Y zi(LXl-}#p@wOl4axk+#-|4#Vw{(mFCOr`&G&VP;kD)*Nj<^U9=q(Xoi6tgE+e7r)WxyW)2vn8w1L6umDw5F4Kil<3jX&@7#{>G+ zmp>wh+1}WIDyX?4;SzZw;9nBXGO|sJKeKBCz$YDD?u&yuzot_? zo|sQNO2zP`2mMtKgu;Kt^x2d-)RG86Tu}3jyGWYm`_. + +.. image:: application.png + :width: 400 + :alt: application screenshot diff --git a/examples/widgets/mainwindows/dockwidgets/dockwidgets.py b/examples/widgets/mainwindows/dockwidgets/dockwidgets.py index 83487ee5..1f28812b 100644 --- a/examples/widgets/mainwindows/dockwidgets/dockwidgets.py +++ b/examples/widgets/mainwindows/dockwidgets/dockwidgets.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/mainwindows/dockwidgets example from Qt v5.x, originating from PyQt""" @@ -39,7 +40,7 @@ class MainWindow(QMainWindow): self._text_edit.clear() cursor = self._text_edit.textCursor() - cursor.movePosition(QTextCursor.Start) + cursor.movePosition(QTextCursor.MoveOperation.Start) top_frame = cursor.currentFrame() top_frame_format = top_frame.frameFormat() top_frame_format.setPadding(16) @@ -47,14 +48,14 @@ class MainWindow(QMainWindow): text_format = QTextCharFormat() bold_format = QTextCharFormat() - bold_format.setFontWeight(QFont.Bold) + bold_format.setFontWeight(QFont.Weight.Bold) italic_format = QTextCharFormat() italic_format.setFontItalic(True) table_format = QTextTableFormat() table_format.setBorder(1) table_format.setCellPadding(16) - table_format.setAlignment(Qt.AlignRight) + table_format.setAlignment(Qt.AlignmentFlag.AlignRight) cursor.insertTable(1, 1, table_format) cursor.insertText("The Firm", bold_format) cursor.insertBlock() @@ -84,7 +85,7 @@ class MainWindow(QMainWindow): printer = QPrinter() dlg = QPrintDialog(printer, self) - if dlg.exec() != QDialog.Accepted: + if dlg.exec() != QDialog.DialogCode.Accepted: return document.print_(printer) @@ -94,21 +95,21 @@ class MainWindow(QMainWindow): def save(self): dialog = QFileDialog(self, "Choose a file name") dialog.setMimeTypeFilters(['text/html']) - dialog.setAcceptMode(QFileDialog.AcceptSave) + dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) dialog.setDefaultSuffix('html') - if dialog.exec() != QDialog.Accepted: + if dialog.exec() != QDialog.DialogCode.Accepted: return filename = dialog.selectedFiles()[0] file = QFile(filename) - if not file.open(QFile.WriteOnly | QFile.Text): + if not file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text): reason = file.errorString() QMessageBox.warning(self, "Dock Widgets", f"Cannot write file {filename}:\n{reason}.") return out = QTextStream(file) - with QApplication.setOverrideCursor(Qt.WaitCursor): + with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor): out << self._text_edit.toHtml() self.statusBar().showMessage(f"Saved '{filename}'", 2000) @@ -144,8 +145,8 @@ class MainWindow(QMainWindow): if cursor.isNull(): return cursor.beginEditBlock() - cursor.movePosition(QTextCursor.PreviousBlock, - QTextCursor.MoveAnchor, 2) + cursor.movePosition(QTextCursor.MoveOperation.PreviousBlock, + QTextCursor.MoveMode.MoveAnchor, 2) cursor.insertBlock() cursor.insertText(paragraph) cursor.insertBlock() @@ -161,24 +162,24 @@ class MainWindow(QMainWindow): def create_actions(self): icon = QIcon.fromTheme('document-new', QIcon(':/images/new.png')) self._new_letter_act = QAction(icon, "&New Letter", - self, shortcut=QKeySequence.New, + self, shortcut=QKeySequence.StandardKey.New, statusTip="Create a new form letter", triggered=self.new_letter) icon = QIcon.fromTheme('document-save', QIcon(':/images/save.png')) self._save_act = QAction(icon, "&Save...", self, - shortcut=QKeySequence.Save, + shortcut=QKeySequence.StandardKey.Save, statusTip="Save the current form letter", triggered=self.save) icon = QIcon.fromTheme('document-print', QIcon(':/images/print.png')) self._print_act = QAction(icon, "&Print...", self, - shortcut=QKeySequence.Print, + shortcut=QKeySequence.StandardKey.Print, statusTip="Print the current form letter", triggered=self.print_) icon = QIcon.fromTheme('edit-undo', QIcon(':/images/undo.png')) self._undo_act = QAction(icon, "&Undo", self, - shortcut=QKeySequence.Undo, + shortcut=QKeySequence.StandardKey.Undo, statusTip="Undo the last editing action", triggered=self.undo) self._quit_act = QAction("&Quit", self, shortcut="Ctrl+Q", @@ -225,7 +226,8 @@ class MainWindow(QMainWindow): def create_dock_windows(self): dock = QDockWidget("Customers", self) - dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) + dock.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea + | Qt.DockWidgetArea.RightDockWidgetArea) self._customer_list = QListWidget(dock) self._customer_list.addItems(( "John Doe, Harmony Enterprises, 12 Lakeside, Ambleton", @@ -235,7 +237,7 @@ class MainWindow(QMainWindow): "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston", "Sally Hobart, Tiroli Tea, 67 Long River, Fedula")) dock.setWidget(self._customer_list) - self.addDockWidget(Qt.RightDockWidgetArea, dock) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock) self._view_menu.addAction(dock.toggleViewAction()) dock = QDockWidget("Paragraphs", self) @@ -259,7 +261,7 @@ class MainWindow(QMainWindow): "You made an overpayment (more than $5). Do you wish to buy more " "items, or should we return the excess to you?")) dock.setWidget(self._paragraphs_list) - self.addDockWidget(Qt.RightDockWidgetArea, dock) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock) self._view_menu.addAction(dock.toggleViewAction()) self._customer_list.currentTextChanged.connect(self.insert_customer) diff --git a/examples/widgets/mainwindows/mdi/doc/mdi.png b/examples/widgets/mainwindows/mdi/doc/mdi.png new file mode 100644 index 0000000000000000000000000000000000000000..240f9e222c49841de72ada45965e59ec22d38f3b GIT binary patch literal 33375 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�`X$Hp&=1=_uXOwl}V#>bFTQV%cdw5 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/mainwindows/mdi/doc/mdi.rst b/examples/widgets/mainwindows/mdi/doc/mdi.rst new file mode 100644 index 00000000..dbbd747d --- /dev/null +++ b/examples/widgets/mainwindows/mdi/doc/mdi.rst @@ -0,0 +1,9 @@ +MDI Example +=========== + +The MDI example shows how to implement a Multiple Document Interface using +Qt's `QMdiArea` class. + +.. image:: mdi.png + :width: 400 + :alt: mdi screenshot diff --git a/examples/widgets/mainwindows/mdi/mdi.py b/examples/widgets/mainwindows/mdi/mdi.py index c1fa01b8..341a7f4d 100644 --- a/examples/widgets/mainwindows/mdi/mdi.py +++ b/examples/widgets/mainwindows/mdi/mdi.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/mainwindows/mdi example from Qt v5.x, originating from PyQt""" @@ -23,7 +24,7 @@ class MdiChild(QTextEdit): def __init__(self): super().__init__() - self.setAttribute(Qt.WA_DeleteOnClose) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self._is_untitled = True def new_file(self): @@ -36,14 +37,14 @@ class MdiChild(QTextEdit): def load_file(self, fileName): file = QFile(fileName) - if not file.open(QFile.ReadOnly | QFile.Text): + if not file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): reason = file.errorString() message = f"Cannot read file {fileName}:\n{reason}." QMessageBox.warning(self, "MDI", message) return False instr = QTextStream(file) - with QApplication.setOverrideCursor(Qt.WaitCursor): + with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor): self.setPlainText(instr.readAll()) self.set_current_file(fileName) @@ -67,9 +68,9 @@ class MdiChild(QTextEdit): def save_file(self, fileName): error = None - with QApplication.setOverrideCursor(Qt.WaitCursor): + with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor): file = QSaveFile(fileName) - if file.open(QFile.WriteOnly | QFile.Text): + if file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text): outstr = QTextStream(file) outstr << self.toPlainText() if not file.commit(): @@ -132,8 +133,8 @@ class MainWindow(QMainWindow): super().__init__() self._mdi_area = QMdiArea() - self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.setCentralWidget(self._mdi_area) self._mdi_area.subWindowActivated.connect(self.update_menus) @@ -272,45 +273,43 @@ class MainWindow(QMainWindow): def create_actions(self): icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew) - self._new_act = QAction(icon, "&New", self, - shortcut=QKeySequence.New, statusTip="Create a new file", - triggered=self.new_file) + self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.StandardKey.New, + statusTip="Create a new file", triggered=self.new_file) icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen) - self._open_act = QAction(icon, "&Open...", self, - shortcut=QKeySequence.Open, statusTip="Open an existing file", - triggered=self.open) + self._open_act = QAction(icon, "&Open...", self, shortcut=QKeySequence.StandardKey.Open, + statusTip="Open an existing file", triggered=self.open) icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave) self._save_act = QAction(icon, "&Save", self, - shortcut=QKeySequence.Save, + shortcut=QKeySequence.StandardKey.Save, statusTip="Save the document to disk", triggered=self.save) self._save_as_act = QAction("Save &As...", self, - shortcut=QKeySequence.SaveAs, + shortcut=QKeySequence.StandardKey.SaveAs, statusTip="Save the document under a new name", triggered=self.save_as) icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit) - self._exit_act = QAction(icon, "E&xit", self, shortcut=QKeySequence.Quit, + self._exit_act = QAction(icon, "E&xit", self, shortcut=QKeySequence.StandardKey.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut) self._cut_act = QAction(icon, "Cu&t", self, - shortcut=QKeySequence.Cut, + shortcut=QKeySequence.StandardKey.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy) self._copy_act = QAction(icon, "&Copy", self, - shortcut=QKeySequence.Copy, + shortcut=QKeySequence.StandardKey.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste) self._paste_act = QAction(icon, "&Paste", self, - shortcut=QKeySequence.Paste, + shortcut=QKeySequence.StandardKey.Paste, statusTip="Paste the clipboard's contents into the current " "selection", triggered=self.paste) @@ -330,12 +329,12 @@ class MainWindow(QMainWindow): statusTip="Cascade the windows", triggered=self._mdi_area.cascadeSubWindows) - self._next_act = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, + self._next_act = QAction("Ne&xt", self, shortcut=QKeySequence.StandardKey.NextChild, statusTip="Move the focus to the next window", triggered=self._mdi_area.activateNextSubWindow) self._previous_act = QAction("Pre&vious", self, - shortcut=QKeySequence.PreviousChild, + shortcut=QKeySequence.StandardKey.PreviousChild, statusTip="Move the focus to the previous window", triggered=self._mdi_area.activatePreviousSubWindow) diff --git a/examples/widgets/painting/basicdrawing/basicdrawing.py b/examples/widgets/painting/basicdrawing/basicdrawing.py index 858a8cd9..ef4af5d8 100644 --- a/examples/widgets/painting/basicdrawing/basicdrawing.py +++ b/examples/widgets/painting/basicdrawing/basicdrawing.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/painting/basicdrawing example from Qt v5.x, originating from PyQt""" @@ -37,7 +38,7 @@ class RenderArea(QWidget): self.transformed = False self.pixmap.load(':/images/qt-logo.png') - self.setBackgroundRole(QPalette.Base) + self.setBackgroundRole(QPalette.ColorRole.Base) self.setAutoFillBackground(True) def minimumSizeHint(self): @@ -81,7 +82,7 @@ class RenderArea(QWidget): painter.setPen(self.pen) painter.setBrush(self.brush) if self.antialiased: - painter.setRenderHint(QPainter.Antialiasing) + painter.setRenderHint(QPainter.RenderHint.Antialiasing) for x in range(0, self.width(), 100): for y in range(0, self.height(), 100): @@ -104,7 +105,7 @@ class RenderArea(QWidget): elif self.shape == RenderArea.Rect: painter.drawRect(rect) elif self.shape == RenderArea.RoundedRect: - painter.drawRoundedRect(rect, 25, 25, Qt.RelativeSize) + painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize) elif self.shape == RenderArea.Ellipse: painter.drawEllipse(rect) elif self.shape == RenderArea.Arc: @@ -117,7 +118,7 @@ class RenderArea(QWidget): painter.drawPath(path) elif self.shape == RenderArea.Text: qv = qVersion() - painter.drawText(rect, Qt.AlignCenter, + painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, f"PySide 6\nQt {qv}") elif self.shape == RenderArea.Pixmap: painter.drawPixmap(10, 10, self.pixmap) @@ -125,11 +126,11 @@ class RenderArea(QWidget): painter.restore() painter.setPen(self.palette().dark().color()) - painter.setBrush(Qt.NoBrush) + painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawRect(QRect(0, 0, self.width() - 1, self.height() - 1)) -id_role = Qt.UserRole +id_role = Qt.ItemDataRole.UserRole class Window(QWidget): @@ -164,52 +165,53 @@ class Window(QWidget): pen_width_label.setBuddy(self._pen_width_spin_box) self._pen_style_combo_box = QComboBox() - self._pen_style_combo_box.addItem("Solid", Qt.SolidLine) - self._pen_style_combo_box.addItem("Dash", Qt.DashLine) - self._pen_style_combo_box.addItem("Dot", Qt.DotLine) - self._pen_style_combo_box.addItem("Dash Dot", Qt.DashDotLine) - self._pen_style_combo_box.addItem("Dash Dot Dot", Qt.DashDotDotLine) - self._pen_style_combo_box.addItem("None", Qt.NoPen) + self._pen_style_combo_box.addItem("Solid", Qt.PenStyle.SolidLine) + self._pen_style_combo_box.addItem("Dash", Qt.PenStyle.DashLine) + self._pen_style_combo_box.addItem("Dot", Qt.PenStyle.DotLine) + self._pen_style_combo_box.addItem("Dash Dot", Qt.PenStyle.DashDotLine) + self._pen_style_combo_box.addItem("Dash Dot Dot", Qt.PenStyle.DashDotDotLine) + self._pen_style_combo_box.addItem("None", Qt.PenStyle.NoPen) pen_style_label = QLabel("&Pen Style:") pen_style_label.setBuddy(self._pen_style_combo_box) self._pen_cap_combo_box = QComboBox() - self._pen_cap_combo_box.addItem("Flat", Qt.FlatCap) - self._pen_cap_combo_box.addItem("Square", Qt.SquareCap) - self._pen_cap_combo_box.addItem("Round", Qt.RoundCap) + self._pen_cap_combo_box.addItem("Flat", Qt.PenCapStyle.FlatCap) + self._pen_cap_combo_box.addItem("Square", Qt.PenCapStyle.SquareCap) + self._pen_cap_combo_box.addItem("Round", Qt.PenCapStyle.RoundCap) pen_cap_label = QLabel("Pen &Cap:") pen_cap_label.setBuddy(self._pen_cap_combo_box) self._pen_join_combo_box = QComboBox() - self._pen_join_combo_box.addItem("Miter", Qt.MiterJoin) - self._pen_join_combo_box.addItem("Bevel", Qt.BevelJoin) - self._pen_join_combo_box.addItem("Round", Qt.RoundJoin) + self._pen_join_combo_box.addItem("Miter", Qt.PenJoinStyle.MiterJoin) + self._pen_join_combo_box.addItem("Bevel", Qt.PenJoinStyle.BevelJoin) + self._pen_join_combo_box.addItem("Round", Qt.PenJoinStyle.RoundJoin) pen_join_label = QLabel("Pen &Join:") pen_join_label.setBuddy(self._pen_join_combo_box) self._brush_style_combo_box = QComboBox() - self._brush_style_combo_box.addItem("Linear Gradient", Qt.LinearGradientPattern) - self._brush_style_combo_box.addItem("Radial Gradient", Qt.RadialGradientPattern) - self._brush_style_combo_box.addItem("Conical Gradient", Qt.ConicalGradientPattern) - self._brush_style_combo_box.addItem("Texture", Qt.TexturePattern) - self._brush_style_combo_box.addItem("Solid", Qt.SolidPattern) - self._brush_style_combo_box.addItem("Horizontal", Qt.HorPattern) - self._brush_style_combo_box.addItem("Vertical", Qt.VerPattern) - self._brush_style_combo_box.addItem("Cross", Qt.CrossPattern) - self._brush_style_combo_box.addItem("Backward Diagonal", Qt.BDiagPattern) - self._brush_style_combo_box.addItem("Forward Diagonal", Qt.FDiagPattern) - self._brush_style_combo_box.addItem("Diagonal Cross", Qt.DiagCrossPattern) - self._brush_style_combo_box.addItem("Dense 1", Qt.Dense1Pattern) - self._brush_style_combo_box.addItem("Dense 2", Qt.Dense2Pattern) - self._brush_style_combo_box.addItem("Dense 3", Qt.Dense3Pattern) - self._brush_style_combo_box.addItem("Dense 4", Qt.Dense4Pattern) - self._brush_style_combo_box.addItem("Dense 5", Qt.Dense5Pattern) - self._brush_style_combo_box.addItem("Dense 6", Qt.Dense6Pattern) - self._brush_style_combo_box.addItem("Dense 7", Qt.Dense7Pattern) - self._brush_style_combo_box.addItem("None", Qt.NoBrush) + self._brush_style_combo_box.addItem("Linear Gradient", Qt.BrushStyle.LinearGradientPattern) + self._brush_style_combo_box.addItem("Radial Gradient", Qt.BrushStyle.RadialGradientPattern) + self._brush_style_combo_box.addItem("Conical Gradient", + Qt.BrushStyle.ConicalGradientPattern) + self._brush_style_combo_box.addItem("Texture", Qt.BrushStyle.TexturePattern) + self._brush_style_combo_box.addItem("Solid", Qt.BrushStyle.SolidPattern) + self._brush_style_combo_box.addItem("Horizontal", Qt.BrushStyle.HorPattern) + self._brush_style_combo_box.addItem("Vertical", Qt.BrushStyle.VerPattern) + self._brush_style_combo_box.addItem("Cross", Qt.BrushStyle.CrossPattern) + self._brush_style_combo_box.addItem("Backward Diagonal", Qt.BrushStyle.BDiagPattern) + self._brush_style_combo_box.addItem("Forward Diagonal", Qt.BrushStyle.FDiagPattern) + self._brush_style_combo_box.addItem("Diagonal Cross", Qt.BrushStyle.DiagCrossPattern) + self._brush_style_combo_box.addItem("Dense 1", Qt.BrushStyle.Dense1Pattern) + self._brush_style_combo_box.addItem("Dense 2", Qt.BrushStyle.Dense2Pattern) + self._brush_style_combo_box.addItem("Dense 3", Qt.BrushStyle.Dense3Pattern) + self._brush_style_combo_box.addItem("Dense 4", Qt.BrushStyle.Dense4Pattern) + self._brush_style_combo_box.addItem("Dense 5", Qt.BrushStyle.Dense5Pattern) + self._brush_style_combo_box.addItem("Dense 6", Qt.BrushStyle.Dense6Pattern) + self._brush_style_combo_box.addItem("Dense 7", Qt.BrushStyle.Dense7Pattern) + self._brush_style_combo_box.addItem("None", Qt.BrushStyle.NoBrush) brush_style_label = QLabel("&Brush Style:") brush_style_label.setBuddy(self._brush_style_combo_box) @@ -232,20 +234,20 @@ class Window(QWidget): main_layout.setColumnStretch(3, 1) main_layout.addWidget(self._render_area, 0, 0, 1, 4) main_layout.setRowMinimumHeight(1, 6) - main_layout.addWidget(shape_label, 2, 1, Qt.AlignRight) + main_layout.addWidget(shape_label, 2, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._shape_combo_box, 2, 2) - main_layout.addWidget(pen_width_label, 3, 1, Qt.AlignRight) + main_layout.addWidget(pen_width_label, 3, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._pen_width_spin_box, 3, 2) - main_layout.addWidget(pen_style_label, 4, 1, Qt.AlignRight) + main_layout.addWidget(pen_style_label, 4, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._pen_style_combo_box, 4, 2) - main_layout.addWidget(pen_cap_label, 5, 1, Qt.AlignRight) + main_layout.addWidget(pen_cap_label, 5, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._pen_cap_combo_box, 5, 2) - main_layout.addWidget(pen_join_label, 6, 1, Qt.AlignRight) + main_layout.addWidget(pen_join_label, 6, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._pen_join_combo_box, 6, 2) - main_layout.addWidget(brush_style_label, 7, 1, Qt.AlignRight) + main_layout.addWidget(brush_style_label, 7, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._brush_style_combo_box, 7, 2) main_layout.setRowMinimumHeight(8, 6) - main_layout.addWidget(other_options_label, 9, 1, Qt.AlignRight) + main_layout.addWidget(other_options_label, 9, 1, Qt.AlignmentFlag.AlignRight) main_layout.addWidget(self._antialiasing_check_box, 9, 2) main_layout.addWidget(self._transformations_check_box, 10, 2) self.setLayout(main_layout) @@ -270,34 +272,34 @@ class Window(QWidget): join = Qt.PenJoinStyle(self._pen_join_combo_box.itemData( self._pen_join_combo_box.currentIndex(), id_role)) - self._render_area.set_pen(QPen(Qt.blue, width, style, cap, join)) + self._render_area.set_pen(QPen(Qt.GlobalColor.blue, width, style, cap, join)) def brush_changed(self): style = Qt.BrushStyle(self._brush_style_combo_box.itemData( self._brush_style_combo_box.currentIndex(), id_role)) - if style == Qt.LinearGradientPattern: + if style == Qt.BrushStyle.LinearGradientPattern: linear_gradient = QLinearGradient(0, 0, 100, 100) - linear_gradient.setColorAt(0.0, Qt.white) - linear_gradient.setColorAt(0.2, Qt.green) - linear_gradient.setColorAt(1.0, Qt.black) + linear_gradient.setColorAt(0.0, Qt.GlobalColor.white) + linear_gradient.setColorAt(0.2, Qt.GlobalColor.green) + linear_gradient.setColorAt(1.0, Qt.GlobalColor.black) self._render_area.set_brush(QBrush(linear_gradient)) - elif style == Qt.RadialGradientPattern: + elif style == Qt.BrushStyle.RadialGradientPattern: radial_gradient = QRadialGradient(50, 50, 50, 70, 70) - radial_gradient.setColorAt(0.0, Qt.white) - radial_gradient.setColorAt(0.2, Qt.green) - radial_gradient.setColorAt(1.0, Qt.black) + radial_gradient.setColorAt(0.0, Qt.GlobalColor.white) + radial_gradient.setColorAt(0.2, Qt.GlobalColor.green) + radial_gradient.setColorAt(1.0, Qt.GlobalColor.black) self._render_area.set_brush(QBrush(radial_gradient)) - elif style == Qt.ConicalGradientPattern: + elif style == Qt.BrushStyle.ConicalGradientPattern: conical_gradient = QConicalGradient(50, 50, 150) - conical_gradient.setColorAt(0.0, Qt.white) - conical_gradient.setColorAt(0.2, Qt.green) - conical_gradient.setColorAt(1.0, Qt.black) + conical_gradient.setColorAt(0.0, Qt.GlobalColor.white) + conical_gradient.setColorAt(0.2, Qt.GlobalColor.green) + conical_gradient.setColorAt(1.0, Qt.GlobalColor.black) self._render_area.set_brush(QBrush(conical_gradient)) - elif style == Qt.TexturePattern: + elif style == Qt.BrushStyle.TexturePattern: self._render_area.set_brush(QBrush(QPixmap(':/images/brick.png'))) else: - self._render_area.set_brush(QBrush(Qt.green, style)) + self._render_area.set_brush(QBrush(Qt.GlobalColor.green, style)) if __name__ == '__main__': diff --git a/examples/widgets/painting/concentriccircles/concentriccircles.py b/examples/widgets/painting/concentriccircles/concentriccircles.py index d2c60178..4eef4df7 100644 --- a/examples/widgets/painting/concentriccircles/concentriccircles.py +++ b/examples/widgets/painting/concentriccircles/concentriccircles.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/painting/concentriccircles example from Qt v5.x, originating from PyQt""" @@ -19,8 +20,8 @@ class CircleWidget(QWidget): self.antialiased = False self._frame_no = 0 - self.setBackgroundRole(QPalette.Base) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + self.setBackgroundRole(QPalette.ColorRole.Base) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) def set_float_based(self, floatBased): self._float_based = floatBased @@ -42,7 +43,7 @@ class CircleWidget(QWidget): def paintEvent(self, event): with QPainter(self) as painter: - painter.setRenderHint(QPainter.Antialiasing, self.antialiased) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, self.antialiased) painter.translate(self.width() / 2, self.height() / 2) for diameter in range(0, 256, 9): @@ -93,9 +94,9 @@ class Window(QWidget): def create_label(self, text): label = QLabel(text) - label.setAlignment(Qt.AlignCenter) + label.setAlignment(Qt.AlignmentFlag.AlignCenter) label.setMargin(2) - label.setFrameStyle(QFrame.Box | QFrame.Sunken) + label.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Sunken) return label diff --git a/examples/widgets/painting/painter/painter.py b/examples/widgets/painting/painter/painter.py index 2ca078ad..4b009b30 100644 --- a/examples/widgets/painting/painter/painter.py +++ b/examples/widgets/painting/painter/painter.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtWidgets import ( QWidget, @@ -36,14 +37,14 @@ class PainterWidget(QWidget): self.setFixedSize(680, 480) self.pixmap = QPixmap(self.size()) - self.pixmap.fill(Qt.white) + self.pixmap.fill(Qt.GlobalColor.white) self.previous_pos = None self.painter = QPainter() self.pen = QPen() self.pen.setWidth(10) - self.pen.setCapStyle(Qt.RoundCap) - self.pen.setJoinStyle(Qt.RoundJoin) + self.pen.setCapStyle(Qt.PenCapStyle.RoundCap) + self.pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) def paintEvent(self, event: QPaintEvent): """Override method from QWidget @@ -71,7 +72,7 @@ class PainterWidget(QWidget): """ current_pos = event.position().toPoint() self.painter.begin(self.pixmap) - self.painter.setRenderHints(QPainter.Antialiasing, True) + self.painter.setRenderHints(QPainter.RenderHint.Antialiasing, True) self.painter.setPen(self.pen) self.painter.drawLine(self.previous_pos, current_pos) self.painter.end() @@ -97,12 +98,12 @@ class PainterWidget(QWidget): def load(self, filename: str): """ load pixmap from filename """ self.pixmap.load(filename) - self.pixmap = self.pixmap.scaled(self.size(), Qt.KeepAspectRatio) + self.pixmap = self.pixmap.scaled(self.size(), Qt.AspectRatioMode.KeepAspectRatio) self.update() def clear(self): """ Clear the pixmap """ - self.pixmap.fill(Qt.white) + self.pixmap.fill(Qt.GlobalColor.white) self.update() @@ -114,19 +115,19 @@ class MainWindow(QMainWindow): self.painter_widget = PainterWidget() self.bar = self.addToolBar("Menu") - self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + self.bar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) self._save_action = self.bar.addAction( - qApp.style().standardIcon(QStyle.SP_DialogSaveButton), # noqa: F821 + qApp.style().standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton), # noqa: F821 "Save", self.on_save ) - self._save_action.setShortcut(QKeySequence.Save) + self._save_action.setShortcut(QKeySequence.StandardKey.Save) self._open_action = self.bar.addAction( - qApp.style().standardIcon(QStyle.SP_DialogOpenButton), # noqa: F821 + qApp.style().standardIcon(QStyle.StandardPixmap.SP_DialogOpenButton), # noqa: F821 "Open", self.on_open ) - self._open_action.setShortcut(QKeySequence.Open) + self._open_action.setShortcut(QKeySequence.StandardKey.Open) self.bar.addAction( - qApp.style().standardIcon(QStyle.SP_DialogResetButton), # noqa: F821 + qApp.style().standardIcon(QStyle.StandardPixmap.SP_DialogResetButton), # noqa: F821 "Clear", self.painter_widget.clear, ) @@ -138,7 +139,7 @@ class MainWindow(QMainWindow): self.setCentralWidget(self.painter_widget) - self.color = Qt.black + self.color = Qt.GlobalColor.black self.set_color(self.color) self.mime_type_filters = ["image/png", "image/jpeg"] @@ -148,14 +149,14 @@ class MainWindow(QMainWindow): dialog = QFileDialog(self, "Save File") dialog.setMimeTypeFilters(self.mime_type_filters) - dialog.setFileMode(QFileDialog.AnyFile) - dialog.setAcceptMode(QFileDialog.AcceptSave) + dialog.setFileMode(QFileDialog.FileMode.AnyFile) + dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptSave) dialog.setDefaultSuffix("png") dialog.setDirectory( - QStandardPaths.writableLocation(QStandardPaths.PicturesLocation) + QStandardPaths.writableLocation(QStandardPaths.StandardLocation.PicturesLocation) ) - if dialog.exec() == QFileDialog.Accepted: + if dialog.exec() == QFileDialog.DialogCode.Accepted: if dialog.selectedFiles(): self.painter_widget.save(dialog.selectedFiles()[0]) @@ -164,14 +165,14 @@ class MainWindow(QMainWindow): dialog = QFileDialog(self, "Save File") dialog.setMimeTypeFilters(self.mime_type_filters) - dialog.setFileMode(QFileDialog.ExistingFile) - dialog.setAcceptMode(QFileDialog.AcceptOpen) + dialog.setFileMode(QFileDialog.FileMode.ExistingFile) + dialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) dialog.setDefaultSuffix("png") dialog.setDirectory( - QStandardPaths.writableLocation(QStandardPaths.PicturesLocation) + QStandardPaths.writableLocation(QStandardPaths.StandardLocation.PicturesLocation) ) - if dialog.exec() == QFileDialog.Accepted: + if dialog.exec() == QFileDialog.DialogCode.Accepted: if dialog.selectedFiles(): self.painter_widget.load(dialog.selectedFiles()[0]) @@ -183,7 +184,7 @@ class MainWindow(QMainWindow): if color: self.set_color(color) - def set_color(self, color: QColor = Qt.black): + def set_color(self, color: QColor = Qt.GlobalColor.black): self.color = color # Create color icon diff --git a/examples/widgets/painting/plot/plot.py b/examples/widgets/painting/plot/plot.py index fd7ff993..5e564511 100644 --- a/examples/widgets/painting/plot/plot.py +++ b/examples/widgets/painting/plot/plot.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import math import sys @@ -52,7 +53,7 @@ class PlotWidget(QWidget): def paintEvent(self, event): with QPainter(self) as painter: rect = QRect(QPoint(0, 0), self.size()) - painter.fillRect(rect, Qt.white) + painter.fillRect(rect, Qt.GlobalColor.white) painter.translate(-self._points[0].x(), 0) painter.drawPolyline(self._points) diff --git a/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst index c33c6660..65cfacd8 100644 --- a/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst +++ b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst @@ -1,12 +1,10 @@ -.. _rhi-widget-example: - Simple RHI Widget Example ========================= Shows how to render a triangle using ``QRhi``, Qt's 3D API and shading language abstraction layer. -This example is, in many ways, the counterpart of the :ref:`rhi-window-example` +This example is, in many ways, the counterpart of the :ref:`example_gui_rhiwindow` in the QWidget world. The ``QRhiWidget`` subclass in this applications renders a single triangle, using a simple graphics pipeline with basic vertex and fragment shaders. Unlike the plain ``QWindow``-based application, this example diff --git a/examples/widgets/rhi/simplerhiwidget/examplewidget.py b/examples/widgets/rhi/simplerhiwidget/examplewidget.py index 5b3e40f5..732c8e55 100644 --- a/examples/widgets/rhi/simplerhiwidget/examplewidget.py +++ b/examples/widgets/rhi/simplerhiwidget/examplewidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import numpy @@ -23,7 +24,7 @@ VERTEX_DATA = numpy.array([ 0.0, 0.5, 1.0, 0.0, 0.0, # noqa E:201 def getShader(name): f = QFile(name) - if f.open(QIODevice.ReadOnly): + if f.open(QIODevice.OpenModeFlag.ReadOnly): return QShader.fromSerialized(f.readAll()) return QShader() @@ -61,17 +62,17 @@ class ExampleRhiWidget(QRhiWidget): if not self.m_pipeline: vertex_size = 4 * VERTEX_DATA.size - self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Immutable, - QRhiBuffer.VertexBuffer, vertex_size) + self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Type.Immutable, + QRhiBuffer.UsageFlag.VertexBuffer, vertex_size) self.m_vbuf.create() - self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Dynamic, - QRhiBuffer.UniformBuffer, 64) + self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Type.Dynamic, + QRhiBuffer.UsageFlag.UniformBuffer, 64) self.m_ubuf.create() self.m_srb = self.m_rhi.newShaderResourceBindings() bindings = [ - QRhiShaderResourceBinding.uniformBuffer(0, QRhiShaderResourceBinding.VertexStage, + QRhiShaderResourceBinding.uniformBuffer(0, QRhiShaderResourceBinding.StageFlag.VertexStage, # noqa: E501 self.m_ubuf) ] self.m_srb.setBindings(bindings) @@ -79,9 +80,9 @@ class ExampleRhiWidget(QRhiWidget): self.m_pipeline = self.m_rhi.newGraphicsPipeline() stages = [ - QRhiShaderStage(QRhiShaderStage.Vertex, + QRhiShaderStage(QRhiShaderStage.Type.Vertex, getShader(":/shader_assets/color.vert.qsb")), - QRhiShaderStage(QRhiShaderStage.Fragment, + QRhiShaderStage(QRhiShaderStage.Type.Fragment, getShader(":/shader_assets/color.frag.qsb")) ] self.m_pipeline.setShaderStages(stages) @@ -89,8 +90,8 @@ class ExampleRhiWidget(QRhiWidget): input_bindings = [QRhiVertexInputBinding(5 * 4)] # sizeof(float) inputLayout.setBindings(input_bindings) attributes = [ # 4: sizeof(float) - QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Float2, 0), - QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Float3, 2 * 4) + QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Format.Float2, 0), + QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Format.Float3, 2 * 4) ] inputLayout.setAttributes(attributes) self.m_pipeline.setVertexInputLayout(inputLayout) diff --git a/examples/widgets/rhi/simplerhiwidget/main.py b/examples/widgets/rhi/simplerhiwidget/main.py index 59be81dd..5b23af0d 100644 --- a/examples/widgets/rhi/simplerhiwidget/main.py +++ b/examples/widgets/rhi/simplerhiwidget/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the Qt Simple RHI Widget Example example from Qt v6.x""" diff --git a/examples/widgets/richtext/orderform/doc/orderform.rst b/examples/widgets/richtext/orderform/doc/orderform.rst new file mode 100644 index 00000000..ce5e53fa --- /dev/null +++ b/examples/widgets/richtext/orderform/doc/orderform.rst @@ -0,0 +1,9 @@ +Order Form Example +================== + +The Order Form example shows how to generate rich text documents by combining a +simple template with data input by the user in a dialog + +.. image:: orderform.webp + :width: 400 + :alt: orderform screenshot diff --git a/examples/widgets/richtext/orderform/doc/orderform.webp b/examples/widgets/richtext/orderform/doc/orderform.webp new file mode 100644 index 0000000000000000000000000000000000000000..501ea16f4442154e107150f4ef4593ee8a3d90c8 GIT binary patch literal 25490 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 literal 0 HcmV?d00001 diff --git a/examples/widgets/richtext/orderform/orderform.py b/examples/widgets/richtext/orderform/orderform.py index 9725624c..6e0818e1 100644 --- a/examples/widgets/richtext/orderform/orderform.py +++ b/examples/widgets/richtext/orderform/orderform.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/richtext/orderform example from Qt v5.x""" @@ -45,7 +46,7 @@ class MainWindow(QMainWindow): self.letters.setCurrentIndex(tab_index) cursor = editor.textCursor() - cursor.movePosition(QTextCursor.Start) + cursor.movePosition(QTextCursor.MoveOperation.Start) top_frame = cursor.currentFrame() top_frame_format = top_frame.frameFormat() top_frame_format.setPadding(16) @@ -53,13 +54,13 @@ class MainWindow(QMainWindow): text_format = QTextCharFormat() bold_format = QTextCharFormat() - bold_format.setFontWeight(QFont.Bold) + bold_format.setFontWeight(QFont.Weight.Bold) reference_frame_format = QTextFrameFormat() reference_frame_format.setBorder(1) reference_frame_format.setPadding(8) - reference_frame_format.setPosition(QTextFrameFormat.FloatRight) - reference_frame_format.setWidth(QTextLength(QTextLength.PercentageLength, 40)) + reference_frame_format.setPosition(QTextFrameFormat.Position.FloatRight) + reference_frame_format.setWidth(QTextLength(QTextLength.Type.PercentageLength, 40)) cursor.insertFrame(reference_frame_format) cursor.insertText("A company", bold_format) @@ -86,7 +87,7 @@ class MainWindow(QMainWindow): cursor.insertBlock() body_frame_format = QTextFrameFormat() - body_frame_format.setWidth(QTextLength(QTextLength.PercentageLength, 100)) + body_frame_format.setWidth(QTextLength(QTextLength.Type.PercentageLength, 100)) cursor.insertFrame(body_frame_format) cursor.insertText("I would like to place an order for the following items:", text_format) @@ -94,7 +95,7 @@ class MainWindow(QMainWindow): cursor.insertBlock() order_table_format = QTextTableFormat() - order_table_format.setAlignment(Qt.AlignHCenter) + order_table_format.setAlignment(Qt.AlignmentFlag.AlignHCenter) order_table = cursor.insertTable(1, 2, order_table_format) order_frame_format = cursor.currentFrame().frameFormat() @@ -188,7 +189,7 @@ class DetailsDialog(QDialog): name_label = QLabel("Name:") address_label = QLabel("Address:") - address_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) + address_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) self._name_edit = QLineEdit() self._address_edit = QTextEdit() @@ -196,7 +197,8 @@ class DetailsDialog(QDialog): self.setup_items_table() - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) button_box.accepted.connect(self.verify) button_box.rejected.connect(self.reject) @@ -217,7 +219,7 @@ class DetailsDialog(QDialog): for row, item in enumerate(self.items): name = QTableWidgetItem(item) - name.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + name.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable) self._items_table.setItem(row, 0, name) quantity = QTableWidgetItem('1') self._items_table.setItem(row, 1, quantity) @@ -227,7 +229,7 @@ class DetailsDialog(QDialog): for row in range(len(self.items)): text = self._items_table.item(row, 0).text() - quantity = int(self._items_table.item(row, 1).data(Qt.DisplayRole)) + quantity = int(self._items_table.item(row, 1).data(Qt.ItemDataRole.DisplayRole)) order_list.append((text, max(0, quantity))) return order_list diff --git a/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py b/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py index 02132897..46534b18 100644 --- a/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py +++ b/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/richtext/syntaxhighlighter example from Qt v5.x""" @@ -39,20 +40,20 @@ class MainWindow(QMainWindow): if file_name: in_file = QFile(file_name) - if in_file.open(QFile.ReadOnly | QFile.Text): + if in_file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): stream = QTextStream(in_file) self._editor.setPlainText(stream.readAll()) def setup_editor(self): class_format = QTextCharFormat() - class_format.setFontWeight(QFont.Bold) - class_format.setForeground(Qt.blue) + class_format.setFontWeight(QFont.Weight.Bold) + class_format.setForeground(Qt.GlobalColor.blue) pattern = r'^\s*class\s+\w+\(.*$' self._highlighter.add_mapping(pattern, class_format) function_format = QTextCharFormat() function_format.setFontItalic(True) - function_format.setForeground(Qt.blue) + function_format.setForeground(Qt.GlobalColor.blue) pattern = r'^\s*def\s+\w+\s*\(.*\)\s*:\s*$' self._highlighter.add_mapping(pattern, function_format) @@ -60,7 +61,7 @@ class MainWindow(QMainWindow): comment_format.setBackground(QColor("#77ff77")) self._highlighter.add_mapping(r'^\s*#.*$', comment_format) - font = QFontDatabase.systemFont(QFontDatabase.FixedFont) + font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont) self._editor = QPlainTextEdit() self._editor.setFont(font) self._highlighter.setDocument(self._editor.document()) @@ -69,15 +70,15 @@ class MainWindow(QMainWindow): file_menu = self.menuBar().addMenu(self.tr("&File")) new_file_act = file_menu.addAction(self.tr("&New...")) - new_file_act.setShortcut(QKeySequence(QKeySequence.New)) + new_file_act.setShortcut(QKeySequence(QKeySequence.StandardKey.New)) new_file_act.triggered.connect(self.new_file) open_file_act = file_menu.addAction(self.tr("&Open...")) - open_file_act.setShortcut(QKeySequence(QKeySequence.Open)) + open_file_act.setShortcut(QKeySequence(QKeySequence.StandardKey.Open)) open_file_act.triggered.connect(self.open_file) quit_act = file_menu.addAction(self.tr("E&xit")) - quit_act.setShortcut(QKeySequence(QKeySequence.Quit)) + quit_act.setShortcut(QKeySequence(QKeySequence.StandardKey.Quit)) quit_act.triggered.connect(self.close) help_menu = self.menuBar().addMenu("&Help") diff --git a/examples/widgets/richtext/textedit/main.py b/examples/widgets/richtext/textedit/main.py index 330ea5fc..3bebace7 100644 --- a/examples/widgets/richtext/textedit/main.py +++ b/examples/widgets/richtext/textedit/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/richtext/textedit example from Qt v6.x""" diff --git a/examples/widgets/richtext/textedit/textedit.py b/examples/widgets/richtext/textedit/textedit.py index 8019446f..8cd330a7 100644 --- a/examples/widgets/richtext/textedit/textedit.py +++ b/examples/widgets/richtext/textedit/textedit.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtCore import (QCoreApplication, QDir, QFile, QFileInfo, @@ -48,7 +49,7 @@ class TextEdit(QMainWindow): self._text_edit.cursorPositionChanged.connect(self.cursor_position_changed) self.setCentralWidget(self._text_edit) - self.setToolButtonStyle(Qt.ToolButtonFollowStyle) + self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonFollowStyle) self.setup_file_actions() self.setup_edit_actions() self.setup_text_actions() @@ -58,7 +59,7 @@ class TextEdit(QMainWindow): help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821 text_font = QFont("Helvetica") - text_font.setStyleHint(QFont.SansSerif) + text_font.setStyleHint(QFont.StyleHint.SansSerif) self._text_edit.setFont(text_font) self.font_changed(self._text_edit.font()) self.color_changed(self._text_edit.textColor()) @@ -87,8 +88,8 @@ class TextEdit(QMainWindow): # Use dark text on light background on macOS, also in dark mode. if sys.platform == 'darwin': pal = self._text_edit.palette() - pal.setColor(QPalette.Base, QColor(Qt.white)) - pal.setColor(QPalette.Text, QColor(Qt.black)) + pal.setColor(QPalette.Base, QColor(Qt.GlobalColor.white)) + pal.setColor(QPalette.Text, QColor(Qt.GlobalColor.black)) self._text_edit.setPalette(pal) def closeEvent(self, e): @@ -105,13 +106,13 @@ class TextEdit(QMainWindow): QIcon(RSRC_PATH + "/filenew.png")) a = menu.addAction(icon, "&New", self.file_new) tb.addAction(a) - a.setPriority(QAction.LowPriority) - a.setShortcut(QKeySequence.New) + a.setPriority(QAction.Priority.LowPriority) + a.setShortcut(QKeySequence.StandardKey.New) icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen, QIcon(RSRC_PATH + "/fileopen.png")) a = menu.addAction(icon, "&Open...", self.file_open) - a.setShortcut(QKeySequence.Open) + a.setShortcut(QKeySequence.StandardKey.Open) tb.addAction(a) menu.addSeparator() @@ -119,19 +120,19 @@ class TextEdit(QMainWindow): icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave, QIcon(RSRC_PATH + "/filesave.png")) self._action_save = menu.addAction(icon, "&Save", self.file_save) - self._action_save.setShortcut(QKeySequence.Save) + self._action_save.setShortcut(QKeySequence.StandardKey.Save) self._action_save.setEnabled(False) tb.addAction(self._action_save) a = menu.addAction("Save &As...", self.file_save_as) - a.setPriority(QAction.LowPriority) + a.setPriority(QAction.Priority.LowPriority) menu.addSeparator() icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentPrint, QIcon(RSRC_PATH + "/fileprint.png")) a = menu.addAction(icon, "&Print...", self.file_print) - a.setPriority(QAction.LowPriority) - a.setShortcut(QKeySequence.Print) + a.setPriority(QAction.Priority.LowPriority) + a.setShortcut(QKeySequence.StandardKey.Print) tb.addAction(a) icon = QIcon.fromTheme("fileprint", QIcon(RSRC_PATH + "/fileprint.png")) @@ -139,14 +140,14 @@ class TextEdit(QMainWindow): icon = QIcon.fromTheme("exportpdf", QIcon(RSRC_PATH + "/exportpdf.png")) a = menu.addAction(icon, "&Export PDF...", self.file_print_pdf) - a.setPriority(QAction.LowPriority) - a.setShortcut(Qt.CTRL | Qt.Key_D) + a.setPriority(QAction.Priority.LowPriority) + a.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_D) tb.addAction(a) menu.addSeparator() a = menu.addAction("&Quit", self.close) - a.setShortcut(Qt.CTRL | Qt.Key_Q) + a.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_Q) def setup_edit_actions(self): tb = self.addToolBar("Edit self.actions") @@ -155,36 +156,36 @@ class TextEdit(QMainWindow): icon = QIcon.fromTheme(QIcon.ThemeIcon.EditUndo, QIcon(RSRC_PATH + "/editundo.png")) self._action_undo = menu.addAction(icon, "&Undo", self._text_edit.undo) - self._action_undo.setShortcut(QKeySequence.Undo) + self._action_undo.setShortcut(QKeySequence.StandardKey.Undo) tb.addAction(self._action_undo) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditRedo, QIcon(RSRC_PATH + "/editredo.png")) self._action_redo = menu.addAction(icon, "&Redo", self._text_edit.redo) - self._action_redo.setPriority(QAction.LowPriority) - self._action_redo.setShortcut(QKeySequence.Redo) + self._action_redo.setPriority(QAction.Priority.LowPriority) + self._action_redo.setShortcut(QKeySequence.StandardKey.Redo) tb.addAction(self._action_redo) menu.addSeparator() icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(RSRC_PATH + "/editcut.png")) self._action_cut = menu.addAction(icon, "Cu&t", self._text_edit.cut) - self._action_cut.setPriority(QAction.LowPriority) - self._action_cut.setShortcut(QKeySequence.Cut) + self._action_cut.setPriority(QAction.Priority.LowPriority) + self._action_cut.setShortcut(QKeySequence.StandardKey.Cut) tb.addAction(self._action_cut) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(RSRC_PATH + "/editcopy.png")) self._action_copy = menu.addAction(icon, "&Copy", self._text_edit.copy) - self._action_copy.setPriority(QAction.LowPriority) - self._action_copy.setShortcut(QKeySequence.Copy) + self._action_copy.setPriority(QAction.Priority.LowPriority) + self._action_copy.setShortcut(QKeySequence.StandardKey.Copy) tb.addAction(self._action_copy) icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(RSRC_PATH + "/editpaste.png")) self._action_paste = menu.addAction(icon, "&Paste", self._text_edit.paste) - self._action_paste.setPriority(QAction.LowPriority) - self._action_paste.setShortcut(QKeySequence.Paste) + self._action_paste.setPriority(QAction.Priority.LowPriority) + self._action_paste.setShortcut(QKeySequence.StandardKey.Paste) tb.addAction(self._action_paste) md = QGuiApplication.clipboard().mimeData() @@ -198,8 +199,8 @@ class TextEdit(QMainWindow): icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatTextBold, QIcon(RSRC_PATH + "/textbold.png")) self._action_text_bold = menu.addAction(icon, "&Bold", self.text_bold) - self._action_text_bold.setShortcut(Qt.CTRL | Qt.Key_B) - self._action_text_bold.setPriority(QAction.LowPriority) + self._action_text_bold.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_B) + self._action_text_bold.setPriority(QAction.Priority.LowPriority) bold = QFont() bold.setBold(True) self._action_text_bold.setFont(bold) @@ -209,8 +210,8 @@ class TextEdit(QMainWindow): icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatTextItalic, QIcon(RSRC_PATH + "/textitalic.png")) self._action_text_italic = menu.addAction(icon, "&Italic", self.text_italic) - self._action_text_italic.setPriority(QAction.LowPriority) - self._action_text_italic.setShortcut(Qt.CTRL | Qt.Key_I) + self._action_text_italic.setPriority(QAction.Priority.LowPriority) + self._action_text_italic.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_I) italic = QFont() italic.setItalic(True) self._action_text_italic.setFont(italic) @@ -221,8 +222,8 @@ class TextEdit(QMainWindow): QIcon(RSRC_PATH + "/textunder.png")) self._action_text_underline = menu.addAction(icon, "&Underline", self.text_underline) - self._action_text_underline.setShortcut(Qt.CTRL | Qt.Key_U) - self._action_text_underline.setPriority(QAction.LowPriority) + self._action_text_underline.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_U) + self._action_text_underline.setPriority(QAction.Priority.LowPriority) underline = QFont() underline.setUnderline(True) self._action_text_underline.setFont(underline) @@ -234,38 +235,38 @@ class TextEdit(QMainWindow): icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyLeft, QIcon(RSRC_PATH + "/textleft.png")) self._action_align_left = QAction(icon, "&Left", self) - self._action_align_left.setShortcut(Qt.CTRL | Qt.Key_L) + self._action_align_left.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_L) self._action_align_left.setCheckable(True) - self._action_align_left.setPriority(QAction.LowPriority) + self._action_align_left.setPriority(QAction.Priority.LowPriority) icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyCenter, QIcon(RSRC_PATH + "/textcenter.png")) self._action_align_center = QAction(icon, "C&enter", self) - self._action_align_center.setShortcut(Qt.CTRL | Qt.Key_E) + self._action_align_center.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_E) self._action_align_center.setCheckable(True) - self._action_align_center.setPriority(QAction.LowPriority) + self._action_align_center.setPriority(QAction.Priority.LowPriority) icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyRight, QIcon(RSRC_PATH + "/textright.png")) self._action_align_right = QAction(icon, "&Right", self) - self._action_align_right.setShortcut(Qt.CTRL | Qt.Key_R) + self._action_align_right.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_R) self._action_align_right.setCheckable(True) - self._action_align_right.setPriority(QAction.LowPriority) + self._action_align_right.setPriority(QAction.Priority.LowPriority) icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatJustifyFill, QIcon(RSRC_PATH + "/textjustify.png")) self._action_align_justify = QAction(icon, "&Justify", self) - self._action_align_justify.setShortcut(Qt.CTRL | Qt.Key_J) + self._action_align_justify.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_J) self._action_align_justify.setCheckable(True) - self._action_align_justify.setPriority(QAction.LowPriority) + self._action_align_justify.setPriority(QAction.Priority.LowPriority) icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatIndentMore, QIcon(RSRC_PATH + "/format-indent-more.png")) self._action_indent_more = menu.addAction(icon, "&Indent", self.indent) - self._action_indent_more.setShortcut(Qt.CTRL | Qt.Key_BracketRight) - self._action_indent_more.setPriority(QAction.LowPriority) + self._action_indent_more.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_BracketRight) + self._action_indent_more.setPriority(QAction.Priority.LowPriority) icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatIndentLess, QIcon(RSRC_PATH + "/format-indent-less.png")) self._action_indent_less = menu.addAction(icon, "&Unindent", self.unindent) - self._action_indent_less.setShortcut(Qt.CTRL | Qt.Key_BracketLeft) - self._action_indent_less.setPriority(QAction.LowPriority) + self._action_indent_less.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_BracketLeft) + self._action_indent_less.setPriority(QAction.Priority.LowPriority) # Make sure the alignLeft is always left of the alignRight align_group = QActionGroup(self) @@ -291,7 +292,7 @@ class TextEdit(QMainWindow): menu.addSeparator() pix = QPixmap(16, 16) - pix.fill(Qt.black) + pix.fill(Qt.GlobalColor.black) self._action_text_color = menu.addAction(pix, "&Color...", self.text_color) tb.addAction(self._action_text_color) @@ -306,14 +307,14 @@ class TextEdit(QMainWindow): QIcon(RSRC_PATH + "/checkbox-checked.png")) self._action_toggle_check_state = menu.addAction(icon, "Chec&ked") self._action_toggle_check_state.toggled.connect(self.set_checked) - self._action_toggle_check_state.setShortcut(Qt.CTRL | Qt.Key_K) + self._action_toggle_check_state.setShortcut(Qt.Modifier.CTRL | Qt.Key.Key_K) self._action_toggle_check_state.setCheckable(True) - self._action_toggle_check_state.setPriority(QAction.LowPriority) + self._action_toggle_check_state.setPriority(QAction.Priority.LowPriority) tb.addAction(self._action_toggle_check_state) tb = self.addToolBar("Format self.actions") - tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea) - self.addToolBarBreak(Qt.TopToolBarArea) + tb.setAllowedAreas(Qt.ToolBarArea.TopToolBarArea | Qt.ToolBarArea.BottomToolBarArea) + self.addToolBarBreak(Qt.ToolBarArea.TopToolBarArea) self.addToolBar(tb) self._combo_style = QComboBox(tb) @@ -343,7 +344,7 @@ class TextEdit(QMainWindow): if not QFile.exists(f): return False file = QFile(f) - if not file.open(QFile.ReadOnly): + if not file.open(QFile.OpenModeFlag.ReadOnly): return False data = file.readAll() @@ -352,7 +353,7 @@ class TextEdit(QMainWindow): text = data.data().decode('utf8') if mime_type_name == "text/html": file_url = QUrl(f) if f[0] == ':' else QUrl.fromLocalFile(f) - options = QUrl.FormattingOptions(QUrl.RemoveFilename) + options = QUrl.FormattingOptions(QUrl.UrlFormattingOption.RemoveFilename) self._text_edit.document().setBaseUrl(file_url.adjusted(options)) self._text_edit.setHtml(text) elif mime_type_name == "text/markdown": @@ -585,7 +586,7 @@ class TextEdit(QMainWindow): @Slot() def underline_color(self): - col = QColorDialog.getColor(Qt.black, self) + col = QColorDialog.getColor(Qt.GlobalColor.black, self) if not col.isValid(): return fmt = QTextCharFormat() @@ -596,13 +597,15 @@ class TextEdit(QMainWindow): @Slot(QAction) def text_align(self, a): if a == self._action_align_left: - self._text_edit.setAlignment(Qt.AlignLeft | Qt.AlignAbsolute) + self._text_edit.setAlignment(Qt.AlignmentFlag.AlignLeft + | Qt.AlignmentFlag.AlignAbsolute) elif a == self._action_align_center: - self._text_edit.setAlignment(Qt.AlignHCenter) + self._text_edit.setAlignment(Qt.AlignmentFlag.AlignHCenter) elif a == self._action_align_right: - self._text_edit.setAlignment(Qt.AlignRight | Qt.AlignAbsolute) + self._text_edit.setAlignment(Qt.AlignmentFlag.AlignRight + | Qt.AlignmentFlag.AlignAbsolute) elif a == self._action_align_justify: - self._text_edit.setAlignment(Qt.AlignJustify) + self._text_edit.setAlignment(Qt.AlignmentFlag.AlignJustify) @Slot(bool) def set_checked(self, checked): @@ -711,11 +714,11 @@ class TextEdit(QMainWindow): self._action_text_color.setIcon(pix) def alignment_changed(self, a): - if a & Qt.AlignLeft: + if a & Qt.AlignmentFlag.AlignLeft: self._action_align_left.setChecked(True) - elif a & Qt.AlignHCenter: + elif a & Qt.AlignmentFlag.AlignHCenter: self._action_align_center.setChecked(True) - elif a & Qt.AlignRight: + elif a & Qt.AlignmentFlag.AlignRight: self._action_align_right.setChecked(True) - elif a & Qt.AlignJustify: + elif a & Qt.AlignmentFlag.AlignJustify: self._action_align_justify.setChecked(True) diff --git a/examples/widgets/richtext/textobject/textobject.py b/examples/widgets/richtext/textobject/textobject.py index 9ab7bf8a..d542ad89 100644 --- a/examples/widgets/richtext/textobject/textobject.py +++ b/examples/widgets/richtext/textobject/textobject.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/richtext/textobject example from Qt v5.x""" @@ -16,7 +17,7 @@ from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLineEdit, from PySide6.QtSvg import QSvgRenderer -SVG_TEXT_FORMAT = QTextFormat.UserObject + 1 +SVG_TEXT_FORMAT = QTextFormat.ObjectTypes.UserObject + 1 SVG_DATA = 1 @@ -55,7 +56,7 @@ class Window(QWidget): file_name = self._file_name_line_edit.text() file = QFile(file_name) - if not file.open(QIODevice.ReadOnly): + if not file.open(QIODevice.OpenModeFlag.ReadOnly): reason = file.errorString() message = f"Could not open '{file_name}': {reason}" QMessageBox.warning(self, "Error Opening File", message) diff --git a/examples/widgets/thread_signals/doc/thread_signals.png b/examples/widgets/thread_signals/doc/thread_signals.png new file mode 100644 index 0000000000000000000000000000000000000000..ac13aa8e9e76b99ce7a30b20ed9651fab34f5422 GIT binary patch literal 2582 zcmb7`XHXM}5`b}#-lR!~&@m?+>4y*r9Sj&z5C{+g1|d<(0ijASp-B;>N>{o{QJNqv zgdzc?<{}8vMS4PgxtV)E-;cL5yWi~0&d$#4?(8N&tc=;11(|7RXxL0m3~i|Nq^cDo zJ=G2hN3~FK)fZ$6Wu!_7qgy=H7w`i=^t*?4_X}|GLC|=h(B24nU$_qff%1Kf_S>QD z(xV2+{}Xh_2jSv}M56#uq&I@*0m2QStPC(jzyT^sDrx{_6>SwIZDl0@#1e1^45ia= z$I#F)p-l}9pn>_@1))#55uy=ABM#@Utb5CvkVFs#>%Sb`)`)Z6GtV#K7f2P96nssH8{)$XTw$EmFcv<{vDV z0D(X`Ah2ii(2Ur$Ob*doKm`=-v=LDudN6%&Q-!y+waLxR>PI^yMgQp+OZyZ#1%-as z#_v8KF&#TTA7`4qh2~q(946WBnvq)^E%TP{?lPxG+mbpuLbbMk_%p6Hgq@#6ymV|q zIex}tbVc*6WRsJVAJ*CqyQ>2!O`|ECf-S2#UdIi-?HW!~t$N!fhVN_#MAFuAujz|gP;?hsiNBnt9H(<8-+ykfuU%eU<#VPlB@w*BAk|G(%-8_R8&-_|U!5@sLhi(h>wtFE3MAJ0gb zn254Ro0M$*rBhH)u)Ms?TAuqyN@61O+v@757q_IWttvCNwzi7LN;?U-;bB&c1Szdw zZsn;)VC4B$rYq>5QS2#~G}FZuMh4YTiVugMz_iAGV%>*)NlIU#Zlu=_S_7QK8EEsI z70hED3Sl2WJv&P#v$D|S!$m&u)v3oaV_ijQHP%&z=@*z?jcG{OL(4YUvQO=kvXQEZ zf!RkNGVQqU#jD4bXh^3kpHJ)B`DBjnzvSi0X~al|x66wf5C9C%FXh0uQ{DPuo4+~k z@ewS!o`#R$_uZ}6^3A(rk7q;fF)v-(WDcK`~xp|_PF4{JmqL~|3;`Muy5rHDK=BzZG%dg#u2hip70TyxOaSY~fE%0J(y$;WRX>I2Gt zIB8&C?&LtTc9eU|uA=WjR<+VHPneS2qq+4^Dt0&{wK+MILu<6d zuKAFcli~QlVr@)_+PZ*KkY-X~f#8p+k32PnxW)kU28y8}h>^|uH3!>v_$fv=68uit z%ut{I;Fk>H%uB|bpH9s zA7x^{;ng2@Cm2$d^pN0G_CNDeoIac0E=~aef!M0G&$1PCNm3=z@lb-R6~uZfQBxcP ziDR(lS+6(Zv(VCg8X$tMbL`K{MK?Zmc>LMB`j)4*4kpD!Pz`vyfcT{-F;iOS&CUGb znEHh>X>p{m=`QWvUE!Mqto5Aff^Wdh;LAM?;K`?&XuyFa1Rx`s5g06Rde?C#yVz7J z;0x;F+=S&WsVOrcm{%YQjQ7ulzqu^Se5Ka~d4j{7-Ou)5Cxt0mPgSmFH|5#Qbf#;) zpLV7U_j6R*BcDw^BkyzleX+)05Scz`@*GZ@d+PW(M%_akKM+3rD$dCj?XS9B>8LjM z2f40NvwJW(m2dxBKC|elH)os`3_COBaGJ-vuzQZzZ9fs|tP3EFkM|sBA%s13+f)PD zU4mStHxsLa!^PT7Ol&OTOBlync3qJ-Vp=BO`>$Kn-A$BS+(pSphX{hN)i(~03 zk^D+lfcW|S$@fCk8DIBuu+iIjlpH8gTJMrb(Yo+G^CA_}v+tZnhBnKDe1Tf9uJ6%_ zg^?E#p(|O7ZexXiF0ZW6wV##mm)3x~{ymNk+Tp%(2lzA%B@;lJ4{RqsG-9;L@1PWbi!WoXp0dTF{^G8H7jiu3*X{+h+ z)+egBr&`2&t;82o`uN#zTa9#TD-ADsBvnj!hQ?MD=~l66QdGl+4{ms%VnO z{;n|=Q0;vFBU{3LtPAYDA6bcS*d!F3@+ey>I$utXUS4S*HRoT57tE#pY&51|E5kaFbL4*j D{K4hU literal 0 HcmV?d00001 diff --git a/examples/widgets/thread_signals/doc/thread_signals.rst b/examples/widgets/thread_signals/doc/thread_signals.rst new file mode 100644 index 00000000..e841ec36 --- /dev/null +++ b/examples/widgets/thread_signals/doc/thread_signals.rst @@ -0,0 +1,9 @@ +Thread Signals Examples +======================= + +This example demonstrates a PySide6 application that uses threads and signals +to perform background tasks. + +.. image:: thread_signals.png + :width: 400 + :alt: thread_signals screenshot diff --git a/examples/widgets/thread_signals/thread_signals.py b/examples/widgets/thread_signals/thread_signals.py index ae8e1ba7..f9101bd8 100644 --- a/examples/widgets/thread_signals/thread_signals.py +++ b/examples/widgets/thread_signals/thread_signals.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtCore import QObject, QThread, Signal, Slot diff --git a/examples/widgets/tools/regularexpression/regularexpression.py b/examples/widgets/tools/regularexpression/regularexpression.py index 261e2619..ac08ddd3 100644 --- a/examples/widgets/tools/regularexpression/regularexpression.py +++ b/examples/widgets/tools/regularexpression/regularexpression.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """ PySide6 port of Qt6 example qtbase/examples/widgets/tools/regularexpression diff --git a/examples/widgets/tools/regularexpression/regularexpressiondialog.py b/examples/widgets/tools/regularexpression/regularexpressiondialog.py index 2d2bb2bb..cdca1a83 100644 --- a/examples/widgets/tools/regularexpression/regularexpressiondialog.py +++ b/examples/widgets/tools/regularexpression/regularexpressiondialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import re import logging @@ -55,13 +56,13 @@ def codeToPattern(code: str) -> str: def createHorizontalSeparator() -> QFrame: result = QFrame() - result.setFrameStyle(QFrame.HLine | QFrame.Sunken) + result.setFrameStyle(QFrame.Shape.HLine | QFrame.Shadow.Sunken) return result def createVerticalSeparator() -> QFrame: result = QFrame() - result.setFrameStyle(QFrame.VLine | QFrame.Sunken) + result.setFrameStyle(QFrame.Shape.VLine | QFrame.Shadow.Sunken) return result @@ -101,7 +102,7 @@ class PatternLineEdit(QLineEdit): def contextMenuEvent(self, event: QContextMenuEvent) -> None: menu = self.createStandardContextMenu() - menu.setAttribute(Qt.WA_DeleteOnClose) + menu.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) menu.addSeparator() self.escapeSelectionAction.setEnabled(self.hasSelectedText()) menu.addAction(self.escapeSelectionAction) @@ -117,8 +118,8 @@ class DisplayLineEdit(QLineEdit): self.setReadOnly(True) self.disablePalette: QPalette = self.palette() self.disablePalette.setBrush( - QPalette.Base, - self.disablePalette.brush(QPalette.Disabled, QPalette.Base), + QPalette.ColorRole.Base, + self.disablePalette.brush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base), ) self.setPalette(self.disablePalette) @@ -128,7 +129,7 @@ class DisplayLineEdit(QLineEdit): self.copyAction.triggered.connect( lambda: QGuiApplication.clipboard().setText(self.text()) ) - self.addAction(self.copyAction, QLineEdit.TrailingPosition) + self.addAction(self.copyAction, QLineEdit.ActionPosition.TrailingPosition) class RegularExpressionDialog(QDialog): @@ -166,7 +167,7 @@ class RegularExpressionDialog(QDialog): def setTextColor(self, widget: QWidget, color: QColor): self.palette: QPalette = widget.palette() - self.palette.setColor(QPalette.Text, color) + self.palette.setColor(QPalette.ColorRole.Text, color) widget.setPalette(self.palette) @Slot() @@ -182,7 +183,7 @@ class RegularExpressionDialog(QDialog): self.setTextColor( self.patternLineEdit, - self.subjectTextEdit.palette().color(QPalette.Text), + self.subjectTextEdit.palette().color(QPalette.ColorRole.Text), ) self.matchDetailsTreeWidget.clear() self.namedGroupsTreeWidget.clear() @@ -213,28 +214,28 @@ class RegularExpressionDialog(QDialog): matchType: QRegularExpression.MatchType = QRegularExpression.MatchType( self.matchTypeComboBox.currentData() ) - patternOptions = QRegularExpression.NoPatternOption - matchOptions = QRegularExpression.NoMatchOption + patternOptions = QRegularExpression.PatternOption.NoPatternOption + matchOptions = QRegularExpression.MatchOption.NoMatchOption if self.anchoredMatchOptionCheckBox.isChecked(): - matchOptions |= QRegularExpression.AnchorAtOffsetMatchOption + matchOptions |= QRegularExpression.MatchOption.AnchorAtOffsetMatchOption if self.dontCheckSubjectStringMatchOptionCheckBox.isChecked(): - matchOptions |= QRegularExpression.DontCheckSubjectStringMatchOption + matchOptions |= QRegularExpression.MatchOption.DontCheckSubjectStringMatchOption if self.caseInsensitiveOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.CaseInsensitiveOption + patternOptions |= QRegularExpression.PatternOption.CaseInsensitiveOption if self.dotMatchesEverythingOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.DotMatchesEverythingOption + patternOptions |= QRegularExpression.PatternOption.DotMatchesEverythingOption if self.multilineOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.MultilineOption + patternOptions |= QRegularExpression.PatternOption.MultilineOption if self.extendedPatternSyntaxOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.ExtendedPatternSyntaxOption + patternOptions |= QRegularExpression.PatternOption.ExtendedPatternSyntaxOption if self.invertedGreedinessOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.InvertedGreedinessOption + patternOptions |= QRegularExpression.PatternOption.InvertedGreedinessOption if self.dontCaptureOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.DontCaptureOption + patternOptions |= QRegularExpression.PatternOption.DontCaptureOption if self.useUnicodePropertiesOptionCheckBox.isChecked(): - patternOptions |= QRegularExpression.UseUnicodePropertiesOption + patternOptions |= QRegularExpression.PatternOption.UseUnicodePropertiesOption self.regularExpression.setPatternOptions(patternOptions) @@ -301,7 +302,7 @@ class RegularExpressionDialog(QDialog): self.horizontalLayout.addWidget(createVerticalSeparator()) self.horizontalLayout.addWidget(self.setupInfoUi()) - self._font = QFontDatabase.systemFont(QFontDatabase.FixedFont) + self._font = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont) self.patternLineEdit.setFont(self._font) self.rawStringLiteralLineEdit.setFont(self._font) self.escapedPatternLineEdit.setFont(self._font) @@ -313,7 +314,7 @@ class RegularExpressionDialog(QDialog): container = QWidget() form_layout = QFormLayout(container) - form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) + form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) form_layout.setContentsMargins(QMargins()) form_layout.addRow(QLabel("

Options

")) @@ -350,15 +351,15 @@ class RegularExpressionDialog(QDialog): form_layout.addRow("Match &offset:", self.offsetSpinBox) self.matchTypeComboBox = QComboBox() - self.matchTypeComboBox.addItem("Normal", QRegularExpression.NormalMatch) + self.matchTypeComboBox.addItem("Normal", QRegularExpression.MatchType.NormalMatch) self.matchTypeComboBox.addItem( "Partial prefer complete", - QRegularExpression.PartialPreferCompleteMatch, + QRegularExpression.MatchType.PartialPreferCompleteMatch, ) self.matchTypeComboBox.addItem( - "Partial prefer first", QRegularExpression.PartialPreferFirstMatch + "Partial prefer first", QRegularExpression.MatchType.PartialPreferFirstMatch ) - self.matchTypeComboBox.addItem("No match", QRegularExpression.NoMatch) + self.matchTypeComboBox.addItem("No match", QRegularExpression.MatchType.NoMatch) form_layout.addRow("Match &type:", self.matchTypeComboBox) self.dontCheckSubjectStringMatchOptionCheckBox = QCheckBox( @@ -381,7 +382,7 @@ class RegularExpressionDialog(QDialog): container = QWidget() form_layout = QFormLayout(container) - form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) + form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) form_layout.setContentsMargins(QMargins()) self.matchInfoLabel = QLabel("

Match information

") @@ -392,7 +393,7 @@ class RegularExpressionDialog(QDialog): self.matchDetailsTreeWidget.setHeaderLabels( ["Match index", "Group index", "Captured string"] ) - self.matchDetailsTreeWidget.setSizeAdjustPolicy(QTreeWidget.AdjustToContents) + self.matchDetailsTreeWidget.setSizeAdjustPolicy(QTreeWidget.SizeAdjustPolicy.AdjustToContents) # noqa: E501 form_layout.addRow("Match details:", self.matchDetailsTreeWidget) form_layout.addRow(createHorizontalSeparator()) @@ -406,7 +407,7 @@ class RegularExpressionDialog(QDialog): self.namedGroupsTreeWidget = QTreeWidget() self.namedGroupsTreeWidget.setHeaderLabels(["Index", "Named group"]) - self.namedGroupsTreeWidget.setSizeAdjustPolicy(QTreeWidget.AdjustToContents) + self.namedGroupsTreeWidget.setSizeAdjustPolicy(QTreeWidget.SizeAdjustPolicy.AdjustToContents) # noqa: E501 self.namedGroupsTreeWidget.setRootIsDecorated(False) form_layout.addRow("Named groups:", self.namedGroupsTreeWidget) @@ -415,7 +416,7 @@ class RegularExpressionDialog(QDialog): def setupTextUi(self): container = QWidget() form_layout = QFormLayout(container) - form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) + form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) form_layout.setContentsMargins(QMargins()) self.regexpAndSubjectLabel = QLabel( diff --git a/examples/widgets/tutorials/addressbook/part1.py b/examples/widgets/tutorials/addressbook/part1.py index 648ddea4..c3785f41 100644 --- a/examples/widgets/tutorials/addressbook/part1.py +++ b/examples/widgets/tutorials/addressbook/part1.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -22,7 +23,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) self.setLayout(main_layout) diff --git a/examples/widgets/tutorials/addressbook/part2.py b/examples/widgets/tutorials/addressbook/part2.py index 3c0eb451..04550871 100644 --- a/examples/widgets/tutorials/addressbook/part2.py +++ b/examples/widgets/tutorials/addressbook/part2.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -12,7 +13,7 @@ from PySide6.QtWidgets import (QApplication, QGridLayout, class SortedDict(dict): - class Iterator(object): + class Iterator: def __init__(self, sorted_dict): self._dict = sorted_dict self._keys = sorted(self._dict.keys()) @@ -67,7 +68,7 @@ class AddressBook(QWidget): self._cancel_button.clicked.connect(self.cancel) button_layout_1 = QVBoxLayout() - button_layout_1.addWidget(self._add_button, Qt.AlignTop) + button_layout_1.addWidget(self._add_button, Qt.AlignmentFlag.AlignTop) button_layout_1.addWidget(self._submit_button) button_layout_1.addWidget(self._cancel_button) button_layout_1.addStretch() @@ -75,7 +76,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) main_layout.addLayout(button_layout_1, 1, 2) @@ -90,7 +91,7 @@ class AddressBook(QWidget): self._address_text.clear() self._name_line.setReadOnly(False) - self._name_line.setFocus(Qt.OtherFocusReason) + self._name_line.setFocus(Qt.FocusReason.OtherFocusReason) self._address_text.setReadOnly(False) self._add_button.setEnabled(False) diff --git a/examples/widgets/tutorials/addressbook/part3.py b/examples/widgets/tutorials/addressbook/part3.py index 611796f5..5083afcf 100644 --- a/examples/widgets/tutorials/addressbook/part3.py +++ b/examples/widgets/tutorials/addressbook/part3.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -12,7 +13,7 @@ from PySide6.QtWidgets import (QApplication, QGridLayout, class SortedDict(dict): - class Iterator(object): + class Iterator: def __init__(self, sorted_dict): self._dict = sorted_dict self._keys = sorted(self._dict.keys()) @@ -73,7 +74,7 @@ class AddressBook(QWidget): self._previous_button.clicked.connect(self.previous) button_layout_1 = QVBoxLayout() - button_layout_1.addWidget(self._add_button, Qt.AlignTop) + button_layout_1.addWidget(self._add_button, Qt.AlignmentFlag.AlignTop) button_layout_1.addWidget(self._submit_button) button_layout_1.addWidget(self._cancel_button) button_layout_1.addStretch() @@ -85,7 +86,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) main_layout.addLayout(button_layout_1, 1, 2) main_layout.addLayout(button_layout_2, 3, 1) @@ -101,7 +102,7 @@ class AddressBook(QWidget): self._address_text.clear() self._name_line.setReadOnly(False) - self._name_line.setFocus(Qt.OtherFocusReason) + self._name_line.setFocus(Qt.FocusReason.OtherFocusReason) self._address_text.setReadOnly(False) self._add_button.setEnabled(False) diff --git a/examples/widgets/tutorials/addressbook/part4.py b/examples/widgets/tutorials/addressbook/part4.py index 95f31d46..de4bbfac 100644 --- a/examples/widgets/tutorials/addressbook/part4.py +++ b/examples/widgets/tutorials/addressbook/part4.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -12,7 +13,7 @@ from PySide6.QtWidgets import (QApplication, QGridLayout, class SortedDict(dict): - class Iterator(object): + class Iterator: def __init__(self, sorted_dict): self._dict = sorted_dict self._keys = sorted(self._dict.keys()) @@ -97,7 +98,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) main_layout.addLayout(button_layout_1, 1, 2) main_layout.addLayout(button_layout_2, 3, 1) @@ -230,7 +231,7 @@ class AddressBook(QWidget): if self._current_mode in (self.AddingMode, self.EditingMode): self._name_line.setReadOnly(False) - self._name_line.setFocus(Qt.OtherFocusReason) + self._name_line.setFocus(Qt.FocusReason.OtherFocusReason) self._address_text.setReadOnly(False) self._add_button.setEnabled(False) diff --git a/examples/widgets/tutorials/addressbook/part5.py b/examples/widgets/tutorials/addressbook/part5.py index 1e9c0586..13538207 100644 --- a/examples/widgets/tutorials/addressbook/part5.py +++ b/examples/widgets/tutorials/addressbook/part5.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -12,7 +13,7 @@ from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout, class SortedDict(dict): - class Iterator(object): + class Iterator: def __init__(self, sorted_dict): self._dict = sorted_dict self._keys = sorted(self._dict.keys()) @@ -103,7 +104,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) main_layout.addLayout(button_layout_1, 1, 2) main_layout.addLayout(button_layout_2, 2, 1) @@ -252,7 +253,7 @@ class AddressBook(QWidget): if self._current_mode in (self.AddingMode, self.EditingMode): self._name_line.setReadOnly(False) - self._name_line.setFocus(Qt.OtherFocusReason) + self._name_line.setFocus(Qt.FocusReason.OtherFocusReason) self._address_text.setReadOnly(False) self._add_button.setEnabled(False) diff --git a/examples/widgets/tutorials/addressbook/part6.py b/examples/widgets/tutorials/addressbook/part6.py index d11298fb..73dcd067 100644 --- a/examples/widgets/tutorials/addressbook/part6.py +++ b/examples/widgets/tutorials/addressbook/part6.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import pickle import sys @@ -13,7 +14,7 @@ from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, class SortedDict(dict): - class Iterator(object): + class Iterator: def __init__(self, sorted_dict): self._dict = sorted_dict self._keys = sorted(self._dict.keys()) @@ -114,7 +115,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) main_layout.addLayout(button_layout_1, 1, 2) main_layout.addLayout(button_layout_2, 2, 1) @@ -263,7 +264,7 @@ class AddressBook(QWidget): if self._current_mode in (self.AddingMode, self.EditingMode): self._name_line.setReadOnly(False) - self._name_line.setFocus(Qt.OtherFocusReason) + self._name_line.setFocus(Qt.FocusReason.OtherFocusReason) self._address_text.setReadOnly(False) self._add_button.setEnabled(False) diff --git a/examples/widgets/tutorials/addressbook/part7.py b/examples/widgets/tutorials/addressbook/part7.py index 3829c003..12e20557 100644 --- a/examples/widgets/tutorials/addressbook/part7.py +++ b/examples/widgets/tutorials/addressbook/part7.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import pickle import sys @@ -13,7 +14,7 @@ from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, class SortedDict(dict): - class Iterator(object): + class Iterator: def __init__(self, sorted_dict): self._dict = sorted_dict self._keys = sorted(self._dict.keys()) @@ -120,7 +121,7 @@ class AddressBook(QWidget): main_layout = QGridLayout() main_layout.addWidget(name_label, 0, 0) main_layout.addWidget(self._name_line, 0, 1) - main_layout.addWidget(address_label, 1, 0, Qt.AlignTop) + main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop) main_layout.addWidget(self._address_text, 1, 1) main_layout.addLayout(button_layout_1, 1, 2) main_layout.addLayout(button_layout_2, 2, 1) @@ -269,7 +270,7 @@ class AddressBook(QWidget): if self._current_mode in (self.AddingMode, self.EditingMode): self._name_line.setReadOnly(False) - self._name_line.setFocus(Qt.OtherFocusReason) + self._name_line.setFocus(Qt.FocusReason.OtherFocusReason) self._address_text.setReadOnly(False) self._add_button.setEnabled(False) @@ -377,7 +378,7 @@ class AddressBook(QWidget): out_file = QFile(file_name) - if not out_file.open(QIODevice.WriteOnly): + if not out_file.open(QIODevice.OpenModeFlag.WriteOnly): QMessageBox.information(self, "Unable to open file", out_file.errorString()) return diff --git a/examples/widgets/tutorials/cannon/t1.py b/examples/widgets/tutorials/cannon/t1.py index 62411ace..92406961 100644 --- a/examples/widgets/tutorials/cannon/t1.py +++ b/examples/widgets/tutorials/cannon/t1.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 1 diff --git a/examples/widgets/tutorials/cannon/t10.py b/examples/widgets/tutorials/cannon/t10.py index 8649bb56..0498eb23 100644 --- a/examples/widgets/tutorials/cannon/t10.py +++ b/examples/widgets/tutorials/cannon/t10.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 10 @@ -20,7 +21,7 @@ class LCDRange(QWidget): super().__init__(parent) lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) @@ -92,8 +93,8 @@ class CannonField(QWidget): def paintEvent(self, event): with QPainter(self) as painter: - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.blue) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.blue) painter.translate(0, self.height()) painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16) @@ -111,7 +112,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("&Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 diff --git a/examples/widgets/tutorials/cannon/t11.py b/examples/widgets/tutorials/cannon/t11.py index fbfd2481..00ac2fc2 100644 --- a/examples/widgets/tutorials/cannon/t11.py +++ b/examples/widgets/tutorials/cannon/t11.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 11 @@ -22,7 +23,7 @@ class LCDRange(QWidget): super().__init__(parent) lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) @@ -127,15 +128,15 @@ class CannonField(QWidget): self.paint_shot(painter) def paint_shot(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.black) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.black) painter.drawRect(self.shot_rect()) barrel_rect = QRect(33, -4, 15, 8) def paint_cannon(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.blue) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.blue) painter.save() painter.translate(0, self.height()) @@ -173,7 +174,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("&Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 @@ -192,7 +193,7 @@ class MyWidget(QWidget): cannon_field.force_changed.connect(force.set_value) shoot = QPushButton("&Shoot") - shoot.setFont(QFont("Times", 18, QFont.Bold)) + shoot.setFont(QFont("Times", 18, QFont.Weight.Bold)) shoot.clicked.connect(cannon_field.shoot) diff --git a/examples/widgets/tutorials/cannon/t12.py b/examples/widgets/tutorials/cannon/t12.py index 749c2468..4960797b 100644 --- a/examples/widgets/tutorials/cannon/t12.py +++ b/examples/widgets/tutorials/cannon/t12.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 12 @@ -33,11 +34,11 @@ class LCDRange(QWidget): def init(self): lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) self.label = QLabel() - self.label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) + self.label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) self.slider.valueChanged.connect(lcd.display) self.slider.valueChanged.connect(self.value_changed) @@ -168,20 +169,20 @@ class CannonField(QWidget): self.paint_target(painter) def paint_shot(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.black) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.black) painter.drawRect(self.shot_rect()) def paint_target(self, painter): - painter.setPen(Qt.black) - painter.setBrush(Qt.red) + painter.setPen(Qt.GlobalColor.black) + painter.setBrush(Qt.GlobalColor.red) painter.drawRect(self.target_rect()) barrel_rect = QRect(33, -4, 15, 8) def paint_cannon(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.blue) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.blue) painter.save() painter.translate(0, self.height()) @@ -224,7 +225,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("&Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 @@ -243,7 +244,7 @@ class MyWidget(QWidget): cannon_field.force_changed.connect(force.set_value) shoot = QPushButton("&Shoot") - shoot.setFont(QFont("Times", 18, QFont.Bold)) + shoot.setFont(QFont("Times", 18, QFont.Weight.Bold)) shoot.clicked.connect(cannon_field.shoot) diff --git a/examples/widgets/tutorials/cannon/t13.py b/examples/widgets/tutorials/cannon/t13.py index f9a771d1..c9b8bd5d 100644 --- a/examples/widgets/tutorials/cannon/t13.py +++ b/examples/widgets/tutorials/cannon/t13.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 13 @@ -34,12 +35,12 @@ class LCDRange(QWidget): def init(self): lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) self.label = QLabel() - self.label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) - self.label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + self.label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) + self.label.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) self.slider.valueChanged.connect(lcd.display) self.slider.valueChanged.connect(self.value_changed) @@ -184,9 +185,9 @@ class CannonField(QWidget): def paintEvent(self, event): with QPainter(self) as painter: if self._game_ended: - painter.setPen(Qt.black) - painter.setFont(QFont("Courier", 48, QFont.Bold)) - painter.drawText(self.rect(), Qt.AlignCenter, "Game Over") + painter.setPen(Qt.GlobalColor.black) + painter.setFont(QFont("Courier", 48, QFont.Weight.Bold)) + painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, "Game Over") self.paint_cannon(painter) if self.is_shooting(): @@ -195,20 +196,20 @@ class CannonField(QWidget): self.paint_target(painter) def paint_shot(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.black) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.black) painter.drawRect(self.shot_rect()) def paint_target(self, painter): - painter.setPen(Qt.black) - painter.setBrush(Qt.red) + painter.setPen(Qt.GlobalColor.black) + painter.setBrush(Qt.GlobalColor.red) painter.drawRect(self.target_rect()) barrel_rect = QRect(33, -4, 15, 8) def paint_cannon(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.blue) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.blue) painter.save() painter.translate(0, self.height()) @@ -257,7 +258,7 @@ class GameBoard(QWidget): super().__init__(parent) quit = QPushButton("&Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 @@ -279,13 +280,13 @@ class GameBoard(QWidget): self._cannon_field.missed.connect(self.missed) shoot = QPushButton("&Shoot") - shoot.setFont(QFont("Times", 18, QFont.Bold)) + shoot.setFont(QFont("Times", 18, QFont.Weight.Bold)) shoot.clicked.connect(self.fire) self._cannon_field.can_shoot.connect(shoot.setEnabled) restart = QPushButton("&New Game") - restart.setFont(QFont("Times", 18, QFont.Bold)) + restart.setFont(QFont("Times", 18, QFont.Weight.Bold)) restart.clicked.connect(self.new_game) diff --git a/examples/widgets/tutorials/cannon/t14.py b/examples/widgets/tutorials/cannon/t14.py index 3c94408f..00674189 100644 --- a/examples/widgets/tutorials/cannon/t14.py +++ b/examples/widgets/tutorials/cannon/t14.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 14 @@ -35,12 +36,12 @@ class LCDRange(QWidget): def init(self): lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) self.label = QLabel() - self.label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) - self.label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + self.label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) + self.label.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) self.slider.valueChanged.connect(lcd.display) self.slider.valueChanged.connect(self.value_changed) @@ -185,7 +186,7 @@ class CannonField(QWidget): self.update(region) def mousePressEvent(self, event): - if event.button() != Qt.LeftButton: + if event.button() != Qt.MouseButton.LeftButton: return if self.barrel_hit(event.position().toPoint()): self._barrel_pressed = True @@ -202,15 +203,15 @@ class CannonField(QWidget): self.set_angle(round(rad * 180 / math.pi)) def mouseReleaseEvent(self, event): - if event.button() == Qt.LeftButton: + if event.button() == Qt.MouseButton.LeftButton: self._barrel_pressed = False def paintEvent(self, event): with QPainter(self) as painter: if self._game_ended: - painter.setPen(Qt.black) - painter.setFont(QFont("Courier", 48, QFont.Bold)) - painter.drawText(self.rect(), Qt.AlignCenter, "Game Over") + painter.setPen(Qt.GlobalColor.black) + painter.setFont(QFont("Courier", 48, QFont.Weight.Bold)) + painter.drawText(self.rect(), Qt.AlignmentFlag.AlignCenter, "Game Over") self.paint_cannon(painter) self.paint_barrier(painter) @@ -220,25 +221,25 @@ class CannonField(QWidget): self.paint_target(painter) def paint_shot(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.black) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.black) painter.drawRect(self.shot_rect()) def paint_target(self, painter): - painter.setPen(Qt.black) - painter.setBrush(Qt.red) + painter.setPen(Qt.GlobalColor.black) + painter.setBrush(Qt.GlobalColor.red) painter.drawRect(self.target_rect()) def paint_barrier(self, painter): - painter.setPen(Qt.black) - painter.setBrush(Qt.yellow) + painter.setPen(Qt.GlobalColor.black) + painter.setBrush(Qt.GlobalColor.yellow) painter.drawRect(self.barrier_rect()) barrel_rect = QRect(33, -4, 15, 8) def paint_cannon(self, painter): - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.blue) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.blue) painter.save() painter.translate(0, self.height()) @@ -300,7 +301,7 @@ class GameBoard(QWidget): super().__init__(parent) quit = QPushButton("&Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 @@ -311,7 +312,7 @@ class GameBoard(QWidget): force.set_range(10, 50) cannon_box = QFrame() - cannon_box.setFrameStyle(QFrame.WinPanel | QFrame.Sunken) + cannon_box.setFrameStyle(QFrame.Shape.WinPanel | QFrame.Shadow.Sunken) self._cannon_field = CannonField() @@ -325,13 +326,13 @@ class GameBoard(QWidget): self._cannon_field.missed.connect(self.missed) shoot = QPushButton("&Shoot") - shoot.setFont(QFont("Times", 18, QFont.Bold)) + shoot.setFont(QFont("Times", 18, QFont.Weight.Bold)) shoot.clicked.connect(self.fire) self._cannon_field.can_shoot.connect(shoot.setEnabled) restart = QPushButton("&New Game") - restart.setFont(QFont("Times", 18, QFont.Bold)) + restart.setFont(QFont("Times", 18, QFont.Weight.Bold)) restart.clicked.connect(self.new_game) @@ -340,9 +341,9 @@ class GameBoard(QWidget): hits_label = QLabel("HITS") shots_left_label = QLabel("SHOTS LEFT") - QShortcut(QKeySequence(Qt.Key_Enter), self, self.fire) - QShortcut(QKeySequence(Qt.Key_Return), self, self.fire) - QShortcut(QKeySequence(Qt.CTRL | Qt.Key_Q), self, self.close) + QShortcut(QKeySequence(Qt.Key.Key_Enter), self, self.fire) + QShortcut(QKeySequence(Qt.Key.Key_Return), self, self.fire) + QShortcut(QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Q), self, self.close) top_layout = QHBoxLayout() top_layout.addWidget(shoot) diff --git a/examples/widgets/tutorials/cannon/t2.py b/examples/widgets/tutorials/cannon/t2.py index d3adba39..2b441391 100644 --- a/examples/widgets/tutorials/cannon/t2.py +++ b/examples/widgets/tutorials/cannon/t2.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 2 @@ -15,7 +16,7 @@ if __name__ == '__main__': quit = QPushButton("Quit") quit.resize(75, 30) - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(app.quit) diff --git a/examples/widgets/tutorials/cannon/t3.py b/examples/widgets/tutorials/cannon/t3.py index 13bd8f73..9befa772 100644 --- a/examples/widgets/tutorials/cannon/t3.py +++ b/examples/widgets/tutorials/cannon/t3.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 3 @@ -17,7 +18,7 @@ if __name__ == '__main__': window.resize(200, 120) quit = QPushButton("Quit", window) - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.setGeometry(10, 40, 180, 40) quit.clicked.connect(app.quit) diff --git a/examples/widgets/tutorials/cannon/t4.py b/examples/widgets/tutorials/cannon/t4.py index 37a2dc9d..9f11de35 100644 --- a/examples/widgets/tutorials/cannon/t4.py +++ b/examples/widgets/tutorials/cannon/t4.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 4 @@ -18,7 +19,7 @@ class MyWidget(QWidget): self.quit = QPushButton("Quit", self) self.quit.setGeometry(62, 40, 75, 30) - self.quit.setFont(QFont("Times", 18, QFont.Bold)) + self.quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) self.quit.clicked.connect(qApp.quit) # noqa: F821 diff --git a/examples/widgets/tutorials/cannon/t5.py b/examples/widgets/tutorials/cannon/t5.py index ed5d085f..12f4847c 100644 --- a/examples/widgets/tutorials/cannon/t5.py +++ b/examples/widgets/tutorials/cannon/t5.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 5 @@ -17,11 +18,11 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) lcd = QLCDNumber(2) - slider = QSlider(Qt.Horizontal) + slider = QSlider(Qt.Orientation.Horizontal) slider.setRange(0, 99) slider.setValue(0) diff --git a/examples/widgets/tutorials/cannon/t6.py b/examples/widgets/tutorials/cannon/t6.py index ea2e044e..04db4b51 100644 --- a/examples/widgets/tutorials/cannon/t6.py +++ b/examples/widgets/tutorials/cannon/t6.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 6 @@ -17,7 +18,7 @@ class LCDRange(QWidget): super().__init__(parent) lcd = QLCDNumber(2) - slider = QSlider(Qt.Horizontal) + slider = QSlider(Qt.Orientation.Horizontal) slider.setRange(0, 99) slider.setValue(0) slider.valueChanged.connect(lcd.display) @@ -32,7 +33,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 layout = QVBoxLayout(self) diff --git a/examples/widgets/tutorials/cannon/t7.py b/examples/widgets/tutorials/cannon/t7.py index 1175107b..2ef051e2 100644 --- a/examples/widgets/tutorials/cannon/t7.py +++ b/examples/widgets/tutorials/cannon/t7.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 7 @@ -21,7 +22,7 @@ class LCDRange(QWidget): lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) @@ -45,7 +46,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 previous_range = None diff --git a/examples/widgets/tutorials/cannon/t8.py b/examples/widgets/tutorials/cannon/t8.py index 9bb5516b..560d73cf 100644 --- a/examples/widgets/tutorials/cannon/t8.py +++ b/examples/widgets/tutorials/cannon/t8.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 8 @@ -20,7 +21,7 @@ class LCDRange(QWidget): super().__init__(parent) lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) @@ -86,7 +87,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 diff --git a/examples/widgets/tutorials/cannon/t9.py b/examples/widgets/tutorials/cannon/t9.py index 7cdda4e7..25d2554f 100644 --- a/examples/widgets/tutorials/cannon/t9.py +++ b/examples/widgets/tutorials/cannon/t9.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations # PySide6 tutorial 9 @@ -20,7 +21,7 @@ class LCDRange(QWidget): super().__init__(parent) lcd = QLCDNumber(2) - self.slider = QSlider(Qt.Horizontal) + self.slider = QSlider(Qt.Orientation.Horizontal) self.slider.setRange(0, 99) self.slider.setValue(0) @@ -78,8 +79,8 @@ class CannonField(QWidget): def paintEvent(self, event): with QPainter(self) as painter: - painter.setPen(Qt.NoPen) - painter.setBrush(Qt.blue) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(Qt.GlobalColor.blue) painter.translate(0, self.rect().height()) painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16) @@ -92,7 +93,7 @@ class MyWidget(QWidget): super().__init__(parent) quit = QPushButton("Quit") - quit.setFont(QFont("Times", 18, QFont.Bold)) + quit.setFont(QFont("Times", 18, QFont.Weight.Bold)) quit.clicked.connect(qApp.quit) # noqa: F821 diff --git a/examples/widgets/tutorials/modelview/1_readonly.py b/examples/widgets/tutorials/modelview/1_readonly.py index 9dc92326..92402eb2 100644 --- a/examples/widgets/tutorials/modelview/1_readonly.py +++ b/examples/widgets/tutorials/modelview/1_readonly.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -20,8 +21,8 @@ class MyModel(QAbstractTableModel): def columnCount(self, parent=None): return 3 - def data(self, index, role=Qt.DisplayRole): - if role == Qt.DisplayRole: + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole: row = index.row() + 1 column = index.column() + 1 return f"Row{row}, Column{column}" diff --git a/examples/widgets/tutorials/modelview/2_formatting.py b/examples/widgets/tutorials/modelview/2_formatting.py index f39ec462..6fef1d59 100644 --- a/examples/widgets/tutorials/modelview/2_formatting.py +++ b/examples/widgets/tutorials/modelview/2_formatting.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -21,36 +22,36 @@ class MyModel(QAbstractTableModel): return 3 #! [1] - def data(self, index, role=Qt.DisplayRole): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): row = index.row() col = index.column() # generate a log message when this method gets called print(f"row {row}, col{col}, role {role}") - if role == Qt.DisplayRole: + if role == Qt.ItemDataRole.DisplayRole: if row == 0 and col == 1: return "<--left" if row == 1 and col == 1: return "right-->" return f"Row{row}, Column{col + 1}" - elif role == Qt.FontRole: + elif role == Qt.ItemDataRole.FontRole: if row == 0 and col == 0: # change font only for cell(0,0) bold_font = QFont() bold_font.setBold(True) return bold_font - elif role == Qt.BackgroundRole: + elif role == Qt.ItemDataRole.BackgroundRole: if row == 1 and col == 2: # change background only for cell(1,2) - return QBrush(Qt.red) + return QBrush(Qt.GlobalColor.red) - elif role == Qt.TextAlignmentRole: + elif role == Qt.ItemDataRole.TextAlignmentRole: if row == 1 and col == 1: # change text alignment only for cell(1,1) - return Qt.AlignRight | Qt.AlignVCenter + return Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter - elif role == Qt.CheckStateRole: + elif role == Qt.ItemDataRole.CheckStateRole: if row == 1 and col == 0: # add a checkbox to cell(1,0) - return Qt.Checked + return Qt.CheckState.Checked return None #! [1] diff --git a/examples/widgets/tutorials/modelview/3_changingmodel.py b/examples/widgets/tutorials/modelview/3_changingmodel.py index 2148ec5d..25194022 100644 --- a/examples/widgets/tutorials/modelview/3_changingmodel.py +++ b/examples/widgets/tutorials/modelview/3_changingmodel.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -26,10 +27,10 @@ class MyModel(QAbstractTableModel): return 3 #! [2] - def data(self, index, role=Qt.DisplayRole): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): row = index.row() col = index.column() - if role == Qt.DisplayRole and row == 0 and col == 0: + if role == Qt.ItemDataRole.DisplayRole and row == 0 and col == 0: return QTime.currentTime().toString() return None #! [2] @@ -40,7 +41,7 @@ class MyModel(QAbstractTableModel): # we identify the top left cell top_left = self.createIndex(0, 0) # emit a signal to make the view reread identified data - self.dataChanged.emit(top_left, top_left, [Qt.DisplayRole]) + self.dataChanged.emit(top_left, top_left, [Qt.ItemDataRole.DisplayRole]) #! [3] diff --git a/examples/widgets/tutorials/modelview/4_headers.py b/examples/widgets/tutorials/modelview/4_headers.py index 3feef17b..12815abf 100644 --- a/examples/widgets/tutorials/modelview/4_headers.py +++ b/examples/widgets/tutorials/modelview/4_headers.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -19,8 +20,8 @@ class MyModel(QAbstractTableModel): def columnCount(self, parent=None): return 3 - def data(self, index, role=Qt.DisplayRole): - if role == Qt.DisplayRole: + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole: row = index.row() + 1 column = index.column() + 1 return f"Row{row}, Column{column}" @@ -28,7 +29,7 @@ class MyModel(QAbstractTableModel): #! [1] def headerData(self, section, orientation, role): - if role == Qt.DisplayRole and orientation == Qt.Horizontal: + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: return ["first", "second", "third"][section] return None #! [1] diff --git a/examples/widgets/tutorials/modelview/5_edit.py b/examples/widgets/tutorials/modelview/5_edit.py index 1a4481fc..ae57634b 100644 --- a/examples/widgets/tutorials/modelview/5_edit.py +++ b/examples/widgets/tutorials/modelview/5_edit.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from itertools import chain @@ -28,14 +29,14 @@ class MyModel(QAbstractTableModel): def columnCount(self, parent=None): return COLS - def data(self, index, role=Qt.DisplayRole): - if role == Qt.DisplayRole and self.checkIndex(index): + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole and self.checkIndex(index): return self._grid_data[index.row()][index.column()] return None #! [1] def setData(self, index, value, role): - if role != Qt.EditRole or not self.checkIndex(index): + if role != Qt.ItemDataRole.EditRole or not self.checkIndex(index): return False # save value from editor to member m_gridData self._grid_data[index.row()][index.column()] = value diff --git a/examples/widgets/tutorials/modelview/6_treeview.py b/examples/widgets/tutorials/modelview/6_treeview.py index cac3c6d5..d0259f48 100644 --- a/examples/widgets/tutorials/modelview/6_treeview.py +++ b/examples/widgets/tutorials/modelview/6_treeview.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgets/tutorials/modelview/7_selections.py b/examples/widgets/tutorials/modelview/7_selections.py index c879d8f6..180fd9b1 100644 --- a/examples/widgets/tutorials/modelview/7_selections.py +++ b/examples/widgets/tutorials/modelview/7_selections.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -53,7 +54,7 @@ class MainWindow(QMainWindow): def selection_changed_slot(self, new_selection, old_selection): # get the text of the selected item index = self._tree_view.selectionModel().currentIndex() - selected_text = index.data(Qt.DisplayRole) + selected_text = index.data(Qt.ItemDataRole.DisplayRole) # find out the hierarchy level of the selected item hierarchy_level = 1 seek_root = index diff --git a/examples/widgets/widgets/charactermap/characterwidget.py b/examples/widgets/widgets/charactermap/characterwidget.py index 0f01f968..91c04081 100644 --- a/examples/widgets/widgets/charactermap/characterwidget.py +++ b/examples/widgets/widgets/charactermap/characterwidget.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from textwrap import dedent @@ -83,7 +84,7 @@ class CharacterWidget(QWidget): QToolTip.showText(event.globalPosition().toPoint(), text, self) def mousePressEvent(self, event): - if event.button() == Qt.LeftButton: + if event.button() == Qt.MouseButton.LeftButton: self._last_key = self._unicode_from_pos(event.position().toPoint()) if self._last_key != -1: c = chr(self._last_key) @@ -98,14 +99,14 @@ class CharacterWidget(QWidget): def render(self, event, painter): painter = QPainter(self) - painter.fillRect(event.rect(), QBrush(Qt.white)) + painter.fillRect(event.rect(), QBrush(Qt.GlobalColor.white)) painter.setFont(self._display_font) redraw_rect = event.rect() begin_row = int(redraw_rect.top() / self._square_size) end_row = int(redraw_rect.bottom() / self._square_size) begin_column = int(redraw_rect.left() / self._square_size) end_column = int(redraw_rect.right() / self._square_size) - painter.setPen(QPen(Qt.gray)) + painter.setPen(QPen(Qt.GlobalColor.gray)) for row in range(begin_row, end_row + 1): for column in range(begin_column, end_column + 1): x = int(column * self._square_size) @@ -113,7 +114,7 @@ class CharacterWidget(QWidget): painter.drawRect(x, y, self._square_size, self._square_size) font_metrics = QFontMetrics(self._display_font) - painter.setPen(QPen(Qt.black)) + painter.setPen(QPen(Qt.GlobalColor.black)) for row in range(begin_row, end_row + 1): for column in range(begin_column, end_column + 1): key = int(row * COLUMNS + column) @@ -124,7 +125,8 @@ class CharacterWidget(QWidget): if key == self._last_key: painter.fillRect(column * self._square_size + 1, row * self._square_size + 1, - self._square_size, self._square_size, QBrush(Qt.red)) + self._square_size, self._square_size, + QBrush(Qt.GlobalColor.red)) text = chr(key) painter.drawText(column * self._square_size + (self._square_size / 2) diff --git a/examples/widgets/widgets/charactermap/fontinfodialog.py b/examples/widgets/widgets/charactermap/fontinfodialog.py index aa874884..9763008e 100644 --- a/examples/widgets/widgets/charactermap/fontinfodialog.py +++ b/examples/widgets/widgets/charactermap/fontinfodialog.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt, qVersion, qFuzzyCompare from PySide6.QtGui import QGuiApplication, QFontDatabase @@ -23,7 +24,7 @@ class FontInfoDialog(QDialog): text_edit.setReadOnly(True) text_edit.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) main_layout.addWidget(text_edit) - button_box = QDialogButtonBox(QDialogButtonBox.Close, self) + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, self) button_box.rejected.connect(self.reject) main_layout.addWidget(button_box) diff --git a/examples/widgets/widgets/charactermap/main.py b/examples/widgets/widgets/charactermap/main.py index e84a1d8a..8feded90 100644 --- a/examples/widgets/widgets/charactermap/main.py +++ b/examples/widgets/widgets/charactermap/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys diff --git a/examples/widgets/widgets/charactermap/mainwindow.py b/examples/widgets/widgets/charactermap/mainwindow.py index d79285de..88d738f8 100644 --- a/examples/widgets/widgets/charactermap/mainwindow.py +++ b/examples/widgets/widgets/charactermap/mainwindow.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations from PySide6.QtCore import Qt, QSignalBlocker, Slot from PySide6.QtGui import QGuiApplication, QClipboard, QFont, QFontDatabase @@ -36,10 +37,10 @@ class MainWindow(QMainWindow): self._filter_label = QLabel("Filter:") self._filter_combo = QComboBox() - self._filter_combo.addItem("All", int(QFontComboBox.AllFonts.value)) - self._filter_combo.addItem("Scalable", int(QFontComboBox.ScalableFonts.value)) - self._filter_combo.addItem("Monospaced", int(QFontComboBox.MonospacedFonts.value)) - self._filter_combo.addItem("Proportional", int(QFontComboBox.ProportionalFonts.value)) + self._filter_combo.addItem("All", QFontComboBox.FontFilter.AllFonts) + self._filter_combo.addItem("Scalable", QFontComboBox.FontFilter.ScalableFonts) + self._filter_combo.addItem("Monospaced", QFontComboBox.FontFilter.MonospacedFonts) + self._filter_combo.addItem("Proportional", QFontComboBox.FontFilter.ProportionalFonts) self._filter_combo.setCurrentIndex(0) self._filter_combo.currentIndexChanged.connect(self.filter_changed) @@ -116,7 +117,7 @@ class MainWindow(QMainWindow): @Slot(int) def filter_changed(self, f): - filter = QFontComboBox.FontFilter(self._filter_combo.itemData(f)) + filter = self._filter_combo.itemData(f) self._font_combo.setFontFilters(filter) count = self._font_combo.count() self.statusBar().showMessage(f"{count} font(s) found") @@ -162,6 +163,6 @@ class MainWindow(QMainWindow): screen_geometry = self.screen().geometry() dialog = FontInfoDialog(self) dialog.setWindowTitle("Fonts") - dialog.setAttribute(Qt.WA_DeleteOnClose) + dialog.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) dialog.resize(screen_geometry.width() / 4, screen_geometry.height() / 4) dialog.show() diff --git a/examples/widgets/widgets/digitalclock/digitalclock.py b/examples/widgets/widgets/digitalclock/digitalclock.py index f0030b35..fba0a437 100644 --- a/examples/widgets/widgets/digitalclock/digitalclock.py +++ b/examples/widgets/widgets/digitalclock/digitalclock.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys from PySide6.QtCore import QTime, QTimer, Slot @@ -9,7 +10,7 @@ from PySide6.QtWidgets import QApplication, QLCDNumber class DigitalClock(QLCDNumber): def __init__(self, parent=None): super().__init__(parent) - self.setSegmentStyle(QLCDNumber.Filled) + self.setSegmentStyle(QLCDNumber.SegmentStyle.Filled) self.setDigitCount(8) self.timer = QTimer(self) diff --git a/examples/widgets/widgets/tetrix/tetrix.py b/examples/widgets/widgets/tetrix/tetrix.py index b5df2aa3..4428b332 100644 --- a/examples/widgets/widgets/tetrix/tetrix.py +++ b/examples/widgets/widgets/tetrix/tetrix.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/widgets/tetrix example from Qt v5.x""" @@ -32,23 +33,23 @@ class TetrixWindow(QWidget): self.board = TetrixBoard() next_piece_label = QLabel() - next_piece_label.setFrameStyle(QFrame.Box | QFrame.Raised) - next_piece_label.setAlignment(Qt.AlignCenter) + next_piece_label.setFrameStyle(QFrame.Shape.Box | QFrame.Shadow.Raised) + next_piece_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.board.set_next_piece_label(next_piece_label) score_lcd = QLCDNumber(5) - score_lcd.setSegmentStyle(QLCDNumber.Filled) + score_lcd.setSegmentStyle(QLCDNumber.SegmentStyle.Filled) level_lcd = QLCDNumber(2) - level_lcd.setSegmentStyle(QLCDNumber.Filled) + level_lcd.setSegmentStyle(QLCDNumber.SegmentStyle.Filled) lines_lcd = QLCDNumber(5) - lines_lcd.setSegmentStyle(QLCDNumber.Filled) + lines_lcd.setSegmentStyle(QLCDNumber.SegmentStyle.Filled) start_button = QPushButton("&Start") - start_button.setFocusPolicy(Qt.NoFocus) + start_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) quit_button = QPushButton("&Quit") - quit_button.setFocusPolicy(Qt.NoFocus) + quit_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) pause_button = QPushButton("&Pause") - pause_button.setFocusPolicy(Qt.NoFocus) + pause_button.setFocusPolicy(Qt.FocusPolicy.NoFocus) start_button.clicked.connect(self.board.start) pause_button.clicked.connect(self.board.pause) @@ -76,7 +77,7 @@ class TetrixWindow(QWidget): def create_label(self, text): lbl = QLabel(text) - lbl.setAlignment(Qt.AlignHCenter | Qt.AlignBottom) + lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignBottom) return lbl @@ -106,8 +107,8 @@ class TetrixBoard(QFrame): self.level = 0 self.board = None - self.setFrameStyle(QFrame.Panel | QFrame.Sunken) - self.setFocusPolicy(Qt.StrongFocus) + self.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self._is_started = False self._is_paused = False self.clear_board() @@ -180,7 +181,7 @@ class TetrixBoard(QFrame): rect = self.contentsRect() if self._is_paused: - painter.drawText(rect, Qt.AlignCenter, "Pause") + painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "Pause") return board_top = rect.bottom() - TetrixBoard.board_height * self.square_height() @@ -208,17 +209,17 @@ class TetrixBoard(QFrame): return key = event.key() - if key == Qt.Key_Left: + if key == Qt.Key.Key_Left: self.try_move(self._cur_piece, self._cur_x - 1, self._cur_y) - elif key == Qt.Key_Right: + elif key == Qt.Key.Key_Right: self.try_move(self._cur_piece, self._cur_x + 1, self._cur_y) - elif key == Qt.Key_Down: + elif key == Qt.Key.Key_Down: self.try_move(self._cur_piece.rotated_right(), self._cur_x, self._cur_y) - elif key == Qt.Key_Up: + elif key == Qt.Key.Key_Up: self.try_move(self._cur_piece.rotated_left(), self._cur_x, self._cur_y) - elif key == Qt.Key_Space: + elif key == Qt.Key.Key_Space: self.drop_down() - elif key == Qt.Key_D: + elif key == Qt.Key.Key_D: self.one_line_down() else: super(TetrixBoard, self).keyPressEvent(event) @@ -367,7 +368,7 @@ class TetrixBoard(QFrame): y + self.square_height() - 1, x + self.square_width() - 1, y + 1) -class TetrixPiece(object): +class TetrixPiece: coords_table = ( ((0, 0), (0, 0), (0, 0), (0, 0)), ((0, -1), (0, 0), (-1, 0), (-1, 1)), diff --git a/examples/widgets/widgetsgallery/main.py b/examples/widgets/widgetsgallery/main.py index e40077a3..ec2b5d65 100644 --- a/examples/widgets/widgetsgallery/main.py +++ b/examples/widgets/widgetsgallery/main.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the widgets/gallery example from Qt v5.15""" diff --git a/examples/widgets/widgetsgallery/widgetgallery.py b/examples/widgets/widgetsgallery/widgetgallery.py index d43ab26a..972ffbaa 100644 --- a/examples/widgets/widgetsgallery/widgetgallery.py +++ b/examples/widgets/widgetsgallery/widgetgallery.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations import sys @@ -153,7 +154,7 @@ class WidgetGallery(QDialog): disable_widgets_checkbox.toggled.connect(simple_input_widgets_groupbox.setDisabled) help_shortcut = QShortcut(self) - help_shortcut.setKey(QKeySequence.HelpContents) + help_shortcut.setKey(QKeySequence.StandardKey.HelpContents) help_shortcut.activated.connect(self.help_on_current_widget) top_layout = QHBoxLayout() @@ -164,8 +165,8 @@ class WidgetGallery(QDialog): top_layout.addStretch(1) top_layout.addWidget(disable_widgets_checkbox) - dialog_buttonbox = QDialogButtonBox(QDialogButtonBox.Help - | QDialogButtonBox.Close) + dialog_buttonbox = QDialogButtonBox(QDialogButtonBox.StandardButton.Help + | QDialogButtonBox.StandardButton.Close) init_widget(dialog_buttonbox, "dialogButtonBox") dialog_buttonbox.helpRequested.connect(launch_module_help) dialog_buttonbox.rejected.connect(self.reject) @@ -223,7 +224,7 @@ class WidgetGallery(QDialog): init_widget(menu_toolbutton, "menuButton") menu_toolbutton.setText("Menu Button") tool_menu = QMenu(menu_toolbutton) - menu_toolbutton.setPopupMode(QToolButton.InstantPopup) + menu_toolbutton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) tool_menu.addAction("Option") tool_menu.addSeparator() action = tool_menu.addAction("Checkable Option") @@ -256,7 +257,7 @@ class WidgetGallery(QDialog): checkbox = QCheckBox("Tri-state check box") init_widget(checkbox, "checkBox") checkbox.setTristate(True) - checkbox.setCheckState(Qt.PartiallyChecked) + checkbox.setCheckState(Qt.CheckState.PartiallyChecked) checkable_layout = QVBoxLayout() checkable_layout.addWidget(radiobutton_1) @@ -299,7 +300,7 @@ class WidgetGallery(QDialog): def create_itemview_tabwidget(self): result = QTabWidget() init_widget(result, "bottomLeftTabWidget") - result.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Ignored) + result.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Ignored) tree_view = QTreeView() init_widget(tree_view, "treeView") @@ -324,7 +325,7 @@ class WidgetGallery(QDialog): icon_mode_listview = QListView() init_widget(icon_mode_listview, "iconModeListView") - icon_mode_listview.setViewMode(QListView.IconMode) + icon_mode_listview.setViewMode(QListView.ViewMode.IconMode) icon_mode_listview.setModel(list_model) result.addTab(embed_into_hbox_layout(tree_view), "Tree View") @@ -343,7 +344,7 @@ class WidgetGallery(QDialog): lineedit = QLineEdit("s3cRe7") init_widget(lineedit, "lineEdit") lineedit.setClearButtonEnabled(True) - lineedit.setEchoMode(QLineEdit.Password) + lineedit.setEchoMode(QLineEdit.EchoMode.Password) spin_box = QSpinBox() init_widget(spin_box, "spinBox") @@ -355,12 +356,12 @@ class WidgetGallery(QDialog): slider = QSlider() init_widget(slider, "slider") - slider.setOrientation(Qt.Horizontal) + slider.setOrientation(Qt.Orientation.Horizontal) slider.setValue(40) scrollbar = QScrollBar() init_widget(scrollbar, "scrollBar") - scrollbar.setOrientation(Qt.Horizontal) + scrollbar.setOrientation(Qt.Orientation.Horizontal) scrollbar.setValue(60) dial = QDial() diff --git a/examples/xml/dombookmarks/dombookmarks.py b/examples/xml/dombookmarks/dombookmarks.py index a35aeb0f..f5afdeef 100644 --- a/examples/xml/dombookmarks/dombookmarks.py +++ b/examples/xml/dombookmarks/dombookmarks.py @@ -1,6 +1,7 @@ # Copyright (C) 2013 Riverbank Computing Limited. # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations """PySide6 port of the xml/dombookmarks example from Qt v5.x""" @@ -37,7 +38,7 @@ class MainWindow(QMainWindow): return in_file = QFile(file_name) - if not in_file.open(QFile.ReadOnly | QFile.Text): + if not in_file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): reason = in_file.errorString() QMessageBox.warning(self, "DOM Bookmarks", f"Cannot read file {file_name}:\n{reason}.") @@ -55,7 +56,7 @@ class MainWindow(QMainWindow): return out_file = QFile(file_name) - if not out_file.open(QFile.WriteOnly | QFile.Text): + if not out_file.open(QFile.OpenModeFlag.WriteOnly | QFile.Text): reason = out_file.errorString() QMessageBox.warning(self, "DOM Bookmarks", f"Cannot write file {file_name}:\n{reason}.") @@ -72,14 +73,14 @@ class MainWindow(QMainWindow): def create_menus(self): self._file_menu = self.menuBar().addMenu("&File") self._file_menu.addAction(QAction("&Open...", self, - shortcut=QKeySequence( - Qt.CTRL | Qt.Key_O), triggered=self.open)) + shortcut=QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_O), + triggered=self.open)) self._file_menu.addAction(QAction("&Save As...", self, - shortcut=QKeySequence( - Qt.CTRL | Qt.Key_S), triggered=self.save_as)) + shortcut=QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_S), + triggered=self.save_as)) self._file_menu.addAction(QAction("E&xit", self, - shortcut=QKeySequence( - Qt.CTRL | Qt.Key_Q), triggered=self.close)) + shortcut=QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Q), + triggered=self.close)) self.menuBar().addSeparator() @@ -92,7 +93,7 @@ class XbelTree(QTreeWidget): def __init__(self, parent=None): super().__init__(parent) - self.header().setSectionResizeMode(QHeaderView.Stretch) + self.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) self.setHeaderLabels(("Title", "Location")) self._dom_document = QDomDocument() @@ -102,11 +103,12 @@ class XbelTree(QTreeWidget): self._folder_icon = QIcon() self._bookmark_icon = QIcon() - self._folder_icon.addPixmap(self.style().standardPixmap(QStyle.SP_DirClosedIcon), - QIcon.Normal, QIcon.Off) - self._folder_icon.addPixmap(self.style().standardPixmap(QStyle.SP_DirOpenIcon), - QIcon.Normal, QIcon.On) - self._bookmark_icon.addPixmap(self.style().standardPixmap(QStyle.SP_FileIcon)) + style = self.style() + self._folder_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_DirClosedIcon), + QIcon.Mode.Normal, QIcon.State.Off) + self._folder_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_DirOpenIcon), + QIcon.Mode.Normal, QIcon.State.On) + self._bookmark_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_FileIcon)) def read(self, device): ok, errorStr, errorLine, errorColumn = self._dom_document.setContent(device, True) @@ -172,7 +174,7 @@ class XbelTree(QTreeWidget): if not title: title = "Folder" - item.setFlags(item.flags() | Qt.ItemIsEditable) + item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) item.setIcon(0, self._folder_icon) item.setText(0, title) @@ -190,13 +192,13 @@ class XbelTree(QTreeWidget): if not title: title = "Folder" - child_item.setFlags(item.flags() | Qt.ItemIsEditable) + child_item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) child_item.setIcon(0, self._bookmark_icon) child_item.setText(0, title) child_item.setText(1, child.attribute('href')) elif child.tagName() == 'separator': child_item = self.create_item(child, item) - child_item.setFlags(item.flags() & ~(Qt.ItemIsSelectable | Qt.ItemIsEditable)) + child_item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEditable)) # noqa: E501 child_item.setText(0, 30 * "\xb7") child = child.nextSiblingElement() diff --git a/requirements-coin.txt b/requirements-coin.txt new file mode 100644 index 00000000..5ee2bac0 --- /dev/null +++ b/requirements-coin.txt @@ -0,0 +1,12 @@ +pip>=24.2 +setuptools==72.1.0 +importlib_metadata>=6 +importlib_resources>=5.10.2 +packaging>=24 +ordered-set>=3.1.1 +more_itertools>=8.8 +jaraco.text>=3.7 +importlib_metadata>=6 +tomli>=2.0.1 +wheel>=0.43.0 +platformdirs >= 2.6.2 diff --git a/requirements-doc.txt b/requirements-doc.txt index 7205ef57..7e795c4f 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -1,12 +1,13 @@ -sphinx==7.2.6 -sphinx-design==0.5.0 +sphinx==7.4.7 +sphinx-design==0.6.0 sphinx-copybutton==0.5.2 -sphinx-tags==0.3.1 -sphinx-toolbox -myst-parser==2.0.0 +sphinx-tags==0.4 +sphinx-toolbox==3.7.0 +sphinx-reredirects==0.1.5 +myst-parser==3.0.1 # FIXME: Using fork in order to enable the 'collapse_navbar=True' # option for the sphinx-theme. Upstream proposal: # https://github.com/pradyunsg/furo/pull/748#issuecomment-1895448722 # furo==2023.9.10 furo @ git+https://github.com/cmaureir/furo@add_collapse -graphviz==0.20 +graphviz==0.20.3 diff --git a/requirements.txt b/requirements.txt index 65380e93..9769977f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,11 @@ # Build dependencies -setuptools==69.1.1 -packaging==23.2 -build==1.0.3 -wheel==0.42.0 +setuptools==72.1.0 +packaging==24.1 +build==1.2.1 +wheel==0.43.0 distro==1.9.0; sys_platform == 'linux' patchelf==0.17.2; sys_platform == 'linux' -# 1.24.4 is the last version that supports Python 3.8 -numpy<1.25; python_version < '3.9' -numpy==1.26.3; python_version >= '3.9' - -# For examples -PyOpenGL +# 2.0.2 is the last version that supports Python 3.9 +numpy<=2.0.2; python_version <= '3.9' +numpy==2.1.3; python_version > '3.9' +mypy[faster-cache]>=1.14.0 diff --git a/setup.py b/setup.py index f63b5905..26996a91 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """ This is a setuptools setup-script for the Qt for Python project. diff --git a/sources/pyside-tools/CMakeLists.txt b/sources/pyside-tools/CMakeLists.txt index e629ec57..cc9a7e5b 100644 --- a/sources/pyside-tools/CMakeLists.txt +++ b/sources/pyside-tools/CMakeLists.txt @@ -29,7 +29,7 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/requirements-android.txt) set(directories ${CMAKE_CURRENT_SOURCE_DIR}/deploy_lib - ${CMAKE_CURRENT_SOURCE_DIR}/project) + ${CMAKE_CURRENT_SOURCE_DIR}/project_lib) if(NOT NO_QT_TOOLS STREQUAL "yes") set(TOOLS_PATH "${QT6_INSTALL_PREFIX}/${QT6_HOST_INFO_BINDIR}") @@ -47,7 +47,8 @@ else() "${TOOLS_PATH}/qmlls${CMAKE_EXECUTABLE_SUFFIX}" "${TOOLS_PATH}/qsb${CMAKE_EXECUTABLE_SUFFIX}" "${TOOLS_PATH}/balsam${CMAKE_EXECUTABLE_SUFFIX}" - "${TOOLS_PATH}/balsamui${CMAKE_EXECUTABLE_SUFFIX}") + "${TOOLS_PATH}/balsamui${CMAKE_EXECUTABLE_SUFFIX}" + "${TOOLS_PATH}/svgtoqml${CMAKE_EXECUTABLE_SUFFIX}") if (APPLE) list(APPEND directories "${TOOLS_PATH}/Assistant.app" @@ -65,6 +66,7 @@ else() # pyside6-rcc, pyside6-uic, pyside6-designer, shiboken and pyside6-lupdate entrypoints foreach(file ${files}) if(EXISTS ${file}) + message(STATUS "Installing tool: ${file}") install(FILES "${file}" DESTINATION bin PERMISSIONS @@ -82,6 +84,18 @@ else() FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ - WORLD_EXECUTE WORLD_READ) + WORLD_EXECUTE WORLD_READ + PATTERN "android_utilities.py" EXCLUDE) # excluding the symlink endforeach() + + # dealing with android_utilities.py + set(ANDROID_UTILITIES_REALPATH + "${CMAKE_CURRENT_SOURCE_DIR}/../../tools/cross_compile_android/android_utilities.py") + message(STATUS "Installing ${ANDROID_UTILITIES_REALPATH}") + install(FILES "${ANDROID_UTILITIES_REALPATH}" + DESTINATION bin/deploy_lib/android + PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ) endif() diff --git a/sources/pyside-tools/android_deploy.py b/sources/pyside-tools/android_deploy.py index 75269d62..46adad61 100644 --- a/sources/pyside-tools/android_deploy.py +++ b/sources/pyside-tools/android_deploy.py @@ -1,6 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations +import sys import argparse import logging import shutil @@ -83,13 +85,12 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non if config_file_exists: logging.info(f"[DEPLOY] Using existing config file {config_file}") else: - config_file = create_config_file(dry_run=dry_run, config_file=config_file, - main_file=main_file) + config_file = create_config_file(main_file=main_file, dry_run=dry_run) config = AndroidConfig(config_file=config_file, source_file=main_file, python_exe=python.exe, dry_run=dry_run, android_data=android_data, existing_config_file=config_file_exists, - extra_ignore_dirs=extra_ignore_dirs) + extra_ignore_dirs=extra_ignore_dirs, name=name) if not config.wheel_pyside and not config.wheel_shiboken: raise RuntimeError(f"[DEPLOY] No PySide{MAJOR_VERSION} and Shiboken{MAJOR_VERSION} wheels" @@ -99,17 +100,13 @@ def main(name: str = None, pyside_wheel: Path = None, shiboken_wheel: Path = Non python.install_dependencies(config=config, packages="android_packages", is_android=True) - # set application name - if name: - config.title = name - try: config.modules += list(set(extra_modules).difference(set(config.modules))) # this cannot be done when config file is initialized because cleanup() removes it # so this can only be done after the cleanup() - config.find_and_set_jars_dir() - config.verify_and_set_recipe_dir() + config.jars_dir = config.find_jars_dir() + config.recipe_dir = config.find_recipe_dir() # TODO: include qml files from pysidedeploy.spec rather than from extensions # buildozer currently includes all the files with .qml extension @@ -191,8 +188,9 @@ if __name__ == "__main__": required=not config_option_exists()) parser.add_argument("--ndk-path", type=lambda p: Path(p).resolve(), - help=("Path to Android NDK. If omitted, the tool's cache at " - ".pyside6_android_deploy is checked to find the NDK") + help=("Path to Android NDK. The required version is r26b." + "If not provided, the tool will check its cache at " + ".pyside6_android_deploy to find the NDK.") ) parser.add_argument("--sdk-path", type=lambda p: Path(p).resolve(), @@ -207,6 +205,11 @@ if __name__ == "__main__": args = parser.parse_args() + # check if the Python version is greater than 3.12 + if sys.version_info >= (3, 12): + raise RuntimeError("[DEPLOY] Android deployment requires Python version 3.11 or lower. " + "This is due to a restriction in buildozer.") + main(args.name, args.wheel_pyside, args.wheel_shiboken, args.ndk_path, args.sdk_path, args.config_file, args.init, args.loglevel, args.dry_run, args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules) diff --git a/sources/pyside-tools/cmake/PySideAndroid.cmake b/sources/pyside-tools/cmake/PySideAndroid.cmake index 4b6260cc..d89da4f1 100644 --- a/sources/pyside-tools/cmake/PySideAndroid.cmake +++ b/sources/pyside-tools/cmake/PySideAndroid.cmake @@ -20,9 +20,10 @@ macro(create_and_install_qt_javabindings) ${android_main_srcs}/QtService.java ) # set android.jar from the sdk, for compiling the java files into .jar - set(sdk_jar_location "${ANDROID_SDK_ROOT}/platforms/${ANDROID_PLATFORM}/android.jar") + set(sdk_jar_location "${ANDROID_SDK_ROOT}/platforms/android-${CMAKE_ANDROID_API}/android.jar") + file(GLOB sources_list LIST_DIRECTORIES true "${ANDROID_SDK_ROOT}/platforms/android-${CMAKE_ANDROID_API}/*") if (NOT EXISTS "${sdk_jar_location}") - message(FATAL_ERROR "Could not locate Android SDK jar for api '${api}'") + message(FATAL_ERROR "Could not locate Android SDK jar for api '${CMAKE_ANDROID_API}' - ${sdk_jar_location}") endif() # this variable is accessed by qt_internal_add_jar diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py index aa03d13d..c26c8c62 100644 --- a/sources/pyside-tools/deploy.py +++ b/sources/pyside-tools/deploy.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """ pyside6-deploy deployment tool @@ -28,6 +29,7 @@ """ +import sys import argparse import logging import traceback @@ -49,12 +51,28 @@ TOOL_DESCRIPTION = dedent(f""" Linux = .bin """) +HELP_MODE = dedent(""" + The mode in which the application is deployed. The options are: onefile, + standalone. The default value is onefile. + + This options translates to the mode Nuitka uses to create the executable. + + macOS by default uses the --standalone option. + """) + def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False, loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False, - force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None): + force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None, + mode: str = None): logging.basicConfig(level=loglevel) + + # in case pyside6-deploy is run from a completely different location than the project + # directory + if main_file and main_file.exists(): + config_file = main_file.parent / "pysidedeploy.spec" + if config_file and not config_file.exists() and not main_file.exists(): raise RuntimeError(dedent(""" Directory does not contain main.py file. @@ -81,21 +99,16 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini extra_modules.append(extra_module) python = PythonExecutable(dry_run=dry_run, init=init, force=force) - config_file_exists = config_file and Path(config_file).exists() + config_file_exists = config_file and config_file.exists() if config_file_exists: logging.info(f"[DEPLOY] Using existing config file {config_file}") else: - config_file = create_config_file(dry_run=dry_run, config_file=config_file, - main_file=main_file) + config_file = create_config_file(main_file=main_file, dry_run=dry_run, ) config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe, dry_run=dry_run, existing_config_file=config_file_exists, - extra_ignore_dirs=extra_ignore_dirs) - - # set application name - if name: - config.title = name + extra_ignore_dirs=extra_ignore_dirs, mode=mode, name=name) cleanup(config=config) @@ -108,20 +121,28 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini config.modules += list(set(extra_modules).difference(set(config.modules))) - # writing config file - # in the case of --dry-run, we use default.spec as reference. Do not save the changes - # for --dry-run + # Do not save the config changes if --dry-run is specified if not dry_run: config.update_config() if config.qml_files: - logging.info(f"[DEPLOY] Included QML files: {config.qml_files}") + logging.info("[DEPLOY] Included QML files: " + f"{[str(qml_file) for qml_file in config.qml_files]}") if init: # config file created above. Exiting. logging.info(f"[DEPLOY]: Config file {config.config_file} created") return + # If modules contain QtSql and the platform is macOS, then pyside6-deploy will not work + # currently. The fix ideally will have to come from Nuitka. + # See PYSIDE-2835 + # TODO: Remove this check once the issue is fixed in Nuitka + # Nuitka Issue: https://github.com/Nuitka/Nuitka/issues/3079 + if "Sql" in config.modules and sys.platform == "darwin": + print("[DEPLOY] QtSql Application is not supported on macOS with pyside6-deploy") + return + try: # create executable if not dry_run: @@ -135,12 +156,14 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini excluded_qml_plugins=config.excluded_qml_plugins, icon=config.icon, dry_run=dry_run, - permissions=config.permissions) + permissions=config.permissions, + mode=config.mode) except Exception: print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}") finally: - if config.generated_files_path and config: - finalize(config=config) + if config.generated_files_path: + if not dry_run: + finalize(config=config) if not keep_deployment_files: cleanup(config=config) @@ -182,7 +205,11 @@ if __name__ == "__main__": parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES) + parser.add_argument("--mode", choices=["onefile", "standalone"], default="onefile", + help=HELP_MODE) + args = parser.parse_args() main(args.main_file, args.name, args.config_file, args.init, args.loglevel, args.dry_run, - args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules) + args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules, + args.mode) diff --git a/sources/pyside-tools/deploy_lib/__init__.py b/sources/pyside-tools/deploy_lib/__init__.py index a40d0838..3f6190b1 100644 --- a/sources/pyside-tools/deploy_lib/__init__.py +++ b/sources/pyside-tools/deploy_lib/__init__.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import sys from pathlib import Path from textwrap import dedent @@ -17,6 +18,9 @@ else: EXE_FORMAT = ".bin" DEFAULT_APP_ICON = str((Path(__file__).parent / f"pyside_icon{IMAGE_FORMAT}").resolve()) +DEFAULT_IGNORE_DIRS = {"site-packages", "deployment", ".git", ".qtcreator", "build", "dist", + "tests", "doc", "docs", "examples", ".vscode", "__pycache__"} + IMPORT_WARNING_PYSIDE = (f"[DEPLOY] Found 'import PySide6' in file {0}" ". Use 'from PySide6 import ' or pass the module" " needed using --extra-modules command line argument") @@ -40,6 +44,10 @@ HELP_EXTRA_MODULES = dedent(""" Example usage 2: --extra-modules=QtNetwork,QtSvg """) +# plugins to be removed from the --include-qt-plugins option because these plugins +# don't exist in site-package under PySide6/Qt/plugins +PLUGINS_TO_REMOVE = ["accessiblebridge", "platforms/darwin", "networkaccess", "scenegraph"] + def get_all_pyside_modules(): """ diff --git a/sources/pyside-tools/deploy_lib/android/__init__.py b/sources/pyside-tools/deploy_lib/android/__init__.py index c3027762..27d4d7b6 100644 --- a/sources/pyside-tools/deploy_lib/android/__init__.py +++ b/sources/pyside-tools/deploy_lib/android/__init__.py @@ -1,5 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations +from pathlib import Path # maps instruction set to Android platform names platform_map = {"aarch64": "arm64-v8a", @@ -10,6 +12,8 @@ platform_map = {"aarch64": "arm64-v8a", "armeabi-v7a": "armeabi-v7a", "x86": "x86"} +ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy" + from .android_helper import (create_recipe, extract_and_copy_jar, get_wheel_android_arch, AndroidData, get_llvm_readobj, find_lib_dependencies, find_qtlibs_in_wheel) diff --git a/sources/pyside-tools/deploy_lib/android/android_config.py b/sources/pyside-tools/deploy_lib/android/android_config.py index ad818c2f..6b9386b8 100644 --- a/sources/pyside-tools/deploy_lib/android/android_config.py +++ b/sources/pyside-tools/deploy_lib/android/android_config.py @@ -1,21 +1,22 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import re +import sys import tempfile import logging import zipfile import xml.etree.ElementTree as ET -from typing import List from pathlib import Path from pkginfo import Wheel from . import (extract_and_copy_jar, get_wheel_android_arch, find_lib_dependencies, - get_llvm_readobj, find_qtlibs_in_wheel, platform_map, create_recipe) -from .. import (Config, find_pyside_modules, get_all_pyside_modules, MAJOR_VERSION) - -ANDROID_NDK_VERSION = "26b" -ANDROID_DEPLOY_CACHE = Path.home() / ".pyside6_android_deploy" + get_llvm_readobj, find_qtlibs_in_wheel, platform_map, create_recipe, + ANDROID_DEPLOY_CACHE) +from .. import (Config, get_all_pyside_modules, MAJOR_VERSION) +from .android_utilities import (ANDROID_NDK_VERSION, ANDROID_NDK_VERSION_NUMBER_SUFFIX, + download_android_ndk) class AndroidConfig(Config): @@ -24,9 +25,9 @@ class AndroidConfig(Config): """ def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, android_data, existing_config_file: bool = False, - extra_ignore_dirs: List[str] = None): + extra_ignore_dirs: list[str] = None, name: str = None): super().__init__(config_file=config_file, source_file=source_file, python_exe=python_exe, - dry_run=dry_run, existing_config_file=existing_config_file) + dry_run=dry_run, existing_config_file=existing_config_file, name=name) self.extra_ignore_dirs = extra_ignore_dirs @@ -50,7 +51,7 @@ class AndroidConfig(Config): if android_data.ndk_path: # from cli self.ndk_path = android_data.ndk_path - else: + elif not existing_config_file: # from config ndk_path_temp = self.get_value("buildozer", "ndk_path") if ndk_path_temp: @@ -58,14 +59,21 @@ class AndroidConfig(Config): else: ndk_path_temp = (ANDROID_DEPLOY_CACHE / "android-ndk" / f"android-ndk-r{ANDROID_NDK_VERSION}") + if sys.platform == "darwin": + ndk_path_temp = ( + ANDROID_DEPLOY_CACHE / "android-ndk" + / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK" + ) if ndk_path_temp.exists(): self.ndk_path = ndk_path_temp + else: + # download NDK + if not ANDROID_DEPLOY_CACHE.exists(): + ANDROID_DEPLOY_CACHE.mkdir() + logging.info(f"Cache created at {str(ANDROID_DEPLOY_CACHE.resolve())}") - if self.ndk_path: - print(f"Using Android NDK: {str(self.ndk_path)}") - else: - raise FileNotFoundError("[DEPLOY] Unable to find Android NDK. Please pass the NDK " - "path either from the CLI or from pysidedeploy.spec") + logging.info("[DEPLOY] Downloading Android NDK") + self.ndk_path = download_android_ndk(ANDROID_DEPLOY_CACHE) self.sdk_path = None if android_data.sdk_path: @@ -92,11 +100,11 @@ class AndroidConfig(Config): if jars_dir_temp and Path(jars_dir_temp).resolve().exists(): self.jars_dir = Path(jars_dir_temp).resolve() - self._arch = None - if self.get_value("buildozer", "arch"): - self.arch = self.get_value("buildozer", "arch") + android_arch = self.get_value("buildozer", "arch") + if android_arch: + self._arch = android_arch else: - self._find_and_set_arch() + self.arch = self._find_arch() # maps to correct platform name incase the instruction set was specified self._arch = platform_map[self.arch] @@ -106,31 +114,33 @@ class AndroidConfig(Config): self.qt_libs_path: zipfile.Path = find_qtlibs_in_wheel(wheel_pyside=self.wheel_pyside) logging.info(f"[DEPLOY] Qt libs path inside wheel: {str(self.qt_libs_path)}") - if self.get_value("qt", "modules"): - self.modules = self.get_value("qt", "modules").split(",") + modls = self.get_value("qt", "modules") + if modls: + self._modules = modls.split(",") else: - self._find_and_set_pysidemodules() - self._find_and_set_qtquick_modules() - self.modules += self._find_dependent_qt_modules() + modls = self._find_pysidemodules() + modls += self._find_qtquick_modules() + modls += self._find_dependent_qt_modules(modules=modls) # remove duplicates - self.modules = list(set(self.modules)) + self.modules = list(set(modls)) # gets the xml dependency files from Qt installation path - self._dependency_files = [] - self._find_and_set_dependency_files() + self.dependency_files = self._find_dependency_files() dependent_plugins = [] self._local_libs = [] - if self.get_value("buildozer", "local_libs"): - self._local_libs = self.get_value("buildozer", "local_libs").split(",") + loc_libs = self.get_value("buildozer", "local_libs") + if loc_libs: + self._local_libs = loc_libs.split(",") else: # the local_libs can also store dependent plugins local_libs, dependent_plugins = self._find_local_libs() self.local_libs = list(set(local_libs)) self._qt_plugins = [] - if self.get_value("android", "plugins"): - self._qt_plugins = self.get_value("android", "plugins").split(",") + qt_plgns = self.get_value("android", "plugins") + if qt_plgns: + self._qt_plugins = qt_plgns.split(",") elif dependent_plugins: self._find_plugin_dependencies(dependent_plugins) self.qt_plugins = list(set(dependent_plugins)) @@ -256,31 +266,29 @@ class AndroidConfig(Config): def dependency_files(self, dependency_files): self._dependency_files = dependency_files - def _find_and_set_pysidemodules(self): - self.modules = find_pyside_modules(project_dir=self.project_dir, - extra_ignore_dirs=self.extra_ignore_dirs, - project_data=self.project_data) - logging.info("The following PySide modules were found from the python files of " - f"the project {self.modules}") - - def find_and_set_jars_dir(self): + def find_jars_dir(self): """Extract out and copy .jar files to {generated_files_path} """ + jars_dir = None if not self.dry_run: logging.info("[DEPLOY] Extract and copy jar files from PySide6 wheel to " f"{self.generated_files_path}") - self.jars_dir = extract_and_copy_jar(wheel_path=self.wheel_pyside, - generated_files_path=self.generated_files_path) + jars_dir = extract_and_copy_jar(wheel_path=self.wheel_pyside, + generated_files_path=self.generated_files_path) - def _find_and_set_arch(self): + return jars_dir + + def _find_arch(self): """Find architecture from wheel name """ - self.arch = get_wheel_android_arch(wheel=self.wheel_pyside) - if not self.arch: + arch = get_wheel_android_arch(wheel=self.wheel_pyside) + if not arch: raise RuntimeError("[DEPLOY] PySide wheel corrupted. Wheel name should end with" "platform name") - def _find_dependent_qt_modules(self): + return arch + + def _find_dependent_qt_modules(self, modules: list[str]) -> list[str]: """ Given pysidedeploy_config.modules, find all the other dependent Qt modules. This is done by using llvm-readobj (readelf) to find the dependent libraries from the module @@ -302,7 +310,7 @@ class AndroidConfig(Config): archive.extractall(tmpdir) qt_libs_tmpdir = Path(tmpdir) / lib_path_suffix # find the lib folder where Qt libraries are stored - for module_name in sorted(self.modules): + for module_name in sorted(modules): qt_module_path = qt_libs_tmpdir / f"libQt6{module_name}_{self.arch}.so" if not qt_module_path.exists(): raise FileNotFoundError(f"[DEPLOY] libQt6{module_name}_{self.arch}.so not found" @@ -315,7 +323,7 @@ class AndroidConfig(Config): match = lib_pattern.search(dependency) if match: module = match.group("mod_name") - if module not in self.modules: + if module not in modules: dependent_modules.add(module) # check if the PySide6 binary for the Qt module actually exists @@ -328,21 +336,23 @@ class AndroidConfig(Config): return dependent_modules - def _find_and_set_dependency_files(self) -> List[zipfile.Path]: + def _find_dependency_files(self) -> list[zipfile.Path]: """ Based on `modules`, returns the Qt6{module}_{arch}-android-dependencies.xml file, which contains the various dependencies of the module, like permissions, plugins etc """ needed_dependency_files = [(f"Qt{MAJOR_VERSION}{module}_{self.arch}" "-android-dependencies.xml") for module in self.modules] - + found_dependency_files = [] for dependency_file_name in needed_dependency_files: dependency_file = self.qt_libs_path / dependency_file_name if dependency_file.exists(): - self._dependency_files.append(dependency_file) + found_dependency_files.append(dependency_file) logging.info("[DEPLOY] The following dependency files were found: " - f"{*self._dependency_files,}") + f"{*found_dependency_files, }") + + return found_dependency_files def _find_local_libs(self): local_libs = set() @@ -377,7 +387,7 @@ class AndroidConfig(Config): return list(local_libs), list(plugins) - def _find_plugin_dependencies(self, dependent_plugins: List[str]): + def _find_plugin_dependencies(self, dependent_plugins: list[str]): # The `bundled` element in the dependency xml files points to the folder where # additional dependencies for the application exists. Inspecting the depenency files # in android, this always points to the specific Qt plugin dependency folder. @@ -424,12 +434,13 @@ class AndroidConfig(Config): if plugin_infix_name not in dependent_plugins: dependent_plugins.append(plugin_infix_name) - def verify_and_set_recipe_dir(self): + def find_recipe_dir(self): # create recipes # https://python-for-android.readthedocs.io/en/latest/recipes/ # These recipes are manually added through buildozer.spec file to be used by # python_for_android while building the distribution + recipe_dir = None if not self.recipes_exist() and not self.dry_run: logging.info("[DEPLOY] Creating p4a recipes for PySide6 and shiboken6") version = Wheel(self.wheel_pyside).version @@ -442,5 +453,6 @@ class AndroidConfig(Config): create_recipe(version=version, component=f"shiboken{MAJOR_VERSION}", wheel_path=self.wheel_shiboken, generated_files_path=self.generated_files_path) - self.recipe_dir = ((self.generated_files_path - / "recipes").resolve()) + recipe_dir = ((self.generated_files_path + / "recipes").resolve()) + return recipe_dir diff --git a/sources/pyside-tools/deploy_lib/android/android_helper.py b/sources/pyside-tools/deploy_lib/android/android_helper.py index 7d2f5d57..f9398d7e 100644 --- a/sources/pyside-tools/deploy_lib/android/android_helper.py +++ b/sources/pyside-tools/deploy_lib/android/android_helper.py @@ -1,11 +1,11 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - +from __future__ import annotations +import sys import logging import zipfile from dataclasses import dataclass from pathlib import Path -from typing import List, Set from zipfile import ZipFile from jinja2 import Environment, FileSystemLoader @@ -25,8 +25,8 @@ class AndroidData: def create_recipe(version: str, component: str, wheel_path: str, generated_files_path: Path, - qt_modules: List[str] = None, local_libs: List[str] = None, - plugins: List[str] = None): + qt_modules: list[str] = None, local_libs: list[str] = None, + plugins: list[str] = None): ''' Create python_for_android recipe for PySide6 and shiboken6 ''' @@ -91,10 +91,10 @@ def get_llvm_readobj(ndk_path: Path) -> Path: ''' # TODO: Requires change if Windows platform supports Android Deployment or if we # support host other than linux-x86_64 - return (ndk_path / "toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readobj") + return (ndk_path / f"toolchains/llvm/prebuilt/{sys.platform}-x86_64/bin/llvm-readobj") -def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies: Set[str] = None, +def find_lib_dependencies(llvm_readobj: Path, lib_path: Path, used_dependencies: set[str] = None, dry_run: bool = False): """ Find all the Qt dependencies of a library using llvm_readobj diff --git a/sources/pyside-tools/deploy_lib/android/android_utilities.py b/sources/pyside-tools/deploy_lib/android/android_utilities.py new file mode 100644 index 00000000..45dd874f --- /dev/null +++ b/sources/pyside-tools/deploy_lib/android/android_utilities.py @@ -0,0 +1,311 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations + +import logging +import shutil +import re +import os +import stat +import sys +import subprocess + +from urllib import request +from pathlib import Path +from packaging import version +from tqdm import tqdm + +# the tag number does not matter much since we update the sdk later +DEFAULT_SDK_TAG = 6514223 +ANDROID_NDK_VERSION = "26b" +ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125" + + +def run_command(command: list[str], cwd: str | None = None, ignore_fail: bool = False, + dry_run: bool = False, accept_prompts: bool = False, show_stdout: bool = False, + capture_stdout: bool = False): + + if capture_stdout and not show_stdout: + raise RuntimeError("capture_stdout should always be used together with show_stdout") + + if dry_run: + print(" ".join(command)) + return + + input = None + if accept_prompts: + input = str.encode("y") + + if show_stdout: + stdout = None + else: + stdout = subprocess.DEVNULL + + result = subprocess.run(command, cwd=cwd, input=input, stdout=stdout, + capture_output=capture_stdout) + + if result.returncode != 0 and not ignore_fail: + sys.exit(result.returncode) + + if capture_stdout and not result.returncode: + return result.stdout.decode("utf-8") + + return None + + +class DownloadProgressBar(tqdm): + def update_to(self, b=1, bsize=1, tsize=None): + if tsize is not None: + self.total = tsize + self.update(b * bsize - self.n) + + +class SdkManager: + def __init__(self, android_sdk_dir: Path, dry_run: bool = False): + self._sdk_manager = android_sdk_dir / "tools" / "bin" / "sdkmanager" + + if not self._sdk_manager.exists(): + raise RuntimeError(f"Unable to find SdkManager in {str(self._sdk_manager)}") + + if not os.access(self._sdk_manager, os.X_OK): + current_permissions = stat.S_IMODE(os.lstat(self._sdk_manager).st_mode) + os.chmod(self._sdk_manager, current_permissions | stat.S_IEXEC) + + self._android_sdk_dir = android_sdk_dir + self._dry_run = dry_run + + def list_packages(self): + command = [self._sdk_manager, f"--sdk_root={self._android_sdk_dir}", "--list"] + return run_command(command=command, dry_run=self._dry_run, show_stdout=True, + capture_stdout=True) + + def install(self, *args, accept_license: bool = False, show_stdout=False): + command = [str(self._sdk_manager), f"--sdk_root={self._android_sdk_dir}", *args] + run_command(command=command, dry_run=self._dry_run, + accept_prompts=accept_license, show_stdout=show_stdout) + + +def extract_zip(file: Path, destination: Path): + """ + Unpacks the zip file into destination preserving all permissions + + TODO: Try to use zipfile module. Currently we cannot use zipfile module here because + extractAll() does not preserve permissions. + + In case `unzip` is not available, the user is requested to install it manually + """ + unzip = shutil.which("unzip") + if not unzip: + raise RuntimeError("Unable to find program unzip. Use `sudo apt-get install unzip`" + "to install it") + + command = [unzip, str(file), "-d", str(destination)] + run_command(command=command, show_stdout=True) + + +def extract_dmg(file: Path, destination: Path): + output = run_command(['hdiutil', 'attach', '-nobrowse', '-readonly', str(file)], + show_stdout=True, capture_stdout=True) + + # find the mounted volume + result = re.search(r'/Volumes/(.*)', output) + if not result: + raise RuntimeError(f"Unable to find mounted volume for file {file}") + mounted_vol_name = result.group(1) + if not mounted_vol_name: + raise RuntimeError(f"Unable to find mounted volume for file {file}") + + # copy files + shutil.copytree(f'/Volumes/{mounted_vol_name}/', destination, dirs_exist_ok=True) + + # Detach mounted volume + run_command(['hdiutil', 'detach', f'/Volumes/{mounted_vol_name}']) + + +def _download(url: str, destination: Path): + """ + Download url to destination + """ + headers, download_path = None, None + # https://github.com/tqdm/tqdm#hooks-and-callbacks + with DownloadProgressBar(unit='B', unit_scale=True, miniters=1, desc=url.split('/')[-1]) as t: + download_path, headers = request.urlretrieve(url=url, filename=destination, + reporthook=t.update_to) + assert Path(download_path).resolve() == destination + + +def download_android_ndk(ndk_path: Path): + """ + Downloads the given ndk_version into ndk_path + """ + ndk_path = ndk_path / "android-ndk" + ndk_extension = "dmg" if sys.platform == "darwin" else "zip" + ndk_zip_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}" + ndk_version_path = "" + if sys.platform == "linux": + ndk_version_path = ndk_path / f"android-ndk-r{ANDROID_NDK_VERSION}" + elif sys.platform == "darwin": + ndk_version_path = (ndk_path + / f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app/Contents/NDK") + else: + raise RuntimeError(f"Unsupported platform {sys.platform}") + + if ndk_version_path.exists(): + print(f"NDK path found in {str(ndk_version_path)}") + else: + try: + ndk_path.mkdir(parents=True, exist_ok=True) + url = (f"https://dl.google.com/android/repository" + f"/android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}") + + print(f"Downloading Android Ndk version r{ANDROID_NDK_VERSION}") + _download(url=url, destination=ndk_zip_path) + + print("Unpacking Android Ndk") + if sys.platform == "darwin": + extract_dmg(file=(ndk_path + / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"), + destination=ndk_path) + ndk_version_path = (ndk_version_path + / (f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app" + "/Contents/NDK")) + else: + extract_zip(file=(ndk_path + / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"), + destination=ndk_path) + except Exception as e: + print(f"Error occurred while downloading and unpacking Android NDK: {e}") + if ndk_path.exists(): + shutil.rmtree(ndk_path) + sys.exit(1) + + return ndk_version_path + + +def download_android_commandlinetools(android_sdk_dir: Path): + """ + Downloads Android commandline tools into cltools_path. + """ + sdk_platform = sys.platform if sys.platform != "darwin" else "mac" + android_sdk_dir = android_sdk_dir / "android-sdk" + url = ("https://dl.google.com/android/repository/" + f"commandlinetools-{sdk_platform}-{DEFAULT_SDK_TAG}_latest.zip") + cltools_zip_path = (android_sdk_dir + / f"commandlinetools-{sdk_platform}-{DEFAULT_SDK_TAG}_latest.zip") + cltools_path = android_sdk_dir / "tools" + + if cltools_path.exists(): + print(f"Command-line tools found in {str(cltools_path)}") + else: + try: + android_sdk_dir.mkdir(parents=True, exist_ok=True) + + print("Download Android Command Line Tools: " + f"commandlinetools-{sys.platform}-{DEFAULT_SDK_TAG}_latest.zip") + _download(url=url, destination=cltools_zip_path) + + print("Unpacking Android Command Line Tools") + extract_zip(file=cltools_zip_path, destination=android_sdk_dir) + except Exception as e: + print(f"Error occurred while downloading and unpacking Android Command Line Tools: {e}") + if android_sdk_dir.exists(): + shutil.rmtree(android_sdk_dir) + sys.exit(1) + + return android_sdk_dir + + +def android_list_build_tools_versions(sdk_manager: SdkManager): + """ + List all the build-tools versions available for download + """ + available_packages = sdk_manager.list_packages() + build_tools_versions = [] + lines = available_packages.split('\n') + + for line in lines: + if not line.strip().startswith('build-tools;'): + continue + package_name = line.strip().split(' ')[0] + if package_name.count(';') != 1: + raise RuntimeError(f"Unable to parse build-tools version: {package_name}") + ver = package_name.split(';')[1] + + build_tools_versions.append(version.Version(ver)) + + return build_tools_versions + + +def find_installed_buildtools_version(build_tools_dir: Path): + """ + It is possible that the user has multiple build-tools installed. The newer version is generally + used. This function find the newest among the installed build-tools + """ + versions = [version.Version(bt_dir.name) for bt_dir in build_tools_dir.iterdir() + if bt_dir.is_dir()] + return max(versions) + + +def find_latest_buildtools_version(sdk_manager: SdkManager): + """ + Uses sdk manager to find the latest build-tools version + """ + available_build_tools_v = android_list_build_tools_versions(sdk_manager=sdk_manager) + + if not available_build_tools_v: + raise RuntimeError('Unable to find any build tools available for download') + + # find the latest build tools version that is not a release candidate + # release candidates end has rc in the version number + available_build_tools_v = [v for v in available_build_tools_v if "rc" not in str(v)] + + return max(available_build_tools_v) + + +def install_android_packages(android_sdk_dir: Path, android_api: str, dry_run: bool = False, + accept_license: bool = False, skip_update: bool = False): + """ + Use the sdk manager to install build-tools, platform-tools and platform API + """ + tools_dir = android_sdk_dir / "tools" + if not tools_dir.exists(): + raise RuntimeError("Unable to find Android command-line tools in " + f"{str(tools_dir)}") + + # incase of --verbose flag + show_output = (logging.getLogger().getEffectiveLevel() == logging.INFO) + + sdk_manager = SdkManager(android_sdk_dir=android_sdk_dir, dry_run=dry_run) + + # install/upgrade platform-tools + if not (android_sdk_dir / "platform-tools").exists(): + print("Installing/Updating Android platform-tools") + sdk_manager.install("platform-tools", accept_license=accept_license, + show_stdout=show_output) + # The --update command is only relevant for platform tools + if not skip_update: + sdk_manager.install("--update", show_stdout=show_output) + + # install/upgrade build-tools + buildtools_dir = android_sdk_dir / "build-tools" + + if not buildtools_dir.exists(): + latest_build_tools_v = find_latest_buildtools_version(sdk_manager=sdk_manager) + print(f"Installing Android build-tools version {latest_build_tools_v}") + sdk_manager.install(f"build-tools;{latest_build_tools_v}", show_stdout=show_output) + else: + if not skip_update: + latest_build_tools_v = find_latest_buildtools_version(sdk_manager=sdk_manager) + installed_build_tools_v = find_installed_buildtools_version(buildtools_dir) + if latest_build_tools_v > installed_build_tools_v: + print(f"Updating Android build-tools version to {latest_build_tools_v}") + sdk_manager.install(f"build-tools;{latest_build_tools_v}", show_stdout=show_output) + installed_build_tools_v = latest_build_tools_v + + # install the platform API + platform_api_dir = android_sdk_dir / "platforms" / f"android-{android_api}" + if not platform_api_dir.exists(): + print(f"Installing Android platform API {android_api}") + sdk_manager.install(f"platforms;android-{android_api}", show_stdout=show_output) + + print("Android packages installation done") diff --git a/sources/pyside-tools/deploy_lib/android/buildozer.py b/sources/pyside-tools/deploy_lib/android/buildozer.py index 0c314c35..36d8313e 100644 --- a/sources/pyside-tools/deploy_lib/android/buildozer.py +++ b/sources/pyside-tools/deploy_lib/android/buildozer.py @@ -1,12 +1,12 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import sys import logging import xml.etree.ElementTree as ET import zipfile from pathlib import Path -from typing import List from . import AndroidConfig from .. import BaseConfig, run_command @@ -76,7 +76,7 @@ class BuildozerConfig(BaseConfig): self.update_config() - def __find_permissions(self, dependency_files: List[zipfile.Path]): + def __find_permissions(self, dependency_files: list[zipfile.Path]): permissions = set() for dependency_file in dependency_files: xml_content = dependency_file.read_text() @@ -85,7 +85,7 @@ class BuildozerConfig(BaseConfig): permissions.add(permission.attrib['name']) return permissions - def __find_jars(self, dependency_files: List[zipfile.Path], jars_dir: Path): + def __find_jars(self, dependency_files: list[zipfile.Path], jars_dir: Path): jars, init_classes = set(), set() for dependency_file in dependency_files: xml_content = dependency_file.read_text() diff --git a/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py index 8a861579..375dadf3 100644 --- a/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py +++ b/sources/pyside-tools/deploy_lib/android/recipes/PySide6/__init__.tmpl.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import shutil import zipfile diff --git a/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py b/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py index d6ab037b..0b822d4d 100644 --- a/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py +++ b/sources/pyside-tools/deploy_lib/android/recipes/shiboken6/__init__.tmpl.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import shutil import zipfile diff --git a/sources/pyside-tools/deploy_lib/commands.py b/sources/pyside-tools/deploy_lib/commands.py index 3a7e2a2e..03f8c20f 100644 --- a/sources/pyside-tools/deploy_lib/commands.py +++ b/sources/pyside-tools/deploy_lib/commands.py @@ -1,11 +1,14 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import json import subprocess import sys from pathlib import Path -from typing import List +from functools import lru_cache +from . import DEFAULT_IGNORE_DIRS + """ All utility functions for deployment @@ -37,24 +40,24 @@ def run_command(command, dry_run: bool, fetch_output: bool = False): return command_str, output -def run_qmlimportscanner(qml_files: List[Path], dry_run: bool): +@lru_cache +def run_qmlimportscanner(project_dir: Path, dry_run: bool): """ - Runs pyside6-qmlimportscanner to find all the imported qml modules + Runs pyside6-qmlimportscanner to find all the imported qml modules in project_dir """ - if not qml_files: - return [] - qml_modules = [] - cmd = ["pyside6-qmlimportscanner", "-qmlFiles"] - cmd.extend([str(qml_file) for qml_file in qml_files]) + cmd = ["pyside6-qmlimportscanner", "-rootPath", str(project_dir)] + + for ignore_dir in DEFAULT_IGNORE_DIRS: + cmd.extend(["-exclude", ignore_dir]) if dry_run: run_command(command=cmd, dry_run=True) - # we need to run qmlimportscanner during dry_run as well to complete the - # command being run by nuitka + # Run qmlimportscanner during dry_run as well to complete the command being run by nuitka _, json_string = run_command(command=cmd, dry_run=False, fetch_output=True) json_string = json_string.decode("utf-8") json_array = json.loads(json_string) qml_modules = [item['name'] for item in json_array if item['type'] == "module"] + return qml_modules diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py index d59dd92a..998a175c 100644 --- a/sources/pyside-tools/deploy_lib/config.py +++ b/sources/pyside-tools/deploy_lib/config.py @@ -1,17 +1,19 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import sys import configparser import logging +import tempfile import warnings from configparser import ConfigParser -from typing import List from pathlib import Path +from enum import Enum -from project import ProjectData -from . import (DEFAULT_APP_ICON, find_pyside_modules, find_permission_categories, - QtDependencyReader, run_qmlimportscanner) +from project_lib import ProjectData, DesignStudioProject +from . import (DEFAULT_APP_ICON, DEFAULT_IGNORE_DIRS, find_pyside_modules, + find_permission_categories, QtDependencyReader, run_qmlimportscanner) # Some QML plugins like QtCore are excluded from this list as they don't contribute much to # executable size. Excluding them saves the extra processing of checking for them in files @@ -31,6 +33,7 @@ PERMISSION_MAP = {"Bluetooth": "NSBluetoothAlwaysUsageDescription:BluetoothAcces class BaseConfig: """Wrapper class around any .spec file with function to read and set values for the .spec file """ + def __init__(self, config_file: Path, comment_prefixes: str = "/", existing_config_file: bool = False) -> None: self.config_file = config_file @@ -40,31 +43,53 @@ class BaseConfig: self.parser.read(self.config_file) def update_config(self): - logging.info(f"[DEPLOY] Creating {self.config_file}") - with open(self.config_file, "w+") as config_file: - self.parser.write(config_file, space_around_delimiters=True) - - def set_value(self, section: str, key: str, new_value: str, raise_warning: bool = True): + logging.info(f"[DEPLOY] Updating config file {self.config_file}") + + # This section of code is done to preserve the formatting of the original deploy.spec + # file where there is blank line before the comments + with tempfile.NamedTemporaryFile('w+', delete=False) as temp_file: + self.parser.write(temp_file, space_around_delimiters=True) + temp_file_path = temp_file.name + + # Read the temporary file and write back to the original file with blank lines before + # comments + with open(temp_file_path, 'r') as temp_file, open(self.config_file, 'w') as config_file: + previous_line = None + for line in temp_file: + if (line.lstrip().startswith('#') and previous_line is not None + and not previous_line.lstrip().startswith('#')): + config_file.write('\n') + config_file.write(line) + previous_line = line + + # Clean up the temporary file + Path(temp_file_path).unlink() + + def set_value(self, section: str, key: str, new_value: str, raise_warning: bool = True) -> None: try: current_value = self.get_value(section, key, ignore_fail=True) if current_value != new_value: self.parser.set(section, key, new_value) except configparser.NoOptionError: - if raise_warning: - logging.warning(f"[DEPLOY] Key {key} does not exist") + if not raise_warning: + return + logging.warning(f"[DEPLOY] Set key '{key}': Key does not exist in section '{section}'") except configparser.NoSectionError: - if raise_warning: - logging.warning(f"[DEPLOY] Section {section} does not exist") + if not raise_warning: + return + logging.warning(f"[DEPLOY] Section '{section}' does not exist") - def get_value(self, section: str, key: str, ignore_fail: bool = False): + def get_value(self, section: str, key: str, ignore_fail: bool = False) -> str | None: try: return self.parser.get(section, key) except configparser.NoOptionError: - if not ignore_fail: - logging.warning(f"[DEPLOY] Key {key} does not exist") + if ignore_fail: + return None + logging.warning(f"[DEPLOY] Get key '{key}': Key does not exist in section {section}") except configparser.NoSectionError: - if not ignore_fail: - logging.warning(f"[DEPLOY] Section {section} does not exist") + if ignore_fail: + return None + logging.warning(f"[DEPLOY] Section '{section}': does not exist") class Config(BaseConfig): @@ -74,7 +99,8 @@ class Config(BaseConfig): """ def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, - existing_config_file: bool = False, extra_ignore_dirs: List[str] = None): + existing_config_file: bool = False, extra_ignore_dirs: list[str] = None, + name: str = None): super().__init__(config_file=config_file, existing_config_file=existing_config_file) self.extra_ignore_dirs = extra_ignore_dirs @@ -94,66 +120,84 @@ class Config(BaseConfig): ) ) - self.title = self.get_value("app", "title") + # set application name + self.title = self.set_or_fetch(config_property_val=name, config_property_key="title") # set application icon config_icon = self.get_value("app", "icon") if config_icon: - self.icon = str(Path(config_icon).resolve()) + self._icon = str(Path(config_icon).resolve()) else: self.icon = DEFAULT_APP_ICON - self.project_dir = None - if self.get_value("app", "project_dir"): - self.project_dir = Path(self.get_value("app", "project_dir")).absolute() - else: - self._find_and_set_project_dir() - - self.exe_dir = None - if self.get_value("app", "exec_directory"): - self.exe_dir = Path(self.get_value("app", "exec_directory")).absolute() + proj_dir = self.get_value("app", "project_dir") + if proj_dir: + self._project_dir = Path(proj_dir).resolve() else: - self._find_and_set_exe_dir() + self.project_dir = self._find_project_dir() - self.project_data: ProjectData = None - if self.get_value("app", "project_file"): - project_file = Path(self.get_value("app", "project_file")).absolute() - self.project_data = ProjectData(project_file=project_file) + exe_directory = self.get_value("app", "exec_directory") + if exe_directory: + self._exe_dir = Path(exe_directory).absolute() else: - self._find_and_set_project_file() + self.exe_dir = self._find_exe_dir() - self.qml_files = [] - config_qml_files = self.get_value("qt", "qml_files") - if config_qml_files and self.project_dir and self.existing_config_file: - self.qml_files = [Path(self.project_dir) / file for file in config_qml_files.split(",")] + self._project_file = None + proj_file = self.get_value("app", "project_file") + if proj_file: + self._project_file = self.project_dir / proj_file else: - self._find_and_set_qml_files() + proj_file = self._find_project_file() + if proj_file: + self.project_file = proj_file + + self.project_data = None + if self.project_file and self.project_file.exists(): + self.project_data = ProjectData(project_file=self.project_file) + + self._qml_files = [] + # Design Studio projects include the qml files using Qt resources + if source_file and not DesignStudioProject.is_ds_project(source_file): + config_qml_files = self.get_value("qt", "qml_files") + if config_qml_files and self.project_dir and self.existing_config_file: + self._qml_files = [Path(self.project_dir) + / file for file in config_qml_files.split(",")] + else: + self.qml_files = self._find_qml_files() - self.excluded_qml_plugins = [] - if self.get_value("qt", "excluded_qml_plugins") and self.existing_config_file: - self.excluded_qml_plugins = self.get_value("qt", "excluded_qml_plugins").split(",") + self._excluded_qml_plugins = [] + excl_qml_plugins = self.get_value("qt", "excluded_qml_plugins") + if excl_qml_plugins and self.existing_config_file: + self._excluded_qml_plugins = excl_qml_plugins.split(",") else: - self._find_and_set_excluded_qml_plugins() + self.excluded_qml_plugins = self._find_excluded_qml_plugins() - self._generated_files_path = self.project_dir / "deployment" + self._generated_files_path = self.source_file.parent / "deployment" self.modules = [] def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"): """ - Write to config_file if 'config_property_key' is known without config_file - Fetch and return from config_file if 'config_property_key' is unknown, but - config_file exists - Otherwise, raise an exception + Set the configuration value if provided, otherwise fetch the existing value. + Raise an exception if neither is available. + + :param value: The value to set if provided. + :param key: The configuration key. + :param group: The configuration group (default is "app"). + :return: The configuration value. + :raises RuntimeError: If no value is provided and no existing value is found. """ + existing_value = self.get_value(config_property_group, config_property_key) + if config_property_val: self.set_value(config_property_group, config_property_key, str(config_property_val)) return config_property_val - elif self.get_value(config_property_group, config_property_key): - return self.get_value(config_property_group, config_property_key) + elif existing_value: + return existing_value else: raise RuntimeError( - f"[DEPLOY] No {config_property_key} specified in config file or as cli option" + f"[DEPLOY] No value for {config_property_key} specified in config file or as cli" + " option" ) @property @@ -171,6 +215,10 @@ class Config(BaseConfig): @qml_files.setter def qml_files(self, qml_files): self._qml_files = qml_files + qml_files = [str(file.absolute().relative_to(self.project_dir.absolute())) + if file.absolute().is_relative_to(self.project_dir) else str(file.absolute()) + for file in self.qml_files] + self.set_value("qt", "qml_files", ",".join(qml_files)) @property def project_dir(self): @@ -179,6 +227,16 @@ class Config(BaseConfig): @project_dir.setter def project_dir(self, project_dir): self._project_dir = project_dir + self.set_value("app", "project_dir", str(project_dir)) + + @property + def project_file(self): + return self._project_file + + @project_file.setter + def project_file(self, project_file): + self._project_file = project_file + self.set_value("app", "project_file", str(project_file.relative_to(self.project_dir))) @property def title(self): @@ -187,7 +245,6 @@ class Config(BaseConfig): @title.setter def title(self, title): self._title = title - self.set_value("app", "title", title) @property def icon(self): @@ -205,6 +262,7 @@ class Config(BaseConfig): @source_file.setter def source_file(self, source_file: Path): self._source_file = source_file + self.set_value("app", "input_file", str(source_file)) @property def python_path(self): @@ -229,6 +287,8 @@ class Config(BaseConfig): @excluded_qml_plugins.setter def excluded_qml_plugins(self, excluded_qml_plugins): self._excluded_qml_plugins = excluded_qml_plugins + if excluded_qml_plugins: # check required for Android + self.set_value("qt", "excluded_qml_plugins", ",".join(excluded_qml_plugins)) @property def exe_dir(self): @@ -237,6 +297,7 @@ class Config(BaseConfig): @exe_dir.setter def exe_dir(self, exe_dir: Path): self._exe_dir = exe_dir + self.set_value("app", "exec_directory", str(exe_dir)) @property def modules(self): @@ -247,120 +308,92 @@ class Config(BaseConfig): self._modules = modules self.set_value("qt", "modules", ",".join(modules)) - def _find_and_set_qml_files(self): - """Fetches all the qml_files in the folder and sets them if the - field qml_files is empty in the config_dir""" + def _find_qml_files(self): + """ + Fetches all the qml_files in the folder and sets them if the + field qml_files is empty in the config_file + """ + qml_files = [] if self.project_data: - qml_files = self.project_data.qml_files + qml_files = [(self.project_dir / str(qml_file)) for qml_file in + self.project_data.qml_files] for sub_project_file in self.project_data.sub_projects_files: - qml_files.extend(ProjectData(project_file=sub_project_file).qml_files) - self.qml_files = qml_files + qml_files.extend([self.project_dir / str(qml_file) for qml_file in + ProjectData(project_file=sub_project_file).qml_files]) else: - qml_files_temp = None - if self.source_file and self.python_path: - if not self.qml_files: - qml_files_temp = list(self.source_file.parent.glob("**/*.qml")) - - # add all QML files, excluding the ones shipped with installed PySide6 - # The QML files shipped with PySide6 gets added if venv is used, - # because of recursive glob - if self.python_path.parent.parent == self.source_file.parent: - # python venv path is inside the main source dir - qml_files_temp = list( - set(qml_files_temp) - set(self.python_path.parent.parent.rglob("*.qml")) - ) - - if len(qml_files_temp) > 500: - if "site-packages" in str(qml_files_temp[-1]): - raise RuntimeError( - "You are including a lot of QML files from a local virtual env." - " This can lead to errors in deployment." - ) - else: - warnings.warn( - "You seem to include a lot of QML files. This can lead to errors in " - "deployment." - ) - - if qml_files_temp: - extra_qml_files = [Path(file) for file in qml_files_temp] - self.qml_files.extend(extra_qml_files) - if self.qml_files: - self.set_value( - "qt", - "qml_files", - ",".join([str(file.absolute().relative_to(self.project_dir)) - for file in self.qml_files]), - ) - logging.info("[DEPLOY] QML files identified and set in config_file") + # Filter out files from DEFAULT_IGNORE_DIRS + qml_files = [ + file for file in self.project_dir.glob("**/*.qml") + if all(part not in file.parts for part in DEFAULT_IGNORE_DIRS) + ] + + if len(qml_files) > 500: + warnings.warn( + "You seem to include a lot of QML files from " + f"{self.project_dir}. This can lead to errors in deployment." + ) + + return qml_files + + def _find_project_dir(self) -> Path: + if DesignStudioProject.is_ds_project(self.source_file): + return DesignStudioProject(self.source_file).project_dir - def _find_and_set_project_dir(self): # there is no other way to find the project_dir than assume it is the parent directory # of source_file - self.project_dir = self.source_file.parent + return self.source_file.parent - # update input_file path - self.set_value("app", "input_file", str(self.source_file.relative_to(self.project_dir))) + def _find_project_file(self) -> Path | None: + if not self.source_file: + raise RuntimeError("[DEPLOY] Source file not set in config file") - if self.project_dir != Path.cwd(): - self.set_value("app", "project_dir", str(self.project_dir)) + if DesignStudioProject.is_ds_project(self.source_file): + pyproject_location = self.source_file.parent else: - self.set_value("app", "project_dir", str(self.project_dir.relative_to(Path.cwd()))) - - def _find_and_set_project_file(self): - if self.project_dir: - files = list(self.project_dir.glob("*.pyproject")) - else: - logging.exception("[DEPLOY] Project directory not set in config file") - raise + pyproject_location = self.project_dir + files = list(pyproject_location.glob("*.pyproject")) if not files: logging.info("[DEPLOY] No .pyproject file found. Project file not set") - elif len(files) > 1: - logging.warning("DEPLOY: More that one .pyproject files found. Project file not set") - raise - else: - self.project_data = ProjectData(files[0]) - self.set_value("app", "project_file", str(files[0].relative_to(self.project_dir))) - logging.info(f"[DEPLOY] Project file {files[0]} found and set in config file") + return None + if len(files) > 1: + warnings.warn("DEPLOY: More that one .pyproject files found. Project file not set") + return None - def _find_and_set_excluded_qml_plugins(self): - if self.qml_files: - self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files, - dry_run=self.dry_run)) - self.excluded_qml_plugins = EXCLUDED_QML_PLUGINS.difference(self.qml_modules) + return files[0] - # needed for dry_run testing - self.excluded_qml_plugins = sorted(self.excluded_qml_plugins) + def _find_excluded_qml_plugins(self) -> list[str] | None: + if not self.qml_files and not DesignStudioProject.is_ds_project(self.source_file): + return None - if self.excluded_qml_plugins: - self.set_value("qt", "excluded_qml_plugins", ",".join(self.excluded_qml_plugins)) + self.qml_modules = set(run_qmlimportscanner(project_dir=self.project_dir, + dry_run=self.dry_run)) + excluded_qml_plugins = EXCLUDED_QML_PLUGINS.difference(self.qml_modules) - def _find_and_set_exe_dir(self): + # sorting needed for dry_run testing + return sorted(excluded_qml_plugins) + + def _find_exe_dir(self) -> Path: if self.project_dir == Path.cwd(): - self.exe_dir = self.project_dir.relative_to(Path.cwd()) - else: - self.exe_dir = self.project_dir - self.exe_dir = Path( - self.set_or_fetch( - config_property_val=self.exe_dir, config_property_key="exec_directory" - ) - ).absolute() + return self.project_dir.relative_to(Path.cwd()) + + return self.project_dir - def _find_and_set_pysidemodules(self): - self.modules = find_pyside_modules(project_dir=self.project_dir, - extra_ignore_dirs=self.extra_ignore_dirs, - project_data=self.project_data) + def _find_pysidemodules(self) -> list[str]: + modules = find_pyside_modules(project_dir=self.project_dir, + extra_ignore_dirs=self.extra_ignore_dirs, + project_data=self.project_data) logging.info("The following PySide modules were found from the Python files of " - f"the project {self.modules}") + f"the project {modules}") + return modules - def _find_and_set_qtquick_modules(self): + def _find_qtquick_modules(self) -> list[str]: """Identify if QtQuick is used in QML files and add them as dependency """ extra_modules = [] - if not self.qml_modules: - self.qml_modules = set(run_qmlimportscanner(qml_files=self.qml_files, + if not self.qml_modules and self.qml_files: + self.qml_modules = set(run_qmlimportscanner(project_dir=self.project_dir, dry_run=self.dry_run)) if "QtQuick" in self.qml_modules: @@ -369,23 +402,32 @@ class Config(BaseConfig): if "QtQuick.Controls" in self.qml_modules: extra_modules.append("QuickControls2") - self.modules += extra_modules + return extra_modules class DesktopConfig(Config): """Wrapper class around pysidedeploy.spec, but specific to Desktop deployment """ + + class NuitkaMode(Enum): + ONEFILE = "onefile" + STANDALONE = "standalone" + def __init__(self, config_file: Path, source_file: Path, python_exe: Path, dry_run: bool, - existing_config_file: bool = False, extra_ignore_dirs: List[str] = None): + existing_config_file: bool = False, extra_ignore_dirs: list[str] = None, + mode: str = "onefile", name: str = None): super().__init__(config_file, source_file, python_exe, dry_run, existing_config_file, - extra_ignore_dirs) + extra_ignore_dirs, name=name) self.dependency_reader = QtDependencyReader(dry_run=self.dry_run) - if self.get_value("qt", "modules"): - self.modules = self.get_value("qt", "modules").split(",") + modules = self.get_value("qt", "modules") + if modules: + self._modules = modules.split(",") else: - self._find_and_set_pysidemodules() - self._find_and_set_qtquick_modules() - self._find_dependent_qt_modules() + modules = self._find_pysidemodules() + modules += self._find_qtquick_modules() + modules += self._find_dependent_qt_modules(modules=modules) + # remove duplicates + self.modules = list(set(modules)) self._qt_plugins = [] if self.get_value("qt", "plugins"): @@ -400,7 +442,21 @@ class DesktopConfig(Config): if nuitka_macos_permissions: self._permissions = nuitka_macos_permissions.split(",") else: - self._find_and_set_permissions() + self.permissions = self._find_permissions() + + self._mode = self.NuitkaMode.ONEFILE + if self.get_value("nuitka", "mode") == self.NuitkaMode.STANDALONE.value: + self._mode = self.NuitkaMode.STANDALONE + elif mode == self.NuitkaMode.STANDALONE.value: + self.mode = self.NuitkaMode.STANDALONE + + if DesignStudioProject.is_ds_project(self.source_file): + ds_project = DesignStudioProject(self.source_file) + if not ds_project.compiled_resources_available(): + raise RuntimeError(f"[DEPLOY] Compiled resources file not found: " + f"{ds_project.compiled_resources_file.absolute()}. " + f"Build the project using 'pyside6-project build' or compile " + f"the resources manually using pyside6-rcc") @property def qt_plugins(self): @@ -420,24 +476,33 @@ class DesktopConfig(Config): self._permissions = permissions self.set_value("nuitka", "macos.permissions", ",".join(permissions)) - def _find_dependent_qt_modules(self): + @property + def mode(self): + return self._mode + + @mode.setter + def mode(self, mode: NuitkaMode): + self._mode = mode + self.set_value("nuitka", "mode", mode.value) + + def _find_dependent_qt_modules(self, modules: list[str]) -> list[str]: """ Given pysidedeploy_config.modules, find all the other dependent Qt modules. """ - all_modules = set(self.modules) + all_modules = set(modules) if not self.dependency_reader.lib_reader: warnings.warn(f"[DEPLOY] Unable to find {self.dependency_reader.lib_reader_name}. This " - "tool helps to find the Qt module dependencies of the application. " - "Skipping checking for dependencies.", category=RuntimeWarning) - return + f"tool helps to find the Qt module dependencies of the application. " + f"Skipping checking for dependencies.", category=RuntimeWarning) + return [] - for module_name in self.modules: + for module_name in modules: self.dependency_reader.find_dependencies(module=module_name, used_modules=all_modules) - self.modules = list(all_modules) + return list(all_modules) - def _find_and_set_permissions(self): + def _find_permissions(self) -> list[str]: """ Finds and sets the usage description string required for each permission requested by the macOS application. @@ -456,4 +521,4 @@ class DesktopConfig(Config): if perm_category in PERMISSION_MAP: permissions.append(PERMISSION_MAP[perm_category]) - self.permissions = permissions + return permissions diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec index 57f99eca..618a0294 100644 --- a/sources/pyside-tools/deploy_lib/default.spec +++ b/sources/pyside-tools/deploy_lib/default.spec @@ -10,7 +10,7 @@ project_dir = # Source file path input_file = -# Directory where exec is stored +# Directory where the executable output is generated exec_directory = # Path to .pyproject project file @@ -25,9 +25,7 @@ icon = python_path = # python packages to install -# ordered-set: increase compile time performance of nuitka packaging -# zstandard: provides final executable size optimization -packages = Nuitka==2.3.2 +packages = Nuitka==2.5.1 # buildozer: for deploying Android application android_packages = buildozer==1.5.0,cython==0.29.33 @@ -44,7 +42,8 @@ excluded_qml_plugins = # Qt modules used. Comma separated modules = -# Qt plugins used by the application +# Qt plugins used by the application. Only relevant for desktop deployment. For Qt plugins used +# in Android application see [android][plugins] plugins = [android] @@ -65,6 +64,9 @@ plugins = # eg: NSCameraUsageDescription:CameraAccess macos.permissions = +# mode of using Nuitka. Accepts standalone or onefile. Default is onefile. +mode = onefile + # (str) specify any extra nuitka arguments # eg: extra_args = --show-modules --follow-stdlib extra_args = --quiet --noinclude-qt-translations diff --git a/sources/pyside-tools/deploy_lib/dependency_util.py b/sources/pyside-tools/deploy_lib/dependency_util.py index 2d5b188d..6bdfb20b 100644 --- a/sources/pyside-tools/deploy_lib/dependency_util.py +++ b/sources/pyside-tools/deploy_lib/dependency_util.py @@ -1,5 +1,6 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import ast import re @@ -11,18 +12,17 @@ import logging import shutil import sys from pathlib import Path -from typing import List, Set from functools import lru_cache -from . import IMPORT_WARNING_PYSIDE, run_command +from . import IMPORT_WARNING_PYSIDE, DEFAULT_IGNORE_DIRS, run_command @lru_cache(maxsize=None) -def get_py_files(project_dir: Path, extra_ignore_dirs: List[Path] = None, project_data=None): +def get_py_files(project_dir: Path, extra_ignore_dirs: tuple[Path] = None, project_data=None): """Finds and returns all the Python files in the project """ py_candidates = [] - ignore_dirs = ["__pycache__", "env", "venv", "deployment"] + ignore_dirs = DEFAULT_IGNORE_DIRS.copy() if project_data: py_candidates = project_data.python_files @@ -53,7 +53,7 @@ def get_py_files(project_dir: Path, extra_ignore_dirs: List[Path] = None, projec # incase there is not .pyproject file, search all python files in project_dir, except # ignore_dirs if extra_ignore_dirs: - ignore_dirs.extend(extra_ignore_dirs) + ignore_dirs.update(extra_ignore_dirs) # find relevant .py files _walk = os.walk(project_dir) @@ -78,7 +78,7 @@ def get_ast(py_file: Path): return tree -def find_permission_categories(project_dir: Path, extra_ignore_dirs: List[Path] = None, +def find_permission_categories(project_dir: Path, extra_ignore_dirs: list[Path] = None, project_data=None): """Given the project directory, finds all the permission categories required by the project. eg: Camera, Bluetooth, Contacts etc. @@ -116,6 +116,8 @@ def find_permission_categories(project_dir: Path, extra_ignore_dirs: List[Path] return set(perm_categories) + if extra_ignore_dirs is not None: + extra_ignore_dirs = tuple(extra_ignore_dirs) py_candidates = get_py_files(project_dir, extra_ignore_dirs, project_data) for py_candidate in py_candidates: all_perm_categories = all_perm_categories.union(pyside_permission_imports(py_candidate)) @@ -126,7 +128,7 @@ def find_permission_categories(project_dir: Path, extra_ignore_dirs: List[Path] return all_perm_categories -def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None, +def find_pyside_modules(project_dir: Path, extra_ignore_dirs: list[Path] = None, project_data=None): """ Searches all the python files in the project to find all the PySide modules used by @@ -135,6 +137,7 @@ def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None, all_modules = set() mod_pattern = re.compile("PySide6.Qt(?P.*)") + @lru_cache def pyside_module_imports(py_file: Path): modules = [] try: @@ -142,7 +145,7 @@ def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None, for node in ast.walk(tree): if isinstance(node, ast.ImportFrom): main_mod_name = node.module - if main_mod_name.startswith("PySide6"): + if main_mod_name and main_mod_name.startswith("PySide6"): if main_mod_name == "PySide6": # considers 'from PySide6 import QtCore' for imported_module in node.names: @@ -171,6 +174,8 @@ def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None, return set(modules) + if extra_ignore_dirs is not None: + extra_ignore_dirs = tuple(extra_ignore_dirs) py_candidates = get_py_files(project_dir, extra_ignore_dirs, project_data) for py_candidate in py_candidates: all_modules = all_modules.union(pyside_module_imports(py_candidate)) @@ -217,12 +222,15 @@ class QtDependencyReader: """ Finds the path to the Qt libs directory inside PySide6 package installation """ + # PYSIDE-2785 consider dist-packages for Debian based systems for possible_site_package in site.getsitepackages(): - if possible_site_package.endswith("site-packages"): + if possible_site_package.endswith(("site-packages", "dist-packages")): self.pyside_install_dir = Path(possible_site_package) / "PySide6" + if self.pyside_install_dir.exists(): + break if not self.pyside_install_dir: - print("Unable to find site-packages. Exiting ...") + print("Unable to find where PySide6 is installed. Exiting ...") sys.exit(-1) if sys.platform == "win32": @@ -234,7 +242,7 @@ class QtDependencyReader: def lib_reader(self): return self._lib_reader - def find_dependencies(self, module: str, used_modules: Set[str] = None): + def find_dependencies(self, module: str, used_modules: set[str] = None): """ Given a Qt module, find all the other Qt modules it is dependent on and add it to the 'used_modules' set @@ -285,7 +293,7 @@ class QtDependencyReader: else: logging.info(f"[DEPLOY] No Qt dependencies found for {module}") - def find_plugin_dependencies(self, used_modules: List[str], python_exe: Path) -> List[str]: + def find_plugin_dependencies(self, used_modules: list[str], python_exe: Path) -> list[str]: """ Given the modules used by the application, returns all the required plugins """ diff --git a/sources/pyside-tools/deploy_lib/deploy_util.py b/sources/pyside-tools/deploy_lib/deploy_util.py index e8b05e99..3786cd20 100644 --- a/sources/pyside-tools/deploy_lib/deploy_util.py +++ b/sources/pyside-tools/deploy_lib/deploy_util.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import logging import shutil @@ -7,7 +8,7 @@ import sys from pathlib import Path from . import EXE_FORMAT -from .config import Config +from .config import Config, DesktopConfig def config_option_exists(): @@ -20,58 +21,86 @@ def config_option_exists(): def cleanup(config: Config, is_android: bool = False): """ - Cleanup the generated build folders/files + Cleanup the generated build folders/files. + + Parameters: + config (Config): The configuration object containing paths and settings. + is_android (bool): Flag indicating if the cleanup is for an Android project. Default is False. """ if config.generated_files_path.exists(): - shutil.rmtree(config.generated_files_path) - logging.info("[DEPLOY] Deployment directory purged") + try: + shutil.rmtree(config.generated_files_path) + logging.info("[DEPLOY] Deployment directory purged") + except PermissionError as e: + print(f"{type(e).__name__}: {e}") + logging.warning(f"[DEPLOY] Could not delete {config.generated_files_path}") if is_android: buildozer_spec: Path = config.project_dir / "buildozer.spec" if buildozer_spec.exists(): - buildozer_spec.unlink() - logging.info(f"[DEPLOY] {str(buildozer_spec)} removed") + try: + buildozer_spec.unlink() + logging.info(f"[DEPLOY] {str(buildozer_spec)} removed") + except PermissionError as e: + print(f"{type(e).__name__}: {e}") + logging.warning(f"[DEPLOY] Could not delete {buildozer_spec}") buildozer_build: Path = config.project_dir / ".buildozer" if buildozer_build.exists(): - shutil.rmtree(buildozer_build) - logging.info(f"[DEPLOY] {str(buildozer_build)} removed") + try: + shutil.rmtree(buildozer_build) + logging.info(f"[DEPLOY] {str(buildozer_build)} removed") + except PermissionError as e: + print(f"{type(e).__name__}: {e}") + logging.warning(f"[DEPLOY] Could not delete {buildozer_build}") -def create_config_file(dry_run: bool = False, config_file: Path = None, main_file: Path = None): +def create_config_file(main_file: Path, dry_run: bool = False): """ - Sets up a new pysidedeploy.spec or use an existing config file + Creates a new pysidedeploy.spec """ - if main_file: - if main_file.parent != Path.cwd(): - config_file = main_file.parent / "pysidedeploy.spec" - else: - config_file = Path.cwd() / "pysidedeploy.spec" - + config_file = main_file.parent / "pysidedeploy.spec" logging.info(f"[DEPLOY] Creating config file {config_file}") - if not dry_run: - shutil.copy(Path(__file__).parent / "default.spec", config_file) + default_config_file = Path(__file__).parent / "default.spec" # the config parser needs a reference to parse. So, in the case of --dry-run # use the default.spec file. if dry_run: - config_file = Path(__file__).parent / "default.spec" + return default_config_file + shutil.copy(default_config_file, config_file) return config_file -def finalize(config: Config): +def finalize(config: DesktopConfig): """ Copy the executable into the final location For Android deployment, this is done through buildozer """ - generated_exec_path = config.generated_files_path / (config.source_file.stem + EXE_FORMAT) - if generated_exec_path.exists() and config.exe_dir: - if sys.platform == "darwin": - shutil.copytree(generated_exec_path, config.exe_dir / (config.title + EXE_FORMAT), - dirs_exist_ok=True) - else: - shutil.copy(generated_exec_path, config.exe_dir) - print("[DEPLOY] Executed file created in " - f"{str(config.exe_dir / (config.source_file.stem + EXE_FORMAT))}") + exe_format = EXE_FORMAT + if config.mode == DesktopConfig.NuitkaMode.STANDALONE and sys.platform != "darwin": + exe_format = ".dist" + + generated_exec_path = config.generated_files_path / (config.source_file.stem + exe_format) + if not generated_exec_path.exists(): + logging.error(f"[DEPLOY] Executable not found at {generated_exec_path.absolute()}") + return + + logging.info(f"[DEPLOY] executable generated at {generated_exec_path.absolute()}") + if not config.exe_dir: + logging.info("[DEPLOY] Not copying output executable because no output directory specified") + return + + output_path = config.exe_dir / (config.title + exe_format) + + if sys.platform == "darwin" or config.mode == DesktopConfig.NuitkaMode.STANDALONE: + # Copy the folder that contains the executable + logging.info(f"[DEPLOY] copying generated folder to {output_path.absolute()}") + shutil.copytree(generated_exec_path, output_path, dirs_exist_ok=True) + else: + # Copy a single file + logging.info(f"[DEPLOY] copying generated file to {output_path.absolute()}") + shutil.copy(generated_exec_path, output_path) + + print(f"[DEPLOY] Executed file created in {output_path.absolute()}") diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index ac9a83f3..bc2896a4 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -1,13 +1,19 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations +# enables to use typehints for classes that has not been defined yet or imported +# used for resolving circular imports +from __future__ import annotations import logging import os +import shlex import sys from pathlib import Path -from typing import List -from . import MAJOR_VERSION, run_command +from project_lib import DesignStudioProject +from . import MAJOR_VERSION, run_command, DEFAULT_IGNORE_DIRS, PLUGINS_TO_REMOVE +from .config import DesktopConfig class Nuitka: @@ -35,11 +41,7 @@ class Nuitka: "generic" # plugins that error with Nuitka ] - # .webp are considered to be dlls by Nuitka instead of data files causing - # the packaging to fail - # https://github.com/Nuitka/Nuitka/issues/2854 - # TODO: Remove .webp when the issue is fixed - self.files_to_ignore = [".cpp.o", ".qsb", ".webp"] + self.files_to_ignore = [".cpp.o", ".qsb"] @staticmethod def icon_option(): @@ -50,12 +52,43 @@ class Nuitka: else: return "--macos-app-icon" - def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path], - qt_plugins: List[str], excluded_qml_plugins: List[str], icon: str, - dry_run: bool, permissions: List[str]): + def _create_windows_command(self, source_file: Path, command: list): + """ + Special case for Windows where the command length is limited to 8191 characters. + """ + + # if the platform is windows and the command is more than 8191 characters, the command + # will fail with the error message "The command line is too long". To avoid this, we will + # we will move the source_file to the intermediate source file called deploy_main.py, and + # include the Nuitka options direcly in the main file as mentioned in + # https://nuitka.net/user-documentation/user-manual.html#nuitka-project-options + + # convert command into a format recognized by Nuitka when written to the main file + # the first item is ignore because it is 'python -m nuitka' + nuitka_comment_options = [] + for command_entry in command[4:]: + nuitka_comment_options.append(f"# nuitka-project: {command_entry}") + nuitka_comment_options_str = "\n".join(nuitka_comment_options) + nuitka_comment_options_str += "\n" + + # read the content of the source file + new_source_content = (nuitka_comment_options_str + + Path(source_file).read_text(encoding="utf-8")) + + # create and write back the new source content to deploy_main.py + new_source_file = source_file.parent / "deploy_main.py" + new_source_file.write_text(new_source_content, encoding="utf-8") + + return new_source_file + + def create_executable(self, source_file: Path, extra_args: str, qml_files: list[Path], + qt_plugins: list[str], excluded_qml_plugins: list[str], icon: str, + dry_run: bool, permissions: list[str], + mode: DesktopConfig.NuitkaMode) -> str: qt_plugins = [plugin for plugin in qt_plugins if plugin not in self.qt_plugins_to_ignore] - extra_args = extra_args.split() + extra_args = shlex.split(extra_args) + # macOS uses the --standalone option by default to create an app bundle if sys.platform == "darwin": # create an app bundle extra_args.extend(["--standalone", "--macos-create-app-bundle"]) @@ -63,18 +96,33 @@ class Nuitka: for permission in permissions: extra_args.append(permission_pattern.format(permission=permission)) else: - extra_args.append("--onefile") + extra_args.append(f"--{mode.value}") qml_args = [] if qml_files: + # include all the subdirectories in the project directory as data directories + # This includes all the qml modules + all_relevant_subdirs = [] + for subdir in source_file.parent.iterdir(): + if subdir.is_dir() and subdir.name not in DEFAULT_IGNORE_DIRS: + extra_args.append(f"--include-data-dir={subdir}=" + f"./{subdir.name}") + all_relevant_subdirs.append(subdir) + + # find all the qml files that are not included via the data directories + extra_qml_files = [file for file in qml_files + if file.parent not in all_relevant_subdirs] + # This will generate options for each file using: # --include-data-files=ABSOLUTE_PATH_TO_FILE=RELATIVE_PATH_TO ROOT - # for each file. This will preserve the directory structure of QML resources. + # for each file. qml_args.extend( [f"--include-data-files={qml_file.resolve()}=" - f"./{qml_file.resolve().relative_to(source_file.parent)}" - for qml_file in qml_files] + f"./{qml_file.resolve().relative_to(source_file.resolve().parent)}" + for qml_file in extra_qml_files] ) + + if qml_files or DesignStudioProject.is_ds_project(source_file): # add qml plugin. The `qml`` plugin name is not present in the module json files shipped # with Qt and hence not in `qt_plugins``. However, Nuitka uses the 'qml' plugin name to # include the necessary qml plugins. There we have to add it explicitly for a qml @@ -111,8 +159,26 @@ class Nuitka: if qt_plugins: # sort qt_plugins so that the result is definitive when testing qt_plugins.sort() + # remove the following plugins from the qt_plugins list as Nuitka only checks + # for plugins within PySide6/Qt/plugins folder, and the following plugins + # are not present in the PySide6/Qt/plugins folder + qt_plugins = [plugin for plugin in qt_plugins if plugin not in PLUGINS_TO_REMOVE] qt_plugins_str = ",".join(qt_plugins) command.append(f"--include-qt-plugins={qt_plugins_str}") + long_command = False + if sys.platform == "win32" and len(" ".join(str(cmd) for cmd in command)) > 7000: + logging.info("[DEPLOY] Nuitka command too long for Windows. " + "Copying the contents of main Python file to an intermediate " + "deploy_main.py file") + long_command = True + new_source_file = self._create_windows_command(source_file=source_file, command=command) + command = self.nuitka + [os.fspath(new_source_file)] + command_str, _ = run_command(command=command, dry_run=dry_run) + + # if deploy_main.py exists, delete it after the command is run + if long_command: + os.remove(source_file.parent / "deploy_main.py") + return command_str diff --git a/sources/pyside-tools/deploy_lib/python_helper.py b/sources/pyside-tools/deploy_lib/python_helper.py index 7cbf323e..f234f937 100644 --- a/sources/pyside-tools/deploy_lib/python_helper.py +++ b/sources/pyside-tools/deploy_lib/python_helper.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import logging import os diff --git a/sources/pyside-tools/metaobjectdump.py b/sources/pyside-tools/metaobjectdump.py index 0970f997..d14c3334 100644 --- a/sources/pyside-tools/metaobjectdump.py +++ b/sources/pyside-tools/metaobjectdump.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import ast import json @@ -8,7 +9,7 @@ import sys import tokenize from argparse import ArgumentParser, RawTextHelpFormatter from pathlib import Path -from typing import Dict, List, Optional, Set, Tuple, Union +from typing import Union DESCRIPTION = """Parses Python source code to create QObject metatype @@ -38,32 +39,38 @@ ITEM_MODELS = ["QAbstractListModel", "QAbstractProxyModel", QOBJECT_DERIVED = ["QObject", "QQuickItem", "QQuickPaintedItem"] + ITEM_MODELS +# Python 3.9 does not support this syntax, yet +# AstDecorator = ast.Name | ast.Call +# AstPySideTypeSpec = ast.Name | ast.Constant AstDecorator = Union[ast.Name, ast.Call] AstPySideTypeSpec = Union[ast.Name, ast.Constant] -ClassList = List[dict] +ClassList = list[dict] -PropertyEntry = Dict[str, Union[str, int, bool]] +# PropertyEntry = dict[str, str | int | bool] +PropertyEntry = dict[str, Union[str, int, bool]] -Argument = Dict[str, str] -Arguments = List[Argument] -Signal = Dict[str, Union[str, Arguments]] -Slot = Dict[str, Union[str, Arguments]] +Argument = dict[str, str] +Arguments = list[Argument] +# Signal = dict[str, str | Arguments] +# Slot = dict[str, str | Arguments] +Signal = dict[str, Union[str, Arguments]] +Slot = dict[str, Union[str, Arguments]] -def _decorator(name: str, value: str) -> Dict[str, str]: +def _decorator(name: str, value: str) -> dict[str, str]: """Create a QML decorator JSON entry""" return {"name": name, "value": value} -def _attribute(node: ast.Attribute) -> Tuple[str, str]: +def _attribute(node: ast.Attribute) -> tuple[str, str]: """Split an attribute.""" return node.value.id, node.attr -def _name(node: Union[ast.Name, ast.Attribute]) -> str: +def _name(node: ast.Name | ast.Attribute) -> str: """Return the name of something that is either an attribute or a name, such as base classes or call.func""" if isinstance(node, ast.Attribute): @@ -82,14 +89,14 @@ def _python_to_cpp_type(type: str) -> str: return c if c else type -def _parse_property_kwargs(keywords: List[ast.keyword], prop: PropertyEntry): +def _parse_property_kwargs(keywords: list[ast.keyword], prop: PropertyEntry): """Parse keyword arguments of @Property""" for k in keywords: if k.arg == "notify": prop["notify"] = _name(k.value) -def _parse_assignment(node: ast.Assign) -> Tuple[Optional[str], Optional[ast.AST]]: +def _parse_assignment(node: ast.Assign) -> tuple[str | None, ast.AST | None]: """Parse an assignment and return a tuple of name, value.""" if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name): var_name = node.targets[0].id @@ -109,7 +116,7 @@ def _parse_call_args(call: ast.Call): """Parse arguments of a Signal call/Slot decorator (type list).""" result: Arguments = [] for n, arg in enumerate(call.args): - par_name = f"a{n+1}" + par_name = f"a{n + 1}" par_type = _parse_pyside_type(arg) result.append({"name": par_name, "type": par_type}) return result @@ -144,10 +151,10 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): self._context = context self._json_class_list: ClassList = [] # Property by name, which will be turned into the JSON List later - self._properties: List[PropertyEntry] = [] - self._signals: List[Signal] = [] + self._properties: list[PropertyEntry] = [] + self._signals: list[Signal] = [] self._within_class: bool = False - self._qt_modules: Set[str] = set() + self._qt_modules: set[str] = set() self._qml_import_name = "" self._qml_import_major_version = 0 self._qml_import_minor_version = 0 @@ -158,7 +165,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): def qml_import_name(self) -> str: return self._qml_import_name - def qml_import_version(self) -> Tuple[int, int]: + def qml_import_version(self) -> tuple[int, int]: return (self._qml_import_major_version, self._qml_import_minor_version) def qt_modules(self): @@ -215,7 +222,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): if bases: data["superClasses"] = bases - class_decorators: List[dict] = [] + class_decorators: list[dict] = [] for d in node.decorator_list: self._parse_class_decorator(d, class_decorators) @@ -247,7 +254,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): self._parse_function_decorator(node.name, d) def _parse_class_decorator(self, node: AstDecorator, - class_decorators: List[dict]): + class_decorators: list[dict]): """Parse ClassInfo decorators.""" if isinstance(node, ast.Call): name = _func_name(node) @@ -298,7 +305,7 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): return -1 def _create_property_entry(self, name: str, type: str, - getter: Optional[str] = None) -> PropertyEntry: + getter: str | None = None) -> PropertyEntry: """Create a property JSON entry.""" result: PropertyEntry = {"name": name, "type": type, "index": len(self._properties)} @@ -390,7 +397,7 @@ def create_arg_parser(desc: str) -> ArgumentParser: def parse_file(file: Path, context: VisitorContext, - suppress_file: bool = False) -> Optional[Dict]: + suppress_file: bool = False) -> dict | None: """Parse a file and return its json data""" ast_tree = MetaObjectDumpVisitor.create_ast(file) visitor = MetaObjectDumpVisitor(context) diff --git a/sources/pyside-tools/project.py b/sources/pyside-tools/project.py index 3706a298..1af51e07 100644 --- a/sources/pyside-tools/project.py +++ b/sources/pyside-tools/project.py @@ -1,6 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - +from __future__ import annotations """ Builds a '.pyproject' file @@ -21,16 +21,15 @@ handled per project file. """ import sys import os -from typing import List, Tuple, Optional from pathlib import Path from argparse import ArgumentParser, RawTextHelpFormatter -from project import (QmlProjectData, check_qml_decorators, is_python_file, - QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, - SHADER_SUFFIXES, TRANSLATION_SUFFIX, - requires_rebuild, run_command, remove_path, - ProjectData, resolve_project_file, new_project, - ProjectType, ClOptions) +from project_lib import (QmlProjectData, check_qml_decorators, is_python_file, + QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, + SHADER_SUFFIXES, TRANSLATION_SUFFIX, + requires_rebuild, run_command, remove_path, + ProjectData, resolve_project_file, new_project, + ProjectType, ClOptions, DesignStudioProject) MODE_HELP = """build Builds the project run Builds the project and runs the first file") @@ -57,7 +56,7 @@ NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK, "new-widget": ProjectType.WIDGET} -def _sort_sources(files: List[Path]) -> List[Path]: +def _sort_sources(files: list[Path]) -> list[Path]: """Sort the sources for building, ensure .qrc is last since it might depend on generated files.""" @@ -71,14 +70,15 @@ class Project: """ Class to wrap the various operations on Project """ + def __init__(self, project_file: Path): self.project = ProjectData(project_file=project_file) self.cl_options = ClOptions() # Files for QML modules using the QmlElement decorators - self._qml_module_sources: List[Path] = [] - self._qml_module_dir: Optional[Path] = None - self._qml_dir_file: Optional[Path] = None + self._qml_module_sources: list[Path] = [] + self._qml_module_dir: Path | None = None + self._qml_dir_file: Path | None = None self._qml_project_data = QmlProjectData() self._qml_module_check() @@ -113,20 +113,24 @@ class Project: print(f"{self.project.project_file.name}, {count} QML file(s)," f" {self._qml_project_data}") - def _get_artifacts(self, file: Path) -> Tuple[List[Path], Optional[List[str]]]: + def _get_artifacts(self, file: Path, output_path: Path | None = None) -> \ + tuple[list[Path], list[str] | None]: """Return path and command for a file's artifact""" if file.suffix == ".ui": # Qt form files py_file = f"{file.parent}/ui_{file.stem}.py" - return ([Path(py_file)], [UIC_CMD, os.fspath(file), "--rc-prefix", "-o", py_file]) + return [Path(py_file)], [UIC_CMD, os.fspath(file), "--rc-prefix", "-o", py_file] if file.suffix == ".qrc": # Qt resources - py_file = f"{file.parent}/rc_{file.stem}.py" - return ([Path(py_file)], [RCC_CMD, os.fspath(file), "-o", py_file]) + if not output_path: + py_file = f"{file.parent}/rc_{file.stem}.py" + else: + py_file = str(output_path.resolve()) + return [Path(py_file)], [RCC_CMD, os.fspath(file), "-o", py_file] # generate .qmltypes from sources with Qml decorators if file.suffix == ".py" and file in self._qml_module_sources: assert self._qml_module_dir qml_module_dir = os.fspath(self._qml_module_dir) json_file = f"{qml_module_dir}/{file.stem}{METATYPES_JSON_SUFFIX}" - return ([Path(json_file)], [MOD_CMD, "-o", json_file, os.fspath(file)]) + return [Path(json_file)], [MOD_CMD, "-o", json_file, os.fspath(file)] # Run qmltyperegistrar if file.name.endswith(METATYPES_JSON_SUFFIX): assert self._qml_module_dir @@ -137,19 +141,19 @@ class Project: os.fspath(qmltypes_file), "-o", os.fspath(cpp_file), os.fspath(file)] cmd.extend(self._qml_project_data.registrar_options()) - return ([qmltypes_file, cpp_file], cmd) + return [qmltypes_file, cpp_file], cmd if file.name.endswith(TRANSLATION_SUFFIX): qm_file = f"{file.parent}/{file.stem}.qm" cmd = [LRELEASE_CMD, os.fspath(file), "-qm", qm_file] - return ([Path(qm_file)], cmd) + return [Path(qm_file)], cmd if file.suffix in SHADER_SUFFIXES: qsb_file = f"{file.parent}/{file.stem}.qsb" cmd = [QSB_CMD, "-o", qsb_file, os.fspath(file)] - return ([Path(qsb_file)], cmd) + return [Path(qsb_file)], cmd - return ([], None) + return [], None def _regenerate_qmldir(self): """Regenerate the 'qmldir' file.""" @@ -161,22 +165,39 @@ class Project: for f in self._qml_module_dir.glob("*.qmltypes"): qf.write(f"typeinfo {f.name}\n") - def _build_file(self, source: Path): - """Build an artifact.""" - artifacts, command = self._get_artifacts(source) + def _build_file(self, source: Path, output_path: Path | None = None): + """Build an artifact if necessary.""" + artifacts, command = self._get_artifacts(source, output_path) for artifact in artifacts: if self.cl_options.force or requires_rebuild([source], artifact): run_command(command, cwd=self.project.project_file.parent) self._build_file(artifact) # Recurse for QML (json->qmltypes) + def build_design_studio_resources(self): + """ + The resources that need to be compiled are defined in autogen/settings.py + """ + ds_project = DesignStudioProject(self.project.main_file) + if (resources_file_path := ds_project.get_resource_file_path()) is None: + return + + compiled_resources_file_path = ds_project.get_compiled_resources_file_path() + self._build_file(resources_file_path, compiled_resources_file_path) + def build(self): - """Build.""" + """Build the whole project""" for sub_project_file in self.project.sub_projects_files: Project(project_file=sub_project_file).build() + if self._qml_module_dir: self._qml_module_dir.mkdir(exist_ok=True, parents=True) + for file in _sort_sources(self.project.files): self._build_file(file) + + if DesignStudioProject.is_ds_project(self.project.main_file): + self.build_design_studio_resources() + self._regenerate_qmldir() def run(self): @@ -207,6 +228,9 @@ class Project: first_module_dir = self._qml_module_dir.parts[project_dir_parts] remove_path(self.project.project_file.parent / first_module_dir) + if DesignStudioProject.is_ds_project(self.project.main_file): + DesignStudioProject(self.project.main_file).clean() + def _qmllint(self): """Helper for running qmllint on .qml files (non-recursive).""" if not self.project.qml_files: @@ -243,44 +267,31 @@ class Project: return source_files = self.project.python_files + self.project.ui_files - cmd_prefix = [LUPDATE_CMD] + [p.name for p in source_files] + project_dir = self.project.project_file.parent + cmd_prefix = [LUPDATE_CMD] + [os.fspath(p.relative_to(project_dir)) for p in source_files] cmd_prefix.append("-ts") for ts_file in self.project.ts_files: if requires_rebuild(source_files, ts_file): cmd = cmd_prefix cmd.append(ts_file.name) - run_command(cmd, cwd=self.project.project_file.parent) + run_command(cmd, cwd=project_dir) -if __name__ == "__main__": - parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) - parser.add_argument("--quiet", "-q", action="store_true", help="Quiet") - parser.add_argument("--dry-run", "-n", action="store_true", help="Only print commands") - parser.add_argument("--force", "-f", action="store_true", help="Force rebuild") - parser.add_argument("--qml-module", "-Q", action="store_true", - help="Perform check for QML module") - mode_choices = ["build", "run", "clean", "qmllint", "deploy", "lupdate"] - mode_choices.extend(NEW_PROJECT_TYPES.keys()) - parser.add_argument("mode", choices=mode_choices, default="build", - type=str, help=MODE_HELP) - parser.add_argument("file", help="Project file", nargs="?", type=str) - - options = parser.parse_args() - cl_options = ClOptions(dry_run=options.dry_run, quiet=options.quiet, force=options.force, - qml_module=options.qml_module) - - mode = options.mode +def main(mode: str = None, file: str = None, dry_run: bool = False, quiet: bool = False, + force: bool = False, qml_module: bool = None): + cl_options = ClOptions(dry_run=dry_run, quiet=quiet, # noqa: F841 + force=force, qml_module=qml_module) new_project_type = NEW_PROJECT_TYPES.get(mode) if new_project_type: - if not options.file: + if not file: print(f"{mode} requires a directory name.", file=sys.stderr) sys.exit(1) - sys.exit(new_project(options.file, new_project_type)) + sys.exit(new_project(file, new_project_type)) - project_file = resolve_project_file(options.file) + project_file = resolve_project_file(file) if not project_file: - print(f"Cannot determine project_file {options.file}", file=sys.stderr) + print(f"Cannot determine project_file {file}", file=sys.stderr) sys.exit(1) project = Project(project_file) if mode == "build": @@ -298,3 +309,23 @@ if __name__ == "__main__": else: print(f"Invalid mode {mode}", file=sys.stderr) sys.exit(1) + + +if __name__ == "__main__": + parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) + parser.add_argument("--quiet", "-q", action="store_true", help="Quiet") + parser.add_argument("--dry-run", "-n", action="store_true", help="Only print commands") + parser.add_argument("--force", "-f", action="store_true", help="Force rebuild") + parser.add_argument("--qml-module", "-Q", action="store_true", + help="Perform check for QML module") + mode_choices = ["build", "run", "clean", "qmllint", "deploy", "lupdate"] + mode_choices.extend(NEW_PROJECT_TYPES.keys()) + parser.add_argument("mode", choices=mode_choices, default="build", + type=str, help=MODE_HELP) + + # TODO: improve the command structure. + # "File" argument is not correct when doing new-... project + parser.add_argument("file", help="Project file", nargs="?", type=str) + + args = parser.parse_args() + main(args.mode, args.file, args.dry_run, args.quiet, args.force, args.qml_module) diff --git a/sources/pyside-tools/project.pyproject b/sources/pyside-tools/project.pyproject index 346ef046..49c7adab 100644 --- a/sources/pyside-tools/project.pyproject +++ b/sources/pyside-tools/project.pyproject @@ -1,4 +1,4 @@ { - "files": ["project.py", "project/__init__.py", "project/newproject.py", - "project/project_data.py", "project/utils.py"] + "files": ["project.py", "project_lib/__init__.py", "project_lib/design_studio_project.py", + "project_lib/newproject.py", "project_lib/project_data.py", "project_lib/utils.py"] } diff --git a/sources/pyside-tools/project/__init__.py b/sources/pyside-tools/project_lib/__init__.py similarity index 93% rename from sources/pyside-tools/project/__init__.py rename to sources/pyside-tools/project_lib/__init__.py index e57a9ff8..aafaa44b 100644 --- a/sources/pyside-tools/project/__init__.py +++ b/sources/pyside-tools/project_lib/__init__.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from dataclasses import dataclass @@ -44,3 +45,4 @@ from .utils import (run_command, requires_rebuild, remove_path, package_dir, qtp from .project_data import (is_python_file, ProjectData, QmlProjectData, check_qml_decorators) from .newproject import new_project, ProjectType +from .design_studio_project import DesignStudioProject diff --git a/sources/pyside-tools/project_lib/design_studio_project.py b/sources/pyside-tools/project_lib/design_studio_project.py new file mode 100644 index 00000000..6c69e4c0 --- /dev/null +++ b/sources/pyside-tools/project_lib/design_studio_project.py @@ -0,0 +1,65 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +import logging +from pathlib import Path +from typing import Optional + + +class DesignStudioProject: + """ + Class to handle Design Studio projects. The project structure is as follows: + - Python folder + - autogen folder + - settings.py + - resources.py (Compiled resources) + - main.py + .qrc (Resources collection file) + .qmlproject + .qmlproject.qtds (should be added to .gitignore) + ... Other files and folders ... + """ + + def __init__(self, main_file: Path): + self.main_file = main_file + self.project_dir = main_file.parent.parent + self.compiled_resources_file = self.main_file.parent / "autogen" / "resources.py" + + @staticmethod + def is_ds_project(main_file: Path) -> bool: + return bool(*main_file.parent.parent.glob("*.qmlproject")) + + def compiled_resources_available(self) -> bool: + """ + Returns whether the resources of the project have been compiled into a .py file. + TODO: Make the resources path configurable. Wait for the pyproject TOML configuration + """ + return self.compiled_resources_file.exists() + + def get_resource_file_path(self) -> Optional[Path]: + """ + Return the path to the *.qrc resources file from the project root folder. + If not found, log an error message and return None + If multiple files are found, log an error message and return None + If a single file is found, return its path + """ + resource_files = list(self.project_dir.glob("*.qrc")) + if not resource_files: + logging.error("No *.qrc resources file found in the project root folder") + return None + if len(resource_files) > 1: + logging.error("Multiple *.qrc resources files found in the project root folder") + return None + return resource_files[0] + + def get_compiled_resources_file_path(self) -> Path: + """ + Return the path of the output file generated by compiling the *.qrc resources file + """ + # TODO: make this more robust and configurable. Wait for the pyproject TOML configuration + return self.main_file.parent / "autogen" / "resources.py" + + def clean(self): + """ + Remove the compiled resources file if it exists + """ + self.compiled_resources_file.unlink(missing_ok=True) diff --git a/sources/pyside-tools/project/newproject.py b/sources/pyside-tools/project_lib/newproject.py similarity index 97% rename from sources/pyside-tools/project/newproject.py rename to sources/pyside-tools/project_lib/newproject.py index c363a9fc..80ed5a75 100644 --- a/sources/pyside-tools/project/newproject.py +++ b/sources/pyside-tools/project_lib/newproject.py @@ -1,17 +1,17 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import json import os import sys from enum import Enum from pathlib import Path -from typing import List, Tuple """New project generation code.""" -Project = List[Tuple[str, str]] # tuple of (filename, contents). +Project = list[tuple[str, str]] # tuple of (filename, contents). class ProjectType(Enum): diff --git a/sources/pyside-tools/project/project_data.py b/sources/pyside-tools/project_lib/project_data.py similarity index 91% rename from sources/pyside-tools/project/project_data.py rename to sources/pyside-tools/project_lib/project_data.py index 52e20be3..445f02b8 100644 --- a/sources/pyside-tools/project/project_data.py +++ b/sources/pyside-tools/project_lib/project_data.py @@ -1,11 +1,11 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import json import os import subprocess import sys -from typing import List, Tuple from pathlib import Path from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, TRANSLATION_SUFFIX, qt_metatype_json_dir, MOD_CMD, QML_IMPORT_MAJOR_VERSION, @@ -20,22 +20,22 @@ def is_python_file(file: Path) -> bool: class ProjectData: def __init__(self, project_file: Path) -> None: """Parse the project.""" - self._project_file = project_file - self._sub_projects_files: List[Path] = [] + self._project_file = project_file.resolve() + self._sub_projects_files: list[Path] = [] # All sources except subprojects - self._files: List[Path] = [] + self._files: list[Path] = [] # QML files - self._qml_files: List[Path] = [] + self._qml_files: list[Path] = [] # Python files self.main_file: Path = None - self._python_files: List[Path] = [] + self._python_files: list[Path] = [] # ui files - self._ui_files: List[Path] = [] + self._ui_files: list[Path] = [] # qrc files - self._qrc_files: List[Path] = [] + self._qrc_files: list[Path] = [] # ts files - self._ts_files: List[Path] = [] + self._ts_files: list[Path] = [] with project_file.open("r") as pyf: pyproject = json.load(pyf) @@ -115,7 +115,7 @@ class ProjectData: # __main__ not found print( - "Python file with main function not found. Add the file to" f" {self.project_file}", + f"Python file with main function not found. Add the file to {self.project_file}", file=sys.stderr, ) sys.exit(1) @@ -128,7 +128,7 @@ class QmlProjectData: self._import_name: str = "" self._import_major_version: int = 0 self._import_minor_version: int = 0 - self._qt_modules: List[str] = [] + self._qt_modules: list[str] = [] def registrar_options(self): result = [ @@ -141,7 +141,7 @@ class QmlProjectData: ] if self._qt_modules: # Add Qt modules as foreign types - foreign_files: List[str] = [] + foreign_files: list[str] = [] meta_dir = qt_metatype_json_dir() for mod in self._qt_modules: mod_id = mod[2:].lower() @@ -198,7 +198,7 @@ class QmlProjectData: return len(self._import_name) > 0 and self._import_major_version > 0 -def _has_qml_decorated_class(class_list: List) -> bool: +def _has_qml_decorated_class(class_list: list) -> bool: """Check for QML-decorated classes in the moc json output.""" for d in class_list: class_infos = d.get("classInfos") @@ -209,7 +209,7 @@ def _has_qml_decorated_class(class_list: List) -> bool: return False -def check_qml_decorators(py_file: Path) -> Tuple[bool, QmlProjectData]: +def check_qml_decorators(py_file: Path) -> tuple[bool, QmlProjectData]: """Check if a Python file has QML-decorated classes by running a moc check and return whether a class was found and the QML data.""" data = None diff --git a/sources/pyside-tools/project/utils.py b/sources/pyside-tools/project_lib/utils.py similarity index 70% rename from sources/pyside-tools/project/utils.py rename to sources/pyside-tools/project_lib/utils.py index d2bff65a..f1e3f0c0 100644 --- a/sources/pyside-tools/project/utils.py +++ b/sources/pyside-tools/project_lib/utils.py @@ -1,15 +1,16 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import sys import subprocess from pathlib import Path -from typing import List, Dict, Optional +import xml.etree.ElementTree as ET from . import QTPATHS_CMD, PROJECT_FILE_SUFFIX, ClOptions -def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False): +def run_command(command: list[str], cwd: str = None, ignore_fail: bool = False): """Run a command observing quiet/dry run""" cloptions = ClOptions() if not cloptions.quiet or cloptions.dry_run: @@ -20,14 +21,33 @@ def run_command(command: List[str], cwd: str = None, ignore_fail: bool = False): sys.exit(ex) -def requires_rebuild(sources: List[Path], artifact: Path) -> bool: +def qrc_file_requires_rebuild(resources_file_path: Path, compiled_resources_path: Path) -> bool: + """Returns whether a compiled qrc file needs to be rebuilt based on the files that references""" + root_element = ET.parse(resources_file_path).getroot() + project_root = resources_file_path.parent + + files = [project_root / file.text for file in root_element.findall(".//file")] + + compiled_resources_time = compiled_resources_path.stat().st_mtime + # If any of the resource files has been modified after the compiled qrc file, the compiled qrc + # file needs to be rebuilt + if any(file.is_file() and file.stat().st_mtime > compiled_resources_time for file in files): + return True + return False + + +def requires_rebuild(sources: list[Path], artifact: Path) -> bool: """Returns whether artifact needs to be rebuilt depending on sources""" if not artifact.is_file(): return True + artifact_mod_time = artifact.stat().st_mtime for source in sources: if source.stat().st_mtime > artifact_mod_time: return True + # The .qrc file references other files that might have changed + if source.suffix == '.qrc' and qrc_file_requires_rebuild(source, artifact): + return True return False @@ -58,10 +78,10 @@ def package_dir() -> Path: return Path(__file__).resolve().parents[2] -_qtpaths_info: Dict[str, str] = {} +_qtpaths_info: dict[str, str] = {} -def qtpaths() -> Dict[str, str]: +def qtpaths() -> dict[str, str]: """Run qtpaths and return a dict of values.""" global _qtpaths_info if not _qtpaths_info: @@ -73,7 +93,7 @@ def qtpaths() -> Dict[str, str]: return _qtpaths_info -_qt_metatype_json_dir: Optional[Path] = None +_qt_metatype_json_dir: Path | None = None def qt_metatype_json_dir() -> Path: @@ -95,7 +115,7 @@ def qt_metatype_json_dir() -> Path: return _qt_metatype_json_dir -def resolve_project_file(cmdline: str) -> Optional[Path]: +def resolve_project_file(cmdline: str) -> Path | None: """Return the project file from the command line value, either from the file argument or directory""" project_file = Path(cmdline).resolve() if cmdline else Path.cwd() diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py index b369be8a..887f2bdd 100644 --- a/sources/pyside-tools/pyside_tool.py +++ b/sources/pyside-tools/pyside_tool.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import importlib import os @@ -214,8 +215,9 @@ def deploy(): def android_deploy(): - if not sys.platform == "linux": - print("pyside6-android-deploy only works from a Linux host") + if sys.platform == "win32": + print("pyside6-android-deploy only works from a Unix host and not a Windows host", + file=sys.stderr) else: android_requirements_file = Path(__file__).parent / "requirements-android.txt" with open(android_requirements_file, 'r', encoding='UTF-8') as file: @@ -239,5 +241,9 @@ def balsamui(): qt_tool_wrapper("balsamui", sys.argv[1:]) +def svgtoqml(): + qt_tool_wrapper("svgtoqml", sys.argv[1:]) + + if __name__ == "__main__": main() diff --git a/sources/pyside-tools/qml.py b/sources/pyside-tools/qml.py index 5d029f93..f138d2e7 100644 --- a/sources/pyside-tools/qml.py +++ b/sources/pyside-tools/qml.py @@ -1,5 +1,6 @@ # Copyright (C) 2018 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """pyside6-qml tool implementation. This tool mimics the capabilities of qml runtime utility for python and enables quick protyping with python modules""" @@ -11,7 +12,6 @@ import sys import os from pathlib import Path from pprint import pprint -from typing import List, Set from PySide6.QtCore import QCoreApplication, Qt, QLibraryInfo, QUrl, SignalInstance from PySide6.QtGui import QGuiApplication, QSurfaceFormat @@ -20,7 +20,7 @@ from PySide6.QtQuick import QQuickView, QQuickItem from PySide6.QtWidgets import QApplication -def import_qml_modules(qml_parent_path: Path, module_paths: List[Path] = []): +def import_qml_modules(qml_parent_path: Path, module_paths: list[Path] = []): ''' Import all the python modules in the qml_parent_path. This way all the classes containing the @QmlElement/@QmlNamedElement are also imported @@ -42,7 +42,7 @@ def import_qml_modules(qml_parent_path: Path, module_paths: List[Path] = []): elif module_path.exists() and module_path.suffix == ".py": search_file_paths.append(module_path) - def import_module(import_module_paths: Set[Path]): + def import_module(import_module_paths: set[Path]): """Import the modules in 'import_module_paths'""" for module_path in import_module_paths: module_name = module_path.name[:-3] @@ -243,4 +243,6 @@ if __name__ == "__main__": if args.config == "resizeToItem": logging.info("qml: Not a QQuickview item. resizeToItem is done by default") - sys.exit(app.exec()) + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/sources/pyside-tools/qtpy2cpp.py b/sources/pyside-tools/qtpy2cpp.py index 857b12b6..bed933ca 100644 --- a/sources/pyside-tools/qtpy2cpp.py +++ b/sources/pyside-tools/qtpy2cpp.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import logging import os diff --git a/sources/pyside-tools/qtpy2cpp_lib/astdump.py b/sources/pyside-tools/qtpy2cpp_lib/astdump.py index d92fb758..469b0368 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/astdump.py +++ b/sources/pyside-tools/qtpy2cpp_lib/astdump.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """Tool to dump a Python AST""" diff --git a/sources/pyside-tools/qtpy2cpp_lib/formatter.py b/sources/pyside-tools/qtpy2cpp_lib/formatter.py index 9a38e803..ad490371 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/formatter.py +++ b/sources/pyside-tools/qtpy2cpp_lib/formatter.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """C++ formatting helper functions and formatter class""" diff --git a/sources/pyside-tools/qtpy2cpp_lib/nodedump.py b/sources/pyside-tools/qtpy2cpp_lib/nodedump.py index de62e970..c101f1fd 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/nodedump.py +++ b/sources/pyside-tools/qtpy2cpp_lib/nodedump.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """Helper to dump AST nodes for debugging""" diff --git a/sources/pyside-tools/qtpy2cpp_lib/qt.py b/sources/pyside-tools/qtpy2cpp_lib/qt.py index 69bd54ae..36fb83b4 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/qt.py +++ b/sources/pyside-tools/qtpy2cpp_lib/qt.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """Provides some type information on Qt classes""" @@ -16,38 +17,44 @@ class ClassFlag(Flag): _QT_CLASS_FLAGS = { - "QBrush": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QGradient": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QIcon": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + # QtCore + "QCoreApplication": ClassFlag.INSTANTIATE_ON_STACK, + "QFile": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, + "QFileInfo": ClassFlag.INSTANTIATE_ON_STACK, "QLine": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, "QLineF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QPixmap": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + "QModelIndex": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + "QPoint": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, "QPointF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, "QRect": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, "QRectF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + "QSaveFile": ClassFlag.INSTANTIATE_ON_STACK, + "QSettings": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, + "QSize": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, "QSizeF": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, "QString": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, - "QFile": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, - "QSettings": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, "QTextStream": ClassFlag.PASS_BY_REF | ClassFlag.INSTANTIATE_ON_STACK, + # QtGui + "QBrush": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, "QColor": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, - "QPoint": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, - "QSize": ClassFlag.PASS_BY_VALUE | ClassFlag.INSTANTIATE_ON_STACK, + "QGradient": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + "QGuiApplication": ClassFlag.INSTANTIATE_ON_STACK, + "QIcon": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + "QPainter": ClassFlag.INSTANTIATE_ON_STACK, + "QPen": ClassFlag.INSTANTIATE_ON_STACK, + "QPixmap": ClassFlag.PASS_BY_CONSTREF | ClassFlag.INSTANTIATE_ON_STACK, + # QtWidgets "QApplication": ClassFlag.INSTANTIATE_ON_STACK, "QColorDialog": ClassFlag.INSTANTIATE_ON_STACK, - "QCoreApplication": ClassFlag.INSTANTIATE_ON_STACK, "QFileDialog": ClassFlag.INSTANTIATE_ON_STACK, - "QFileInfo": ClassFlag.INSTANTIATE_ON_STACK, "QFontDialog": ClassFlag.INSTANTIATE_ON_STACK, - "QGuiApplication": ClassFlag.INSTANTIATE_ON_STACK, "QMessageBox": ClassFlag.INSTANTIATE_ON_STACK, - "QPainter": ClassFlag.INSTANTIATE_ON_STACK, - "QPen": ClassFlag.INSTANTIATE_ON_STACK, + # QtQml "QQmlApplicationEngine": ClassFlag.INSTANTIATE_ON_STACK, "QQmlComponent": ClassFlag.INSTANTIATE_ON_STACK, "QQmlEngine": ClassFlag.INSTANTIATE_ON_STACK, - "QQuickView": ClassFlag.INSTANTIATE_ON_STACK, - "QSaveFile": ClassFlag.INSTANTIATE_ON_STACK + # QtQuick + "QQuickView": ClassFlag.INSTANTIATE_ON_STACK } diff --git a/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py b/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py index 1466ac6b..62805cd1 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py +++ b/sources/pyside-tools/qtpy2cpp_lib/tests/baseline/basic_test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +from __future__ import annotations import sys diff --git a/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py b/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py index 894b2a95..73dbd2e5 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py +++ b/sources/pyside-tools/qtpy2cpp_lib/tests/test_qtpy2cpp.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations import subprocess import tempfile diff --git a/sources/pyside-tools/qtpy2cpp_lib/tokenizer.py b/sources/pyside-tools/qtpy2cpp_lib/tokenizer.py index d5e26c2a..4f81ba5a 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/tokenizer.py +++ b/sources/pyside-tools/qtpy2cpp_lib/tokenizer.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """Tool to dump Python Tokens""" diff --git a/sources/pyside-tools/qtpy2cpp_lib/visitor.py b/sources/pyside-tools/qtpy2cpp_lib/visitor.py index 2056951a..e3f39e99 100644 --- a/sources/pyside-tools/qtpy2cpp_lib/visitor.py +++ b/sources/pyside-tools/qtpy2cpp_lib/visitor.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """AST visitor printing out C++""" diff --git a/sources/pyside-tools/requirements-android.txt b/sources/pyside-tools/requirements-android.txt index 1169fd66..9ed5d842 100644 --- a/sources/pyside-tools/requirements-android.txt +++ b/sources/pyside-tools/requirements-android.txt @@ -1,2 +1,3 @@ jinja2 pkginfo +tqdm diff --git a/sources/pyside6/.cmake.conf b/sources/pyside6/.cmake.conf index 8f6fb6ec..86ca5ff4 100644 --- a/sources/pyside6/.cmake.conf +++ b/sources/pyside6/.cmake.conf @@ -1,5 +1,5 @@ set(pyside_MAJOR_VERSION "6") -set(pyside_MINOR_VERSION "7") -set(pyside_MICRO_VERSION "2") +set(pyside_MINOR_VERSION "8") +set(pyside_MICRO_VERSION "2.1") set(pyside_PRE_RELEASE_VERSION_TYPE "") set(pyside_PRE_RELEASE_VERSION "") diff --git a/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml b/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml index 61af82d6..c74f6467 100644 --- a/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml +++ b/sources/pyside6/PySide6/Qt3DAnimation/typesystem_3danimation.xml @@ -4,7 +4,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - @@ -23,7 +23,7 @@ - + diff --git a/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml b/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml index 985c459b..c529b637 100644 --- a/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml +++ b/sources/pyside6/PySide6/Qt3DCore/typesystem_3dcore.xml @@ -4,7 +4,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - - diff --git a/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml b/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml index fbdfc596..83fb09b8 100644 --- a/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml +++ b/sources/pyside6/PySide6/Qt3DInput/typesystem_3dinput.xml @@ -4,7 +4,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml b/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml index 60f36253..dd9ad2ae 100644 --- a/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml +++ b/sources/pyside6/PySide6/Qt3DLogic/typesystem_3dlogic.xml @@ -4,7 +4,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml b/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml index 66fd7092..671e1fd9 100644 --- a/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml +++ b/sources/pyside6/PySide6/Qt3DRender/typesystem_3drender.xml @@ -4,7 +4,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py index 60d1846d..d284e4f6 100644 --- a/sources/pyside6/PySide6/QtAsyncio/__init__.py +++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from .events import ( QAsyncioEventLoopPolicy, QAsyncioEventLoop, QAsyncioHandle, QAsyncioTimerHandle @@ -7,8 +8,9 @@ from .events import ( from .futures import QAsyncioFuture from .tasks import QAsyncioTask +from typing import Coroutine, Any + import asyncio -import typing __all__ = [ "QAsyncioEventLoopPolicy", "QAsyncioEventLoop", @@ -17,12 +19,33 @@ __all__ = [ ] -def run(coro: typing.Optional[typing.Coroutine] = None, - keep_running: bool = True, - quit_qapp: bool = True, *, - handle_sigint: bool = False, - debug: typing.Optional[bool] = None) -> typing.Any: - """Run the QtAsyncio event loop.""" +def run(coro: Coroutine | None = None, + keep_running: bool = True, quit_qapp: bool = True, *, handle_sigint: bool = False, + debug: bool | None = None) -> Any: + """ + Run the QtAsyncio event loop. + + If there is no instance of a QCoreApplication, QGuiApplication or + QApplication yet, a new instance of QCoreApplication is created. + + :param coro: The coroutine to run. Optional if keep_running is + True. + :param keep_running: If True, QtAsyncio (the asyncio event loop) will + continue running after the coroutine finished, or + run "forever" if no coroutine was provided. + If False, QtAsyncio will stop after the + coroutine finished. A coroutine must be provided if + this argument is set to False. + :param quit_qapp: If True, the QCoreApplication will quit when + QtAsyncio (the asyncio event loop) stops. + If False, the QCoreApplication will remain active + after QtAsyncio stops, and can continue to be used. + :param handle_sigint: If True, the SIGINT signal will be handled by the + event loop, causing it to stop. + :param debug: If True, the event loop will run in debug mode. + If False, the event loop will run in normal mode. + If None, the default behavior is used. + """ # Event loop policies are expected to be deprecated with Python 3.13, with # subsequent removal in Python 3.15. At that point, part of the current @@ -49,7 +72,7 @@ def run(coro: typing.Optional[typing.Coroutine] = None, ret = asyncio.run(coro, debug=debug) else: exc = RuntimeError( - "QtAsyncio was set to keep running after the coroutine " + "QtAsyncio was set not to keep running after the coroutine " "finished, but no coroutine was provided.") asyncio.set_event_loop_policy(default_policy) diff --git a/sources/pyside6/PySide6/QtAsyncio/events.py b/sources/pyside6/PySide6/QtAsyncio/events.py index 86b89014..36c7fea9 100644 --- a/sources/pyside6/PySide6/QtAsyncio/events.py +++ b/sources/pyside6/PySide6/QtAsyncio/events.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer, QEventLoop, QObject, QTimer, QThread, Slot) @@ -7,6 +8,8 @@ from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer, from . import futures from . import tasks +from typing import Any, Callable + import asyncio import collections.abc import concurrent.futures @@ -16,7 +19,6 @@ import os import signal import socket import subprocess -import typing import warnings __all__ = [ @@ -40,7 +42,7 @@ class QAsyncioExecutorWrapper(QObject): the actual callable for the executor into this new event loop. """ - def __init__(self, func: typing.Callable, *args: typing.Tuple) -> None: + def __init__(self, func: Callable, *args: tuple) -> None: super().__init__() self._loop: QEventLoop self._func = func @@ -63,7 +65,10 @@ class QAsyncioExecutorWrapper(QObject): self._loop = QEventLoop() asyncio.events._set_running_loop(self._loop) - QTimer.singleShot(0, self._loop, lambda: self._cb()) + # The do() function will always be executed from the new executor + # thread and never from outside, so using the overload without the + # context argument is sufficient. + QTimer.singleShot(0, lambda: self._cb()) self._loop.exec() if self._exception is not None: @@ -87,16 +92,10 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): https://discuss.python.org/t/removing-the-asyncio-policy-system-asyncio-set-event-loop-policy-in-python-3-15/37553 """ def __init__(self, - application: typing.Optional[QCoreApplication] = None, quit_qapp: bool = True, handle_sigint: bool = False) -> None: super().__init__() - if application is None: - if QCoreApplication.instance() is None: - application = QCoreApplication() - else: - application = QCoreApplication.instance() - self._application: QCoreApplication = application # type: ignore[assignment] + self._application = QCoreApplication.instance() or QCoreApplication() # Configure whether the QCoreApplication at the core of QtAsyncio # should be shut down when asyncio finishes. A special case where one @@ -105,7 +104,7 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): # this instance is shut down every time. self._quit_qapp = quit_qapp - self._event_loop: typing.Optional[asyncio.AbstractEventLoop] = None + self._event_loop: asyncio.AbstractEventLoop | None = None if handle_sigint: signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -115,7 +114,7 @@ class QAsyncioEventLoopPolicy(asyncio.AbstractEventLoopPolicy): self._event_loop = QAsyncioEventLoop(self._application, quit_qapp=self._quit_qapp) return self._event_loop - def set_event_loop(self, loop: typing.Optional[asyncio.AbstractEventLoop]) -> None: + def set_event_loop(self, loop: asyncio.AbstractEventLoop | None) -> None: self._event_loop = loop def new_event_loop(self) -> asyncio.AbstractEventLoop: @@ -191,7 +190,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): self._quit_from_outside = False # A set of all asynchronous generators that are currently running. - self._asyncgens: typing.Set[collections.abc.AsyncGenerator] = set() + self._asyncgens: set[collections.abc.AsyncGenerator] = set() # Starting with Python 3.11, this must be an instance of # ThreadPoolExecutor. @@ -202,14 +201,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): # asynchonrous generator raises an exception when closed, and two, if # an exception is raised during the execution of a task. Currently, the # default exception handler just prints the exception to the console. - self._exception_handler: typing.Optional[typing.Callable] = self.default_exception_handler + self._exception_handler: Callable | None = self.default_exception_handler # The task factory, if set with set_task_factory(). Otherwise, a new # task is created with the QAsyncioTask constructor. - self._task_factory: typing.Optional[typing.Callable] = None + self._task_factory: Callable | None = None # The future that is currently being awaited with run_until_complete(). - self._future_to_complete: typing.Optional[futures.QAsyncioFuture] = None + self._future_to_complete: futures.QAsyncioFuture | None = None self._debug = bool(os.getenv("PYTHONASYNCIODEBUG", False)) @@ -228,7 +227,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): future.get_loop().stop() def run_until_complete(self, - future: futures.QAsyncioFuture) -> typing.Any: # type: ignore[override] + future: futures.QAsyncioFuture) -> Any: # type: ignore[override] if self.is_closed(): raise RuntimeError("Event loop is closed") if self.is_running(): @@ -322,7 +321,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): self._asyncgens.clear() async def shutdown_default_executor(self, # type: ignore[override] - timeout: typing.Union[int, float, None] = None) -> None: + timeout: int | float | None = None) -> None: shutdown_successful = False if timeout is not None: deadline_timer = QDeadlineTimer(int(timeout * 1000)) @@ -347,51 +346,46 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): # Scheduling callbacks - def _call_soon_impl(self, callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None, - is_threadsafe: typing.Optional[bool] = False) -> asyncio.Handle: + def _call_soon_impl(self, callback: Callable, *args: Any, + context: contextvars.Context | None = None, + is_threadsafe: bool | None = False) -> asyncio.Handle: return self._call_later_impl(0, callback, *args, context=context, is_threadsafe=is_threadsafe) - def call_soon(self, callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None) -> asyncio.Handle: + def call_soon(self, callback: Callable, *args: Any, + context: contextvars.Context | None = None) -> asyncio.Handle: return self._call_soon_impl(callback, *args, context=context, is_threadsafe=False) - def call_soon_threadsafe(self, callback: typing.Callable, *args: typing.Any, - context: - typing.Optional[contextvars.Context] = None) -> asyncio.Handle: + def call_soon_threadsafe(self, callback: Callable, *args: Any, + context: contextvars.Context | None = None) -> asyncio.Handle: if self.is_closed(): raise RuntimeError("Event loop is closed") if context is None: context = contextvars.copy_context() return self._call_soon_impl(callback, *args, context=context, is_threadsafe=True) - def _call_later_impl(self, delay: typing.Union[int, float], - callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None, - is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: + def _call_later_impl(self, delay: int | float, callback: Callable, *args: Any, + context: contextvars.Context | None = None, + is_threadsafe: bool | None = False) -> asyncio.TimerHandle: if not isinstance(delay, (int, float)): raise TypeError("delay must be an int or float") return self._call_at_impl(self.time() + delay, callback, *args, context=context, is_threadsafe=is_threadsafe) - def call_later(self, delay: typing.Union[int, float], - callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None) -> asyncio.TimerHandle: + def call_later(self, delay: int | float, callback: Callable, *args: Any, + context: contextvars.Context | None = None) -> asyncio.TimerHandle: return self._call_later_impl(delay, callback, *args, context=context, is_threadsafe=False) - def _call_at_impl(self, when: typing.Union[int, float], - callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None, - is_threadsafe: typing.Optional[bool] = False) -> asyncio.TimerHandle: + def _call_at_impl(self, when: int | float, callback: Callable, *args: Any, + context: contextvars.Context | None = None, + is_threadsafe: bool | None = False) -> asyncio.TimerHandle: """ All call_at() and call_later() methods map to this method. """ if not isinstance(when, (int, float)): raise TypeError("when must be an int or float") return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe) - def call_at(self, when: typing.Union[int, float], - callback: typing.Callable, *args: typing.Any, - context: typing.Optional[contextvars.Context] = None) -> asyncio.TimerHandle: + def call_at(self, when: int | float, callback: Callable, *args: Any, + context: contextvars.Context | None = None) -> asyncio.TimerHandle: return self._call_at_impl(when, callback, *args, context=context, is_threadsafe=False) def time(self) -> float: @@ -403,9 +397,9 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): return futures.QAsyncioFuture(loop=self) def create_task(self, # type: ignore[override] - coro: typing.Union[collections.abc.Generator, collections.abc.Coroutine], - *, name: typing.Optional[str] = None, - context: typing.Optional[contextvars.Context] = None) -> tasks.QAsyncioTask: + coro: collections.abc.Generator | collections.abc.Coroutine, + *, name: str | None = None, + context: contextvars.Context | None = None) -> tasks.QAsyncioTask: if self._task_factory is None: task = tasks.QAsyncioTask(coro, loop=self, name=name, context=context) else: @@ -414,12 +408,12 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): return task - def set_task_factory(self, factory: typing.Optional[typing.Callable]) -> None: + def set_task_factory(self, factory: Callable | None) -> None: if factory is not None and not callable(factory): raise TypeError("The task factory must be a callable or None") self._task_factory = factory - def get_task_factory(self) -> typing.Optional[typing.Callable]: + def get_task_factory(self) -> Callable | None: return self._task_factory # Opening network connections @@ -432,14 +426,15 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): ssl_handshake_timeout=None, ssl_shutdown_timeout=None, happy_eyeballs_delay=None, interleave=None): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.create_connection() is not implemented yet") async def create_datagram_endpoint(self, protocol_factory, local_addr=None, remote_addr=None, *, family=0, proto=0, flags=0, reuse_address=None, reuse_port=None, allow_broadcast=None, sock=None): - raise NotImplementedError + raise NotImplementedError( + "QAsyncioEventLoop.create_datagram_endpoint() is not implemented yet") async def create_unix_connection( self, protocol_factory, path=None, *, @@ -447,7 +442,8 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): server_hostname=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None): - raise NotImplementedError + raise NotImplementedError( + "QAsyncioEventLoop.create_unix_connection() is not implemented yet") # Creating network servers @@ -459,7 +455,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.create_server() is not implemented yet") async def create_unix_server( self, protocol_factory, path=None, *, @@ -467,20 +463,21 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): ssl_handshake_timeout=None, ssl_shutdown_timeout=None, start_serving=True): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.create_unix_server() is not implemented yet") async def connect_accepted_socket( self, protocol_factory, sock, *, ssl=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None): - raise NotImplementedError + raise NotImplementedError( + "QAsyncioEventLoop.connect_accepted_socket() is not implemented yet") # Transferring files async def sendfile(self, transport, file, offset=0, count=None, *, fallback=True): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sendfile() is not implemented yet") # TLS Upgrade @@ -489,82 +486,83 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): server_hostname=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.start_tls() is not implemented yet") # Watching file descriptors def add_reader(self, fd, callback, *args): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.add_reader() is not implemented yet") def remove_reader(self, fd): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.remove_reader() is not implemented yet") def add_writer(self, fd, callback, *args): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.add_writer() is not implemented yet") def remove_writer(self, fd): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.remove_writer() is not implemented yet") # Working with socket objects directly async def sock_recv(self, sock, nbytes): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_recv() is not implemented yet") async def sock_recv_into(self, sock, buf): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_recv_into() is not implemented yet") async def sock_recvfrom(self, sock, bufsize): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_recvfrom() is not implemented yet") async def sock_recvfrom_into(self, sock, buf, nbytes=0): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_recvfrom_into() is not implemented yet") async def sock_sendall(self, sock, data): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_sendall() is not implemented yet") async def sock_sendto(self, sock, data, address): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_sendto() is not implemented yet") async def sock_connect(self, sock, address): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_connect() is not implemented yet") async def sock_accept(self, sock): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_accept() is not implemented yet") async def sock_sendfile(self, sock, file, offset=0, count=None, *, fallback=None): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.sock_sendfile() is not implemented yet") # DNS async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.getaddrinfo() is not implemented yet") async def getnameinfo(self, sockaddr, flags=0): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.getnameinfo() is not implemented yet") # Working with pipes async def connect_read_pipe(self, protocol_factory, pipe): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.connect_read_pipe() is not implemented yet") async def connect_write_pipe(self, protocol_factory, pipe): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.connect_write_pipe() is not implemented yet") # Unix signals def add_signal_handler(self, sig, callback, *args): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.add_signal_handler() is not implemented yet") def remove_signal_handler(self, sig): - raise NotImplementedError + raise NotImplementedError( + "QAsyncioEventLoop.remove_signal_handler() is not implemented yet") # Executing code in thread or process pools def run_in_executor(self, - executor: typing.Optional[concurrent.futures.ThreadPoolExecutor], - func: typing.Callable, *args: typing.Tuple) -> asyncio.futures.Future: + executor: concurrent.futures.ThreadPoolExecutor | None, + func: Callable, *args: tuple) -> asyncio.futures.Future: if self.is_closed(): raise RuntimeError("Event loop is closed") if executor is None: @@ -582,28 +580,27 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): ) def set_default_executor(self, - executor: typing.Optional[ - concurrent.futures.ThreadPoolExecutor]) -> None: + executor: concurrent.futures.ThreadPoolExecutor | None) -> None: if not isinstance(executor, concurrent.futures.ThreadPoolExecutor): raise TypeError("The executor must be a ThreadPoolExecutor") self._default_executor = executor # Error Handling API - def set_exception_handler(self, handler: typing.Optional[typing.Callable]) -> None: + def set_exception_handler(self, handler: Callable | None) -> None: if handler is not None and not callable(handler): raise TypeError("The handler must be a callable or None") self._exception_handler = handler - def get_exception_handler(self) -> typing.Optional[typing.Callable]: + def get_exception_handler(self) -> Callable | None: return self._exception_handler - def default_exception_handler(self, context: typing.Dict[str, typing.Any]) -> None: + def default_exception_handler(self, context: dict[str, Any]) -> None: # TODO if context["message"]: print(context["message"]) - def call_exception_handler(self, context: typing.Dict[str, typing.Any]) -> None: + def call_exception_handler(self, context: dict[str, Any]) -> None: if self._exception_handler is not None: self._exception_handler(context) @@ -624,14 +621,14 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject): stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.subprocess_exec() is not implemented yet") async def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): - raise NotImplementedError + raise NotImplementedError("QAsyncioEventLoop.subprocess_shell() is not implemented yet") class QAsyncioHandle(): @@ -646,9 +643,9 @@ class QAsyncioHandle(): CANCELLED = enum.auto() DONE = enum.auto() - def __init__(self, callback: typing.Callable, args: typing.Tuple, - loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context], - is_threadsafe: typing.Optional[bool] = False) -> None: + def __init__(self, callback: Callable, args: tuple, + loop: QAsyncioEventLoop, context: contextvars.Context | None, + is_threadsafe: bool | None = False) -> None: self._callback = callback self._args = args self._loop = loop @@ -663,7 +660,7 @@ class QAsyncioHandle(): def _start(self) -> None: self._schedule_event(self._timeout, lambda: self._cb()) - def _schedule_event(self, timeout: int, func: typing.Callable) -> None: + def _schedule_event(self, timeout: int, func: Callable) -> None: # Do not schedule events from asyncio when the app is quit from outside # the event loop, as this would cause events to be enqueued after the # event loop was destroyed. @@ -703,13 +700,17 @@ class QAsyncioHandle(): class QAsyncioTimerHandle(QAsyncioHandle, asyncio.TimerHandle): - def __init__(self, when: float, callback: typing.Callable, args: typing.Tuple, - loop: QAsyncioEventLoop, context: typing.Optional[contextvars.Context], - is_threadsafe: typing.Optional[bool] = False) -> None: + def __init__(self, when: float, callback: Callable, args: tuple, + loop: QAsyncioEventLoop, context: contextvars.Context | None, + is_threadsafe: bool | None = False) -> None: QAsyncioHandle.__init__(self, callback, args, loop, context, is_threadsafe) self._when = when time = self._loop.time() + + # PYSIDE-2644: Timeouts should be rounded up or down instead of only up + # as happens with int(). Otherwise, a timeout of e.g. 0.9 would be + # handled as 0, where 1 would be more appropriate. self._timeout = round(max(self._when - time, 0) * 1000) QAsyncioHandle._start(self) diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py index cbb005fc..6b441549 100644 --- a/sources/pyside6/PySide6/QtAsyncio/futures.py +++ b/sources/pyside6/PySide6/QtAsyncio/futures.py @@ -1,12 +1,14 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from . import events +from typing import Any, Callable + import asyncio import contextvars import enum -import typing class QAsyncioFuture(): @@ -23,8 +25,8 @@ class QAsyncioFuture(): DONE_WITH_RESULT = enum.auto() DONE_WITH_EXCEPTION = enum.auto() - def __init__(self, *, loop: typing.Optional["events.QAsyncioEventLoop"] = None, - context: typing.Optional[contextvars.Context] = None) -> None: + def __init__(self, *, loop: "events.QAsyncioEventLoop | None" = None, + context: contextvars.Context | None = None) -> None: self._loop: "events.QAsyncioEventLoop" if loop is None: self._loop = asyncio.events.get_event_loop() # type: ignore[assignment] @@ -33,13 +35,13 @@ class QAsyncioFuture(): self._context = context self._state = QAsyncioFuture.FutureState.PENDING - self._result: typing.Any = None - self._exception: typing.Optional[BaseException] = None + self._result: Any = None + self._exception: BaseException | None = None - self._cancel_message: typing.Optional[str] = None + self._cancel_message: str | None = None # List of callbacks that are called when the future is done. - self._callbacks: typing.List[typing.Callable] = list() + self._callbacks: list[Callable] = list() def __await__(self): if not self.done(): @@ -51,13 +53,13 @@ class QAsyncioFuture(): __iter__ = __await__ - def _schedule_callbacks(self, context: typing.Optional[contextvars.Context] = None): + def _schedule_callbacks(self, context: contextvars.Context | None = None): """ A future can optionally have callbacks that are called when the future is done. """ for cb in self._callbacks: self._loop.call_soon( cb, self, context=context if context else self._context) - def result(self) -> typing.Union[typing.Any, Exception]: + def result(self) -> Any | Exception: if self._state == QAsyncioFuture.FutureState.DONE_WITH_RESULT: return self._result if self._state == QAsyncioFuture.FutureState.DONE_WITH_EXCEPTION and self._exception: @@ -69,7 +71,7 @@ class QAsyncioFuture(): raise asyncio.CancelledError raise asyncio.InvalidStateError - def set_result(self, result: typing.Any) -> None: + def set_result(self, result: Any) -> None: self._result = result self._state = QAsyncioFuture.FutureState.DONE_WITH_RESULT self._schedule_callbacks() @@ -85,20 +87,20 @@ class QAsyncioFuture(): def cancelled(self) -> bool: return self._state == QAsyncioFuture.FutureState.CANCELLED - def add_done_callback(self, cb: typing.Callable, *, - context: typing.Optional[contextvars.Context] = None) -> None: + def add_done_callback(self, cb: Callable, *, + context: contextvars.Context | None = None) -> None: if self.done(): self._loop.call_soon( cb, self, context=context if context else self._context) else: self._callbacks.append(cb) - def remove_done_callback(self, cb: typing.Callable) -> int: + def remove_done_callback(self, cb: Callable) -> int: original_len = len(self._callbacks) self._callbacks = [_cb for _cb in self._callbacks if _cb != cb] return original_len - len(self._callbacks) - def cancel(self, msg: typing.Optional[str] = None) -> bool: + def cancel(self, msg: str | None = None) -> bool: if self.done(): return False self._state = QAsyncioFuture.FutureState.CANCELLED @@ -106,7 +108,7 @@ class QAsyncioFuture(): self._schedule_callbacks() return True - def exception(self) -> typing.Optional[BaseException]: + def exception(self) -> BaseException | None: if self._state == QAsyncioFuture.FutureState.CANCELLED: raise asyncio.CancelledError if self.done(): diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py index 7edc1509..be1809d5 100644 --- a/sources/pyside6/PySide6/QtAsyncio/tasks.py +++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py @@ -1,23 +1,24 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from . import events from . import futures +from typing import Any + import asyncio import collections.abc import concurrent.futures import contextvars -import typing class QAsyncioTask(futures.QAsyncioFuture): """ https://docs.python.org/3/library/asyncio-task.html """ - def __init__(self, coro: typing.Union[collections.abc.Generator, collections.abc.Coroutine], *, - loop: typing.Optional["events.QAsyncioEventLoop"] = None, - name: typing.Optional[str] = None, - context: typing.Optional[contextvars.Context] = None) -> None: + def __init__(self, coro: collections.abc.Generator | collections.abc.Coroutine, *, + loop: "events.QAsyncioEventLoop | None" = None, name: str | None = None, + context: contextvars.Context | None = None) -> None: super().__init__(loop=loop, context=context) self._coro = coro # The coroutine for which this task was created. @@ -25,15 +26,18 @@ class QAsyncioTask(futures.QAsyncioFuture): # The task creates a handle for its coroutine. The handle enqueues the # task's step function as its callback in the event loop. - self._handle = self._loop.call_soon(self._step, context=self._context) + self._loop.call_soon(self._step, context=self._context) # The task step function executes the coroutine until it finishes, # raises an exception or returns a future. If a future was returned, - # the task will await its completion (or exception). - self._future_to_await: typing.Optional[asyncio.Future] = None + # the task will await its completion (or exception). If the task is + # cancelled while it awaits a future, this future must also be + # cancelled in order for the cancellation to be successful. + self._future_to_await: asyncio.Future | None = None - self._cancelled = False - self._cancel_message: typing.Optional[str] = None + self._cancelled = False # PYSIDE-2644; see _step + self._cancel_count = 0 + self._cancel_message: str | None = None # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._register_task(self) # type: ignore[arg-type] @@ -53,17 +57,16 @@ class QAsyncioTask(futures.QAsyncioFuture): class QtTaskApiMisuseError(Exception): pass - def set_result(self, result: typing.Any) -> None: # type: ignore[override] + def set_result(self, result: Any) -> None: # type: ignore[override] # This function is not inherited from the Future APIs. raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot set results") - def set_exception(self, exception: typing.Any) -> None: # type: ignore[override] + def set_exception(self, exception: Any) -> None: # type: ignore[override] # This function is not inherited from the Future APIs. raise QAsyncioTask.QtTaskApiMisuseError("Tasks cannot set exceptions") def _step(self, - exception_or_future: typing.Union[ - BaseException, futures.QAsyncioFuture, None] = None) -> None: + exception_or_future: BaseException | futures.QAsyncioFuture | None = None) -> None: """ The step function is the heart of a task. It is scheduled in the event loop repeatedly, executing the coroutine "step" by "step" (i.e., @@ -77,6 +80,10 @@ class QAsyncioTask(futures.QAsyncioFuture): result = None self._future_to_await = None + if self._cancelled: + exception_or_future = asyncio.CancelledError(self._cancel_message) + self._cancelled = False + if asyncio.futures.isfuture(exception_or_future): try: exception_or_future.result() @@ -113,11 +120,17 @@ class QAsyncioTask(futures.QAsyncioFuture): # called again. result.add_done_callback( self._step, context=self._context) # type: ignore[arg-type] + + # The task will await the completion (or exception) of this + # future. If the task is cancelled while it awaits a future, + # this future must also be cancelled. self._future_to_await = result + if self._cancelled: - # If the task was cancelled, then a new future should be - # cancelled as well. Otherwise, in some scenarios like - # a loop inside the task and with bad timing, if the new + # PYSIDE-2644: If the task was cancelled at this step and a + # new future was created to be awaited, then it should be + # cancelled as well. Otherwise, in some scenarios like a + # loop inside the task and with bad timing, if the new # future is not cancelled, the task would continue running # in this loop despite having been cancelled. This bad # timing can occur especially if the first future finishes @@ -135,10 +148,13 @@ class QAsyncioTask(futures.QAsyncioFuture): asyncio._leave_task(self._loop, self) # type: ignore[arg-type] if self._exception: + message = str(self._exception) + if message == "None": + message = "" + else: + message = "An exception occurred during task execution" self._loop.call_exception_handler({ - "message": (str(self._exception) if self._exception - else "An exception occurred during task " - "execution"), + "message": message, "exception": self._exception, "task": self, "future": (exception_or_future @@ -152,7 +168,7 @@ class QAsyncioTask(futures.QAsyncioFuture): # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support asyncio._unregister_task(self) # type: ignore[arg-type] - def get_stack(self, *, limit=None) -> typing.List[typing.Any]: + def get_stack(self, *, limit=None) -> list[Any]: # TODO raise NotImplementedError("QtTask.get_stack is not implemented") @@ -160,7 +176,7 @@ class QAsyncioTask(futures.QAsyncioFuture): # TODO raise NotImplementedError("QtTask.print_stack is not implemented") - def get_coro(self) -> typing.Union[collections.abc.Generator, collections.abc.Coroutine]: + def get_coro(self) -> collections.abc.Generator | collections.abc.Coroutine: return self._coro def get_name(self) -> str: @@ -169,22 +185,22 @@ class QAsyncioTask(futures.QAsyncioFuture): def set_name(self, value) -> None: self._name = str(value) - def cancel(self, msg: typing.Optional[str] = None) -> bool: + def cancel(self, msg: str | None = None) -> bool: if self.done(): return False + self._cancel_count += 1 self._cancel_message = msg - self._handle.cancel() if self._future_to_await is not None: # A task that is awaiting a future must also cancel this future in # order for the cancellation to be successful. self._future_to_await.cancel(msg) - self._cancelled = True + self._cancelled = True # PYSIDE-2644; see _step return True - def uncancel(self) -> None: - # TODO - raise NotImplementedError("QtTask.uncancel is not implemented") + def uncancel(self) -> int: + if self._cancel_count > 0: + self._cancel_count -= 1 + return self._cancel_count - def cancelling(self) -> bool: - # TODO - raise NotImplementedError("QtTask.cancelling is not implemented") + def cancelling(self) -> int: + return self._cancel_count diff --git a/sources/pyside6/PySide6/QtCore/CMakeLists.txt b/sources/pyside6/PySide6/QtCore/CMakeLists.txt index f3e4be0f..d985b977 100644 --- a/sources/pyside6/PySide6/QtCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtCore/CMakeLists.txt @@ -67,6 +67,8 @@ ${QtCore_GEN_DIR}/qdatetime_wrapper.cpp ${QtCore_GEN_DIR}/qdeadlinetimer_wrapper.cpp ${QtCore_GEN_DIR}/qdir_wrapper.cpp ${QtCore_GEN_DIR}/qdiriterator_wrapper.cpp +${QtCore_GEN_DIR}/qdirlisting_wrapper.cpp +${QtCore_GEN_DIR}/qdirlisting_direntry_wrapper.cpp ${QtCore_GEN_DIR}/qdynamicpropertychangeevent_wrapper.cpp ${QtCore_GEN_DIR}/qeasingcurve_wrapper.cpp ${QtCore_GEN_DIR}/qelapsedtimer_wrapper.cpp @@ -116,6 +118,7 @@ ${QtCore_GEN_DIR}/qmimedatabase_wrapper.cpp ${QtCore_GEN_DIR}/qmimetype_wrapper.cpp ${QtCore_GEN_DIR}/qmodelindex_wrapper.cpp ${QtCore_GEN_DIR}/qmodelroledata_wrapper.cpp +${QtCore_GEN_DIR}/qmodelroledataspan_wrapper.cpp ${QtCore_GEN_DIR}/qmutex_wrapper.cpp ${QtCore_GEN_DIR}/qnativeipckey_wrapper.cpp ${QtCore_GEN_DIR}/qobject_wrapper.cpp @@ -165,6 +168,7 @@ ${QtCore_GEN_DIR}/qstringlistmodel_wrapper.cpp ${QtCore_GEN_DIR}/qsysinfo_wrapper.cpp ${QtCore_GEN_DIR}/qsystemsemaphore_wrapper.cpp ${QtCore_GEN_DIR}/qt_wrapper.cpp +${QtCore_GEN_DIR}/qtcorehelper_qdirlistingiterator_wrapper.cpp ${QtCore_GEN_DIR}/qtcorehelper_qgenericargumentholder_wrapper.cpp ${QtCore_GEN_DIR}/qtcorehelper_qgenericreturnargumentholder_wrapper.cpp ${QtCore_GEN_DIR}/qtcorehelper_qiopipe_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index f6acf9d6..c073c8bc 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -45,8 +45,8 @@ QMetaType QVariant_resolveMetaType(PyTypeObject *type) // that has added any python fields or slots to its object layout. // See https://mail.python.org/pipermail/python-list/2009-January/520733.html if (type->tp_bases) { - for (Py_ssize_t i = 0, size = PyTuple_GET_SIZE(type->tp_bases); i < size; ++i) { - auto baseType = reinterpret_cast(PyTuple_GET_ITEM(type->tp_bases, i)); + for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) { + auto baseType = reinterpret_cast(PyTuple_GetItem(type->tp_bases, i)); const QMetaType derived = QVariant_resolveMetaType(baseType); if (derived.isValid()) return derived; @@ -103,9 +103,9 @@ bool QVariant_isStringList(PyObject *list) } Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList")); - const Py_ssize_t size = PySequence_Fast_GET_SIZE(fast.object()); + const Py_ssize_t size = PySequence_Size(fast.object()); for (Py_ssize_t i = 0; i < size; ++i) { - PyObject *item = PySequence_Fast_GET_ITEM(fast.object(), i); + Shiboken::AutoDecRef item(PySequence_GetItem(fast.object(), i)); if (PyUnicode_Check(item) == 0) return false; } @@ -224,13 +224,13 @@ void qObjectFindChildren(const QObject *parent, const QRegularExpression &patter QString qObjectTr(PyTypeObject *type, const char *sourceText, const char *disambiguation, int n) { PyObject *mro = type->tp_mro; - auto len = PyTuple_GET_SIZE(mro); + auto len = PyTuple_Size(mro); QString result = QString::fromUtf8(sourceText); QString oldResult = result; static auto *sbkObjectType = reinterpret_cast(SbkObject_TypeF()); for (Py_ssize_t idx = 0; idx < len - 1; ++idx) { // Skip the last class which is `object`. - auto *type = reinterpret_cast(PyTuple_GET_ITEM(mro, idx)); + auto *type = reinterpret_cast(PyTuple_GetItem(mro, idx)); if (type == sbkObjectType) continue; const char *context = type->tp_name; diff --git a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp index e154b932..d806b23e 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp @@ -69,7 +69,7 @@ void PySideEasingCurveFunctor::init() CustomFunctions::init(); } -QEasingCurve::EasingFunction PySideEasingCurveFunctor::createCustomFuntion(PyObject *parent, PyObject *pyFunc) +QEasingCurve::EasingFunction PySideEasingCurveFunctor::createCustomFunction(PyObject *parent, PyObject *pyFunc) { for(int i=0; i < MAX_CUSTOM_FUNCTIONS; i++) { CustomFunctionsData &data = CustomFunctionsData::m_list[i]; @@ -120,4 +120,3 @@ PySideEasingCurveFunctor::PySideEasingCurveFunctor(int index, PyObject *parent, PyObject_SetAttr(m_parent, Shiboken::PyMagicName::ecf(), m_func); PySide::WeakRef::create(m_parent, deleteData, this); } - diff --git a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h index 78adaae2..181a205f 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h +++ b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h @@ -11,7 +11,7 @@ class PySideEasingCurveFunctor { public: static void init(); - static QEasingCurve::EasingFunction createCustomFuntion(PyObject *parent, PyObject *pyFunc); + static QEasingCurve::EasingFunction createCustomFunction(PyObject *parent, PyObject *pyFunc); qreal operator()(qreal progress); diff --git a/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp b/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp index 948c0ce5..a56417ac 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp @@ -103,6 +103,72 @@ const void *QGenericReturnArgumentHolder::data() const return d->m_argument.data(); } +// QDirListing::const_iterator has no copy semantics (shared internal state, QTBUG-125512). +// The Python iterable semantics (calling __next__() first before retrieving the first value) +// can therefore not be implemented as "return iterator++;". Wrap a helper class +// around it that does a no-op in the first call to __next__(). +struct QDirListingIteratorPrivate +{ + enum State { First, Iterating, End }; + + explicit QDirListingIteratorPrivate(const QDirListing &dl) : + iterator(dl.cbegin()), state(First) {} + QDirListingIteratorPrivate() : state(End) {} + + bool next(); + + QDirListing::const_iterator iterator; + State state; +}; + +inline bool QDirListingIteratorPrivate::next() +{ + switch (state) { + case First: + state = iterator != QDirListing::sentinel{} ? Iterating : End; + break; + case Iterating: + if (++iterator == QDirListing::sentinel{}) + state = End; + break; + case End: + break; + } + return state != End; +} + +QDirListingIterator::QDirListingIterator(const QDirListing &dl) : + d(std::make_shared(dl)) +{ +} + +QDirListingIterator::QDirListingIterator() : + d(std::make_shared()) +{ +} + +QDirListingIterator::QDirListingIterator(const QDirListingIterator &) = default; +QDirListingIterator &QDirListingIterator::operator=(const QDirListingIterator &) = default; +QDirListingIterator::QDirListingIterator(QDirListingIterator &&) noexcept = default; +QDirListingIterator &QDirListingIterator::operator=(QDirListingIterator &&) noexcept = default; +QDirListingIterator::~QDirListingIterator() = default; + +bool QDirListingIterator::next() +{ + return d->next(); +} + +const QDirListing::DirEntry &QDirListingIterator::value() const +{ + Q_ASSERT(d->state == QDirListingIteratorPrivate::Iterating); + return *d->iterator; +} + +bool QDirListingIterator::atEnd() const +{ + return d->state == QDirListingIteratorPrivate::End; +} + } // namespace QtCoreHelper QT_END_NAMESPACE diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 19a50848..cbcbbf8f 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -66,39 +66,38 @@ - - + + - - + + - + - - + + + + + + - - This function takes a type (or a type string) and a value of that type - and returns an internal object that can be passed to - QMetaObject.invokeMethod(). See also Q_RETURN_ARG(). - + - - This macro takes a type (or a type string) a value of which is then - returned by QMetaObject::invokeMethod(). See also Q_ARG(). - + @@ -383,12 +382,12 @@ - + - + @@ -578,6 +577,7 @@ + @@ -637,6 +637,7 @@ + @@ -672,7 +673,7 @@ - + @@ -691,7 +692,7 @@ - + @@ -714,7 +715,7 @@ - + @@ -723,7 +724,7 @@ - + @@ -744,7 +745,7 @@ - + @@ -753,7 +754,7 @@ - + @@ -783,7 +784,7 @@ - + Returns a read only buffer object pointing to the segment of data that this resource represents. If the resource is compressed the data returns is compressed and qUncompress() must be used to access the data. If the resource is a directory None is returned. @@ -827,7 +828,7 @@ - + @@ -836,7 +837,7 @@ - + @@ -888,17 +889,17 @@ - + - + - + - + @@ -906,7 +907,7 @@ - + @@ -945,7 +946,7 @@ - + @@ -955,6 +956,23 @@ + + + + + + + + + + + + + + + @@ -983,7 +1001,7 @@ - + @@ -992,7 +1010,7 @@ - + @@ -1016,7 +1034,7 @@ - + @@ -1025,7 +1043,7 @@ - + @@ -1049,7 +1067,7 @@ - + @@ -1057,7 +1075,7 @@ - + @@ -1112,7 +1130,7 @@ - + @@ -1125,7 +1143,7 @@ FIXME These functions return qreal. Will convert to double (format string) mess things up in other architectures? --> - + @@ -1179,7 +1197,7 @@ - + @@ -1188,7 +1206,7 @@ - + @@ -1212,7 +1230,7 @@ - + @@ -1221,7 +1239,7 @@ - + @@ -1260,7 +1278,7 @@ - + @@ -1268,7 +1286,7 @@ - + @@ -1315,7 +1333,7 @@ - + @@ -1323,7 +1341,7 @@ - + @@ -1363,25 +1381,8 @@ - - Returns a QLocale object initialized to the system locale. - - The system locale may use system-specific sources for locale data, where available, - otherwise falling back on QLocale's built-in database entry for the language, script and - territory the system reports. - - For example, on Windows, this locale will use the decimal/grouping characters and - date/time formats specified in the system configuration panel. - - .. note:: Qt for Python on macOS will not reflect the user's region and language - preferences though QLocale::system(), but will instead reflect the environment - variables POSIX uses to specify locale, similar to Python's locale module. If the - system locale cannot be determined, which can be due to none of the variables 'LC_ALL', - 'LC_CTYPE', 'LANG' or 'LANGUAGE' being set by your environment, then the default POSIX - locale or 'C' locale is returned. - - See also c(). - + @@ -1513,7 +1514,7 @@ - + @@ -1624,9 +1625,27 @@ - + + + + + + + + + + + + + + + - + + @@ -1639,18 +1658,8 @@ - - Creates a model index for the given row and column with the internal pointer ptr. - When using a QSortFilterProxyModel, its indexes have their own internal pointer. - It is not advisable to access this internal pointer outside of the model. - Use the data() function instead. - This function provides a consistent interface that model subclasses must use to create model indexes. - - .. warning:: Because of some Qt/Python itegration rules, the ptr argument do not get the reference - incremented during the QModelIndex life time. So it is necessary to keep the object used - on ptr argument alive during the whole process. - Do not destroy the object if you are not sure about that. - + @@ -1735,9 +1744,6 @@ - - - @@ -1761,9 +1767,6 @@ - - - @@ -1815,7 +1818,7 @@ - + @@ -1833,21 +1836,10 @@ - - To find the child of a certain QObject, the first argument of this function should be the child's type, and the second the name of the child: - - :: - - ... - parent = QWidget() - ... - # The first argument must be the child type - child1 = parent.findChild(QPushButton, "child_button") - child2 = parent.findChild(QWidget, "child_widget") - - + - + @@ -1857,14 +1849,14 @@ Like the method *findChild*, the first parameter should be the child's type. - + - + @@ -1922,7 +1914,7 @@ - + @@ -1931,7 +1923,7 @@ - + @@ -1975,7 +1967,7 @@ - + @@ -2022,7 +2014,7 @@ - + @@ -2034,31 +2026,31 @@ - + - + - + - + - + - + - + - + @@ -2066,10 +2058,10 @@ - + - + @@ -2211,10 +2203,10 @@ - + - + @@ -2229,6 +2221,9 @@ + + + @@ -2479,6 +2474,17 @@ + + + + + + + + + + @@ -2510,6 +2516,8 @@ + + @@ -2581,6 +2589,7 @@ + @@ -2648,15 +2657,8 @@ - - Constructs a Qt kernel application. Kernel applications are applications - without a graphical user interface. These type of applications are used - at the console or as server processes. - - The *args* argument is processed by the application, and made available - in a more convenient form by the :meth:`~PySide6.QtCore.QCoreApplication.arguments()` - method. - + @@ -2744,33 +2746,14 @@ - - Custom overload that adds an optional named parameter to the function ``value()`` - to automatically cast the type that is being returned by the function. - - An example of this situation could be an ini file that contains - the value of a one-element list:: - - settings.setValue('var', ['a']) - - The the ini file will be:: - - [General] - var=a # we cannot know that this is a list! - - Once we read it, we could specify if we want - the default behavior, a str, or to cast the output - to a list. - - settings.value('var') # Will get "a" - settings.value('var', type=list) # Will get ["a"] - + - + @@ -2800,7 +2783,6 @@ - @@ -2815,7 +2797,6 @@ - @@ -3049,6 +3030,7 @@ + @@ -3067,7 +3049,7 @@ - + @@ -3321,7 +3303,7 @@ - + @@ -3432,7 +3414,7 @@ - + @@ -3493,37 +3475,37 @@ - + - + - + - + - + - + - + @@ -3582,30 +3564,30 @@ - + - - qCDebug((*category), "%s", %2); + + qCDebug(*%1, "%s", %2); - - - qCCritical((*category), "%s", %2); + + + qCCritical(*%1, "%s", %2); - - - qCInfo((*category), "%s", %2); + + + qCInfo(*%1, "%s", %2); - - - qCWarning((*category), "%s", %2); + + + qCWarning(*%1, "%s", %2); diff --git a/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml b/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml index ebed2225..c8d7221c 100644 --- a/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml +++ b/sources/pyside6/PySide6/QtDBus/typesystem_dbus.xml @@ -86,7 +86,7 @@ - + DBus signals can be captured with string-based connections (see :ref:`signals-and-slots-strings`). @@ -107,7 +107,7 @@ - + diff --git a/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml b/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml index 70014e3d..7d3369ad 100644 --- a/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml +++ b/sources/pyside6/PySide6/QtDataVisualization/typesystem_datavisualization.xml @@ -3,7 +3,7 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp b/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp index 31f8ec15..19e97423 100644 --- a/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp +++ b/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp @@ -114,7 +114,7 @@ QWidget *PyDesignerCustomWidget::createWidget(QWidget *parent) } Shiboken::AutoDecRef pyArgs(PyTuple_New(1)); - PyTuple_SET_ITEM(pyArgs, 0, pyParent); // tuple will keep pyParent reference + PyTuple_SetItem(pyArgs, 0, pyParent); // tuple will keep pyParent reference // Call python constructor auto result = reinterpret_cast(PyObject_CallObject(m_pyTypeObject, pyArgs)); diff --git a/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt b/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt index 59cef220..b32c39f8 100644 --- a/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt @@ -11,6 +11,10 @@ set(QtGraphs_SRC # 3D ${QtGraphs_GEN_DIR}/qabstract3daxis_wrapper.cpp ${QtGraphs_GEN_DIR}/qcategory3daxis_wrapper.cpp +${QtGraphs_GEN_DIR}/qtgraphs3d_wrapper.cpp +${QtGraphs_GEN_DIR}/qgraphsline_wrapper.cpp +${QtGraphs_GEN_DIR}/qgraphstheme_wrapper.cpp +${QtGraphs_GEN_DIR}/qgraphsthemedirtybitfield_wrapper.cpp ${QtGraphs_GEN_DIR}/qlogvalue3daxisformatter_wrapper.cpp ${QtGraphs_GEN_DIR}/qvalue3daxis_wrapper.cpp ${QtGraphs_GEN_DIR}/qvalue3daxisformatter_wrapper.cpp @@ -26,33 +30,31 @@ ${QtGraphs_GEN_DIR}/qheightmapsurfacedataproxy_wrapper.cpp ${QtGraphs_GEN_DIR}/qitemmodelbardataproxy_wrapper.cpp ${QtGraphs_GEN_DIR}/qitemmodelscatterdataproxy_wrapper.cpp ${QtGraphs_GEN_DIR}/qitemmodelsurfacedataproxy_wrapper.cpp +${QtGraphs_GEN_DIR}/qlegenddata_wrapper.cpp ${QtGraphs_GEN_DIR}/qscatter3dseries_wrapper.cpp ${QtGraphs_GEN_DIR}/qscatterdataitem_wrapper.cpp ${QtGraphs_GEN_DIR}/qscatterdataproxy_wrapper.cpp ${QtGraphs_GEN_DIR}/qsurface3dseries_wrapper.cpp ${QtGraphs_GEN_DIR}/qsurfacedataitem_wrapper.cpp ${QtGraphs_GEN_DIR}/qsurfacedataproxy_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dbars_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dscatter_wrapper.cpp ${QtGraphs_GEN_DIR}/q3dscene_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dsurface_wrapper.cpp -${QtGraphs_GEN_DIR}/qabstract3dgraph_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dinputhandler_wrapper.cpp -${QtGraphs_GEN_DIR}/qabstract3dinputhandler_wrapper.cpp -${QtGraphs_GEN_DIR}/qtouch3dinputhandler_wrapper.cpp -${QtGraphs_GEN_DIR}/q3dtheme_wrapper.cpp # 2D -${QtGraphs_GEN_DIR}/qbarcategoryaxis_wrapper.cpp ${QtGraphs_GEN_DIR}/qabstractaxis_wrapper.cpp -${QtGraphs_GEN_DIR}/qvalueaxis_wrapper.cpp -${QtGraphs_GEN_DIR}/qabstractbarseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qabstractseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qareaseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qbarcategoryaxis_wrapper.cpp +${QtGraphs_GEN_DIR}/qbarmodelmapper_wrapper.cpp ${QtGraphs_GEN_DIR}/qbarseries_wrapper.cpp ${QtGraphs_GEN_DIR}/qbarset_wrapper.cpp +${QtGraphs_GEN_DIR}/qdatetimeaxis_wrapper.cpp ${QtGraphs_GEN_DIR}/qlineseries_wrapper.cpp -${QtGraphs_GEN_DIR}/qabstractseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qpiemodelmapper_wrapper.cpp +${QtGraphs_GEN_DIR}/qpieseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qpieslice_wrapper.cpp ${QtGraphs_GEN_DIR}/qscatterseries_wrapper.cpp -${QtGraphs_GEN_DIR}/qgraphtheme_wrapper.cpp -${QtGraphs_GEN_DIR}/qseriestheme_wrapper.cpp +${QtGraphs_GEN_DIR}/qsplineseries_wrapper.cpp +${QtGraphs_GEN_DIR}/qvalueaxis_wrapper.cpp +${QtGraphs_GEN_DIR}/qxymodelmapper_wrapper.cpp ${QtGraphs_GEN_DIR}/qxyseries_wrapper.cpp # module is always needed ${QtGraphs_GEN_DIR}/qtgraphs_module_wrapper.cpp @@ -63,26 +65,22 @@ set(QtGraphs_include_dirs ${QtGraphs_SOURCE_DIR} ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}QtNetwork_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} - ${Qt${QT_MAJOR_VERSION}QuickWidgets_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Graphs_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Quick3D_INCLUDE_DIRS} ${libpyside_SOURCE_DIR} ${QtCore_GEN_DIR} ${QtNetwork_GEN_DIR} ${QtGui_GEN_DIR} - ${QtWidgets_GEN_DIR} ${QtQml_GEN_DIR} ${QtQuick_GEN_DIR} - ${QtQuickWidgets_GEN_DIR} ${QtQuick3D_GEN_DIR}) set(QtGraphs_libraries pyside6 ${Qt${QT_MAJOR_VERSION}Graphs_LIBRARIES}) -set(QtGraphs_deps QtCore QtNetwork QtGui QtWidgets QtQml QtQuick QtQuickWidgets QtQuick3D) +set(QtGraphs_deps QtCore QtNetwork QtGui QtQml QtQuick QtQuick3D) check_qt_opengl("Graphs" QtGraphs_include_dirs QtGraphs_deps QtGraphs_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml b/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml index 43e4458b..d929bcc0 100644 --- a/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml +++ b/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml @@ -10,11 +10,26 @@ + + - + + + + + + + + + + + + + + @@ -44,6 +59,7 @@ + @@ -53,7 +69,9 @@ + + @@ -63,6 +81,7 @@ snippet="qcustom3dvolume-settexturedata"/> + @@ -71,6 +90,7 @@ + @@ -78,10 +98,17 @@ + + + + + + + @@ -109,148 +136,15 @@ ``deltaZ``, respectively. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + @@ -258,8 +152,9 @@ - + + @@ -270,7 +165,7 @@ - + @@ -281,20 +176,31 @@ - - - - - - - + + + + + + + + Adds the list of data points specified by two + one-dimensional, equally sized numpy arrays representing the x, y values, respectively. + + + + + + Replaces the current points with the points specified by two + one-dimensional, equally sized numpy arrays representing the x, y values, respectively. + + diff --git a/sources/pyside6/PySide6/QtGraphsWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtGraphsWidgets/CMakeLists.txt new file mode 100644 index 00000000..d434d60f --- /dev/null +++ b/sources/pyside6/PySide6/QtGraphsWidgets/CMakeLists.txt @@ -0,0 +1,54 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(QtGraphsWidgets) + +set (QtGraphsWidgets_DROPPED_ENTRIES) + +set(QtGraphsWidgets_SRC +${QtGraphsWidgets_GEN_DIR}/q3dbarswidgetitem_wrapper.cpp +${QtGraphsWidgets_GEN_DIR}/q3dgraphswidgetitem_wrapper.cpp +${QtGraphsWidgets_GEN_DIR}/q3dscatterwidgetitem_wrapper.cpp +${QtGraphsWidgets_GEN_DIR}/q3dsurfacewidgetitem_wrapper.cpp + +# module is always needed +${QtGraphsWidgets_GEN_DIR}/qtgraphswidgets_module_wrapper.cpp +) + +set(QtGraphsWidgets_include_dirs ${QtGraphsWidgets_SOURCE_DIR} + ${QtGraphsWidgets_BINARY_DIR} + ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}QtNetwork_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Graphs_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}QuickWidgets_INCLUDE_DIRS} + ${QtCore_GEN_DIR} + ${QtGui_GEN_DIR} + ${QtNetwork_GEN_DIR} + ${QtWidgets_GEN_DIR} + ${QtGraphs_GEN_DIR} + ${QtQml_GEN_DIR} + ${QtQuick_GEN_DIR} + ${QtQuickWidgets_GEN_DIR} + ${libpyside_SOURCE_DIR} +) + +set(QtGraphsWidgets_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}GraphsWidgets_LIBRARIES}) + +set(QtGraphsWidgets_deps QtQuickWidgets QtGraphs) + +check_qt_opengl("GraphsWidgets" QtGraphsWidgets_include_dirs QtGraphsWidgets_deps + QtGraphsWidgets_DROPPED_ENTRIES) + +create_pyside_module(NAME QtGraphsWidgets + INCLUDE_DIRS QtGraphsWidgets_include_dirs + LIBRARIES QtGraphsWidgets_libraries + DEPS QtGraphsWidgets_deps + TYPESYSTEM_PATH QtGraphsWidgets_SOURCE_DIR + SOURCES QtGraphsWidgets_SRC + STATIC_SOURCES QtGraphsWidgets_src + DROPPED_ENTRIES QtGraphsWidgets_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtGraphsWidgets/typesystem_graphswidgets.xml b/sources/pyside6/PySide6/QtGraphsWidgets/typesystem_graphswidgets.xml new file mode 100644 index 00000000..806903ac --- /dev/null +++ b/sources/pyside6/PySide6/QtGraphsWidgets/typesystem_graphswidgets.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtGui/CMakeLists.txt b/sources/pyside6/PySide6/QtGui/CMakeLists.txt index 99e0789d..94007538 100644 --- a/sources/pyside6/PySide6/QtGui/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGui/CMakeLists.txt @@ -57,6 +57,7 @@ ${QtGui_GEN_DIR}/qrhitexturerendertargetdescription_wrapper.cpp ${QtGui_GEN_DIR}/qrhitexturesubresourceuploaddescription_wrapper.cpp ${QtGui_GEN_DIR}/qrhitextureuploaddescription_wrapper.cpp ${QtGui_GEN_DIR}/qrhitextureuploadentry_wrapper.cpp +${QtGui_GEN_DIR}/qrhitexture_viewformat_wrapper.cpp ${QtGui_GEN_DIR}/qrhivertexinputattribute_wrapper.cpp ${QtGui_GEN_DIR}/qrhivertexinputbinding_wrapper.cpp ${QtGui_GEN_DIR}/qrhivertexinputlayout_wrapper.cpp @@ -85,6 +86,8 @@ ${QtGui_GEN_DIR}/qabstracttextdocumentlayout_selection_wrapper.cpp ${QtGui_GEN_DIR}/qabstracttextdocumentlayout_wrapper.cpp ${QtGui_GEN_DIR}/qaccessible_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleactioninterface_wrapper.cpp +${QtGui_GEN_DIR}/qaccessibleannouncementevent_wrapper.cpp +${QtGui_GEN_DIR}/qaccessibleattributesinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleeditabletextinterface_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleevent_wrapper.cpp ${QtGui_GEN_DIR}/qaccessibleobject_wrapper.cpp @@ -190,6 +193,7 @@ ${QtGui_GEN_DIR}/qpainterpath_wrapper.cpp ${QtGui_GEN_DIR}/qpainterpathstroker_wrapper.cpp ${QtGui_GEN_DIR}/qpaintevent_wrapper.cpp ${QtGui_GEN_DIR}/qpalette_wrapper.cpp +${QtGui_GEN_DIR}/qpdfoutputintent_wrapper.cpp ${QtGui_GEN_DIR}/qpdfwriter_wrapper.cpp ${QtGui_GEN_DIR}/qpen_wrapper.cpp ${QtGui_GEN_DIR}/qpicture_wrapper.cpp @@ -313,6 +317,15 @@ set(QtGui_private_include_dirs ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) +if (${CMAKE_SYSTEM_NAME} STREQUAL "Android") + if (QT_FEATURE_opengles2) + # add openGL ES 2.0 + find_package(GLESv2 REQUIRED) + else() + message(FATAL_ERROR "QtGui requires OpenGL ES 2.0 on Android") + endif() +endif() + configure_file("${QtGui_SOURCE_DIR}/QtGui_global.post.h.in" "${QtGui_BINARY_DIR}/QtGui_global.post.h" @ONLY) @@ -343,4 +356,3 @@ create_pyside_module(NAME QtGui install(FILES ${pyside6_SOURCE_DIR}/qpytextobject.h ${pyside6_SOURCE_DIR}/qtguihelper.h DESTINATION include/PySide6/QtGui/) - diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml index 0cab80a1..e5637cf2 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml @@ -81,7 +81,8 @@ - + @@ -105,12 +106,14 @@ file="../glue/qtgui.cpp" snippet="qaccessible-pysidefactory"/> + + + @@ -129,6 +133,8 @@ + - + - + @@ -192,7 +198,7 @@ - + @@ -202,7 +208,7 @@ - + @@ -243,7 +249,7 @@ - + @@ -325,11 +331,11 @@ - + - + @@ -378,7 +384,7 @@ - + @@ -456,7 +462,7 @@ - + @@ -687,14 +693,14 @@ - + - + @@ -782,12 +788,12 @@ - + - + @@ -795,7 +801,7 @@ - + @@ -807,7 +813,7 @@ - + @@ -1017,9 +1023,11 @@ + + @@ -1116,6 +1124,8 @@ + + @@ -1175,10 +1185,10 @@ - + - + @@ -1325,21 +1335,21 @@ - + - + - + @@ -1351,7 +1361,7 @@ - + @@ -1365,7 +1375,7 @@ - + @@ -1385,7 +1395,7 @@ - + @@ -1396,7 +1406,7 @@ polymorphic-id-expression="%B->type() == QEvent::InputMethodQuery"/> - + @@ -1409,7 +1419,7 @@ - + @@ -1425,7 +1435,7 @@ polymorphic-id-expression="%B->type() == QEvent::StatusTip"/> - + @@ -1436,7 +1446,7 @@ - + @@ -1445,7 +1455,7 @@ - + @@ -1454,10 +1464,10 @@ - + - + @@ -1474,7 +1484,7 @@ - + @@ -1485,7 +1495,7 @@ - + @@ -1874,13 +1884,13 @@ - + - + @@ -1894,12 +1904,12 @@ - + - + @@ -1918,15 +1928,15 @@ - + - + - + @@ -1936,7 +1946,7 @@ - + @@ -2011,7 +2021,7 @@ - + @@ -2020,7 +2030,7 @@ - + @@ -2028,7 +2038,7 @@ - + @@ -2048,7 +2058,7 @@ - + @@ -2057,7 +2067,7 @@ - + @@ -2065,7 +2075,7 @@ - + @@ -2085,7 +2095,7 @@ - + @@ -2094,7 +2104,7 @@ - + @@ -2102,7 +2112,7 @@ - + @@ -2122,7 +2132,7 @@ - + @@ -2131,7 +2141,7 @@ - + @@ -2139,7 +2149,7 @@ - + @@ -2159,7 +2169,7 @@ - + @@ -2168,7 +2178,7 @@ - + @@ -2176,7 +2186,7 @@ - + @@ -2196,7 +2206,7 @@ - + @@ -2205,7 +2215,7 @@ - + @@ -2213,7 +2223,7 @@ - + @@ -2233,7 +2243,7 @@ - + @@ -2242,7 +2252,7 @@ - + @@ -2250,7 +2260,7 @@ - + @@ -2270,7 +2280,7 @@ - + @@ -2279,7 +2289,7 @@ - + @@ -2287,7 +2297,7 @@ - + @@ -2327,7 +2337,7 @@ - + @@ -2336,7 +2346,7 @@ - + @@ -2398,7 +2408,7 @@ _______ end of matrix block _______ --> - + @@ -2407,7 +2417,7 @@ - + @@ -2440,7 +2450,7 @@ - + @@ -2453,7 +2463,7 @@ - + @@ -2462,7 +2472,7 @@ - + @@ -2485,7 +2495,7 @@ - + @@ -2494,7 +2504,7 @@ - + @@ -2517,7 +2527,7 @@ - + @@ -2526,7 +2536,7 @@ - + @@ -2573,7 +2583,7 @@ - + @@ -2622,7 +2632,7 @@ - + @@ -3078,6 +3088,7 @@ + @@ -3098,7 +3109,10 @@ - + + + + diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml index 22ab56e2..656f18ca 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml @@ -65,6 +65,7 @@ + diff --git a/sources/pyside6/PySide6/QtHelp/CMakeLists.txt b/sources/pyside6/PySide6/QtHelp/CMakeLists.txt index 7bf46dce..46509e70 100644 --- a/sources/pyside6/PySide6/QtHelp/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtHelp/CMakeLists.txt @@ -18,6 +18,7 @@ ${QtHelp_GEN_DIR}/qhelpindexmodel_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpindexwidget_wrapper.cpp ${QtHelp_GEN_DIR}/qhelplink_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpsearchengine_wrapper.cpp +${QtHelp_GEN_DIR}/qhelpsearchenginecore_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpsearchquery_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpsearchquerywidget_wrapper.cpp ${QtHelp_GEN_DIR}/qhelpsearchresult_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtHelp/typesystem_help.xml b/sources/pyside6/PySide6/QtHelp/typesystem_help.xml index 5fa30e03..204fd563 100644 --- a/sources/pyside6/PySide6/QtHelp/typesystem_help.xml +++ b/sources/pyside6/PySide6/QtHelp/typesystem_help.xml @@ -9,24 +9,29 @@ - + - + - + + + + + + diff --git a/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt b/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt index c931f064..b2a4fda8 100644 --- a/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtHttpServer/CMakeLists.txt @@ -12,6 +12,7 @@ set(QtHttpServer_SRC ${QtHttpServer_GEN_DIR}/qhttpserverresponse_wrapper.cpp ${QtHttpServer_GEN_DIR}/qhttpserverrouter_wrapper.cpp ${QtHttpServer_GEN_DIR}/qhttpserverrouterrule_wrapper.cpp + ${QtHttpServer_GEN_DIR}/qhttpserverwebsocketupgraderesponse_wrapper.cpp # module is always needed ${QtHttpServer_GEN_DIR}/qthttpserver_module_wrapper.cpp) diff --git a/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml b/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml index 026a8758..66b2ff9e 100644 --- a/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml +++ b/sources/pyside6/PySide6/QtHttpServer/typesystem_httpserver.xml @@ -21,9 +21,9 @@ - + + snippet="qhttpserver-addafterrequesthandler"/> @@ -35,4 +35,7 @@ + + + diff --git a/sources/pyside6/PySide6/QtLocation/CMakeLists.txt b/sources/pyside6/PySide6/QtLocation/CMakeLists.txt index db5777e0..659aa095 100644 --- a/sources/pyside6/PySide6/QtLocation/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtLocation/CMakeLists.txt @@ -13,6 +13,7 @@ ${QtLocation_GEN_DIR}/qgeoroute_wrapper.cpp ${QtLocation_GEN_DIR}/qgeoroutereply_wrapper.cpp ${QtLocation_GEN_DIR}/qgeoroutesegment_wrapper.cpp ${QtLocation_GEN_DIR}/qgeoserviceprovider_wrapper.cpp +${QtLocation_GEN_DIR}/qlocation_wrapper.cpp ${QtLocation_GEN_DIR}/qplace_wrapper.cpp ${QtLocation_GEN_DIR}/qplacecontentreply_wrapper.cpp ${QtLocation_GEN_DIR}/qplacedetailsreply_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtLocation/typesystem_location.xml b/sources/pyside6/PySide6/QtLocation/typesystem_location.xml index e35b7bd1..f3b5f0b2 100644 --- a/sources/pyside6/PySide6/QtLocation/typesystem_location.xml +++ b/sources/pyside6/PySide6/QtLocation/typesystem_location.xml @@ -7,6 +7,9 @@ namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE"> + + + diff --git a/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt b/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt index e40c5a2e..8e6d3e3c 100644 --- a/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt @@ -4,7 +4,11 @@ project(QtMultimedia) set(QtMultimedia_SRC +${QtMultimedia_GEN_DIR}/qabstractvideobuffer_wrapper.cpp +${QtMultimedia_GEN_DIR}/qabstractvideobuffer_mapdata_wrapper.cpp ${QtMultimedia_GEN_DIR}/qaudiobuffer_wrapper.cpp +${QtMultimedia_GEN_DIR}/qaudiobufferinput_wrapper.cpp +${QtMultimedia_GEN_DIR}/qaudiobufferoutput_wrapper.cpp ${QtMultimedia_GEN_DIR}/qaudiodecoder_wrapper.cpp ${QtMultimedia_GEN_DIR}/qaudiodevice_wrapper.cpp ${QtMultimedia_GEN_DIR}/qaudioformat_wrapper.cpp @@ -30,9 +34,10 @@ ${QtMultimedia_GEN_DIR}/qmediatimerange_interval_wrapper.cpp ${QtMultimedia_GEN_DIR}/qscreencapture_wrapper.cpp ${QtMultimedia_GEN_DIR}/qsoundeffect_wrapper.cpp ${QtMultimedia_GEN_DIR}/qtvideo_wrapper.cpp -${QtMultimedia_GEN_DIR}/qvideoframe_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideoframe_paintoptions_wrapper.cpp +${QtMultimedia_GEN_DIR}/qvideoframe_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideoframeformat_wrapper.cpp +${QtMultimedia_GEN_DIR}/qvideoframeinput_wrapper.cpp ${QtMultimedia_GEN_DIR}/qvideosink_wrapper.cpp ${QtMultimedia_GEN_DIR}/qwavedecoder_wrapper.cpp ${QtMultimedia_GEN_DIR}/qwindowcapture_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml index 2791f695..8a8eb5e2 100644 --- a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml +++ b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml @@ -21,7 +21,9 @@ - + + + @@ -30,6 +32,8 @@ + + @@ -123,6 +127,7 @@ + diff --git a/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml b/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml index 05b4e245..c20f5c64 100644 --- a/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml +++ b/sources/pyside6/PySide6/QtMultimediaWidgets/typesystem_multimediawidgets.xml @@ -3,7 +3,7 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt b/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt index 529e2e86..ca2f02c1 100644 --- a/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNetwork/CMakeLists.txt @@ -15,6 +15,9 @@ ${QtNetwork_GEN_DIR}/qdnslookup_wrapper.cpp ${QtNetwork_GEN_DIR}/qdnsmailexchangerecord_wrapper.cpp ${QtNetwork_GEN_DIR}/qdnsservicerecord_wrapper.cpp ${QtNetwork_GEN_DIR}/qdnstextrecord_wrapper.cpp +${QtNetwork_GEN_DIR}/qdnstlsassociationrecord_wrapper.cpp +${QtNetwork_GEN_DIR}/qformdatabuilder_wrapper.cpp +${QtNetwork_GEN_DIR}/qformdatapartbuilder_wrapper.cpp ${QtNetwork_GEN_DIR}/qhostaddress_wrapper.cpp ${QtNetwork_GEN_DIR}/qhostinfo_wrapper.cpp ${QtNetwork_GEN_DIR}/qhstspolicy_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml index 4dc7c9b0..9f727547 100644 --- a/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml +++ b/sources/pyside6/PySide6/QtNetwork/typesystem_network.xml @@ -64,12 +64,17 @@ + - + + + + + @@ -81,6 +86,11 @@ + + + + + @@ -173,12 +183,56 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -214,7 +268,7 @@ - + @@ -243,7 +297,7 @@ - + @@ -472,4 +526,3 @@ - diff --git a/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt b/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt index 4d9498e4..f713ff10 100644 --- a/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtNetworkAuth/CMakeLists.txt @@ -12,6 +12,7 @@ ${QtNetworkAuth_GEN_DIR}/qoauth1signature_wrapper.cpp ${QtNetworkAuth_GEN_DIR}/qoauth2authorizationcodeflow_wrapper.cpp ${QtNetworkAuth_GEN_DIR}/qoauthhttpserverreplyhandler_wrapper.cpp ${QtNetworkAuth_GEN_DIR}/qoauthoobreplyhandler_wrapper.cpp +${QtNetworkAuth_GEN_DIR}/qoauthurischemereplyhandler_wrapper.cpp # module is always needed ${QtNetworkAuth_GEN_DIR}/qtnetworkauth_module_wrapper.cpp ) diff --git a/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml b/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml index bedfc5a2..888f3d43 100644 --- a/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml +++ b/sources/pyside6/PySide6/QtNetworkAuth/typesystem_networkauth.xml @@ -102,9 +102,12 @@ - + + + + diff --git a/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml b/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml index 1e308a3f..0d7fdfaa 100644 --- a/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml +++ b/sources/pyside6/PySide6/QtOpenGLWidgets/typesystem_openglwidgets.xml @@ -3,7 +3,7 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml b/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml index 5df72075..8228bf72 100644 --- a/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml +++ b/sources/pyside6/PySide6/QtPdfWidgets/typesystem_pdfwidgets.xml @@ -4,7 +4,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> + namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE" + doc-package="PySide6.QtPdf"> diff --git a/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport_common.xml b/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport_common.xml index 126902b7..aa64abed 100644 --- a/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport_common.xml +++ b/sources/pyside6/PySide6/QtPrintSupport/typesystem_printsupport_common.xml @@ -55,7 +55,7 @@ - + @@ -68,4 +68,3 @@ - diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp index ca3dfebe..2cab76b4 100644 --- a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp +++ b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp @@ -5,6 +5,7 @@ #include #include +#include #include diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml index 3392ef37..8477ac39 100644 --- a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml +++ b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml @@ -9,6 +9,11 @@ + + + + + // Volatile Bool Ptr type definition for QQmlIncubationController::incubateWhile(std::atomic<bool> *, int) @@ -53,42 +58,42 @@ - - - - - - @@ -142,7 +147,7 @@ - + @@ -193,7 +198,7 @@ - + @@ -224,7 +229,7 @@ - - diff --git a/sources/pyside6/PySide6/QtQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtQuick/CMakeLists.txt index 77523024..a2bef339 100644 --- a/sources/pyside6/PySide6/QtQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtQuick/CMakeLists.txt @@ -104,9 +104,10 @@ check_qt_opengl("Quick" QtQuick_include_dirs QtQuick_deps if (Qt${QT_MAJOR_VERSION}OpenGL_FOUND) list(APPEND QtQuick_SRC ${QtQuick_GEN_DIR}/qquickframebufferobject_wrapper.cpp - ${QtQuick_GEN_DIR}/qquickframebufferobject_renderer_wrapper.cpp) + ${QtQuick_GEN_DIR}/qquickframebufferobject_renderer_wrapper.cpp + ${QtQuick_GEN_DIR}/qquickopenglutils_wrapper.cpp) else() - list(APPEND QtQuick_DROPPED_ENTRIES QQuickFramebufferObject) + list(APPEND QtQuick_DROPPED_ENTRIES QQuickFramebufferObject QQuickOpenGLUtils) endif() create_pyside_module(NAME QtQuick diff --git a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml index 0a24123f..853c1589 100644 --- a/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml +++ b/sources/pyside6/PySide6/QtQuick/typesystem_quick.xml @@ -39,6 +39,7 @@ + @@ -53,7 +54,7 @@ - + @@ -96,6 +97,7 @@ + @@ -183,6 +185,7 @@ + diff --git a/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml b/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml index fe8f4e70..b981de4b 100644 --- a/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml +++ b/sources/pyside6/PySide6/QtQuick3D/typesystem_quick3d.xml @@ -22,6 +22,16 @@ + + + + + + + diff --git a/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml b/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml index 72fc0522..38937222 100644 --- a/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml +++ b/sources/pyside6/PySide6/QtQuickControls2/typesystem_quickcontrols2.xml @@ -3,7 +3,7 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml b/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml index 4f4484cb..410ae2d2 100644 --- a/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml +++ b/sources/pyside6/PySide6/QtQuickWidgets/typesystem_quickwidgets.xml @@ -3,7 +3,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml b/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml index fdd2b148..6cf562c5 100644 --- a/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml +++ b/sources/pyside6/PySide6/QtSerialBus/typesystem_serialbus.xml @@ -19,8 +19,8 @@ - - + + @@ -33,6 +33,22 @@ The function returns a tuple of (device_list, error_string). + + + + + + + + + + + + The function returns a tuple of (device_list, error_string). + + + @@ -44,7 +60,7 @@ - + The function returns a tuple of (device, error_string). @@ -81,7 +97,28 @@ - + + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml b/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml index b9c2557a..98c18f07 100644 --- a/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml +++ b/sources/pyside6/PySide6/QtSpatialAudio/typesystem_spatialaudio.xml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ - + diff --git a/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml b/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml index 0e29f240..369c78bd 100644 --- a/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml +++ b/sources/pyside6/PySide6/QtStateMachine/typesystem_statemachine.xml @@ -108,7 +108,7 @@ s1.addTransition(button.clicked, s1h)</code> - + @@ -146,7 +146,7 @@ s1.addTransition(button.clicked, s1h)</code> - + @@ -179,6 +179,17 @@ s1.addTransition(button.clicked, s1h)</code> + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml b/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml index 36bda1ea..b9271427 100644 --- a/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml +++ b/sources/pyside6/PySide6/QtSvgWidgets/typesystem_svgwidgets.xml @@ -3,7 +3,7 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtTest/typesystem_test.xml b/sources/pyside6/PySide6/QtTest/typesystem_test.xml index 2fc4f23c..173392f3 100644 --- a/sources/pyside6/PySide6/QtTest/typesystem_test.xml +++ b/sources/pyside6/PySide6/QtTest/typesystem_test.xml @@ -3,7 +3,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - diff --git a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt index 0cdaf2f9..9cc3d87e 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt @@ -3,17 +3,26 @@ project(QtWebEngineCore) +set(QtWebEngineCore_static_sources + "${QtWebEngineCore_SOURCE_DIR}/glue/webenginepage_functors.cpp" + "${QtWebEngineCore_SOURCE_DIR}/glue/webenginepage_functors.h" + "${QtWebEngineCore_SOURCE_DIR}/glue/webengineframe.cpp" + "${QtWebEngineCore_SOURCE_DIR}/glue/webengineframe.h" +) + set(QtWebEngineCore_SRC ${QtWebEngineCore_GEN_DIR}/qwebenginecertificateerror_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificateselection_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificatestore_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineclienthints_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecontextmenurequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_filterrequest_wrapper.cpp -# FIXME ${QtWebEngineCore_GEN_DIR}/qwebenginedesktopmediarequest_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginedesktopmediarequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginedownloadrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefilesystemaccessrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefindtextresult_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebengineframe_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefullscreenrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineglobalsettings_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineglobalsettings_dnsmode_wrapper.cpp @@ -23,10 +32,10 @@ ${QtWebEngineCore_GEN_DIR}/qwebenginehistorymodel_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginehttprequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginenavigationrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineloadinginfo_wrapper.cpp -# FIXME ${QtWebEngineCore_GEN_DIR}/qwebenginemediasourcemodel_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginenewwindowrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginenotification_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginepage_wrapper.cpp +${QtWebEngineCore_GEN_DIR}/qwebenginepermission_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineprofile_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginequotarequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineregisterprotocolhandlerrequest_wrapper.cpp @@ -68,8 +77,8 @@ set(QtWebEngineCore_deps QtCore QtGui QtNetwork QtPrintSupport QtWebChannel) create_pyside_module(NAME QtWebEngineCore INCLUDE_DIRS QtWebEngineCore_include_dirs + STATIC_SOURCES QtWebEngineCore_static_sources LIBRARIES QtWebEngineCore_libraries DEPS QtWebEngineCore_deps TYPESYSTEM_PATH QtWebEngineCore_SOURCE_DIR SOURCES QtWebEngineCore_SRC) - diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp new file mode 100644 index 00000000..fb209d51 --- /dev/null +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "webengineframe.h" + +#include + +QT_BEGIN_NAMESPACE + +// Enable using QWebEngineFrame as a value-type by adding a way of +// default-constructing by creating a replica with the same data members. +// (see attribute "default-constructor"). +QWebEngineFrame defaultConstructedWebEngineFrame() +{ + class FriendlyWebEngineFrame // Keep in sync with QWebEngineFrame + { + public: + QWeakPointer m_w; + quint64 m_id = 0; + }; + + FriendlyWebEngineFrame frame; + return std::move(*reinterpret_cast(&frame)); +} + +QT_END_NAMESPACE diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h new file mode 100644 index 00000000..a792c7ba --- /dev/null +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h @@ -0,0 +1,17 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef WEBENGINEFRAME_H +#define WEBENGINEFRAME_H + +#include + +QT_FORWARD_DECLARE_CLASS(QWebEngineFrame) + +QT_BEGIN_NAMESPACE + +QWebEngineFrame defaultConstructedWebEngineFrame(); + +QT_END_NAMESPACE + +#endif // WEBENGINEFRAME_H diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp new file mode 100644 index 00000000..3591591a --- /dev/null +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "webenginepage_functors.h" + +#include "autodecref.h" +#include "gilstate.h" +#include "sbkconverter.h" + +#include "pysideutils.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +void RunJavascriptFunctor::operator()(const QVariant &result) +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + switch (result.typeId()) { + case QMetaType::Bool: { + PyObject *pyValue = result.toBool() ? Py_True : Py_False; + Py_INCREF(pyValue); + PyTuple_SetItem(arglist, 0, pyValue); + } + break; + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::Double: + PyTuple_SetItem(arglist, 0, PyFloat_FromDouble(result.toDouble())); + break; + default: { + const QString value = result.toString(); + PyTuple_SetItem(arglist, 0, PySide::qStringToPyUnicode(value)); + } + break; + } + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); + release(); // single shot +} + +void PrintToPdfFunctor::operator()(const QByteArray &pdf) +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + + Shiboken::Conversions::SpecificConverter converter("QByteArray"); + PyObject *pyPdf = converter.toPython(&pdf); + PyTuple_SetItem(arglist, 0, pyPdf); + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); + release(); // single shot +} + +QT_END_NAMESPACE diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h new file mode 100644 index 00000000..535c2792 --- /dev/null +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h @@ -0,0 +1,32 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef WEBENGINEPAGE_FUNCTORS_H +#define WEBENGINEPAGE_FUNCTORS_H + +#include "pyobjectholder.h" + +#include + +QT_FORWARD_DECLARE_CLASS(QByteArray) +QT_FORWARD_DECLARE_CLASS(QVariant) + +QT_BEGIN_NAMESPACE + +struct RunJavascriptFunctor : public Shiboken::PyObjectHolder +{ + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(const QVariant &result); +}; + +struct PrintToPdfFunctor : public Shiboken::PyObjectHolder +{ + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(const QByteArray &pdf); +}; + +QT_END_NAMESPACE + +#endif // WEBENGINEPAGE_FUNCTORS_H diff --git a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml index da9259cc..36c15c4e 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml +++ b/sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml @@ -3,7 +3,7 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - @@ -11,6 +11,10 @@ + + + + @@ -34,6 +38,8 @@ + + @@ -53,6 +59,35 @@ + + + + + + + + + + + + + + + + + + + + + @@ -72,6 +107,9 @@ + + + @@ -92,20 +130,64 @@ - - + + + - - + + + + - - + + + + - - + + + + + + + + + + + + + + + + + + + + + + + @@ -114,6 +196,7 @@ snippet="qwebengineprofile-functor"/> + @@ -133,8 +216,10 @@ + + @@ -170,10 +255,11 @@ - + + + + diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml index 2e7d2233..3d383337 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml +++ b/sources/pyside6/PySide6/QtWebEngineQuick/typesystem_webenginequick.xml @@ -3,7 +3,7 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - @@ -13,6 +13,7 @@ + diff --git a/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt index 9da97243..919db570 100644 --- a/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineWidgets/CMakeLists.txt @@ -40,6 +40,3 @@ create_pyside_module(NAME QtWebEngineWidgets DEPS QtWebEngineWidgets_deps TYPESYSTEM_PATH QtWebEngineWidgets_SOURCE_DIR SOURCES QtWebEngineWidgets_SRC) - - - diff --git a/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml b/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml index 61874856..206efb68 100644 --- a/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml +++ b/sources/pyside6/PySide6/QtWebEngineWidgets/typesystem_webenginewidgets.xml @@ -3,7 +3,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only --> - @@ -13,8 +13,9 @@ - - + + diff --git a/sources/pyside6/PySide6/QtWebView/CMakeLists.txt b/sources/pyside6/PySide6/QtWebView/CMakeLists.txt new file mode 100644 index 00000000..158c720c --- /dev/null +++ b/sources/pyside6/PySide6/QtWebView/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(QtWebView) + +set(QtWebView_Src +${QtWebView_GEN_DIR}/qtwebview_wrapper.cpp +# module is always needed +${QtWebView_GEN_DIR}/qtwebview_module_wrapper.cpp +) + +set(QtWebView_include_dirs ${QtWebView_SOURCE_DIR} + ${QtWebView_BINARY_DIR} + ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}WebView_INCLUDE_DIRS} + ${libpyside_SOURCE_DIR} + ${QtGui_GEN_DIR} + ${QtCore_GEN_DIR} + ${QtWebView_GEN_DIR}) + +set(QtWebView_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}WebView_LIBRARIES}) + +set(QtWebView_deps QtGui) + +# for Windows and Linux, QtWebView depends on QtWebEngine to render content +if ((WIN32 OR UNIX) AND NOT APPLE) + list(APPEND QtWebView_deps QtWebEngineCore QtWebEngineQuick) +endif() + +create_pyside_module(NAME QtWebView + INCLUDE_DIRS QtWebView_include_dirs + LIBRARIES QtWebView_libraries + DEPS QtWebView_deps + TYPESYSTEM_PATH QtWebView_SOURCE_DIR + SOURCES QtWebView_Src) diff --git a/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml b/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml new file mode 100644 index 00000000..1983ed81 --- /dev/null +++ b/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 0669c18f..7879cdeb 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -1151,7 +1151,7 @@ - + @@ -1205,7 +1205,7 @@ - + @@ -1291,7 +1291,7 @@ - + @@ -1980,7 +1980,7 @@ - + @@ -2032,9 +2032,6 @@ - - - @@ -2311,8 +2308,12 @@ + - + + + + @@ -2737,7 +2739,7 @@ - + @@ -2901,7 +2903,7 @@ - + @@ -2944,15 +2946,15 @@ - - - + + + diff --git a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml index 3661a67b..089978b6 100644 --- a/sources/pyside6/PySide6/QtXml/typesystem_xml.xml +++ b/sources/pyside6/PySide6/QtXml/typesystem_xml.xml @@ -170,4 +170,3 @@ - diff --git a/sources/pyside6/PySide6/__init__.py.in b/sources/pyside6/PySide6/__init__.py.in index d0a4ecc3..45c19f2e 100644 --- a/sources/pyside6/PySide6/__init__.py.in +++ b/sources/pyside6/PySide6/__init__.py.in @@ -1,15 +1,16 @@ import os import sys from pathlib import Path -from textwrap import dedent +from types import ModuleType +# mypy: disable-error-code="name-defined" -# __all__ is also corrected below. -__all__ = list("Qt" + body for body in - "@all_module_shortnames@" - .split(";")) +# __all__ is computed below. +__pre_all__ = [@init_modules@] __version__ = "@FINAL_PACKAGE_VERSION@" __version_info__ = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_PRE_RELEASE_VERSION_TYPE@", "@BINDING_API_PRE_RELEASE_VERSION@") +SKIP_MYPY_TEST = bool("@SKIP_MYPY_TEST@") + def _additional_dll_directories(package_dir): # Find shiboken6 relative to the package directory. @@ -18,7 +19,7 @@ def _additional_dll_directories(package_dir): if root.suffix == '.zip': return [] shiboken6 = root / 'shiboken6' - if shiboken6.is_dir(): # Standard case, only shiboken6 is needed + if shiboken6.is_dir(): # Standard case, only shiboken6 is needed return [shiboken6] # The below code is for the build process when generate_pyi.py # is executed in the build directory. We need libpyside and Qt in addition. @@ -31,9 +32,9 @@ def _additional_dll_directories(package_dir): result.append(libpysideqml) for path in os.environ.get('PATH').split(';'): if path: - if (Path(path) / 'qmake.exe').exists(): - result.append(path) - break + if (Path(path) / 'qmake.exe').exists(): + result.append(path) + break return result @@ -54,13 +55,14 @@ def _setupQtDirectories(): pyside_package_dir = Path(__file__).parent.resolve() - if sys.platform == 'win32' and sys.version_info[0] == 3 and sys.version_info[1] >= 8: + if sys.platform == 'win32': for dir in _additional_dll_directories(pyside_package_dir): os.add_dll_directory(os.fspath(dir)) try: # PYSIDE-1497: we use the build dir or install dir or site-packages, whatever the path # setting dictates. There is no longer a difference in path structure. + global Shiboken from shiboken6 import Shiboken except Exception: paths = ', '.join(sys.path) @@ -97,20 +99,13 @@ def _find_all_qt_modules(): # Since the wheel split, the __all__ variable cannot be computed statically, # because we don't know all modules in advance. - # Instead, we look into the file system and quickly build a list of all - # existing .pyi files, because importing is not desired and also impossible during import. - # By using the initially created list, we can keep some order intact. + # Instead, we use __getattr__ which is supported since Python 3.7 + # and create the __all__ list on demand when needed. location = Path(__file__).resolve().parent - - # Note: We should _not_ call this function while still building, but use the existing value! - in_build = Path("@CMAKE_BINARY_DIR@") in location.parents - - if in_build: - return __all__ - files = os.listdir(location) - unordered = set(name[:-4] for name in files if name.startswith("Qt") and name.endswith(".pyi")) - ordered_part = __all__ + unordered = set(name[: name.find(".")] for name in files if name.startswith("Qt") and ( + name.endswith((".pyd", ".so")))) + ordered_part = __pre_all__ result = [] for name in ordered_part: if name in unordered: @@ -120,5 +115,27 @@ def _find_all_qt_modules(): return result -__all__ = _find_all_qt_modules() +# Provide the __all__ variable only on access. +def __getattr__(name: str) -> list[str]: + if name == "__all__": + global __all__ + __all__ = _find_all_qt_modules() + return __all__ + raise AttributeError(f"module '{__name__}' has no attribute '{name}' :)") + + +# Be prepared that people can access the module dict instead. +class ModuleDict(dict): + def __missing__(self, key): + if key == "__all__": + self[key] = __all__ if "__all__" in globals() else __getattr__("__all__") + return __all__ + raise KeyError(f"dict of module '{__name__}' has no key '{key}' :)") + + +class SubModule(ModuleType): + pass + + _setupQtDirectories() +Shiboken.replaceModuleDict(sys.modules["PySide6"], SubModule, ModuleDict(globals())) diff --git a/sources/pyside6/PySide6/doc/qtcore.rst b/sources/pyside6/PySide6/doc/qtcore.rst new file mode 100644 index 00000000..35ba0d1c --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtcore.rst @@ -0,0 +1,95 @@ +// @snippet q_arg +This function takes a type (or a type string) and a value of that type +and returns an internal object that can be passed to +:meth:`QMetaObject.invokeMethod`. See also Q_RETURN_ARG(). +// @snippet q_arg + +// @snippet q_return_arg +This macro takes a type (or a type string) a value of which is then +returned by :meth:`QMetaObject.invokeMethod`. See also Q_ARG(). +// @snippet q_return_arg + +// @snippet qlocale-system +Returns a QLocale object initialized to the system locale. + +The system locale may use system-specific sources for locale data, where +available, otherwise falling back on QLocale's built-in database entry for the +language, script and territory the system reports. + +For example, on Windows, this locale will use the decimal/grouping characters and +date/time formats specified in the system configuration panel. + +.. note:: Qt for Python on macOS will not reflect the user's region and language + preferences though QLocale::system(), but will instead reflect the + environment variables POSIX uses to specify locale, similar to Python's + locale module. If the system locale cannot be determined, which can be + due to none of the variables 'LC_ALL', 'LC_CTYPE', 'LANG' or 'LANGUAGE' + being set by your environment, then the default POSIX locale or + 'C' locale is returned. + +See also c(). +// @snippet qlocale-system + +// @snippet qabstractitemmodel-createindex +Creates a model index for the given row and column with the internal pointer +ptr. When using a :class:`QSortFilterProxyModel`, its indexes have their own +internal pointer. It is not advisable to access this internal pointer outside +of the model. Use the ``data()`` function instead. + +This function provides a consistent interface that model subclasses must use to +create model indexes. + +.. warning:: Because of some Qt/Python integration rules, the ``ptr`` argument does + not get the reference incremented during the QModelIndex life time. + So it is necessary to keep the object used on ``ptr`` argument alive + during the whole process. Do not destroy the object if you are not + sure about that. +// @snippet qabstractitemmodel-createindex + +// @snippet qobject-findChild +To find the child of a certain :class:`QObject`, the first argument of this +function should be the child's type, and the second the name of the child: + +:: + + ... + parent = QWidget() + ... + # The first argument must be the child type + child1 = parent.findChild(QPushButton, "child_button") + child2 = parent.findChild(QWidget, "child_widget") + +// @snippet qobject-findChild + +// @snippet qcoreapplication-init +Constructs a Qt kernel application. Kernel applications are applications +without a graphical user interface. These type of applications are used +at the console or as server processes. + +The *args* argument is processed by the application, and made available +in a more convenient form by the :meth:`~PySide6.QtCore.QCoreApplication.arguments()` +method. +// @snippet qcoreapplication-init + +// @snippet qsettings-value +Custom overload that adds an optional named parameter to the function ``value()`` +to automatically cast the type that is being returned by the function. + +An example of this situation could be an ini file that contains +the value of a one-element list:: + + settings.setValue('var', ['a']) + +The the ini file will be:: + + [General] + var=a # we cannot know that this is a list! + +Once we read it, we could specify if we want +the default behavior, a str, or to cast the output +to a list. + + settings.value('var') # Will get "a" + settings.value('var', type=list) # Will get ["a"] + +// @snippet qsettings-value diff --git a/sources/pyside6/PySide6/doc/qtqml_functions.rst b/sources/pyside6/PySide6/doc/qtqml.rst similarity index 100% rename from sources/pyside6/PySide6/doc/qtqml_functions.rst rename to sources/pyside6/PySide6/doc/qtqml.rst diff --git a/sources/pyside6/PySide6/doc/qtwebenginecore.rst b/sources/pyside6/PySide6/doc/qtwebenginecore.rst new file mode 100644 index 00000000..75e2e32d --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtwebenginecore.rst @@ -0,0 +1,75 @@ +// @snippet qwebenginepage-async-note +.. note:: We guarantee that the ``resultCallback`` is always called, but it + might be done during page destruction. When ``QWebEnginePage`` + is deleted, the callback is triggered with an invalid value and it + is not safe to use the corresponding ``QWebEnginePage``, + ``QWebEngineFrame``, or ``QWebEngineView`` instance inside it. +// @snippet qwebenginepage-async-note + +// @snippet qwebenginepage-findtext +Finds the specified string, ``subString``, in the page, using the given +``options``. The ``findTextFinished()`` signal is emitted when a string search +is completed. + +To clear the search highlight, just pass an empty string. + +The ``resultCallback`` must take a ``QWebEngineFindTextResult`` parameter. +// @snippet qwebenginepage-findtext + +// @snippet qwebenginepage-tohtml +Asynchronous method to retrieve the page's content as HTML, enclosed in HTML +and BODY tags. Upon successful completion, ``resultCallback`` is called with +the page's content. +// @snippet qwebenginepage-tohtml + +// @snippet qwebenginepage-toplaintext +Asynchronous method to retrieve the page's content converted to plain text, +completely stripped of all HTML formatting. + +Upon successful completion, ``resultCallback`` is called with the page's content. +// @snippet qwebenginepage-toplaintext + +// @snippet qwebenginepage-runjavascript +Runs the JavaScript code contained in ``scriptSource`` script on this frame, +without checking whether the DOM of the page has been constructed. + +To avoid conflicts with other scripts executed on the page, the world in which +the script is run is specified by ``worldId``. The world ID values are the same +as provided by ``QWebEngineScript.ScriptWorldId``, and between 0 and 256. If +you leave out the world ID, the script is run in the ``MainWorld`` (0). + +When the script has been executed, the callable ``resultCallback`` is called +with the result of the last executed statement. + +Only plain data can be returned from JavaScript as the result value. + +.. note:: Do not execute lengthy routines in the callback function, because + it might block the rendering of the web engine page. +// @snippet qwebenginepage-runjavascript + +// @snippet qwebenginepage-printtopdf +Renders the current content of the page into a PDF document and returns a byte +array containing the PDF data as parameter to ``resultCallback``. + +The page size and orientation of the produced PDF document are taken from the +values specified in ``layout``, while the range of pages printed is taken from +``ranges`` with the default being printing all pages. + +.. note:: The ``QWebEnginePage.WebAction.Stop`` web action can be used to + interrupt this operation. +// @snippet qwebenginepage-printtopdf + +// @snippet qwebenginepage-findframebyname +Returns the frame with the given ``name``. If there are multiple frames with +the same name, which one is returned is arbitrary. If no frame was found, +returns ``None``. +// @snippet qwebenginepage-findframebyname + +// @snippet qwebengineframe-printtopdf +Renders the current content of the frame into a PDF document and returns a byte +array containing the PDF data as parameter to ``resultCallback``. Printing uses +a page size of A4, portrait layout, and includes the full range of pages. + +.. note:: The ``QWebEnginePage.WebAction.Stop`` web action can be used to + interrupt this operation. +// @snippet qwebengineframe-printtopdf diff --git a/sources/pyside6/PySide6/glue/qhttpserver.cpp b/sources/pyside6/PySide6/glue/qhttpserver.cpp index b6e485e2..dd08a38a 100644 --- a/sources/pyside6/PySide6/glue/qhttpserver.cpp +++ b/sources/pyside6/PySide6/glue/qhttpserver.cpp @@ -15,7 +15,7 @@ bool cppResult = %CPPSELF.%FUNCTION_NAME(rule, Shiboken::GilState state; auto *requestPtr = &request; Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist, 0, + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QHttpServerRequest *](requestPtr)); PyObject *ret = PyObject_CallObject(callable, arglist); if (PyErr_Occurred()) @@ -29,22 +29,23 @@ bool cppResult = %CPPSELF.%FUNCTION_NAME(rule, %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); // @snippet qhttpserver-route -// @snippet qhttpserver-afterrequest -auto *callable = %PYARG_1; +// @snippet qhttpserver-addafterrequesthandler +auto *callable = %PYARG_2; -%CPPSELF.%FUNCTION_NAME([callable](QHttpServerResponse &&response, - const QHttpServerRequest &request) { +auto callback = [callable](const QHttpServerRequest &request, + QHttpServerResponse &response) { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(2)); auto *responsePtr = &response; auto *requestPtr = &request; - PyTuple_SET_ITEM(arglist, 0, - %CONVERTTOPYTHON[QHttpServerResponse *](responsePtr)); - PyTuple_SET_ITEM(arglist, 1, + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QHttpServerRequest *](requestPtr)); + PyTuple_SetItem(arglist, 1, + %CONVERTTOPYTHON[QHttpServerResponse *](responsePtr)); PyObject_CallObject(callable, arglist); if (PyErr_Occurred()) PyErr_Print(); - return std::move(response); -}); -// @snippet qhttpserver-afterrequest +}; + +%CPPSELF.%FUNCTION_NAME(%1, callback); +// @snippet qhttpserver-addafterrequesthandler diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index 0c206db7..c3465682 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -231,8 +231,8 @@ static QVariant QVariant_convertToVariantMap(PyObject *map) Shiboken::AutoDecRef keys(PyDict_Keys(map)); if (!QVariant_isStringList(keys)) return {}; - PyObject *key; - PyObject *value; + PyObject *key{}; + PyObject *value{}; QMap ret; while (PyDict_Next(map, &pos, &key, &value)) { QString cppKey = %CONVERTTOCPP[QString](key); @@ -259,9 +259,9 @@ static QVariant QVariant_convertToVariantList(PyObject *list) QList lst; Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList")); - const Py_ssize_t size = PySequence_Fast_GET_SIZE(fast.object()); + const Py_ssize_t size = PySequence_Size(fast.object()); for (Py_ssize_t i = 0; i < size; ++i) { - PyObject *pyItem = PySequence_Fast_GET_ITEM(fast.object(), i); + Shiboken::AutoDecRef pyItem(PySequence_GetItem(fast.object(), i)); QVariant item = %CONVERTTOCPP[QVariant](pyItem); lst.append(item); } @@ -322,7 +322,7 @@ QByteArray uncompressed = %FUNCTION_NAME(ptr, %2); QList version = QByteArray(qVersion()).split('.'); PyObject *pyQtVersion = PyTuple_New(3); for (int i = 0; i < 3; ++i) - PyTuple_SET_ITEM(pyQtVersion, i, PyLong_FromLong(version[i].toInt())); + PyTuple_SetItem(pyQtVersion, i, PyLong_FromLong(version[i].toInt())); PyModule_AddObject(module, "__version_info__", pyQtVersion); PyModule_AddStringConstant(module, "__version__", qVersion()); // @snippet qt-version @@ -374,7 +374,7 @@ PyModule_AddStringConstant(module, "__version__", qVersion()); // @snippet qobject-connect-6 // @snippet qobject-emit -%RETURN_TYPE %0 = PySide::SignalManager::instance().emitSignal(%CPPSELF, %1, %PYARG_2); +%RETURN_TYPE %0 = PySide::SignalManager::emitSignal(%CPPSELF, %1, %PYARG_2); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); // @snippet qobject-emit @@ -424,7 +424,6 @@ Shiboken::Conversions::registerConverterName(SbkPySide6_QtCoreTypeConverters[SBK PySide::registerInternalQtConf(); PySide::init(module); -Py_AtExit(QtCoreModuleExit); // @snippet qt-pysideinit // @snippet qt-messagehandler @@ -435,17 +434,13 @@ static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, co { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(3)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QtMsgType](type)); - PyTuple_SET_ITEM(arglist, 1, %CONVERTTOPYTHON[QMessageLogContext &](ctx)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QtMsgType](type)); + PyTuple_SetItem(arglist, 1, %CONVERTTOPYTHON[QMessageLogContext &](ctx)); QByteArray array = msg.toUtf8(); // Python handler requires UTF-8 const char *data = array.constData(); - PyTuple_SET_ITEM(arglist, 2, %CONVERTTOPYTHON[const char *](data)); + PyTuple_SetItem(arglist, 2, %CONVERTTOPYTHON[const char *](data)); Shiboken::AutoDecRef ret(PyObject_CallObject(qtmsghandler, arglist)); } -static void QtCoreModuleExit() -{ - PySide::SignalManager::instance().clear(); -} // @snippet qt-messagehandler // @snippet qt-installmessagehandler @@ -479,8 +474,8 @@ namespace PySide { QPointF p; %RETURN_TYPE retval = %CPPSELF.%FUNCTION_NAME(%ARGUMENT_NAMES, &p); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QPointF](p)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QPointF](p)); // @snippet qlinef-intersect // @snippet qresource-data @@ -503,23 +498,26 @@ if (!PyDateTimeAPI) int year, month, day; %CPPSELF.%FUNCTION_NAME(&year, &month, &day); %PYARG_0 = PyTuple_New(3); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[int](year)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[int](month)); -PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[int](day)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](year)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](month)); +PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](day)); // @snippet qdate-getdate // @snippet qdate-weeknumber int yearNumber; int week = %CPPSELF.%FUNCTION_NAME(&yearNumber); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[int](week)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[int](yearNumber)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](week)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](yearNumber)); // @snippet qdate-weeknumber // @snippet qdatetime-1 QDate date(%1, %2, %3); QTime time(%4, %5, %6, %7); -%0 = new %TYPE(date, time, Qt::TimeSpec(%8)); +%0 = new %TYPE(date, time, + Qt::TimeSpec(%8) == Qt::UTC + ? QTimeZone(QTimeZone::UTC) : QTimeZone(QTimeZone::LocalTime)); +Shiboken::Warnings::warnDeprecated("QDateTime", "QDateTime(..., Qt::TimeSpec spec)"); // @snippet qdatetime-1 // @snippet qdatetime-2 @@ -531,7 +529,9 @@ QTime time(%4, %5, %6); // @snippet qdatetime-3 QDate date(%1, %2, %3); QTime time(%4, %5, %6, %7); -%0 = new %TYPE(date, time, %8); +%0 = new %TYPE(date, time, + %8 == Qt::UTC ? QTimeZone(QTimeZone::UTC) : QTimeZone(QTimeZone::LocalTime)); +Shiboken::Warnings::warnDeprecated("QDateTime", "QDateTime(..., Qt::TimeSpec spec)"); // @snippet qdatetime-3 // @snippet qdatetime-topython @@ -553,9 +553,10 @@ return %CPPSELF.size(); // @snippet qbitarray-len // @snippet qbitarray-getitem -if (_i < 0 || _i >= %CPPSELF.size()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return 0; +const Py_ssize_t size = %CPPSELF.size(); +if (_i < 0 || _i >= size) { + Shiboken::Errors::setIndexOutOfBounds(_i, 0, size); + return nullptr; } bool ret = %CPPSELF.at(_i); return %CONVERTTOPYTHON[bool](ret); @@ -569,6 +570,26 @@ Py_XDECREF(result); return !result ? -1 : 0; // @snippet qbitarray-setitem +// @snippet qmodelroledata-setdata +// Call template void QModelRoleData::setData(T &&value) +%CPPSELF.%FUNCTION_NAME(%1); +// @snippet qmodelroledata-setdata + +// @snippet qmodelroledataspan-len +return %CPPSELF.size(); +// @snippet qmodelroledataspan-len + +// @snippet qmodelroledataspan-getitem +const Py_ssize_t size = %CPPSELF.size(); +if (_i < 0 || _i >= size) { + Shiboken::Errors::setIndexOutOfBounds(_i, 0, size); + return nullptr; +} +// Return a pointer to allow for modification using QModelRoleData::setData() +QModelRoleData *item = &((*%CPPSELF)[_i]); +return %CONVERTTOPYTHON[QModelRoleData *](item); +// @snippet qmodelroledataspan-getitem + // @snippet default-enter Py_INCREF(%PYSELF); pyResult = %PYSELF; @@ -630,8 +651,11 @@ if (ret == nullptr) { // @snippet qbytearray-mgetitem if (PyIndex_Check(_key)) { const Py_ssize_t _i = PyNumber_AsSsize_t(_key, PyExc_IndexError); - if (_i < 0 || _i >= %CPPSELF.size()) - return PyErr_Format(PyExc_IndexError, "index out of bounds"); + const Py_ssize_t size = %CPPSELF.size(); + if (_i < 0 || _i >= size) { + Shiboken::Errors::setIndexOutOfBounds(_i, 0, size); + return nullptr; + } char res[2] = {%CPPSELF.at(_i), '\0'}; return PyBytes_FromStringAndSize(res, 1); } @@ -812,7 +836,7 @@ static PyBufferProcs SbkQByteArrayBufferProc = { // @snippet qbytearray-bufferprotocol // @snippet qbytearray-operatorplus-1 -QByteArray ba = QByteArray(PyBytes_AS_STRING(%PYARG_1), PyBytes_GET_SIZE(%PYARG_1)) + *%CPPSELF; +QByteArray ba = QByteArray(PyBytes_AsString(%PYARG_1), PyBytes_Size(%PYARG_1)) + *%CPPSELF; %PYARG_0 = %CONVERTTOPYTHON[QByteArray](ba); // @snippet qbytearray-operatorplus-1 @@ -833,7 +857,7 @@ QByteArray ba = *%CPPSELF + QByteArray(PyByteArray_AsString(%PYARG_1), PyByteArr // @snippet qbytearray-operatorequalequal if (PyUnicode_CheckExact(%PYARG_1)) { Shiboken::AutoDecRef data(PyUnicode_AsASCIIString(%PYARG_1)); - QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_GET_SIZE(data.object())); + QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_Size(data.object())); bool cppResult = %CPPSELF == ba; %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); } @@ -842,7 +866,7 @@ if (PyUnicode_CheckExact(%PYARG_1)) { // @snippet qbytearray-operatornotequal if (PyUnicode_CheckExact(%PYARG_1)) { Shiboken::AutoDecRef data(PyUnicode_AsASCIIString(%PYARG_1)); - QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_GET_SIZE(data.object())); + QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_Size(data.object())); bool cppResult = %CPPSELF != ba; %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); } @@ -851,7 +875,7 @@ if (PyUnicode_CheckExact(%PYARG_1)) { // @snippet qbytearray-operatorgreater if (PyUnicode_CheckExact(%PYARG_1)) { Shiboken::AutoDecRef data(PyUnicode_AsASCIIString(%PYARG_1)); - QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_GET_SIZE(data.object())); + QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_Size(data.object())); bool cppResult = %CPPSELF > ba; %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); } @@ -860,7 +884,7 @@ if (PyUnicode_CheckExact(%PYARG_1)) { // @snippet qbytearray-operatorgreaterequal if (PyUnicode_CheckExact(%PYARG_1)) { Shiboken::AutoDecRef data(PyUnicode_AsASCIIString(%PYARG_1)); - QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_GET_SIZE(data.object())); + QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_Size(data.object())); bool cppResult = %CPPSELF >= ba; %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); } @@ -869,7 +893,7 @@ if (PyUnicode_CheckExact(%PYARG_1)) { // @snippet qbytearray-operatorlower if (PyUnicode_CheckExact(%PYARG_1)) { Shiboken::AutoDecRef data(PyUnicode_AsASCIIString(%PYARG_1)); - QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_GET_SIZE(data.object())); + QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_Size(data.object())); bool cppResult = %CPPSELF < ba; %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); } @@ -878,7 +902,7 @@ if (PyUnicode_CheckExact(%PYARG_1)) { // @snippet qbytearray-operatorlowerequal if (PyUnicode_CheckExact(%PYARG_1)) { Shiboken::AutoDecRef data(PyUnicode_AsASCIIString(%PYARG_1)); - QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_GET_SIZE(data.object())); + QByteArray ba = QByteArray(PyBytes_AsString(data.object()), PyBytes_Size(data.object())); bool cppResult = %CPPSELF <= ba; %PYARG_0 = %CONVERTTOPYTHON[bool](cppResult); } @@ -899,7 +923,7 @@ Py_DECREF(aux); // @snippet qbytearray-2 // @snippet qbytearray-3 -%0 = new QByteArray(PyBytes_AS_STRING(%PYARG_1), PyBytes_GET_SIZE(%PYARG_1)); +%0 = new QByteArray(PyBytes_AsString(%PYARG_1), PyBytes_Size(%PYARG_1)); // @snippet qbytearray-3 // @snippet qbytearray-py3 @@ -924,15 +948,16 @@ return %CPPSELF.size(); // @snippet qbytearray-len // @snippet qbytearray-getitem -if (_i < 0 || _i >= %CPPSELF.size()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return 0; -} else { - char res[2]; - res[0] = %CPPSELF.at(_i); - res[1] = 0; - return PyBytes_FromStringAndSize(res, 1); +const Py_ssize_t size = %CPPSELF.size(); +if (_i < 0 || _i >= size) { + Shiboken::Errors::setIndexOutOfBounds(_i, 0, size); + return nullptr; } + +char res[2]; +res[0] = %CPPSELF.at(_i); +res[1] = 0; +return PyBytes_FromStringAndSize(res, 1); // @snippet qbytearray-getitem // @snippet qbytearray-setitem @@ -1061,25 +1086,8 @@ if (msec == 0) { QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); timer->setSingleShot(true); - - if (PyObject_TypeCheck(%2, PySideSignalInstance_TypeF())) { - PySideSignalInstance *signalInstance = reinterpret_cast(%2); - Shiboken::AutoDecRef signalSignature(Shiboken::String::fromFormat("2%s", PySide::Signal::getSignature(signalInstance))); - Shiboken::AutoDecRef result( - PyObject_CallMethod(pyTimer, "connect", "OsOO", - pyTimer, - SIGNAL(timeout()), - PySide::Signal::getObject(signalInstance), - signalSignature.object()) - ); - } else { - Shiboken::AutoDecRef result( - PyObject_CallMethod(pyTimer, "connect", "OsO", - pyTimer, - SIGNAL(timeout()), - %PYARG_2) - ); - } + if (!PySide::callConnect(pyTimer, SIGNAL(timeout()), %PYARG_2)) + return nullptr; timer->connect(timer, &QTimer::timeout, timer, &QObject::deleteLater, Qt::DirectConnection); Shiboken::Object::releaseOwnership(reinterpret_cast(pyTimer)); @@ -1133,8 +1141,8 @@ if (msec == 0) { qint64 pid; %RETURN_TYPE retval = %TYPE::%FUNCTION_NAME(%1, %2, %3, &pid); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[qint64](pid)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[qint64](pid)); // @snippet qprocess-startdetached // @snippet qcoreapplication-init @@ -1142,7 +1150,7 @@ static void QCoreApplicationConstructor(PyObject *self, PyObject *pyargv, QCoreA { static int argc; static char **argv; - PyObject *stringlist = PyTuple_GET_ITEM(pyargv, 0); + PyObject *stringlist = PyTuple_GetItem(pyargv, 0); if (Shiboken::listToArgcArgv(stringlist, &argc, &argv, "PySideApp")) { *cptr = new QCoreApplicationWrapper(argc, argv); Shiboken::Object::releaseOwnership(reinterpret_cast(self)); @@ -1230,7 +1238,7 @@ PySideEasingCurveFunctor::init(); // @snippet qeasingcurve // @snippet qeasingcurve-setcustomtype -QEasingCurve::EasingFunction func = PySideEasingCurveFunctor::createCustomFuntion(%PYSELF, %PYARG_1); +QEasingCurve::EasingFunction func = PySideEasingCurveFunctor::createCustomFunction(%PYSELF, %PYARG_1); if (func) %CPPSELF.%FUNCTION_NAME(func); // @snippet qeasingcurve-setcustomtype @@ -1265,26 +1273,24 @@ QT_END_NAMESPACE // @snippet qt-registerresourcedata // @snippet qt-qregisterresourcedata -%RETURN_TYPE %0 = %FUNCTION_NAME(%1, reinterpret_cast(PyBytes_AS_STRING(%PYARG_2)), - reinterpret_cast(PyBytes_AS_STRING(%PYARG_3)), - reinterpret_cast(PyBytes_AS_STRING(%PYARG_4))); +%RETURN_TYPE %0 = %FUNCTION_NAME(%1, reinterpret_cast(PyBytes_AsString(%PYARG_2)), + reinterpret_cast(PyBytes_AsString(%PYARG_3)), + reinterpret_cast(PyBytes_AsString(%PYARG_4))); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); // @snippet qt-qregisterresourcedata // @snippet qt-qunregisterresourcedata -%RETURN_TYPE %0 = %FUNCTION_NAME(%1, reinterpret_cast(PyBytes_AS_STRING(%PYARG_2)), - reinterpret_cast(PyBytes_AS_STRING(%PYARG_3)), - reinterpret_cast(PyBytes_AS_STRING(%PYARG_4))); +%RETURN_TYPE %0 = %FUNCTION_NAME(%1, reinterpret_cast(PyBytes_AsString(%PYARG_2)), + reinterpret_cast(PyBytes_AsString(%PYARG_3)), + reinterpret_cast(PyBytes_AsString(%PYARG_4))); %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0); // @snippet qt-qunregisterresourcedata -// @snippet use-stream-for-format-security -// Uses the stream version for security reasons -// see gcc man page at -Wformat-security +// @snippet qdebug-format-string Py_BEGIN_ALLOW_THREADS -%FUNCTION_NAME() << %1; +%FUNCTION_NAME("%s", %1); // Uses placeholder for security reasons Py_END_ALLOW_THREADS -// @snippet use-stream-for-format-security +// @snippet qdebug-format-string // @snippet qresource-registerResource auto ptr = reinterpret_cast(Shiboken::Buffer::getPointer(%PYARG_1)); @@ -1318,8 +1324,8 @@ QString &res = *%0; // @snippet return-readData %RETURN_TYPE %0 = 0; if (PyBytes_Check(%PYARG_0)) { - %0 = PyBytes_GET_SIZE(%PYARG_0.object()); - memcpy(%1, PyBytes_AS_STRING(%PYARG_0.object()), %0); + %0 = PyBytes_Size(%PYARG_0.object()); + memcpy(%1, PyBytes_AsString(%PYARG_0.object()), %0); } else if (Shiboken::String::check(%PYARG_0.object())) { %0 = Shiboken::String::len(%PYARG_0.object()); memcpy(%1, Shiboken::String::toCString(%PYARG_0.object()), %0); @@ -1351,8 +1357,8 @@ Py_END_ALLOW_THREADS if (!atexit.isNull() && !regFunc.isNull()){ PyObject *shutDownFunc = PyObject_GetAttrString(module, "__moduleShutdown"); Shiboken::AutoDecRef args(PyTuple_New(1)); - PyTuple_SET_ITEM(args, 0, shutDownFunc); - Shiboken::AutoDecRef retval(PyObject_Call(regFunc, args, 0)); + PyTuple_SetItem(args, 0, shutDownFunc); + Shiboken::AutoDecRef retval(PyObject_Call(regFunc, args, nullptr)); Q_ASSERT(!retval.isNull()); } } @@ -1580,7 +1586,7 @@ int usec = PyDateTime_TIME_GET_MICROSECOND(%in); // @snippet conversion-qtime-pytime // @snippet conversion-qbytearray-pybytes -%out = %OUTTYPE(PyBytes_AS_STRING(%in), PyBytes_GET_SIZE(%in)); +%out = %OUTTYPE(PyBytes_AsString(%in), PyBytes_Size(%in)); // @snippet conversion-qbytearray-pybytes // @snippet conversion-qbytearray-pybytearray @@ -1692,7 +1698,7 @@ if (converterO.has_value()) return converterO.value().toPython(cppInRef.data()); PyErr_Format(PyExc_RuntimeError, "Can't find converter for '%s'.", %in.typeName()); -return 0; +return nullptr; // @snippet return-qvariant // @snippet return-qjsonobject @@ -1803,13 +1809,6 @@ if (dataChar == nullptr) { } // @snippet qdatastream-read-bytes -// @snippet qloggingcategory_to_cpp -// PYSIDE-2404: Usage of the `get()` function not necessary, the type exists. - QLoggingCategory *category{nullptr}; - Shiboken::Conversions::pythonToCppPointer(SbkPySide6_QtCoreTypeStructs[SBK_QLoggingCategory_IDX].type, - pyArgs[0], &(category)); -// @snippet qloggingcategory_to_cpp - // Q_ARG()-equivalent // @snippet q_arg const QArgData qArgData = qArgDataFromPyType(%1); @@ -2071,12 +2070,12 @@ if ((classMethod && (count > 2)) || (!classMethod && (count > 1))) { bool arg_qpermission = (classMethod && (count == 2)) || (!classMethod && (count == 1)); -auto callback = [callable, count, arg_qpermission](const QPermission &permission) -> void +auto callback = [callable, arg_qpermission](const QPermission &permission) -> void { Shiboken::GilState state; if (arg_qpermission) { Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist.object(), 0, %CONVERTTOPYTHON[QPermission](permission)); + PyTuple_SetItem(arglist.object(), 0, %CONVERTTOPYTHON[QPermission](permission)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); } else { Shiboken::AutoDecRef ret(PyObject_CallObject(callable, nullptr)); @@ -2095,9 +2094,9 @@ qint64 pid{}; QString hostname, appname; %CPPSELF.%FUNCTION_NAME(&pid, &hostname, &appname); %PYARG_0 = PyTuple_New(3); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[qint64](pid)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](hostname)); -PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[QString](appname)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[qint64](pid)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](hostname)); +PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[QString](appname)); // @snippet qlockfile-getlockinfo // @snippet darwin_permission_plugin @@ -2148,3 +2147,23 @@ PyRun_String(R"PY(if True: )PY", Py_file_input, _inputDict, _inputDict); // @snippet qt-modifier + +// @snippet qdirlisting-iter +auto result = QtCoreHelper::QDirListingIterator(*%CPPSELF); +%PYARG_0 = %CONVERTTOPYTHON[QtCoreHelper::QDirListingIterator](result); +// @snippet qdirlisting-iter + +// @snippet qdirlistingiterator-next +if (%CPPSELF.next()) { + Py_INCREF(%PYSELF); + %PYARG_0 = %PYSELF; +} +// @snippet qdirlistingiterator-next + +// @snippet qdirlisting-direntry-repr +QByteArray result = '<' + QByteArray(Py_TYPE(%PYSELF)->tp_name) + + " object at 0x" + + QByteArray::number(quintptr(%PYSELF), 16) + " (\"" + + %CPPSELF.absoluteFilePath().toUtf8() + "\")>"; +%PYARG_0 = Shiboken::String::fromCString(result.constData()); +// @snippet qdirlisting-direntry-repr diff --git a/sources/pyside6/PySide6/glue/qtgui.cpp b/sources/pyside6/PySide6/glue/qtgui.cpp index 5c860a2b..a91478c5 100644 --- a/sources/pyside6/PySide6/glue/qtgui.cpp +++ b/sources/pyside6/PySide6/glue/qtgui.cpp @@ -84,8 +84,8 @@ QAccessibleInterface *PySideAccessibleFactory::callFactories(const QString &key, { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(2)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QString](key)); - PyTuple_SET_ITEM(arglist, 1, %CONVERTTOPYTHON[QObject *](o)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QString](key)); + PyTuple_SetItem(arglist, 1, %CONVERTTOPYTHON[QObject *](o)); for (auto *f : m_factoryFunctions) { if (PyObject *pyResult = PyObject_CallObject(f, arglist)) { @@ -305,14 +305,15 @@ QBitmap %0 = QBitmap::fromData(%1, buffer, %3); // @snippet qtextline-cursortox %RETURN_TYPE %0 = %CPPSELF->::%TYPE::%FUNCTION_NAME(&%1, %2); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[%ARG1_TYPE](%1)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[%ARG1_TYPE](%1)); // @snippet qtextline-cursortox // @snippet qkeysequence-getitem -if (_i < 0 || _i >= %CPPSELF.count()) { - PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return 0; +const Py_ssize_t size = %CPPSELF.count(); +if (_i < 0 || _i >= size) { + Shiboken::Errors::setIndexOutOfBounds(_i, 0, size); + return nullptr; } QKeyCombination item = (*%CPPSELF)[_i]; return %CONVERTTOPYTHON[QKeyCombination](item); @@ -411,8 +412,8 @@ static void imageDecrefDataHandler(void *data) // @snippet qimage-scanline // @snippet qcolor-setstate -Shiboken::AutoDecRef func(PyObject_GetAttr(%PYSELF, PyTuple_GET_ITEM(%1, 0))); -PyObject *args = PyTuple_GET_ITEM(%1, 1); +Shiboken::AutoDecRef func(PyObject_GetAttr(%PYSELF, PyTuple_GetItem(%1, 0))); +PyObject *args = PyTuple_GetItem(%1, 1); %PYARG_0 = PyObject_Call(func, args, nullptr); // @snippet qcolor-setstate @@ -687,8 +688,8 @@ for (int r=0, r_max = %CPPSELF.rowCount(); r < r_max; r++) { %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(%1, %2); %END_ALLOW_THREADS %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[%ARG1_TYPE](%1)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[%ARG1_TYPE](%1)); // @snippet qclipboard-text // @snippet qpainter-drawpointsnp-numpy-x-y @@ -715,7 +716,7 @@ Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_1, "Can't turn into sequence")); if (PySequence_Size(seq) == 16) { float values[16]; for (Py_ssize_t i = 0; i < 16; ++i) { - PyObject *pv = PySequence_Fast_GET_ITEM(seq.object(), i); + Shiboken::AutoDecRef pv(PySequence_GetItem(seq.object(), i)); values[i] = PyFloat_AsDouble(pv); } @@ -732,16 +733,16 @@ float values[16]; %PYARG_0 = PyTuple_New(16); for (Py_ssize_t i = 0; i < 16; ++i) { PyObject *v = PyFloat_FromDouble(values[i]); - PyTuple_SET_ITEM(%PYARG_0, i, v); + PyTuple_SetItem(%PYARG_0, i, v); } // @snippet qmatrix4x4-copydatato // @snippet qmatrix4x4-mgetitem if (PySequence_Check(_key)) { Shiboken::AutoDecRef key(PySequence_Fast(_key, "Invalid matrix index.")); - if (PySequence_Fast_GET_SIZE(key.object()) == 2) { - PyObject *posx = PySequence_Fast_GET_ITEM(key.object(), 0); - PyObject *posy = PySequence_Fast_GET_ITEM(key.object(), 1); + if (PySequence_Size(key.object()) == 2) { + Shiboken::AutoDecRef posx(PySequence_GetItem(key.object(), 0)); + Shiboken::AutoDecRef posy(PySequence_GetItem(key.object(), 1)); Py_ssize_t x = PyLong_AsSsize_t(posx); Py_ssize_t y = PyLong_AsSsize_t(posy); float ret = (*%CPPSELF)(x,y); @@ -757,7 +758,7 @@ static void QGuiApplicationConstructor(PyObject *self, PyObject *pyargv, QGuiApp { static int argc; static char **argv; - PyObject *stringlist = PyTuple_GET_ITEM(pyargv, 0); + PyObject *stringlist = PyTuple_GetItem(pyargv, 0); if (Shiboken::listToArgcArgv(stringlist, &argc, &argv, "PySideApp")) { *cptr = new QGuiApplicationWrapper(argc, argv, 0); Shiboken::Object::releaseOwnership(reinterpret_cast(self)); @@ -894,17 +895,17 @@ QVector3D outVec{}; float angle{}; %CPPSELF.%FUNCTION_NAME(&outVec, &angle); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[QVector3D](outVec)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[float](angle)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[QVector3D](outVec)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[float](angle)); // @snippet qquaternion-getaxisandangle-vector3d-float // @snippet qquaternion-geteulerangles float pitch{}, yaw{}, roll{}; %CPPSELF.%FUNCTION_NAME(&pitch, &yaw, &roll); %PYARG_0 = PyTuple_New(3); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[float](pitch)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[float](yaw)); -PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[float](roll)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[float](pitch)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[float](yaw)); +PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[float](roll)); // @snippet qquaternion-geteulerangles // @snippet qregion-len @@ -912,8 +913,11 @@ return %CPPSELF.rectCount(); // @snippet qregion-len // @snippet qregion-getitem -if (_i < 0 || _i >= %CPPSELF.rectCount()) - return PyErr_Format(PyExc_IndexError, "index out of bounds"); +const Py_ssize_t size = %CPPSELF.rectCount(); +if (_i < 0 || _i >= size) { + Shiboken::Errors::setIndexOutOfBounds(_i, 0, size); + return nullptr; +} const QRect cppResult = *(%CPPSELF.cbegin() + _i); return %CONVERTTOPYTHON[QRect](cppResult); diff --git a/sources/pyside6/PySide6/glue/qtnetwork.cpp b/sources/pyside6/PySide6/glue/qtnetwork.cpp index f635f467..7b1c2d56 100644 --- a/sources/pyside6/PySide6/glue/qtnetwork.cpp +++ b/sources/pyside6/PySide6/glue/qtnetwork.cpp @@ -10,9 +10,9 @@ quint16 port; %END_ALLOW_THREADS QByteArray ba(data, retval); %PYARG_0 = PyTuple_New(3); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[QByteArray](ba)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QHostAddress](ha)); -PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[quint16](port)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[QByteArray](ba)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QHostAddress](ha)); +PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[quint16](port)); // @snippet qudpsocket-readdatagram // @snippet qhostinfo-lookuphost-functor @@ -29,7 +29,7 @@ void QHostInfoFunctor::operator()(const QHostInfo &hostInfo) Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); auto *pyHostInfo = %CONVERTTOPYTHON[QHostInfo](hostInfo); - PyTuple_SET_ITEM(arglist.object(), 0, pyHostInfo); + PyTuple_SetItem(arglist.object(), 0, pyHostInfo); Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); release(); // single shot } @@ -46,7 +46,7 @@ return 16; // @snippet qipv6address-getitem if (_i >= 16) { PyErr_SetString(PyExc_IndexError, "index out of bounds"); - return 0; + return nullptr; } if (_i < 0) _i = 16 - qAbs(_i); @@ -89,7 +89,7 @@ void QRestFunctor::operator()(QRestReply &restReply) Shiboken::AutoDecRef arglist(PyTuple_New(1)); auto *restReplyPtr = &restReply; auto *pyRestReply = %CONVERTTOPYTHON[QRestReply*](restReplyPtr); - PyTuple_SET_ITEM(arglist.object(), 0, pyRestReply); + PyTuple_SetItem(arglist.object(), 0, pyRestReply); Shiboken::AutoDecRef ret(PyObject_CallObject(m_callable, arglist)); Py_DECREF(m_callable); m_callable = nullptr; diff --git a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp index 7877a8dd..aa28f500 100644 --- a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp +++ b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp @@ -21,8 +21,8 @@ void QAbstractOAuthModifyFunctor::operator()(QAbstractOAuth::Stage stage, Shiboken::GilState state; QMultiMap dict = *dictPointer; Shiboken::AutoDecRef arglist(PyTuple_New(2)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QAbstractOAuth::Stage](stage)); - PyTuple_SET_ITEM(arglist, 1, %CONVERTTOPYTHON[QMultiMap](dict)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QAbstractOAuth::Stage](stage)); + PyTuple_SetItem(arglist, 1, %CONVERTTOPYTHON[QMultiMap](dict)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); if (!ret.isNull() && PyDict_Check(ret.object()) != 0) { @@ -41,4 +41,3 @@ void QAbstractOAuthModifyFunctor::operator()(QAbstractOAuth::Stage stage, // @snippet qabstractoauth-setmodifyparametersfunction %CPPSELF.%FUNCTION_NAME(QAbstractOAuthModifyFunctor(%PYARG_1)); // @snippet qabstractoauth-setmodifyparametersfunction - diff --git a/sources/pyside6/PySide6/glue/qtquick3d.cpp b/sources/pyside6/PySide6/glue/qtquick3d.cpp new file mode 100644 index 00000000..eee28645 --- /dev/null +++ b/sources/pyside6/PySide6/glue/qtquick3d.cpp @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/********************************************************************* + * INJECT CODE + ********************************************************************/ + +// @snippet qquick3dinstancing-getinstancebuffer-virtual-redirect +std::pair resultPair = getInstanceBufferOverride(gil, pyOverride.object()); +if (instanceCount != nullptr) + *instanceCount = resultPair.second; +return resultPair.first; +// @snippet qquick3dinstancing-getinstancebuffer-virtual-redirect + +// @snippet qquick3dinstancing-getinstancebuffer-return +int count{}; +%RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&count); +%PYARG_0 = PyTuple_New(2); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](count)); +// @snippet qquick3dinstancing-getinstancebuffer-return diff --git a/sources/pyside6/PySide6/glue/qtserialbus.cpp b/sources/pyside6/PySide6/glue/qtserialbus.cpp index b5b491f4..0bf918fd 100644 --- a/sources/pyside6/PySide6/glue/qtserialbus.cpp +++ b/sources/pyside6/PySide6/glue/qtserialbus.cpp @@ -7,12 +7,20 @@ // @snippet qcanbus-available-devices QString errorMessage; -const QList result = %CPPSELF.%FUNCTION_NAME(%1, &errorMessage); +const QList result = %CPPSELF.%FUNCTION_NAME(&errorMessage); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[QList](result)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](errorMessage)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[QList](result)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](errorMessage)); // @snippet qcanbus-available-devices +// @snippet qcanbus-available-devices-plugin +QString errorMessage; +const QList result = %CPPSELF.%FUNCTION_NAME(%1, &errorMessage); +%PYARG_0 = PyTuple_New(2); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[QList](result)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](errorMessage)); +// @snippet qcanbus-available-devices-plugin + // @snippet qcanbus-createdevice PyObject *pyDevice{}; QString errorMessage; @@ -25,6 +33,6 @@ if (auto *device = %CPPSELF.%FUNCTION_NAME(%1, %2, &errorMessage)) { Py_INCREF(pyDevice); } %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, pyDevice); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](errorMessage)); +PyTuple_SetItem(%PYARG_0, 0, pyDevice); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](errorMessage)); // @snippet qcanbus-createdevice diff --git a/sources/pyside6/PySide6/glue/qtstatemachine.cpp b/sources/pyside6/PySide6/glue/qtstatemachine.cpp index 098200b1..66c4acf1 100644 --- a/sources/pyside6/PySide6/glue/qtstatemachine.cpp +++ b/sources/pyside6/PySide6/glue/qtstatemachine.cpp @@ -41,7 +41,8 @@ if (PySide::SignalManager::registerMetaMethod(%1, signalName.constData(), // http://bugs.openbossa.org/show_bug.cgi?id=362 // PYSIDE-2256: The label was removed if (!PyObject_TypeCheck(%1, PySideSignalInstance_TypeF())) - return Shiboken::returnWrongArguments(args, fullName, errInfo); + return Shiboken::returnWrongArguments(args, "addTransition", errInfo, + SbkPySide6_QtStateMachineTypeStructs[SBK_QAbstractTransition_IDX]); PySideSignalInstance *signalInstance = reinterpret_cast(%1); auto sender = %CONVERTTOCPP[QObject *](PySide::Signal::getObject(signalInstance)); QSignalTransition *%0 = %CPPSELF->%FUNCTION_NAME(sender, PySide::Signal::getSignature(signalInstance),%2); diff --git a/sources/pyside6/PySide6/glue/qttest.cpp b/sources/pyside6/PySide6/glue/qttest.cpp index 626cc160..36d3bfa3 100644 --- a/sources/pyside6/PySide6/glue/qttest.cpp +++ b/sources/pyside6/PySide6/glue/qttest.cpp @@ -20,5 +20,5 @@ if (emitter == nullptr || signature.isEmpty()) { PyErr_SetString(PyExc_ValueError, error.constData()); return -1; } -%0 = new QSignalSpyWrapper(emitter, signature.constData()); +%0 = new QSignalSpy(emitter, signature.constData()); // @snippet qsignalspy-signal diff --git a/sources/pyside6/PySide6/glue/qtuitools.cpp b/sources/pyside6/PySide6/glue/qtuitools.cpp index 1835ed09..3fe3554c 100644 --- a/sources/pyside6/PySide6/glue/qtuitools.cpp +++ b/sources/pyside6/PySide6/glue/qtuitools.cpp @@ -207,6 +207,6 @@ if (%PYARG_0 == nullptr) { qCritical() << "Error while creating the return Tuple"; Py_RETURN_NONE; } -PyTuple_SET_ITEM(%PYARG_0, 0, classObj); -PyTuple_SET_ITEM(%PYARG_0, 1, baseClassObj); +PyTuple_SetItem(%PYARG_0, 0, classObj); +PyTuple_SetItem(%PYARG_0, 1, baseClassObj); // @snippet loaduitype diff --git a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp index 76a7c6d7..42ac6e76 100644 --- a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp +++ b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp @@ -14,7 +14,7 @@ bool QWebEngineCookieFilterFunctor::operator()(const QWebEngineCookieStore::Filt { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist, 0, + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QWebEngineCookieStore::FilterRequest](filterRequest)); Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); return ret.object() == Py_True; @@ -39,7 +39,7 @@ void QWebEngineNotificationFunctor::operator() Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(1)); auto *notification = webEngineNotification.release(); - PyTuple_SET_ITEM(arglist.object(), 0, + PyTuple_SetItem(arglist.object(), 0, %CONVERTTOPYTHON[QWebEngineNotification*](notification)); Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); }; @@ -59,6 +59,101 @@ return resultPair.first; QString str; %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(%1, %2, %3, &str); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](str)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](str)); // @snippet qwebenginepage-javascriptprompt-return + +// @snippet qwebenginepage-findtext +auto callable = %PYARG_3; +auto callback = [callable](const QWebEngineFindTextResult &result) +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QWebEngineFindTextResult](result)); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); + Py_DECREF(callable); + +}; +Py_INCREF(callable); +%CPPSELF.%FUNCTION_NAME(%1, %2, callback); +// @snippet qwebenginepage-findtext + +// @snippet qwebenginepage-print +auto printer = %PYARG_1; +auto callable = %PYARG_2; +auto callback = [printer, callable](bool succeeded) +{ + if (!PyCallable_Check(callable)) { + qWarning("Argument 2 of %FUNCTION_NAME must be a callable."); + return; + } + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[bool](succeeded)); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); + Py_DECREF(callable); + Py_DECREF(printer); + +}; +Py_INCREF(printer); // Add a reference to the printer until asynchronous printing has finished +Py_INCREF(callable); +%CPPSELF.%FUNCTION_NAME(%1, callback); +// @snippet qwebenginepage-print + +// @snippet qwebenginepage-convertto +auto callable = %PYARG_1; +auto callback = [callable](const QString &text) +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QString](text)); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); + Py_DECREF(callable); +}; + +Py_INCREF(callable); +%CPPSELF.%FUNCTION_NAME(callback); +// @snippet qwebenginepage-convertto + +// @snippet qwebenginepage-runjavascript-2 +using RunJavascriptCallback = std::function; + +if (%PYARG_2 != nullptr && %PYARG_2 != Py_None) { + %CPPSELF.%FUNCTION_NAME(%1, RunJavascriptCallback(RunJavascriptFunctor(%PYARG_2))); +} else { + %CPPSELF.%FUNCTION_NAME(%1, 0, RunJavascriptCallback{}); +} +// @snippet qwebenginepage-runjavascript-2 + +// @snippet qwebenginepage-runjavascript-3 +using RunJavascriptCallback = std::function; + +if (%PYARG_3 != nullptr && %PYARG_3 != Py_None) { + %CPPSELF.%FUNCTION_NAME(%1, %2, RunJavascriptCallback(RunJavascriptFunctor(%PYARG_3))); +} else { + %CPPSELF.%FUNCTION_NAME(%1, %2, RunJavascriptCallback{}); +} +// @snippet qwebenginepage-runjavascript-3 + +// @snippet qwebenginepage-printtopdf +using PrintToPdfCallback = std::function; + +%CPPSELF.%FUNCTION_NAME(PrintToPdfCallback(PrintToPdfFunctor(%PYARG_1)), %2, %3); +// @snippet qwebenginepage-printtopdf + +// @snippet qwebenginepage-findframebyname +auto frameOptional = %CPPSELF.%FUNCTION_NAME(%1); +if (frameOptional.has_value()) { + const %RETURN_TYPE &frame = frameOptional.value(); + %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](frame); +} else { + Py_INCREF(Py_None); + %PYARG_0 = Py_None; +} +// @snippet qwebenginepage-findframebyname + +// @snippet qwebengineframe-printtopdf +using PrintToPdfCallback = std::function; + +%CPPSELF.%FUNCTION_NAME(PrintToPdfCallback(PrintToPdfFunctor(%PYARG_1))); +// @snippet qwebengineframe-printtopdf diff --git a/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp b/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp deleted file mode 100644 index af15130a..00000000 --- a/sources/pyside6/PySide6/glue/qtwebenginewidgets.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -// @snippet qwebenginepage-findtext -auto callable = %PYARG_3; -auto callback = [callable](const QWebEngineFindTextResult &result) -{ - if (!PyCallable_Check(callable)) { - qWarning("Argument 3 of %FUNCTION_NAME must be a callable."); - return; - } - Shiboken::GilState state; - Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QWebEngineFindTextResult](result)); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); - -}; -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(%1, %2, callback); -// @snippet qwebenginepage-findtext - -// @snippet qwebenginepage-print -auto printer = %PYARG_1; -auto callable = %PYARG_2; -auto callback = [printer, callable](bool succeeded) -{ - if (!PyCallable_Check(callable)) { - qWarning("Argument 2 of %FUNCTION_NAME must be a callable."); - return; - } - Shiboken::GilState state; - Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[bool](succeeded)); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); - Py_DECREF(printer); - -}; -Py_INCREF(printer); // Add a reference to the printer until asynchronous printing has finished -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(%1, callback); -// @snippet qwebenginepage-print - -// @snippet qwebenginepage-convertto -auto callable = %PYARG_1; -auto callback = [callable](const QString &text) -{ - if (!PyCallable_Check(callable)) { - qWarning("Argument 1 of %FUNCTION_NAME must be a callable."); - return; - } - Shiboken::GilState state; - Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QString](text)); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); -}; - -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(callback); -// @snippet qwebenginepage-convertto - -// @snippet qwebenginepage-runjavascript -auto callable = %PYARG_3; -auto callback = [callable](const QVariant &result) -{ - if (!PyCallable_Check(callable)) { - qWarning("Argument 3 of %FUNCTION_NAME must be a callable."); - return; - } - Shiboken::GilState state; - Shiboken::AutoDecRef arglist(PyTuple_New(1)); - switch (result.type()) { - case QVariant::Bool: { - const bool value = result.toBool(); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QString](value)); - } - break; - case QVariant::Int: - case QVariant::UInt: - case QVariant::LongLong: - case QVariant::ULongLong: - case QVariant::Double: { - const double number = result.toDouble(); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[double](number)); - } - break; - default: { - const QString value = result.toString(); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QString](value)); - } - break; - } - // PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[bool](found)); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); -}; - -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(%1, %2, callback); -// @snippet qwebenginepage-runjavascript - -// @snippet qwebenginepage-printtopdf -auto callable = %PYARG_1; -auto callback = [callable](const QByteArray &pdf) -{ - if (!PyCallable_Check(callable)) { - qWarning("Argument 1 of %FUNCTION_NAME must be a callable."); - return; - } - Shiboken::GilState state; - Shiboken::AutoDecRef arglist(PyTuple_New(1)); - PyTuple_SET_ITEM(arglist, 0, %CONVERTTOPYTHON[QByteArray](pdf)); - Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); - Py_DECREF(callable); -}; - -Py_INCREF(callable); -%CPPSELF.%FUNCTION_NAME(callback, %2); -// @snippet qwebenginepage-printtopdf diff --git a/sources/pyside6/PySide6/glue/qtwidgets.cpp b/sources/pyside6/PySide6/glue/qtwidgets.cpp index f886106c..bc9dc99d 100644 --- a/sources/pyside6/PySide6/glue/qtwidgets.cpp +++ b/sources/pyside6/PySide6/glue/qtwidgets.cpp @@ -36,8 +36,8 @@ if (%0) { QGraphicsItem *item_ = nullptr; %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&item_); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QGraphicsItem *](item_)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QGraphicsItem *](item_)); // @snippet qgraphicsitem-isblockedbymodalpanel // @snippet qitemeditorfactory-registereditor @@ -60,13 +60,13 @@ int _row; QFormLayout::ItemRole _role; %CPPSELF->%FUNCTION_NAME(%ARGUMENT_NAMES, &_row, &_role); %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[int](_row)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](_row)); // On the C++ side, *rolePtr is not set if row == -1, in which case on // the Python side this gets converted to a random value outside the // enum range. Fix this by setting _role to a default value here. if (_row == -1) _role = QFormLayout::LabelRole; -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QFormLayout::ItemRole](_role)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QFormLayout::ItemRole](_role)); // @snippet qformlayout-fix-args // @snippet qfiledialog-return @@ -74,8 +74,8 @@ PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QFormLayout::ItemRole](_role)); %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(%1, %2, %3, %4, &%5, %6); %END_ALLOW_THREADS %PYARG_0 = PyTuple_New(2); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](%5)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](retval_)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](%5)); // @snippet qfiledialog-return // @snippet qwidget-addaction-glue @@ -392,10 +392,10 @@ removeLayoutOwnership(%CPPSELF, %1); int a, b, c, d; %CPPSELF.%FUNCTION_NAME(%1, &a, &b, &c, &d); %PYARG_0 = PyTuple_New(4); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[int](a)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[int](b)); -PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[int](c)); -PyTuple_SET_ITEM(%PYARG_0, 3, %CONVERTTOPYTHON[int](d)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](a)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](b)); +PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](c)); +PyTuple_SetItem(%PYARG_0, 3, %CONVERTTOPYTHON[int](d)); // @snippet qgridlayout-getitemposition // @snippet qgraphicsscene-destroyitemgroup @@ -569,7 +569,7 @@ static void QApplicationConstructor(PyObject *self, PyObject *pyargv, QApplicati { static int argc; static char **argv; - PyObject *stringlist = PyTuple_GET_ITEM(pyargv, 0); + PyObject *stringlist = PyTuple_GetItem(pyargv, 0); if (Shiboken::listToArgcArgv(stringlist, &argc, &argv, "PySideApp")) { *cptr = new QApplicationWrapper(argc, argv, 0); Shiboken::Object::releaseOwnership(reinterpret_cast(self)); @@ -809,6 +809,12 @@ const QByteArray signature = QByteArrayLiteral("2") + method.methodSignature(); Shiboken::Object::removeParent(reinterpret_cast(%PYSELF)); // @snippet qdialog-exec-remove-parent-relation +// @snippet qmessagebox-open-connect-accept +if (!PySide::callConnect(%PYSELF, SIGNAL(accepted()), %PYARG_1)) + return nullptr; +%CPPSELF.%FUNCTION_NAME(); +// @snippet qmessagebox-open-connect-accept + /********************************************************************* * CONVERSIONS ********************************************************************/ diff --git a/sources/pyside6/PySide6/glue/qtxml.cpp b/sources/pyside6/PySide6/glue/qtxml.cpp index 12057992..b97d774e 100644 --- a/sources/pyside6/PySide6/glue/qtxml.cpp +++ b/sources/pyside6/PySide6/glue/qtxml.cpp @@ -10,8 +10,8 @@ bool _ret_ = %CPPSELF.%FUNCTION_NAME(%ARGUMENT_NAMES, &_errorMsg_, &_errorLine_, &_errorColumn_); %END_ALLOW_THREADS %PYARG_0 = PyTuple_New(4); -PyTuple_SET_ITEM(%PYARG_0, 0, %CONVERTTOPYTHON[bool](_ret_)); -PyTuple_SET_ITEM(%PYARG_0, 1, %CONVERTTOPYTHON[QString](_errorMsg_)); -PyTuple_SET_ITEM(%PYARG_0, 2, %CONVERTTOPYTHON[int](_errorLine_)); -PyTuple_SET_ITEM(%PYARG_0, 3, %CONVERTTOPYTHON[int](_errorColumn_)); +PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[bool](_ret_)); +PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[QString](_errorMsg_)); +PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](_errorLine_)); +PyTuple_SetItem(%PYARG_0, 3, %CONVERTTOPYTHON[int](_errorColumn_)); // @snippet qdomdocument-setcontent diff --git a/sources/pyside6/PySide6/qpytextobject.h b/sources/pyside6/PySide6/qpytextobject.h index 45bb0bd5..730cc441 100644 --- a/sources/pyside6/PySide6/qpytextobject.h +++ b/sources/pyside6/PySide6/qpytextobject.h @@ -24,5 +24,3 @@ public: QT_END_NAMESPACE #endif - - diff --git a/sources/pyside6/PySide6/qtcorehelper.h b/sources/pyside6/PySide6/qtcorehelper.h index 589d0b29..6bce2f5f 100644 --- a/sources/pyside6/PySide6/qtcorehelper.h +++ b/sources/pyside6/PySide6/qtcorehelper.h @@ -4,6 +4,7 @@ #ifndef QTCOREHELPER_H #define QTCOREHELPER_H +#include #include #include @@ -115,6 +116,28 @@ namespace QtCoreHelper { std::shared_ptr d; }; + struct QDirListingIteratorPrivate; + + class QDirListingIterator + { + public: + explicit QDirListingIterator(const QDirListing &dl); + QDirListingIterator(); + + QDirListingIterator(const QDirListingIterator &); + QDirListingIterator &operator=(const QDirListingIterator &); + QDirListingIterator(QDirListingIterator &&) noexcept; + QDirListingIterator &operator=(QDirListingIterator &&) noexcept; + ~QDirListingIterator(); + + bool next(); + const QDirListing::DirEntry &value() const; + bool atEnd() const; + + private: + std::shared_ptr d; + }; + } // namespace QtCoreHelper QT_END_NAMESPACE diff --git a/sources/pyside6/PySide6/support/__init__.py b/sources/pyside6/PySide6/support/__init__.py index 5127df0f..e64ee57c 100644 --- a/sources/pyside6/PySide6/support/__init__.py +++ b/sources/pyside6/PySide6/support/__init__.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations from shiboken6.Shiboken import VoidPtr diff --git a/sources/pyside6/PySide6/support/deprecated.py b/sources/pyside6/PySide6/support/deprecated.py index 263dd3ed..bacafce7 100644 --- a/sources/pyside6/PySide6/support/deprecated.py +++ b/sources/pyside6/PySide6/support/deprecated.py @@ -1,5 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """ deprecated.py diff --git a/sources/pyside6/PySide6/support/generate_pyi.py b/sources/pyside6/PySide6/support/generate_pyi.py index 92808e2a..7eee7705 100644 --- a/sources/pyside6/PySide6/support/generate_pyi.py +++ b/sources/pyside6/PySide6/support/generate_pyi.py @@ -1,11 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations """ generate_pyi.py This script generates the .pyi files for all PySide modules. """ +# mypy: disable-error-code="import-not-found" import argparse import inspect # noqa: F401 diff --git a/sources/pyside6/PySide6/templates/common.xml b/sources/pyside6/PySide6/templates/common.xml index 44ed931e..aece7eaa 100644 --- a/sources/pyside6/PySide6/templates/common.xml +++ b/sources/pyside6/PySide6/templates/common.xml @@ -18,7 +18,7 @@