aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/async/eratosthenes/doc/eratosthenes.rst34
-rw-r--r--examples/async/eratosthenes/eratosthenes_asyncio.py94
-rw-r--r--examples/async/minimal/doc/minimal.rst34
-rw-r--r--examples/async/minimal/minimal_asyncio.py93
-rw-r--r--examples/axcontainer/axviewer/axviewer.py4
-rw-r--r--examples/bluetooth/btscanner/device.py5
-rw-r--r--examples/bluetooth/btscanner/ui_device.py2
-rw-r--r--examples/bluetooth/btscanner/ui_service.py2
-rw-r--r--examples/bluetooth/heartrate_game/connectionhandler.py32
-rw-r--r--examples/bluetooth/heartrate_game/devicefinder.py21
-rw-r--r--examples/bluetooth/heartrate_game/devicehandler.py17
-rw-r--r--examples/bluetooth/heartrate_game/doc/heartrate_game.rst2
-rw-r--r--examples/bluetooth/heartrate_game/heartrate_global.py17
-rw-r--r--examples/bluetooth/heartrate_server/heartrate_server.py9
-rw-r--r--examples/bluetooth/lowenergyscanner/characteristicinfo.py1
-rw-r--r--examples/bluetooth/lowenergyscanner/device.py3
-rw-r--r--examples/bluetooth/lowenergyscanner/deviceinfo.py1
-rw-r--r--examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst2
-rw-r--r--examples/bluetooth/lowenergyscanner/main.py2
-rw-r--r--examples/bluetooth/lowenergyscanner/serviceinfo.py2
-rw-r--r--examples/charts/callout/callout.py44
-rw-r--r--examples/charts/chartthemes/main.py46
-rw-r--r--examples/charts/chartthemes/ui_themewidget.py4
-rw-r--r--examples/charts/donutbreakdown/donutbreakdown.py2
-rw-r--r--examples/charts/legend/legend.py9
-rw-r--r--examples/charts/memoryusage/memoryusage.py2
-rw-r--r--examples/charts/modeldata/modeldata.py5
-rw-r--r--examples/charts/percentbarchart/percentbarchart.py8
-rw-r--r--examples/charts/pointconfiguration/chartwindow.py8
-rw-r--r--examples/charts/pointselectionandmarkers/doc/pointselectionandmarkers.rst12
-rw-r--r--examples/charts/pointselectionandmarkers/pointselectionandmarkers.py27
-rw-r--r--examples/charts/pointselectionandmarkers/utilities.py12
-rw-r--r--examples/corelib/ipc/sharedmemory/dialog.py2
-rw-r--r--examples/corelib/ipc/sharedmemory/ui_dialog.py2
-rw-r--r--examples/corelib/settingseditor/settingseditor.py70
-rw-r--r--examples/corelib/threads/mandelbrot.py22
-rw-r--r--examples/datavisualization/graphgallery/bargraph.py15
-rw-r--r--examples/datavisualization/graphgallery/main.py4
-rw-r--r--examples/datavisualization/graphgallery/variantbardataproxy.py4
-rw-r--r--examples/datavisualization/qmlsurfacegallery/main.py4
-rw-r--r--examples/dbus/pingpong/ping.py3
-rw-r--r--examples/dbus/pingpong/pong.py2
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml71
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml139
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ColorView.qml381
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/Main.qml62
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml241
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml139
-rw-r--r--examples/demos/colorpaletteclient/ColorPalette/qmldir7
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/Button.qml48
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt54
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml27
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml22
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml29
-rw-r--r--examples/demos/colorpaletteclient/QtExampleStyle/qmldir5
-rw-r--r--examples/demos/colorpaletteclient/abstractresource.py24
-rw-r--r--examples/demos/colorpaletteclient/basiclogin.py100
-rw-r--r--examples/demos/colorpaletteclient/colorpaletteclient.pyproject21
-rw-r--r--examples/demos/colorpaletteclient/colorpaletteclient.qrc17
-rw-r--r--examples/demos/colorpaletteclient/doc/colorpaletteclient.rst79
-rw-r--r--examples/demos/colorpaletteclient/doc/colorpaletteclient.webpbin0 -> 28034 bytes
-rw-r--r--examples/demos/colorpaletteclient/icons/close.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/delete.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/dots.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/edit.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/login.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/logout.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/ok.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/plus.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/qt.pngbin0 -> 2963 bytes
-rw-r--r--examples/demos/colorpaletteclient/icons/qt_attribution.json14
-rw-r--r--examples/demos/colorpaletteclient/icons/testserver.pngbin0 -> 6803 bytes
-rw-r--r--examples/demos/colorpaletteclient/icons/update.svg3
-rw-r--r--examples/demos/colorpaletteclient/icons/user.svg4
-rw-r--r--examples/demos/colorpaletteclient/icons/userMask.svg3
-rw-r--r--examples/demos/colorpaletteclient/main.py33
-rw-r--r--examples/demos/colorpaletteclient/paginatedresource.py278
-rw-r--r--examples/demos/colorpaletteclient/rc_colorpaletteclient.py1098
-rw-r--r--examples/demos/colorpaletteclient/restservice.py53
-rw-r--r--examples/demos/documentviewer/abstractviewer.py185
-rw-r--r--examples/demos/documentviewer/doc/abstractviewer.py.rstinc25
-rw-r--r--examples/demos/documentviewer/doc/documentviewer.pngbin0 -> 22897 bytes
-rw-r--r--examples/demos/documentviewer/doc/documentviewer.rst13
-rw-r--r--examples/demos/documentviewer/doc/jsonviewer.py.rstinc11
-rw-r--r--examples/demos/documentviewer/doc/mainwindow.py.rstinc11
-rw-r--r--examples/demos/documentviewer/doc/viewerfactory.py.rstinc3
-rw-r--r--examples/demos/documentviewer/documentviewer.pyproject14
-rw-r--r--examples/demos/documentviewer/documentviewer.qrc42
-rw-r--r--examples/demos/documentviewer/images/copy.png (renamed from examples/widgets/mainwindows/mdi/images/copy.png)bin1338 -> 1338 bytes
-rw-r--r--examples/demos/documentviewer/images/copy@2x.pngbin0 -> 2219 bytes
-rw-r--r--examples/demos/documentviewer/images/cut.png (renamed from examples/widgets/mainwindows/mdi/images/cut.png)bin1323 -> 1323 bytes
-rw-r--r--examples/demos/documentviewer/images/cut@2x.pngbin0 -> 2888 bytes
-rw-r--r--examples/demos/documentviewer/images/document-open-recent.svgzbin0 -> 4656 bytes
-rw-r--r--examples/demos/documentviewer/images/go-next-view-page.pngbin0 -> 1125 bytes
-rw-r--r--examples/demos/documentviewer/images/go-next-view-page@2x.pngbin0 -> 2312 bytes
-rw-r--r--examples/demos/documentviewer/images/go-next-view.pngbin0 -> 990 bytes
-rw-r--r--examples/demos/documentviewer/images/go-next-view@2x.pngbin0 -> 1830 bytes
-rw-r--r--examples/demos/documentviewer/images/go-next.svgzbin0 -> 5473 bytes
-rw-r--r--examples/demos/documentviewer/images/go-previous-view-page.pngbin0 -> 1119 bytes
-rw-r--r--examples/demos/documentviewer/images/go-previous-view-page@2x.pngbin0 -> 2067 bytes
-rw-r--r--examples/demos/documentviewer/images/go-previous-view.pngbin0 -> 1010 bytes
-rw-r--r--examples/demos/documentviewer/images/go-previous-view@2x.pngbin0 -> 1875 bytes
-rw-r--r--examples/demos/documentviewer/images/go-previous.svgzbin0 -> 5690 bytes
-rw-r--r--examples/demos/documentviewer/images/help-about.svgzbin0 -> 7056 bytes
-rw-r--r--examples/demos/documentviewer/images/magnifier.pngbin0 -> 1006 bytes
-rw-r--r--examples/demos/documentviewer/images/magnifier@2x.pngbin0 -> 2249 bytes
-rw-r--r--examples/demos/documentviewer/images/open.png (renamed from examples/widgets/mainwindows/mdi/images/open.png)bin2073 -> 2073 bytes
-rw-r--r--examples/demos/documentviewer/images/open@2x.pngbin0 -> 1017 bytes
-rw-r--r--examples/demos/documentviewer/images/paste.png (renamed from examples/widgets/mainwindows/mdi/images/paste.png)bin1645 -> 1645 bytes
-rw-r--r--examples/demos/documentviewer/images/paste@2x.pngbin0 -> 2782 bytes
-rw-r--r--examples/demos/documentviewer/images/print.pngbin0 -> 678 bytes
-rw-r--r--examples/demos/documentviewer/images/print2x.pngbin0 -> 931 bytes
-rw-r--r--examples/demos/documentviewer/images/qt-logo.pngbin0 -> 1483 bytes
-rw-r--r--examples/demos/documentviewer/images/qt-logo@2x.pngbin0 -> 1933 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-fit-best.pngbin0 -> 1067 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-fit-best@2x.pngbin0 -> 1692 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-fit-width.pngbin0 -> 905 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-fit-width@2x.pngbin0 -> 1724 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-in.pngbin0 -> 952 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-in@2x.pngbin0 -> 2100 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-original.pngbin0 -> 946 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-original@2x.pngbin0 -> 2005 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-out.pngbin0 -> 940 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-out@2x.pngbin0 -> 1971 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-previous.pngbin0 -> 946 bytes
-rw-r--r--examples/demos/documentviewer/images/zoom-previous@2x.pngbin0 -> 2049 bytes
-rw-r--r--examples/demos/documentviewer/jsonviewer/jsonviewer.py396
-rw-r--r--examples/demos/documentviewer/main.py37
-rw-r--r--examples/demos/documentviewer/mainwindow.py178
-rw-r--r--examples/demos/documentviewer/mainwindow.ui272
-rw-r--r--examples/demos/documentviewer/pdfviewer/pdfviewer.py204
-rw-r--r--examples/demos/documentviewer/pdfviewer/zoomselector.py54
-rw-r--r--examples/demos/documentviewer/rc_documentviewer.py5135
-rw-r--r--examples/demos/documentviewer/recentfilemenu.py34
-rw-r--r--examples/demos/documentviewer/recentfiles.py159
-rw-r--r--examples/demos/documentviewer/txtviewer/txtviewer.py144
-rw-r--r--examples/demos/documentviewer/ui_mainwindow.py230
-rw-r--r--examples/demos/documentviewer/viewerfactory.py114
-rw-r--r--examples/designer/taskmenuextension/doc/taskmenuextension.rst6
-rw-r--r--examples/designer/taskmenuextension/registertictactoe.py2
-rw-r--r--examples/designer/taskmenuextension/tictactoe.py12
-rw-r--r--examples/designer/taskmenuextension/tictactoeplugin.py2
-rw-r--r--examples/external/matplotlib/widget3d/widget3d.py4
-rw-r--r--examples/external/opencv/webcam_pattern_detection.py4
-rw-r--r--examples/external/scikit/staining_colors_separation.py4
-rw-r--r--examples/graphs/2d/hellographs/HelloGraphs/Main.qml153
-rw-r--r--examples/graphs/2d/hellographs/HelloGraphs/qmldir2
-rw-r--r--examples/graphs/2d/hellographs/doc/hellographs.rst51
-rw-r--r--examples/graphs/2d/hellographs/doc/hellographs.webpbin0 -> 60020 bytes
-rw-r--r--examples/graphs/2d/hellographs/hellographs.pyproject3
-rw-r--r--examples/graphs/2d/hellographs/main.py22
-rw-r--r--examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst4
-rw-r--r--examples/graphs/3d/minimalsurfacegraph/main.py54
-rw-r--r--examples/graphs/3d/widgetgallery/axesinputhandler.py100
-rw-r--r--examples/graphs/3d/widgetgallery/bargraph.py272
-rw-r--r--examples/graphs/3d/widgetgallery/custominputhandler.py177
-rw-r--r--examples/graphs/3d/widgetgallery/data/layer_1.pngbin0 -> 34540 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/layer_2.pngbin0 -> 10553 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/layer_3.pngbin0 -> 7119 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/license.txt77
-rw-r--r--examples/graphs/3d/widgetgallery/data/maptexture.jpgbin0 -> 352922 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/narrowarrow.meshbin0 -> 15420 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/oilrig.meshbin0 -> 69728 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/pipe.meshbin0 -> 4760 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/raindata.txt158
-rw-r--r--examples/graphs/3d/widgetgallery/data/refinery.meshbin0 -> 75216 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/data/topography.pngbin0 -> 395504 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/doc/widgetgallery.rst11
-rw-r--r--examples/graphs/3d/widgetgallery/doc/widgetgallery.webpbin0 -> 93150 bytes
-rw-r--r--examples/graphs/3d/widgetgallery/graphmodifier.py391
-rw-r--r--examples/graphs/3d/widgetgallery/highlightseries.py94
-rw-r--r--examples/graphs/3d/widgetgallery/main.py41
-rw-r--r--examples/graphs/3d/widgetgallery/rainfalldata.py125
-rw-r--r--examples/graphs/3d/widgetgallery/scatterdatamodifier.py149
-rw-r--r--examples/graphs/3d/widgetgallery/scattergraph.py121
-rw-r--r--examples/graphs/3d/widgetgallery/surfacegraph.py256
-rw-r--r--examples/graphs/3d/widgetgallery/surfacegraphmodifier.py641
-rw-r--r--examples/graphs/3d/widgetgallery/topographicseries.py57
-rw-r--r--examples/graphs/3d/widgetgallery/variantbardatamapping.py67
-rw-r--r--examples/graphs/3d/widgetgallery/variantbardataproxy.py100
-rw-r--r--examples/graphs/3d/widgetgallery/variantdataset.py39
-rw-r--r--examples/graphs/3d/widgetgallery/widgetgallery.pyproject29
-rw-r--r--examples/gui/analogclock/main.py93
-rw-r--r--examples/gui/rhiwindow/doc/rhiwindow.rst49
-rw-r--r--examples/gui/rhiwindow/doc/rhiwindow.webpbin0 -> 38466 bytes
-rw-r--r--examples/gui/rhiwindow/main.py80
-rw-r--r--examples/gui/rhiwindow/rc_rhiwindow.py319
-rw-r--r--examples/gui/rhiwindow/rhiwindow.py420
-rw-r--r--examples/gui/rhiwindow/rhiwindow.pyproject5
-rw-r--r--examples/gui/rhiwindow/rhiwindow.qrc8
-rw-r--r--examples/gui/rhiwindow/shaders/color.frag15
-rw-r--r--examples/gui/rhiwindow/shaders/color.vert17
-rw-r--r--examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsbbin0 -> 1035 bytes
-rw-r--r--examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsbbin0 -> 1131 bytes
-rw-r--r--examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsbbin0 -> 1023 bytes
-rw-r--r--examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsbbin0 -> 982 bytes
-rw-r--r--examples/gui/rhiwindow/shaders/quad.frag11
-rw-r--r--examples/gui/rhiwindow/shaders/quad.vert10
-rw-r--r--examples/installer_test/hello.py2
-rw-r--r--examples/location/mapviewer/MapViewer/Main.qml (renamed from examples/location/mapviewer/mapviewer.qml)4
-rw-r--r--examples/location/mapviewer/MapViewer/forms/Geocode.qml (renamed from examples/location/mapviewer/forms/Geocode.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/GeocodeForm.ui.qml (renamed from examples/location/mapviewer/forms/GeocodeForm.ui.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/Locale.qml (renamed from examples/location/mapviewer/forms/Locale.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/LocaleForm.ui.qml (renamed from examples/location/mapviewer/forms/LocaleForm.ui.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/Message.qml (renamed from examples/location/mapviewer/forms/Message.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml (renamed from examples/location/mapviewer/forms/MessageForm.ui.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/ReverseGeocode.qml (renamed from examples/location/mapviewer/forms/ReverseGeocode.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/ReverseGeocodeForm.ui.qml (renamed from examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteAddress.qml (renamed from examples/location/mapviewer/forms/RouteAddress.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteAddressForm.ui.qml (renamed from examples/location/mapviewer/forms/RouteAddressForm.ui.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteCoordinate.qml (renamed from examples/location/mapviewer/forms/RouteCoordinate.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteCoordinateForm.ui.qml (renamed from examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteList.qml (renamed from examples/location/mapviewer/forms/RouteList.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteListDelegate.qml (renamed from examples/location/mapviewer/forms/RouteListDelegate.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/forms/RouteListHeader.qml (renamed from examples/location/mapviewer/forms/RouteListHeader.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/helper.js (renamed from examples/location/mapviewer/helper.js)0
-rw-r--r--examples/location/mapviewer/MapViewer/map/MapComponent.qml (renamed from examples/location/mapviewer/map/MapComponent.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/map/MapSliders.qml (renamed from examples/location/mapviewer/map/MapSliders.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/map/Marker.qml (renamed from examples/location/mapviewer/map/Marker.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/map/MiniMap.qml (renamed from examples/location/mapviewer/map/MiniMap.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/menus/ItemPopupMenu.qml (renamed from examples/location/mapviewer/menus/ItemPopupMenu.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/menus/MainMenu.qml (renamed from examples/location/mapviewer/menus/MainMenu.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/menus/MapPopupMenu.qml (renamed from examples/location/mapviewer/menus/MapPopupMenu.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/menus/MarkerPopupMenu.qml (renamed from examples/location/mapviewer/menus/MarkerPopupMenu.qml)0
-rw-r--r--examples/location/mapviewer/MapViewer/qmldir27
-rw-r--r--examples/location/mapviewer/MapViewer/resources/marker.png (renamed from examples/location/mapviewer/resources/marker.png)bin752 -> 752 bytes
-rw-r--r--examples/location/mapviewer/MapViewer/resources/marker_blue.png (renamed from examples/location/mapviewer/resources/marker_blue.png)bin3523 -> 3523 bytes
-rw-r--r--examples/location/mapviewer/MapViewer/resources/scale.png (renamed from examples/location/mapviewer/resources/scale.png)bin98 -> 98 bytes
-rw-r--r--examples/location/mapviewer/MapViewer/resources/scale_end.png (renamed from examples/location/mapviewer/resources/scale_end.png)bin93 -> 93 bytes
-rw-r--r--examples/location/mapviewer/doc/mapviewer.rst2
-rw-r--r--examples/location/mapviewer/main.py7
-rw-r--r--examples/location/mapviewer/mapviewer.pyproject54
-rw-r--r--examples/multimedia/audiooutput/audiooutput.py32
-rw-r--r--examples/multimedia/audiooutput/doc/audiooutput.rst2
-rw-r--r--examples/multimedia/audiosource/audiosource.py55
-rw-r--r--examples/multimedia/audiosource/doc/audiosource.rst2
-rw-r--r--examples/multimedia/camera/camera.py61
-rw-r--r--examples/multimedia/camera/camera.pyproject4
-rw-r--r--examples/multimedia/camera/camera_mobile.ui504
-rw-r--r--examples/multimedia/camera/doc/camera.rst2
-rw-r--r--examples/multimedia/camera/ui_camera.py10
-rw-r--r--examples/multimedia/camera/ui_camera_mobile.py251
-rw-r--r--examples/multimedia/camera/ui_imagesettings.py4
-rw-r--r--examples/multimedia/camera/ui_videosettings.py6
-rw-r--r--examples/multimedia/camera/ui_videosettings_mobile.py176
-rw-r--r--examples/multimedia/camera/videosettings.py8
-rw-r--r--examples/multimedia/camera/videosettings_mobile.ui207
-rw-r--r--examples/multimedia/player/player.py22
-rw-r--r--examples/multimedia/screencapture/doc/screencapture.rst57
-rw-r--r--examples/multimedia/screencapture/doc/screencapture.webpbin20604 -> 53592 bytes
-rw-r--r--examples/multimedia/screencapture/main.py3
-rw-r--r--examples/multimedia/screencapture/screencapture.pyproject2
-rw-r--r--examples/multimedia/screencapture/screencapturepreview.py143
-rw-r--r--examples/multimedia/screencapture/screenlistmodel.py2
-rw-r--r--examples/multimedia/screencapture/windowlistmodel.py30
-rw-r--r--examples/network/blockingfortuneclient/blockingfortuneclient.py27
-rw-r--r--examples/network/downloader/downloader.py3
-rw-r--r--examples/network/fortuneclient/fortuneclient.py23
-rw-r--r--examples/network/fortuneserver/fortuneserver.py18
-rw-r--r--examples/network/googlesuggest/googlesuggest.py3
-rw-r--r--examples/network/threadedfortuneserver/threadedfortuneserver.py10
-rw-r--r--examples/networkauth/redditclient/redditmodel.py2
-rw-r--r--examples/networkauth/redditclient/redditwrapper.py3
-rw-r--r--examples/opengl/contextinfo/contextinfo.py16
-rw-r--r--examples/opengl/hellogl2/doc/hellogl2.rst14
-rw-r--r--examples/opengl/hellogl2/glwidget.py272
-rw-r--r--examples/opengl/hellogl2/hellogl2.py439
-rw-r--r--examples/opengl/hellogl2/hellogl2.pyproject2
-rw-r--r--examples/opengl/hellogl2/logo.py101
-rw-r--r--examples/opengl/hellogl2/main.py58
-rw-r--r--examples/opengl/hellogl2/mainwindow.py29
-rw-r--r--examples/opengl/hellogl2/window.py110
-rw-r--r--examples/opengl/textures/textures.py16
-rw-r--r--examples/opengl/threadedqopenglwidget/renderer.py2
-rw-r--r--examples/pdf/quickpdfviewer/main.py2
-rw-r--r--examples/pdfwidgets/pdfviewer/rc_resources.py (renamed from examples/pdfwidgets/pdfviewer/resources_rc.py)0
-rw-r--r--examples/pdfwidgets/pdfviewer/ui_mainwindow.py10
-rw-r--r--examples/qml/editingmodel/doc/editingmodel.rst4
-rw-r--r--examples/qml/editingmodel/main.py2
-rw-r--r--examples/qml/editingmodel/model.py13
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst4
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/adding/main.py2
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/adding/person.py1
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/main.py7
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/main.py9
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/main.py8
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/main.py8
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/main.py8
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/main.py8
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/binding/main.py6
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/methods/main.py4
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst10
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/properties/main.py4
-rw-r--r--examples/qml/tutorials/extending-qml-advanced/properties/person.py1
-rw-r--r--examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py3
-rw-r--r--examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst9
-rw-r--r--examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py3
-rw-r--r--examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py1
-rw-r--r--examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py1
-rw-r--r--examples/qml/usingmodel/doc/usingmodel.rst2
-rw-r--r--examples/qml/usingmodel/usingmodel.py48
-rw-r--r--examples/qml/usingmodel/view.qml12
-rw-r--r--examples/quick/models/objectlistmodel/doc/objectlistmodel.rst2
-rw-r--r--examples/quick/models/objectlistmodel/objectlistmodel.py6
-rw-r--r--examples/quick/models/stringlistmodel/doc/stringlistmodel.rst2
-rw-r--r--examples/quick/models/stringlistmodel/stringlistmodel.py2
-rw-r--r--examples/quick/painteditem/doc/painteditem.rst2
-rw-r--r--examples/quick/painteditem/painteditem.py10
-rw-r--r--examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py34
-rw-r--r--examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py2
-rw-r--r--examples/quick/scenegraph/openglunderqml/main.py2
-rw-r--r--examples/quick/window/main.py4
-rw-r--r--examples/quick3d/customgeometry/main.py6
-rw-r--r--examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml188
-rw-r--r--examples/quick3d/proceduraltexture/ProceduralTextureModule/app.qrc6
-rw-r--r--examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir2
-rw-r--r--examples/quick3d/proceduraltexture/doc/proceduraltexture-example.webpbin0 -> 5166 bytes
-rw-r--r--examples/quick3d/proceduraltexture/doc/proceduraltexture.rst12
-rw-r--r--examples/quick3d/proceduraltexture/gradienttexture.py105
-rw-r--r--examples/quick3d/proceduraltexture/main.py30
-rw-r--r--examples/quick3d/proceduraltexture/proceduraltexture.pyproject9
-rw-r--r--examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml82
-rw-r--r--examples/quickcontrols/contactslist/Contact/ContactDialog.qml45
-rw-r--r--examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml72
-rw-r--r--examples/quickcontrols/contactslist/Contact/ContactList.qml70
-rw-r--r--examples/quickcontrols/contactslist/Contact/ContactView.ui.qml36
-rw-r--r--examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml17
-rw-r--r--examples/quickcontrols/contactslist/Contact/qmldir7
-rw-r--r--examples/quickcontrols/contactslist/contactlist.pyproject10
-rw-r--r--examples/quickcontrols/contactslist/contactmodel.py116
-rw-r--r--examples/quickcontrols/contactslist/doc/contactslist.rst15
-rw-r--r--examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.pngbin0 -> 23581 bytes
-rw-r--r--examples/quickcontrols/contactslist/main.py28
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml229
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc2
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc1
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg2
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml88
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml29
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml160
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml117
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml44
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml19
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml184
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml10
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml147
-rw-r--r--examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir2
-rw-r--r--examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst13
-rw-r--r--examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webpbin83086 -> 47416 bytes
-rw-r--r--examples/quickcontrols/filesystemexplorer/editormodels.py116
-rw-r--r--examples/quickcontrols/filesystemexplorer/filesystemexplorer.py69
-rw-r--r--examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject6
-rw-r--r--examples/quickcontrols/filesystemexplorer/main.py48
-rw-r--r--examples/quickcontrols/gallery/doc/gallery.rst2
-rw-r--r--examples/quickcontrols/gallery/gallery.py2
-rw-r--r--examples/samplebinding/CMakeLists.txt2
-rw-r--r--examples/samplebinding/doc/samplebinding.rst6
-rw-r--r--examples/scriptableapplication/CMakeLists.txt2
-rw-r--r--examples/scriptableapplication/doc/scriptableapplication.rst6
-rw-r--r--examples/scriptableapplication/mainwindow.cpp60
-rw-r--r--examples/scriptableapplication/mainwindow.h6
-rw-r--r--examples/scriptableapplication/pythonutils.cpp10
-rw-r--r--examples/scriptableapplication/pythonutils.h4
-rw-r--r--examples/serialbus/can/main.py1
-rw-r--r--examples/serialbus/can/mainwindow.py20
-rw-r--r--examples/serialbus/can/sendframebox.py4
-rw-r--r--examples/serialbus/can/ui_canbusdeviceinfobox.py2
-rw-r--r--examples/serialbus/can/ui_canbusdeviceinfodialog.py4
-rw-r--r--examples/serialbus/can/ui_connectdialog.py4
-rw-r--r--examples/serialbus/can/ui_mainwindow.py6
-rw-r--r--examples/serialbus/can/ui_sendframebox.py6
-rw-r--r--examples/serialbus/modbus/modbusclient/mainwindow.py4
-rw-r--r--examples/serialbus/modbus/modbusclient/ui_mainwindow.py12
-rw-r--r--examples/serialbus/modbus/modbusclient/ui_settingsdialog.py4
-rw-r--r--examples/serialport/terminal/mainwindow.py2
-rw-r--r--examples/serialport/terminal/settingsdialog.py5
-rw-r--r--examples/serialport/terminal/ui_mainwindow.py4
-rw-r--r--examples/serialport/terminal/ui_settingsdialog.py4
-rw-r--r--examples/spatialaudio/audiopanning/main.py2
-rw-r--r--examples/speech/hello_speak/ui_mainwindow.py10
-rw-r--r--examples/sql/books/bookdelegate.py13
-rw-r--r--examples/sql/books/books.qrc3
-rw-r--r--examples/sql/books/bookwindow.py14
-rw-r--r--examples/sql/books/images/star-filled.svg1
-rw-r--r--examples/sql/books/images/star.pngbin782 -> 0 bytes
-rw-r--r--examples/sql/books/images/star.svg1
-rw-r--r--examples/sql/books/main.py2
-rw-r--r--examples/sql/books/rc_books.py142
-rw-r--r--examples/sql/books/ui_bookwindow.py2
-rw-r--r--examples/statemachine/rogue/rogue.py14
-rw-r--r--examples/utils/pyside_config.py16
-rw-r--r--examples/webchannel/standalone/ui_dialog.py2
-rw-r--r--examples/webenginequick/nanobrowser/quicknanobrowser.py13
-rw-r--r--examples/webenginewidgets/markdowneditor/main.py2
-rw-r--r--examples/webenginewidgets/markdowneditor/ui_mainwindow.py2
-rw-r--r--examples/webenginewidgets/notifications/notificationpopup.py4
-rw-r--r--examples/webenginewidgets/simplebrowser/browser.py3
-rw-r--r--examples/webenginewidgets/simplebrowser/browserwindow.py19
-rw-r--r--examples/webenginewidgets/simplebrowser/downloadwidget.py7
-rw-r--r--examples/webenginewidgets/simplebrowser/main.py9
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py8
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py4
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_downloadwidget.py4
-rw-r--r--examples/webenginewidgets/simplebrowser/ui_passworddialog.py4
-rw-r--r--examples/webenginewidgets/simplebrowser/webview.py17
-rw-r--r--examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py2
-rw-r--r--examples/widgetbinding/doc/widgetbinding.md8
-rw-r--r--examples/widgets/animation/animatedtiles/animatedtiles.py27
-rw-r--r--examples/widgets/animation/appchooser/appchooser.py2
-rw-r--r--examples/widgets/animation/easing/easing.py14
-rw-r--r--examples/widgets/animation/easing/ui_form.py16
-rw-r--r--examples/widgets/animation/states/states.py2
-rw-r--r--examples/widgets/desktop/systray/window.py8
-rw-r--r--examples/widgets/dialogs/classwizard/classwizard.py4
-rw-r--r--examples/widgets/dialogs/classwizard/listchooser.py2
-rw-r--r--examples/widgets/dialogs/standarddialogs/standarddialogs.py37
-rw-r--r--examples/widgets/dialogs/trivialwizard/trivialwizard.py2
-rw-r--r--examples/widgets/draganddrop/draggabletext/draggabletext.py5
-rw-r--r--examples/widgets/effects/lighting/lighting.py3
-rw-r--r--examples/widgets/gettext/doc/gettext.rst7
-rw-r--r--examples/widgets/graphicsview/anchorlayout/anchorlayout.py2
-rw-r--r--examples/widgets/graphicsview/collidingmice/collidingmice.py16
-rw-r--r--examples/widgets/graphicsview/diagramscene/diagramscene.py128
-rw-r--r--examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py53
-rw-r--r--examples/widgets/graphicsview/elasticnodes/elasticnodes.py20
-rw-r--r--examples/widgets/imageviewer/imageviewer.py2
-rw-r--r--examples/widgets/itemviews/address_book/adddialogwidget.py4
-rw-r--r--examples/widgets/itemviews/address_book/address_book.py15
-rw-r--r--examples/widgets/itemviews/address_book/addresswidget.py5
-rw-r--r--examples/widgets/itemviews/address_book/tablemodel.py4
-rw-r--r--examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py26
-rw-r--r--examples/widgets/itemviews/dirview/dirview.py1
-rw-r--r--examples/widgets/itemviews/editabletreemodel/mainwindow.py2
-rw-r--r--examples/widgets/itemviews/fetchmore/fetchmore.py4
-rw-r--r--examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py3
-rw-r--r--examples/widgets/itemviews/stardelegate/stardelegate.py12
-rw-r--r--examples/widgets/itemviews/stardelegate/stareditor.py3
-rw-r--r--examples/widgets/itemviews/stardelegate/starrating.py6
-rw-r--r--examples/widgets/layouts/basiclayouts/basiclayouts.py5
-rw-r--r--examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py10
-rw-r--r--examples/widgets/linguist/doc/linguist.rst7
-rw-r--r--examples/widgets/linguist/linguist.qrc4
-rw-r--r--examples/widgets/linguist/main.py4
-rw-r--r--examples/widgets/mainwindows/application/application.py79
-rw-r--r--examples/widgets/mainwindows/dockwidgets/dockwidgets.py76
-rw-r--r--examples/widgets/mainwindows/mdi/images/new.pngbin852 -> 0 bytes
-rw-r--r--examples/widgets/mainwindows/mdi/images/save.pngbin1187 -> 0 bytes
-rw-r--r--examples/widgets/mainwindows/mdi/mdi.py112
-rw-r--r--examples/widgets/mainwindows/mdi/mdi.pyproject2
-rw-r--r--examples/widgets/mainwindows/mdi/mdi.qrc10
-rw-r--r--examples/widgets/mainwindows/mdi/mdi_rc.py608
-rw-r--r--examples/widgets/painting/basicdrawing/basicdrawing.py27
-rw-r--r--examples/widgets/painting/concentriccircles/concentriccircles.py9
-rw-r--r--examples/widgets/painting/painter/painter.py8
-rw-r--r--examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst34
-rw-r--r--examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webpbin0 -> 8556 bytes
-rw-r--r--examples/widgets/rhi/simplerhiwidget/examplewidget.py135
-rw-r--r--examples/widgets/rhi/simplerhiwidget/main.py35
-rw-r--r--examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py163
-rw-r--r--examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsbbin0 -> 738 bytes
-rw-r--r--examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsbbin0 -> 1091 bytes
-rw-r--r--examples/widgets/rhi/simplerhiwidget/shaders/color.frag10
-rw-r--r--examples/widgets/rhi/simplerhiwidget/shaders/color.vert16
-rw-r--r--examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject4
-rw-r--r--examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc6
-rw-r--r--examples/widgets/richtext/orderform/orderform.py24
-rw-r--r--examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py4
-rw-r--r--examples/widgets/richtext/textedit/main.py2
-rw-r--r--examples/widgets/richtext/textedit/textedit.py45
-rw-r--r--examples/widgets/tools/regularexpression/regularexpressiondialog.py39
-rw-r--r--examples/widgets/tutorials/addressbook/part2.py7
-rw-r--r--examples/widgets/tutorials/addressbook/part3.py7
-rw-r--r--examples/widgets/tutorials/addressbook/part4.py21
-rw-r--r--examples/widgets/tutorials/addressbook/part5.py26
-rw-r--r--examples/widgets/tutorials/addressbook/part6.py41
-rw-r--r--examples/widgets/tutorials/addressbook/part7.py48
-rw-r--r--examples/widgets/tutorials/cannon/t10.py6
-rw-r--r--examples/widgets/tutorials/cannon/t11.py6
-rw-r--r--examples/widgets/tutorials/cannon/t12.py6
-rw-r--r--examples/widgets/tutorials/cannon/t13.py6
-rw-r--r--examples/widgets/tutorials/cannon/t14.py9
-rw-r--r--examples/widgets/tutorials/cannon/t4.py2
-rw-r--r--examples/widgets/tutorials/cannon/t5.py2
-rw-r--r--examples/widgets/tutorials/cannon/t6.py2
-rw-r--r--examples/widgets/tutorials/cannon/t7.py2
-rw-r--r--examples/widgets/tutorials/cannon/t8.py6
-rw-r--r--examples/widgets/tutorials/cannon/t9.py6
-rw-r--r--examples/widgets/tutorials/modelview/2_formatting.py2
-rw-r--r--examples/widgets/tutorials/modelview/3_changingmodel.py1
-rw-r--r--examples/widgets/tutorials/modelview/6_treeview.py1
-rw-r--r--examples/widgets/tutorials/modelview/7_selections.py1
-rw-r--r--examples/widgets/widgets/charactermap/characterwidget.py4
-rw-r--r--examples/widgets/widgets/charactermap/mainwindow.py2
-rw-r--r--examples/widgets/widgets/digitalclock/doc/digitalclock.rst2
-rw-r--r--examples/widgets/widgets/tetrix/tetrix.py49
-rw-r--r--examples/widgets/widgetsgallery/widgetgallery.py4
-rw-r--r--examples/xml/dombookmarks/dombookmarks.py61
-rw-r--r--examples/xml/dombookmarks/dombookmarks.pyproject2
-rw-r--r--examples/xml/dombookmarks/frank.xbel230
-rw-r--r--examples/xml/dombookmarks/jennifer.xbel82
500 files changed, 20033 insertions, 3435 deletions
diff --git a/examples/async/eratosthenes/doc/eratosthenes.rst b/examples/async/eratosthenes/doc/eratosthenes.rst
index 494a94df3..56e9f391b 100644
--- a/examples/async/eratosthenes/doc/eratosthenes.rst
+++ b/examples/async/eratosthenes/doc/eratosthenes.rst
@@ -1,27 +1,21 @@
-Async examples
-==============
+Async "Eratosthenes" Example
+============================
The Python language provides keywords for asynchronous operations, i.e.,
"async" to define coroutines or "await" to schedule asynchronous calls in the
event loop (see `PEP 492 <https://peps.python.org/pep-0492/>`_). It is up to
packages to implement an event loop, support for these keywords, and more.
-The best-known package for this is `asyncio`. Since both an async package and
-Qt itself work with event loops, special care must be taken to ensure that both
-event loops work with each other. asyncio offers a function `stop` that allows
-stopping an event loop without closing it. If it is called while a loop is
-running through `run_forever`, the loop will run the current batch of callbacks
-and then exit. New callbacks wil be scheduled the next time `run_forever` is
-called.
+The best-known package for this is `asyncio`. asyncio offers an API that allows
+for the asyncio event loop to be replaced by a custom implementation. Such an
+implementation is available with the `QtAsyncio` module. It is based on Qt and
+uses Qt's event loop in the backend.
-This approach is highly experimental and does not represent the state of the
-art of integrating Qt with asyncio. Instead it should rather be regarded more
-as a proof of concept to contrast asyncio with other async packages such as
-`trio`, which offers a dedicated `low-level API
+`trio` is another popular package that offers a dedicated `low-level API
<https://trio.readthedocs.io/en/stable/reference-lowlevel.html>`_ for more
-complicated use cases such as this. Specifically, there exists a function
-`start_guest_run` that enables running the Trio event loop as a "guest" inside
-another event loop - Qt's in our case.
+complex use cases. Specifically, there exists a function `start_guest_run` that
+enables running the Trio event loop as a "guest" inside another event loop -
+Qt's in our case, standing in contrast to asyncio's approach.
Based on this functionality, two examples for async usage with Qt have been
implemented: `eratosthenes` and `minimal`:
@@ -39,14 +33,6 @@ implemented: `eratosthenes` and `minimal`:
boilerplate code is essential for an async program with Qt and offers a
starting point for more complex programs.
-Both examples feature:
-
-1. A window class.
-2. An `AsyncHelper` class containing `start_guest_run` plus helpers and
- callbacks necessary for its invocation. The entry point for the Trio/asyncio
- guest run is provided as an argument from outside, which can be any async
- function.
-
While `eratosthenes` offloads the asynchronous logic that will run in
trio's/asyncio's event loop into a separate class, `minimal` demonstrates that
async functions can be integrated into any class, including subclasses of Qt
diff --git a/examples/async/eratosthenes/eratosthenes_asyncio.py b/examples/async/eratosthenes/eratosthenes_asyncio.py
index a5177a94b..c5d7fc2fe 100644
--- a/examples/async/eratosthenes/eratosthenes_asyncio.py
+++ b/examples/async/eratosthenes/eratosthenes_asyncio.py
@@ -1,12 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from PySide6.QtCore import (Qt, QEvent, QObject, QTimer, Signal, Slot)
+from PySide6.QtCore import (Qt, QObject, Signal, Slot)
from PySide6.QtGui import (QColor, QFont, QPalette)
from PySide6.QtWidgets import (QApplication, QGridLayout, QLabel, QMainWindow, QVBoxLayout, QWidget)
+import PySide6.QtAsyncio as QtAsyncio
+
import asyncio
-import signal
import sys
from random import randint
@@ -61,8 +62,6 @@ class MainWindow(QMainWindow):
class Eratosthenes(QObject):
- done_signal = Signal()
-
""" This Sieve of Eratosthenes runs on a configurable tick (default
0.1 seconds). At each tick, a new subroutine will be created
that will check multiples of the next prime number. Each of
@@ -120,83 +119,6 @@ class Eratosthenes(QObject):
"🥳 Congratulations! You found all the prime numbers and solved mathematics. 🥳"
)
- # This signals to the guest run when there are no more asyncio tasks
- # left so its event loop can finish.
- self.done_signal.emit()
-
-
-class AsyncHelper(QObject):
-
- class ReenterQtObject(QObject):
- """ This is a QObject to which an event will be posted, allowing
- asyncio to resume when the event is handled. event.fn() is
- the next entry point of the asyncio event loop. """
- def event(self, event):
- if event.type() == QEvent.Type.User + 1:
- event.fn()
- return True
- return False
-
- class ReenterQtEvent(QEvent):
- """ This is the QEvent that will be handled by the ReenterQtObject.
- self.fn is the next entry point of the asyncio event loop. """
- def __init__(self, fn):
- super().__init__(QEvent.Type(QEvent.Type.User + 1))
- self.fn = fn
-
- def __init__(self, worker, entry):
- super().__init__()
- self.reenter_qt = self.ReenterQtObject()
- self.entry = entry
- self.loop = asyncio.new_event_loop()
- self.done = False
-
- self.worker = worker
- if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal):
- self.worker.start_signal.connect(self.on_worker_started)
- if hasattr(self.worker, "done_signal") and isinstance(self.worker.done_signal, Signal):
- self.worker.done_signal.connect(self.on_worker_done)
-
- @Slot()
- def on_worker_started(self):
- """ To use asyncio and Qt together, one must run the asyncio
- event loop as a "guest" inside the Qt "host" event loop. """
- if not self.entry:
- raise Exception("No entry point for the asyncio event loop was set.")
- asyncio.set_event_loop(self.loop)
- self.loop.create_task(self.entry())
- self.loop.call_soon(self.next_guest_run_schedule)
- self.done = False # Set this explicitly as we might want to restart the guest run.
- self.loop.run_forever()
-
- @Slot()
- def on_worker_done(self):
- """ When all our current asyncio tasks are finished, we must end
- the "guest run" lest we enter a quasi idle loop of switching
- back and forth between the asyncio and Qt loops. We can
- launch a new guest run by calling launch_guest_run() again. """
- self.done = True
-
- def continue_loop(self):
- """ This function is called by an event posted to the Qt event
- loop to continue the asyncio event loop. """
- if not self.done:
- self.loop.call_soon(self.next_guest_run_schedule)
- self.loop.run_forever()
-
- def next_guest_run_schedule(self):
- """ This function serves to pause and re-schedule the guest
- (asyncio) event loop inside the host (Qt) event loop. It is
- registered in asyncio as a callback to be called at the next
- iteration of the event loop. When this function runs, it
- first stops the asyncio event loop, then by posting an event
- on the Qt event loop, it both relinquishes to Qt's event
- loop and also schedules the asyncio event loop to run again.
- Upon handling this event, a function will be called that
- resumes the asyncio event loop. """
- self.loop.stop()
- QApplication.postEvent(self.reenter_qt, self.ReenterQtEvent(self.continue_loop))
-
if __name__ == "__main__":
rows = 40
@@ -206,15 +128,7 @@ if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow(rows, cols)
eratosthenes = Eratosthenes(num, main_window)
- async_helper = AsyncHelper(eratosthenes, eratosthenes.start)
-
- # This establishes the entry point for the asyncio guest run. It
- # varies depending on how and when its event loop is to be
- # triggered, e.g., from the beginning (as here) or rather at a
- # specific moment like a button press.
- QTimer.singleShot(0, async_helper.on_worker_started)
main_window.show()
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- app.exec()
+ QtAsyncio.run(eratosthenes.start(), handle_sigint=True)
diff --git a/examples/async/minimal/doc/minimal.rst b/examples/async/minimal/doc/minimal.rst
index 5a1cf8544..54f847ea9 100644
--- a/examples/async/minimal/doc/minimal.rst
+++ b/examples/async/minimal/doc/minimal.rst
@@ -1,27 +1,21 @@
-Async examples
-==============
+Async "Minimal" Example
+=======================
The Python language provides keywords for asynchronous operations, i.e.,
"async" to define coroutines or "await" to schedule asynchronous calls in the
event loop (see `PEP 492 <https://peps.python.org/pep-0492/>`_). It is up to
packages to implement an event loop, support for these keywords, and more.
-The best-known package for this is `asyncio`. Since both an async package and
-Qt itself work with event loops, special care must be taken to ensure that both
-event loops work with each other. asyncio offers a function `stop` that allows
-stopping an event loop without closing it. If it is called while a loop is
-running through `run_forever`, the loop will run the current batch of callbacks
-and then exit. New callbacks wil be scheduled the next time `run_forever` is
-called.
+The best-known package for this is `asyncio`. asyncio offers an API that allows
+for the asyncio event loop to be replaced by a custom implementation. Such an
+implementation is available with the `QtAsyncio` module. It is based on Qt and
+uses Qt's event loop in the backend.
-This approach is highly experimental and does not represent the state of the
-art of integrating Qt with asyncio. Instead it should rather be regarded more
-as a proof of concept to contrast asyncio with other async packages such as
-`trio`, which offers a dedicated `low-level API
+`trio` is another popular package that offers a dedicated `low-level API
<https://trio.readthedocs.io/en/stable/reference-lowlevel.html>`_ for more
-complicated use cases such as this. Specifically, there exists a function
-`start_guest_run` that enables running the Trio event loop as a "guest" inside
-another event loop - Qt's in our case.
+complex use cases. Specifically, there exists a function `start_guest_run` that
+enables running the Trio event loop as a "guest" inside another event loop -
+Qt's in our case, standing in contrast to asyncio's approach.
Based on this functionality, two examples for async usage with Qt have been
implemented: `eratosthenes` and `minimal`:
@@ -38,14 +32,6 @@ implemented: `eratosthenes` and `minimal`:
boilerplate code is essential for an async program with Qt and offers a
starting point for more complex programs.
-Both examples feature:
-
-1. A window class.
-2. An `AsyncHelper` class containing `start_guest_run` plus helpers and
- callbacks necessary for its invocation. The entry point for the Trio/asyncio
- guest run is provided as an argument from outside, which can be any async
- function.
-
While `eratosthenes` offloads the asynchronous logic that will run in
trio's/asyncio's event loop into a separate class, `minimal` demonstrates that
async functions can be integrated into any class, including subclasses of Qt
diff --git a/examples/async/minimal/minimal_asyncio.py b/examples/async/minimal/minimal_asyncio.py
index 80c81da3b..a6c4708b3 100644
--- a/examples/async/minimal/minimal_asyncio.py
+++ b/examples/async/minimal/minimal_asyncio.py
@@ -1,19 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from PySide6.QtCore import (Qt, QEvent, QObject, Signal, Slot)
+from PySide6.QtCore import Qt
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget)
+import PySide6.QtAsyncio as QtAsyncio
+
import asyncio
-import signal
import sys
class MainWindow(QMainWindow):
- start_signal = Signal()
- done_signal = Signal()
-
def __init__(self):
super().__init__()
@@ -26,98 +24,17 @@ class MainWindow(QMainWindow):
layout.addWidget(self.text, alignment=Qt.AlignmentFlag.AlignCenter)
async_trigger = QPushButton(text="What is the question?")
- async_trigger.clicked.connect(self.async_start)
+ async_trigger.clicked.connect(lambda: asyncio.ensure_future(self.set_text()))
layout.addWidget(async_trigger, alignment=Qt.AlignmentFlag.AlignCenter)
- @Slot()
- def async_start(self):
- self.start_signal.emit()
-
async def set_text(self):
await asyncio.sleep(1)
self.text.setText("What do you get if you multiply six by nine?")
- self.done_signal.emit()
-
-
-class AsyncHelper(QObject):
-
- class ReenterQtObject(QObject):
- """ This is a QObject to which an event will be posted, allowing
- asyncio to resume when the event is handled. event.fn() is
- the next entry point of the asyncio event loop. """
- def event(self, event):
- if event.type() == QEvent.Type.User + 1:
- event.fn()
- return True
- return False
-
- class ReenterQtEvent(QEvent):
- """ This is the QEvent that will be handled by the ReenterQtObject.
- self.fn is the next entry point of the asyncio event loop. """
- def __init__(self, fn):
- super().__init__(QEvent.Type(QEvent.Type.User + 1))
- self.fn = fn
-
- def __init__(self, worker, entry):
- super().__init__()
- self.reenter_qt = self.ReenterQtObject()
- self.entry = entry
- self.loop = asyncio.new_event_loop()
- self.done = False
-
- self.worker = worker
- if hasattr(self.worker, "start_signal") and isinstance(self.worker.start_signal, Signal):
- self.worker.start_signal.connect(self.on_worker_started)
- if hasattr(self.worker, "done_signal") and isinstance(self.worker.done_signal, Signal):
- self.worker.done_signal.connect(self.on_worker_done)
-
- @Slot()
- def on_worker_started(self):
- """ To use asyncio and Qt together, one must run the asyncio
- event loop as a "guest" inside the Qt "host" event loop. """
- if not self.entry:
- raise Exception("No entry point for the asyncio event loop was set.")
- asyncio.set_event_loop(self.loop)
- self.loop.create_task(self.entry())
- self.loop.call_soon(self.next_guest_run_schedule)
- self.done = False # Set this explicitly as we might want to restart the guest run.
- self.loop.run_forever()
-
- @Slot()
- def on_worker_done(self):
- """ When all our current asyncio tasks are finished, we must end
- the "guest run" lest we enter a quasi idle loop of switching
- back and forth between the asyncio and Qt loops. We can
- launch a new guest run by calling launch_guest_run() again. """
- self.done = True
-
- def continue_loop(self):
- """ This function is called by an event posted to the Qt event
- loop to continue the asyncio event loop. """
- if not self.done:
- self.loop.call_soon(self.next_guest_run_schedule)
- self.loop.run_forever()
-
- def next_guest_run_schedule(self):
- """ This function serves to pause and re-schedule the guest
- (asyncio) event loop inside the host (Qt) event loop. It is
- registered in asyncio as a callback to be called at the next
- iteration of the event loop. When this function runs, it
- first stops the asyncio event loop, then by posting an event
- on the Qt event loop, it both relinquishes to Qt's event
- loop and also schedules the asyncio event loop to run again.
- Upon handling this event, a function will be called that
- resumes the asyncio event loop. """
- self.loop.stop()
- QApplication.postEvent(self.reenter_qt, self.ReenterQtEvent(self.continue_loop))
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = MainWindow()
- async_helper = AsyncHelper(main_window, main_window.set_text)
-
main_window.show()
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- app.exec()
+ QtAsyncio.run(handle_sigint=True)
diff --git a/examples/axcontainer/axviewer/axviewer.py b/examples/axcontainer/axviewer/axviewer.py
index abc0599e5..bcd4c2714 100644
--- a/examples/axcontainer/axviewer/axviewer.py
+++ b/examples/axcontainer/axviewer/axviewer.py
@@ -7,7 +7,7 @@ import sys
from PySide6.QtAxContainer import QAxSelect, QAxWidget
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (QApplication, QDialog,
- QMainWindow, QMessageBox, QToolBar)
+ QMainWindow, QMessageBox, QToolBar)
class MainWindow(QMainWindow):
@@ -25,7 +25,7 @@ class MainWindow(QMainWindow):
fileMenu.addAction(exitAction)
aboutMenu = self.menuBar().addMenu("&About")
- aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt)
+ aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt) # noqa: F821
aboutMenu.addAction(aboutQtAct)
self.axWidget = QAxWidget()
self.setCentralWidget(self.axWidget)
diff --git a/examples/bluetooth/btscanner/device.py b/examples/bluetooth/btscanner/device.py
index e51160b84..c75f5b8a1 100644
--- a/examples/bluetooth/btscanner/device.py
+++ b/examples/bluetooth/btscanner/device.py
@@ -46,7 +46,7 @@ class DeviceDiscoveryDialog(QDialog):
item = QListWidgetItem(label)
pairing_status = self._local_device.pairingStatus(info.address())
if (pairing_status == QBluetoothLocalDevice.Paired
- or pairing_status == QBluetoothLocalDevice.AuthorizedPaired):
+ or pairing_status == QBluetoothLocalDevice.AuthorizedPaired):
item.setForeground(QColor(Qt.green))
else:
item.setForeground(QColor(Qt.black))
@@ -123,7 +123,8 @@ class DeviceDiscoveryDialog(QDialog):
items = self._ui.list.findItems(address.toString(), Qt.MatchContains)
color = QColor(Qt.red)
- if pairing == QBluetoothLocalDevice.Paired or pairing == QBluetoothLocalDevice.AuthorizedPaired:
+ if (pairing == QBluetoothLocalDevice.Paired
+ or pairing == QBluetoothLocalDevice.AuthorizedPaired):
color = QColor(Qt.green)
for item in items:
item.setForeground(color)
diff --git a/examples/bluetooth/btscanner/ui_device.py b/examples/bluetooth/btscanner/ui_device.py
index 0740afa7a..b443b2bc2 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.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 fc1f45fef..ccc36677a 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
diff --git a/examples/bluetooth/heartrate_game/connectionhandler.py b/examples/bluetooth/heartrate_game/connectionhandler.py
index 4cce58723..7bf60bbc5 100644
--- a/examples/bluetooth/heartrate_game/connectionhandler.py
+++ b/examples/bluetooth/heartrate_game/connectionhandler.py
@@ -5,9 +5,12 @@ import sys
from PySide6.QtBluetooth import QBluetoothLocalDevice
from PySide6.QtQml import QmlElement
-from PySide6.QtCore import QObject, Property, Signal, Slot
+from PySide6.QtCore import QObject, Property, Signal, Slot, Qt
-from heartrate_global import simulator
+from heartrate_global import simulator, is_android, error_not_nuitka
+
+if is_android or sys.platform == "darwin":
+ from PySide6.QtCore import QBluetoothPermission
# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
@@ -22,8 +25,8 @@ class ConnectionHandler(QObject):
def __init__(self, parent=None):
super().__init__(parent)
- self.m_localDevice = QBluetoothLocalDevice()
- self.m_localDevice.hostModeStateChanged.connect(self.hostModeChanged)
+ self.m_hasPermission = False
+ self.initLocalDevice()
@Property(bool, notify=deviceChanged)
def alive(self):
@@ -48,8 +51,27 @@ class ConnectionHandler(QObject):
@Property(bool, notify=deviceChanged)
def hasPermission(self):
- return True
+ return self.m_hasPermission
@Slot(QBluetoothLocalDevice.HostMode)
def hostModeChanged(self, mode):
self.deviceChanged.emit()
+
+ def initLocalDevice(self):
+ if is_android or sys.platform == "darwin":
+ error_not_nuitka()
+ permission = QBluetoothPermission()
+ permission.setCommunicationModes(QBluetoothPermission.Access)
+ permission_status = qApp.checkPermission(permission) # noqa: F821
+ if permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(permission, self, self.initLocalDevice) # noqa: F821
+ return
+ if permission_status == Qt.PermissionStatus.Denied:
+ return
+ elif permission_status == Qt.PermissionStatus.Granted:
+ print("[HeartRateGame] Bluetooth Permission Granted")
+
+ self.m_localDevice = QBluetoothLocalDevice()
+ self.m_localDevice.hostModeStateChanged.connect(self.hostModeChanged)
+ self.m_hasPermission = True
+ self.deviceChanged.emit()
diff --git a/examples/bluetooth/heartrate_game/devicefinder.py b/examples/bluetooth/heartrate_game/devicefinder.py
index 283897f28..e581d12ec 100644
--- a/examples/bluetooth/heartrate_game/devicefinder.py
+++ b/examples/bluetooth/heartrate_game/devicefinder.py
@@ -1,14 +1,18 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import sys
from PySide6.QtBluetooth import (QBluetoothDeviceDiscoveryAgent,
QBluetoothDeviceInfo)
from PySide6.QtQml import QmlElement
-from PySide6.QtCore import QTimer, Property, Signal, Slot
+from PySide6.QtCore import QTimer, Property, Signal, Slot, Qt
from bluetoothbaseclass import BluetoothBaseClass
from deviceinfo import DeviceInfo
-from heartrate_global import simulator
+from heartrate_global import simulator, is_android, error_not_nuitka
+
+if is_android or sys.platform == "darwin":
+ from PySide6.QtCore import QBluetoothPermission
# To be used on the @QmlElement decorator
# (QML_IMPORT_MINOR_VERSION is optional)
@@ -43,6 +47,19 @@ class DeviceFinder(BluetoothBaseClass):
@Slot()
def startSearch(self):
+ if is_android or sys.platform == "darwin":
+ error_not_nuitka()
+ permission = QBluetoothPermission()
+ permission.setCommunicationModes(QBluetoothPermission.Access)
+ permission_status = qApp.checkPermission(permission) # noqa: F821
+ if permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(permission, self, self.startSearch) # noqa: F82 1
+ return
+ elif permission_status == Qt.PermissionStatus.Denied:
+ return
+ elif permission_status == Qt.PermissionStatus.Granted:
+ print("[HeartRateGame] Bluetooth Permission Granted")
+
self.clearMessages()
self.m_deviceHandler.setDevice(None)
self.m_devices.clear()
diff --git a/examples/bluetooth/heartrate_game/devicehandler.py b/examples/bluetooth/heartrate_game/devicehandler.py
index 85d160c4d..df34052b8 100644
--- a/examples/bluetooth/heartrate_game/devicehandler.py
+++ b/examples/bluetooth/heartrate_game/devicehandler.py
@@ -105,7 +105,7 @@ class DeviceHandler(BluetoothBaseClass):
# Disconnect and delete old connection
if self.m_control:
self.m_control.disconnectFromDevice()
- m_control = None
+ self.m_control = None
# Create new controller and connect it if device available
if self.m_currentDevice:
@@ -166,7 +166,8 @@ class DeviceHandler(BluetoothBaseClass):
#! [Filter HeartRate service 2]
# If heartRateService found, create new service
if self.m_foundHeartRateService:
- self.m_service = self.m_control.createServiceObject(QBluetoothUuid(QBluetoothUuid.ServiceClassUuid.HeartRate), self)
+ self.m_service = self.m_control.createServiceObject(
+ QBluetoothUuid(QBluetoothUuid.ServiceClassUuid.HeartRate), self)
if self.m_service:
self.m_service.stateChanged.connect(self.serviceStateChanged)
@@ -185,9 +186,11 @@ class DeviceHandler(BluetoothBaseClass):
self.info = "Discovering services..."
elif switch == QLowEnergyService.RemoteServiceDiscovered:
self.info = "Service discovered."
- hrChar = self.m_service.characteristic(QBluetoothUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement))
+ hrChar = self.m_service.characteristic(
+ QBluetoothUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement))
if hrChar.isValid():
- self.m_notificationDesc = hrChar.descriptor(QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration)
+ self.m_notificationDesc = hrChar.descriptor(
+ QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration)
if self.m_notificationDesc.isValid():
self.m_service.writeDescriptor(self.m_notificationDesc,
QByteArray.fromHex(b"0100"))
@@ -233,7 +236,7 @@ class DeviceHandler(BluetoothBaseClass):
@Slot(QLowEnergyCharacteristic, QByteArray)
def confirmedDescriptorWrite(self, d, value):
if (d.isValid() and d == self.m_notificationDesc
- and value == QByteArray.fromHex(b"0000")):
+ and value == QByteArray.fromHex(b"0000")):
# disabled notifications . assume disconnect intent
self.m_control.disconnectFromDevice()
self.m_service = None
@@ -244,7 +247,7 @@ class DeviceHandler(BluetoothBaseClass):
# disable notifications
if (self.m_notificationDesc.isValid() and self.m_service
- and self.m_notificationDesc.value() == QByteArray.fromHex(b"0100")):
+ and self.m_notificationDesc.value() == QByteArray.fromHex(b"0100")):
self.m_service.writeDescriptor(self.m_notificationDesc,
QByteArray.fromHex(b"0000"))
else:
@@ -301,6 +304,6 @@ class DeviceHandler(BluetoothBaseClass):
self.m_sum += value
self.m_avg = float(self.m_sum) / len(self.m_measurements)
self.m_calories = ((-55.0969 + (0.6309 * self.m_avg) + (0.1988 * 94)
- + (0.2017 * 24)) / 4.184) * 60 * self.time / 3600
+ + (0.2017 * 24)) / 4.184) * 60 * self.time / 3600
self.statsChanged.emit()
diff --git a/examples/bluetooth/heartrate_game/doc/heartrate_game.rst b/examples/bluetooth/heartrate_game/doc/heartrate_game.rst
index 0a0938cad..9d190d991 100644
--- a/examples/bluetooth/heartrate_game/doc/heartrate_game.rst
+++ b/examples/bluetooth/heartrate_game/doc/heartrate_game.rst
@@ -1,6 +1,8 @@
Bluetooth Low Energy Heart Rate Game
====================================
+.. tags:: Android
+
The Bluetooth Low Energy Heart Rate Game shows how to develop a
Bluetooth Low Energy application using the Qt Bluetooth API. The
application covers the scanning for Bluetooth Low Energy devices,
diff --git a/examples/bluetooth/heartrate_game/heartrate_global.py b/examples/bluetooth/heartrate_game/heartrate_global.py
index b4c29bf4b..de5c37ac3 100644
--- a/examples/bluetooth/heartrate_game/heartrate_global.py
+++ b/examples/bluetooth/heartrate_game/heartrate_global.py
@@ -1,9 +1,8 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
+import os
import sys
-
_simulator = False
@@ -15,3 +14,17 @@ def simulator():
def set_simulator(s):
global _simulator
_simulator = s
+
+
+is_android = os.environ.get('ANDROID_ARGUMENT')
+
+
+def error_not_nuitka():
+ """Errors and exits for macOS if run in interpreted mode.
+ """
+ is_nuitka = "__compiled__" in globals()
+ if not is_nuitka and sys.platform == "darwin":
+ print("This example does not work on macOS when Python is run in interpreted mode."
+ "For this example to work on macOS, package the example using pyside6-deploy"
+ "For more information, read `Notes for Developer` in the documentation")
+ sys.exit(0)
diff --git a/examples/bluetooth/heartrate_server/heartrate_server.py b/examples/bluetooth/heartrate_server/heartrate_server.py
index f98cc6fe8..abbf4eb7f 100644
--- a/examples/bluetooth/heartrate_server/heartrate_server.py
+++ b/examples/bluetooth/heartrate_server/heartrate_server.py
@@ -39,8 +39,8 @@ if __name__ == '__main__':
char_data.setUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement)
char_data.setValue(QByteArray(2, 0))
char_data.setProperties(QLowEnergyCharacteristic.Notify)
- client_config = QLowEnergyDescriptorData(QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration,
- QByteArray(2, 0))
+ client_config = QLowEnergyDescriptorData(
+ QBluetoothUuid.DescriptorType.ClientCharacteristicConfiguration, QByteArray(2, 0))
char_data.addDescriptor(client_config)
service_data = QLowEnergyServiceData()
@@ -66,8 +66,9 @@ if __name__ == '__main__':
value = QByteArray()
value.append(chr(0)) # Flags that specify the format of the value.
value.append(chr(current_heart_rate)) # Actual value.
- characteristic = service.characteristic(QBluetoothUuid.CharacteristicType.HeartRateMeasurement)
- assert(characteristic.isValid())
+ characteristic = service.characteristic(
+ QBluetoothUuid.CharacteristicType.HeartRateMeasurement)
+ assert characteristic.isValid()
# Potentially causes notification.
service.writeCharacteristic(characteristic, value)
if current_heart_rate == 60:
diff --git a/examples/bluetooth/lowenergyscanner/characteristicinfo.py b/examples/bluetooth/lowenergyscanner/characteristicinfo.py
index a0e9df77e..42bde8753 100644
--- a/examples/bluetooth/lowenergyscanner/characteristicinfo.py
+++ b/examples/bluetooth/lowenergyscanner/characteristicinfo.py
@@ -85,4 +85,3 @@ class CharacteristicInfo(QObject):
def characteristic(self, characteristic):
self._characteristic = characteristic
self.characteristic_changed.emit()
-
diff --git a/examples/bluetooth/lowenergyscanner/device.py b/examples/bluetooth/lowenergyscanner/device.py
index e69a8450e..09108cf69 100644
--- a/examples/bluetooth/lowenergyscanner/device.py
+++ b/examples/bluetooth/lowenergyscanner/device.py
@@ -13,6 +13,7 @@ from characteristicinfo import CharacteristicInfo
QML_IMPORT_NAME = "Scanner"
QML_IMPORT_MAJOR_VERSION = 1
+
@QmlElement
@QmlSingleton
class Device(QObject):
@@ -275,5 +276,3 @@ class Device(QObject):
def stop_device_discovery(self):
if self.discovery_agent.isActive():
self.discovery_agent.stop()
-
-
diff --git a/examples/bluetooth/lowenergyscanner/deviceinfo.py b/examples/bluetooth/lowenergyscanner/deviceinfo.py
index edcbef89d..35a568821 100644
--- a/examples/bluetooth/lowenergyscanner/deviceinfo.py
+++ b/examples/bluetooth/lowenergyscanner/deviceinfo.py
@@ -32,4 +32,3 @@ class DeviceInfo(QObject):
def set_device(self, device):
self._device = device
self.device_changed.emit()
-
diff --git a/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst b/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst
index c20c2d8b7..a0c574350 100644
--- a/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst
+++ b/examples/bluetooth/lowenergyscanner/doc/lowenergyscanner.rst
@@ -1,6 +1,8 @@
Bluetooth Low Energy Scanner Example
====================================
+.. tags:: Android
+
A Python application that demonstrates the analogous example in Qt
`Bluetooth Low Energy Scanner <https://doc.qt.io/qt-6/qtbluetooth-lowenergyscanner-example.html>`_
diff --git a/examples/bluetooth/lowenergyscanner/main.py b/examples/bluetooth/lowenergyscanner/main.py
index dfbff97b3..ec12f99e7 100644
--- a/examples/bluetooth/lowenergyscanner/main.py
+++ b/examples/bluetooth/lowenergyscanner/main.py
@@ -10,7 +10,7 @@ from PySide6.QtCore import QCoreApplication
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
-from device import Device
+from device import Device # noqa: F401
from pathlib import Path
if __name__ == '__main__':
diff --git a/examples/bluetooth/lowenergyscanner/serviceinfo.py b/examples/bluetooth/lowenergyscanner/serviceinfo.py
index 092e9898f..cddffe663 100644
--- a/examples/bluetooth/lowenergyscanner/serviceinfo.py
+++ b/examples/bluetooth/lowenergyscanner/serviceinfo.py
@@ -62,5 +62,3 @@ class ServiceInfo(QObject):
@service.setter
def service(self, service):
self._service = service
-
-
diff --git a/examples/charts/callout/callout.py b/examples/charts/callout/callout.py
index 3de00b8df..622938d16 100644
--- a/examples/charts/callout/callout.py
+++ b/examples/charts/callout/callout.py
@@ -4,8 +4,8 @@
"""PySide6 port of the Callout example from Qt v5.x"""
import sys
-from PySide6.QtWidgets import (QApplication, QGraphicsScene,
- QGraphicsView, QGraphicsSimpleTextItem, QGraphicsItem)
+from PySide6.QtWidgets import (QApplication, QGraphicsScene, QGraphicsView,
+ QGraphicsSimpleTextItem, QGraphicsItem)
from PySide6.QtCore import Qt, QPointF, QRectF, QRect
from PySide6.QtCharts import QChart, QLineSeries, QSplineSeries
from PySide6.QtGui import QPainter, QFont, QFontMetrics, QPainterPath, QColor
@@ -42,37 +42,37 @@ class Callout(QGraphicsItem):
# establish the position of the anchor point in relation to _rect
above = anchor.y() <= self._rect.top()
- above_center = (anchor.y() > self._rect.top() and
- anchor.y() <= self._rect.center().y())
- below_center = (anchor.y() > self._rect.center().y() and
- anchor.y() <= self._rect.bottom())
+ above_center = (anchor.y() > self._rect.top()
+ and anchor.y() <= self._rect.center().y())
+ below_center = (anchor.y() > self._rect.center().y()
+ and anchor.y() <= self._rect.bottom())
below = anchor.y() > self._rect.bottom()
on_left = anchor.x() <= self._rect.left()
- left_of_center = (anchor.x() > self._rect.left() and
- anchor.x() <= self._rect.center().x())
- right_of_center = (anchor.x() > self._rect.center().x() and
- anchor.x() <= self._rect.right())
+ left_of_center = (anchor.x() > self._rect.left()
+ and anchor.x() <= self._rect.center().x())
+ right_of_center = (anchor.x() > self._rect.center().x()
+ and anchor.x() <= self._rect.right())
on_right = anchor.x() > self._rect.right()
# get the nearest _rect corner.
x = (on_right + right_of_center) * self._rect.width()
y = (below + below_center) * self._rect.height()
- corner_case = ((above and on_left) or (above and on_right) or
- (below and on_left) or (below and on_right))
+ corner_case = ((above and on_left) or (above and on_right)
+ or (below and on_left) or (below and on_right))
vertical = abs(anchor.x() - x) > abs(anchor.y() - y)
- x1 = (x + left_of_center * 10 - right_of_center * 20 + corner_case *
- int(not vertical) * (on_left * 10 - on_right * 20))
- y1 = (y + above_center * 10 - below_center * 20 + corner_case *
- vertical * (above * 10 - below * 20))
+ x1 = (x + left_of_center * 10 - right_of_center * 20 + corner_case
+ * int(not vertical) * (on_left * 10 - on_right * 20))
+ y1 = (y + above_center * 10 - below_center * 20 + corner_case
+ * vertical * (above * 10 - below * 20))
point1.setX(x1)
point1.setY(y1)
- x2 = (x + left_of_center * 20 - right_of_center * 10 + corner_case *
- int(not vertical) * (on_left * 20 - on_right * 10))
- y2 = (y + above_center * 20 - below_center * 10 + corner_case *
- vertical * (above * 20 - below * 10))
+ x2 = (x + left_of_center * 20 - right_of_center * 10 + corner_case
+ * int(not vertical) * (on_left * 20 - on_right * 10))
+ y2 = (y + above_center * 20 - below_center * 10 + corner_case
+ * vertical * (above * 20 - below * 10))
point2.setX(x2)
point2.setY(y2)
@@ -90,7 +90,7 @@ class Callout(QGraphicsItem):
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
- self.setPos(mapToParent(
+ self.setPos(self.mapToParent(
event.pos() - event.buttonDownPos(Qt.LeftButton)))
event.setAccepted(True)
else:
@@ -127,7 +127,7 @@ class View(QGraphicsView):
self._chart = QChart()
self._chart.setMinimumSize(640, 480)
self._chart.setTitle("Hover the line to show callout. Click the line "
- "to make it stay")
+ "to make it stay")
self._chart.legend().hide()
self.series = QLineSeries()
self.series.append(1, 3)
diff --git a/examples/charts/chartthemes/main.py b/examples/charts/chartthemes/main.py
index 8b023f927..5787710ca 100644
--- a/examples/charts/chartthemes/main.py
+++ b/examples/charts/chartthemes/main.py
@@ -7,7 +7,7 @@ import sys
from PySide6.QtCore import QPointF, Qt
from PySide6.QtGui import QColor, QPainter, QPalette
from PySide6.QtWidgets import (QApplication, QMainWindow, QSizePolicy,
- QWidget)
+ QWidget)
from PySide6.QtCharts import (QAreaSeries, QBarSet, QChart, QChartView,
QLineSeries, QPieSeries, QScatterSeries,
QSplineSeries, QStackedBarSeries)
@@ -27,7 +27,7 @@ class ThemeWidget(QWidget):
self.value_max = 10
self.value_count = 7
self.data_table = self.generate_random_data(self.list_count,
- self.value_max, self.value_count)
+ self.value_max, self.value_count)
self.ui.setupUi(self)
self.populate_themebox()
@@ -41,8 +41,7 @@ class ThemeWidget(QWidget):
# Pie Chart
chart_view = QChartView(self.create_pie_chart())
- chart_view.setSizePolicy(QSizePolicy.Ignored,
- QSizePolicy.Ignored)
+ chart_view.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.ui.gridLayout.addWidget(chart_view, 1, 1)
self.charts.append(chart_view)
@@ -70,10 +69,10 @@ class ThemeWidget(QWidget):
self.ui.antialiasCheckBox.setChecked(True)
# Set the colors from the light theme as default ones
- pal = qApp.palette()
+ pal = qApp.palette() # noqa: F821
pal.setColor(QPalette.Window, QColor(0xf0f0f0))
pal.setColor(QPalette.WindowText, QColor(0x404044))
- qApp.setPalette(pal)
+ qApp.setPalette(pal) # noqa: F821
self.update_ui()
@@ -271,26 +270,7 @@ class ThemeWidget(QWidget):
chart_theme = self.charts[0].chart().theme()
if chart_theme != theme:
for chart_view in self.charts:
- if theme == 0:
- theme_name = QChart.ChartThemeLight
- elif theme == 1:
- theme_name = QChart.ChartThemeBlueCerulean
- elif theme == 2:
- theme_name = QChart.ChartThemeDark
- elif theme == 3:
- theme_name = QChart.ChartThemeBrownSand
- elif theme == 4:
- theme_name = QChart.ChartThemeBlueNcs
- elif theme == 5:
- theme_name = QChart.ChartThemeHighContrast
- elif theme == 6:
- theme_name = QChart.ChartThemeBlueIcy
- elif theme == 7:
- theme_name = QChart.ChartThemeQt
- else:
- theme_name = QChart.ChartThemeLight
-
- chart_view.chart().setTheme(theme_name)
+ chart_view.chart().setTheme(theme)
# Set palette colors based on selected theme
if theme == QChart.ChartThemeLight:
@@ -320,20 +300,10 @@ class ThemeWidget(QWidget):
options = self.ui.animatedComboBox.itemData(idx)
if len(self.charts):
- chart = self.charts[0].chart()
- animation_options = chart.animationOptions()
+ animation_options = self.charts[0].chart().animationOptions()
if animation_options != options:
for chart_view in self.charts:
- options_name = QChart.NoAnimation
- if options == 0:
- options_name = QChart.NoAnimation
- elif options == 1:
- options_name = QChart.GridAxisAnimations
- elif options == 2:
- options_name = QChart.SeriesAnimations
- elif options == 3:
- options_name = QChart.AllAnimations
- chart_view.chart().setAnimationOptions(options_name)
+ chart_view.chart().setAnimationOptions(options)
# Update legend alignment
idx = self.ui.legendComboBox.currentIndex()
diff --git a/examples/charts/chartthemes/ui_themewidget.py b/examples/charts/chartthemes/ui_themewidget.py
index 231813a67..711615b3e 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -64,7 +64,7 @@ class Ui_ThemeWidgetForm(object):
self.horizontalLayout.addWidget(self.antialiasCheckBox)
- self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
diff --git a/examples/charts/donutbreakdown/donutbreakdown.py b/examples/charts/donutbreakdown/donutbreakdown.py
index 1597685c2..9111d4aac 100644
--- a/examples/charts/donutbreakdown/donutbreakdown.py
+++ b/examples/charts/donutbreakdown/donutbreakdown.py
@@ -38,7 +38,7 @@ class MainSlice(QPieSlice):
class DonutBreakdownChart(QChart):
def __init__(self, parent=None):
super().__init__(QChart.ChartTypeCartesian,
- parent, Qt.WindowFlags())
+ parent, Qt.WindowFlags())
self.main_series = QPieSeries()
self.main_series.setPieSize(0.7)
self.addSeries(self.main_series)
diff --git a/examples/charts/legend/legend.py b/examples/charts/legend/legend.py
index f1929547d..5417a940f 100644
--- a/examples/charts/legend/legend.py
+++ b/examples/charts/legend/legend.py
@@ -7,7 +7,8 @@ import sys
from PySide6.QtCore import Qt, QRectF, Slot
from PySide6.QtGui import QBrush, QColor, QPainter, QPen
from PySide6.QtWidgets import (QApplication, QDoubleSpinBox,
- QFormLayout, QGridLayout, QGroupBox, QPushButton, QWidget)
+ QFormLayout, QGridLayout, QGroupBox,
+ QPushButton, QWidget)
from PySide6.QtCharts import QBarSeries, QBarSet, QChart, QChartView
@@ -206,10 +207,8 @@ class MainWidget(QWidget):
def update_legend_layout(self):
legend = self.chart.legend()
- rect = QRectF(self.legend_posx.value(),
- self.legend_posy.value(),
- self.legend_width.value(),
- self.legend_height.value())
+ rect = QRectF(self.legend_posx.value(), self.legend_posy.value(),
+ self.legend_width.value(), self.legend_height.value())
legend.setGeometry(rect)
legend.update()
diff --git a/examples/charts/memoryusage/memoryusage.py b/examples/charts/memoryusage/memoryusage.py
index d0bf842e5..3ba42368e 100644
--- a/examples/charts/memoryusage/memoryusage.py
+++ b/examples/charts/memoryusage/memoryusage.py
@@ -54,7 +54,7 @@ def get_memory_usage():
legend = f'{command} {memory_usage}%'
result.append([legend, memory_usage])
- result.sort(key = lambda x: x[1], reverse=True)
+ result.sort(key=lambda x: x[1], reverse=True)
return result
diff --git a/examples/charts/modeldata/modeldata.py b/examples/charts/modeldata/modeldata.py
index 646021037..0e36f7770 100644
--- a/examples/charts/modeldata/modeldata.py
+++ b/examples/charts/modeldata/modeldata.py
@@ -9,7 +9,7 @@ from random import randrange
from PySide6.QtCore import QAbstractTableModel, QModelIndex, QRect, Qt
from PySide6.QtGui import QColor, QPainter
from PySide6.QtWidgets import (QApplication, QGridLayout, QHeaderView,
- QTableView, QWidget)
+ QTableView, QWidget)
from PySide6.QtCharts import QChart, QChartView, QLineSeries, QVXYModelMapper
@@ -101,9 +101,6 @@ class TableWidget(QWidget):
self.mapper.setModel(self.model)
self.chart.addSeries(self.series)
- # for storing color hex from the series
- seriesColorHex = "#000000"
-
# get the color of the series and use it for showing the mapped area
self.model.add_mapping(self.series.pen().color().name(),
QRect(0, 0, 2, self.model.rowCount()))
diff --git a/examples/charts/percentbarchart/percentbarchart.py b/examples/charts/percentbarchart/percentbarchart.py
index cfb11800a..9f70c0328 100644
--- a/examples/charts/percentbarchart/percentbarchart.py
+++ b/examples/charts/percentbarchart/percentbarchart.py
@@ -21,11 +21,11 @@ class MainWindow(QMainWindow):
set3 = QBarSet("Mary")
set4 = QBarSet("Samantha")
- set0.append([1, 2, 3, 4, 5, 6])
- set1.append([5, 0, 0, 4, 0, 7])
+ set0.append([1, 2, 3, 4, 5, 6])
+ set1.append([5, 0, 0, 4, 0, 7])
set2.append([3, 5, 8, 13, 8, 5])
- set3.append([5, 6, 7, 3, 4, 5])
- set4.append([9, 7, 5, 3, 1, 2])
+ set3.append([5, 6, 7, 3, 4, 5])
+ set4.append([9, 7, 5, 3, 1, 2])
series = QPercentBarSeries()
series.append(set0)
diff --git a/examples/charts/pointconfiguration/chartwindow.py b/examples/charts/pointconfiguration/chartwindow.py
index 055802b9f..36b10aa16 100644
--- a/examples/charts/pointconfiguration/chartwindow.py
+++ b/examples/charts/pointconfiguration/chartwindow.py
@@ -109,14 +109,14 @@ class ChartWindow(QMainWindow):
self._selectedPointConfig = self._series.pointConfiguration(index)
selected_point = self._series.at(index)
selected_index_lineedit = self._selected_point_index_lineedit
- selected_index_lineedit.setText("(" + str(selected_point.x()) + ", " +
- str(selected_point.y()) + ")")
+ selected_index_lineedit.setText("(" + str(selected_point.x()) + ", "
+ + str(selected_point.y()) + ")")
config = self._series.pointConfiguration(index)
color = config.get(PointConfig.Color) or self._series.color()
size = config.get(PointConfig.Size) or self._series.markerSize()
- labelVisibility = (config.get(PointConfig.LabelVisibility) or
- self._series.pointLabelsVisible())
+ labelVisibility = (config.get(PointConfig.LabelVisibility)
+ or self._series.pointLabelsVisible())
customLabel = config.get(PointConfig.LabelFormat) or ""
combobox_value_list = [
diff --git a/examples/charts/pointselectionandmarkers/doc/pointselectionandmarkers.rst b/examples/charts/pointselectionandmarkers/doc/pointselectionandmarkers.rst
index cf8b9a264..e8776daf8 100644
--- a/examples/charts/pointselectionandmarkers/doc/pointselectionandmarkers.rst
+++ b/examples/charts/pointselectionandmarkers/doc/pointselectionandmarkers.rst
@@ -18,7 +18,7 @@ Creating the chart and its elements
We start by creating a series, filling it with the data, and enabling the light marker and point selection features.
It is important not to set points visibility to :py:`True`, because light markers functionality is an independent feature and setting both would result in undesired behavior.
-.. literalinclude:: ../../../../examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+.. literalinclude:: pointselectionandmarkers.py
:linenos:
:lineno-start: 20
:lines: 20-42
@@ -26,7 +26,7 @@ It is important not to set points visibility to :py:`True`, because light marker
Then we create the :py:`QChart`, the :py:`QChartview` and the control widget with its layout to arrange customization elements.
-.. literalinclude:: ../../../../examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+.. literalinclude:: pointselectionandmarkers.py
:lineno-start: 44
:lines: 44-53
:emphasize-lines: 1,6,9
@@ -36,7 +36,7 @@ Creating UI for configuring the chart
The next step is where we create user interface elements that allow customizing the chart, including setting light marker and selection marker images.
-.. literalinclude:: ../../../../examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+.. literalinclude:: pointselectionandmarkers.py
:linenos:
:lineno-start: 54
:lines: 54-57
@@ -44,7 +44,7 @@ The next step is where we create user interface elements that allow customizing
We create the label for the marker selection combobox and fill the combobox with the items. We then provide functionality to the combobox, allowing the user's selection to set the desired light marker image. As light markers are enabled and disabled by setting a valid QImage or setting an empty :py:`QImage()`, we need to make sure that if the user does not wish unselected points to be displayed, we do not actually set the light marker image.
If checking isn't performed, a new :py:`QImage` will be set as the light marker and unselected points will be visible even though it has been switched off.
-.. literalinclude:: ../../../../examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+.. literalinclude:: pointselectionandmarkers.py
:linenos:
:lineno-start: 59
:lines: 59-67
@@ -52,14 +52,14 @@ If checking isn't performed, a new :py:`QImage` will be set as the light marker
Almost the same procedure applies to the selected point light marker and line color. The only difference is that there is no need to check the visibility of unselected points as it doesn't affect the functionality.
-.. literalinclude:: ../../../../examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+.. literalinclude:: pointselectionandmarkers.py
:linenos:
:lineno-start: 70
:lines: 70-85
A small difference comes with changing visibility of unselected points. As it was mentioned before, making light markers invisible is achieved by setting the light marker to an empty :py:`QImage()`. That is why, depending on checkbox state, selected point light marker is set to an empty :py:`QImage` or to the light marker extracted from the current index of the corresponding combobox.
-.. literalinclude:: ../../../../examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+.. literalinclude:: pointselectionandmarkers.py
:linenos:
:lineno-start: 88
:lines: 88-97
diff --git a/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py b/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
index 4f9540d42..df7b61687 100644
--- a/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
+++ b/examples/charts/pointselectionandmarkers/pointselectionandmarkers.py
@@ -7,7 +7,8 @@ import sys
from PySide6.QtCore import Slot, QPointF, Qt
from PySide6.QtCharts import QChart, QChartView, QSplineSeries
from PySide6.QtGui import QPainter, QImage
-from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QComboBox, QCheckBox, QLabel, QHBoxLayout
+from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QGridLayout,
+ QComboBox, QCheckBox, QLabel, QHBoxLayout)
import utilities as Utilities
@@ -20,12 +21,12 @@ if __name__ == "__main__":
marker_size = 20.
series = QSplineSeries()
series.append([QPointF(0, 0),
- QPointF(0.5, 2.27),
- QPointF(1.5, 2.2),
- QPointF(3.3, 1.7),
- QPointF(4.23, 3.1),
- QPointF(5.3, 2.3),
- QPointF(6.47, 4.1)])
+ QPointF(0.5, 2.27),
+ QPointF(1.5, 2.2),
+ QPointF(3.3, 1.7),
+ QPointF(4.23, 3.1),
+ QPointF(5.3, 2.3),
+ QPointF(6.47, 4.1)])
series.setMarkerSize(marker_size)
series.setLightMarker(Utilities.default_light_marker(marker_size))
series.setSelectedLightMarker(Utilities.default_selected_light_marker(marker_size))
@@ -66,16 +67,16 @@ if __name__ == "__main__":
char_point_combobox.addItems(["Red rectangle", "Green triangle", "Orange circle"])
char_point_combobox.currentIndexChanged.connect(set_light_marker)
-
@Slot(int)
def set_selected_light_marker(index):
- series.setSelectedLightMarker(Utilities.get_selected_point_representation(Utilities.selected_point_type(index), marker_size))
+ series.setSelectedLightMarker(
+ Utilities.get_selected_point_representation(
+ Utilities.selected_point_type(index), marker_size))
char_point_selected = QLabel("Char point selected: ")
char_point_selected_combobox.addItems(["Blue triangle", "Yellow rectangle", "Lavender circle"])
char_point_selected_combobox.currentIndexChanged.connect(set_selected_light_marker)
-
@Slot(int)
def set_line_color(index):
series.setColor(Utilities.make_line_color(Utilities.line_color(index)))
@@ -84,11 +85,12 @@ if __name__ == "__main__":
line_color_combobox.addItems(["Blue", "Black", "Mint"])
line_color_combobox.currentIndexChanged.connect(set_line_color)
-
@Slot(int)
def display_unselected_points(checkbox_state):
if checkbox_state:
- series.setLightMarker(Utilities.get_point_representation(Utilities.point_type(char_point_combobox.currentIndex()), marker_size))
+ series.setLightMarker(
+ Utilities.get_point_representation(
+ Utilities.point_type(char_point_combobox.currentIndex()), marker_size))
else:
series.setLightMarker(QImage())
@@ -96,7 +98,6 @@ if __name__ == "__main__":
show_unselected_points_checkbox.setChecked(True)
show_unselected_points_checkbox.stateChanged.connect(display_unselected_points)
-
control_label = QLabel("Marker and Selection Controls")
control_label.setAlignment(Qt.AlignHCenter)
control_label_font = control_label.font()
diff --git a/examples/charts/pointselectionandmarkers/utilities.py b/examples/charts/pointselectionandmarkers/utilities.py
index 6b96d6e26..b27a2542b 100644
--- a/examples/charts/pointselectionandmarkers/utilities.py
+++ b/examples/charts/pointselectionandmarkers/utilities.py
@@ -4,7 +4,8 @@
from PySide6.QtGui import QImage, QPainter, QColor
from PySide6.QtCore import Qt
-import rc_markers
+import rc_markers # noqa: F401
+
def rectangle(point_type, image_size):
image = QImage(image_size, image_size, QImage.Format_RGB32)
@@ -15,9 +16,11 @@ def rectangle(point_type, image_size):
painter.end()
return image
+
def triangle(point_type, image_size):
return QImage(point_type[3]).scaled(image_size, image_size)
+
def circle(point_type, image_size):
image = QImage(image_size, image_size, QImage.Format_ARGB32)
image.fill(QColor(0, 0, 0, 0))
@@ -32,6 +35,7 @@ def circle(point_type, image_size):
painter.end()
return image
+
_point_types = [("RedRectangle", rectangle, Qt.red),
("GreenTriangle", triangle, Qt.green, ":/images/green_triangle.png"),
("OrangeCircle", circle, QColor(255, 127, 80))]
@@ -40,12 +44,15 @@ _selected_point_types = [("BlueTriangle", triangle, Qt.blue, ":/images/blue_tria
("LavenderCircle", circle, QColor(147, 112, 219))]
_line_colors = [("Blue", QColor(65, 105, 225)), ("Black", Qt.black), ("Mint", QColor(70, 203, 155))]
+
def point_type(index):
return _point_types[index]
+
def selected_point_type(index):
return _selected_point_types[index]
+
def line_color(index):
return _line_colors[index]
@@ -53,6 +60,7 @@ def line_color(index):
def default_light_marker(image_size):
return rectangle(_point_types[0], image_size)
+
def default_selected_light_marker(image_size):
return triangle(_selected_point_types[0], image_size)
@@ -60,8 +68,10 @@ def default_selected_light_marker(image_size):
def get_point_representation(point_type, image_size):
return point_type[1](point_type, image_size)
+
def get_selected_point_representation(point_type, image_size):
return point_type[1](point_type, image_size)
+
def make_line_color(line_color):
return line_color[1]
diff --git a/examples/corelib/ipc/sharedmemory/dialog.py b/examples/corelib/ipc/sharedmemory/dialog.py
index c374f0119..134900047 100644
--- a/examples/corelib/ipc/sharedmemory/dialog.py
+++ b/examples/corelib/ipc/sharedmemory/dialog.py
@@ -90,4 +90,4 @@ class Dialog(QDialog):
def detach(self):
if not self._shared_memory.detach():
- self.ui.label.setText(tr("Unable to detach from shared memory."))
+ self.ui.label.setText(self.tr("Unable to detach from shared memory.")) # noqa: F821
diff --git a/examples/corelib/ipc/sharedmemory/ui_dialog.py b/examples/corelib/ipc/sharedmemory/ui_dialog.py
index 035b60d68..2cd544f40 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
diff --git a/examples/corelib/settingseditor/settingseditor.py b/examples/corelib/settingseditor/settingseditor.py
index a77349e5e..f87a2f4b5 100644
--- a/examples/corelib/settingseditor/settingseditor.py
+++ b/examples/corelib/settingseditor/settingseditor.py
@@ -7,14 +7,20 @@
import sys
from PySide6.QtCore import (QByteArray, QDate, QDateTime, QDir, QEvent, QPoint,
- QRect, QRegularExpression, QSettings, QSize, QTime, QTimer, Qt, Slot)
+ QRect, QRegularExpression, QSettings, QSize, QTime,
+ QTimer, Qt, Slot)
from PySide6.QtGui import (QAction, QColor, QIcon, QIntValidator,
- QDoubleValidator, QRegularExpressionValidator, QValidator)
+ QDoubleValidator, QRegularExpressionValidator,
+ QValidator)
from PySide6.QtWidgets import (QAbstractItemView, QApplication,
- QCheckBox, QComboBox, QFileDialog, QDialog, QDialogButtonBox, QGridLayout,
- QGroupBox, QHeaderView, QInputDialog, QItemDelegate, QLabel, QLineEdit,
- QMainWindow, QMessageBox, QStyle, QSpinBox, QStyleOptionViewItem,
- QTableWidget, QTableWidgetItem, QTreeWidget, QTreeWidgetItem, QVBoxLayout)
+ QCheckBox, QComboBox, QFileDialog, QDialog,
+ QDialogButtonBox, QGridLayout,
+ QGroupBox, QHeaderView, QInputDialog,
+ QItemDelegate, QLabel, QLineEdit,
+ QMainWindow, QMessageBox, QStyle, QSpinBox,
+ QStyleOptionViewItem, QTableWidget,
+ QTableWidgetItem, QTreeWidget, QTreeWidgetItem,
+ QVBoxLayout)
class TypeChecker:
@@ -160,7 +166,7 @@ class MainWindow(QMainWindow):
@Slot()
def open_inifile(self):
file_name, _ = QFileDialog.getOpenFileName(self, "Open INI File",
- '', "INI Files (*.ini *.conf)")
+ '', "INI Files (*.ini *.conf)")
if file_name:
self.load_ini_file(file_name)
@@ -175,7 +181,8 @@ class MainWindow(QMainWindow):
@Slot()
def open_property_list(self):
file_name, _ = QFileDialog.getOpenFileName(self,
- "Open Property List", '', "Property List Files (*.plist)")
+ "Open Property List", '',
+ "Property List Files (*.plist)")
if file_name:
settings = QSettings(file_name, QSettings.NativeFormat)
@@ -185,8 +192,8 @@ class MainWindow(QMainWindow):
@Slot()
def open_registry_path(self):
path, ok = QInputDialog.getText(self, "Open Registry Path",
- "Enter the path in the Windows registry:",
- QLineEdit.Normal, 'HKEY_CURRENT_USER\\')
+ "Enter the path in the Windows registry:",
+ QLineEdit.Normal, 'HKEY_CURRENT_USER\\')
if ok and path != '':
settings = QSettings(path, QSettings.NativeFormat)
@@ -196,56 +203,46 @@ class MainWindow(QMainWindow):
@Slot()
def about(self):
QMessageBox.about(self, "About Settings Editor",
- "The <b>Settings Editor</b> example shows how to access "
- "application settings using Qt.")
-
- def create_actions(self):
- self._open_settings_act = QAction("&Open Application Settings...",
- self, shortcut="Ctrl+O", triggered=self.openSettings)
-
- self._open_ini_file_act = QAction("Open I&NI File...", self,
- shortcut="Ctrl+N", triggered=self.openIniFile)
-
- self._open_property_list_act = QAction("Open macOS &Property List...",
- self, shortcut="Ctrl+P", triggered=self.openPropertyList)
+ "The <b>Settings Editor</b> example shows how to access "
+ "application settings using Qt.")
def create_actions(self):
self.open_settings_action = QAction("&Open Application Settings...",
- self, shortcut="Ctrl+O", triggered=self.open_settings)
+ self, shortcut="Ctrl+O", triggered=self.open_settings)
self.open_ini_file_action = QAction("Open I&NI File...", self,
- shortcut="Ctrl+N", triggered=self.open_inifile)
+ shortcut="Ctrl+N", triggered=self.open_inifile)
- self.open_property_list_action = QAction("Open macOS &Property List...",
- self, shortcut="Ctrl+P", triggered=self.open_property_list)
+ self.open_property_list_action = QAction("Open macOS &Property List...", self,
+ shortcut="Ctrl+P",
+ triggered=self.open_property_list)
if sys.platform != 'darwin':
self.open_property_list_action.setEnabled(False)
self.open_registry_path_action = QAction(
- "Open Windows &Registry Path...", self, shortcut="Ctrl+G",
- triggered=self.open_registry_path)
+ "Open Windows &Registry Path...", self, shortcut="Ctrl+G",
+ triggered=self.open_registry_path)
if sys.platform != 'win32':
self.open_registry_path_action.setEnabled(False)
self.refresh_action = QAction("&Refresh", self, shortcut="Ctrl+R",
- enabled=False, triggered=self.settings_tree.refresh)
+ enabled=False, triggered=self.settings_tree.refresh)
- self.exit_action = QAction("E&xit", self, shortcut="Ctrl+Q",
- triggered=self.close)
+ self.exit_action = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close)
self.auto_refresh_action = QAction("&Auto-Refresh", self,
- shortcut="Ctrl+A", checkable=True, enabled=False)
+ shortcut="Ctrl+A", checkable=True, enabled=False)
self.auto_refresh_action.triggered[bool].connect(self.settings_tree.set_auto_refresh)
self.auto_refresh_action.triggered[bool].connect(self.refresh_action.setDisabled)
self.fallbacks_action = QAction("&Fallbacks", self,
- shortcut="Ctrl+F", checkable=True, enabled=False)
+ shortcut="Ctrl+F", checkable=True, enabled=False)
self.fallbacks_action.triggered[bool].connect(self.settings_tree.set_fallbacks_enabled)
self.about_action = QAction("&About", self, triggered=self.about)
self.about_Qt_action = QAction("About &Qt", self,
- triggered=qApp.aboutQt)
+ triggered=qApp.aboutQt) # noqa: F821
def create_menus(self):
self.file_menu = self.menuBar().addMenu("&File")
@@ -508,7 +505,7 @@ class SettingsTree(QTreeWidget):
# The signal might not be connected.
try:
self.itemChanged.disconnect(self.update_setting)
- except:
+ except Exception:
pass
self.settings.sync()
@@ -531,7 +528,6 @@ class SettingsTree(QTreeWidget):
key = ancestor.text(0) + '/' + key
ancestor = ancestor.parent()
- d = item.data(2, Qt.UserRole)
self.settings.setValue(key, item.data(2, Qt.UserRole))
if self.auto_refresh:
@@ -703,7 +699,7 @@ class VariantDelegate(QItemDelegate):
value = editor.value()
else:
value = self.value_from_lineedit(editor, model, index)
- if not value is None:
+ if value is not None:
model.setData(index, value, Qt.UserRole)
model.setData(index, self.display_text(value), Qt.DisplayRole)
diff --git a/examples/corelib/threads/mandelbrot.py b/examples/corelib/threads/mandelbrot.py
index b8cab06ef..4689813d4 100644
--- a/examples/corelib/threads/mandelbrot.py
+++ b/examples/corelib/threads/mandelbrot.py
@@ -30,7 +30,7 @@ INFO_KEY = 'info'
HELP = ("Use mouse wheel or the '+' and '-' keys to zoom. Press and "
- "hold left mouse button to scroll.")
+ "hold left mouse button to scroll.")
class RenderThread(QThread):
@@ -53,7 +53,8 @@ class RenderThread(QThread):
self.abort = False
for i in range(RenderThread.colormap_size):
- self.colormap.append(self.rgb_from_wave_length(380.0 + (i * 400.0 / RenderThread.colormap_size)))
+ self.colormap.append(
+ self.rgb_from_wave_length(380.0 + (i * 400.0 / RenderThread.colormap_size)))
def stop(self):
self.mutex.lock()
@@ -132,7 +133,8 @@ class RenderThread(QThread):
if num_iterations < max_iterations:
image.setPixel(x + half_width, y + half_height,
- self.colormap[num_iterations % RenderThread.colormap_size])
+ self.colormap[
+ num_iterations % RenderThread.colormap_size])
all_black = False
else:
image.setPixel(x + half_width, y + half_height, qRgb(0, 0, 0))
@@ -221,7 +223,7 @@ class MandelbrotWidget(QWidget):
if self.pixmap.isNull():
painter.setPen(Qt.white)
painter.drawText(self.rect(), Qt.AlignCenter,
- "Rendering initial image, please wait...")
+ "Rendering initial image, please wait...")
return
if self._cur_scale == self._pixmap_scale:
@@ -250,10 +252,10 @@ class MandelbrotWidget(QWidget):
painter.setPen(Qt.NoPen)
painter.setBrush(QColor(0, 0, 0, 127))
painter.drawRect((self.width() - text_width) / 2 - 5, 0, text_width + 10,
- metrics.lineSpacing() + 5)
+ metrics.lineSpacing() + 5)
painter.setPen(Qt.white)
painter.drawText((self.width() - text_width) / 2,
- metrics.leading() + metrics.ascent(), text)
+ metrics.leading() + metrics.ascent(), text)
def resizeEvent(self, event):
self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size())
@@ -302,7 +304,7 @@ class MandelbrotWidget(QWidget):
delta_y = (self.height() - self.pixmap.height()) / 2 - self._pixmap_offset.y()
self.scroll(delta_x, delta_y)
- @Slot(QImage,float)
+ @Slot(QImage, float)
def update_pixmap(self, image, scale_factor):
if not self._last_drag_pos.isNull():
return
@@ -317,15 +319,13 @@ class MandelbrotWidget(QWidget):
def zoom(self, zoomFactor):
self._cur_scale *= zoomFactor
self.update()
- self.thread.render(self._center_x, self._center_y, self._cur_scale,
- self.size())
+ self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size())
def scroll(self, deltaX, deltaY):
self._center_x += deltaX * self._cur_scale
self._center_y += deltaY * self._cur_scale
self.update()
- self.thread.render(self._center_x, self._center_y, self._cur_scale,
- self.size())
+ self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size())
if __name__ == '__main__':
diff --git a/examples/datavisualization/graphgallery/bargraph.py b/examples/datavisualization/graphgallery/bargraph.py
index 6c61d6708..7938a5ca1 100644
--- a/examples/datavisualization/graphgallery/bargraph.py
+++ b/examples/datavisualization/graphgallery/bargraph.py
@@ -8,8 +8,7 @@ from PySide6.QtGui import QFont
from PySide6.QtWidgets import (QButtonGroup, QCheckBox, QComboBox, QFontComboBox,
QLabel, QPushButton, QHBoxLayout, QSizePolicy,
QRadioButton, QSlider, QVBoxLayout, QWidget)
-from PySide6.QtDataVisualization import (QAbstract3DGraph, QAbstract3DSeries,
- Q3DBars)
+from PySide6.QtDataVisualization import (QAbstract3DGraph, QAbstract3DSeries, Q3DBars)
class BarGraph(QObject):
@@ -92,18 +91,14 @@ class BarGraph(QObject):
selectionModeList.addItem("Slice into Row and Item", sel)
sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionColumn
selectionModeList.addItem("Slice into Column", sel)
- sel = (QAbstract3DGraph.SelectionSlice
- | QAbstract3DGraph.SelectionItemAndColumn)
+ sel = (QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndColumn)
selectionModeList.addItem("Slice into Column and Item", sel)
- sel = (QAbstract3DGraph.SelectionItemRowAndColumn
- | QAbstract3DGraph.SelectionMultiSeries)
+ sel = (QAbstract3DGraph.SelectionItemRowAndColumn | QAbstract3DGraph.SelectionMultiSeries)
selectionModeList.addItem("Multi: Bar, Row, Col", sel)
- sel = (QAbstract3DGraph.SelectionSlice
- | QAbstract3DGraph.SelectionItemAndRow
+ sel = (QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndRow
| QAbstract3DGraph.SelectionMultiSeries)
selectionModeList.addItem("Multi, Slice: Row, Item", sel)
- sel = (QAbstract3DGraph.SelectionSlice
- | QAbstract3DGraph.SelectionItemAndColumn
+ sel = (QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndColumn
| QAbstract3DGraph.SelectionMultiSeries)
selectionModeList.addItem("Multi, Slice: Col, Item", sel)
selectionModeList.setCurrentIndex(1)
diff --git a/examples/datavisualization/graphgallery/main.py b/examples/datavisualization/graphgallery/main.py
index 668117245..4b57b85dd 100644
--- a/examples/datavisualization/graphgallery/main.py
+++ b/examples/datavisualization/graphgallery/main.py
@@ -34,8 +34,8 @@ if __name__ == "__main__":
surface = SurfaceGraph()
if (not bars.initialize(minimum_graph_size, screen_size)
- or not scatter.initialize(minimum_graph_size, screen_size)
- or not surface.initialize(minimum_graph_size, screen_size)):
+ or not scatter.initialize(minimum_graph_size, screen_size)
+ or not surface.initialize(minimum_graph_size, screen_size)):
QMessageBox.warning(None, "Graph Gallery", "Couldn't initialize the OpenGL context.")
sys.exit(-1)
diff --git a/examples/datavisualization/graphgallery/variantbardataproxy.py b/examples/datavisualization/graphgallery/variantbardataproxy.py
index 2b6f5d84e..f69ebaf80 100644
--- a/examples/datavisualization/graphgallery/variantbardataproxy.py
+++ b/examples/datavisualization/graphgallery/variantbardataproxy.py
@@ -64,8 +64,8 @@ class VariantBarDataProxy(QBarDataProxy):
# If we have no data or mapping, or the categories are not defined,
# simply clear the array
if (not self._dataSet or not self._mapping
- or not self._mapping.rowCategories()
- or not self._mapping.columnCategories()):
+ or not self._mapping.rowCategories()
+ or not self._mapping.columnCategories()):
self.resetArray()
return
diff --git a/examples/datavisualization/qmlsurfacegallery/main.py b/examples/datavisualization/qmlsurfacegallery/main.py
index a02e060cd..7b980bc17 100644
--- a/examples/datavisualization/qmlsurfacegallery/main.py
+++ b/examples/datavisualization/qmlsurfacegallery/main.py
@@ -12,8 +12,8 @@ from PySide6.QtGui import QGuiApplication
from PySide6.QtQuick import QQuickView
from PySide6.QtDataVisualization import qDefaultSurfaceFormat
-from datasource import DataSource
-import rc_qmlsurfacegallery
+from datasource import DataSource # noqa: F401
+import rc_qmlsurfacegallery # noqa: F401
if __name__ == "__main__":
diff --git a/examples/dbus/pingpong/ping.py b/examples/dbus/pingpong/ping.py
index 8dd741c0e..d61f25499 100644
--- a/examples/dbus/pingpong/ping.py
+++ b/examples/dbus/pingpong/ping.py
@@ -5,7 +5,7 @@
import sys
from PySide6.QtCore import QCoreApplication
-from PySide6.QtDBus import QDBusConnection, QDBusInterface, QDBusReply
+from PySide6.QtDBus import QDBusConnection, QDBusInterface, QDBusReply
SERVICE_NAME = 'org.example.QtDBus.PingExample'
@@ -36,4 +36,3 @@ if __name__ == "__main__":
value = reply.value()
print(f'ping: Reply was: {value}')
sys.exit(0)
-
diff --git a/examples/dbus/pingpong/pong.py b/examples/dbus/pingpong/pong.py
index cdb610c50..0dec6eda0 100644
--- a/examples/dbus/pingpong/pong.py
+++ b/examples/dbus/pingpong/pong.py
@@ -18,7 +18,7 @@ class Pong(QObject):
@Slot(str, result=str)
def ping(self, arg):
print(f'pong: Received ping({arg})')
- qApp.quit()
+ qApp.quit() # noqa: F821
return f'ping("{arg}") got called'
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml
new file mode 100644
index 000000000..0fd26e4d0
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml
@@ -0,0 +1,71 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import QtExampleStyle
+
+Popup {
+ id: colorDeleter
+ padding: 10
+ modal: true
+ focus: true
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+ signal deleteClicked(int cid)
+
+ property int colorId: -1
+
+ property string colorName: ""
+
+ function maybeDelete(color_id, name) {
+ colorName = name
+ colorId = color_id
+ open()
+ }
+
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 10
+
+ Text {
+ color: "#222222"
+ text: qsTr("Delete Color?")
+ font.pixelSize: 16
+ font.bold: true
+ }
+
+ Text {
+ color: "#222222"
+ text: qsTr("Are you sure, you want to delete color") + " \"" + colorDeleter.colorName + "\"?"
+ font.pixelSize: 12
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 10
+
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("Cancel")
+ onClicked: colorDeleter.close()
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: qsTr("Delete")
+
+ buttonColor: "#CC1414"
+ textColor: "#FFFFFF"
+
+ onClicked: {
+ colorDeleter.deleteClicked(colorDeleter.colorId)
+ colorDeleter.close()
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml
new file mode 100644
index 000000000..cba6e5a76
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Dialogs
+
+import QtExampleStyle
+
+Popup {
+ id: colorEditor
+ // Popup for adding or updating a color
+ padding: 10
+ modal: true
+ focus: true
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+ signal colorAdded(string name, string color, string pantone_value)
+ signal colorUpdated(string name, string color, string pantone_value, int cid)
+
+ property bool newColor: true
+ property int colorId: -1
+ property alias currentColor: colordialogButton.buttonColor
+
+ function createNewColor() {
+ newColor = true
+ colorNameField.text = "cute green"
+ colorRGBField.text = "#41cd52"
+ colorPantoneField.text = "PMS 802C"
+ open()
+ }
+
+ function updateColor(color_id, name, color, pantone_value) {
+ newColor = false
+ colorNameField.text = name
+ currentColor = color
+ colorPantoneField.text = pantone_value
+ colorId = color_id
+ open()
+ }
+
+ ColorDialog {
+ id: colorDialog
+ title: qsTr("Choose a color")
+ onAccepted: {
+ colorEditor.currentColor = Qt.color(colorDialog.selectedColor)
+ colorDialog.close()
+ }
+ onRejected: {
+ colorDialog.close()
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 10
+
+ GridLayout {
+ columns: 2
+ rowSpacing: 10
+ columnSpacing: 10
+
+ Label {
+ text: qsTr("Color Name")
+ }
+ TextField {
+ id: colorNameField
+ padding: 10
+ }
+
+ Label {
+ text: qsTr("Pantone Value")
+ }
+ TextField {
+ id: colorPantoneField
+ padding: 10
+ }
+
+ Label {
+ text: qsTr("Rgb Value")
+ }
+
+ TextField {
+ id: colorRGBField
+ text: colorEditor.currentColor.toString()
+ readOnly: true
+ padding: 10
+ }
+ }
+
+ Button {
+ id: colordialogButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 30
+ text: qsTr("Set Color")
+ textColor: isColorDark(buttonColor) ? "#E6E6E6" : "#191919"
+
+ onClicked: colorDialog.open()
+
+ function isColorDark(color) {
+ return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) < 0.5;
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 10
+
+ Button {
+ text: qsTr("Cancel")
+ onClicked: colorEditor.close()
+ Layout.fillWidth: true
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: colorEditor.newColor ? qsTr("Add") : qsTr("Update")
+
+ buttonColor: "#2CDE85"
+ textColor: "#FFFFFF"
+
+ onClicked: {
+ if (colorEditor.newColor) {
+ colorEditor.colorAdded(colorNameField.text,
+ colorRGBField.text,
+ colorPantoneField.text)
+ } else {
+ colorEditor.colorUpdated(colorNameField.text,
+ colorRGBField.text,
+ colorPantoneField.text,
+ colorEditor.colorId)
+ }
+ colorEditor.close()
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml
new file mode 100644
index 000000000..c6ad36f80
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml
@@ -0,0 +1,381 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Effects
+import QtQuick.Shapes
+
+import QtExampleStyle
+import ColorPalette
+
+Item {
+ id: root
+ required property BasicLogin loginService
+ required property PaginatedColorsResource colors
+ required property PaginatedColorUsersResource colorViewUsers
+
+ ColorDialogEditor {
+ id: colorPopup
+ onColorAdded: (colorNameField, colorRGBField, colorPantoneField) => {
+ root.colors.add({"name" : colorNameField,
+ "color" : colorRGBField,
+ "pantone_value" : colorPantoneField})
+ }
+
+ onColorUpdated: (colorNameField, colorRGBField, colorPantoneField, cid) => {
+ root.colors.update({"name" : colorNameField,
+ "color" : colorRGBField,
+ "pantone_value" : colorPantoneField},
+ cid)
+ }
+ }
+
+ ColorDialogDelete {
+ id: colorDeletePopup
+ onDeleteClicked: (cid) => {
+ root.colors.remove(cid)
+ }
+ }
+
+ ColumnLayout {
+ // The main application layout
+ anchors.fill :parent
+
+ ToolBar {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 25 + 4
+
+ UserMenu {
+ id: userMenu
+
+ userMenuUsers: root.colorViewUsers
+ userLoginService: root.loginService
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ Text {
+ text: qsTr("QHTTP Server")
+ font.pixelSize: 8
+ color: "#667085"
+ }
+ Item { Layout.fillWidth: true }
+
+ AbstractButton {
+ id: loginButton
+ Layout.preferredWidth: 25
+ Layout.preferredHeight: 25
+ Item {
+ id: userImageCliped
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: 25
+ height: 25
+
+ Image {
+ id: userImage
+ anchors.fill: parent
+ source: getCurrentUserImage()
+ visible: false
+
+ function getCurrentUserImage() {
+ if (root.loginService.loggedIn)
+ return users.avatarForEmail(loginService.user)
+ return "qrc:/qt/qml/ColorPalette/icons/user.svg";
+ }
+ }
+
+ Image {
+ id: userMask
+ source: "qrc:/qt/qml/ColorPalette/icons/userMask.svg"
+ anchors.fill: userImage
+ anchors.margins: 4
+ visible: false
+ }
+
+ MultiEffect {
+ source: userImage
+ anchors.fill: userImage
+ maskSource: userMask
+ maskEnabled: true
+ }
+ }
+
+ onClicked: {
+ userMenu.open()
+ var pos = mapToGlobal(Qt.point(x, y))
+ pos = userMenu.parent.mapFromGlobal(pos)
+ userMenu.x = x - userMenu.width + 25 + 3
+ userMenu.y = y + 25 + 3
+ }
+
+ Shape {
+ id: bubble
+ x: -text.width - 25
+ anchors.margins: 3
+
+ preferredRendererType: Shape.CurveRenderer
+
+ visible: !root.loginService.loggedIn
+
+ ShapePath {
+ strokeWidth: 0
+ fillColor: "#667085"
+ startX: 5; startY: 0
+ PathLine { x: 5 + text.width + 6; y: 0 }
+ PathArc { x: 10 + text.width + 6; y: 5; radiusX: 5; radiusY: 5}
+ // arrow
+ PathLine { x: 10 + text.width + 6; y: 8 + text.height / 2 - 6 }
+ PathLine { x: 10 + text.width + 6 + 6; y: 8 + text.height / 2 }
+ PathLine { x: 10 + text.width + 6; y: 8 + text.height / 2 + 6}
+ PathLine { x: 10 + text.width + 6; y: 5 + text.height + 6 }
+ // end arrow
+ PathArc { x: 5 + text.width + 6; y: 10 + text.height + 6 ; radiusX: 5; radiusY: 5}
+ PathLine { x: 5; y: 10 + text.height + 6 }
+ PathArc { x: 0; y: 5 + text.height + 6 ; radiusX: 5; radiusY: 5}
+ PathLine { x: 0; y: 5 }
+ PathArc { x: 5; y: 0 ; radiusX: 5; radiusY: 5}
+ }
+ Text {
+ x: 8
+ y: 8
+ id: text
+ color: "white"
+ text: qsTr("Log in to edit")
+ font.bold: true
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ }
+ }
+ }
+ }
+
+ Image {
+ anchors.centerIn: parent
+ source: "qrc:/qt/qml/ColorPalette/icons/qt.png"
+ fillMode: Image.PreserveAspectFit
+ height: 25
+ }
+
+ }
+ ToolBar {
+ Layout.fillWidth: true
+ Layout.minimumHeight: 32
+
+ RowLayout {
+ anchors.fill: parent
+ Text {
+ Layout.alignment: Qt.AlignVCenter
+ text: qsTr("Color Palette")
+ font.pixelSize: 14
+ font.bold: true
+ color: "#667085"
+ }
+
+ Item { Layout.fillWidth: true }
+
+ AbstractButton {
+ Layout.preferredWidth: 25
+ Layout.preferredHeight: 25
+ Layout.alignment: Qt.AlignVCenter
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 4
+ color: "#192CDE85"
+ border.color: "#DDE2E8"
+ border.width: 1
+ }
+
+ Image {
+ source: UIStyle.iconPath("plus")
+ fillMode: Image.PreserveAspectFit
+ anchors.fill: parent
+ sourceSize.width: width
+ sourceSize.height: height
+
+ }
+ visible: root.loginService.loggedIn
+ onClicked: colorPopup.createNewColor()
+ }
+
+ AbstractButton {
+ Layout.preferredWidth: 25
+ Layout.preferredHeight: 25
+ Layout.alignment: Qt.AlignVCenter
+
+ Rectangle {
+ anchors.fill: parent
+ radius: 4
+ color: "#192CDE85"
+ border.color: "#DDE2E8"
+ border.width: 1
+ }
+
+ Image {
+ source: UIStyle.iconPath("update")
+ fillMode: Image.PreserveAspectFit
+ anchors.fill: parent
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+
+ onClicked: {
+ root.colors.refreshCurrentPage()
+ root.colorViewUsers.refreshCurrentPage()
+ }
+ }
+ }
+ }
+
+
+
+ //! [View and model]
+ ListView {
+ id: colorListView
+
+ model: root.colors.model
+ //! [View and model]
+ footerPositioning: ListView.OverlayFooter
+ spacing: 15
+ clip: true
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ header: Rectangle {
+ height: 32
+ width: parent.width
+ color: "#F0F1F3"
+
+ RowLayout {
+ anchors.fill: parent
+
+ component HeaderText : Text {
+ Layout.alignment: Qt.AlignVCenter
+ horizontalAlignment: Qt.AlignHCenter
+
+ font.pixelSize: 12
+ color: "#667085"
+ }
+ HeaderText {
+ id: headerName
+ text: qsTr("Color Name")
+ Layout.preferredWidth: colorListView.width * 0.3
+ }
+ HeaderText {
+ id: headerRgb
+ text: qsTr("Rgb Value")
+ Layout.preferredWidth: colorListView.width * 0.25
+ }
+ HeaderText {
+ id: headerPantone
+ text: qsTr("Pantone Value")
+ Layout.preferredWidth: colorListView.width * 0.25
+ }
+ HeaderText {
+ id: headerAction
+ text: qsTr("Action")
+ Layout.preferredWidth: colorListView.width * 0.2
+ }
+ }
+ }
+
+ delegate: Item {
+ id: colorInfo
+
+ required property int color_id
+ required property string name
+ required property string color
+ required property string pantone_value
+
+ width: colorListView.width
+ height: 25
+ RowLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 5
+ anchors.rightMargin: 5
+
+ Rectangle {
+ id: colorSample
+ Layout.alignment: Qt.AlignVCenter
+ implicitWidth: 36
+ implicitHeight: 21
+ radius: 6
+ color: colorInfo.color
+ }
+
+ Text {
+ Layout.preferredWidth: colorInfo.width * 0.3 - colorSample.width
+ horizontalAlignment: Qt.AlignLeft
+ leftPadding: 5
+ text: colorInfo.name
+ }
+
+ Text {
+ Layout.preferredWidth: colorInfo.width * 0.25
+ horizontalAlignment: Qt.AlignHCenter
+ text: colorInfo.color
+ }
+
+ Text {
+ Layout.preferredWidth: colorInfo.width * 0.25
+ horizontalAlignment: Qt.AlignHCenter
+ text: colorInfo.pantone_value
+ }
+
+ Item {
+ Layout.maximumHeight: 28
+ implicitHeight: buttonBox.implicitHeight
+ implicitWidth: buttonBox.implicitWidth
+
+ RowLayout {
+ id: buttonBox
+ anchors.fill: parent
+ ToolButton {
+ icon.source: UIStyle.iconPath("delete")
+ enabled: root.loginService.loggedIn
+ onClicked: colorDeletePopup.maybeDelete(color_id, name)
+ }
+ ToolButton {
+ icon.source: UIStyle.iconPath("edit")
+ enabled: root.loginService.loggedIn
+ onClicked: colorPopup.updateColor(color_id, name, color, pantone_value)
+ }
+ }
+ }
+ }
+ }
+
+ footer: ToolBar {
+ // Paginate buttons if more than one page
+ visible: root.colors.pages > 1
+ implicitWidth: parent.width
+
+ RowLayout {
+ anchors.fill: parent
+
+ Item { Layout.fillWidth: true /* spacer */ }
+
+ Repeater {
+ model: root.colors.pages
+
+ ToolButton {
+ text: page
+ font.bold: root.colors.page === page
+
+ required property int index
+ readonly property int page: (index + 1)
+
+ onClicked: root.colors.page = page
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/Main.qml b/examples/demos/colorpaletteclient/ColorPalette/Main.qml
new file mode 100644
index 000000000..ae1e85533
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/Main.qml
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+
+import ColorPalette
+
+Window {
+ id: window
+ width: 500
+ height: 400
+ visible: true
+ title: qsTr("Color Palette Client")
+
+ enum DataView {
+ UserView = 0,
+ ColorView = 1
+ }
+
+ ServerSelection {
+ id: serverview
+ anchors.fill: parent
+ onServerSelected: {colorview.visible = true; serverview.visible = false}
+ colorResources: colors
+ restPalette: paletteService
+ colorUsers: users
+ }
+
+ ColorView {
+ id: colorview
+ anchors.fill: parent
+ visible: false
+ loginService: colorLogin
+ colors: colors
+ colorViewUsers: users
+ }
+
+ //! [RestService QML element]
+ RestService {
+ id: paletteService
+
+ PaginatedColorUsersResource {
+ id: users
+ path: "/api/users"
+ }
+
+ PaginatedColorsResource {
+ id: colors
+ path: "/api/unknown"
+ }
+
+ BasicLogin {
+ id: colorLogin
+ loginPath: "/api/login"
+ logoutPath: "/api/logout"
+ }
+ }
+ //! [RestService QML element]
+
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml
new file mode 100644
index 000000000..c170773cc
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml
@@ -0,0 +1,241 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ColorPalette
+import QtExampleStyle
+
+pragma ComponentBehavior: Bound
+
+Item {
+ id: root
+ // A popup for selecting the server URL
+
+ signal serverSelected()
+
+ required property PaginatedColorsResource colorResources
+ required property PaginatedColorUsersResource colorUsers
+ required property RestService restPalette
+
+ Connections {
+ target: root.colorResources
+ // Closes the URL selection popup once we have received data successfully
+ function onDataUpdated() {
+ fetchTester.stop()
+ root.serverSelected()
+ }
+ }
+
+
+ ListModel {
+ id: server
+ ListElement {
+ title: qsTr("Public REST API Test Server")
+ url: "https://reqres.in"
+ icon: "qrc:/qt/qml/ColorPalette/icons/testserver.png"
+ }
+ ListElement {
+ title: qsTr("Qt-based REST API server")
+ url: "http://127.0.0.1:49425"
+ icon: "qrc:/qt/qml/ColorPalette/icons/qt.png"
+ }
+ }
+
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 20
+ spacing: 10
+
+ Image {
+ Layout.alignment: Qt.AlignHCenter
+ source: "qrc:/qt/qml/ColorPalette/icons/qt.png"
+ fillMode: Image.PreserveAspectFit
+ Layout.preferredWidth: 20
+ }
+
+ Label {
+ text: qsTr("Choose a server")
+ Layout.alignment: Qt.AlignHCenter
+ font.pixelSize: 24
+ }
+
+ component ServerListDelegate: Rectangle {
+ id: serverListDelegate
+ required property string title
+ required property string url
+ required property string icon
+ required property int index
+
+ radius: 10
+ color: "#00000000"
+
+ border.color: ListView.view.currentIndex === index ? "#2CDE85" : "#E0E2E7"
+ border.width: 2
+
+ implicitWidth: 180
+ implicitHeight: 100
+
+ Rectangle {
+ id: img
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: 10
+ anchors.leftMargin: 20
+
+ width: 30
+ height: 30
+ radius: 200
+ border. color: "#E7F4EE"
+ border.width: 5
+
+ Image {
+ anchors.centerIn: parent
+ source: serverListDelegate.icon
+ width: 15
+ height: 15
+ fillMode: Image.PreserveAspectFit
+ smooth: true
+ }
+ }
+
+ Text {
+ text: parent.url
+
+ anchors.left: parent.left
+ anchors.top: img.bottom
+ anchors.topMargin: 10
+ anchors.leftMargin: 20
+ color: "#667085"
+ font.pixelSize: 13
+ }
+ Text {
+ text: parent.title
+
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 10
+ color: "#222222"
+ font.pixelSize: 11
+ font.bold: true
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: serverList.currentIndex = serverListDelegate.index;
+ }
+ }
+
+ ListView {
+ id: serverList
+ Layout.alignment: Qt.AlignHCenter
+ Layout.minimumWidth: 180 * server.count + 20
+ Layout.minimumHeight: 100
+ orientation: ListView.Horizontal
+
+ model: server
+ spacing: 20
+
+ delegate: ServerListDelegate {}
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: restPalette.sslSupported ? qsTr("Connect (SSL)") : qsTr("Connect")
+
+ buttonColor: "#2CDE85"
+ textColor: "#FFFFFF"
+
+ onClicked: {
+ busyIndicatorPopup.title = (serverList.currentItem as ServerListDelegate).title
+ busyIndicatorPopup.icon = (serverList.currentItem as ServerListDelegate).icon
+ busyIndicatorPopup.open()
+
+ fetchTester.test((serverList.currentItem as ServerListDelegate).url)
+ }
+ }
+
+ Timer {
+ id: fetchTester
+ interval: 2000
+
+ function test(url) {
+ root.restPalette.url = url
+ root.colorResources.refreshCurrentPage()
+ root.colorUsers.refreshCurrentPage()
+ start()
+ }
+ onTriggered: busyIndicatorPopup.close()
+ }
+ }
+
+ onVisibleChanged: {if (!visible) busyIndicatorPopup.close();}
+
+ Popup {
+ id: busyIndicatorPopup
+ padding: 10
+ modal: true
+ focus: true
+ anchors.centerIn: parent
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
+
+ property alias title: titleText.text
+ property alias icon: titleImg.source
+
+ ColumnLayout {
+ id: fetchIndicator
+ anchors.fill: parent
+
+ RowLayout {
+ Rectangle {
+ Layout.preferredWidth: 50
+ Layout.preferredHeight: 50
+ radius: 200
+ border. color: "#E7F4EE"
+ border.width: 5
+
+ Image {
+ id: titleImg
+ anchors.centerIn: parent
+ width: 25
+ height: 25
+ fillMode: Image.PreserveAspectFit
+ }
+ }
+
+ Label {
+ id: titleText
+ text:""
+ font.pixelSize: 18
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: false
+ Layout.alignment: Qt.AlignHCenter
+ BusyIndicator {
+ running: visible
+ Layout.fillWidth: true
+ }
+
+ Label {
+ text: qsTr("Testing URL")
+ font.pixelSize: 18
+ }
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Cancel")
+ onClicked: {
+ busyIndicatorPopup.close()
+ }
+ }
+
+ }
+
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml
new file mode 100644
index 000000000..6c4b25683
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml
@@ -0,0 +1,139 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Effects
+
+import QtExampleStyle
+import ColorPalette
+
+Popup {
+ id: userMenu
+
+ required property BasicLogin userLoginService
+ required property PaginatedColorUsersResource userMenuUsers
+
+ width: 280
+ height: 270
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ ListView {
+ id: userListView
+
+ model: userMenu.userMenuUsers.model
+ spacing: 5
+ footerPositioning: ListView.PullBackFooter
+ clip: true
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+
+ delegate: Rectangle {
+ id: userInfo
+
+ required property string email
+ required property string avatar
+
+ height: 30
+ width: userListView.width
+
+
+ readonly property bool logged: (email === loginService.user)
+
+ Rectangle {
+ id: userImageCliped
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ width: 30
+ height: 30
+
+ Image {
+ id: userImage
+ anchors.fill: parent
+ source: userInfo.avatar
+ visible: false
+ }
+
+ Image {
+ id: userMask
+ source: "qrc:/qt/qml/ColorPalette/icons/userMask.svg"
+ anchors.fill: userImage
+ anchors.margins: 4
+ visible: false
+ }
+
+ MultiEffect {
+ source: userImage
+ anchors.fill: userImage
+ maskSource: userMask
+ maskEnabled: true
+ }
+ }
+
+ Text {
+ id: userMailLabel
+ anchors.left: userImageCliped.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.margins: 5
+ text: userInfo.email
+ font.bold: userInfo.logged
+ }
+
+ ToolButton {
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.margins: 5
+
+ icon.source: UIStyle.iconPath(userInfo.logged
+ ? "logout" : "login")
+ enabled: userInfo.logged || !userMenu.userLoginService.loggedIn
+
+ onClicked: {
+ if (userInfo.logged) {
+ userMenu.userLoginService.logout()
+ } else {
+ //! [Login]
+ userMenu.userLoginService.login({"email" : userInfo.email,
+ "password" : "apassword",
+ "id" : userInfo.id})
+ //! [Login]
+ userMenu.close()
+ }
+ }
+ }
+
+ }
+ footer: ToolBar {
+ // Paginate buttons if more than one page
+ visible: userMenu.userMenuUsers.pages > 1
+ implicitWidth: parent.width
+
+ RowLayout {
+ anchors.fill: parent
+
+ Item { Layout.fillWidth: true /* spacer */ }
+
+ Repeater {
+ model: userMenu.userMenuUsers.pages
+
+ ToolButton {
+ text: page
+ font.bold: userMenu.userMenuUsers.page === page
+
+ required property int index
+ readonly property int page: (index + 1)
+
+ onClicked: userMenu.userMenuUsers.page = page
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/examples/demos/colorpaletteclient/ColorPalette/qmldir b/examples/demos/colorpaletteclient/ColorPalette/qmldir
new file mode 100644
index 000000000..7a153fea8
--- /dev/null
+++ b/examples/demos/colorpaletteclient/ColorPalette/qmldir
@@ -0,0 +1,7 @@
+module ColorPalette
+Main 1.0 Main.qml
+ColorDialogDelete 1.0 ColorDialogDelete.qml
+ColorDialogEditor 1.0 ColorDialogEditor.qml
+ColorView 1.0 ColorView.qml
+ServerSelection 1.0 ServerSelection.qml
+UserMenu 1.0 UserMenu.qml
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml
new file mode 100644
index 000000000..6b3f922a1
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+
+T.Button {
+ id: control
+
+ property alias buttonColor: rect.color
+ property alias textColor: label.color
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: 15
+ rightPadding: 15
+ topPadding: 10
+ bottomPadding: 10
+
+ background: Rectangle {
+ id: rect
+ radius: 8
+ border.color: "#E0E2E7"
+ border.width: 1
+ color: "#FFFFFF"
+ }
+
+ icon.width: 24
+ icon.height: 24
+ icon.color: control.palette.buttonText
+
+ contentItem: IconLabel {
+ id: label
+ spacing: control.spacing
+ mirrored: control.mirrored
+ display: control.display
+
+ icon: control.icon
+ text: control.text
+ font.pixelSize: 14
+ color: "#667085"
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt
new file mode 100644
index 000000000..a911f8742
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt
@@ -0,0 +1,54 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qtexamplestyle LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols/colorpaletteclient/QtExampleStyle")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick QuickControls2)
+
+set_source_files_properties(UIStyle.qml
+ PROPERTIES
+ QT_QML_SINGLETON_TYPE TRUE
+)
+
+qt_policy(SET QTP0001 NEW)
+qt_add_qml_module(qtexamplestyle
+ URI QtExampleStyle
+ PLUGIN_TARGET qtexamplestyle
+ QML_FILES
+ Button.qml
+ Popup.qml
+ UIStyle.qml
+ TextField.qml
+)
+
+target_link_libraries(qtexamplestyle PUBLIC
+ Qt6::Core
+ Qt6::Gui
+ Qt6::Quick
+ Qt6::QuickControls2
+)
+
+if(UNIX AND NOT APPLE AND CMAKE_CROSSCOMPILING)
+ find_package(Qt6 REQUIRED COMPONENTS QuickTemplates2)
+
+ # Work around QTBUG-86533
+ target_link_libraries(qtexamplestyle PRIVATE Qt6::QuickTemplates2)
+endif()
+
+install(TARGETS qtexamplestyle
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qmldir
+ DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml
new file mode 100644
index 000000000..a3132bcea
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Templates as T
+
+T.Popup {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ implicitContentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ implicitContentHeight + topPadding + bottomPadding)
+
+ leftPadding: 15
+ rightPadding: 15
+ topPadding: 10
+ bottomPadding: 10
+
+ background: Rectangle {
+ id: bg
+ radius: 8
+ border.color: "#E0E2E7"
+ border.width: 2
+ color: "#FFFFFF"
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml
new file mode 100644
index 000000000..7db2d4f98
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Templates as T
+
+T.TextField {
+ id: control
+ placeholderText: ""
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ contentHeight + topPadding + bottomPadding)
+
+ background: Rectangle {
+ implicitWidth: 200
+ implicitHeight: 40
+ radius: 8
+ color: control.enabled ? "transparent" : "#353637"
+ border.color: "#E0E2E7"
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml
new file mode 100644
index 000000000..3c4741d7f
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma Singleton
+
+import QtQuick
+
+QtObject {
+ id: uiStyle
+
+ // Font Sizes
+ readonly property int fontSizeXXS: 10
+ readonly property int fontSizeXS: 15
+ readonly property int fontSizeS: 20
+ readonly property int fontSizeM: 25
+ readonly property int fontSizeL: 30
+ readonly property int fontSizeXL: 35
+ readonly property int fontSizeXXL: 40
+
+ // Color Scheme
+ // Green
+ readonly property color colorQtPrimGreen: "#41cd52"
+ readonly property color colorQtAuxGreen1: "#21be2b"
+ readonly property color colorQtAuxGreen2: "#17a81a"
+
+ function iconPath(baseImagePath) {
+ return `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}.svg`
+ }
+}
diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/qmldir b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir
new file mode 100644
index 000000000..7bdfb44d8
--- /dev/null
+++ b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir
@@ -0,0 +1,5 @@
+module QtExampleStyle
+Button 1.0 Button.qml
+Popup 1.0 Popup.qml
+TextField 1.0 TextField.qml
+singleton UIStyle 1.0 UIStyle.qml
diff --git a/examples/demos/colorpaletteclient/abstractresource.py b/examples/demos/colorpaletteclient/abstractresource.py
new file mode 100644
index 000000000..3f3a7ed6a
--- /dev/null
+++ b/examples/demos/colorpaletteclient/abstractresource.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QObject
+from PySide6.QtQml import QmlAnonymous
+
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlAnonymous
+class AbstractResource(QObject):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_manager = None # QRestAccessManager
+ self.m_api = None # QNetworkRequestFactory
+
+ def setAccessManager(self, manager):
+ self.m_manager = manager
+
+ def setServiceApi(self, serviceApi):
+ self.m_api = serviceApi
diff --git a/examples/demos/colorpaletteclient/basiclogin.py b/examples/demos/colorpaletteclient/basiclogin.py
new file mode 100644
index 000000000..b9139c2e2
--- /dev/null
+++ b/examples/demos/colorpaletteclient/basiclogin.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from functools import partial
+from dataclasses import dataclass
+
+from PySide6.QtCore import Property, Signal, Slot
+from PySide6.QtNetwork import QHttpHeaders
+from PySide6.QtQml import QmlElement
+
+from abstractresource import AbstractResource
+
+
+tokenField = "token"
+emailField = "email"
+idField = "id"
+
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class BasicLogin(AbstractResource):
+ @dataclass
+ class User:
+ email: str
+ token: bytes
+ id: int
+
+ userChanged = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_user = None
+ self.m_loginPath = ""
+ self.m_logoutPath = ""
+ self.m_user = None
+
+ @Property(str, notify=userChanged)
+ def user(self):
+ return self.m_user.email if self.m_user else ""
+
+ @Property(bool, notify=userChanged)
+ def loggedIn(self):
+ return bool(self.m_user)
+
+ @Property(str)
+ def loginPath(self):
+ return self.m_loginPath
+
+ @loginPath.setter
+ def loginPath(self, p):
+ self.m_loginPath = p
+
+ @Property(str)
+ def logoutPath(self):
+ return self.m_logoutPath
+
+ @logoutPath.setter
+ def logoutPath(self, p):
+ self.m_logoutPath = p
+
+ @Slot("QVariantMap")
+ def login(self, data):
+ request = self.m_api.createRequest(self.m_loginPath)
+ self.m_manager.post(request, data, self, partial(self.loginReply, data))
+
+ def loginReply(self, data, reply):
+ self.m_user = None
+ if not reply.isSuccess():
+ print("login: ", reply.errorString(), file=sys.stderr)
+ (json, error) = reply.readJson()
+ if json and json.isObject():
+ json_object = json.object()
+ token = json_object.get(tokenField)
+ if token:
+ email = data[emailField]
+ token = json_object[tokenField]
+ id = data[idField]
+ self.m_user = BasicLogin.User(email, token, id)
+
+ headers = QHttpHeaders()
+ headers.append("token", self.m_user.token if self.m_user else "")
+ self.m_api.setCommonHeaders(headers)
+ self.userChanged.emit()
+
+ @Slot()
+ def logout(self):
+ request = self.m_api.createRequest(self.m_logoutPath)
+ self.m_manager.post(request, b"", self, self.logoutReply)
+
+ def logoutReply(self, reply):
+ if reply.isSuccess():
+ self.m_user = None
+ self.m_api.clearCommonHeaders() # clears 'token' header
+ self.userChanged.emit()
+ else:
+ print("logout: ", reply.errorString(), file=sys.stderr)
diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.pyproject b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject
new file mode 100644
index 000000000..d05f7cb29
--- /dev/null
+++ b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject
@@ -0,0 +1,21 @@
+{
+ "files": [
+ "abstractresource.py",
+ "basiclogin.py",
+ "main.py",
+ "paginatedresource.py",
+ "restservice.py",
+ "colorpaletteclient.qrc",
+ "ColorPalette/ColorDialogDelete.qml",
+ "ColorPalette/ColorDialogEditor.qml",
+ "ColorPalette/ColorView.qml",
+ "ColorPalette/Main.qml",
+ "ColorPalette/ServerSelection.qml",
+ "ColorPalette/UserMenu.qml",
+ "QtExampleStyle/Button.qml",
+ "QtExampleStyle/Popup.qml",
+ "QtExampleStyle/TextField.qml",
+ "QtExampleStyle/UIStyle.qml",
+ "colorpaletteclient.qrc"
+ ]
+}
diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.qrc b/examples/demos/colorpaletteclient/colorpaletteclient.qrc
new file mode 100644
index 000000000..16260cbd7
--- /dev/null
+++ b/examples/demos/colorpaletteclient/colorpaletteclient.qrc
@@ -0,0 +1,17 @@
+<RCC>
+ <qresource prefix="/qt/qml/ColorPalette">
+ <file>icons/close.svg</file>
+ <file>icons/delete.svg</file>
+ <file>icons/dots.svg</file>
+ <file>icons/edit.svg</file>
+ <file>icons/login.svg</file>
+ <file>icons/logout.svg</file>
+ <file>icons/ok.svg</file>
+ <file>icons/plus.svg</file>
+ <file>icons/qt.png</file>
+ <file>icons/testserver.png</file>
+ <file>icons/update.svg</file>
+ <file>icons/user.svg</file>
+ <file>icons/userMask.svg</file>
+ </qresource>
+</RCC>
diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst
new file mode 100644
index 000000000..0dcb91d4e
--- /dev/null
+++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst
@@ -0,0 +1,79 @@
+RESTful API client
+==================
+
+Example of how to create a RESTful API QML client.
+
+This example shows how to create a basic QML RESTful API client with an
+imaginary color palette service. The application uses RESTful communication
+with the selected server to request and send data. The REST service is provided
+as a QML element whose child elements wrap the individual JSON data APIs
+provided by the server.
+
+Application functionality
+-------------------------
+
+The example provides the following basic functionalities:
+* Select the server to communicate with
+* List users and colors
+* Login and logout users
+* Modify and create new colors
+
+Server selection
+----------------
+
+At start the application presents the options for the color palette server to communicate
+with. The predefined options are:
+
+* ``https://reqres.in``, a publicly available REST API test service
+* A Qt-based REST API server example in ``QtHttpServer``
+
+Once selected, the RESTful API client issues a test HTTP GET to the color API
+to check if the service is accessible.
+
+One major difference between the two predefined API options is that the
+Qt-based REST API server example is a stateful application which allows
+modifying colors, whereas the ``reqres.in`` is a stateless API testing service.
+In other words, when using the ``reqres.in`` backend, modifying the colors has
+no lasting impact.
+
+The users and colors are paginated resources on the server-side. This means
+that the server provides the data in chunks called pages. The UI listing
+reflects this pagination and views the data on pages.
+
+Viewing the data on UI is done with standard QML views where the model are
+QAbstractListModel-derived classes representing JSON data received from the
+server.
+
+Logging in happens via the login function provided by the login popup. Under
+the hood the login sends a HTTP POST request. Upon receiving a successful
+response the authorization token is extracted from the response, which in turn
+is then used in subsequent HTTP requests which require the token.
+
+Editing and adding new colors is done in a popup. Note that uploading the color
+changes to the server requires that a user has logged in.
+
+REST implementation
+-------------------
+
+The example illustrates one way to compose a REST service from individual resource elements. In
+this example the resources are the paginated user and color resources plus the login service.
+The resource elements are bound together by the base URL (server URL) and the shared network access
+manager.
+
+The basis of the REST service is the RestService QML element whose children items
+compose the actual service.
+
+Upon instantiation the RestService element loops its children elements and sets
+them up to use the same network access manager. This way the individual
+resources share the same access details such as the server URL and
+authorization token.
+
+The actual communication is done with a rest access manager which implements
+some convenience functionality to deal specifically with HTTP REST APIs and
+effectively deals with sending and receiving the ``QNetworkRequest`` and
+``QNetworkReply`` as needed.
+
+.. image:: colorpaletteclient.webp
+ :width: 90%
+ :align: center
+ :alt: RESTful API client
diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp b/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp
new file mode 100644
index 000000000..8f4d9a621
--- /dev/null
+++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.webp
Binary files differ
diff --git a/examples/demos/colorpaletteclient/icons/close.svg b/examples/demos/colorpaletteclient/icons/close.svg
new file mode 100644
index 000000000..3a0d4be65
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/close.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12.4501 37.65L10.3501 35.55L21.9001 24L10.3501 12.45L12.4501 10.35L24.0001 21.9L35.5501 10.35L37.6501 12.45L26.1001 24L37.6501 35.55L35.5501 37.65L24.0001 26.1L12.4501 37.65Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/delete.svg b/examples/demos/colorpaletteclient/icons/delete.svg
new file mode 100644
index 000000000..8f04948c8
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/delete.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13.05 42C12.225 42 11.5187 41.7062 10.9313 41.1188C10.3438 40.5312 10.05 39.825 10.05 39V10.5H8V7.5H17.4V6H30.6V7.5H40V10.5H37.95V39C37.95 39.8 37.65 40.5 37.05 41.1C36.45 41.7 35.75 42 34.95 42H13.05ZM34.95 10.5H13.05V39H34.95V10.5ZM18.35 34.7H21.35V14.75H18.35V34.7ZM26.65 34.7H29.65V14.75H26.65V34.7Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/dots.svg b/examples/demos/colorpaletteclient/icons/dots.svg
new file mode 100644
index 000000000..49df163fd
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/dots.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.3929 26.4C9.73097 26.4 9.16667 26.1643 8.7 25.6929C8.23333 25.2215 8 24.6548 8 23.9929C8 23.3309 8.2357 22.7666 8.7071 22.3C9.17847 21.8333 9.74513 21.6 10.4071 21.6C11.069 21.6 11.6333 21.8357 12.1 22.3071C12.5667 22.7784 12.8 23.3451 12.8 24.0071C12.8 24.669 12.5643 25.2333 12.0929 25.7C11.6215 26.1666 11.0549 26.4 10.3929 26.4ZM23.9929 26.4C23.331 26.4 22.7667 26.1643 22.3 25.6929C21.8333 25.2215 21.6 24.6548 21.6 23.9929C21.6 23.3309 21.8357 22.7666 22.3071 22.3C22.7785 21.8333 23.3451 21.6 24.0071 21.6C24.669 21.6 25.2333 21.8357 25.7 22.3071C26.1667 22.7784 26.4 23.3451 26.4 24.0071C26.4 24.669 26.1643 25.2333 25.6929 25.7C25.2215 26.1666 24.6549 26.4 23.9929 26.4ZM37.5929 26.4C36.931 26.4 36.3667 26.1643 35.9 25.6929C35.4333 25.2215 35.2 24.6548 35.2 23.9929C35.2 23.3309 35.4357 22.7666 35.9071 22.3C36.3785 21.8333 36.9451 21.6 37.6071 21.6C38.269 21.6 38.8333 21.8357 39.3 22.3071C39.7667 22.7784 40 23.3451 40 24.0071C40 24.669 39.7643 25.2333 39.2929 25.7C38.8215 26.1666 38.2549 26.4 37.5929 26.4Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/edit.svg b/examples/demos/colorpaletteclient/icons/edit.svg
new file mode 100644
index 000000000..1cfc2a73a
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/edit.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 39H11.2L33.35 16.85L31.15 14.65L9 36.8V39ZM39.7 14.7L33.3 8.29998L35.4 6.19998C35.9667 5.63331 36.6667 5.34998 37.5 5.34998C38.3333 5.34998 39.0333 5.63331 39.6 6.19998L41.8 8.39998C42.3667 8.96664 42.65 9.66664 42.65 10.5C42.65 11.3333 42.3667 12.0333 41.8 12.6L39.7 14.7ZM37.6 16.8L12.4 42H6V35.6L31.2 10.4L37.6 16.8ZM32.25 15.75L31.15 14.65L33.35 16.85L32.25 15.75Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/login.svg b/examples/demos/colorpaletteclient/icons/login.svg
new file mode 100644
index 000000000..c8fe5bc54
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/login.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24.45 42V39H39V9H24.45V6H39C39.8 6 40.5 6.3 41.1 6.9C41.7 7.5 42 8.2 42 9V39C42 39.8 41.7 40.5 41.1 41.1C40.5 41.7 39.8 42 39 42H24.45ZM20.55 32.75L18.4 30.6L23.5 25.5H6V22.5H23.4L18.3 17.4L20.45 15.25L29.25 24.05L20.55 32.75Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/logout.svg b/examples/demos/colorpaletteclient/icons/logout.svg
new file mode 100644
index 000000000..91d4fd869
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/logout.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 42C8.2 42 7.5 41.7 6.9 41.1C6.3 40.5 6 39.8 6 39V9C6 8.2 6.3 7.5 6.9 6.9C7.5 6.3 8.2 6 9 6H23.55V9H9V39H23.55V42H9ZM33.3 32.75L31.15 30.6L36.25 25.5H18.75V22.5H36.15L31.05 17.4L33.2 15.25L42 24.05L33.3 32.75Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/ok.svg b/examples/demos/colorpaletteclient/icons/ok.svg
new file mode 100644
index 000000000..506e2d690
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/ok.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.9002 35.7L7.7002 24.5L9.8502 22.35L18.9002 31.4L38.1002 12.2L40.2502 14.35L18.9002 35.7Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/plus.svg b/examples/demos/colorpaletteclient/icons/plus.svg
new file mode 100644
index 000000000..81837784a
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/plus.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M22.5 38V25.5H10V22.5H22.5V10H25.5V22.5H38V25.5H25.5V38H22.5Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/qt.png b/examples/demos/colorpaletteclient/icons/qt.png
new file mode 100644
index 000000000..abd3a4f14
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/qt.png
Binary files differ
diff --git a/examples/demos/colorpaletteclient/icons/qt_attribution.json b/examples/demos/colorpaletteclient/icons/qt_attribution.json
new file mode 100644
index 000000000..44633c474
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/qt_attribution.json
@@ -0,0 +1,14 @@
+{
+ "Id": "colorpaletteclient",
+ "Name": "Selected Material Icons",
+ "QDocModule": "qtdoc",
+ "QtUsage": "Used in Color Palette Client example in QtDoc",
+ "QtParts": [
+ "examples"
+ ],
+ "Files": "close.svg delete.svg dots.svg edit.svg login.svg logout.svg ok.svg update.svg user.svg",
+ "Homepage": "https://fonts.google.com/icons",
+ "License": "Apache License Version 2.0",
+ "LicenseId": "Apache-2.0",
+ "Copyright": "Copyright 2018 Google, Inc. All Rights Reserved."
+}
diff --git a/examples/demos/colorpaletteclient/icons/testserver.png b/examples/demos/colorpaletteclient/icons/testserver.png
new file mode 100644
index 000000000..0890e5e4b
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/testserver.png
Binary files differ
diff --git a/examples/demos/colorpaletteclient/icons/update.svg b/examples/demos/colorpaletteclient/icons/update.svg
new file mode 100644
index 000000000..303ff4d3d
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/update.svg
@@ -0,0 +1,3 @@
+<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24 40C19.5667 40 15.7917 38.4417 12.675 35.325C9.55833 32.2083 8 28.4333 8 24C8 19.5667 9.55833 15.7917 12.675 12.675C15.7917 9.55833 19.5667 8 24 8C26.8333 8 29.3167 8.575 31.45 9.725C33.5833 10.875 35.4333 12.45 37 14.45V8H40V20.7H27.3V17.7H35.7C34.4333 15.7 32.8167 14.0833 30.85 12.85C28.8833 11.6167 26.6 11 24 11C20.3667 11 17.2917 12.2583 14.775 14.775C12.2583 17.2917 11 20.3667 11 24C11 27.6333 12.2583 30.7083 14.775 33.225C17.2917 35.7417 20.3667 37 24 37C26.7667 37 29.3 36.2083 31.6 34.625C33.9 33.0417 35.5 30.95 36.4 28.35H39.5C38.5333 31.85 36.6167 34.6667 33.75 36.8C30.8833 38.9333 27.6333 40 24 40Z" fill="#667085"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/user.svg b/examples/demos/colorpaletteclient/icons/user.svg
new file mode 100644
index 000000000..ed782385e
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/user.svg
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12Z" fill="#E6E6E6"/>
+<path d="M15.5 12C16.3284 12 17 12.6716 17 13.5V14C17 15.9714 15.1405 18 12 18C8.85951 18 7 15.9714 7 14V13.5C7 12.6716 7.67157 12 8.5 12H15.5ZM15.5 13H8.5C8.22386 13 8 13.2239 8 13.5V14C8 15.4376 9.43216 17 12 17C14.5678 17 16 15.4376 16 14V13.5C16 13.2239 15.7761 13 15.5 13ZM12 5.5C13.5188 5.5 14.75 6.73122 14.75 8.25C14.75 9.76878 13.5188 11 12 11C10.4812 11 9.25 9.76878 9.25 8.25C9.25 6.73122 10.4812 5.5 12 5.5ZM12 6.5C11.0335 6.5 10.25 7.2835 10.25 8.25C10.25 9.2165 11.0335 10 12 10C12.9665 10 13.75 9.2165 13.75 8.25C13.75 7.2835 12.9665 6.5 12 6.5Z" fill="#616161"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/icons/userMask.svg b/examples/demos/colorpaletteclient/icons/userMask.svg
new file mode 100644
index 000000000..5e3065d7f
--- /dev/null
+++ b/examples/demos/colorpaletteclient/icons/userMask.svg
@@ -0,0 +1,3 @@
+<svg width="30" height="30" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
+<ellipse cx="15" cy="15" rx="13" ry="13" fill="black"/>
+</svg>
diff --git a/examples/demos/colorpaletteclient/main.py b/examples/demos/colorpaletteclient/main.py
new file mode 100644
index 000000000..a249b9fa2
--- /dev/null
+++ b/examples/demos/colorpaletteclient/main.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the Qt RESTful API client demo from Qt v6.x"""
+
+import os
+import sys
+from pathlib import Path
+
+from PySide6.QtCore import QUrl
+from PySide6.QtGui import QIcon, QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+
+from basiclogin import BasicLogin # noqa: F401
+from paginatedresource import PaginatedResource # noqa: F401
+from restservice import RestService # noqa: F401
+import rc_colorpaletteclient # noqa: F401
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+ QIcon.setThemeName("colorpaletteclient")
+
+ engine = QQmlApplicationEngine()
+ app_dir = Path(__file__).parent
+ app_dir_url = QUrl.fromLocalFile(os.fspath(app_dir))
+ engine.addImportPath(os.fspath(app_dir))
+ engine.loadFromModule("ColorPalette", "Main")
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ ex = app.exec()
+ del engine
+ sys.exit(ex)
diff --git a/examples/demos/colorpaletteclient/paginatedresource.py b/examples/demos/colorpaletteclient/paginatedresource.py
new file mode 100644
index 000000000..b7f036c4e
--- /dev/null
+++ b/examples/demos/colorpaletteclient/paginatedresource.py
@@ -0,0 +1,278 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+from dataclasses import dataclass
+from PySide6.QtCore import (QAbstractListModel, QByteArray,
+ QUrlQuery, Property, Signal, Slot, Qt)
+from PySide6.QtQml import QmlAnonymous, QmlElement
+
+from abstractresource import AbstractResource
+
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+totalPagesField = "total_pages"
+currentPageField = "page"
+
+
+@dataclass
+class ColorUser:
+ id: int
+ email: str
+ avatar: str # URL
+
+
+@QmlElement
+class ColorUserModel (QAbstractListModel):
+ IdRole = Qt.UserRole + 1
+ EmailRole = Qt.UserRole + 2
+ AvatarRole = Qt.UserRole + 3
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._users = []
+
+ def clear(self):
+ self.set_data([])
+
+ def set_data(self, json_list):
+ if not self._users and not json_list:
+ return
+ self.beginResetModel()
+ self._users.clear()
+ for e in json_list:
+ self._users.append(ColorUser(int(e["id"]), e["email"], e["avatar"]))
+ self.endResetModel()
+
+ def roleNames(self):
+ roles = {
+ ColorUserModel.IdRole: QByteArray(b'id'),
+ ColorUserModel.EmailRole: QByteArray(b'email'),
+ ColorUserModel.AvatarRole: QByteArray(b'avatar')
+ }
+ return roles
+
+ def rowCount(self, index):
+ return len(self._users)
+
+ def data(self, index, role):
+ if index.isValid():
+ d = self._users[index.row()]
+ if role == ColorUserModel.IdRole:
+ return d.id
+ if role == ColorUserModel.EmailRole:
+ return d.email
+ if role == ColorUserModel.AvatarRole:
+ return d.avatar
+ return None
+
+ def avatarForEmail(self, email):
+ for e in self._users:
+ if e.email == email:
+ return e.avatar
+ return ""
+
+
+@dataclass
+class Color:
+ id: int
+ color: str
+ name: str
+ pantone_value: str
+
+
+@QmlElement
+class ColorModel (QAbstractListModel):
+ IdRole = Qt.UserRole + 1
+ ColorRole = Qt.UserRole + 2
+ NameRole = Qt.UserRole + 3
+ PantoneValueRole = Qt.UserRole + 4
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._colors = []
+
+ def clear(self):
+ self.set_data([])
+
+ def set_data(self, json_list):
+ if not self._colors and not json_list:
+ return
+ self.beginResetModel()
+ self._colors.clear()
+ for e in json_list:
+ self._colors.append(Color(int(e["id"]), e["color"],
+ e["name"], e["pantone_value"]))
+ self.endResetModel()
+
+ def roleNames(self):
+ roles = {
+ ColorModel.IdRole: QByteArray(b'color_id'),
+ ColorModel.ColorRole: QByteArray(b'color'),
+ ColorModel.NameRole: QByteArray(b'name'),
+ ColorModel.PantoneValueRole: QByteArray(b'pantone_value')
+ }
+ return roles
+
+ def rowCount(self, index):
+ return len(self._colors)
+
+ def data(self, index, role):
+ if index.isValid():
+ d = self._colors[index.row()]
+ if role == ColorModel.IdRole:
+ return d.id
+ if role == ColorModel.ColorRole:
+ return d.color
+ if role == ColorModel.NameRole:
+ return d.name
+ if role == ColorModel.PantoneValueRole:
+ return d.pantone_value
+ return None
+
+
+@QmlAnonymous
+class PaginatedResource(AbstractResource):
+ """This class manages a simple paginated Crud resource,
+ where the resource is a paginated list of JSON items."""
+
+ dataUpdated = Signal()
+ pageUpdated = Signal()
+ pagesUpdated = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ # The total number of pages as reported by the server responses
+ self.m_pages = 0
+ # The default page we request if the user hasn't set otherwise
+ self.m_currentPage = 1
+ self.m_path = ""
+
+ def _clearModel(self):
+ pass
+
+ def _populateModel(self, json_list):
+ pass
+
+ @Property(str)
+ def path(self):
+ return self.m_path
+
+ @path.setter
+ def path(self, p):
+ self.m_path = p
+
+ @Property(int, notify=pagesUpdated)
+ def pages(self):
+ return self.m_pages
+
+ @Property(int, notify=pageUpdated)
+ def page(self):
+ return self.m_currentPage
+
+ @page.setter
+ def page(self, page):
+ if self.m_currentPage == page or page < 1:
+ return
+ self.m_currentPage = page
+ self.pageUpdated.emit()
+ self.refreshCurrentPage()
+
+ @Slot()
+ def refreshCurrentPage(self):
+ query = QUrlQuery()
+ query.addQueryItem("page", str(self.m_currentPage))
+ request = self.m_api.createRequest(self.m_path, query)
+ self.m_manager.get(request, self, self.refreshCurrentPageReply)
+
+ def refreshCurrentPageReply(self, reply):
+ if not reply.isSuccess():
+ print("PaginatedResource: ", reply.errorString(), file=sys.stderr)
+ (json, error) = reply.readJson()
+ if json:
+ self.refreshRequestFinished(json)
+ else:
+ self.refreshRequestFailed()
+
+ def refreshRequestFinished(self, json):
+ json_object = json.object()
+ self._populateModel(json_object["data"])
+ self.m_pages = int(json_object[totalPagesField])
+ self.m_currentPage = int(json_object[currentPageField])
+ self.pageUpdated.emit()
+ self.pagesUpdated.emit()
+ self.dataUpdated.emit()
+
+ def refreshRequestFailed(self):
+ if self.m_currentPage != 1:
+ # A failed refresh. If we weren't on page 1, try that.
+ # Last resource on currentPage might have been deleted, causing a failure
+ self.setPage(1)
+ else:
+ # Refresh failed and we we're already on page 1 => clear data
+ self.m_pages = 0
+ self.pagesUpdated.emit()
+ self._clearModel()
+ self.dataUpdated.emit()
+
+ @Slot("QVariantMap", int)
+ def update(self, data, id):
+ request = self.m_api.createRequest(f"{self.m_path}/{id}")
+ self.m_manager.put(request, self, self.updateReply)
+
+ def updateReply(self, reply):
+ if reply.isSuccess():
+ self.refreshCurrentPage()
+
+ @Slot("QVariantMap")
+ def add(self, data):
+ request = self.m_api.createRequest(self.m_path)
+ self.m_manager.post(request, data, self, self.updateReply)
+
+ @Slot(int)
+ def remove(self, id):
+ request = self.m_api.createRequest(f"{self.m_path}/{id}")
+ self.m_manager.deleteResource(request, self, self.updateReply)
+
+
+@QmlElement
+class PaginatedColorUsersResource(PaginatedResource):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_model = ColorUserModel(self)
+
+ @Property(ColorUserModel, constant=True)
+ def model(self):
+ return self.m_model
+
+ def _clearModel(self):
+ self.m_model.clear()
+
+ def _populateModel(self, json_list):
+ self.m_model.set_data(json_list)
+
+ @Slot(str, result=str)
+ def avatarForEmail(self, email):
+ return self.m_model.avatarForEmail(email)
+
+
+@QmlElement
+class PaginatedColorsResource(PaginatedResource):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_model = ColorModel(self)
+
+ @Property(ColorModel, constant=True)
+ def model(self):
+ return self.m_model
+
+ def _clearModel(self):
+ self.m_model.clear()
+
+ def _populateModel(self, json_list):
+ self.m_model.set_data(json_list)
diff --git a/examples/demos/colorpaletteclient/rc_colorpaletteclient.py b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py
new file mode 100644
index 000000000..74b3eaf13
--- /dev/null
+++ b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py
@@ -0,0 +1,1098 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.7.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x00\xc0\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M22.5 38\
+V25.5H10V22.5H22\
+.5V10H25.5V22.5H\
+38V25.5H25.5V38H\
+22.5Z\x22 fill=\x22#66\
+7085\x22/>\x0a</svg>\x0a\
+\x00\x00\x00\x94\
+<\
+svg width=\x2230\x22 h\
+eight=\x2230\x22 viewB\
+ox=\x220 0 30 30\x22 x\
+mlns=\x22http://www\
+.w3.org/2000/svg\
+\x22>\x0a<ellipse cx=\x22\
+15\x22 cy=\x2215\x22 rx=\x22\
+13\x22 ry=\x2213\x22 fill\
+=\x22black\x22/>\x0a</svg\
+>\x0a\x0a\
+\x00\x00\x0b\x93\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00d\x00\x00\x00H\x08\x06\x00\x00\x00\x00\x8cP\x19\
+\x00\x00\x01\x84iCCPICC prof\
+ile\x00\x00(\x91}\x91=H\xc3@\x1c\xc5_\
+S\xa5*-\x0ev\x90\xe2\x90\xa1:Y\x10\x15q\xd4\
+*\x14\xa1B\xa8\x15Zu0\xb9\xf4\x0b\x9a\x18\x92\x14\
+\x17G\xc1\xb5\xe0\xe0\xc7b\xd5\xc1\xc5YW\x07WA\
+\x10\xfc\x00quqRt\x91\x12\xff\x97\x14Z\xc4x\
+p\xdc\x8fw\xf7\x1ew\xef\x00\xa1Qe\x9a\xd55\x06\
+h\xbamfRI1\x97_\x11C\xaf\x08#\x82^\
+\xc4\x11\x93\x99e\xccJR\x1a\xbe\xe3\xeb\x1e\x01\xbe\xde\
+%x\x96\xff\xb9?GD-X\x0c\x08\x88\xc43\xcc\
+0m\xe2u\xe2\xa9M\xdb\xe0\xbcO\x1ceeY%\
+>'\x1e5\xe9\x82\xc4\x8f\x5cW<~\xe3\x5crY\
+\xe0\x99Q3\x9b\x99#\x8e\x12\x8b\xa5\x0eV:\x98\x95\
+M\x8dx\x928\xaej:\xe5\x0b9\x8fU\xce[\x9c\
+\xb5j\x8d\xb5\xee\xc9_\x18.\xe8\xcbK\x5c\xa79\x84\
+\x14\x16\xb0\x08\x09\x22\x14\xd4PA\x156\x12\xb4\xea\xa4\
+X\xc8\xd0~\xd2\xc7\x1fs\xfd\x12\xb9\x14rU\xc0\xc8\
+1\x8f\x0dh\x90]?\xf8\x1f\xfc\xee\xd6*N\x8c{\
+I\xe1$\xd0\xfd\xe28\x1f\xc3@h\x17h\xd6\x1d\xe7\
+\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfe\x8d\x060\xfd\
+Iz\xbd\xad\xc5\x8f\x80\xfem\xe0\xe2\xba\xad){\xc0\
+\xe5\x0e0\xf8d\xc8\xa6\xecJA\x9aB\xb1\x08\xbc\x9f\
+\xd17\xe5\x81\x81[\xa0o\xd5\xeb\xad\xb5\x8f\xd3\x07 \
+K]\xa5o\x80\x83C`\xa4D\xd9k>\xef\xee\xe9\
+\xec\xed\xdf3\xad\xfe~\x00a\xaer\xa0\xbc\xa9O\xc0\
+\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\
+\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\x00\
+.#\x01x\xa5?v\x00\x00\x00\x07tIME\x07\
+\xe7\x0c\x0d\x09$9Q3\xe6g\x00\x00\x00\x19tE\
+XtComment\x00Create\
+d with GIMPW\x81\x0e\x17\x00\
+\x00\x09kIDATx\xda\xed]ilT\xd7\x19\
+=\xe7\xbe\x99\xb1\xcd\x1aC\xc0\xac\xb6\xb1\x81\xda\x1eC\
+T\x11\x10\xa4i\x135\x91\x12E\xa4KT\x14\xda&\
+$]\x14W(\xa9\x02\x81\x006\x84Q\xf0B\x96\x06\
+*D\xd5\xf6\x17\x0a\xaa\xda\x84F)R\xc2\xa2$\x85\
+&M\xd3\x85%\xc2cV\x8f1aKR\xb0qX\
+\xec\x99y\xef~\xfdAAN\xeb\xfb\xbc\xf0f\xf0x\
+\xe6\xfe\x9b\xf9\xde\xdc\xef\xbe{\xde\xf9\xb6{\xef\x1b\xa2\
+\x87\xad\xbc\xf9\x85Rh{'\x80\xf1\xc8\xb4\x844\x81\
+\x08{\x04F\xa4\xfav\xa1\xbcA\xa8\xfc\xcc\xb4%\x12\
+\x10\xe8n\x01\x09\x9e\xa8-\xa3-;AL\xc8LY\
+\xe2\x01Q\xdd1\x03\x8e\xb3=\x03F\xf2\x9a\x91!e\
+\xc7\xd6\x06\x95\xa5w\x00\x92\x01\xe3f3$\x18y~\
+&\xad\xf8\xb6\x0c\x18\xfd\x80!e'\xd6\x06\x95\xed\xec\
+\x043\xd1\xd4MgH0\xf2\xfcL\xda\xf6\xf6\x0c\x18\
+\xfd\x80!\xa5\x8d5\xe5\x16\xb1#\x03F?`H0\
+R7SQ\xb6e\xc0\xe8\x07\x0c)\xfd\xa4\xa6\x5c\xd9\
+\xd8I`\xdcM}:D\x84\x8a\xed\x10DE\xc4\x01\
+\x00\x92>\x00\xd9\x02\xc9\x22\xc8t`\x88O\xd9\xfam\
+B\xdd\x0c0: \xd8\x01\xca\x87\x8e\xb6\x22>\xb2\xd1\
+\xa6s\xae\xfdB\xbc}\xd8\xd8\xc1q\x00\xf8\xe2\xece\
+\x7fN\xae?'\x00=&\xae\xd5d\x05N\x15p6\
+!w\x01\x18:\x10A\xf1\x11jB\x12i\x00\x90{\
+\x04\xd8t\x99\xf1\xdf5\x17\x85.\xf4\xe0W\x17\x00\x9c\
+\x05\xb0\xffz\xc2\xdaT\x93G\xe1\x0f5\xf4\xcfHN\
+\x19P&\xab\xbc\xa9\xc6\x01\xa0\x92\x10=\xb4A\xb8(\
+\x90\x13{m\xef\xb8\xd0\x15/\xfa\x9c|\xec\xa9\xac,\
+k\xecRB\x9e\x060r \x98\xac\xe4\x00B\x1c\x12\
+\xc5\xef5\x14T\x1eLD\xf7\xc1\xc6\xda\xafQ\xc9\xab\
+\x00\x8az9\x03\xbfQ>l\xe9J\xe4h\xf5\x14E\
+\x7f;\xe9>$\x09Z\xc2v4\xfe\xe0\xe1\xd2Ps\
+\xa2T4L\xae\xfc\xb04\xb2\xe6~K\xa9w (\
+\xe8\xf9\xd0\xe4\xf0\x81\x82\x95\xefu]\xad\xa8y\x087\
+!\x8cP\x09\x06\xe3\x1c\xfc\xbe\x1f%\x12\x8ck\xedP\
+\xf1\xaacZ\xe1'W\x1dU\x0a;\xf5\xc4Z*\xd9\
+P\x9f\xbflOw\xd7\x956U\x17(\x8d\x87I\x94\
+\x80\x9c\x00\x91\x5c\x90\x1a@\xab\x10'\xe8H\x03rr\
+\xde\x0c\x8f\x7f\xe6\xa4[?\x07\x0b\xaa\xde\x9bv\xbc\xfa\
+%\x11<\x9b\x01\xe4\xff\xd1\x88\x5c\xa4\xfd\xa2\xdb%e\
+\x91\xd5\xf9\x14\xff*\x80?\xa6\xea\xc4\xd6N)\x07\x05\
+\x80\x22$\xda\xb1\xa6<R\xf3{;v\xa9\xeapi\
+\xddyS\x9f1\xcb\xffK\xbf\x1d\x7f\x14\xe0\xd8T\x04\
+$a&K\x93\xbfj\x9e\x14\xea0\xc9o;V\xf7\
+U\xa5\xb2vQ\xf1\xa7\xec\xc18\x08\x0c\x03Qae\
+\x0d\xf9\xdb\xb4\xa3!\xa3\xf3>\x92\xbf\xec\x8ch\xbc\x91\
+\xaa\x0cI\x0c \xe4yZ\xd9\x9bL\xe2\xf2\xa6\xf5y\
+\xb6\x92\xcd\x10)\xea=\xf10U\xfc\xfe\xcd\x08\x85\x8c\
+c\x8f_\xb17^\x0dZ2\x80\xfc\xd7\x99\xebm\x0d\
+\x13\x17\xb7\x98\xf1\xbaRAJ\xf0\x06\x82\x85;\xca\x17\
+\xf8\x17\x99\xc4G\xa7\x87\x0e\x83\xf8G\x06\x90k\x13\xae\
+\xf5\x9fM\xb2\x92C+F\x8a\xc8\x93\x1e\xa8y\xa4\xac\
+!\x140\x9aL-[3N\xfd*\x1cq\xfa\xf8\x17\
+\xa3B\xdf\xe0\x07\x00\x8c\xf2\x80\x86\xb7I\x8eo6\x80\
+\xf7\xbb\x1c\x85e\xfd\x13Z\x0b:/\xc2i\xfc\x09\xc4\
+\xc7\xd7>Z\x1a\x7f7>\xa9\x16\xdf\x16G>7j\
+W\x14\x8a~\x04\xf0\xb6t\x93\x00@t\xd3C\x05\xce\
+\x89\x03&\xb1\x85\xbb\xbcrT\x8ab\x04$\x1e\xf5\x1f\
+\x08\xf8;\xda\x00\xder}\x12\xfd\xd6\x9b\x0d\x05\xcb_\
+\xedI\xef\xf5\x85\x95\xdb\x00ls\xad\x104U\xcf!\
+0\xa5_\x9b,R\xed\x0b1\xa4]\xae\xb8\xd3\xbb\xc8\
+\x9a\xb3\x8d~d\xea\x92\xf3\x00\x1b\xd3\xde\x87h\xc8'\
+&\xd9\xf4OC\xa3\x05\xe2\xd9\x13%\x22s\xdc\xb2R\
+\x11}4\xed\x01\x11\x91SFY{\xa0\x94\xa0\xf2\x8e\
+\x8d\xcc\x9bu\xa8\xd6\x5c\xe5U\xea\xb3\xb4\x07\xc4\xd2f\
+@\xe8\xa0\xd0[\xf4\xc1+\x01=\xc9\xa8O\xe4\x5c\xda\
+GY\x9a\xbe\xd3\xc6\xf9\xf3#\x0f\x8e\xe7\x9c\xcc\x07`\
+\xa8\x97\xa9\xcf:\xe7\x87t\xf4\xbc`\xa4\xfaz\xfe#\
+\xd4o\x1d,z\xee\x83.\x93\xd7\xc6\xba\xef\x0a\x9d\xd9\
+\xdd\xf8\xcb\xa9^\xd72\xbd\x8f\xb2\x02h5\x82e\xcb\
+\x08\xafW\xc6\x85j\x84\x0b\x83Z\xbf\x5cB\x97\xb9$\
+\xe7v\x8a]\xcf\x02\xf8\xa0\xeb~\xf5\xbd$\x17vc\
+\x9fS\xc0\x87\xd8\xd1K.\xda\x02\xdeW\x05d\x88Q\
+\x9dO\xda\xd2\xdc\x87\x88\x040\xbc\xc3E\xec\xf7\x1c\x0f\
+ \xdbl?}\x97\xd3\x1a\x10\x01\xe5\x92\xdd\xe6\x96\x83\
+x\xbe\x06G*\xa3\xd9\xb5\xe3\x8e\xa49C2\xad_\
+\x01B\x08\x87\xf8\xdc\xfaL\x80\x17\x14\x89\x1bo.\xa0\
+\xac4g\x08\x19s\xb3\xe9.\x93\xd7\xf7\xca\x806\xfb\
+,mv\xf8\xe9\x93\xa9kk\xa8\x99A\xaa\xc3\xf3D\
+JY_\x98}\x9a=,\xed\x01\xf1Y\xd6\xad.6\
+\xad\xd5\xfb[\xd0-.\xa3\xb9%\xc3\x10\xcaD\x17\xf1\
+Y\xaf\xf5\xd96O\x9a\xf1\xd7y\x99(\xcb\x11\xe3\xd1\
+ij\xe7\x90\xb7a\xb68\x83\x06\xc7\x8d\xd5e\x0ao\
+\xcd\x00\x224n\xde\x0e\x0c\xca;\x04 \xe6]T\x87\
+S{\xc7\xae>o\x1e\x8a\x1e\x9f\xf6\x80\x08\xcd\x15\xdd\
+\xbd\xe3*\xae@\xe4\x80\x87q\xf6>\x90]\x87\xd2\x12\
+R\x04K\xd3\x1e\x10\x12\xe5\x85\xc7C\xd9.\x80\xed\xf0\
+L\x97\xa6qgIY\xf3\xa0\xd1\x00\x8a3&K$\
+?K0\xd5\x05\xb1\xbfz\xe4@lZx\xc7\x18_\
+\x89=K\x80\x9c\xbe\xa3-z`\x00\x02\xfa\x03\xf0\x1b\
+\xd7\x11\xa2\xf6\xa7\xbb\xa9e\xbf\x07\xd1\xdc\xbb\x07\x0a*\
+\xf7\xbb\xc8\xef\xe8\x9e\xcdb\xb9\x00~e\x80\x00\x02\x80\
+\xf8\x8eI\xd48eCT,\xeb\x85\x1b\x84#F\x91\
+\x8d&\xffQ&\xa1\x80\x80\xdd\x9e\xed\xd0B\xbf\x8b\xe9\
+m\x1b0\x80\x88\xe0\xbe\x92\xb3uF\xe7\x1e\x9e\xb4\xe2\
+5\x81\xbc\xd2wk\xc5\x17\xc2\xc5\xab\xde2\xdeTS\
+`.DJ\xba\xbfy\x0e3\x97dxn\xe00\x04\
+P\xbev\xbd\xd8\xed\x82\xecI\xa3\x96\x8b\xc8\x22\x11\xb9\
+\xd8\x0b\xea}\x0e\x8d\x9f7\x14U=g\xbc$\x14R\
+\xa0<\xdeC\xb3g<\xec\xaa\xc4\xb7\x0f\x80=P\x00\
+\x01\xc0\x87\xbf\xd2\xbc\xc6\xb8\x01a/+\xe2\x0d\xc5+\
+\xd7S\x10\xa4\xe23B\xd9!W7%H\xa7\xc4O\
+@i\x01e\x1b\xa1\x9f\xd4\xa2f\x86'Wmp\xd3\
+\x1a|\xd4?\x1f\x90\xb9=\xb3\xac,7\xb2\xb8x\xd9\
+\x1e\x00\x95\xc0\x97\xcb=\x22\x22\x00\xa2\x22\xb8\xe8eN\
+u}L\x09=c(\xfcC\xb8\xb8\xf2\xfb\xbd\xb0u\
+\xaa\xe4t].cz\x88\x95\x95\xa3y>v\xa9~\
+Z\xb4\x0d\xae\x1b\xef:\xf9\x8e\xc8\xea|K\x05v\x8b\
+`R\xcfL\x1f:Z\xb3\xe3#\xcf\xb8\x1cB\x9ds\
+rQN\x8b3b\x8c\xdf\x1ed\xfb%\xda\x11\x8d\xc5\
+\xda\x01\xc4\x0en\x81]\xbe\xc0\xbf\x0b\xc07\xbc\xab<\
+$\xe1\xd0\xa7\x08\x167\x14W\xadK4\xd5g\x9c\x09\
+\x0d\x8av\xf8\xdf\x050\xa7w&B\xcf;P\xb4\xea\
+\x8f}\xd1\x19l\xaa\xddJ\xc8\xb7\xbc\x04$\xf1\xc7\xa1\
+\x89\x9a\xe9\xc7\xea\x16&RGis\xcd\xd8h\xbbo\
+ko\xc1\x00\x00\x07j\xfe\x0d\xa4*\x17\xbd\xbe\x97d\
+,\xe1\xe6hKo\x9c\x16\xa9]7\xbd\xf1\xa5\xd1^\
+w^r\xaav\xa4%\xb2\x1d\xe4\xbd}\x8b\xd0\xf1`\
+\xb0y\xcd7\xfb6{LI@\xaeE4Ok+\
+\xf6Q0R\xbdp\xc6\x99\x90gU\xd8\xc3\xe3W\xb4\
+@X\x7f\x03]\x04 \xd6\xe6\xe9\xc7\xeb\xee\xef\xf5/\
+\xb5\x8e\xa6\x96S7\xb7\x16@\xb6\x13|\x1fb}\xe4\
+\x14u\x1c9\xc8\x909b\x91y\x16\xb8\xc5\xb8\xe7\xb1\
+\xbc\xa9&\x0f\x90\x8f\x01\x8e\xe9{.\x8bvM\xbe\xae\
+\xb4\xf3\xfa\xf9\x9c\xd1\xbb\xcf\x8c\xab\xe8\xd2\xd1\xdf\xbd\xeb\
+n\xdf\xbf'\xdeWH\x89\xcf\x86\xa5*\xbc\xdc\xcd\x9f\
+\xbc79t?\x92\x8b\xa04\x0bp\x9a\x1am\xa2\xd0\
+\x01R(\xcc!0N(A\x08\xe6\x87\x8b\xaa\x8c\xb5\
+\xab\xe0\xf1\xea\xc7(\xdc\xe4\xd1\x88.\x028,\x82\x93\
+\x10}\x89\xca\x22\x94\xce\x15\x07\x93\x14Y(\xc0\xe0\xc4\
+LC\x7f\x01\xa4g\xcf\xf0\xbf\xe2g.\xdcs\xe4\xce\
+\x17\xbb\xb4\xdb3\xf6<\xe1\xef\xc8\xcd\xdfN\xf2\x1e\xa4\
+hKJ\x94\xe5\xe1pg\xfa\xf2\x86/0&\x9a\xb7\
+\xff6\xaeb\xbeE\x90\xe4g\xd7)\xe9\xd4=\xe1\x88\
+\x85\xba\x92\xe3\xa1B\x93\xbc\xbety=\xc8\xd5\x19@\
+\x92\xd7\x86\xfa$\xb0\xd6\x95G\xed\xb1_\x03\x08\xa7\xa2\
+\xb9\x22\xb8,\x05\xb7\x92\xca\xbci\xcd\xb5\x0f\x98\xa4\x0d\
+\xc1P\x8b\x80U\xa9\xf4\x12\x1a\x11\x11\xa5di\xb8\xa8\
+\xf2\xe5T\xdc\xdb\xabD\xcb\xcb3\xf6<\xd1\xe5Z\xc6\
+\xe4c\xb5\xa3D\xcb\x18\x08/\xa5\x06\x18\xd0\xb4\xac\xa5\
+\xf5\x85+_\x01\x12\xfc6\xa0DVKb\xb9\x05k\
+\x00,\xbf\xf6\xc5\xacS\xb5#\xdb\xa3\xfaY\x81\xfc \
+U\xdeU/\x22\x02\xc5%\xe1\xc2\x15\xebnvb\xe8\
+Ek\xd5\x96\xf5\xf5\xac\x98\x16[\xe11!*\x00\x19\
+\x9e:>C\x1c\x82\xcb\xc2EU\xbf\xe8\xfc}\xaa2\
+\x04\x00ri\xdb[c\x8a\xa3\xc9\xd4zC\xe9\xd55\
+\x15.\x0dwQ\x05Oe@@2\xe5\xb6\xf9\x08\xc4\
+!\xb9,\x5c\xd4\xf5\x92DJ\x03\x92r`\xb80#\
+U\xf3\x90\xd4\x05\x03\xe2(\xa8%\xdd-\xd6e\x18\x92\
+4f`I}q\xe5\xfa\x81\x96\xa9\xa7(3\xb0\xa4\
+\xa1x\xe5\xfa\x9e\x5c\xef\x03\xe4q\xe9\xc1\x9f\x83eZ\
+\x1f\x03\x0f\x9b-\xf5S\xcd{\xc8\xfe\xb7\xfd\x07:\xcc\
+\xccF\x8ay\xc7t\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x02\xed\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M24 40C1\
+9.5667 40 15.791\
+7 38.4417 12.675\
+ 35.325C9.55833 \
+32.2083 8 28.433\
+3 8 24C8 19.5667\
+ 9.55833 15.7917\
+ 12.675 12.675C1\
+5.7917 9.55833 1\
+9.5667 8 24 8C26\
+.8333 8 29.3167 \
+8.575 31.45 9.72\
+5C33.5833 10.875\
+ 35.4333 12.45 3\
+7 14.45V8H40V20.\
+7H27.3V17.7H35.7\
+C34.4333 15.7 32\
+.8167 14.0833 30\
+.85 12.85C28.883\
+3 11.6167 26.6 1\
+1 24 11C20.3667 \
+11 17.2917 12.25\
+83 14.775 14.775\
+C12.2583 17.2917\
+ 11 20.3667 11 2\
+4C11 27.6333 12.\
+2583 30.7083 14.\
+775 33.225C17.29\
+17 35.7417 20.36\
+67 37 24 37C26.7\
+667 37 29.3 36.2\
+083 31.6 34.625C\
+33.9 33.0417 35.\
+5 30.95 36.4 28.\
+35H39.5C38.5333 \
+31.85 36.6167 34\
+.6667 33.75 36.8\
+C30.8833 38.9333\
+ 27.6333 40 24 4\
+0Z\x22 fill=\x22#66708\
+5\x22/>\x0a</svg>\x0a\
+\x00\x00\x01\xb3\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M13.05 4\
+2C12.225 42 11.5\
+187 41.7062 10.9\
+313 41.1188C10.3\
+438 40.5312 10.0\
+5 39.825 10.05 3\
+9V10.5H8V7.5H17.\
+4V6H30.6V7.5H40V\
+10.5H37.95V39C37\
+.95 39.8 37.65 4\
+0.5 37.05 41.1C3\
+6.45 41.7 35.75 \
+42 34.95 42H13.0\
+5ZM34.95 10.5H13\
+.05V39H34.95V10.\
+5ZM18.35 34.7H21\
+.35V14.75H18.35V\
+34.7ZM26.65 34.7\
+H29.65V14.75H26.\
+65V34.7Z\x22 fill=\x22\
+#667085\x22/>\x0a</svg\
+>\x0a\
+\x00\x00\x01\xf7\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M9 39H11\
+.2L33.35 16.85L3\
+1.15 14.65L9 36.\
+8V39ZM39.7 14.7L\
+33.3 8.29998L35.\
+4 6.19998C35.966\
+7 5.63331 36.666\
+7 5.34998 37.5 5\
+.34998C38.3333 5\
+.34998 39.0333 5\
+.63331 39.6 6.19\
+998L41.8 8.39998\
+C42.3667 8.96664\
+ 42.65 9.66664 4\
+2.65 10.5C42.65 \
+11.3333 42.3667 \
+12.0333 41.8 12.\
+6L39.7 14.7ZM37.\
+6 16.8L12.4 42H6\
+V35.6L31.2 10.4L\
+37.6 16.8ZM32.25\
+ 15.75L31.15 14.\
+65L33.35 16.85L3\
+2.25 15.75Z\x22 fil\
+l=\x22#667085\x22/>\x0a</\
+svg>\x0a\x0a\
+\x00\x00\x1a\x93\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00J\x00\x00\x00H\x08\x06\x00\x00\x00Q\x18cz\
+\x00\x00\x01\x85iCCPICC prof\
+ile\x00\x00(\x91}\x91=H\xc3@\x18\x86\xdf\
+\xa6\x8a\x22-\x0ev\x10q\xc8P\x1d\xa4\x05Q\x11G\
+\xadB\x11*\x84Z\xa1U\x07\x93K\xff\xa0IC\x92\
+\xe2\xe2(\xb8\x16\x1c\xfcY\xac:\xb88\xeb\xea\xe0*\
+\x08\x82? \xae.N\x8a.R\xe2wI\xa1E\x8c\
+w\x1c\xf7\xf0\xde\xf7\xbe\xdc}\x07\x08\x8d\x0a\xd3\xac\xae\
+q@\xd3m3\x9dL\x88\xd9\xdc\xaa\xd8\xf3\x8a\x10\xc2\
+4\xc7\x10\x93\x99e\xccIR\x0a\xbe\xe3\xeb\x1e\x01\xbe\
+\xdf\xc5y\x96\x7f\xdd\x9f#\xac\xe6-\x06\x04D\xe2Y\
+f\x986\xf1\x06\xf1\xf4\xa6mp\xde'\x8e\xb0\x92\xac\
+\x12\x9f\x13\xc7L\xba \xf1#\xd7\x15\x8f\xdf8\x17]\
+\x16xf\xc4\xcc\xa4\xe7\x89#\xc4b\xb1\x83\x95\x0ef\
+%S#\x9e\x22\x8e\xaa\x9aN\xf9B\xd6c\x95\xf3\x16\
+g\xadRc\xad{\xf2\x17\x86\xf2\xfa\xca2\xd7i\x0d\
+#\x89E,A\x82\x08\x055\x94Q\x81\x8d8\xed:\
+)\x16\xd2t\x9e\xf0\xf1\x0f\xb9~\x89\x5c\x0a\xb9\xca`\
+\xe4X@\x15\x1ad\xd7\x0f\xfe\x07\xbf{k\x15&'\
+\xbc\xa4P\x02\xe8~q\x9c\x8f\x11\xa0g\x17h\xd6\x1d\
+\xe7\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfej\x03\x98\
+\xf9$\xbd\xde\xd6\xa2G@\xff6pq\xdd\xd6\x94=\
+\xe0r\x07\x18|2dSv\xa5 -\xa1P\x00\xde\
+\xcf\xe8\x9br\xc0\xc0-\xd0\xb7\xe6\xf5\xadu\x8e\xd3\x07\
+ C\xbdJ\xdd\x00\x07\x87\xc0h\x91\xb2\xd7}\xde\xdd\
+\xdb\xd9\xb7\x7fkZ\xfd\xfb\x01\xa7vr\xbc\xf6x\x14\
+.\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\
+\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\
+\x00.#\x01x\xa5?v\x00\x00\x00\x07tIME\
+\x07\xe7\x0c\x0d\x0d+\x19\xea\xccr\xbc\x00\x00\x00\x19t\
+EXtComment\x00Creat\
+ed with GIMPW\x81\x0e\x17\
+\x00\x00\x18jIDATx\xda\xed[y`T\xd5\
+\xd5\xff\xdd\xfb\xde\xac\x99}\xb2/da\x93E@P\
+\xb1\xdaZZ\xfd\x94-\xc8\xe2\x04\x14\xb7JK\xe5\xf3\
+\x0b&\x80\x88\xd5\xb6Q\x8b~\x22\x90\x80b-\xa2b\
+QI\x18\x11H \x91\x8a\x15k\xddQPY\x0ck\
+\x12\xb2'\x93\x99\xc9\xccd\x96\xf7\xde\xbd\xdf\x1f\x81@\
+\x08\x89Qh\xc1\xaf\x9c?g\xee\xbcw\xde\xef\x9d\xfb\
+;\xe7\xfc\xce\x1d\xe0\x92]\xb2Kv\xc9.Y\xaf\xcd\
+\x91\xb7O\xedpl\x10.6\xbf\xc8\xc5\xe2\xc8\xf4\xe7\
+\xf8H@~\x82s\x0c\x03\xe1!\x0a\xfa1\x04\xe9\x0f\
+\x85st\x15\x97\x80\x02\xe0X\xe2\x8f\xa7:\xed\xef\x08\
+\xf8o8\xa0=\xe3\xebVp\xaca\xb2\xb8\xd89\x8f\
+\xb4\xfcG\x02\xe5\xd8\xc0\x05\xda \xfd\x0f\x07\xfd=\x01\
+\xb7\xf7\xec%\xa9\x00g\x8f\x17e\xab_\xf9\x8f\x02j\
+\xc6Jy\x02'\xfc\x7f\x01\x0c\xfd>\xbf\xe3\xe0\xdf\x10\
+\x8aYE\xf7\xab?\xff\x7f\x0d\xd4\xa4\xa7\x03\x89:\xbd\
+\xfae\x007\x9f\xcbu8\xe7\xaf\x8bT\xf5\xc8\x1b\xff\
+C*\xff_\x015\xe9in\xd4F\xc9\x8f\x10\x86\x5c\
+\x10\xa8\xbb[\xa7\x84$\xb8\x0f\xd6B\xd0\xa9aJ\x8d\
+\x86\xa0V\xf5t\xd9V\x0e\xb6\xbc\xd1\xa5^\xbc3\x8f\
+\xc8?z\xa0\xb2V\x86o#Dx\x0e\xe0\xb6n#\
+Da\xf0V6\xc1_\xed\x02g\x1c\x00@U\x14\xa6\
+\x94\x18\x18R\xec \x84\xf4\xc8_DQ\xe6\x15>\xa0\
+\xd9\xf4\xa3\x04\xea\xd6\x95\xd2\xcfD\x82\xa5\x1c\xb8\xba\x87\
+-\x84`S+\xdc\x07\xeb\xc0d\xe5\xackD\xad\x1a\
+\x96~q\xd0E\x9b\xbekC~\xaa\x12Tw\xbf\xf6\
+\xdf\xa4\xfcG\x01\xd4\xac5\xdc\xe6o\x93\x0a@\xc8\x9d\
+=\xad\x0b{\xdb\xe0>T\x0f\xc9\x1f\xec\xd5u5\x96\
+(\xd8.K\x84\xa8U\x7f\x17\xe1\xbf\xc0\x15\xd5S\xce\
+\x1cRuQ\x025s%7)T\x9e\xc3\x81\x05\xe0\
+\x88\xee\x96\x87\x22\x12Z+\x9b\xe1\xaf\xf9ae\x911\
+\xd9\x0eC\xb2\x1d\xa2\xb6g\xfe\x22\x9c,PY\x847\
+\xd6\xddE\x02\x17\x0dP\x8e\x15\xd2X\x22`\x09\xe1\xb8\
+\xbc\xdb7-3\xf8j\x5ch\xadl\xea\xe0\xa1\x1fj\
+\x82F\x05sZ,\xa2\xe2-=?\x01A9!\xe4\
+\xd1\xc2\xfb\xc57/8P\xb3\xd7*s\xbd>\xb6\xa2\
+\xa75A\x97\x0f\xde\xa3\x8d\x90\x02\xa1\xf3\xba\xcdU\x06\
+-,\x19q\xd0\xda\x0c=p=`\x8c\xa2\x8b^\xfc\
+\x95\xf0\xf4\xb9\xdc\x8b\x9e\xab\xb3\xdc\xef\xbf\x22%^\x80\
+^wv\xcc\x99\xac\xa0\xad\xa9\xf5\xbc\x83\x04\x00\x92?\
+\x04_\x8d\x0b\xbc\x9bD\xa0\xd3\x12$\xc4P\xc0\xe7\x1b\
+p\xae\xf7\x12\xcf\xf5\x02\xfb\xfe~\x14q\xe96)\xed\
+\x8ax\xc1\xa0\x17\xa9\xdb\xc3 \x9d\xe67\x15\x05\xd8/\
+K\x82!\xc1\x0a\xcf\x91zDZ\x83\xe7'\x9a\xa2\xb4\
+\xb0\xf4\x8b\x87\xd6\x1a\xd5\xf5\xa1\x04\xc0f\xa6\xa0R\x84\
+\x1d\xfe\xb0\x96\xb9\xaa[\xc9\x05\x07\x0a h\xa8p\xab\
+\x9a\xab[\xa5\xb8t\x0bRG$ (\x11\xea\xf2\xb0\
+\xceY\xcb\xacG\xdc\xc8\x0c\x04\xea=\xf0\x1ci\x00\x93\
+~X\x8dHE\x0aC\xb2\x1d\xa6\x94h\x10\x81v\xe1\
+\x11\xb3\x91\xc2\xa8\xe7J\xcd\xfe&\xd4|\xdbDeI\
+\x11A\x08\xbf\xe0@q\xc2\x09\x01\x81\x22+\xaa\xdaC\
+.\xb8j[\x91:4NJ\xedkS\xb5x\x19|\
+\x81\xce>F\xc5[\xa0\xb5F\xc1_\xebFke\xd3\
+\xf7\xcbxIv\x18S\xa3!\xa8\xbb\xba\x1d\xa5#\xb0\
+[(\x9a\x8e\xb5(\x07\xf6\xd4\x139$\xd3\x19\xd7\xd7\
+`o\xa5\x11{+M\xb8\x08\x22\xea\x8c\xfa( \xe1\
+\xe0\xa7\xd5\xaa\xe3\x07\x9a\xe4!?M\x859^+6\
+63Dd\xde9k\xa5\xc7\x22*\xc1\x0a\xf7\xc1:\
+\x84Z|=^Sm\xd2\xc1\xda/\x01j\x93\xae\xeb\
+\x03\x88@\x9cM\x80\x1c\x0c\xc9{\xdf\xad\x15\xbc\x8d~\
+\xe1Z\xa1\x12aQ\xc4\xc8\x0c/v~c\x07\x01\xf8\
+E\x00\x14\xc5\xd9\xfc\x08\xb6\x86\xc5]\xa5\x07\x11\x93j\
+\x8e\xf4\x1b\x95\xa8\x0eC\x84\xdb\xcbp:\xef\x8aZ\x15\
+b\x86\xf5A\xd8\xdb\x86\x96\xf2Z\xc8m\xe1\xceWV\
+\x09\xb0\x0dL\x82\xd6n\xe8\xd2\xc6\x10\x02D[)t\
+*\x8e\xf2\x8f\xab\xe4\xe6*\x8fh\x89mC\xd2\xc00\
+\xa6T\xee\xc7\xb2\xc8O\xb1pmR\xfbZ\x8e\x8b/\
+\xa2\xce\xb4\xa6J\xaf\xba\xb9\xaa\x15\xa9\xc3b\x918(\
+\x16\xfe \xe0\xf6v\xe5\xaf\x84\xab\xfa\xc1_\xe3\x82\xb7\
+\xaa\x19\x5cV`H\xb1\xc3\x9c\x1a\x03Bi\xd7t\xaf\
+\xa7\xb0Y\x08\xea\xca]\xf8\xfa\xabz\xc8\x92\x22\x02\xc0\
+\xe8[\x8e\xe2\xc0G\x09x04\xaes\xe6\x05\xbb\xf8\
+\x81:\xd9\xd3U|\xd5\x80\xfa\xc3nd\x8cL@j\
+\x8a\x19\xcdn\x86@\x90wbbC\xb2\x1d\xfa8\x0b\
+\x00\x0e\xaa\xea\xea\x9aVC\x10k\xa3\x084\x07\xf0\xd9\
+\xa6*\x84\xdb\xa4N\xdf\xbf\xf3\xca`0\x99\x9e\xa5\x96\
+\x12\xd8\x85\x07J\x11\x9f\xe7\x824\x86\x00\xa9\xdf\xb54\
+\x14\x88`\xff\x07\x95\x88\xb2j1\xf8\xfa4XLj\
+4\xba\x14\x9c\x9e\x00\xa9J8k\xba\xb7[(\x88,\
+\xe1\xdb\x0f\xea\xe0\xaa\xf6vS\xb3\x9d\xb5,t\x83\x93\
+\xd7.\x8a\x16\xe6g\xd9\x87b\xd4\x11\xfaN\xc8\x13\x18\
+\xce\xe5\xde\xbf\xbc\xf8~v\xa4\x0f\x8bE\x88\x8b\xf0\xb6\
+v\xae\xbf\x00\x80\x12\xc0j\xa6\xd0\xab9\xaa\xbei@\
+Mys\xaf\xdb\x1f\x22P\xe8l\x86]A\x8f\xe7\x96\
+\x0f\x0bG\xd6^P\xa0&M\xcd\xb9\x8ed\xdc`\xd3\
+\xa5\x8e]\xc6\x81\xferH\x82\xf7h\x03\xda\x1a\xbd\xbd\
+w\x80\x12\xf4\xbd2\x11q\x196x\xfd\x1c\xa1\x08\xc0\
+\x19\xa0\xd5\x00V\x13Ec\x85\x1b\xc7\xf6\xd4A\x0a\xf6\
+\xbe\xee\xd2Z\xa3`\x1d\x98t\xa2q\xe6\xfb\xa5\xa3\xa5\
+\x8fJ\xc7w\x86J\xde,(\xfb\xc1\xfd\xe5\xb9\x005\
+h\xf4X_\xca\x15\xe3WP\x8dqTDj\xaf\xc2\
+\xf51&hLzH\xbe\x10\x98\xa4\xf4\x82\xc0\x80\x96\
+\x1a\x1f\x9a+\xbd0[T\xb0YE\x18t\x80\xec\x0f\
+\xe2\xdb\x0f\xabPw\xd0\x05v\x22J\x09%P\xa9E\
+0\x85u_F\xf4O\x809#\x0eTl\x7f4\x83\
+\x9e\xc6\x98-\xa6\x18_\xf9\xae%\xe5\xe5\x1fF.\xd8\
+\xd6\xbb\xf9\xd7\xdf\xbe\x9c:<a\xa6!\xce\xa8nv\
+3\x84#\xbc\x03\x80@\x83\x1b\xde\x8a&(!\xa9\xf7\
+\x0e\x11\x02\x10t\xd9b\xa2\x8a\x22q`4\x98\xc2P\
+}\xa0\xf9\x8cj]\x801\xc5\x0ec\xb2\xbd\xa3Z\xd7\
+\xaa\x09l\x16\x8a\xb0'\xc0\x8f|Q\xf7\xca\xf6\xd5\x03\
+f\xfd[\x9a\xe2\x89Y\xb9\x99\x9c\xf3.\xc0\xfaZB\
+d\xef\xcec\xea\x8a]\xc7\x11mP\x10k\xa3\x10\x84\
+\xf6W\x10\x15oE\xdc\x88t\x18\x12\xad=\xcb\xb9g\
+d\xc83A\x8a\xefgCB\x7f\x1b\xd4:\xb1\xcb\xab\
+5&\xd9\x10\x7fu_\x98Rc@\x04\x0a\x81\x021\
+V\x0a\xabNa\x15_TG\xbe\xdaq\x94\xfb\x5cm\
+g\xa7\x8e\xdbr\xc7\x8e\x1a5[u~\xb3\x1e'\xfa\
+\xcc\xacy\xaf\xdd2#\xf7\x85-\x85\xf9\x1f\x9c\xf6`\
+\x84\x10\x82\xfa#n4W\xb5\xca\xf1\xfd\xedH\x19\x1c\
+#\xb6I\x14\xeeV\x06A\xab\x82u@\x22\x8c)v\
+\xb8\x0f7 \xe4\xf2\xf5\xfa\x96\xf6d#\x0cV\x1d\xa8\
+H\xc1\x15\x8eHPF\x9b'\xdc\xa1x\x9a\xd3c\xa1\
+1\xeb;\xd6[M\x14\xe6(\xa0j_#\xaf\xf9\xb6\
+\x99&\x98\xfcj\xa3VDk[g,&e\xcd\xbf\
+^\x01\xbf\x1b2w~\xf1\xc5j\xe9\xbcr\xd4\xc1\xfd\
+\x1f\xef\x1b\xf3\xd3a%\x9e\x88\xe6\xa6\x81\x83G\xcfM\
+\xed{\xe5gG\xcb?\xf3\xa5\x0c\xb9o2\x01\x19\x01\
+\x00\x8cq\xda\xda\x14\xa0\xcdU^\x18\xcdj%!Y\
+G\x15\x05\x88H\x00U\x89\x88\x8a3C\x15\xa5\x85\xe4\
+\x0bv\xf0NO\x16\xdb\xc7\x02A-\x00\x1c\x08\xfa\x22\
+\xa8-w!\x12a\xb0\x0dJ\x82%=\xb6C\x16\xd6\
+k\x09\xe2\xec\x02B\xcd\xbe\xc8\xde\x7fT\xb0`u\xb3\
+@\xa1 \xff\xb7\xfbP^mD\x83[\xfb\xe5\xf1\x03\
+/\x14\xdf2c^J\xff!\xa3W3\x10\xbdMC\
+\xff\xe0\x5c\xbf\xfc\xc0\x0f\xe6\xa8\x89\x8e\xdc$\x86\xb0R\
+\xea|\xbe\xbe\xbb\x1f\x8d\x9d\x91\x93&*4\x1bP\x94\
+\x16vW\x02 \xdcq\xd6&6Z\x8b\x81\xa3\x93!\
+\x1a\xf4\x9d\xea%\xce\x18\x02u\x1ex\x8e5v\xab%\
+\x01@\x9f\xa1\xb1\xa0\x94\xa0j\x7f\xbb*jL\xb6\xc1\
+\xd4'\x16Dlg\x0c\x81\x02q\xd1\x02 EP\xfe\
+Q5<\x0d~P\x91\xe1\x09\xe1\x1d\xbc-\x0fDs\
+\xa2\x1d\x07\x8e\x1bA\x10*\xb2\x08\xeb?\x07H\x7fY\
+\xc6c\xdb\xdf*\xa8\xeb\xee\x9e\x93'\xe7X\x14\x9d \
+\x96\xac_\xd6\xdc#Pcg\xe4\xa4\x89\x8c\xe4\x82\x12\
+\xf7\xd6\xf5\xcb\x1e\xebI\xa2\x980}\xc1P\x9f\xf2_\
+\xaf\xcb<nX\x8f\xf5R\x7f;\xd2\x87\xc7#\xa4P\
+\xb4x\x19\xd8\x89`\x92\xc32|UM\xdd\xea\xe7\xa2\
+F\x80\x1cV\xa0\x8b5\xc3\xd6/\x1e\xf4\x84j@\x08\
+`5R\x18u\x1c\x87v\xd5\xa0\xf1\xa8\x1b\x9c\x03\xa3\
+\xc6\x1d\x83\x1c\x11 \xbc'\xe2+\x16\x0f\xe5\x04\x05\xab\
+H\xcd\xa1(\xf1ow\x96\x16\xae\xf8\xb4;\x1f\x07;\
+\x1c\xea\x0c\xd2'\x87\x83\xa5\x12\xae<\xbd\xd5\xb9\xb2\xaa\
+\x0bP\xe3o\x9f\x9f\x0a\xa6\x5c_ZX\xb0\xeeTd\
+-\xbc\x1a\x90\x9e\x86\xa0\xfcik\xe1\xcaw;\xed\xf1\
+I\x0b\x8d\x8aV\x9aJ\xb4\xd8\xe1\x0a\xdc\xb3\x98\x80\xdf\
+\xfd\x9dd\xa8\x11\x91<(\x06\xc9\x83b\xe0\xf5sx\
+|\xa7\xb6\x9e\x1c\x8c\xa0yo\x15\xa4@\xb8\x8b8g\
+\xed\x1f\x0f\x8d\xe5\x948g\xd4S\xd8\xad\x04\xf5\x87[\
+plw\x1d\xe4\xc8\xa9\x88\xec{E#*\xf7FC\
+\x96\xe8\x19\x09\x82\xbd\xa4a\x7f]`R\xd3\x89\x08y\
+\xb6\x14\x17\xbf\xdc\x89('d\xe5\xdeH\x81\xf9\x0cX\
+\xb6mC\xfe\x8eS\x810\x7f\xaa:\x12z\x7f\xd3\xa6\
+U\xae\x8e\x88\x1a\x9f\x95\x9bE9\xee\x0c3e\xde;\
+\x1bW\x1e:\xf9yfVn.\x07\x1f!F\xf0\xc7\
+\xcd\x9b\x0b*&N\x9f?\x93@\x99*+\xe4/T\
+\xe0\xd3\x82\xca\xa8)!~yL\xaf\x8bA\x83\x1a\xe9\
+#\x13`O6\xa3\xc1\xc5\x10\x0a\x9f\x0a\xd8\x90\xcb\x87\
+@\x83\x17 \x04\xfaX\x13tv\xe3\xa9\xc6Y\x0d\xc4\
+X\x05\xb4\xb5\xf8q\xf0\xd3\x1a\x04[\xc3\xbd/@\xc9\
+\xber=\xdd\xb5\x9b\x82?\xc79\x99\xc7)/\xdbZ\
+T\xb0f\x92#\xb7\x1f'\xe4I\x0e\xfew\x1d\xaf~\
+\xd9\xe9tF\x00`\xe2\xf4\x9c\x91\x9c\x93\xc7\x05NV\
+\x17;\x97\x17w\xd9zc\xc6\xe4i\x8d\xd1\xde<F\
+\x01U\x84\xbf\xb0ysA\x05\x00\xdc4eA\xacF\
+%?\xcaA\x86\x80\xe3\xb9\xad\xce\xfcMc\xc6\xe4\x89\
+;w\xe6\xc9\xe3\xb2r\xae\x8b\xb0Q\xeb\xc2HOg\
+\xdc\xd0k\xe7\xcd1Q\x18\xf4\xb3>P\x04\x15\x5c\xee\
+\xce\xfd^\xa7lC\xdb\xfb<\x15\x14\x1c\xdb]\x87\x86\
+c\xee\xefQ\x93\x85\xa1Ay\xb9\x06_\xe6\x96\xbdY\
+P\x06p\x02\x10\x9e9}\xc1-\xe0\xca\x03\x9c\xa0B\
+\x11\xcd9e\xaf\xe7\xb5\x02\x80\xc3\x91\x1d\xd3F\xc4\xf9\
+\x04\x90h\xc8\xbb\xe4\xf4\xc8#\xdd\x91\xb5\xc00\x8fp\
+ZQw\xd4\xff\xec\xe9)t\xf2\xe4\x1c\x8b\xac\xa1\x0f\
+\x83\xf3\x0cF\xe8\xf6\xd2\xc2\xa5/e=\x17\x99\xc9\x82\
+\x81\xb5\xad\xb5~\xc1_\xe3\x06\xbe\xc78*iP4\
+\xfa\x0c\x8aAP\x11\xe1\xf6\x9d\xe2/B\x00\x8b\x91\xc2\
+\xa8\xe5\xa8\xda\xd7\x88\x9a\xf2\xe6^e\xca\x93\x15|T\
+\x82\x0d\x86D\xbdG\xd4\xeb\xe7\x1fy\xf5\x81uI\xe9\
+Q\xd3\x18\xe5\xb7\x81\xd2}\xac5\xb4\xb2\xb4\xf4T\xb2\
+\x1a\x959[\x9f\xa0\x8f\x9a\x0b\xf0\xc1r\x18\x7fx\xfb\
+D\x80\xf4\xba2\x9f8c\xee\x0dP\xc4\x1c\x85\xf0\xd5\
+\xa3\x07\x9b\xb7}\xbe\xdf{'%\xb8\x81\x81?\xb5\xad\
+\xa8\xe0@fV\xcem\xa6A\x93\x17(1\xd7\x8d\xe4\
+'\xb0\x91C\x11\xb8\x0f\xd5!\xe4\xf2\xf7^\xc2P\x0b\
+\xe8;*\x11\xf1}m\x08\x8698\x07t\x1a\xc0]\
+\xef\xc7\xc1\x8f\xab\x11n\xeb}\xe7\xa1\xb1\xe8a\x1b\x90\
+\x08Q\xaf\xe9h\xac\xa9\xeb\xa3\xc3\xad\x07\x8a\x1f/)\
+\x5c\xb6n\xe2\xb4\x07\xfbC\x94\xf38\xc7\xdf\xb6m\xc8\
+\x7fuR\xd6\xbc_0\xce\xfe\x9b\x13a\xcd\xb6\x0d\xcb\
+\xb6\xff\xe0\x16&//\x8f\xee\xda\xef\x99\x03\x90\xe1\xe0\
+\xec\xdd\xad\xce\x15E\x9d\xe6z\xcfz^\xb6$\x9a~\
+\xe5\xf20\xf8\xdbNER[c+\xbcG\x1b \x87\
+z\xff\x90z\x93\x16\xd6D#\x08\x01\xbcM\x01\xf8\x9a\
+\xdb\xce\xd8\xaez\x08j\x01-5]\x8bV\x95N\x03\
+Sz\x0c\xf4\xb1\xe6S\xd7\xd3\x12\xd8\xcc\x94\xb5\xd6\xb5\
+\xbe\xf2\xe2\x03\x96_\x9f\xbe~|Vn\x16!\xb8\x9d\
+3|X\xea\xcc\x7f\xe6_\xde\xeb];u\xf7+\xe6\
+X\xc3\xed\xfd\xaeNV\x04\xbdF\xe7\xf2(\x88H'\
+\xeb%\x0e\x7fm\x0b|\xc7\x9b\xa1\x84\x7f\xf8\xc9\x9c(\
+\x8b\x16\x1a\xbd\x0a\xb6$#B\xfeH\xa7^\x8f\x88\x14\
+\xa6d;\x8c\xa91\x1dm\x92Z\x0d\xd8\xcd\x14\x92\xaf\
+\x8d\x1f\xdeU\xc7}M\x81W?z\xeb\x8a{/\xf8\
+\xb8\xca\xdb\x14P\x7fQz\x90\xc5gXy\xea\xb0X\
+\x22\x11\x15<^\x06\x09\x04\xc6d;t\xd1&\xb4V\
+4\xa2\xad\xc1\x0b\xce{\xcf_*\xad\x08\xbdI\x03[\
+\x92\x11L\xe1\xa7\x9a\xe6\x93\x11\x13k\x86)=\x16*\
+\x9d\xba\x83\xd7lf\x0a\xad\xa0\xb0\xea\xbd\xf5\xbc\xf6p\
+\x0ba\x92B/\xaaq\x158\xa7\xf5GZ\xe0\xae\xf5\
+)\x89\x03\xa3\x95\xf8\x016Uk\x1b!\xbe\x00\x87\xa8\
+U\xc1vY\x12\x0c\xc96x\x0e7 \xec\xe9\xdd\xb9\
+\x89\xc4~6\x08j\x01\x9cs\x10\x00\x8d\x15\x1e\xb4y\
+\xc3P\x1bu\xb0\xf4\x8d\x87\xc6r\xaa\xcf3\x1b\x08\xac\
+f\x8a\xe3{\x1bP}\xa0\x99\xca\x11\x05\xf7\xdcp\x1c\
+\xbb\x8e\x98\xb1\xb7\xe2b\x1cW\x05%\xe1\xd8\x9e:\xa1\
+\xee\xb0\x0b}\xafLTR\x12LB\xb3\x87\xa1-\xc8\
+\xa16\xe8\x10;\x22\x0dm\xf5\x1ex*\x1a\xbfS~\
+\xe1'\xc8\xc1u\xdc\x87\x90/\x0cY\xe1\xed\x873\x12\
+\xac \x94\x9c\xd0\xd1\x81h\x8b\x80\xa0\xbbM\xda\xb3\xbd\
+\x96\xf8\x9a\xdb\xc4\x1b\x85ChS\xa9\xd17>\x88\xd2\
+/b/\xeeqU\xc8\x1f\xc1\xbe\x9d\x15\x82%\xde\xc8\
+/\xbb&\x89\x98\xa2Tp\xb9\xdb\xe5^}\xbc\x05Z\
+\xbb\x11m\x0d^x\x8e\xd6w+\xef\x06\xbda\x04\x03\
+\x11\x04\xdc!\x18R\xec\x88I\x89\xee\xd0\xd4E\xa1}\
+\x5c%0\x19\xe5\x1fT\xa1\xa5\xd6\xa7\x8a\xe9\xe3\x83u\
+h\x04\xd7\x1d\xaa\xc4\x9f\xa5k\xf0\xfb\xd7\x0d?\x9eq\
+\x95\xa7\xdeG>\xd9\xfc-\x92\x07\xc5 mh\x0c\x02\
+\x92\x00\x97\x97\x81\xaa\x04\x18\x92m\xd0F\x1b\xe1=\xd6\
+\x80\xb6\x86\xae\xf2q\xd3q/\xb46\x03\xe2\xae\xea\x0b\
+\xd5\x89tO\x08`6PX\x8d@\xf5\xfe&T\xed\
+k\x84,\xb5\xd7W#n\xa8\xc2W\xef\xa5\xe0\xb1\xf0\
+\x8d?\xceq\x15\x00T\x1fhBs\x95\x07)\x83b\
+\xd0g@4\xbc\xbe\xf6~O\xd4\xaa`\x1f\x94\xdc\xae\
+W\x1d\xac\xeb8\xc4!\xea\xd5\xb0\xf6K\xe8t\xa4\xc7\
+\xa8oW-=5^|\xb2\xa3\x06\x913t\xf4\x1d\
+k\x07\xe3,\xda\x22\x08\xa8r\xee)\xeb\x1c\xed\xea)\
+{\x06\x08\x14\xdb\x09\x90\xf6}\xfa\xbdacR\xa16\
+\xe9P\xdb\xd8\xb9}\xe1\x0a\x03g\xbc\xd3\xd8J%\x02\
+16\x01,\x10\xc4\xa1\xcfk\xe1i\xec\xfd!:\x0e\
+\xd4sQ\xf8\xe5'E\x97\x1f\xb8\xa0\x11uE\xe6p\
+\x11L6\xc7\x86\x9b\xb0\xe7\xcb\x16\xb8<\xdf]/\x85\
+\xfc\x11|\xb6\xf5\x10\xe2\xd2\xcc\xc8\x18\x99\x88\xc8\x89q\
+\xbb\xa4\xb4\x8f\x99\xc8\x09\x8c(m\xaf\x87\xf4*\x86\xa3\
+\xbb\xabQ{\xa8\xf7\xc7\x19\x0dz\x8a\xd1\xd7\xc6\xc2\xad\
+\xb1\xb5IP\x91O\x8a.\xf0\xd6k\x0bJ\xf3\xef\xbf\
+Y\xb4\x8e\xee\x9b\x00\x97'\x1a\xafnl\xc0\xc62W\
+\xaf~\xdbP\xe1EC\x85\x17\x19#\xe2\x918\xc0\x0e\
+\x7f\x84\xb6O\x8fy{6\xb3\x18\x08j\xcb\x9b\xf0\xf5\
+7M\x90\xa5\xde\xed\x1eB\x80\x9f\x8f6c\xd1\x9c\x14\
+\x18\xa3\x04\xbc\xb7\x9fe\xbc\xf4\xbe\x94\x0d`\xce\xbfe\
+\xb80e\xca\x82\xd8\xb3}\xae3\xa8\x1e[\xf5\x8e\xb2\
+\xf1/\x7fW\xc0\x04\x15\xe6\xcdJ\xc6k\xf9\x031\xa4\
+\x7fT\xaf\x9d8\xba\xa7\x1e\xbbJ\x0e\x22\xd4\xe4\x81U\
+-\xc1\x1e\xa5\x00~?v\x95\x94\xe3\xc8\x97\xf5\x1d \
+\x09\x22\x85F\xd7\xfd,`\xf8 =\xfe\xfcD?,\
+^\x90\x06\x85\x08X\xfb\x0f\x86Wv\xb2\xf7\x94\xb0\xea\
+\xac\xc7\x12\xc7M\xcf\xee{\xde8j\xd2\x1d\x0f\x0cT\
+$\xb2\x88pb\xe4\xe0\xc7\xb9,\x15\x94\xbe\xb5\xaa\xe3\
+\xaf\x15\x99\xd3\xe7]\xc3\x81\x1c\xebe\xe3+Y\xdc\xf5\
+YY\xa3\x85\xb4_\x0e\xa60h\x81\xb7\xdfwcM\
+Q=\xea\x1a#\xe7\x9c\x0cD\x95\x80\xb4\x11q]Z\
+\x18\x00\x88\xb6\x8a\xf8\xf5\x8cxd\xde`GH\x02J\
+\xf70\xbc\xb3W)\x8f4~\xb3\xb1\xf1\xcb\xd7b\x14\
+N\x0b\xca\x9c\xcb\xf6\x9f\x5c\xefp\xe4\xdaB\x94\xcfa\
+\x0c\xfd\x08A\x1d\xe1\xf2\x8b%\xce\xe7\x8e\xfd \xa0&\
+O\xce\xb1DT\xf4^B\x90f\x12\xb4\x8b\xd7\xaf\x7f\
+\xb2a\xe2\xed\x0f\xf6\xe7\xb24\x1f\xa0\x9f\xe99\xdf\x1e\
+$\xec>\x80\xc8V\x8d\xb8t\xdd\xba\xa5\x81)3\x1f\
+\x9c\xa8\xc9\xb8%;~\xe05\x03\xa6]I\xd2~:\
+\x90\xa2-\xc8\xb0nS\x03\xde\xda\xee\x82?\xf0\xfd\x93\
+\x0f\xa1\x04\x89\xfd\xed\x105\x02T\x1a\x01\xa1@\x04\xd5\
+\xfb\xdb\x81\xd2i)\x1c\xe3\xa3q\xf7\xb4xh5\x04\
+\xdf\x1c\xe7X\xff\x11\xf3\x1c\xfe\xe6\x93j\xb9b\xdb\xd2\
+M\xaf?\xf5\xea\xa4I\xf7\x1ae\xad\xe9a\x01\x84B\
+\x91\xd6BP\xdf\xc4\xc1\xae&\x8a\xf0\xa7\x92\x8d\xcb\xbe\
+\xbd\xe9\xce\xfbbUa\xdd\x22\x01\xc4\x0dAY]\xbc\
+~EC\xaf\x80\x1a\xecp\xa8\xd3\x912\x0b`#\x04\
+\x82\xbf\x16o(\xf8\x10\x00~\xe2\xc8\xd5Y\x18\xb9\xac\
+l\xe3\xf2\xdd\x99Y9\x93\x01:\x81P\xe9\xf9\xe2\xc2\
+gw\xdf<\xfd\xfe\x91\x22W\xcd\xa6\x1c\xc7I\xb8u\
+\xa5j\xf8\xaf\xfb\xa9\xd5\xdam\xa3\xaf\x1a\x16?q\x04\
+%}\xe3\x08\xea\x1a#xiC=\xde~\xbf]\xdf\
+\xee\xdd\xb8\xca\x04\x83U\xdb1\xaeR\x14\x0e_s\x00\
+\x9e\x86\x00~>\xda\x8c;\xa7\xc4bP?=\x8e4\
+rl\xfa\x9c){\xf6|\xed\xd5\xb1\xc6\xc9\x87\x8f~\
+\xf2\xb9\xa1\xadu\x16\xe7,\x81I\xaaW\xca6?s\
+d\xc2\xd4\xdc\x0c\x22\xe07\x10\xc8\x8e\xad\x85\xcb\xdf\xbd\
+yZ\xee\x90\xe6\x8a\xc0\xc1\x93Z\xdb\xcd\xb7\xdd?D\
+T\xd4\xb3\x00\xb2w[\xd1\xb2W\xce\xec\x0f;\x015\
+nF\xf6\x15\x22\x13~\xcb\x18\xd9\xa9'\xd5N\xa7\xd3\
+\xa9\x00@\xe6\xad\xf3\xc7q\xca&\x11\x10?\xe7\x5c\xab\
+\x92#y\x9b6\xadr\x8d\x9b\x99m\xa2\xb2\xb0\x10\x5c\
+p\xcb\xb2\xef\x8d\xedo\xad\xae\x9b\xe0\xc8\x99\x03JF\
+1\x905\x833\x97)M\xae\x06\xe7O\x86D\xa7\xce\
+\xbcN\x80Q\x0b\xec\xfa\xc6\x8f\xd5o\xd4c\xdf\xa1\xef\
+N\xf1\xa9Cc!\xa8\x050\x99\xc1\xef\x0e\xa1\xf9\xb8\
+\x17}Su\x98{w\x02\xae\x1cfDD\x02\xde\xda\
+\xc5\xb0\xe3\xcb\x96\xe3\x01n\xb9\xab\xed\x1f\x0b\xea\x18!\
+\xd9\x9c\x93\xdd\xdb\x9c\xf9/\x8d\x9d\x91\x93\xa6b\xe4>\
+\x80\x8a\x11\xce\x9e\xdc\xee\xcco\xc9\xbcm~4g\xca\
+\xc3\x04\x82\x9f16\x94\x10\xf2\xd2\xd6\x0d\xcbKO\xd3\
+\xcf\xef\xa6\x84\x8f!\x84\xac*.\xcc\xdf\xd5\x09\xa8\x89\
+\x8e\xb9}\x00q\x11'Ju B\x9e\xdf\xb9\xb9\xc0\
+\x03\x00\x99\xb7\xe5\x0e\xe1\x0a\xf2@\xf0\x191\x07V\x95\
+\xac^\xdd6ujnFD\x85\xdf\x00\xd0\x81\xa3\x0f\
+\xa7d\xe9\xb6\xc2\xe5\x1fMt\xe4\xfc\x84\x80\xdc\xa3\x00\
+\x9bJ\x9d\xf9o\x8fsd\x0f\x16\x88\xf0\x18\x08\xf9\x22\
+i\xdc3\x95Q\x22\x16\xffb0I\x1f;\x9c\x82\x12\
+\xa0\xec\xbd\x16\xac\xd9P\x8f\xc6f\xa9\xc7q\x95,)\
+\xa8;\xd4\x02\xabI\xc0\xad\xe3\xa2\xe1\x98\x10\x03\xbd\x96\
+\xa2\xec+\x86\xed_\xf3\xa3m\x11\xb6\xb8n\xc7\xc3j\
+p>L&l\xc9\xdb\x85\x05\x15\x93\xa6\xcfs0\xce\
+o\x13\x80\xe7\xb7l\xc8\xdf1\xc9\xf1\xc0@\x85\x0a\x8b\
+\x08\xb8\x97p\xae\x85\x22\x14\x94l\x5c\xf6\xed\xec\xd9\xb3\
+U5n\xfd\xaf\x08\xc1U\x22\xc5\xe2\xcd\x85\xed\xca\xe6\
+\x09\x15\xf7^\x02>\x98\x04\xd9#\xc5\xc5+\x1a\xc8\xc9\
+Y\x9e \x10\xba\xa5p\xf9q\x00\xb8a\xca\xc3v\x9d\
+*2\x0b\xe0\x86\x88D\x9f\xfb\xdb\xa6\xa5\x8d]\xce\x1c\
+L\xbb\x7f\xc8\xf6\x8d\xab\xf6M\xbd}~jD\xe6\x0f\
+r\xa242\xd1Z`\x88\xec\x0b\x04y\x9f\xf9 J\
+\x9c\xa4`\xe9\xf6\xb7\x0a\xea&L\xcf\x19\xa4QG=\
+\xa2\x1f>\xab6.>%w\xc6\xb5T\xbc2\x9d\xc2\
+\xed\x95\xb1\xe5\x1d\x17^,\xac\xefV\xf9\x94%\x05Y\
+\xe3\xa3qoV<\x8cQ\x02\x0e\xd6q\xbc\xf6!\xe3\
+\x87k\xfck\x02\xbb\x96E\x0b\x01\xdf#\x9b6\x15\x1c\
+\x18;#'\x8d*\xc8\x11\x08\x91}\x8d\xbb\x17\x99L\
+W\xeb\x98F\xba\x97\x10\x0cP\xe4\xc8\x92\xd2\xb7VU\
+\x8ew\xe4\x8c(u\x16\xec9\xfb\x88\x8e\xce\xe1`j\
+I#>\xfa\xb7uK\x03'?\xf7\xd3\xb0\xf7\x9fo\
+\xfc\xd9\xdd\x85\xa3\xc6e\xe5\x5c'p\xba\x90P\xf2\xa7\
+\x92\xa2e\xdd\xfe\xd3r\xdc\xb8l\x8dh\x14\xe7\x03$\
+>\xc2y\xdevg~\xcb\xa4\xe993\x19\xe8\x14\x81\
+(\xcfl)\x5c\xf1\xe9\x98\xc99\x16\x83\x9a>\x03\xc2\
+\x9bi\xd0\xfbdq\xf1\xcb\xbe\x19\xf9\xc14\xb5V\x95\
+wy\x1f\xdc}\xe7u\x14q\xe6v\xfeZ\xfab\x0d\
+>\xd9\xdd\xda\xe9\x1e\x83\xfa\xea\x913+\x09C\x07\xe8\
+\xd1\x1a\x04^\xda\xa9\xe0\xcbc\xfc\xcd\x18\x8b\xf8P\xfe\
+LrtT\xe6l}\xbc\xce\x90\x0d\xf0>\x8c\xcby\
+e\xceg\x9bnq\xe4^\xce\x80<F\xe1\xdcV\x94\
+_8~\xea\xfd\xa9\x82\xa8\xcef\x04\x81\xab\x06\x99\x1f\
+\xcb\xcb\xcb\xeb\xb6\xf1\x9b4)\xb7\x1f\xd7\xf0\xa7\x89@\
+\x9e:}\xdbu\x9f\xf58'=\x0f>s\xa6\x82\x93\
+\xe9\x84\xd3g\xb7:\x97\xfd\xb3}\xac5o\x1a\xe7\xfc\
+A\x05\xda\xcc2\xe7SM\xe3\xa7\xde\x9fJ\x05\xf5\xfb\
+\x80r\xc7V\xe7\xca\x7f\x9ey\x8d9/\xf1\x81~I\
+.\xbaa0\x1d>\xf5J\x0a\xa3\x0e\xd8s \x80\xaf\
+\xf7\x07\x00\x02\x0c\xe9\xaf\xc3\xa8\xcb\x8d\x88\xc8@\xc9n\
+\x86w\xf6\xf1c\x01?\xbf\xf7\xb5l\xd5\xce.\xfe\xdc\
+>\x7f\x14\x95\x95\x22\xceC?\xdf\xea|\xa1\xe6F\xc7\
+Cf-\x09\xbfK\x08\x7f\xb1\xa4h\xc5_\x00`\xca\
+\xf4\x05C%\xae<N\x08^-)\xca\xdf\xf2/\xed\
+\xf5\xc6:\xe6\x0c\x14\x89\xf6)\x02\xea,\xd9\xb0l\xfd\
+YC\x98\x93\xc79\xc8\x97\xdb\x8a\x96\x17\xdc\xe8\x98\xdb\
+GK\xe8\xef\xc1P\xedo\xde\xb3x\xe7\xce\x9d]\xfa\
+\x9b\xd9/+sT\x94/\x1c?\x82\xa4\x8d\x1f\xde\xb9\
+\xfe\xfd\xaa\x92\xe3/\x7fW<A\x89>\xf6\xcb\xb4\xa6\
+\x17\xef\xba9>p\xe6x\xcd\x10\xeb]\x00\xf0t\x95\
+\xa4[\xb8i\xd3\x93-\x99\x8e\xdc;\x002Y\xa0\xe2\
+\xc2\xcdE\xcf\x1c\xe9\x02jV\xce\xbd\x00\xb9\x99E\xc4\
+\xdf\x95m\xee\xfa\xfd9\x035qz\xce\xf3`\xdcK\
+\xc3\x9a'\x8b\x8b\x97t{$e\xf2\x8cEi2\x0f\
+\x95\x10\xd0\x87K\x8a\x96o\x05\x80\x89\xd3\xe6\xf6\x87 \
+\xfcU\xe0X\xb2\xc5\x99\xdf\xe5\x9f\x9a\xab\xde\xe3\x86\x0f\
+\xf6\xcb\xf3\x93\xcc\xe4\xd11\x83\x89\xa8\x12\x80\xcf\x8e2\
+^\xd9L\xfe\xaa\x88B\xce\xda_\x11\xcf\xd9&D\x9c\
+\x09\x8fPB\x1f:I\x11\x99\xd3\xe7M\x04\xf8\x1f\x05\
+\xc2\x1d'\x89\xf9l6*s\xb6>!*\xeaa\xae\
+\x10\xc2\xfc\xd2\x13ee\xcf\x86\xcf\x1bP'\x07\x9e\xdd\
+n\xc7\x09s\xac\xd4\xa0\xcf\x01W\xe2|\x11,:\x99\
+9o\xbf}\x8e\xb5U\xd1<\x0a\x0e\x9f\xe2\xcbx\xaa\
+\xacln\xb7N-\xde\xc2\xe3\x0e7*\xd7\x1850\
+E\x10\xdc\xfe\xc2]\xc6\xc6\xee\xd6\xdes\xcf=\xda\xe6\
+\xa0\xed1\xce\xb9A\xa4\xe4\x7fO&\xa2)S\xee\x8b\
+\x8d\xa8u\xbf'\x9c\xb8\xfc*q\xcd\xce\xd7\x97Tw\
+\xcf\xb3+5=\xf9s^e\x961c\xc6\x88\xa6\xd8\
+Q3\x15\xb0_\x10EY\xbc\xf5\xc48~\xd4\xa8\xd9\
+\xaa\x84\x8c\xa8\xdf\x82\xf0\xab\x08\x17\xf3J\x9cK\x8f\xfd\
++t\xae\x89w>\xd4\x07\xe1\xf0\x13`d\xe7\xd67\
+\xf3\xd7\x9e\x94[\xc7\xde\xb1p\xa0\x18\x91\x1e\x22\x9c\xff\
+\xa3\xc4Y\xb0\xf6\x82\xeaQ\xe3fd_!0\xf1\x11\
+\xae\xf0\xd2m\x1b\x0b^\xee\xe8\xffn\xcd\xbd\x89S>\
+\x93\x02\xabOV\xf6\xffj\x9bp\xeb\xdck\x09\x11\x1e\
+\xa6`/\x16;W\x14\x9fJ<\x0f8\x08\xa7\xf7@\
+\x22\xbf\xdb\xbai\xf9W\x17\x06\xa8\x99\x0b\x93=\x11\xc9\
+\xf5\xb13?\x08\x00\xe3\x1c\xd9\x83E*\xce\x03#\xc7\
+J\x9c\xcb\x17\xe3\xdfl\xb3g\xcfV\xd5y\xa3~\xc3\
+\x19\x06\x10H+N6\xba?q\xe4\xeabDf\xea\
+\xae\x8f\xfb\xb7l\xbd\x8e\x1a\xc4\x91;V!d\x8c\x10\
+R\xf2\x8b\x8b\x7f\xb8C\xe7\xc3\xc69\xb2c\x04\xaaZ\
+\xc4\x99\xf2\xfe\xb6\xd3\xa2\xeb\xe2\xb0\xb3\x89\xd5\x17\xd8\xf2\
+\xf2\xf2(.\xd9%\xbbd\x97\xec?\xc0\xfe\x0f\x14\xd5\
+\xea\x92\xe4\xa3Oi\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x012\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M12.4501\
+ 37.65L10.3501 3\
+5.55L21.9001 24L\
+10.3501 12.45L12\
+.4501 10.35L24.0\
+001 21.9L35.5501\
+ 10.35L37.6501 1\
+2.45L26.1001 24L\
+37.6501 35.55L35\
+.5501 37.65L24.0\
+001 26.1L12.4501\
+ 37.65Z\x22 fill=\x22#\
+667085\x22/>\x0a</svg>\
+\x0a\
+\x00\x00\x04\x83\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M10.3929\
+ 26.4C9.73097 26\
+.4 9.16667 26.16\
+43 8.7 25.6929C8\
+.23333 25.2215 8\
+ 24.6548 8 23.99\
+29C8 23.3309 8.2\
+357 22.7666 8.70\
+71 22.3C9.17847 \
+21.8333 9.74513 \
+21.6 10.4071 21.\
+6C11.069 21.6 11\
+.6333 21.8357 12\
+.1 22.3071C12.56\
+67 22.7784 12.8 \
+23.3451 12.8 24.\
+0071C12.8 24.669\
+ 12.5643 25.2333\
+ 12.0929 25.7C11\
+.6215 26.1666 11\
+.0549 26.4 10.39\
+29 26.4ZM23.9929\
+ 26.4C23.331 26.\
+4 22.7667 26.164\
+3 22.3 25.6929C2\
+1.8333 25.2215 2\
+1.6 24.6548 21.6\
+ 23.9929C21.6 23\
+.3309 21.8357 22\
+.7666 22.3071 22\
+.3C22.7785 21.83\
+33 23.3451 21.6 \
+24.0071 21.6C24.\
+669 21.6 25.2333\
+ 21.8357 25.7 22\
+.3071C26.1667 22\
+.7784 26.4 23.34\
+51 26.4 24.0071C\
+26.4 24.669 26.1\
+643 25.2333 25.6\
+929 25.7C25.2215\
+ 26.1666 24.6549\
+ 26.4 23.9929 26\
+.4ZM37.5929 26.4\
+C36.931 26.4 36.\
+3667 26.1643 35.\
+9 25.6929C35.433\
+3 25.2215 35.2 2\
+4.6548 35.2 23.9\
+929C35.2 23.3309\
+ 35.4357 22.7666\
+ 35.9071 22.3C36\
+.3785 21.8333 36\
+.9451 21.6 37.60\
+71 21.6C38.269 2\
+1.6 38.8333 21.8\
+357 39.3 22.3071\
+C39.7667 22.7784\
+ 40 23.3451 40 2\
+4.0071C40 24.669\
+ 39.7643 25.2333\
+ 39.2929 25.7C38\
+.8215 26.1666 38\
+.2549 26.4 37.59\
+29 26.4Z\x22 fill=\x22\
+#667085\x22/>\x0a</svg\
+>\x0a\
+\x00\x00\x037\
+<\
+svg width=\x2224\x22 h\
+eight=\x2224\x22 viewB\
+ox=\x220 0 24 24\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M0 12C0 \
+5.37258 5.37258 \
+0 12 0C18.6274 0\
+ 24 5.37258 24 1\
+2C24 18.6274 18.\
+6274 24 12 24C5.\
+37258 24 0 18.62\
+74 0 12Z\x22 fill=\x22\
+#E6E6E6\x22/>\x0a<path\
+ d=\x22M15.5 12C16.\
+3284 12 17 12.67\
+16 17 13.5V14C17\
+ 15.9714 15.1405\
+ 18 12 18C8.8595\
+1 18 7 15.9714 7\
+ 14V13.5C7 12.67\
+16 7.67157 12 8.\
+5 12H15.5ZM15.5 \
+13H8.5C8.22386 1\
+3 8 13.2239 8 13\
+.5V14C8 15.4376 \
+9.43216 17 12 17\
+C14.5678 17 16 1\
+5.4376 16 14V13.\
+5C16 13.2239 15.\
+7761 13 15.5 13Z\
+M12 5.5C13.5188 \
+5.5 14.75 6.7312\
+2 14.75 8.25C14.\
+75 9.76878 13.51\
+88 11 12 11C10.4\
+812 11 9.25 9.76\
+878 9.25 8.25C9.\
+25 6.73122 10.48\
+12 5.5 12 5.5ZM1\
+2 6.5C11.0335 6.\
+5 10.25 7.2835 1\
+0.25 8.25C10.25 \
+9.2165 11.0335 1\
+0 12 10C12.9665 \
+10 13.75 9.2165 \
+13.75 8.25C13.75\
+ 7.2835 12.9665 \
+6.5 12 6.5Z\x22 fil\
+l=\x22#616161\x22/>\x0a</\
+svg>\x0a\x0a\
+\x00\x00\x00\xdf\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M18.9002\
+ 35.7L7.7002 24.\
+5L9.8502 22.35L1\
+8.9002 31.4L38.1\
+002 12.2L40.2502\
+ 14.35L18.9002 3\
+5.7Z\x22 fill=\x22#667\
+085\x22/>\x0a</svg>\x0a\
+\x00\x00\x01V\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M9 42C8.\
+2 42 7.5 41.7 6.\
+9 41.1C6.3 40.5 \
+6 39.8 6 39V9C6 \
+8.2 6.3 7.5 6.9 \
+6.9C7.5 6.3 8.2 \
+6 9 6H23.55V9H9V\
+39H23.55V42H9ZM3\
+3.3 32.75L31.15 \
+30.6L36.25 25.5H\
+18.75V22.5H36.15\
+L31.05 17.4L33.2\
+ 15.25L42 24.05L\
+33.3 32.75Z\x22 fil\
+l=\x22#667085\x22/>\x0a</\
+svg>\x0a\
+\x00\x00\x01f\
+<\
+svg width=\x2248\x22 h\
+eight=\x2248\x22 viewB\
+ox=\x220 0 48 48\x22 f\
+ill=\x22none\x22 xmlns\
+=\x22http://www.w3.\
+org/2000/svg\x22>\x0a<\
+path d=\x22M24.45 4\
+2V39H39V9H24.45V\
+6H39C39.8 6 40.5\
+ 6.3 41.1 6.9C41\
+.7 7.5 42 8.2 42\
+ 9V39C42 39.8 41\
+.7 40.5 41.1 41.\
+1C40.5 41.7 39.8\
+ 42 39 42H24.45Z\
+M20.55 32.75L18.\
+4 30.6L23.5 25.5\
+H6V22.5H23.4L18.\
+3 17.4L20.45 15.\
+25L29.25 24.05L2\
+0.55 32.75Z\x22 fil\
+l=\x22#667085\x22/>\x0a</\
+svg>\x0a\
+"
+
+qt_resource_name = b"\
+\x00\x02\
+\x00\x00\x07\x84\
+\x00q\
+\x00t\
+\x00\x03\
+\x00\x00x<\
+\x00q\
+\x00m\x00l\
+\x00\x0c\
+\x0fN\xa7E\
+\x00C\
+\x00o\x00l\x00o\x00r\x00P\x00a\x00l\x00e\x00t\x00t\x00e\
+\x00\x05\
+\x00o\xa6S\
+\x00i\
+\x00c\x00o\x00n\x00s\
+\x00\x08\
+\x03\xc6T'\
+\x00p\
+\x00l\x00u\x00s\x00.\x00s\x00v\x00g\
+\x00\x0c\
+\x07\x11\xd4\xa7\
+\x00u\
+\x00s\x00e\x00r\x00M\x00a\x00s\x00k\x00.\x00s\x00v\x00g\
+\x00\x06\
+\x07\x87WG\
+\x00q\
+\x00t\x00.\x00p\x00n\x00g\
+\x00\x0a\
+\x08\xab\xd7\x87\
+\x00u\
+\x00p\x00d\x00a\x00t\x00e\x00.\x00s\x00v\x00g\
+\x00\x0a\
+\x0c\xad\x02\x87\
+\x00d\
+\x00e\x00l\x00e\x00t\x00e\x00.\x00s\x00v\x00g\
+\x00\x08\
+\x0b\x07W\xa7\
+\x00e\
+\x00d\x00i\x00t\x00.\x00s\x00v\x00g\
+\x00\x0e\
+\x05\x92p\xc7\
+\x00t\
+\x00e\x00s\x00t\x00s\x00e\x00r\x00v\x00e\x00r\x00.\x00p\x00n\x00g\
+\x00\x09\
+\x06\x98\x8e\xa7\
+\x00c\
+\x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\
+\x00\x08\
+\x06\xb6W\xa7\
+\x00d\
+\x00o\x00t\x00s\x00.\x00s\x00v\x00g\
+\x00\x08\
+\x09\xc5UG\
+\x00u\
+\x00s\x00e\x00r\x00.\x00s\x00v\x00g\
+\x00\x06\
+\x07^Z\xc7\
+\x00o\
+\x00k\x00.\x00s\x00v\x00g\
+\x00\x0a\
+\x06\xc91\x07\
+\x00l\
+\x00o\x00g\x00o\x00u\x00t\x00.\x00s\x00v\x00g\
+\x00\x09\
+\x0e\x01\xbcg\
+\x00l\
+\x00o\x00g\x00i\x00n\x00.\x00s\x00v\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x0a\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x16\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x004\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x05\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x13\x96\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00.-\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00/c\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01L\x00\x00\x00\x00\x00\x01\x00\x008\x08\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x00\xc4\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x007%\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x01\x5c\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xf3\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x003\xea\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x11\x9b\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xe4\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x009b\
+\x00\x00\x01\x8d\x87\xa2.\x0a\
+"
+
+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/demos/colorpaletteclient/restservice.py b/examples/demos/colorpaletteclient/restservice.py
new file mode 100644
index 000000000..d334ecd03
--- /dev/null
+++ b/examples/demos/colorpaletteclient/restservice.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Property, Signal, ClassInfo
+from PySide6.QtNetwork import (QNetworkAccessManager, QRestAccessManager,
+ QNetworkRequestFactory, QSslSocket)
+from PySide6.QtQml import QmlElement, QPyQmlParserStatus, ListProperty
+from abstractresource import AbstractResource
+
+QML_IMPORT_NAME = "ColorPalette"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+@ClassInfo(DefaultProperty="resources")
+class RestService(QPyQmlParserStatus):
+
+ urlChanged = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_resources = []
+ self.m_qnam = QNetworkAccessManager()
+ self.m_qnam.setAutoDeleteReplies(True)
+ self.m_manager = QRestAccessManager(self.m_qnam)
+ self.m_serviceApi = QNetworkRequestFactory()
+
+ @Property(str, notify=urlChanged)
+ def url(self):
+ return self.m_serviceApi.baseUrl()
+
+ @url.setter
+ def url(self, url):
+ if self.m_serviceApi.baseUrl() != url:
+ self.m_serviceApi.setBaseUrl(url)
+ self.urlChanged.emit()
+
+ @Property(bool, constant=True)
+ def sslSupported(self):
+ return QSslSocket.supportsSsl()
+
+ def classBegin(self):
+ pass
+
+ def componentComplete(self):
+ for resource in self.m_resources:
+ resource.setAccessManager(self.m_manager)
+ resource.setServiceApi(self.m_serviceApi)
+
+ def appendResource(self, r):
+ self.m_resources.append(r)
+
+ resources = ListProperty(AbstractResource, appendResource)
diff --git a/examples/demos/documentviewer/abstractviewer.py b/examples/demos/documentviewer/abstractviewer.py
new file mode 100644
index 000000000..4039fe9fb
--- /dev/null
+++ b/examples/demos/documentviewer/abstractviewer.py
@@ -0,0 +1,185 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QObject
+
+from PySide6.QtWidgets import (QDialog, QMenu)
+from PySide6.QtCore import Signal, Slot
+from PySide6.QtPrintSupport import QPrinter, QPrintDialog
+
+
+MENU_NAME = "qtFileMenu"
+
+
+class AbstractViewer(QObject):
+
+ uiInitialized = Signal()
+ printingEnabledChanged = Signal(bool)
+ showMessage = Signal(str, int)
+ documentLoaded = Signal(str)
+
+ def __init__(self):
+ super().__init__()
+ self._file = None
+ self._widget = None
+ self._menus = []
+ self._toolBars = []
+ self._printingEnabled = False
+ self._actions = []
+ self._fileMenu = None
+
+ def __del__(self):
+ self.cleanup()
+
+ def viewerName(self):
+ return ""
+
+ def saveState(self):
+ return False
+
+ def restoreState(self, state):
+ return False
+
+ def supportedMimeTypes():
+ return []
+
+ def init(self, file, widget, mainWindow):
+ self._file = file
+ self._widget = widget
+ self._uiAssets_mainWindow = mainWindow
+
+ def isEmpty(self):
+ return not self.hasContent()
+
+ def isPrintingEnabled(self):
+ return self._printingEnabled
+
+ def hasContent(self):
+ return False
+
+ def supportsOverview(self):
+ return False
+
+ def isModified(self):
+ return False
+
+ def saveDocument(self):
+ return False
+
+ def saveDocumentAs(self):
+ return False
+
+ def actions(self):
+ return self._actions
+
+ def widget(self):
+ return self._widget
+
+ def menus(self):
+ return self._menus
+
+ def mainWindow(self):
+ return self._uiAssets_mainWindow
+
+ def statusBar(self):
+ return self.mainWindow().statusBar()
+
+ def menuBar(self):
+ return self.mainWindow().menuBar()
+
+ def maybeEnablePrinting(self):
+ self.maybeSetPrintingEnabled(True)
+
+ def disablePrinting(self):
+ self.maybeSetPrintingEnabled(False)
+
+ def isDefaultViewer(self):
+ return False
+
+ def viewer(self):
+ return self
+
+ def statusMessage(self, message, type="", timeout=8000):
+ msg = self.viewerName()
+ if type:
+ msg += "/" + type
+ msg += ": " + message
+ self.showMessage.emit(msg, timeout)
+
+ def addToolBar(self, title):
+ bar = self.mainWindow().addToolBar(title)
+ name = title.replace(' ', '')
+ bar.setObjectName(name)
+ self._toolBars.append(bar)
+ return bar
+
+ def addMenu(self, title):
+ menu = QMenu(title, self.menuBar())
+ menu.setObjectName(title)
+ self.menuBar().insertMenu(self._uiAssets_help, menu)
+ self._menus.append(menu)
+ return menu
+
+ def cleanup(self):
+ # delete all objects created by the viewer which need to be displayed
+ # and therefore parented on MainWindow
+ if self._file:
+ self._file = None
+ self._menus.clear()
+ self._toolBars.clear()
+
+ def fileMenu(self):
+ if self._fileMenu:
+ return self._fileMenu
+
+ menus = self.mainWindow().findChildren(QMenu)
+ for menu in menus:
+ if menu.objectName() == MENU_NAME:
+ self._fileMenu = menu
+ return self._fileMenu
+ self._fileMenu = self.addMenu("File")
+ self._fileMenu.setObjectName(MENU_NAME)
+ return self._fileMenu
+
+ @Slot()
+ def print_(self):
+ type = "Printing"
+ if not self.hasContent():
+ self.statusMessage("No content to print.", type)
+ return
+ printer = QPrinter(QPrinter.HighResolution)
+ dlg = QPrintDialog(printer, self.mainWindow())
+ dlg.setWindowTitle("Print Document")
+ if dlg.exec() == QDialog.Accepted:
+ self.printDocument(printer)
+ else:
+ self.statusMessage("Printing canceled!", type)
+ return
+ state = printer.printerState()
+ message = self.viewerName() + " :"
+ if state == QPrinter.PrinterState.Aborted:
+ message += "Printing aborted."
+ elif state == QPrinter.PrinterState.Active:
+ message += "Printing active."
+ elif state == QPrinter.PrinterState.Idle:
+ message += "Printing completed."
+ elif state == QPrinter.PrinterState.Error:
+ message += "Printing error."
+ self.statusMessage(message, type)
+
+ def maybeSetPrintingEnabled(self, enabled):
+ if enabled == self._printingEnabled:
+ return
+ self._printingEnabled = enabled
+ self.printingEnabledChanged.emit(enabled)
+
+ def initViewer(self, back, forward, help, tabs):
+ self._uiAssets_back = back
+ self._uiAssets_forward = forward
+ self._uiAssets_help = help
+ self._uiAssets_tabs = tabs
+ # Tabs can be populated individually by the viewer, if it
+ # supports overview
+ tabs.clear()
+ tabs.setVisible(self.supportsOverview())
+ self.uiInitialized.emit()
diff --git a/examples/demos/documentviewer/doc/abstractviewer.py.rstinc b/examples/demos/documentviewer/doc/abstractviewer.py.rstinc
new file mode 100644
index 000000000..4c040b956
--- /dev/null
+++ b/examples/demos/documentviewer/doc/abstractviewer.py.rstinc
@@ -0,0 +1,25 @@
+``AbstractViewer`` provides a generalized API to view, save, and print a
+document. Properties of both the document and the viewer can be queried:
+
+ * Does the document have content?
+ * Has it been modified?
+ * Is an overview (thumbnails or bookmarks) supported?
+
+``AbstractViewer`` provides protected methods for derived classes to create
+actions and menus on the main window. In order to display these assets on the
+main window, they are parented to it. ``AbstractViewer`` is responsible for
+removing and destroying the UI assets it creates. It inherits from ``QObject``
+to implement signals and slots.
+
+The ``uiInitialized()`` signal is emitted after a viewer receives all necessary
+information about UI assets on the main window.
+
+The ``printingEnabledChanged()`` signal is emitted when document printing is
+either enabled or disabled. This happens after a new document was successfully
+loaded, or, for example, all content was removed.
+
+The ``printStatusChanged`` signal notifies about changes in its progress after
+starting the printing process.
+
+The ``documentLoaded()`` signal notifies the application that a document was
+successfully loaded.
diff --git a/examples/demos/documentviewer/doc/documentviewer.png b/examples/demos/documentviewer/doc/documentviewer.png
new file mode 100644
index 000000000..750aca589
--- /dev/null
+++ b/examples/demos/documentviewer/doc/documentviewer.png
Binary files differ
diff --git a/examples/demos/documentviewer/doc/documentviewer.rst b/examples/demos/documentviewer/doc/documentviewer.rst
new file mode 100644
index 000000000..b21e3f43e
--- /dev/null
+++ b/examples/demos/documentviewer/doc/documentviewer.rst
@@ -0,0 +1,13 @@
+Document Viewer Example
+=======================
+
+A Widgets application to display and print JSON, text, and PDF files.
+
+Document Viewer demonstrates how to use a QMainWindow with static
+and dynamic toolbars, menus, and actions.
+
+
+.. image:: documentviewer.png
+ :width: 90%
+ :align: center
+ :alt: Document Viewer Example
diff --git a/examples/demos/documentviewer/doc/jsonviewer.py.rstinc b/examples/demos/documentviewer/doc/jsonviewer.py.rstinc
new file mode 100644
index 000000000..1ba92f247
--- /dev/null
+++ b/examples/demos/documentviewer/doc/jsonviewer.py.rstinc
@@ -0,0 +1,11 @@
+``JsonViewer`` displays a JSON file in a ``QTreeView``. Internally, it loads
+the contents of a file into a data structure via a string and uses it to
+populate a custom tree model with JsonItemModel.
+
+The JSON viewer demonstrates how to implement a custom item model
+inherited from ``QAbstractItemModel``.
+
+``JsonViewer`` uses the top-level objects of the document as bookmarks for
+navigation. Other nodes (keys and values) can be added as additional bookmarks,
+or removed from the bookmark list. A ``QLineEdit`` is used as a search field to
+navigate through the JSON tree.
diff --git a/examples/demos/documentviewer/doc/mainwindow.py.rstinc b/examples/demos/documentviewer/doc/mainwindow.py.rstinc
new file mode 100644
index 000000000..976443ceb
--- /dev/null
+++ b/examples/demos/documentviewer/doc/mainwindow.py.rstinc
@@ -0,0 +1,11 @@
+The ``MainWindow`` class provides an application screen with menus, actions,
+and a toolbar. It can open a file, automatically detecting its content type. It
+also maintains a list of previously opened files, using ``QSettings`` to store
+and reload settings when launched. The ``MainWindow`` creates a suitable viewer
+for the opened file, based on its content type, and provides support for
+printing a document.
+
+``MainWindow's`` constructor initializes the user interface created in Qt
+Designer. The ``mainwindow.ui`` file provides a ``QTabWidget`` on the left,
+showing bookmarks and thumbnails. On the right, there is a ``QScrollArea`` for
+viewing file content.
diff --git a/examples/demos/documentviewer/doc/viewerfactory.py.rstinc b/examples/demos/documentviewer/doc/viewerfactory.py.rstinc
new file mode 100644
index 000000000..1627a576b
--- /dev/null
+++ b/examples/demos/documentviewer/doc/viewerfactory.py.rstinc
@@ -0,0 +1,3 @@
+The ``ViewerFactory`` class manages viewers for known file types. It loads all
+available viewers on construction and provides a public API to query the loaded
+plugins, their names, and supported MIME types.
diff --git a/examples/demos/documentviewer/documentviewer.pyproject b/examples/demos/documentviewer/documentviewer.pyproject
new file mode 100644
index 000000000..461e3b9d7
--- /dev/null
+++ b/examples/demos/documentviewer/documentviewer.pyproject
@@ -0,0 +1,14 @@
+{
+ "files": ["abstractviewer.py",
+ "documentviewer.qrc",
+ "main.py",
+ "mainwindow.py",
+ "mainwindow.ui",
+ "jsonviewer/jsonviewer.py",
+ "pdfviewer/pdfviewer.py",
+ "pdfviewer/zoomselector.py",
+ "txtviewer/txtviewer.py",
+ "recentfilemenu.py",
+ "recentfiles.py",
+ "viewerfactory.py"]
+}
diff --git a/examples/demos/documentviewer/documentviewer.qrc b/examples/demos/documentviewer/documentviewer.qrc
new file mode 100644
index 000000000..0aeeac229
--- /dev/null
+++ b/examples/demos/documentviewer/documentviewer.qrc
@@ -0,0 +1,42 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="demos/documentviewer">
+ <file>images/copy@2x.png</file>
+ <file>images/copy.png</file>
+ <file>images/cut@2x.png</file>
+ <file>images/cut.png</file>
+ <file>images/go-next-view@2x.png</file>
+ <file>images/go-next-view-page@2x.png</file>
+ <file>images/go-next-view-page.png</file>
+ <file>images/go-next-view.png</file>
+ <file>images/go-previous-view@2x.png</file>
+ <file>images/go-previous-view-page@2x.png</file>
+ <file>images/go-previous-view-page.png</file>
+ <file>images/go-previous-view.png</file>
+ <file>images/magnifier@2x.png</file>
+ <file>images/magnifier.png</file>
+ <file>images/open@2x.png</file>
+ <file>images/open.png</file>
+ <file>images/paste@2x.png</file>
+ <file>images/paste.png</file>
+ <file>images/print2x.png</file>
+ <file>images/print.png</file>
+ <file>images/qt-logo@2x.png</file>
+ <file>images/qt-logo.png</file>
+ <file>images/zoom-fit-best@2x.png</file>
+ <file>images/zoom-fit-best.png</file>
+ <file>images/zoom-fit-width@2x.png</file>
+ <file>images/zoom-fit-width.png</file>
+ <file>images/zoom-in@2x.png</file>
+ <file>images/zoom-in.png</file>
+ <file>images/zoom-original@2x.png</file>
+ <file>images/zoom-original.png</file>
+ <file>images/zoom-out@2x.png</file>
+ <file>images/zoom-out.png</file>
+ <file>images/zoom-previous@2x.png</file>
+ <file>images/zoom-previous.png</file>
+ <file>images/document-open-recent.svgz</file>
+ <file>images/go-next.svgz</file>
+ <file>images/go-previous.svgz</file>
+ <file>images/help-about.svgz</file>
+</qresource>
+</RCC>
diff --git a/examples/widgets/mainwindows/mdi/images/copy.png b/examples/demos/documentviewer/images/copy.png
index 2aeb28288..2aeb28288 100644
--- a/examples/widgets/mainwindows/mdi/images/copy.png
+++ b/examples/demos/documentviewer/images/copy.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/copy@2x.png b/examples/demos/documentviewer/images/copy@2x.png
new file mode 100644
index 000000000..f4ebabba1
--- /dev/null
+++ b/examples/demos/documentviewer/images/copy@2x.png
Binary files differ
diff --git a/examples/widgets/mainwindows/mdi/images/cut.png b/examples/demos/documentviewer/images/cut.png
index 54638e938..54638e938 100644
--- a/examples/widgets/mainwindows/mdi/images/cut.png
+++ b/examples/demos/documentviewer/images/cut.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/cut@2x.png b/examples/demos/documentviewer/images/cut@2x.png
new file mode 100644
index 000000000..5a5da4fd3
--- /dev/null
+++ b/examples/demos/documentviewer/images/cut@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/document-open-recent.svgz b/examples/demos/documentviewer/images/document-open-recent.svgz
new file mode 100644
index 000000000..3b6eaf978
--- /dev/null
+++ b/examples/demos/documentviewer/images/document-open-recent.svgz
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-next-view-page.png b/examples/demos/documentviewer/images/go-next-view-page.png
new file mode 100644
index 000000000..bd2a33836
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-next-view-page.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-next-view-page@2x.png b/examples/demos/documentviewer/images/go-next-view-page@2x.png
new file mode 100644
index 000000000..5ddcbbcc9
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-next-view-page@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-next-view.png b/examples/demos/documentviewer/images/go-next-view.png
new file mode 100644
index 000000000..98b79dea3
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-next-view.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-next-view@2x.png b/examples/demos/documentviewer/images/go-next-view@2x.png
new file mode 100644
index 000000000..919406437
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-next-view@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-next.svgz b/examples/demos/documentviewer/images/go-next.svgz
new file mode 100644
index 000000000..c6dc7d0f4
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-next.svgz
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-previous-view-page.png b/examples/demos/documentviewer/images/go-previous-view-page.png
new file mode 100644
index 000000000..ecd3768ef
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-previous-view-page.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-previous-view-page@2x.png b/examples/demos/documentviewer/images/go-previous-view-page@2x.png
new file mode 100644
index 000000000..f0d91c9f1
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-previous-view-page@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-previous-view.png b/examples/demos/documentviewer/images/go-previous-view.png
new file mode 100644
index 000000000..086bd9a1c
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-previous-view.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-previous-view@2x.png b/examples/demos/documentviewer/images/go-previous-view@2x.png
new file mode 100644
index 000000000..900860ce0
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-previous-view@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/go-previous.svgz b/examples/demos/documentviewer/images/go-previous.svgz
new file mode 100644
index 000000000..971b1d989
--- /dev/null
+++ b/examples/demos/documentviewer/images/go-previous.svgz
Binary files differ
diff --git a/examples/demos/documentviewer/images/help-about.svgz b/examples/demos/documentviewer/images/help-about.svgz
new file mode 100644
index 000000000..001aa7f2f
--- /dev/null
+++ b/examples/demos/documentviewer/images/help-about.svgz
Binary files differ
diff --git a/examples/demos/documentviewer/images/magnifier.png b/examples/demos/documentviewer/images/magnifier.png
new file mode 100644
index 000000000..6eb457d90
--- /dev/null
+++ b/examples/demos/documentviewer/images/magnifier.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/magnifier@2x.png b/examples/demos/documentviewer/images/magnifier@2x.png
new file mode 100644
index 000000000..ed84af187
--- /dev/null
+++ b/examples/demos/documentviewer/images/magnifier@2x.png
Binary files differ
diff --git a/examples/widgets/mainwindows/mdi/images/open.png b/examples/demos/documentviewer/images/open.png
index 45fa2883a..45fa2883a 100644
--- a/examples/widgets/mainwindows/mdi/images/open.png
+++ b/examples/demos/documentviewer/images/open.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/open@2x.png b/examples/demos/documentviewer/images/open@2x.png
new file mode 100644
index 000000000..12c2c3c1d
--- /dev/null
+++ b/examples/demos/documentviewer/images/open@2x.png
Binary files differ
diff --git a/examples/widgets/mainwindows/mdi/images/paste.png b/examples/demos/documentviewer/images/paste.png
index c14425cad..c14425cad 100644
--- a/examples/widgets/mainwindows/mdi/images/paste.png
+++ b/examples/demos/documentviewer/images/paste.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/paste@2x.png b/examples/demos/documentviewer/images/paste@2x.png
new file mode 100644
index 000000000..360b0f6c7
--- /dev/null
+++ b/examples/demos/documentviewer/images/paste@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/print.png b/examples/demos/documentviewer/images/print.png
new file mode 100644
index 000000000..4ace26140
--- /dev/null
+++ b/examples/demos/documentviewer/images/print.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/print2x.png b/examples/demos/documentviewer/images/print2x.png
new file mode 100644
index 000000000..1c3655be6
--- /dev/null
+++ b/examples/demos/documentviewer/images/print2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/qt-logo.png b/examples/demos/documentviewer/images/qt-logo.png
new file mode 100644
index 000000000..c9171422c
--- /dev/null
+++ b/examples/demos/documentviewer/images/qt-logo.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/qt-logo@2x.png b/examples/demos/documentviewer/images/qt-logo@2x.png
new file mode 100644
index 000000000..95d1d09b1
--- /dev/null
+++ b/examples/demos/documentviewer/images/qt-logo@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-fit-best.png b/examples/demos/documentviewer/images/zoom-fit-best.png
new file mode 100644
index 000000000..6a13de4c1
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-fit-best.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-fit-best@2x.png b/examples/demos/documentviewer/images/zoom-fit-best@2x.png
new file mode 100644
index 000000000..904b41c88
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-fit-best@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-fit-width.png b/examples/demos/documentviewer/images/zoom-fit-width.png
new file mode 100644
index 000000000..d51fbac69
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-fit-width.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-fit-width@2x.png b/examples/demos/documentviewer/images/zoom-fit-width@2x.png
new file mode 100644
index 000000000..4d1fd0b4d
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-fit-width@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-in.png b/examples/demos/documentviewer/images/zoom-in.png
new file mode 100644
index 000000000..5ae1046c0
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-in.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-in@2x.png b/examples/demos/documentviewer/images/zoom-in@2x.png
new file mode 100644
index 000000000..863ef4aca
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-in@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-original.png b/examples/demos/documentviewer/images/zoom-original.png
new file mode 100644
index 000000000..8aa9bb49c
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-original.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-original@2x.png b/examples/demos/documentviewer/images/zoom-original@2x.png
new file mode 100644
index 000000000..d5473007c
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-original@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-out.png b/examples/demos/documentviewer/images/zoom-out.png
new file mode 100644
index 000000000..081b6d981
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-out.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-out@2x.png b/examples/demos/documentviewer/images/zoom-out@2x.png
new file mode 100644
index 000000000..34c8e1748
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-out@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-previous.png b/examples/demos/documentviewer/images/zoom-previous.png
new file mode 100644
index 000000000..0ff5c0416
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-previous.png
Binary files differ
diff --git a/examples/demos/documentviewer/images/zoom-previous@2x.png b/examples/demos/documentviewer/images/zoom-previous@2x.png
new file mode 100644
index 000000000..e9909abc5
--- /dev/null
+++ b/examples/demos/documentviewer/images/zoom-previous@2x.png
Binary files differ
diff --git a/examples/demos/documentviewer/jsonviewer/jsonviewer.py b/examples/demos/documentviewer/jsonviewer/jsonviewer.py
new file mode 100644
index 000000000..1e5a34ca0
--- /dev/null
+++ b/examples/demos/documentviewer/jsonviewer/jsonviewer.py
@@ -0,0 +1,396 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import json
+
+from PySide6.QtWidgets import (QLabel, QLineEdit, QListWidget,
+ QListWidgetItem, QMenu, QTreeView)
+from PySide6.QtGui import (QAction, QIcon, QKeySequence,
+ QPixmap, QTextDocument)
+from PySide6.QtCore import (QAbstractItemModel, QDir,
+ QIODevice, QModelIndex,
+ QPoint, QSize, Qt, Slot)
+
+from abstractviewer import AbstractViewer
+
+
+def resizeToContents(tree):
+ for i in range(0, tree.header().count()):
+ tree.resizeColumnToContents(i)
+
+
+class JsonTreeItem:
+
+ def __init__(self, parent=None):
+ self._key = ""
+ self._value = None
+ self._children = []
+ self._parent = parent
+
+ def key(self):
+ return self._key
+
+ def value(self):
+ return self._value
+
+ def appendChild(self, item):
+ self._children.append(item)
+
+ def child(self, row):
+ return self._children[row]
+
+ def parent(self):
+ return self._parent
+
+ def childCount(self):
+ return len(self._children)
+
+ def row(self):
+ if self._parent:
+ return self._parent._children.index(self)
+ return 0
+
+ def setKey(self, key):
+ self._key = key
+
+ def setValue(self, value):
+ self._value = value
+
+ @staticmethod
+ def load(value, parent=None):
+ rootItem = JsonTreeItem(parent)
+ rootItem.setKey("root")
+
+ if isinstance(value, dict):
+ for key, val in value.items():
+ child = JsonTreeItem.load(val, rootItem)
+ child.setKey(key)
+ rootItem.appendChild(child)
+
+ elif isinstance(value, list):
+ for index, val in enumerate(value):
+ child = JsonTreeItem.load(val, rootItem)
+ child.setKey(f"{index}")
+ rootItem.appendChild(child)
+
+ else:
+ rootItem.setValue(value)
+
+ return rootItem
+
+
+class JsonItemModel(QAbstractItemModel):
+
+ def columnCount(self, index=QModelIndex()):
+ return 2
+
+ def itemFromIndex(self, index):
+ return index.internalPointer()
+
+ def __init__(self, doc, parent):
+ super().__init__(parent)
+ self._textItem = JsonTreeItem()
+
+ # Append header lines
+ self._headers = ["Key", "Value"]
+
+ # Reset the model. Root can either be a value or an array.
+ self.beginResetModel()
+ self._textItem = JsonTreeItem.load(doc) if doc else JsonTreeItem()
+ self.endResetModel()
+
+ def data(self, index, role):
+ if not index.isValid():
+ return None
+
+ item = self.itemFromIndex(index)
+ if role == Qt.DisplayRole:
+ if index.column() == 0:
+ return item.key()
+ if index.column() == 1:
+ return item.value()
+ elif role == Qt.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)
+
+ def index(self, row, column, parent=QModelIndex()):
+ if not self.hasIndex(row, column, parent):
+ return None
+
+ parentItem = JsonTreeItem()
+
+ if not parent.isValid():
+ parentItem = self._textItem
+ else:
+ parentItem = self.itemFromIndex(parent)
+
+ childItem = parentItem.child(row)
+ if childItem:
+ return self.createIndex(row, column, childItem)
+ return None
+
+ def parent(self, index):
+ if not index.isValid():
+ return None
+
+ childItem = self.itemFromIndex(index)
+ parentItem = childItem.parent()
+
+ if parentItem == self._textItem:
+ return QModelIndex()
+
+ return self.createIndex(parentItem.row(), 0, parentItem)
+
+ def rowCount(self, parent=QModelIndex()):
+ parentItem = JsonTreeItem()
+ if parent.column() > 0:
+ return 0
+
+ if not parent.isValid():
+ parentItem = self._textItem
+ else:
+ parentItem = self.itemFromIndex(parent)
+ return parentItem.childCount()
+
+
+class JsonViewer(AbstractViewer):
+
+ def __init__(self):
+ super().__init__()
+ self._tree = QTreeView()
+ self._toplevel = None
+ self._text = ""
+ self._searchKey = None
+ self.uiInitialized.connect(self.setupJsonUi)
+
+ def init(self, file, parent, mainWindow):
+ self._tree = QTreeView(parent)
+ super().init(file, self._tree, mainWindow)
+
+ def viewerName(self):
+ return "JsonViewer"
+
+ def supportedMimeTypes(self):
+ return ["application/json"]
+
+ @Slot()
+ def setupJsonUi(self):
+ # Build Menus and toolbars
+ menu = self.addMenu("Json")
+ tb = self.addToolBar("Json Actions")
+
+ 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)
+
+ 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)
+
+ if not self._searchKey:
+ self._searchKey = QLineEdit(tb)
+
+ label = QLabel(tb)
+ magnifier = QPixmap(":/icons/images/magnifier.png").scaled(QSize(28, 28))
+ label.setPixmap(magnifier)
+ tb.addWidget(label)
+ tb.addWidget(self._searchKey)
+ self._searchKey.textEdited.connect(self._tree.keyboardSearch)
+
+ if not self.openJsonFile():
+ return
+
+ # Populate bookmarks with toplevel
+ self._uiAssets_tabs.clear()
+ self._toplevel = QListWidget(self._uiAssets_tabs)
+ self._uiAssets_tabs.addTab(self._toplevel, "Bookmarks")
+ for i in range(0, self._tree.model().rowCount()):
+ index = self._tree.model().index(i, 0)
+ self._toplevel.addItem(index.data())
+ item = self._toplevel.item(i)
+ item.setData(Qt.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._toplevel.itemClicked.connect(self.onTopLevelItemClicked)
+ self._toplevel.itemDoubleClicked.connect(self.onTopLevelItemDoubleClicked)
+ self._toplevel.customContextMenuRequested.connect(self.onBookmarkMenuRequested)
+ self._tree.customContextMenuRequested.connect(self.onJsonMenuRequested)
+
+ # Connect back and forward
+ self._uiAssets_back.triggered.connect(self._back)
+ self._uiAssets_forward.triggered.connect(self._forward)
+
+ @Slot()
+ def _back(self):
+ index = self._tree.indexAbove(self._tree.currentIndex())
+ if index.isValid():
+ self._tree.setCurrentIndex(index)
+
+ @Slot()
+ def _forward(self):
+ current = self._tree.currentIndex()
+ next = self._tree.indexBelow(current)
+ if next.isValid():
+ self._tree.setCurrentIndex(next)
+ return
+ # Expand last item to go beyond
+ if not self._tree.isExpanded(current):
+ self._tree.expand(current)
+ next = self._tree.indexBelow(current)
+ if next.isValid():
+ self._tree.setCurrentIndex(next)
+
+ def openJsonFile(self):
+ self.disablePrinting()
+ file_name = QDir.toNativeSeparators(self._file.fileName())
+ type = "open"
+ self._file.open(QIODevice.ReadOnly)
+ self._text = self._file.readAll().data().decode("utf-8")
+ self._file.close()
+
+ data = None
+ message = None
+ try:
+ data = json.loads(self._text)
+ message = f"Json document {file_name} opened"
+ model = JsonItemModel(data, self)
+ self._tree.setModel(model)
+ except ValueError as e:
+ message = f"Unable to parse Json document from {file_name}: {e}"
+ self.statusMessage(message, type)
+ self.maybeEnablePrinting()
+
+ return self._tree.model() is not None
+
+ def indexOf(self, item):
+ return QModelIndex(item.data(Qt.UserRole))
+
+ @Slot(QListWidgetItem)
+ def onTopLevelItemClicked(self, item):
+ """Move to the clicked toplevel index"""
+ # return in the unlikely case that the tree has not been built
+ if not self._tree.model():
+ return
+
+ index = self.indexOf(item)
+ if not index.isValid():
+ return
+
+ self._tree.setCurrentIndex(index)
+
+ @Slot(QListWidgetItem)
+ def onTopLevelItemDoubleClicked(self, item):
+ """Toggle double clicked index between collaps/expand"""
+
+ # return in the unlikely case that the tree has not been built
+ if not self._tree.model():
+ return
+
+ index = self.indexOf(item)
+ if not index.isValid():
+ return
+
+ if self._tree.isExpanded(index):
+ self._tree.collapse(index)
+ return
+
+ # Make sure the node and all parents are expanded
+ while index.isValid():
+ self._tree.expand(index)
+ index = index.parent()
+
+ @Slot(QPoint)
+ def onJsonMenuRequested(self, pos):
+ index = self._tree.indexAt(pos)
+ if not index.isValid():
+ return
+
+ # Don't show a context menu, if the index is already a bookmark
+ for i in range(0, self._toplevel.count()):
+ if self.indexOf(self._toplevel.item(i)) == index:
+ return
+
+ menu = QMenu(self._tree)
+ action = QAction("Add bookmark")
+ action.setData(index)
+ menu.addAction(action)
+ action.triggered.connect(self.onBookmarkAdded)
+ menu.exec(self._tree.mapToGlobal(pos))
+
+ @Slot(QPoint)
+ def onBookmarkMenuRequested(self, pos):
+ item = self._toplevel.itemAt(pos)
+ if not item:
+ return
+
+ # Don't delete toplevel items
+ index = self.indexOf(item)
+ if not index.parent().isValid():
+ return
+
+ menu = QMenu()
+ action = QAction("Delete bookmark")
+ action.setData(self._toplevel.row(item))
+ menu.addAction(action)
+ action.triggered.connect(self.onBookmarkDeleted)
+ menu.exec(self._toplevel.mapToGlobal(pos))
+
+ @Slot()
+ def onBookmarkAdded(self):
+ action = self.sender()
+ if not action:
+ return
+
+ index = action.data()
+ if not index.isValid():
+ return
+
+ item = QListWidgetItem(index.data(Qt.DisplayRole), self._toplevel)
+ item.setData(Qt.UserRole, index)
+
+ # Set a tooltip that shows where the item is located in the tree
+ parent = index.parent()
+ tooltip = index.data(Qt.DisplayRole).toString()
+ while parent.isValid():
+ tooltip = parent.data(Qt.DisplayRole).toString() + "." + tooltip
+ parent = parent.parent()
+
+ item.setToolTip(tooltip)
+
+ @Slot()
+ def onBookmarkDeleted(self):
+ action = self.sender()
+ if not action:
+ return
+
+ row = action.data().toInt()
+ if row < 0 or row >= self._toplevel.count():
+ return
+
+ self._toplevel.takeItem(row)
+
+ def hasContent(self):
+ return bool(self._text)
+
+ def supportsOverview(self):
+ return True
+
+ def printDocument(self, printer):
+ if not self.hasContent():
+ return
+ doc = QTextDocument(self._text)
+ doc.print_(printer)
diff --git a/examples/demos/documentviewer/main.py b/examples/demos/documentviewer/main.py
new file mode 100644
index 000000000..2af373ef3
--- /dev/null
+++ b/examples/demos/documentviewer/main.py
@@ -0,0 +1,37 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the Qt Document Viewer demo from Qt v6.x"""
+
+import sys
+from argparse import ArgumentParser, RawTextHelpFormatter
+
+from PySide6.QtWidgets import QApplication
+from PySide6.QtCore import QCoreApplication
+
+from mainwindow import MainWindow
+
+
+DESCRIPTION = "A viewer for JSON, PDF and text files"
+
+
+if __name__ == "__main__":
+
+ app = QApplication([])
+ QCoreApplication.setOrganizationName("QtExamples")
+ QCoreApplication.setApplicationName("DocumentViewer")
+ QCoreApplication.setApplicationVersion("1.0")
+
+ arg_parser = ArgumentParser(description=DESCRIPTION,
+ formatter_class=RawTextHelpFormatter)
+ arg_parser.add_argument("file", type=str, nargs="?",
+ help="JSON, PDF or text file to open")
+ args = arg_parser.parse_args()
+ fileName = args.file
+
+ w = MainWindow()
+ w.show()
+ if args.file and not w.openFile(args.file):
+ sys.exit(-1)
+
+ sys.exit(app.exec())
diff --git a/examples/demos/documentviewer/mainwindow.py b/examples/demos/documentviewer/mainwindow.py
new file mode 100644
index 000000000..e9abd0bec
--- /dev/null
+++ b/examples/demos/documentviewer/mainwindow.py
@@ -0,0 +1,178 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, QMessageBox)
+from PySide6.QtCore import (QDir, QFile, QFileInfo, QSettings, Slot)
+
+from ui_mainwindow import Ui_MainWindow
+from viewerfactory import ViewerFactory
+from recentfiles import RecentFiles
+from recentfilemenu import RecentFileMenu
+
+
+settingsDir = "WorkingDir"
+settingsMainWindow = "MainWindow"
+settingsViewers = "Viewers"
+settingsFiles = "RecentFiles"
+
+
+ABOUT_TEXT = """A Widgets application to display and print JSON,
+text and PDF files. Demonstrates various features to use
+in widget applications: Using QSettings, query and save
+user preferences, manage file histories and control cursor
+behavior when hovering over widgets.
+
+"""
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.ui = Ui_MainWindow()
+
+ self._currentDir = QDir()
+ self._viewer = None
+ self._recentFiles = RecentFiles()
+
+ self.ui.setupUi(self)
+ self.ui.actionOpen.triggered.connect(self.onActionOpenTriggered)
+ self.ui.actionAbout.triggered.connect(self.onActionAboutTriggered)
+ self.ui.actionAboutQt.triggered.connect(self.onActionAboutQtTriggered)
+
+ self._recentFiles = RecentFiles(self.ui.actionRecent)
+ self._recentFiles.countChanged.connect(self._recentFilesCountChanged)
+
+ self.readSettings()
+ self._factory = ViewerFactory(self.ui.viewArea, self)
+ viewers = ", ".join(self._factory.viewerNames())
+ self.statusBar().showMessage(f'Available viewers: {viewers}')
+
+ menu = RecentFileMenu(self, self._recentFiles)
+ self.ui.actionRecent.setMenu(menu)
+ menu.fileOpened.connect(self.openFile)
+ button = self.ui.mainToolBar.widgetForAction(self.ui.actionRecent)
+ if button:
+ self.ui.actionRecent.triggered.connect(button.showMenu)
+
+ @Slot(int)
+ def _recentFilesCountChanged(self, count):
+ self.ui.actionRecent.setText(f"{count} recent files")
+
+ def closeEvent(self, event):
+ self.saveSettings()
+
+ @Slot(int)
+ def onActionOpenTriggered(self):
+ fileDialog = QFileDialog(self, "Open Document",
+ self._currentDir.absolutePath())
+ while (fileDialog.exec() == QDialog.Accepted
+ and not self.openFile(fileDialog.selectedFiles()[0])):
+ pass
+
+ @Slot(str)
+ def openFile(self, fileName):
+ file = QFile(fileName)
+ if not file.exists():
+ nf = QDir.toNativeSeparators(fileName)
+ self.statusBar().showMessage(f"File {nf} could not be opened")
+ return False
+
+ fileInfo = QFileInfo(file)
+ self._currentDir = fileInfo.dir()
+ self._recentFiles.addFile(fileInfo.absoluteFilePath())
+
+ # If a viewer is already open, clean it up and save its settings
+ self.resetViewer()
+ self._viewer = self._factory.viewer(file)
+ if not self._viewer:
+ nf = QDir.toNativeSeparators(fileName)
+ self.statusBar().showMessage(f"File {nf} can't be opened.")
+ return False
+
+ self.ui.actionPrint.setEnabled(self._viewer.hasContent())
+ self._viewer.printingEnabledChanged.connect(self.ui.actionPrint.setEnabled)
+ self.ui.actionPrint.triggered.connect(self._viewer.print_)
+ self._viewer.showMessage.connect(self.statusBar().showMessage)
+
+ self._viewer.initViewer(self.ui.actionBack, self.ui.actionForward,
+ self.ui.menuHelp.menuAction(),
+ self.ui.tabWidget)
+ self.restoreViewerSettings()
+ self.ui.scrollArea.setWidget(self._viewer.widget())
+ return True
+
+ @Slot()
+ def onActionAboutTriggered(self):
+ viewerNames = ", ".join(self._factory.viewerNames())
+ mimeTypes = '\n'.join(self._factory.supportedMimeTypes())
+ text = ABOUT_TEXT
+ text += f"\nThis version has loaded the following plugins:\n{viewerNames}\n"
+ text += f"\n\nIt supports the following mime types:\n{mimeTypes}"
+
+ defaultViewer = self._factory.defaultViewer()
+ if defaultViewer:
+ n = defaultViewer.viewerName()
+ text += f"\n\nOther mime types will be displayed with {n}."
+
+ QMessageBox.about(self, "About Document Viewer Demo", text)
+
+ @Slot()
+ def onActionAboutQtTriggered(self):
+ QMessageBox.aboutQt(self)
+
+ def readSettings(self):
+ settings = QSettings()
+
+ # Restore working directory
+ if settings.contains(settingsDir):
+ self._currentDir = QDir(settings.value(settingsDir))
+ else:
+ self._currentDir = QDir.current()
+
+ # Restore QMainWindow state
+ if settings.contains(settingsMainWindow):
+ mainWindowState = settings.value(settingsMainWindow)
+ self.restoreState(mainWindowState)
+
+ # Restore recent files
+ self._recentFiles.restoreFromSettings(settings, settingsFiles)
+
+ def saveSettings(self):
+ settings = QSettings()
+
+ # Save working directory
+ settings.setValue(settingsDir, self._currentDir.absolutePath())
+
+ # Save QMainWindow state
+ settings.setValue(settingsMainWindow, self.saveState())
+
+ # Save recent files
+ self._recentFiles.saveSettings(settings, settingsFiles)
+
+ settings.sync()
+
+ def saveViewerSettings(self):
+ if not self._viewer:
+ return
+ settings = QSettings()
+ settings.beginGroup(settingsViewers)
+ settings.setValue(self._viewer.viewerName(), self._viewer.saveState())
+ settings.endGroup()
+ settings.sync()
+
+ def resetViewer(self):
+ if not self._viewer:
+ return
+ self.saveViewerSettings()
+ self._viewer.cleanup()
+
+ def restoreViewerSettings(self):
+ if not self._viewer:
+ return
+ settings = QSettings()
+ settings.beginGroup(settingsViewers)
+ viewerSettings = settings.value(self._viewer.viewerName())
+ settings.endGroup()
+ if viewerSettings:
+ self._viewer.restoreState(viewerSettings)
diff --git a/examples/demos/documentviewer/mainwindow.ui b/examples/demos/documentviewer/mainwindow.ui
new file mode 100644
index 000000000..482061fc7
--- /dev/null
+++ b/examples/demos/documentviewer/mainwindow.ui
@@ -0,0 +1,272 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>983</width>
+ <height>602</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Document Viewer Demo</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>:/demos/documentviewer/images/qt-logo.png</iconset>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="viewArea" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="tabPosition">
+ <enum>QTabWidget::West</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="bookmarkTab">
+ <attribute name="title">
+ <string>Pages</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="pagesTab">
+ <attribute name="title">
+ <string>Bookmarks</string>
+ </attribute>
+ </widget>
+ </widget>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>800</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>798</width>
+ <height>479</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>983</width>
+ <height>23</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="qtFileMenu">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionOpen"/>
+ <addaction name="actionRecent"/>
+ <addaction name="actionPrint"/>
+ <addaction name="actionQuit"/>
+ </widget>
+ <widget class="QMenu" name="menuHelp">
+ <property name="title">
+ <string>Help</string>
+ </property>
+ <addaction name="actionAbout"/>
+ <addaction name="actionAboutQt"/>
+ </widget>
+ <addaction name="qtFileMenu"/>
+ <addaction name="menuHelp"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <widget class="QToolBar" name="mainToolBar">
+ <property name="windowTitle">
+ <string>toolBar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionOpen"/>
+ <addaction name="actionRecent"/>
+ <addaction name="actionPrint"/>
+ <addaction name="separator"/>
+ <addaction name="actionBack"/>
+ <addaction name="actionForward"/>
+ <addaction name="separator"/>
+ </widget>
+ <action name="actionOpen">
+ <property name="icon">
+ <iconset resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/open.png</normaloff>:/demos/documentviewer/images/open.png</iconset>
+ </property>
+ <property name="text">
+ <string>Open</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionAbout">
+ <property name="icon">
+ <iconset theme="help-about" resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/help-about.svgz</normaloff>:/demos/documentviewer/images/help-about.svgz</iconset>
+ </property>
+ <property name="text">
+ <string>about documentviewer</string>
+ </property>
+ <property name="toolTip">
+ <string>Show information about the Document Viewer deomo.</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+H</string>
+ </property>
+ </action>
+ <action name="actionForward">
+ <property name="icon">
+ <iconset resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/go-next.svgz</normaloff>:/demos/documentviewer/images/go-next.svgz</iconset>
+ </property>
+ <property name="text">
+ <string>actionForward</string>
+ </property>
+ <property name="toolTip">
+ <string>One step forward</string>
+ </property>
+ <property name="shortcut">
+ <string>Right</string>
+ </property>
+ </action>
+ <action name="actionBack">
+ <property name="icon">
+ <iconset resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/go-previous.svgz</normaloff>:/demos/documentviewer/images/go-previous.svgz</iconset>
+ </property>
+ <property name="text">
+ <string>actionBack</string>
+ </property>
+ <property name="toolTip">
+ <string>One step back</string>
+ </property>
+ <property name="shortcut">
+ <string>Left</string>
+ </property>
+ </action>
+ <action name="actionPrint">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="icon">
+ <iconset theme="document-print" resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/print2x.png</normaloff>:/demos/documentviewer/images/print2x.png</iconset>
+ </property>
+ <property name="text">
+ <string>Print</string>
+ </property>
+ <property name="toolTip">
+ <string>Print current file</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+P</string>
+ </property>
+ </action>
+ <action name="actionAboutQt">
+ <property name="icon">
+ <iconset resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/qt-logo.png</normaloff>
+ <normalon>:/demos/documentviewer/images/qt-logo.png</normalon>:/demos/documentviewer/images/qt-logo.png</iconset>
+ </property>
+ <property name="text">
+ <string>About Qt</string>
+ </property>
+ <property name="toolTip">
+ <string>Show Qt license information</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+I</string>
+ </property>
+ </action>
+ <action name="actionRecent">
+ <property name="icon">
+ <iconset resource="documentviewer.qrc">
+ <normaloff>:/demos/documentviewer/images/document-open-recent.svgz</normaloff>:/demos/documentviewer/images/document-open-recent.svgz</iconset>
+ </property>
+ <property name="text">
+ <string>Recently opened...</string>
+ </property>
+ <property name="shortcut">
+ <string>Meta+R</string>
+ </property>
+ </action>
+ <action name="actionQuit">
+ <property name="icon">
+ <iconset theme="application-exit">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ <property name="toolTip">
+ <string>Quit the application</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ </widget>
+ <resources>
+ <include location="documentviewer.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>actionQuit</sender>
+ <signal>triggered()</signal>
+ <receiver>MainWindow</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>491</x>
+ <y>300</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/demos/documentviewer/pdfviewer/pdfviewer.py b/examples/demos/documentviewer/pdfviewer/pdfviewer.py
new file mode 100644
index 000000000..d5695e4d0
--- /dev/null
+++ b/examples/demos/documentviewer/pdfviewer/pdfviewer.py
@@ -0,0 +1,204 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from math import sqrt
+
+from PySide6.QtWidgets import (QListView, QTreeView)
+from PySide6.QtGui import QIcon, QPainter
+from PySide6.QtCore import (QDir, QIODevice, QModelIndex,
+ QPointF, Slot)
+from PySide6.QtPrintSupport import QPrinter
+from PySide6.QtPdf import QPdfDocument, QPdfBookmarkModel
+from PySide6.QtPdfWidgets import QPdfView, QPdfPageSelector
+
+from abstractviewer import AbstractViewer
+from pdfviewer.zoomselector import ZoomSelector
+
+
+ZOOM_MULTIPLIER = sqrt(2.0)
+
+
+class PdfViewer(AbstractViewer):
+
+ def __init__(self):
+ super().__init__()
+ self.uiInitialized.connect(self.initPdfViewer)
+ self._toolBar = None
+ self._zoomSelector = None
+ self._pageSelector = None
+ self._document = None
+ self._pdfView = None
+ self._actionForward = None
+ self._actionBack = None
+ self._bookmarks = None
+ self._pages = None
+
+ def init(self, file, parent, mainWindow):
+ self._pdfView = QPdfView(parent)
+ super().init(file, self._pdfView, mainWindow)
+ self._document = QPdfDocument(self)
+
+ def supportedMimeTypes(self):
+ return ["application/pdf"]
+
+ def initPdfViewer(self):
+ self._toolBar = self.addToolBar("PDF")
+ self._zoomSelector = ZoomSelector(self._toolBar)
+
+ nav = self._pdfView.pageNavigator()
+ self._pageSelector = QPdfPageSelector(self._toolBar)
+ self._toolBar.insertWidget(self._uiAssets_forward, self._pageSelector)
+ self._pageSelector.setDocument(self._document)
+ self._pageSelector.currentPageChanged.connect(self.pageSelected)
+ nav.currentPageChanged.connect(self._pageSelector.setCurrentPage)
+ nav.backAvailableChanged.connect(self._uiAssets_back.setEnabled)
+ self._actionBack = self._uiAssets_back
+ self._actionForward = self._uiAssets_forward
+ self._uiAssets_back.triggered.connect(self.onActionBackTriggered)
+ self._uiAssets_forward.triggered.connect(self.onActionForwardTriggered)
+
+ 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.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.triggered.connect(self.onActionZoomOutTriggered)
+
+ nav.backAvailableChanged.connect(self._actionBack.setEnabled)
+ nav.forwardAvailableChanged.connect(self._actionForward.setEnabled)
+
+ self._zoomSelector.zoomModeChanged.connect(self._pdfView.setZoomMode)
+ self._zoomSelector.zoomFactorChanged.connect(self._pdfView.setZoomFactor)
+ self._zoomSelector.reset()
+
+ bookmarkModel = QPdfBookmarkModel(self)
+ bookmarkModel.setDocument(self._document)
+ self._uiAssets_tabs.clear()
+ self._bookmarks = QTreeView(self._uiAssets_tabs)
+ self._bookmarks.activated.connect(self.bookmarkSelected)
+ self._bookmarks.setModel(bookmarkModel)
+ self._pdfView.setDocument(self._document)
+ self._pdfView.setPageMode(QPdfView.PageMode.MultiPage)
+
+ self.openPdfFile()
+ if not self._document.pageCount():
+ return
+
+ self._pages = QListView(self._uiAssets_tabs)
+ self._pages.setModel(self._document.pageModel())
+
+ self._pages.selectionModel().currentRowChanged.connect(self._currentRowChanged)
+ self._pdfView.pageNavigator().currentPageChanged.connect(self._pageChanged)
+
+ self._uiAssets_tabs.addTab(self._pages, "Pages")
+ self._uiAssets_tabs.addTab(self._bookmarks, "Bookmarks")
+
+ def viewerName(self):
+ return "PdfViewer"
+
+ @Slot(QModelIndex, QModelIndex)
+ def _currentRowChanged(self, current, previous):
+ if previous == current:
+ return
+
+ nav = self._pdfView.pageNavigator()
+ row = current.row()
+ if nav.currentPage() == row:
+ return
+ nav.jump(row, QPointF(), nav.currentZoom())
+
+ @Slot(int)
+ def _pageChanged(self, page):
+ if self._pages.currentIndex().row() == page:
+ return
+ self._pages.setCurrentIndex(self._pages.model().index(page, 0))
+
+ @Slot()
+ def openPdfFile(self):
+ self.disablePrinting()
+
+ if self._file.open(QIODevice.ReadOnly):
+ self._document.load(self._file)
+
+ documentTitle = self._document.metaData(QPdfDocument.MetaDataField.Title)
+ if not documentTitle:
+ documentTitle = "PDF Viewer"
+ self.statusMessage(documentTitle)
+ self.pageSelected(0)
+
+ file_name = QDir.toNativeSeparators(self._file.fileName())
+ self.statusMessage(f"Opened PDF file {file_name}")
+ self.maybeEnablePrinting()
+
+ def hasContent(self):
+ return self._document if self._document.pageCount() > 0 else False
+
+ def supportsOverview(self):
+ return True
+
+ def printDocument(self, printer):
+ if not self.hasContent():
+ return
+
+ painter = QPainter()
+ painter.begin(printer)
+ pageRect = printer.pageRect(QPrinter.Unit.DevicePixel).toRect()
+ pageSize = pageRect.size()
+ for i in range(0, self._document.pageCount()):
+ if i > 0:
+ printer.newPage()
+ page = self._document.render(i, pageSize)
+ painter.drawImage(pageRect, page)
+ painter.end()
+
+ @Slot(QModelIndex)
+ def bookmarkSelected(self, index):
+ if not index.isValid():
+ return
+
+ page = index.data(int(QPdfBookmarkModel.Role.Page))
+ zoomLevel = index.data(int(QPdfBookmarkModel.Role.Level)).toReal()
+ self._pdfView.pageNavigator().jump(page, QPointF(), zoomLevel)
+
+ @Slot(int)
+ def pageSelected(self, page):
+ nav = self._pdfView.pageNavigator()
+ nav.jump(page, QPointF(), nav.currentZoom())
+
+ @Slot()
+ def onActionZoomInTriggered(self):
+ self._pdfView.setZoomFactor(self._pdfView.zoomFactor() * ZOOM_MULTIPLIER)
+
+ @Slot()
+ def onActionZoomOutTriggered(self):
+ self._pdfView.setZoomFactor(self._pdfView.zoomFactor() / ZOOM_MULTIPLIER)
+
+ @Slot()
+ def onActionPreviousPageTriggered(self):
+ nav = self._pdfView.pageNavigator()
+ nav.jump(nav.currentPage() - 1, QPointF(), nav.currentZoom())
+
+ @Slot()
+ def onActionNextPageTriggered(self):
+ nav = self._pdfView.pageNavigator()
+ nav.jump(nav.currentPage() + 1, QPointF(), nav.currentZoom())
+
+ @Slot()
+ def onActionBackTriggered(self):
+ self._pdfView.pageNavigator().back()
+
+ @Slot()
+ def onActionForwardTriggered(self):
+ self._pdfView.pageNavigator().forward()
diff --git a/examples/demos/documentviewer/pdfviewer/zoomselector.py b/examples/demos/documentviewer/pdfviewer/zoomselector.py
new file mode 100644
index 000000000..e5da5708f
--- /dev/null
+++ b/examples/demos/documentviewer/pdfviewer/zoomselector.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import QComboBox
+from PySide6.QtCore import Signal, Slot
+from PySide6.QtPdfWidgets import QPdfView
+
+
+ZOOM_LEVELS = ["Fit Width", "Fit Page", "12%", "25%", "33%", "50%", "66%",
+ "75%", "100%", "125%", "150%", "200%", "400%"]
+
+
+class ZoomSelector(QComboBox):
+ zoomModeChanged = Signal(QPdfView.ZoomMode)
+ zoomFactorChanged = Signal(float)
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.setEditable(True)
+
+ for z in ZOOM_LEVELS:
+ self.addItem(z)
+
+ self.currentTextChanged.connect(self.onCurrentTextChanged)
+ self.lineEdit().editingFinished.connect(self._editingFinished)
+
+ @Slot()
+ def _editingFinished(self):
+ self.onCurrentTextChanged(self.lineEdit().text())
+
+ @Slot(float)
+ def setZoomFactor(self, zoomFactor):
+ z = int(100 * zoomFactor)
+ self.setCurrentText(f"{z}%")
+
+ @Slot()
+ def reset(self):
+ self.setCurrentIndex(8) # 100%
+
+ @Slot(str)
+ def onCurrentTextChanged(self, text):
+ if text == "Fit Width":
+ self.zoomModeChanged.emit(QPdfView.ZoomMode.FitToWidth)
+ elif text == "Fit Page":
+ self.zoomModeChanged.emit(QPdfView.ZoomMode.FitInView)
+ else:
+ factor = 1.0
+ withoutPercent = text.replace('%', '')
+ zoomLevel = int(withoutPercent)
+ if zoomLevel:
+ factor = zoomLevel / 100.0
+
+ self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom)
+ self.zoomFactorChanged.emit(factor)
diff --git a/examples/demos/documentviewer/rc_documentviewer.py b/examples/demos/documentviewer/rc_documentviewer.py
new file mode 100644
index 000000000..fee824311
--- /dev/null
+++ b/examples/demos/documentviewer/rc_documentviewer.py
@@ -0,0 +1,5135 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.5.1
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x0a\xde\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x07tIME\x07\xe7\x03\x09\x07\x09\x01^\xb7)\
+\x9a\x00\x00\x09\xbeIDATX\xc3\xad\x98\xddo\x5c\
+\xc7y\x87\x9f9s\xce\xd9s\xcer\x97\xe4J\xa4L\
+\x85\x96IJr\x1c\x08\x8e\x9b2\xad\xfb\x85&\x06Z\
+\x040\x9c\x00\x06\x92\x5c\xf4\x0f\x88\xe1\xd6@\xff\x90&\
+\xbe\xf1E\xe1\xdb\x02\x85\x80\xa2\x17m\xe3\xcb\xa2Jc\
+\x1b\x84c\xb9\x94YJb\x14\xd1\x12\xbfD\xf1kI\
+\xee\xd7\xf9\x9a3o/vI)\x14\x97\xa2+\xbd\xc0\
+`\xb1\xb3sf\x1e\xfc~\xef\xbc;g\x14'\xc7\x14\
+\xf0m\xe0\x22\xa7\x8f/\x81/\x80\xdd\xaf\xf1L\xdfP\
+}\xfa\xffL)U\x15\x91W\xde|\xf3\xcd?\xfa\xe0\
+\x83\x0f^?\xed\x84\x17/^\xfc\x0dp\x07\xf8_`\
+\x1e\xf8\xed\xb3\x00\xbaG\xbe\x0f\x02\xafN\x9fQ\x7f\x13\
+h\x06vS56@6\x11\x86\xe1\xc5 \x08N\x9c\
+\xc8ZK\x96e\xbc\x5cU\xeex\xc4\xcb\xcd\x9co-\
+\xb6d\xac\x9e\xf2\x00h>/\xc0\xb3\xc0\xdbW\x06\xd5\
+\xdb\xa1\x8b\xbb\x99\xe0\xfb\xf1^iqq\x91\xc1\xa1A\
+\xd4c\x82\x0b\x82\x12@)\x10\xc1\x18C\xab\xdd\xe6b\
+\x85\xf3\xdf\x1eR\xa3\xf7Z2\xb6\x9e`\xeb)\x1f=\
+O\xc0!\xe0{o\x8c9/\x88\xc0\xef\x1a\xc2\x9e\xc4\
+,--Q\xa9WN\x9c\xa8(\x0a:\x9d\x0eUO\
+y\xe7B\xc7k\x1b\xe9\x0cy6\x5cE\xce\x00\xf7\x9f\
+\x0b\xa0R\x0a\xe8\x0ac\x01\xab\xa0\xbe\xb7\xcb\x7f]\xbb\
+F\x1c\xc7\xd8\xa2@D(\x8a\x82,\xcbH\xd3\x14c\
+\x0c\xc6\x18\xb4\xd6T*\x15B\xe8f\xb6C\xff\x0c\x7f\
+\x06\x05{\xf6u\x01\x03\x0d^\xdabi\xee:q\xdc\
+B\x89\x05\xe9\xda+V\x08\x95\x01\xc0C\xd0\x8eB\xef\
+iF\xcb\x1aO\x0b\x22`,\x01\xf0\x12]\x8bG\x81\
+\x01 \x04\xaat\xd3\x09`\x1fX\xa6\xbb\xfb7\xfa\x02\
+~\xf8\xe1\x87\xbc\xfb\xee\xbbJD\x14\xaa\x0bY\x8eB\
+F\x83\x90\xc44\x19\xd0\x19\x88<R\x1b\x18\x0e4\xae\
+\xa3\xd0Z!V\xe8\xa4\x19\xeb\xa9f/5\xac\xc5N\
+`\xc2\xca\x85\xefLM\xbe\x15\x04\xc1wGGGk\
+Q\x14E\xa5R)\x08\xc3p \x8a\xa2\xa1J\xa5\xa2\
+:\x9dNkee\xe5\xce\xd5\xabW}\xe0\xa3\x93\x14\
+TJ)D\xa4\xbb\xbat\x01\xcf\x07C\x94\x8c\x8b\xb5\
+\x01V\xba\xca\x1c\xec\x8dZ\xe4\xe3i\x8dv\xa0\x9d[\
+\x16wR\x82\xd1\x8b\xa4V1\xe8\xf8\xc1te\xe8\xa5\
+\x17_\x9a\xaci\xad\xf3\xb1\xb11\xafR\xa9\xe8(\x8a\
+\x9cj\xb5\xea\xd6j5\xb7V\xab\xe9\xdd\xdd]37\
+7\xf7\xe2\xec\xec\xac]XX\x98\x036\x81\xe4D\x8b\
+\x0f$\xf2=M\xa9\x5cb\xc0\x0dH\xf2\x02S\x08\x85\
+\x08\x88\xa0\x94\xc2w\x1d|\xd7!/,\x9d\xd4aC\
+U\xf9\xc5\xcf\xff\x91\xb3#\xa3\xf8\xa5\x92\x06\xa2^{\
+\x94>\x22Xk\xc9\xf3\x9c\x81\x81\x01&&&J\xc3\
+\xc3\xc3\x13\xb3\xb3\xb3?ZXXx\x00\xfcs\xcf\xf2\
+\xfe\x80\x8e\x06\xc7\x82\xe3\x80\xa3\x15\xa2\x15\xda*D\xf5\
+v\x90(\x94\x02\xad\xbb\xf6n43\x9aT\xf8\xfe_\
+\xff\x15c\xdf\x18?\x049.\x0e\xe0\x1a\x8d\x06\xae\xeb\
+\xe2y\x1e\x97.]*\xbd\xff\xfe\xfb\x17\xb2,{\xe7\
+\xe3\x8f?>\x7f\xe7\xce\x9d\x7f\x01>>\x0a(\xbdI\
+\xc5Q\x0a\xad\xe8&\xbeVh\xed\x80\x80\xb1\x82\xed-\
+\xac\x14x\x8e\x83\xa7\x1d\x5c\xc7\xc1\x11N\x04{\x5cA\
+c\x0c\xedv\x9br\xb9L\x14E\xf8\xbe\x8f\xef\xfb\xbc\
+\xf7\xde{\xe7FFF\xbe\xff\xd9g\x9f\xed\x5c\xbbv\
+m\x17\x98w\x0e\x1e|\xe7\x9dw\x0e\x09\xb5\xd3m\xae\
+Vx\xda\xa1\xe4i\x02O\xe3\xbb\x0e(\xc8\x0aK\x9c\
+\x15\xec'9\x8d$'\xce\x0dYa1\xc6\xf0\xb48\
+\xb08\x8ec\xf2<\xc7Z\xdbu\xcdq\x98\x9c\x9c\x0c\
+\xa6\xa7\xa7_\x9c\x9c\x9c\xfc\x03\xe0J\x7f\x8b{5\xcc\
+\xd5\x0a\xcf\xedZ\x1c\xe7Bb\x15\xa9\x0a\xc8}\x17\xab\
+4\x16\x97\xc2\x11\xb2\xb4MQx4Z\x1d\xf6\xf6\xf6\
+\x08\x82\x00\xcf\xf3\x10\x11Z\xad\x16\xedv\x9b \x08(\
+\x97\xcb\xb8\xae{hs\xbd^'\xcb2J\xa5\xd2!\
+\xf8\xe8\xe8hP\xadV\xc7\x80\x0bO\x00>\xb2\xb8k\
+\xa1\xab\xbby\x96\x88\xf0`\xbf\xc3\x9eD\x84g_\xa0\
+6\xf6\x12\xe5\xa1Q\xbc\xa8F\x9e\xb4(67H7\
+7y\xb0\xb1\xc3\x8d\x1b7\x98\x9c\x9cddd\x844\
+M\xf9\xf2\xcb/\x99\x9f\x9fgrr\x92+W\xae0\
+>>~\xb8\xde\xed\xdb\xb7\xa9\xd7\xebt:\x1d\x92$\
+\xe1\xf2\xe5\xcb\x18cd\x7f\x7f\xbf\xb7MQ\xc7*\xa8\
+u\xb7\x944R\xc3\xf6N\xc2\xc3\xc2\xe3O~\xf4w\
+\xb8\xe7\xbfC[\xd7\xd8m\xc1z#c\xbf\x99\x91K\
+\x01g \x18\x11.\x07\x96\xc46\xf9\xf5'\x9fS\x1b\
+.S\xadTYZ\xd9\xe0/\x7f\xf03\xe6>\xff\x88\
+\x8d\x8d\x0d\xc6\xc7\xc7\xd1Z\x13E\x11Q\x14\xd1n\xb7\
+\xc9\xf3\x1c\xdf\xf7q]\xf7\xf14Q'X\xac\xd8O\
+\x85FP\xa141\xcd\xf7\xfe\xe2m\xdcs\xaf\x90H\
+\x88M\x158\x06q\x14\xa9\x11\x1amC\x12\x1bL^\
+\xa0\xc4\xf2p\xdb\xe3\x5c\xf5\x22*\xee\xb0\xd7\xc9\xd8I\
+'\xf8\x9f[u:\xed\xecp\x039\x8eC\xb9\x5cf\
+dd\x84R\xa9D\x92$(\xa5\x18\x1a\x1a\xa2^\xaf\
+\x8b\xeb\xbaBW\xa3\xe3\x01\x95\x82\xbd\x0cJ\xa3S\x8c\
+O\xbf\xc1\x85W_g\xf9\xee\xefh\xd7wH\x93\x0c\
+W|\xcex\x83\xb8C5\x1c\xd1l\x1bK'6\xc4\
+qA\xabm\xd9o\xbbD\xaeO\x91\x09\xf5\x96\xe6\x92\
+\xc4\x84\x99}\xcc!M\x18\x86\x0c\x0f\x0f\x13\x86\xe1\xa1\
+ja\x18\x92$\x09\x9e\xe7\x1d\xfc\xdb\xf6)\xd4\x0e\xe4\
+\x02\x95\x81A\x06\x06\x87h\xed<`\x7f\xfe?I\xea\
+\x0f\xb1E\x8eW\x1a\xc0\x1b:\x8fS\xb9\x8c\x8e\xce\xe0\
+\x11\xe2\xe0\x93\x1bK\xb3\x99\xd3h\x09X\xc8s\x8fF\
+#\xe6\xfc9CI\xec\xa1\x82J)|\xdf\xc7q\x1c\
+\xc20<\xec\xd7Z\x1fUP\xfa*X\x0b\xa1\xb9r\
+\x8b\xcf\x7f\xf9O\x08\x8ap\xff\x1e\xc3e\x1f\xd7Q\xa4\
+\xa6`\xfb\xb79\xdbv\x80\x89\xef\xfe\x80\xea\xe8\x1f\x13\
+\x95\xc7\xc8\x8d\x10'\x86N^\x90t\x0a\xe2\xd8\x90$\
+\xe6\xf7N5\x070\xae\xeb\xa2\xb5~l\xcd\xee \xcf\
+\xf3~\xaf\xbfo\x99\x19-+\xcex{\x88,\xe1\x06\
+\x11g\xa6jO\x8c\xdb\xeb\xe4\xac\xdd\xfew\xb6\x16f\
+Hj\x7f\xc8\xa5\xa9\x1f\xd2l\xfb\x88\x05\xb1`\xa5[\
+\xd8=\xd7\x81\xbc\x0b\xd7\xaf\x90\x1f\xf4\x1f\x1d\xd3\xbf\x0e\
+\x02\xbe\xaf\xf0#\x177\xf0p\x8e9\xdbU\x03\x97V\
+\xe8\xb1\xbc\xb9\xcdv\xbc\xc8\xd4\xcb\x86\xc0\xd7d%\x8d\
+1BQ\x08Eaq\xb4\x832'\x03\x1e\x84\xb5\xf2\
+\xf8\xa1\xa9\xff&\x81n\xa1\xf6]\x85\xeb:\xc7N\xb6\
+\xd9L\xd8j\xe7\xf8\xb5\x0b\xbcp\xee5\xac.\xe1\xfa\
+\x19\x9e\xef\xe0\xe7\x0eE\xe1P\x14\x1aW\xabc\xd59\
+>\x0e7p\x7f@\x0exz\x05\x9b\xe3\xf9X\xdcj\
+\xa1G.q\xfe\x9bo\xe0\xbf\xf8\xa7<h\x0fpf\
+\xa8M\xe89t\x02C\x92\x14$\xb1\xa16X\x22M\
+\xd5\xa9\x00\x8f\xfe\xdeG\xc1\xee\xeb\x91\xe3(p\xd4\xa1\
+\xa2Gcm\xb7\xc3O\xff\xf6\xef)\x8f\xbf\xc6\xc3z\
+\x82\xd7\xc88[\xf1\xc82K\x96\x15\xbdfy\xed\x95\
+!n\xb6\xdcc\x01\x8e\x03<u\x0e*\xa7\xd7\xfa\x00\
+^~\xa1\xc2/\xff\xe1g,mw\xd8N]\xfc\xb3\
+\x13L\xbf\xfe\xe7O\x8c\x9bY\x86\x89\x89\x09\xce\x9e=\
+\xfb|\x14t\x9cG\xa7f\xa7\xf7yp\xca~<\xbe\
+5^ej4da\xd5\xe1^C\x13]z\x99\xb7\
+\xdez\xeb\xd8\x85\x83 \x8a\xa2S\xe4 OW\xf0\
+@1\xa5\x1e\xb5n\x07\x88X\xa40\xd8<%\xc8b\
+\xfc<\xa1R\xb4\x88\x9c*\xd5\x812cccO\x05\
+xf\x05Q\xbd\xa3\x04\x00\x16\xb1\x05R\xe4\x885\x88\
+\xc9)\xf2\x84\x22\xed`\xe2\x066\x8b\xc9[\x06k\xbd\
+.\xfc)\x14z\x1a\xe0)\x14<\x90P\x10k\xb0Y\
+\x9b\xac\xb5\x83\xe94\xb0y\x8c\x14\xe6\x89\xf1\x07\xcf<\
++\xe0\xd18\xd1\xe2\xbc\xb9E\xde\xdc\xea\xfb;@j\
+\xb0\x1bm\xc9\x97R\xb1S\xb9\xf2ED\xf3\x0cq\xba\
+2\xe3\xf4\x9f\xc0X\xa4\x99\x8aY\xd9\x97\xf8\xf6\x96m\
+-\xd6ew\xb5!\xad\x81s\xae?\xe5\xb8S\x222\
+\xf8,\x80i\x9a\x8a1&\xa3\xf7\xeay\xa2\x82\x00\x85\
+E\xd2\x02\xbb\xde\x90d\xbd%i=\x96\xb8\x91\xc8\xde\
+~\x22\xcb\xcb\xfb\xb2\xb0X\x97\xc5z,\xe5+\xe7\x82\
+W\x95\xa3\xbf\xf1\xff\x81\xb2\xd6b\x8c\x91\x95\x95\x95\xe4\
+\x8b/\xbeh\xae\xae\xae.\x03\xf7\x8e\x03,\x80f\x9c\
+Sd\x85\xd88\xa7hf\x92\xed%t\xee\xee\xd8\xd5\
+\xf9MY_\xdc\xb1[\x9bmy\x00\xdc\xa5{]q\
+\x17\xf8f\x14EC\x8e\xe3\x14_'\x07\xe38\xb6\xed\
+v\xdb4\x1a\x0dS\xaf\xd7\xcd\xcd\x9b7\xef\xcf\xcc\xcc\
+\xac\xde\xbf\x7f\xff3\xe0\xd6q\x80\x1d\xe0\xce\xf2\xbe\x9d\
+^oJ:\xbf!\xed\x1b\xebv\xe7\xab]\xbb\x06\xfc\
+\x0a\x98\xa1{9\xb9\xd3o\xd1\xaf\x03\xb8\xb6\xb6\x96\xcc\
+\xce\xce\xb6>\xfd\xf4\xd3\xc6'\x9f|\xd2\xda\xdd\xdd\xfd\
+7\xe0\xbf{p\x9b\xc7\x01n\x01\xff\xfa\xee\x7fd\x00\
++\xc0\x8d\x9eJ+O[\xec\xa0<\x9c\x04\xd8n\xb7\
+\x8b\x95\x95\x95dff\xa6q\xf5\xea\xd5\xed\x8d\x8d\x8d\
+\x9b\x9dN\xe7:\xf0\x9b\xdeZO\xdc#\x1e\x05l\x02\
+\xd7\x81% \x05Z\xbdv\xaa\x10\xba\x80\x079l\x0b\
++Y\x96\xc9\xfc\xcd\x9b\xad\xf9\xf9\xf9\xf6\xdd\xbbww\
+\xd6\xd6\xd6\x96\x92$\x99\x8b\xe3\xf8\xd7\x22\xb2\x0e\xd4\x81\
+F\xcf\xbd'\xe2(\xa0\xa1{\xf9\xfdu/\xc0\x0b\x11\
+\xc9\xb3\xb4\xfb\xe2\xb1\xb5\xb5\x95mnnf\xeb\xeb\x0f\
+\xb3\x95\xe5\xe5\xf4\xc1\xfa\xfa\x8d\xe5\xe5\xe5\xbb\x8b\x8b\x8b\
+_---\xdd\xef9r\xa7\x07U\x9c4\xb1\xfb\xd4\
+\xa5O\x17\xcd,\xcb\xd6666V\xe7\xe6\xe6\xaa\xab\
+\xab\xab[\xf7\xee\xdd\xdb\xbau\xeb\xd6\xf6\xf5\xeb\xd7\xb7\
+vww\x7fE7\xaf\xbe\xe2\x84\xfc=.\x9e\xc3\x1d\
+(\x00\x1a\x98\x00~\x02\xfc\x98G\xb9{\x83>\xb9u\
+\xda\xf8?\x8d\x91Q\xef\x82F)\xd6\x00\x00\x00%t\
+EXtdate:create\x002\
+023-03-09T07:07:\
+18+00:00+#\x10\x1d\x00\x00\x00%\
+tEXtdate:modify\x00\
+2023-03-09T07:07\
+:18+00:00Z~\xa8\xa1\x00\x00\x00\
+(tEXtdate:timest\
+amp\x002023-03-09T0\
+7:09:01+00:00J\x90\xfc\
+\x80\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x03\xb2\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xa7\
+PLTE\xff\xff\xff\xfe\xfe\xfe\xf6\xf6\xf6\xdc\xde\xe0\
+\xca\xcd\xcf\xc6\xc8\xcb\xce\xcf\xd1\xe5\xe6\xe6\xfc\xfc\xfc\xe7\
+\xe8\xe9\xbc\xc2\xca\xc0\xc8\xd3\xd4\xda\xe3\xd7\xdd\xe6\xc8\xcf\
+\xd9\xae\xb5\xbf\xc2\xc5\xc7\xf6\xf6\xf5\xea\xeb\xeb\xb7\xbf\xcb\
+\xce\xda\xe9\xe6\xef\xfb\xe9\xf2\xfc\xdd\xe7\xf4\xaf\xb9\xc7\xbe\
+\xc1\xc3\xfb\xfb\xfb\xfb\xfb\xfa\xbf\xc5\xcc\xbb\xcb\xe1\xd4\xe2\
+\xf2\xd1\xdd\xe9\xec\xf6\xfd\xee\xf8\xfe\xda\xe6\xef\xd5\xe1\xee\
+\xd3\xe1\xf2\xa8\xb2\xbf\xe8\xe8\xe8\xee\xee\xed\xa1\xaf\xc4\xbc\
+\xd2\xee\xd1\xe0\xef\xc9\xd7\xe3\xec\xf5\xf8\xef\xf7\xfa\xd7\xe2\
+\xea\xd3\xe0\xea\xe3\xef\xfc\xad\xbe\xd4\xcf\xd1\xd4\xe4\xe4\xe3\
+\x98\xab\xc4\xb9\xd3\xf1\xd7\xea\xf6\xd4\xe3\xec\xe7\xf1\xf3\xec\
+\xf3\xf6\xe3\xee\xf4\xdb\xe9\xf1\xe0\xf1\xfc\xa5\xba\xd6\xad\xb2\
+\xb6\xff\xfe\xfe\xe7\xe7\xe6\x9c\xac\xc2\xbd\xd7\xf2\xdc\xee\xf7\
+\xd6\xe4\xec\xe9\xf1\xf4\xed\xf4\xf6\xe4\xee\xf4\xdd\xea\xf1\xdf\
+\xf2\xfd\x9f\xb4\xce\xab\xae\xb0\xbe\xc5\xcf\xc3\xd7\xef\xe1\xf3\
+\xfb\xeb\xf5\xf9\xf1\xf7\xf8\xf4\xf8\xf9\xf3\xf9\xfb\xed\xf7\xfa\
+\xdc\xef\xfb\x91\xa0\xb2\xc9\xca\xc9\xdb\xdc\xdd\xa3\xb4\xc8\xce\
+\xe4\xf6\xf2\xfe\xff\xfa\xff\xff\xfe\xff\xff\xfb\xff\xff\xf1\xfd\
+\xff\xba\xcb\xdc\x98\x9e\xa2\xf4\xf3\xf3\xf9\xf9\xf8\xaf\xb3\xb5\
+\x90\xa0\xb1\xc5\xd8\xe7\xe8\xf4\xfb\xf0\xf9\xfd\xe4\xef\xf6\xb8\
+\xc5\xd1\x94\x9a\x9f\xe2\xe2\xe1\xf9\xf9\xf9\xc4\xc5\xc4\x96\x9d\
+\xa1\x96\xa0\xa8\x9d\xa6\xad\x9d\xa4\xa8\xa2\xa4\xa5\xb7\xb8\xb6\
+\xde\xdf\xde\xf8\xf8\xf7\xe9\xe9\xe8\xe6\xe6\xe5\xf3\xf3\xf2\xe5\
+\xe5\xe4\xd3\xd3\xd3\xaa\xab\xa9\xed\xed\xed\xcc\xcc\xcc\xc5\xc5\
+\xc4\xb6\xb7\xb6\xd5\xd5\xd5\xd2\xd2\xd1\xad\xae\xac\xd9\xda\xd9\
+\xf5\xf5\xf5\xc9\xc9\xc9\xca\xcb\xca\xb3\xb4\xb3\xca\xca\xca\xaf\
+\xb0\xae\xd5\xd6\xd4\xfa\xfa\xfa\xfd\xfd\xfd\x08\xe8\xba\x00\x00\
+\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x09\
+pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\
+\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\
+\x1f\xc9N\xa7\x00\x00\x00\xcfIDAT\x18\xd3c`\
+\x80\x01F\x06t\xc0\xc4\xcc\xc2\xca\xc6\xce\x81\x22\xc6\xc9\
+\xc5\xcd\xc3\xcb\xc7/ \x88$&$,\x22*&&\
+&.!)\x05\x17\x93\x96\x91\x95\x93WPTRV\
+QU\x83\x0b\xaakhji\xeb\xe8\xea\xe9\x1b\x18\x1a\
+\xc1\x05\x8dML\xcd\xcc-,\xad\xacml\xed\xeca\
+\x82\x0e\x8eN\xce.\xaen\xee\x1e\x9e^\xde\x08\xf7\xf8\
+\xf8\xfa\xf9\x07\x04\x06\x05\x87\x84\x86!l\x0f\x8f\x88\x8c\
+\x8a\x8e\x89\x8d\x8bOHD\x08&%\xa7\xa4\xa6\xa5g\
+dfe#;>'7/\xbf\xa0\xb0\xa8\xb8\x04\xd5\
+\xaf\xa5e\xe5\x15\x95U\xd5\x0e\x18\x01PS[W\xcf\
+\x81!\xda\xd0\xd8\xd4\x8c!\xd8\xd2\xda\xd6\xc4\x84!Z\
+R\xdb\xde\x86!(\xd5\xd1\xd6\x89\xa9\xb4\xa6\xb9\xab\x1b\
+C\x90\xa1\x07\xe4R\x00\x9f\xbe*\xa3\xfa_u\xfe\x00\
+\x00\x00%tEXtdate:crea\
+te\x002023-03-08T17\
+:27:26+00:00\x9e\x98\xa5\x85\
+\x00\x00\x00%tEXtdate:mod\
+ify\x002023-03-08T1\
+7:27:26+00:00\xef\xc5\x1d\
+9\x00\x00\x00(tEXtdate:ti\
+mestamp\x002023-03-\
+09T06:20:50+00:0\
+0\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\xaeB`\
+\x82\
+\x00\x00\x15a\
+\x1f\
+\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed]ms\x1b\xb9\x91\
+\xfe\x9e_\xc1\xd3~\x89*$\xd4\xefhh\xd7I\xe5\
+\xbc\x95T\xaa\x9c\xca\xd5%\xa9\xbbo)Z\x1c\xd9\xba\
+H\xa2\x8a\xa4\xd6\xd6\xfe\xfa\x03\x86/C\x884)\xca\
+\x92\x15o<\x96\x8a\x83\x07\x8d\x97~\xd0\xdd\xc0@\xc4\
+\xf8\x87\xdf}\xbc\xba\xec\xfd\xd4L\xa6\x17\xe3\xebWG\
+\x18\xe0\xa8\xd7\x5c\x9f\x8dG\x17\xd7\xef^\x1d\xfd\xfdo\
+\x7f\x18\xf8Qo:\x1b^\x8f\x86\x97\xe3\xeb\xe6\xd5\xd1\
+\xf5\xf8\xe8w\xbf\xfd\xd5\x0f\xff1\x18\xf4^O\x9a\xe1\
+\xac\x19\xf5>\x5c\xcc\xde\xf7\xfet\xfd\xcf\xe9\xd9\xf0\xa6\
+\xe9\xfd\xfa\xfdlvszr\xf2\xe1\xc3\x87p\xb1\x00\
+\xc3x\xf2\xee\xe4\xb87\x18\xe4\x92\xd3\x9f\xde\xfd\xaa\xd7\
+\xeb\xe5f\xaf\xa7\xa7\xa3\xb3WG\x0b\xf9\x9b\xdb\xc9e\
++7:;i.\x9b\xab\xe6z6=\xc1\x80'G\
+\x9d\xf8Y'~V\x1a\xbf\xf8\xa99\x1b_]\x8d\xaf\
+\xa7m\xc9\xeb\xe9wk\xc2\x93\xd1y\x96\xee:\xf3\x81\
+[!L)\x9d\x00\x9d\x10\x0d\xb2\xc4`zw=\x1b\
+~\x1c\xd4Es\x1f\xb7\x15%\x008\xc9y\x9d\xe4\xc3\
+\xa4N?^f&>\xd9\x996w\xbd\xf5\xcc\xfeM\
+\xfe]\x15X\x02a:\xbe\x9d\x9c5\xe7\xb9d\x13\xae\
+\x9b\xd9\xc9\x8f\x7f\xfbq\x959\x800\x9a\x8d\xd6\xaaY\
+\x92_\xb5[\x8d\xc8\xf5\xf0\xaa\x99\xde\x0c\xcf\x9a\xe9\xc9\
+\x12/\xe5\xf3\x80\x8ef\xef\xb31\x90\xb7\xc9\xf7\xcd\xc5\
+\xbb\xf7\xb3.}1zu\x94\xf5#\x03k\xd3\xcb\x1e\
+\x9c\xae\xcc\x08\x02S\x9b\xb5\xacv=K\xf47\xa3\xe6\
+\xa7\xe6\xb2\x08T\x96WW6\x1a\x9f\xe5\xfe\xe5\xde\xbf\
+\x1b\x0f\xae\x9b\x8f\xb3\x90\x9b\xfc\xb9\xaet|;\xbb\xb9\
+\x9d\xfd#g6\xd7\xf3Z\xb2V\x9d\x8am\xf6\xbc\xdc\
+\x0a\xdch\xe3\xedp\x9a\xdb8y?\xbejNn.\
+\xae\xb3\xae\x93\xf1\xc9p2\xfb0\x9e\xfc\xf3\xe4/\x1f\
+\xef\xde5\xd7'\xd3\xb3\xc9\xc5\xdb\xdbi\xfe\x1c^\x0e\
+\xdf^6u/\x9a\x8f7\xe3\xc9lp~q\xd9\xe4\
+\x0eoT\x96o\xce\xa6'\xe3\xb6\xa6U\x0d'\xc3\xb3\
+Y\xee\xf0\xf4d\xa9\xdc\xcd\xf5\xbb\xad\xb5~\x1c\xddd\
+;@\x87\xad\xb9w\xab\xdc\xdf\xe6\xec\x1fF\xcd\xf94\
+\x7f.\x86\xa8\xa4\xc8\xc0\xdb\xbc\x9c\x9b\xad\xac\x19N\xfe\
+8\x19\x8e.\xb2o\xb5\xd8z\x95g\xe3\xcb\xcb\xe6,\
+\x0f\xf3\xf0\xf2\xc3\xf0nZ\xda[UU\x17e\x91\xd4\
+V\xdaV;\x9d\x8do\x16\xf7\x85\xda\xd9\xdde\xa6\xa0\
+\x80\x83\x5c\xe3xr\xfa\x1d\xb4\xd7\xf7-4\xce\xd6v\
+1\xbb;\xc5\xef\x8f\xba2\xe3\xf3\xf3i\x93\x1b\x86\x16\
+\xebZ-%X\x14\x8fz'\x9f\xd7\x1alk\x0d\xb7\
+\xb7\xc6\xab\xd6~8\xa9\xd5\xdeM\xe3&K\x94\xf4\x00\
+\x96\x8c\x9bt~\xfeX\x96(\xc5\x83X:o\xaf\xc7\
+\xb2D)\xed`\xe9\x89\x8d\x8d\x0c\x8e\x0eT\xec\xf14\
+\x1a}I\x1aM\x9e\xce\xd8\x80\x0e`\xe9l\xd44\x9f\
+\xc1\x12\xc8A,\x8d\xa8\xf1f\xf4h\x96\xc0\xf6\xb1T\
+R\xc3\xcbm,\xfd\xef\x9f\xdf\xfc\xe9\xc7\x7f\xc8?V\
+\x15\x9f}|u$\xde%\xef^\x1d\x0d \x10\xaea\
+\x93WG\xaaa\x1dy\xb7\xa8\xf9o\x93\xe1\xf54\xcf\
+\xbeW\xaf\x8e\xae\x86\xb3\xc9\xc5\xc7_CH1Q\x1f\
+\xf2\xbfrK\xdaG\xe6\x00\x00\xd4'\x08\x1e\x8d\x8e7\
+j\xf9\xfb\xf5\xc5,/\x1cn\xa7\xcd\xe4\xafe\xf2\xfd\
+\xcb\xf5\xdf\xa7M=x\xdb\x06b'\xc5\x91~D\xfe\
+q\x1b}\xe0\xc5\xa4\xf7\xd5\x1eX\x95w7\xc1\xfa\xfb\
+\xd7\xf8z{\x13\xf2\x90&\x0c\x93\xecn\x02\xfe\x90\x14\
+|{\x13\xf6\x90&\xa2\xc6}M\x80\xbf\x06\xd8\xde\x84\
+\xefo\x02\xf7\xd5\x1e\x7f\xff\x89\xda\x13\xacYqe\xb0\
+\xb5\x15\x1f\x1c2\xdb\xd5\xe3\xe9\xfbI\x93W\xbb\xdfU\
+\xf6\xbe\xec@]1\x1b\xa5\x07\xda\xe4~\xfb\xc7\xd6\xf4\
+1 ;'J\xf9~\x80\x10\x14\xa3k<\xae\x9c\x0e\
+#U^\x17m\x95<\xbf\x97}^g\x17\x7f,\xa3\
+\xff\x844\xd5Q\xc4\xf2\xb5\x8b0B\xfe\x17$\xcc=\
+\xb8c\xe4\x8a6$\x0c\x22\x84\xfc)\xf6:\xcd\x9f\x8a\
+=&\x83]\xf37\x03v\xd5`\xd6\x83C\x142\xf6\
+\x15z\x97Q\xd4\xc0l*\xd2\xc9R\x96\xf5\x9081\
+\xa7N6\xa3h!*D=\xd4\x8c\xa77\x93f8\
+\xfas3{?\xce\xdd\xcc\xba\x14U;j\xf2\x02~\
+\xd6L\x1eNI;s \x10D@]\xc0\xdd\xb3S\
+ \x10\x14\xe0\xae\xe7sy\x92\xa4\xd6Q\xd2=\x5c\x85\
+\x9c\x93\x90**\xe7]b\xf6\xd8\xcd\x0f\xe7\xcd\x1f\x87\
+\xb7\xd3\xe9\xc5\xf0\xfa?/oKw\xf7t\xb8\x8aX\
+\xa3\x1f\x9b\x9f.\x86\xb3\xe5\x93\x98\x98\xaa\xa5\xfb\xd3n\
+\xdd\x02\xb3\xaf\xaf\xf6\xe6]zFkJ\xba\xd3\x9a\x12\
+?\xc5\xa8\xd7\x16)!jm\x8d\x12(\xaa\xfbA\xd6\
+\xf8\x9cNf\x02\xb2\x8b\x16\x81C\x9d\xa1(\x8eI\xc2\
+=\xc5\x89C\x02s\xae\x14G\x90@Zi\xad\x01U\
+\x93:= \xe6\x0dp\xb1@rC\x10\xe3>\xb1\xe5\
+\xb0\x17\x84\x0d\xdd\xe2\xf1\xe7\xba jd`\xd9\xe2\x82\
+\x8cb\x107\x5c0*'\xb5m.\xa8\x0a1\xe16\
+\x1f\x14\xe1g\xf1A&\x95\x84\xea\xbb}PD7|\
+\xf0\xd9\xe6C\x16I\xbb\xe6CQ\xadf\xaaH\x01\x08\
+\x1c\xb9\x9a\xaf\xccB\xe96t\xb2\xe7[e\xcf\xb7\xca\
+N2\x08\x0f\xb1-\x08\xeah\xe4\xda\xc7\x10-\xe6\x9f\
+f@}\x0a\x96\xdcHRI@p\x8f\x9e\x9c\xfa\xc9\
+\x02DG\x95\xbe\x86$\x98 \xc9\x03\xd7\xe7\xcf;\x83\
+\x02\xedrn%|\xc2\xf5G\xed\x8a\x03\x04\x0fh\xc8\
+^y\xe4\xc3\x83'\x11\x07\xa6\x88T\xc5\x11\xe5\x10]\
+P\xeb8Bd\xc1\xb4\x0e\x9f\x1a\x03\xc5DL\x99\xe1\
+\x97\x9aU\x88\xc8\x1e\x13>c\x0a\x18\x89d#\x84F\
+u\x93:\x84F\x0e\xc9!b\xa5\xbci\x10q\xc4\x17\
+V\xde_V\xf9]f;+\xb7\x97\xc3Y\xf3\xeb\x14\
+\xfbp\xfc\x92nH\xca\x15\x03\x12C2\x97H5\x03\
+\x16\x90]1V\x0cH\x0a\x84\xb5lF]\x83P\xd4\
+C\x16\xb2\xfby*\x1113\xf5\x92K~\x05yb\
+\x8d\x08II\xad?\x80\x05\xbb\xe9\xb8\x1a\x0b\x8fA\xcc\
+\xc5\xea\x87\x8a,\xcd\x86\xe2\xa4V\xdb#Z\x88e8\
+\xea\x85\x1cX;\x1c\xf6\xa2\xd4\x91}a\xea0\xd9\x9c\
+\xbb\x8a:\xe3\xb9\xb4V\xc4)\x05\xc2\x9afjgo\
+\x22\x11\x8d/J\x1c\xc3\xbf\xc7$\xf9b\xcb>ey\
+2\x86\x8b]\xaa\x9bA,v\xa9\x84$Q\xca\xadS\
+\x14\x16\xc9\xe4\x0b\x82\x01b\x1f\x8d\x83r\x12\xed+\x04\
+P\xd5\xa4\xf5F\xc9\x00-\x98\xb9x\xbd\xad\xeb\x16\x9c\
+,Y\xb5\xfe\xdc&|^\x09W\x0b\xd0=t\x17!\
+\xe1 Q\xa2QU\x9fb\xd0D)a\xd5\xb8{\x00\
+!\x87To\xecHH1\x09Z\xbd\xa8\x8eA\x999\
+\xe2~3\xdb\xcf\xb5$CP+\xac\x0a$\xa7T\xa8\
+\x8el\xa0i\xc1\xba\x83Yy(\xc3\x10\xc9\x9c\xb5\xef\
+1x\x8a\xa0\xfe\xc0\x05\xf2.\xcbAN\x0f1@J\
+\x09\x8e\xf6Yr7$g\x97\x177\xff5\x9c\xbd_\
+ox\x891r7\x94Kp\xcf\xc6\xfbM\x16\xd9\xd8\
+\xdd\xcd\x8fZ\x97\xa7\xb7\x93\xcb_\x7f\xb7\xa9\xd3\xf1\xf7\
+%w\xed\x0f(\xd3\xd9d\xfc\xcf\xe6\xf4z|\xdd,\
+\xee\x07\xed\xb3\xe8)Z\x90\xa4\x00\xe0\xb2\xcc(\xaag\
+\xfdN'\xe3\xdb\xeb\xd1:\xf8\x7f\xe3\x8b\xeb\x1a\xbd\xba\
+\x985\x93\xcb\x8b\xfcq\xba*>\x1aN\xdf\x0f'\x93\
+\xe1]\xd5ZA\xdb\xad\xea,\x19P\x17p\xd7\xc3\xb5\
+\x87\xccL\xd6\x9f{\x83\x14\x03\x13:\xf6)\x86$\x1c\
+\xb5\xf7\xba7@\x88A\x0d\x91V(f\x10c\x88\x09\
+\x81\xfb\x8c\xc1\xd11Z\x06I\x83\xaa\x1a\xf59\x05u\
+\x8aFm\x05\xcc!\x83.}\x89\x19N)q\x01c\
+\x10\x11\xb7\xbez \x22\xc3\x05\x88\xa9\xc4X\xf3\x10c\
+\x12H\xbd7\xebp*\x8eI X\xea\xed`\x04\x08\
+\x1e9\xfa\xbc\xb1$\x80\x05\x94\x80\xe4T@J\xc1\xdd\
+9u`\xa9\xd7$DUb\xed\xe0R\xafA\x88\xc0\
+\x92\xd6\xe1\x81\x96\xeezJ\xbaj\xac\x02\xbb\x8e\xbdY\
+\x87;5^\xb70&\x8f\xa6+\x95)7\x86\x01\x1c\
+b\xd4ur,e\xd0\x01\xb0&2Zp%u\x5c\
+\x91\x9eG\xc2c\x96%Iq9<\x92\xb6\x0d\xe4\xcf\
+\xbd\xf5\xf1\x15\x0e\xe2\x08(\xa5\xda\x04!9\x89\xfa\x0a\
+\xd6\x5c\xad\x04c\xd6\xcc\x8dhH\x18Sn>\x85h\
+\x82\xd1J\xe8\x8d\x1eI\xad\xed\x95\x84\xa4\x94\x15P\x0d\
+\x9a\xdc<C\x14\x145\x17\xea\x1b\x06gf\xa7\x16\x14\
+\x8d\x89\xa5\xb0\x02\x09\x80Z\xb2\x90(\xa0F\x8ek\xf0\
+\xeb\x05\x0c\x80\xb4\xaa\xa2\xb5\xba\x14,\x81\xdb\xb2\xad\x98\
+\x0a(\xc1c\x12\xa9\xbb\x85\x08\x01\x10\xc0\x17\x0ax\xd2\
+\x0c\x02\x073D[\xa9\xca[Y\xf9\xf9\xde\xfe\xcb\xcd\
+<\x88\xc4\x0e\xee\xbe5r=\x1e5\xb3\xbb\x9bf\x9a\
+\xe3\xcd\xf4\xac\xbd\xa6\x8b\x9f\xb5M\x9ae\xd4\xf9\xf7\x9a\
+@R\x0a((\xf1\x89\xe7\x0f\xfe\xe2\xf3\x07\xa5\xc7\xcc\
+\x1f\x87Y\xcbv\x9b\xdb\x8c\xd2\xe8R\xc8\x16\xad\xc3t\
+*\xe3\xae\xd1\xd6\xc34\x81\x06\x02\xb5\xb8\x1e\xa6\x09)\
+K\x02Y\x1d]\x88 \xc3Q|=\x12\x11IpM\
+]\xc8Z`\x06\x89b\x15\xa5;\xb8\x8a\xd2\x1d\xbc\x1e\
+8\x890\xb0J\x8c\xeb!\x960\x06\x06\x06\xae\xa34\
+*\x05\xb4\xe4\xb1\x8e\xd2(\x1e\x10\xfd^\xa0\x17\x09n\
+\x5cM\x09\x1dXE\xe9\x0e\xae\xa3t\x81\x0dY\xba \
+=oK\x92\x0aV3\x98ZP \x93\x9aF4\x09\
+d\x91h=Hc\x94 )\x22wA\xba\x1a\xc7*\
+Jwx\x15\xa61z\xe0\x14!\xad`- \x05\xc8\
+\x89\xd8\x85i\xb4\x18\xb2\xac\xe3\xbdxh\x14\xd8\x22\xa7\
+\xb58\x8d\x9aB\xa2\xe4\xd8\xc5\xe99XJ\xc5:N\
+\x13\xa4\xa0\x91\x00\xeb8\xdd\xc2(\xd1\xd6\xe34A\x0c\
+\x08\xaa\xeba\x9a\x80\x02%\x01\xbf\xd7\xad\x14\x83\x90D\
+\xaa\xc2t\xc2\x00qmB\xe2\x9a\x95.L\x1f\xb2B\
+\xe3\xafo\x85\xf6\xcc\x13H\xe4\xa0\x8a\x84\xb6m\x02\x01\
+\xaf&\x10\xe3\x00\x821\xea\xe7O \x18\x10\x04\xdc\xb5\
+\xbf\x9c*\xda\xe7>3\x89\x92\xac\xdc\xba\x1bs\x09\x19\
+\x09\x83\x88\x11\xf7\x8d\x02)'\xb0\xcf\x9f@H\xe09\
+\x1e@\xea\xc2\xeb\xcf\xeb\x83\x12Y\x8d\x08\xeb}P\x83\
+ @`Zo\xb5H\x80h(k]\xc4J\xf6@\
+\xed77.\xfdA\xda\x9bEy\xb4\xf6\x87u\xb2p\
+\xe4\x155\x03\x83\x8a\x14\x04\x0d\xce\xa0R\x91\x92\xa5B\
+\x12%\xc5\x9d\xfa\x0a?lG\x89\xe3\x93\x8c6a\x00\
+F\xd2J!\xc6\xc0\xd1,z\xa5V\xcc\xa2.\x09\xbc\
+R\x0bQC'\xbbwS\x0f\x021\x13Q\xeacH\
+\xa8\x90\x04\x8f\x9f\xc0J\xf8A\xac\x99\x88=\x09kb\
+\x81\xc8]SE\x9b\xa6\x8c\xaaC\xaci\x0b\x9e\x22\x13\
+\xc6\x8a\xb6N\xf6\xa1\xb4\x19\xb1e\xda\x9e\x86.\xfc\xa2\
+t9\x05\xa0\xe4ZG\x94(A$F\xa3z\xdf\x9b\
+,$6#\xabC\x8a|\xb6\xda\xe4h\x87\xab\xfd\xfc\
+\xb1\xc4\x831\x8b\xd7\xe4`\x0a\x89\x85\xd9*r\x14\x82\
+:\x98T\xb6Td!Y\x8c\xbaCy3\x87/\x12\
+H\xf7\x9br\x9f\x8f\x8f\x0e\xa7I!D\xf2\xe4u\xa4\
+2\x0a\xc0~\x7fs\x1a5$\xe4\x88\x15K\x9d\xe8.\
+\x96\xe4\x81\xd3\x8d\xf0\x174\x91(E\x1f\x94X\x87\x1b\
+\x0dL\x09\x98k\xdd\x91*\xb5;\xa9]js|\x98\
+\xda\x8cO3\xebx \xd6tO!\xe4\x90\x0cA*\
+}\x08\x03\x02\xa6$\x9d\xe8\x1c\x85$\x19}\xec\xfa\xae\
+\x0e\xae\x82\x85\x22\xeaC0L\xac\xe2\x9f\x1fdM\x22\
+=4\xda|\xe3\xf4\xc1\x9c\xc2\x97\xe54\x06\x88\x00)\
+\xd5\x9cj@2w\xff\x85\x90j\xf2%H\xdd\xaf\xde\
+ \x06\xf5\x08\xc9\xbc\x9fo\xd9\xc1D\xe9\xf8\x00\xe2v\
+\xa9_\x0f*DW\xd9\x18TO\xc8|\xc8\xa0\xee\xa4\
+U\xe9_p\xb51\xa0Zm\xaa#\x83{\xa5(\xd3\
+N\x0d\xf1\x81\xde\x08\xf2\x10\x0d\xf7\xee\x15PPM\xe2\
+V\x7fk\x1c8\xa8\x03w\xe0\xc7V4\x02\xf0\xda\x08\
+\x9cm\x13=\xabD\xb7\xdb\xd9\xcdp\xf4\x90\xbf\xba\x07\
+N\xa6\xa4\xdc\xb7\x90\x9cQ\x14\x9b\x01\x173\x8e\x8e\x92\
+\xac$0\xa8\x1a:k\xbf\xfd{\x81\x93!\xf6\x07\x96\
+\x82%7|\x82\xad\x82d\x89\x0e<3p\xb8\xc9\xed\
+\xb2\x07\x90\xfa(\xd0\xe6Y\x12\x13\xb0\xa3\x83O\x08\x81\
+c\xc3\xcd\xc6\x91\xaf\xde\xc9\xfe\xd6\xfc\xe8\xe0\x830\xec\
+q\xd4\xc8FkOt\xda\xcdDl?K\xe2\x87\xb3\
+t\xde^\x8faIa\x1bK\x87\xb7\x06O\xc7\x921\
+\xeee\xc9\x98\x0fg)\x02G\x80G\xb0d\xac\x87\xb3\
+\xf4\xf6\x5c\x1b\x80\xe7cIx?K\xa2\x8f\xb2\xa5\xd1\
+\xe8\xec1,I|\x8c-\x8d\xce\xdf>\xa3-\x99\x1d\
+p\xbe4\x9d\x9f\xe19=\xf2|\xa9\x99\xf9~\x96\x18\
+}\xab\xe5\x06\xdd\xcd\x93zl\xde\xea\xbeQ\xd9\x1dJ\
+\xcfh\xa8\xfe\x00\xedp\xabv\x11\x9enT\xa2\xec\xb7\
+\xdd\xf8\x98\xd9\x02\xd0\xc4\x1fc\xbb\xf1\x11\xb3\x05\xe20\
+=_\x1cd\xe2\xb8\x8f%&N\x87\xb34\xd2\xc6\x1e\
+3[0\x09\xfe\xab\xcd\x16\x94\x12\xecc)\xcb\xd0#\
+X\x8a\xe5\xdf\xe1,QJ\xf24,\xe1\x0e\x96\x0e?\
+xT\x9f\x0b\xc2g:\x9b\xa7\xa6\xa4N{\xce\x05\xe1\
+\x96\xb3y/v\x08\x8d\xf0\xc9\x0e\x16o\x9e\x19\xe3\xa8\
+\xd5\x97o\x9f\xf8\x1c\xdb\x8b~\xb3\x96\x08_\xe2\x9b\xb5\
+\x0c\xf4b\xdf\xac}\xc9\xaf\x8a\x13=\xc9\x19\xd2\xe7:\
+\xd5\xfc\xa2g\x5cH\xbf}\x8b\xfe\x99}=\x1e\xc0\xf0\
+S\x1e~$\xd6\x00\xd1\xd1\xaa\xc3\x8f_\xe2\x1c\xe7\x0b\
+\x9enc\xa2\xf4ox`|\x7f\x0cd\xfa\xe6\xe8\xcf\
+\xfb\xd6\x10\x96\xaf\xff\xad!_\xf8\xd54\xc4\xf6\xf5\xbd\
+\x9a\xe6E\xa3\x1b\xfb\x81\x84\xed?c\xfb\x0b<\xe3\xcc\
+$\xf0K>\xe0\x0d\xb4[yzJ#A\xf6\xf6\x84\
+\xf1\xf3\x1c\x86~\xe1iQ\xe4\xa9\xa9\x8a\x1e\xc5\x7f\xf9\
+\x07\x97I\xecy\x99{\xfes\xcb?\x9c\x94W\xa6\xb6\
+w\xdd\xe9\x8d\xe1U3\xfa\xe9\xa2\xf9\xd0\xbdW\xf5\xed\
+p\xa5\xc4\xcd\xf0]\xd3\xee;e\xba\xce\xdbk\x91\xf1\
+v<\x195\x93e\x96\xb5W\x95\xb5\xd8\x9aZ\xbc\xf7\
+\xb6\x1e\x9dR\xeb*\x1f\xb6\xe7O\xdf\x0fG\xe3\x0fy\
+\xf9s?\xf3\xe7\xf1\xf8\xaa\x94J\x8b\xeb~~\x99\xf0\
+X,$&\xd6\x8d\xcc\xdc\xa0\x041aOr?s\
+4>\xbb-oe\x1e\xdc\xce\x87\xf5\xe6\xe3F\xf1\xdb\
+\xc9\xa4\x08\x5c\x0e\xef\x9a\xacw\xfb\x81E\xa8z\xa7p\
+)W\xbdw\xa7\xc3\x8aQ\x5c\x8c\xa6E\xf7\xebw\xb9\
+\xa3\xb2\x0d\xbf\xbb\x8f7W7\x8b\xac\x8e\x8f\xe9\xfb\xf1\
+\x87\x9cWv\xcc\x86\x97\xd3\xe6~OK\xd6\xe0f|\
+q]\x14\x99Mn\x9b\xf5b\xb7\xb9\xca\x0e\xae\xcb\x95\
+\xbc\xc1\xdb\xb7\xe3\x8f\xdb\xf3?\x5c\x5c\xe7a\x19,\x95\
+\x05\xb4OH,u\xb7D\x9f\x90(\xe3d\xf0\x89\xcc\
+\xcc\x81\xc5\xe5\x8b}+\xad\x96\x06>\xbb+o~\xfe\
+xW\xb0\xcao\x0b@\x1am\x05Vt\xdfG\xefj\
+\xb4\xa2z\xe56\x9b\xde\xd2\xe2W\xcdl8\x1a\xce\x86\
+\x9d\xeb,\x112\xc4e\xef'\xa3\xf3\xd3\xff\xfe\xf1\x0f\
+\xab=\xd4\xb3\xb3\xd3\xff\xc9\xef^\xee\xb6=\x8b\xc0\xf0\
+\xed\xf86\xb3\xd5\x96Y\xc8\x8d\xceNK\xd0\x18\xce~\
+{q\x95\x1d\xa2\xbcq\xfb7\xf9\xc5\xd7\xd9\x89\x97\x19\
+\xb5p!$\x03U\xb5\x93f\xfeF\xedm/!\xcf\
+?W\x17\xa5\xd0\xc9_g\xf9\x10\xc9\x9fJ#\xeb\x9b\
+\xd6'\x8b\x8e.7_;=rb\xa9h\x9bzw\
+o\x0c\xf3\x1b\xa0\x9b\xcbWGo.\xf2K\xb0/\xc7\
+=\xdc4\xce\xf1\xed\xcd\xd5x\xd4,\x5ch\x99?Z\
+\xa4\x0bu\xdd\x89\xb3\x1d\xe7\xcd\xca\x19\xb3\xee\xaa\xac\xa0\
+\x14\xa5\xc5^]w\xd2L%DG\xd2>R@S\
+\x11\xcb\x07\x81\x90(\x888\xb4\x07~Pb\xf4r:\
+\x09\x89\x830J\x01]\x19\x942\x94S\x14<9\x88\
+\xcdSE\xb2\xbd\xd1\x80\x80Ie\xad\x9c\x05\x14K\xe2\
+]\x03\x05s%\xc2\xd2\xacJ\x80\xc4Q\xfb\x88\x1a\x9c\
+\x941\xd7%)$\x05\xe3\x0cz\x10\xd5\xd8\x13i\xef\
+\x9d\x5ch\x9e\xe0\x5c8\xdf\xb8\x07(\x97\xb4*t\xc9\
+\x5cK\x0a\x861\xd9\x023\xf5^,\xf1\x92\x04\xb8\xef\
+\x16@\x11\x14{\x8b\x02\x05+utI)ut%\
+\x04C\x12O\xba\xaa\x96S\xc9c\x91\xd2\xae@\xdb\x9d\
+\xd5'\xea\x224\x17m\xa4%\x1a\xdc\x8d{\x8a\x01\x08\
+\x94\xfa)\x88X\x04\xf5-\xc3\xd1\x1d\x80\xda<\xfe\xb4\
+\xf1J\xb8\xcd\x03\xeamrr{\xd9\x9c6?5\xd9\
+PF\x9f>\x10\xe5\xc1\x089\xba\xa7\xc5\xe5\xfa\x8c\x07\
+\xa3\xea\x13POc\xde\xacL\xdf\xcc\xfb+7\xef\xa5\
+Y@ \x15s4\xf9\xfe\x93\x07\xfe\x94\xe5),>\
+\xbd\x9c\x9d\xef\xf7j\x06\xfc\x1c\x1d\xf1\xe6\xe3}\xed\xde\
+\xde\xcef\x1b\xca\xb5\xfa,\xd0\xba\xa5\x8c\xcf;\xd6\xbd\
+\x9b\xf4\xb8\xf6\xb2H\xa1Xu\x00#-\x96\x84A\x88\
+\x12R\xc1\x90bt\xeeE\x08\xec\x06)\x15L8\x91\
+[\xcfR)F\xbd7\xa5\x84\x17\xc7H\xc1Hs\xda\
+\xa1\x8f\xedZ?\xaeR\xb9\xde\xf2\xc9\x01\x1cM\xbc\x17\
+-\x80!\xc4\x5c!\x04\x173\xab\xbb\xf1\xf3ft\xa0\
+H+p\xf6\xa9\xbf?b\xbf\xb87\x1c\xd7\x83\xb5\x19\
+i0\xd5\x1c\x14\xa76\xc5\xb9\x83\xbcn\x93\x8ch^\
+\xbc\x09U\xd40c\xa5\xf7nY\xc4\x02\x92n\x02o\
+2\x94\x82\xf3\x02*\xe1\xa2\x06\x8b\xa7.\x92\x89\xbd\xf3\
+TD\x0fD`\x95\xaf\xa2.J\x15W\xad;\xf8s\
+\xef\xd3ng(\xc4D\xdf\x7f\xd2\x22\x95\xf0+\xf0\xba\
+]&,\xc2\xc7\xfbM!\x7f y\xf9-\x83s\xfc\
+@\xf7\xdd|u\xe6WD\xd6\xb7\xd9\xf3+\x9c=\xeb\
+\xc8$\x12\x8f\x0eXC\xed\x0es\x9c\xf0+\x0a\xf5\x9b\
+\xb1,\x82 &\x8f\x9f\x0ef\x9c\xf8_nz\xdd\x19\
+\x9b 8\xa3\x09\xd2*F!\x04e\xe46PE\x17\
+\xd4\xe3\x9d\x06\xb0k\xe07\xdb\xa3\xe0\xec\x84\xec\xe5O\
+\xcb\x03(\xd6\xaa)R\xbf\xbb\xab%X\x03\xa2\x83x\
+\x1f\xd5\x83\xb8)\x1f\xef\x18\x1fI`l\xbac\xb2a\
+\xf8%L6_\xffZ\xa1^\xfe\x90w\xe0\xd6\xff\xc0\
+*\xc1'\x05\xee\x16\x02\x9d\x09\xbe{\xe8ljt\xd0\
+;\xd8\x9ee\xd0\xf7/\x1c \xb8Ar\xd3\xee\xbd\xe7\
+\x92b\xb4\xe3z[\x8e%\xd2A/\x13\xaa\x1f}7\
+\x02\x7f\x94\x02W\x86\xe6\x18TA\xa4O\x18\xc4$I\
+;\xb81(\xa8\xc4~\xe4\xe0`ISk(\x16\x10\
+\x93eT\x83\xa0)`\x8b\xb1\xa0\xf6\xa3\x05'G\x8e\
+ma\x01\x06\xeeGo\xa3\xb2a\xa9\xb2\xc4kJ\x01\
+\xa2\x9b\x95\xca\x5c\xfa\xc8\x14\xcc\xc0s\xc2\xdb\x84+z\
+jS\x9d\xe8\x9b6\x9dR J\x18)\xa7\x89d#\
+\xad\x14b\x04\xe7\x85\xfcF\x9a8$J\xc4T\x9a.\
+i\x08\x91\x9d\xb5\xb4&\xc1%\x22\x97\x09\x87T\xc8y\
+\x93\x94\xc3^2C\xb81a\x048h\xca\xb0/\xfd\
+\xb8y\xf2n\x97\x9f\x95\xc2\xdb}j[@&\x22;\
+\xbe\xa7P\x80\x18#\xa4\xe8i~\xbdd\xb4E\x8f\xc7\
+\x87x(3\xcf\x0dz\x97\x9fZzR?\xc5\xfb~\
+J\xd17\xfd\x94P\x02n\xf8)!\x85X\xfbi\x8b\
+\xa5\xdaOKa\xd8\xf0S\x8a\xb1\xf2Sr\xec\xfc\x94\
+\x5c;?-\xa9\xcaOs\xba\xf2K&\xbc\x9f\xae\xfc\
+\x92\x5c\xef\xa7+?-\xe9\xceO\xc9q\xc3OkR\
+j?\xddo\xc2\x9b\x1e\xb9\xdb\xa8\xfdSF\x9d^\xc0\
+[\x0f\xdf%\x22\xfd\xbce\xac\xbd\xc8\xf3%\x84d\xed\
+\xd3\x80\x05T#\x9d?\xd2\x91\x00\x01\x15\x90!r\x94\
+\x9ex\x1fc\x10N\xe8\xde&\xd2B\xfcMI\xc9\xfc\
+\x91n\xf5i\xba\xaa\x8c0$N\x02\xa9o\x16\xd8@\
+\x15{\xe4A\xb4\x08\xf4-\x06(\x17\xf68\x05F\xd2\
+\x8c\xe4Bf\xc5\x02Y\xa5$\x13\x84\xe4\x1as\x05\x1c\
+\x80\x1c\x84{\x88\x108wZ-\x08\xcc{\xa1\x12\xa4\
+@\x18\x030\xb6M+\x07\x02$\xc1V\x0f\x8c\x92\xb4\
+\xa7\x14\x00\xd4c*\x18\x98j\xd4\x0d\x0e\xb6\xed\x5c\x11\
+1\xed_\xd2\x97\xc7\xba\xc7m1*\xc8\xe6\x94\xf6\x95\
+\x99\x0fF\xf4<z\x05\x84\xa4\xea-\x98XI\x8bI\
+q\x84\xd8BJ\xeeT\x8c(\xf8j\xc4T\xac\x14-\
+\xa0\xbaS* C\xd2\x98\x0a\xc4\x9a\xc0\x0b\xa4n\xc9\
+\x0aB\xe8\xf3\x92\x89\xd2\xaa$\xa1BjAvvj\
+!\x22\xe6\x16RBOuQ\x83\xc4\x8b\xa2\xa8$\xd4\
+\xc7|C1Q[\x14Rt+\x10\x83E\x98CF\
+Z\x10I\xd4j:\xc7@\xbc\x80\x0aD\x84sH\x84\
+Z\x08\x99u\xbd\xa0\x12\x0b/\x0b\x92\xc7\x16\x8c\x08\xa4\
+\xa5k%\x91\x901\xce\x13\xa9cTbH\xf9\xc2\x16\
+\xa4\x18-.%8:\xd22!\x85\xd0\xb9G\x12T\
+I\xe1\x10\x85\xd0\xe7\xc9\xb9\x87\xd4`.^\xc0\xb6\x0f\
+\xc9\x17\xbdLE\xabD\xad\xb3D\x92\x98Q\xa5 \x0c\
+:\x1fJG\x11\xeb\x5c\xaf(\xc5\xf36\x11R \x9f\
+\xbb(\xc8\x82\xae\xa4!\x1aF\xec[1\x1b\xf1Lt\
+\x5c2h\x12\xc4T\xb2\xee\x06\x8bH`\x01\x5c\xdb!\
+*b\x86\xa6T\xc4\x8c\x13\x99\x96\xcaX\x91\xdb\xed.\
+\xd6$\x1eK\xa3\xcb\xb0`1\x99/;\x22`\xd4\xa2\
+\x11\x17C^\xd0H\x16\xb1\x95\x05\xf6dmP\x01\x8d\
+\x9c\x0a\xa6`\x09\xe2z\xa0\xe1\xb4\xa0\xb3\xc5d\x89Z\
+\x9c\x13\x18SHm3,K\xa3\xac\x83\xd2r\xe8\x95\
+\x03\xc7\xa4\xad\xcb\x08\x92Q\xcb)\xdb\x22\xde\xa2Fe\
+\xed).J\xdar\x1c_\xb7\x98\xa6\x14\xe7 \x80q\
+\x0b\xc5Hs9Rl\x91\xca\x1b\x0b\xc0D\xcas\x90\
+1\xc6\x02!;y\x0b\x01\xb1\xc5m\x8e\x5c\xde\x92Y\
+b:/=\xe5\xcd2Y\xcc\x7f\x99\x94*iZJ\
+/\x93!\x97\xd2J\xa45\xc5O&\x917\x92U\xe3\
+]r\xeb\xae\x9b\xed\x89\xd0\xd3\xf6:\xab\xae\xe9\xee\x9d\
+\xb8\xc7\xd7\xb5\xed\x81]\xbe\x05\xcfo\xc1\xf3[\xf0\xfc\
+\x16<_<x>xYJ\xf6U\xed\xb4\xb4On\
+?\xe4\xaf\xa5\xe5\x8f\xff\x07K$K_f\x83\x00\x00\
+\
+\x00\x00\x1b\x90\
+\x1f\
+\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=io\x1b9\x96\
+\x9f'\xbfB\xeb\xfe\xb2\xc1J\x14\xef\xc3\x1dg\xe0\xb3\
+\xd1@\x0ff\xb0\x99c\xbf\x05\x8aTv4-K\x86\
+$'v\xff\xfa}\x8fu\x97\x8aeQ)w\xecA\
++\x08,\xb1\xc8G\xf2\xdd\x8f|E\xbe\xfb\xf3\xc3\xed\
+b\xf0%Yo\xe6\xab\xe5\xc9\x11#\xf4h\x90,\xa7\
+\xab\xd9|ysr\xf4\x8f\xbf_\x8d\xec\xd1`\xb3\x9d\
+,g\x93\xc5j\x99\x9c\x1c-WG\x7f~\xff\xe6\xdd\
+\x7f\x8dF\x83\x9f\x92e\xb2\x9elW\xeb\xe3\xc1\xe9l\
+\xf5)\x19\xfc\xbcX\xdco\xb6\xbeh\xc08\xa1\x84\x0e\
+\x07\x1f\xfe\xf9\xd3\xe0\xf2\xe1n\xb5\xde\x0e\xfe\xb6\xb8\xbf\
+\x19\xfd\xbc\x1c\x10_\xf8\xcf\xb4\xcf\xe3\x81&\x94\x0e\xce\
+\xee\xe7\x8b\xd9@1)\xed\xdb\xc1`4z\xff\xe6\xcd\
+\xbb\xcd\x97\x9b7\x83\xc1\x00\x06\xb8\xdc\x1c/7'G\
+\x9f\xb7\xdb\xbb\xe3\xf1x\xb9!\x13\xec\x8fLW\xb7\xe3\
+\x0f\x93/\xc9\xd5j\xfd\xaf\xe4\xd3\x18\x06?>*[\
+\xcc\xa6E\x8b\xbb\xfb\xf5\x82\xac\xd67\xe3\xd9t\x9c,\
+\x92\xdbd\xb9\xdd@uV\xad>-\xabO\xd7\xc9d\
+;\xff\x92\x00\xfc\xdb\x15\xf4\x86-\x97\x9b\x1f*\x95\xd7\
+\xb3\xeb\xa2\xf6\xd7\xaf_\xc9W\xe1+1\xe7\xdc\x98\xf2\
+1\xe7#\xa81\xda<.\xb7\x93\x87Q\xbd)\xcc\xaa\
+\xad)\xa7\x94\x8e\xe1YYs\xbfZ\xc7\x0f\x8b\xf9\xf2\
+\xd7\xe0`\xfc\xd3j\xef@\xd9;\xf8_4\xc8\x0b\xc8\
+fu\xbf\x9e&\xd7\xd02!\xcbd;\xbe\xf8\xfbE\
+\xf1pD\xc9l;\xab\x80\x01\xa0\x9b\xe9\xe4.\xa9\xf5\
+\x9b\x17\xa6\xf8\x9a\xdc&\x9b\xbb\xc94\xd9\x8c\xf3r\xdf\
+\xbe\xc6jX0\x9f\x9d\x1c\xfd\x02\xc8^,V\x1f\x99\
+/\xf9:\x9fm?C\x05n\xfd\xcf\xcf\xc9\xfc\xe6\xf3\
+\xb6\xfc\xfde\x9e|=[=\x9c\x1c\xd1\x01\x056c\
+\xf0?\x85\xb4\x02\xd8\xd7\x8b\xd5\xd7\x93\xa3/\xf3\xcd\xfc\
+\xd3\x22\xed0YN\xe0\xeb\xe8\xd3d\xfa\xeb\xcdzu\
+\xbf\x84\xee\x96\xc9\xd7A\xb31\xcc\xeb\xd8\x8f\xf7\xe4\xe8\
+n\x9dl\x92\xf5\x97\xb4y\x8e\x82\xe3b\xe0\x94\x08\x9e\
+\x8e<\x9bW\xf5\x91\xb4\x84\x0d\xd6\xcehZo=[\
+M\x11#'G\xb39\x08\xd2\xcdh\xbe\x04L\xdf\x02\
+\x97\xad\x96\x04\xe8\xf9[\x1d\xe0\xea~{w\xbf\xfd\x98\
+<l\x93e\x0a\x19PZ\xc1\xaf\x7f\xec\xdb\x91\x1ar\
+7\xdb\xc7\x05\xf4\x91#\xe2\xb8\x8a\x87\x02x\xe2\xa5q\
+t=_$\xe9\x90\xc6\x9fW\xb7\xc9x6\xf92\x9f\
+\x8dW\x0f\x8f7\xc9r\xbc]\xdf/\x7f\x1dC\xfd\x05\
+\x22o\x0c\x0a`{\xbf\x19\xb7\x8c\xfdny\xd3\x0a\xfd\
+av\x07<&\x041\xaa\xf5\xf9c\xe5\xf9\xfbw\xb3\
+\xe4z\x93\xf3\x02~\xe7\x8ck(\x06\xdeM&\xeb\x9f\
+\xd6\x93\xd9\x1c$\x16+T\x00MW\x8bE2\x05\xbe\
+\x98,\xbeN\x1e7G\xd9c\x80Po&\xac\xc5.\
+6\xdb\xd5]Z\xa5@\x13\x16\x8d\x00\x0c(\xb0\x1f(\
+\x15L'?\xfa\xa2\x15\xb0\xc1|\xfbx\xcc~<\xca\
+[\xac\xae\xaf7\x09\xf4E\x8b\x12\xec\x08k\x03xs\
+4\x18\x1f\xd4\x01\xdd\xed\x80\xb5u\xe0|\x07\xe3\xfa\xbc\
+zC\x8f\x11\xcf\x8a\x1e \xf0\xf3\xa2\xc7\x98gD\x8f\
+4\x92=#z\x00\xbcxV\xf4@\x07*\x02=\xbb\
+\xf3\xa7\xbc9\xff\x024\xe5\xa2c\x8a-\xb3\x98\xc8)\
+Md\x03M;\xd3\xaf\xc0W\x1d3\xdc\x13>\xfd\xb6\
+\xd9\xb3\xa6p\x94\xa3c\xbb\xa3\xeb\x9c\xfd\xb5\xff\xec0\
+Ix\xfa\xcc\xc5M\xbf\xad\x83o\x9a>\xb7\xda\x1e=\
+!8\xed\x83\xe7\xd6\xd08\xecP:\x95\xd3\xe9\xbe\xbc\
+\x01\xf0y\x0f\xc8\xf9\xf1[\xb0\xa3\x99\xc8\xb1s\x93\x15\
+\xfdc9\xdf\x82\xdfv\x0f\xee\xc3\x07\xf4%\xfe\xba\xfc\
+\xc7&\xc9\xea<\xb0\x93\xa3\x91\x95\xe0\xecrIs\xc1\
+y\xc4Ba\x05q\xce\xe4\xc3\x7f\xe0P\x86\xee3\xe5\
+9\x03<\xf2\xddjy\x9f\x7f_O\x96\x1b4\xc8'\
+G`\x93\xd7\xf3\x87\xff\xa6C6\x1c\xb1!\x1d\x8e\x84\
+\x83\x16V\x0d\x99\xa4\x1e\xdc\xdb\x06\xb9\xf6\xa4\x8ceI\
+\x932?\xeeh\x1a\x0d\x9d\xec\xd0k/\xd2P*\xa7\
+\xce\xee\xd3\x01\x7fFM\xaf\x9c\xa3Ok\xfa\x80\x10?\
+\xad\xe9\x01<\xdfG\xd3\x07\xf8\xf4iM\x0f\x1d\xc8o\
+ag\xe54\xfd\x83\x9d+\xe8\xd4\xbb\xf4\xda\x93\x9d\xc1\
+'a\xfbt\xf0\x8d\xf4\x02\xfb\xf0\x9c\xfc\xcaw\xd5o\
+\xbf\xfc\xcaE\xfb\xfc!2\xd9&\xeb\xbd\xc58\xad\xae\
+\x9c\xc2\x90\xe1:\xf9ir\xbf\xd9\xcc'\xcb\xb3\xc5\xfd\
+\xba\xe8\xb3\x1b\x0aNlv\x91|\x99\xfb\x98\x06\xc38\
+\xa5$\x03\xc7R\xd4F]\x87\x0d\x1d\xdat\xfc\xe9\x00\
+\x0e\x1c\xb7\xa6F\xf65nF\x1c\xe8H\xcal\xc7\xb0\
+\xa1?]\x1f\xf6t1\xbf\xfb\xdbd\xfb9m\x93\xff\
+\xea\x92}\x84\x9a\xd7\xd3\xd4\xa2\xda\xbc+\x00\x0c\x06\xf0\
+\xf4/\x83\x11\x13\x8aX\xc9\x87N\x10\x01\xce+\x95\x83\
+\xd3\x81\xa0D;\xca\xb9\x1b\x16\xdf0\x0c\x87\x7f#\xe6\
+\x0c\xe1\x9cK\xbdw\x83\x1d\xf8\xbf\x95h\xc9\xc3\xee\xf5\
+#\x04\x99y\xcb\x96\xc7\x0f\x9d\x8f\xa7\xd0\xba\x00\xdf\xf2\
+\xf8\x015\x9b\xd6D\x09\xce\xeb\xacr\x97b\x867\xf5\
+D)\x82\x80\xfd\xc5\xf1\xfdz\xf1\xdf? \xdfO\x16\
+\x85WA\xad|\xeb\x9fV\xe4u\xb3]\xaf~M\x8e\
+\x97\xabe\x92}\x1f\xf9E\x92\xe2\xd1\x08e\x088\xe5\
+\xd8\xafpT\x0b\xff\xbd\x9a/\xeb\xa5\xb7s \xfbb\
+\x0e\x7f\x8ee^6\x9bl>O\xd6\xeb\xc9c\xad\x0f\
+,M\x05\xf8\x98\x11\x0a\xf2\xe0\x9c\xb7\xd3\xfeam*\
+\x001\x9dL\xc9\xd4ow\x11\xb6}\xc4\x15\xa3\xc9z\
+\x9a2`\xceB\xef\xdf\xd5Q\xb0\x97\x04\xf9\x85\xad\xe3\
+\xcf\xeb\xe4\xfa\xe4\xe8\x87\x16K^r\xea.~#\xcc\
+\x5c\xd8,\xa1E\x82\x7fD+c\x99\x91\xf0\x9ds\xa2\
+4w\xd4\xe6Soe\x10d*\xad\x88\x13\x96\xd3\x9c\
+?\xae\xdb*^\xb7T\x5c\x03\xbf2\xc2R~E$\
+\xf6\x8b\xb9\xaa\xc3\xbf\x839\x11\xe7 t\x18t\x98\xa6\
+QZ\x9a\x06\x06G\xc6\x10\xca\xc0z\xb2\xff\x00TV\
+\xdc\xa9VT\xf2\x08T\x06'\x0f\xfe\x177J\x1a\xf5\
+\xd4\xe4\x9b\x15\x9f}\xf2\x1d\x12(\xd0\xf1\xf9C\x02\xdb\
+1W\x09*[1\xa7\xfb`\x1b\xc64\xd1\x9cS\xf7\
+\xd4\xe4\x9b\x15w&\x7f@\xe4\xb5?\xdb4\x16*\x9d\
+1\xfd\xb0\x0d\xd1V\x09\xd03\x9e}\xca_B\x09b\
+\xb5`\x1c\xa2\x0d\x06\xb3\xb4\xca\x8a\x9c\x8f|\xb4\xc3\x8c\
+&\x16]\x94J\xb4c8\x11\x8eK\x91c-\x8dv\
+\xa0\x22\x93\x92\xe6\xe3\xc5x\xc7\x81\xb7\x22\x00\x9f\xec\x19\
+\xf0\xe6\x97\xa9\xc2x\xb3\xfd\xa8\xed.\xbc\x01\x8fp\x8b\
+x3D\x0a\xc9\xeahS\x96\x18m\xabXcL\xa2\
+\xa0\x16>W\x8a5\x0b05\x155\xacq\xc287\
+N=\x0b\xb7\x15\x0bl\xadX\x13/\x8f\xdb@\x17i\
+I\x95\x93Mn\xabU\x84BF\x1d\xa1N)\xfe,\
+\xdc\xc6Y\x17\xde\xfa\x92Rc\xb9\x85`%\xc3[\xfe\
+Kh\x01\xae\xa8\x848\x01\xf0\x86\x81\x9ai0\x1b\xee\
+\xc1I\xa9*X\xd3\x96P\xad\x85\xabs[\xb3\x22b\
+\x0d\xd8\xd7R\xa9\xd3\xb8\xee\xa0pN\x1a\xe0\xdf\xde\xc2\
+P\xb4\x06T\xaa24\xd9\x8d\xe7\xa0CW\x8f\xe7z\
+\xa66n\x80\x84\xa8\x0d\x0fM\x9dgA\xcb\xe9\xc2V\
+zQ\xe7\x8aP\xa5\x85\xaa\xb3\xac$\xa0\x96\x0ag\x13\
+\x91\xaf\x0d\x11V\x99\xa2b7\xfb\xf4oy\xfdFX\
+\xd0\xf2Z\x93\x13\x01\xad*h4M\x9d(\xdc)4\
+\xaa\xc2\x11\xa1\x99\x13U_\xacY\xef\xba\xa5\xde\x1a\xf9\
+\x8eH\xcd\xa9\x89\xf1\x09\xc3\x92#\xc0a\xa1\x9a3\x95\
+I\x0e\xe3B+\x09\xee53 F\x9aY7\x1c\x81\
+\xfaU`\xec\x1d\x7f{8\xab\x0bkY\x7f\xac.\x95\
+\x95 \xe2]\xac\x0e\x1d\x8a:\xab\xf7\xcd\x01Vup\
+\x80\xe3\x15\x0e\xd0\x10\x10\x839\xe7\xb2\xc2\x01\x10\xbeX\
+`\x22&+\x1c\xd0\xacw\xddR\x0f9\x00#\x1f\xb0\
+\x88M\xe6\xdf\xa5.'\xd6P\xf0\xd5R\xea\x82\xf7!\
+\x1c\x84R\xde\xf6\x0ap:\x14\xc5\xaf\x94\x11%]a\
+\x86\x9f\x16\xa5\x03\x19\xc0\xf5\xb6tEAo8m\x8d\
+\xea\xa4\xbf\xd3=\xac\xb8\x81\xbf\xa6\xfa\x1b6c`\xcf\
+y\xe7\xb0\x1d7\xf5a\x8f1\xc7\xe1\xfd\xbbb]\x04\
+\xd30f\x98\xd6\xf2\xa6:\x82\xaf\xf3\xe5l\xf5u\x94\
+\xa7\xbf\x98f\xd6I\xf6<\xcb\x961\xd2\xd6\x1f\xdfM\
+n\x92\xcd\xe7\xc9\x0c\xf3a\xf8\xee\xa3l\xf9\x06\xa7\x90\
+\xba\x877\xf7\xf3Y\xb2]-\x12`9\xcc\x82a\xc5\
+\x835\xf4P-\x87\x8f\x7f\xb0\xfa\xf4o\xc0P[\x93\
+O\xab\xf5,Y\x17]\xb0Z\xb1_8\x06\xe1\xd3\xfe\
+\xe3\x1f\xe0\x80\xf2\xe2tA9\x1d0\xe0\xf2\xd3d\xd3\
+\xc8_\xf9m\xb5\x02q\xe0`\xbd\xd1T7f\x8d\xb2\
+)5\xe1\x8ae\xfe[\xf9$\xd3\xbdVim[1\
+\x09MM\xb3U\xf6\x08\xdb*\xd9\x00x\xbf^\x83P\
+\x8d\x16\x93\xc7d\xdd\xcca\xaa$-\xdd=\xf8\x922\
+\xab)+\xd8|^}E\xdc\x9e\x1cm\xd7\xf7\x8d9\
+b\xf9\xe8n5_\xa2\xbc\xee>\xceFu;y\x98\
+\xdf\xce\x7fKf\xb8\x80\xff\xfe]\xadu\xc9\xf5?\xc1\
+\xaf\xab\xf5\xea\xf6o\xeb\x04\x10\xf6!\xd9n\xe7\xcb\x9b\
+\x9c\xa1\xd3%\xb9\x87Gl\x92\x15\xad\xd6\xf3\x9b\xf9\x12\
+\xf3\xab\xb2\x81\xe6E\x8f\xd5\x22\xcc\x95\x028\x88\xeef\
+\xd9c\xb5,'+rMF\xd6\xc1 \xb9\xbdk-\
+\xaf0%/\xabVJeY\x9au\x06}\xa5\xc2\xb5\
++M\xef\xdf\xbc\xbbM\xb6\x93\xd9d;\xc9\xb9)\xff\
+-\x8e\xde\xbf\xf9\xd3;\xcc\x87\xbb\x86j\x7fJ\xbf.\
+\xe6\xd3d\x03\xb0\xaa\xbf?\xf8\xac\xb83\x5c?\xf5\x19\
+J8_?\x06\xe4\x88\x06\xa5i\x9d\xce,\xe3y\x90\
+\x90\xdb_\x92\xeb\xed_=\x123r\xfan\xde\x8d\xf3\
+\x11\xbc[\xcf\xae\x8f\xff\xf7\xe2\xea\xfd\xbb\xe9\xf4\xf8_\
+\xab\xf5\xaf\x99e\x80\xd2\xc9\xa7\xd5=\x80\xc7,\xa9\xe9\
+q\x9ax\xf5~~\x0b\x02\x83y\x80\xff\xf3p\xbb\x00\
+uR<\xc0:H\xd2\x5c\x1b!\x80u\x92\xa6\xf6\xb5\
+fC\xce\xa6\xb7sl0\xfe\xb0\x9d/\x16?#\x5c\
+\x8fN\x843\xdf.\x92\xf7\x1ez\xfe5\x1b\xdc\x8b\x19\
+e9\xa0q\x81\xc0qNc\xc0ji\x1f\x90\xf8\xa7\
+?\x7f\xfcp\xbd\x9aN\xb6\xf7\xeb\xc9\xc7\x94\x03v\x8d\
+@]\xbf\x8bB\x0f5vQ<\x01\x0b\x8d\xde\xd9\x13\
+\xdf\xab'\x1e\xe8\xc9E\xf4\xc4\xf6\xea\x89\x05zb\xbc\
+\xd9U\xbe\xa9\xd3\xb2\x80\x9f\x82\xf5{\x1a\xe5N\xc6n\
+p\xf0\xeaw20\x9e{[ \x0c\x11\x22M\xb6D\
+\xd6\xb5\x0f\xd4\xb1\x85\xd4\xb1\xf9\xd4\xb1mU\xdbS\x1b\
+\xb4n\x91\x0d\x9a[dOn\xaa\xb5\xb5\xd8\xe9!\xdd\
+U\xdb\xee8\xa2#\x08&\xa8\x15\x8e\xc9<B\xcf~\
+\x8d\xa0C\xdc\x9e\xe7j8r\x8e@\xa8\xeex\xb6\xb4\
+\x81;>#D\x22\xe6\x91/S\x19\x8ee\xb2\xddM\
+\x89W\xcfd\xe5vY\xced\xdcj\xf9\x07\x93y&\
+\x83\xb0\x80)\x96\xee\x15U~\x02\x971\xe248\xd7\
+C\xce,\x11T*\xbd\xc3d\x1e\xc7\xd5\x9d\xea\xb75\
+\x96{\xb2\xbf\xd6\xeeF.\xeb.\xdbe\xf8\xdeX\xeb\
+ \xe8>l\xd0\xc1<-<\x973\xa8RY\xfa\xe3\
+\xd3\xf2\xc9_\x97|\x1e\x05\x94Q\x95q\xbaY\xec\x0f\
+\xd6\xfa&\xd6\x02mh\xf7d\xad\xff\xa4L\x89'X\
+\xee`\xf3\xa8_\x17\x8e\xeaZ\xc6Q\xfd\x87\x19\xf4\xba\
+C9\xad\x9c\xe1\x99\xee\xc8\x7f\x8d8UDX\x89k\
+\xba\xd6\x11.\x0cG_\xab\xca8%.\x19-\xa7\xa7\
+!\x96v\x8aI3d\x94\x18'\x84\xb4\x83\xf3\x81\x10\
+\xe0\xae\x19\xdcK/K\x19\xf4\xc6\x94\xe5|(81\
+\x1c\x1e\x8bJ\x99r\xc4\x18\xae\xb1qY\xa89\xe1\x8e\
+\x1a\xa1\xb1L\x1b\xa5\xb4\x1aj\x89[8Rk\xfff\
+\xa0\xe4\xd2\x8a\xa16\x849\xab\x8c\xc3\xd6X\xaa\xa8\xf2\
+\xa5\x9c)%\x9d/\x13\xceZ\xe5\xcb\x84\xe4\x86\xd7[\
+s\xc5\xb8T\xd0\x9as\x22$\xa3P\xd1\x12\xeb\xac\x84\
+\x01\xc1\x5c\x04\xb5\xa0a\x87\x1a\xc6\xc8\xb5\x92f %\
+\xd0@\x09%+e\xe7\x03\xf8N9\xd4\xe5\x95R\xc7\
+\x884\xd6\x81z\xd6\x8ah\xcd\x04\x87YS\xf0f\x0d\
+ h\xa8\xa0\x17\x8e~-\x0e\x9cZ\xd0\xde\x16\xfa\x01\
+\xfcp&\xac\xe0\x03\x8b\xac\xa0\xb9q9\x1e\x15o\xc5\
+\xf8o\x83\xaa\x10\x17\xe9\x930\x05\xce\xa0\xc6\x8f\xa1\xd0\
+\x0aw\xd9^\x99Xw\xfb]0eFA\xef\xdb,\
+\xa5#\xff\xc5\x04\x90\x86\x19\xc7\x86\x0aX\xcar!D\
+\xc52\xfa\x8d=\xae\x06 G\x12\x9c@\xa3@\xe0\x80\
+D~A\xb6\xfc\x92\x89\x9bt\xc4\xaf\xb0\x0f\xf7\xaa\xbd\
+\x0b\xba\xc5\x22\xe6\x8dvuM\xfb\x13\xbf\x04\x99\x03\xdc\
+\xd5k\xf5\xdd\xa2\x5cn1\x99\xb8\x9dK2\xfe\xf0\xc4\
+h\xe7\x856\x8b`\x8d{[\xe7\x0f\x10\x0am@J\
+\x99xVF\xe92\x83\xb8\x97\xb4\x97\x19,\xb5\x197\
+\x1d\x8eV\xeb\x96\xfb\x08\xd3Y\x94D\x97\x8bQ\x8d9\
+\x09\xa2\xe1gA\xdc\x8a\xf8\x1f\x82\x1b\x86\xc4?\xcf\xcb\
+\xb42X\x06|\xa8\x95N\x0b\x81\xb8X\xe4\x94\x01\x05\
+W)r\xf8'o\x89%\x8c)\x02\x1a\x95c%\x8e\
+\x12\x02Z\x8aqK<S\xc1@\x14n\x10\xf1J\x19\
+\xb4\xb5\x00\xd8rJY\xa5\xa6\xc1\xcd\x7fkP\x7f\x94\
+\x10\xb3\xc2j\xbfe\x91\x1f\x9du\x22+S0\xb5l\
+\x16R\xd6\xebe\x935\x1a\xb4,\xb0\xa0\x1eb\x82\x86\
+\x94\x02\xfb\x05\xb3\xc2$\xd8\x0e\x10FnZFm\x8a\
+Q3N\x89c\x82\xd5j\x82\x1a\xb6\x5c\xd3\x02\xa4\xd4\
+-\x88\xae\xebA\xcf\xd5?\x80\x02\xb8N&\xafJ\xc9\
+\xb506\xa6\xa5\x87\xecq\xb6\xf9Q\xd8c\xe02\xc1\
+\xd0:X\x0ev+\xb7\xc7\xcc\x18\xa3+\xa5\xb8Yl\
+\xb5\x04z\xa0=\x06\xf8\x5cW\xca\xc0\x1e[p)\x9d\
+K\x0drV\x0a\xd0\xa9\x82\x8fK\xcb\x04G\xabHA\
+\xa3\x1aAM\xbd\x9e\x92\x12\xbaI[;\xc7\x1c\x95C\
+\x81;\xca@:\x8e\xe31B*\x07v\x8d\x83\xb1\x07\
+b\xca\xea\xc8\xd32\xcb\xa0\xb5\xc5\xc41+\x9c\xa9\x94\
+\xa2\xa5\xa4\x9c\x0a\x8d\x10\xa9\x00w\x80{\x9b\x8a\x09T\
+\xde\xa4\xc2d\xa8\xf7$\xd0\xd0R\xa3EZ*\x8cH\
++J0\x01iM\xad\xb8Ui\x198\x0d\xa6\xd1\x1a\
+\x18_8\x96\x1ad\x9c\x99B\x83\xcc\x80\xdb\x01b\x86\
+H\xa3[Q\xde\xc2\x88\xed\xe6W\xbcb\xf3[\xee\x94\
+\xacf\x09\xaa\xd9\xcd\xc9\xd1t\x03\xff\xa6{\xad\xebb\
+\x22^s\xf6\x84\xbe\xec\xf9\xffX\xf1\xad^\xb1\xd01\
+\x02\x22%\x84\xab\x8e<-S\xa6.tE\xe9\xeb\x11\
+\xba\xca{\x13>\x01\xebM\xf1i\xc9\xf3\xc0\xca\xff\xf7\
+\x97_~\xbe\xf8\x080>\xa6\xeb~\x98n!A\xd9\
+H\x9d\xbd\x90\x86\xfe\x96R\x10\xa0(\x90X_\xb2>\
+9\x02?PC\xb4\x95e\x83=\x9dj\xd3\x95\xc7\xee\
+R//sZ\xd3_\x98\x98\x06a\x9d\x81HH&\
+#54\x04p\xe5\xa8\xc5\xb7\xd5\xde\xfc\xa9x\x9d\xaa\
+\xfe\xe6U\xfb{j\x8e\x9f\x17\x98\xf1o@\xa6\xefr\
+\xb7@aa(\xfa2\x0bkK(&\xdb\xe8\xa9\xa3\
+5CxK\xfeX\x15\xd9\xe2\xe3\x9ex\xc3\x840\x89\
+\xc9\xd3\x5ce\xc8\xf7\xf9\xb2\x06\xa22f\xb3\xf8\x1c\x93\
+\xc1$\xc4J\x92\xb1\x8c\x1e\x98\x09V\xd6\x89B\xd8\x85\
+\xff\xd4\xa7jX\x00a s!8W\xfe\xd3\x80#\
+\x82p\x1c\xc8p\x08\xd4\x99\xff4@\xa9\x0c\xfb\x8d\xd7\
+\xde\x9e\xc4\xbc\x8c\xc1\xbc\xcf)eR\x14\x88g\x8a\x13\
+gt\x96K\xf8\xe0\xb3\x1d9\x087/\xf1^\xaf\xd2\
+\xc5\xf7\x86\x1a6\x1ce\x7f\xeb\x7f\xc0\xb9\xb3\xa0\x5c\x86\
+x\x10\x0apZ$\xd3\xb7\xd1\xd0\xd2~hhyo\
+4\xb4\xf2@\x1a\xaa(\xe9\xc1\xc5\x13WH\x06J\x0f\
+\x98\x0f\x01\xca\xcc\x94\xd2S\xaf\x83y\x94\x9aP\x5c\xba\
+0q\x98o\x9d\xa6\x0bb\x8cK\xaeC\xa0.\xfd\xa7\
+\x0e\xca\x85\x05Q@h\x12\xc3\x0f.,\x8b\x9aeI\
+:{\xb2\x84;T\x16u\x0c\x1dG\x12|\x06)i\
+\x95\x90\xb8@\xc0\xf2\xb7J|N,PR\x0a\xa5(\
++\xf5`\xad\xd2\x93\xf2\xd8.\x96L\x80\x15\xa6\x16\xac\
+:\xd8[+\xe4\xb7\x0b\xa4\xa1\xfd\x08\xa4\xa1\xbd\x09\xa4\
+\xa1\x87\x0a\xa4\x89R\xaa\xa0\xda\xa8\x14%\x199sD\
+qp\xecJ\xa5\x8a)f\x8a\xcbR\x1e\xebu\xba^\
+s\x1f\x01\xb5\xfc&\xa7\xd6C\xcet\x1f\x94\x0a\x0bp\
+\x14\xa5XXzc)\xc5\xc4\x81\x94\xb2Q\x22\xc7-\
+\xb8\xd7Y\x86\xad7\x7f\x94\xe3{K\xa5\xdb1\xc2%\
+\x0a\xc3mEs\xd6\xea\x1c*n\xe0\xce8)\xf9\x90\
+Kb\x98\xebC\xdc\x98\xed\x87\x88<,\xb6\xb1D\xe4\
+\xfc@\x22\xbao\xf4\x1e\x1d\xbe\xec\xc7\xbb\xbd\xc7\xa2\xce\
+7c>\xcd\xdb\xed\x01\xf3a1\x8c\xc5\xbc`\x87a\
+\xde\xd1(\xf1\xc1\x136\x1c\xf8\x82\xa5\xfc\xe0\xf2\xb9\xb5\
+\x15M7BU\xa7mE|jU\xf68\xcf\x839\
+;\xe4\x1c\x8f\xf2\xa0\xaa\x079\x11\xba\x1fj\x89\xb0\xbc\
+\xc5R+=\x11\xe7\x00j\xb1\x18j\x81e\x077A\
+\xd7\xdd\x8b\x86\x9c(\x88I9\x13u\xef\xa2/9\x09\
+\x86\xa5\x91\x98\x97ay\x8b\xc6\xbc;\x10\xf3<\x0a\xf3\
+\x9cXmY\xa7\x86R\x12\xecL\x96q\xde3\xde\x95\
+\xec\x07\xef*,9\xb1xO\xcfD9\x00\xefQ\xeb\
+\x0aJ\x11iD\xd5\xa1\xb6\xa0F\x04\xaf\xe1\x1d\xf7z\
+\x14\xaf\xe2\x9d[\xa6)\xfbv\xbc\xebp\x04\x12\x85w\
+\x1d\x96\x9bX\xbck\x13\xc2{\xf5\xf3\x04\x0d\xa2\x5c,\
+\x8c+\xb4,-\x84\xc6\xd5\x84\x8c\xcf\x11\xff\xfe]\x0b\
+W\xc8\x06\xaf\xd7\xf8^\xabj\xca\x7fj\xb8\xb32\xc4\
+\xfe\x1d\xabj\xc5K0%\x94C9?\xce'\x12\xc4\
+\x0a+\xb3\x98\xb0\x0d\xef\x12\xe2\x7f-duE\xe0\x85\
+\xe2]\x85\xc4(\x0a\xef\xea\xb0\x18^\xd0(\x8f\xa8\xc1\
+\xcdmxg\xa48\xbc\xe1%c]\x87\xc2\x80(\xac\
+\xeb\xc3\x22\x00p@bW\xc0\x0a\x9c\xb6\xe1\xbc.\x0d\
+/\x19\xeb!\xd7&\x0e\xeba\xaf\xa6k\x8bDP^\
+n\x91\x08K8c\xf9\xdb\xf0\xfe,\x14N\x94\xa5,\
+\xf3\x0d\xd7\xe9\x09\xd7\x06\xdf\xf3KK\xae\xd3V\x8c\x8b\
+\xa2$m%\x1d\xe3{\xed\xa3\x04\xd6\x10#l\xa55\
+a_Gj\x13\xb3\x80hMo\xee\x8e5\xb9\xd2\xdf\
+\xd9Jy\x82 \xa2$\x08\x84ZR\xe8\x1c\xb5\xfb\x12\
+\x04Z\x09i\xf3\xe08#\x88\xcaO\xcfxvr\xd8\
+\xb0\x0b\x14K\x0e\xdb\x9b\x17\x94\x1d\xc5}\x009d\x85\
+\x1c\x1cO!Q\x91\xf2!\xf1\xe4!\xe6\xbe\x9f|\xb8\
+\xf0bj,A\x5cX\xd4b\x09\xe2\xf4\x81\x04Q\x15\
+\x82H\x02<b\xe2\xc8!\x09DG.3\x0c99\
+p\x1d\xf3\xf7 \x06\xb2AO\xc4p\xb4\xb7\x9dGG\
+U\x88\x18O\x85\x06\x82Fmx(\x86Y\x0e\xb90\
+x\xc3m\x08\xd7.\xdf\xdc\xf0\xe1\x99\xc0U\xf1\x9a\x9b\
+Z\xab\xf3\x82\x8c\xb7\x0b\xae\xc0\xc6\x18o\xc7\x0f[\x0c\
+\x124j\x8fB\x19\xe2\x0c\xab.\x06\xed\xe2^SL\
+\xcc\xa8.\x06\xbd\x5c\xdc\xf3\x03R\x1eZp\x1f\x0c\x8f\
+\x9f\xc0}TH\xdc\xe0\xe9v\xbeW\xc4\x80\x22z\x15\
+\xb8\x17!\x9b\x12\x85{q\xd8\xde\x9c\xa0Q\x81q\x03\
+\xaf\xed\xb8\xaf\xcb\xc6\xcb\xc6}h\xcf \x0a\xf72\xbc\
+]\xd0i\x7f\x19-\xed\xaf\xc2w+d\xc5\xfe\x1a\x9f\
+\xc1^fT\xb5\xd8_h\xc3\x15h\x98\x8a\xfd5D\
+\x88b\xc9\xee\xd9-ppM'\xde\x02\xcb\xde\xf6\x03\
+\x80\xd1\x0es\x87X%\xc5M\xe1\xd1y.G\xed\xde\
+\x04\x91\xb8\x1f\x9a\xefv\xe6\x04\x91\xe0#\xfd>\x01\x83\
+Sa/?\x96 \xaa\xb7m\x02\xa7\xdc\x81\x04\xa9\x04\
+\xd4J\xe3\xeb<.\x8e\x1c\x1a_D\xe2\xdfO>t\
+o\xe14\xa6\xb1\xf7E\x0e}`8\xcd*\xe1\xb4r\
+\x84\xb2\xec\xb4\xc9\xbd\xc9a\xf1D.\xdd\x90\x0e\xc6\xed\
+\xef%\x1d\x1d\xf9\x85\xb1\xe40\xbd\x85\xd3\xcet$\x8a\
+v\xdbn\x16\x95\xac\x88\xaf\xea\x818T\x16W}F\
+\xb5\xd3\x15\xdb-\x14QN\x96\x0b}\xf5\x1a/\xc9r\
+\xdb^\xa2\x05\x17\x8e\x16\xbaE\xa1\x129\x0bP\xfa,\
+K\xae\xf3\xb9\xd0\x8e8\xcd\xc3\xa2\xf0\xecl\xee\xfa3\
+\x02\xae?#\xe0\x82F\xa0\xcfO7\xd9D\xc5\xa00\
+\x8dg\xb7\xea\xca\x0a\x14\x94\x080\x16Y\xc9\x1a\x0f\x82\
+\x15N\x815\xb7{J\xd9\xf7\x92\x8f\x0b}u\xd9`\
+\x02\xa6i\xf4~\xdb\x19\xbf\xb2\xbb`\xc2\xc6\xa2S7\
+\x89\xa8\xadfO\x0c\xcd+q\x05\xc7\xecW\x9d/\x10\
+\xfa\xa4\xbfz\x1d,\xc1c\xa1\xa5\xea\x01Y,z\x93\
+\xcc\xba\xb3\xabs\xd9\x04\x13\xde%\xeb\xe6K\xd9\xc5\x97\
+\x5c\x13&\x94\xa8s\xe5~\xda\xe4{r\xe5\x95;?\
+\xbd\xb8l`(\x98\x1e\x17D\xf4\xa5\xb5gg\xbc\x09\
+\x86\x1f\xe6\xc2\x08\xd5\x85h\xa1\xab\x9b6\xaf\x08\xd5\x17\
+\xf6\xd2\x9e\xef\xe0(\xac\xbb\xb9\xc9\x8e2o\x83\xa5.\
+\xf5\xd9\x0e\xd9:R\x5cu\x10\xd4\xf9\xd9%;c\x0d\
+P\x22\xbcjk\xb5\x08\x1a\xba\xb3\xd3\x0b\xe3\xce\x9a\xb0\
+\xa2\xc5\xf6\x8c]p\xdb\x94~\x11\x5c\xb2}\x82\x9bt\
+\x177a\x8a\x00\xd7\xf9\xb6\xe9+\xe2\xa6\xab\xab\x8b\xab\
+\xb3&\x8ed8\xaf\x95K\xde\x91\x90sqq\xd6$\
+\x9b\x0c\xef\xa2(*\x82\xec\x04\xb0\xf4i\x933e8\
+\xc42\x86\x05\xdf\x9e\xb8\xba:?\xcb\xecz\x05V\xb4\
+\xc9\xbc\x02DY\xda\x04s`|%L\x17;)U\
+]V{E\xec\x04\xae\xbbQ\xa2\x81\xa3`V\x0a\xf5\
+\x11a\x10\x16SB5\xf1\xdd\xb1\xe8\xa1\xa5\x0b\xd2N\
+Ji\xb3\xcc\xaa\x0a\xac\x0e\x87\xd7\xa9\xa0v\xe2\x97B\
+\x8a\x1dX\xd1\x8b\x8b\xad`4;\xd0\x03\x8bZU\x87\
+\x80E\x12${\xe1\x81\xd5\xdda\xf4\xc0\xf0\xb8\x19<\
+<C\x96\x1eX\xadN\x17\x87\xa5\xd7\xc8\xb0\xe1\x88\x1b\
+\x88\x8f\x86\xd9\x01^\xb5s\xc2\xb3q\xeb\xccFo\xee\
+\xd6\xc9d\xf6\x97d\xfby5\xc3wVgql\xc7\
+p\xf3\xb2\xc9*\xc1U\x94\x0e\x09\xdf\x09\xb0\x98\xd6\x87\
+e\xa1\x09\x11\xb5\xd8\xbeK\x92\xaa'\xd8N\x90j\x8d\
+~\xc8!\xcd\xf3\x91#\xb8 \x13G\x8e\x03_\xf6\x14\
+2*9m\x97\x1cu\x7f\xb1\x9d \xf5:\xfdI\xc8\
+\xb7#?\xf8\xc6g\x1c\xf2\xed\x819j2*Gm\
+\x17\xf9u\xf7\xaa\x1d\xf9\xf5:/\x0a\xf9\xd1\x99j\xed\
+\xc8?,\x01_\xc8\xa8\x04\xfc]\xe4\xd7\x9d\x91v\xe4\
+\xd7\xeb\xbc$\xe4\x07So\xe2\x90_\xa4\xdd\xb4g\x83\
+?A\x81(S`\x88T\xaeXAL/w*\x13\
+4\x1bO\xf1F'G\x94\xb4\xc6\xfd>)\x9a\x84\x05\
+\xfd\x7fk\xed\xa9\xad/\xdd2\xdb\x91\xb8#\xc2\xcb\x93\
+\xe2\xc2\x9c_5!\x1d\xf6\x96r;\xac\x0e727\
+\xa9mA.?W\xcdx\xd9\xd2\xb0\x1b\xa9\xb5\x09\x8e\
+\xeb\xf4\xfc\xf4\xea\xd45a\x85coC\xc3.\xa9;\
+w\x97\xae\x09\xab\xe3\x15S\xd3\x01\x8a:a/\x9a\xa0\
+\xc2\xa8\xb7,\xbbD\xae\x8d!N\xedyvyU\x05\
+Vt\xfaI+_\xb1\x03\xf3OT\x94\x1f\xb0+\x8b\
+\x06f,\xa5\xabh\xc3]\x89\xa4x\x16\x97\xa5;\xbb\
+\x00/E&y8&\x8f\x94I\x1e\x8e\xc8\xa3e\x92\
+\x87w=\xa3e\x92\x873\x0c\xa2eR\x84WC\xa2\
+e2\x98\xff\x13/\x93\x22\x8c\xfah\x99\x0c\xbe\xe8\x19\
+'\x93\xe2\xc0PIE\xb9\x87#\xed\x8f\x05\xe5\xe5\xab\
+\xe8#\xdc\xee4E.\xbd\x7f\x97\xb6^);\x0e\x82\
+\x81{\xfe\xb4L\xa2\xf41\x81g\xf51/\x92\x9cR\
+\x01\x9e\xcaPXb\x84\x96\xdc\x0dG\x06\x8flb\x8c\
+\xf3\x9e$Rv,\x91\xc4Id\xc7\x9b\xa3\xd1\x12\xd9\
+\xf1\xf6h\xb4D\xca\xb0e\x8b\x96H\xd5a\xd9b%\
+\xb2cq*V\x22;\xd6\xa6\xa2%2\xb86\x15'\
+\x91\xea\xc0\x98AE\xc5\x0c#\xa9\xaa\x09\xe4Y\xd0 \
+D\xb9x1\x92\x02\xd8\xcfTO\x15\xa8\xd4\x08\xcbb\
+E\x103\xfb\x08\xc2\xe8\xcfNS`\x0e\xb5\x18\xe2\xab\
+y\x80,\x15\xbbP\xe9\x0f\xd49o`\xab#\x85'\
+,\xcf\xad\x92\xd3\x91\xc1\x13)\xcf\xba\xe3\xac\x0a\xae\xc3\
+'\x06\x9d]\xd1\xcb&S\x99\xb0%\x0b\xae\xc2\x9e^\
+\x9c\xd1\xd3\xd3&\xa0\xe84\xd6V\xee4\xc1<\xd6=\
+\x82*\x13wz\x97 F\xaaJ*\xab&\xcc\x00B\
+J\x16\xad\xd5\xe0\xfe\x9eA\xe7\x8as0\xbe\xd7\x82z\
+\x0b\xde8Ua/\x82S\x16\x93?\x02\xb0\xc2<\xcf\
+\x99\x0b*\xad\xcb\x8b\xcb\xab\xcb\xb3&\xac0\xd7s\x19\
+\x1c\xd6\xc5\xe5%\xbb\xd8\x01\x15f{\xaeMxCS\
+_\xb8\x0b\xde\x80\x15|?\x93\xfaS\xf1\x22\xc4\x11`\
+\x85Qo\xb5\x0b\xc2jE}P\xdd\xc4H\x11\x80\x09\
+.M<!@QG\xa7\xed\x08\x90\x02\xd5\xcc\xf2U\
+\x87V\x01\xc2\xd31\xed\x1e\xfe\xd6\xb3\x0a\xd0\xd9\xe9\xd9\
+\xc5\x99n\xa0\xac\xe3\x10C\xee\xb2\xd3\xfa\xf63\x1a\x00\
+\xab\xe3\xe8\x02\x1ad\xd4vP\xd1\xa1q+\x8f\x9a\x03\
+Cc\x13\xf5JR\x93!\x1cP\xc8*[M\xe2i\
+0\x04\xde\xca\xe0\xec>\xcb\xe3\xbf\xbbF\xed8\xc90\
+Z\xa3\xda\x1e5\xaa\xedO\xa3\x06\x93(\x0f\xd0\xa8\xae\
+G\x8d\xda\xf1\x9ae\xb4F\x8d_\xecm\xe5\x86\xf0b\
+\xef\x13\x02\x14y\xf6]C\x80\xf0\x0a\x16\xc7*A\xec\
+\x8e\x00\xe1y\xef\x8a7R\xa8_\x82Fe\x1d\xcb\xbc\
+\xb1\x1a\x95u,\xf4FjT\x16\x5c\xe7\x8d\xd2\xa8\x8c\
+\x1e\xaaQ\xa3\xb6\xe5\x81\xdc\xaez\x1c\x86$\xa0K\x5c\
+]\xa1\x02\x8b\x9b\xd2\xc2\x165\x22s~vE\x87\xb1\
+>\xdeL\x030\x07\xbe\x9af\xa2\xb6H\x1a\x98r\x8a\
+H\xc7y\xdd\x99\xafa\xaa\xac\xd1\x03\xa6\xfax\x8f\x8c\
+\xe3m\xb6!Lu\x1e+\x8f\x9fMv\xae\xfc|\x96\
+\xdf|\x90\xad\xa0\xcc\xf0\x10vc\x89eN\xe3)\xe1\
+\x12bl\xa3\x05\x9e'\x0e(\xa0\xda1\x8b'\xc4\x01\
+\xdb\x08E\x9d?c\x5c\x0ap\xb6\x87\x02\xcfr\x12J\
+\xf9\x22|\xf9\x86c\x90-\x9c\x90Vcc\xbc`\xc7\
+\x0a\xbcuH\x11\x0e:^\x88\x01|\x13\x12\x81\xe3\x15\
+Ax\xb4\x9d\xb6\x03\xc9\x08\x85\x90\x89\xba\xbc\xcc\xb7\x16\
+xy:\xba\x06x\x1d\x10s\xc0\xb2\xda\xdf/d\x84\
+\xbfL\xc8\xe2\x05\xec<\xbd\xdb\xc8Y\xad\x8cMo,\
+\x92<?\xc0\xdd:\xc6\xd3\x1b\x8b\x18\x1e\xb6\x8e%\xc2\
+\x19\xe6\x8b\x80\xff\xa5h4\xd50Q\x9d^b\xc4\xad\
+b\x5c\xf8\xe10\x09H\xc7\xc3\xdf\xb9\xe2\xa0\x0d\xf1j\
+\x22\x09\xe0\xf0\x0a#\xe1\xa4\xe6E\xc99^V\xe3\xb4\
+a\xae,\xc3\xeb\x8b`\xe2\xd8N\x11m\x0c\xf5\xe8\xc3\
+U@pz\xfc\xc1\xed\x92\x0a-\xb2c\xdf)\xa0R\
+\xe3\x82\xa1vZ`k\x8d\xfc\xe7\xa8\x18r\x80\x83\xef\
+\x9a\x99V2\xed}[\xc2k\xbb\xa2\xb3\xbc- \xbd\
+p\xfeI\x16O?5.\x17xZK\xc1\xe5\xd9}\
+BC\x0b\xa8\xc5[\x5c\xe4\xe0\x97\x81Q\xa4R\xa4\xca\
+\x12\xb0\x9cx\xbb\x8a\xaf$\x81oMkQ\xa5\x1d\x1e\
+\xdc])\xf2\xb5\xf22\x85i\x1ei\xb5\xac\xe1n\x89\
+\x04\xd2\xe3\xddHP\x92\x0f\xb4\xa5\xa8\x84\xde \xfcj\
+\xb9\x1dm\xe6\xbf%\xc7\x8c\xdf=\xfc\x98\xfe\xc4G\x80\
+\xa6\xf5\xedd\x91\x96|\xf5\xf7d\xe7E%\x86\xd5a\
+\xb7\x189\xde\xb8\xc5\x08\xbc\x0f\x0bND\x93}>\xdd\
+o\xb7;\xdc\xe3\x19\xa6\xef\x1b\x8c\x1c\xdeF\x883\xbd\
+\x9e\xdc\xce\x17\x8f\xc7\x17\xc9\xbf'\xff\xbc\x1f|H\xd6\
+\xf3\xebv.j\xbd\xca\xaf\xd6\xc9\xe2\xf8\x87k\xff\xd9\
+[~\xec\xb3\xcaO\xfb5}\x02\x8f7\xaa\xcdl\xf7\
+:\xab}.\xc1\x0a\xdf\x9c\xb5s\xdb\xd6\xec;\xdd\xff\
+\xd5zu\x99\xa3\x06tI~uY\xf6\xcb\x82+\xa9\
+A\x85\x83\x12\x05?\x983C\x1b\x17\xf3}\x83\xf4\xa4\
+\x9c\x91\xf8\xcf\xfe\x9a\x15\xa0G\x0bG\x85\x1bC\xac\xbd\
+\xab\xe3\x14Q\x5c\xe0\xd5%\xa5\x8e\xcb\x8alM\xc9\xe1\
+e\x03\xca\xd4\x94\x5c\xb3\xa8\xda\xb0\xd0r\x15\xf8\xcf\xa0\
+\xe5\x0a\xe8\x95{I\xb6\xc9\x03\xe8\x1c\xcd\x8f\xf6\xb1\x02\
+\xbd\x129R\xfc\xbf\x95\xc8\xbbz\xcdq\xd5\xa1\xd7\x0a\
+\xe2Kp+\xd0\x9d2\xd6y\x94\xfb{m\x9c\xc5\xa0\
+\x06\xc9\xa4\x88U\xda\x0a\x83\xa5\x16u%\xaf\x95\x1ap\
+\x91-\x83o\xe8\xcd0\x08\xcb\xd1)\x83Bc\xa4w\
+\xde4\x11\x0a\x023\x81e\x8a\x19\xa7\xc4\xc0\x80\x9bf\
+(xd&e\x01\xe4\xac\xbc\xa81\x92\xf2gF\xd2\
+6)v@|\xe4\x92,\x08\xcd~\x8d\xf0\x96\x1am\
+1\x18\x15N5\xae\x82\x17\xf9\xab\x94\x1d\x1c\xe1\xb9a\
+\xbc\xf9r\xf3\xfe\xff\x01 ;\x82Wk\xab\x00\x00\
+\x00\x00\x08\xc9\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x07\x94IDATX\xc3\
+\xcd\x98[lT\xd7\x15\x86\xbf\xbd\xcf\xdcl<\xe3\xcb\
+\x18\xf0\xd8\x13\xc6\xd8 0\xe1\xe2@0.\x09\x97(\
+\x14*Z\x12D\x12\x91Ji\xda\xb4M\xa5\xb4\x12/\
+U\xd4\x87>\xf4\xa5/\x09\xaa\x22\xb5\x12D\xa5j\x89\
+\xa1\xa4M\x80P\xb5\x10\xc5\xc4\xdc\x9b@\x8cKmZ\
+\x9b2C\x901\xe0\x16\xdb3x\xb0\xe7~\xce\xee\xc3\
+\x999\xb1\xa1x\xc6@D\x97d\xe9xk\xefu\xbe\
+\xf9\xf7Zk\xaf}\x84RJQ\x80\xa53\x19z\xfb\
+\xfa\xe9\xea\x09\xd1\x13\xbcL_\xff\x0d\xa2\xb7F\xd0u\
+\x03\x97\xcb\xc1To9\xb3\x02~\xe6\xcf\x9dE\xc3\xec\
+Z\xbc\xe5\xa5\x85\xb8\xcdk\x22\x1f`,\x9e\xe0\xcc\xdf\
+\xfe\xc1G\xc7O\xd3\xd9\x1dd(2L&\x93A)\
+@d'e=H)(.r1\xa3\xa6\x8a\x15\xcb\
+\x1aY\xbb\xb2\x99\xba@\xcd\x97\x03\xa8\x80\x8e\xce\x1eZ\
+\xf6\x1e\xa4\xfd\xef\xdd\xc4\x13\x09\xa4\x94\x08!\xc6\xcfS\
+j\xdc\x98\x02\x94a\x00\xe0\x9b>\x95\x8d\xebV\xf1\xfc\
+7\x9e\xa6\xa2\xcc\xf3\xe0\x00\x13\xc9$\xef\x1ehe\xf7\
+\xbeC\x0cE\xa2h\x9a\xb4`r@6\x9b\x0d\xbb\xdd\
+\x8e\x10\x02]\xd7I\xa7\xd3\xe8\xba\x9eU\xd2\x9co(\
+\x85\x14\x82%\x0b\xe6\xb2\xe5\xfb/2\x7fN\xfd\xfd\x03\
+\x8e\x8c\xc6\xf8\xd5\xef\xde\xe3\x83CG\xc9\xe8:B\x88\
+,\x18\x94\x96z\xa8\xaf\x9f\xc9\xecY\xf5T\xf9\xaa(\
+))A\x93\x92d2E8\x1c\xa6\xb7\xf7\x0a\x17\x83\
+!\xae]\xbbN:\x9d\xb6@u\xdd \xe0\xaf\xe2'\
+?\xfa6\xcb\x1f_x\xef\x80\x89D\x92\xb7~\xf3.\
+{\xff\xd2f)e\x18\x06n\xb7\x9b\xe6eKY\xfe\
+\xc4W\xa8\xa9\xf6\xe1p8\x10X\xa1g:\xca\xce\x8d\
+Fo\xd1\xdd\xdd\xc3\xd1c'\x08\x85.\x8d\xf3S=\
+}*?\xfb\xf1\xab45>:y@\xa5`\xe7{\
+\x7ff\xdb;{\xd1\xc7(W[\x1b\xe0\xb9M\xcf\xd2\
+\xd00\x17M\xd30\xb2\xf1uW\x87B \x84`x\
+8\xca\xe1\x8f\xdbhk;F\x22\x910C\xc10\x98\
+S7\x837~\xba\x85\xdaG|\x05\x01\xca\xdc\xc3\xd9\
+\xaenv\xed;4\x0en^\xc3\x5c~\xf0\xeawY\
+\xb4p>\x0e\x9b4\x7fE\x1eSJa\x18\x06\x1e\x8f\
+\x9b\x8d\xcf>\xc3\x0b/l\xa2\xb8\xb8\x18\xa5\x14\x9a\x94\
+\xfc\xeb\xf3+\xec\xd8\xf3\x01\xc9T\xaap\xc0\xd1x\x82\
+\x96\xf7\x0f\x12\x8eD\xad\xed\xa8\xad\x0d\xf0\xd2K\xdfd\
+\xe6#\xd5\xb8\x1d\x82R\x97\x8d\xd2\x22\x0d\x87&\x0ar\
+\xac\x94BJ\xc1\xeaU+xf\xc3z\xecv\xbb\xf9\
+B!9r\xea,'N\x9f+\x1c\xf0t\xc7y\xda\
+;\xbb\xd14\x89R\x0a\xb7\xdb\xcds\x9b6\xe2\xaf\xf1\
+\xe1\xb2\x81&\x05B\x80M\x0a\x8a\x1d\x1a\xa20Fk\
+\xcbW\xaf^\xc9\xd2\xa5K0\x0c\x03! \x9eH\xb2\
+\xef\xe0\x11Fc\xf1\xfc\x80\xe9t\x86\x8f\x8e}J\x22\
+\x91\xb4\x06\x9b\x9b\x9bhh\x98\x03\xca@\xdeF\xa3I\
+\xee\x18\xcb\xa7\xa4\xd3\xe9d\xdd\xda5x\xbd^SY\
+Mr\xfeB\x88\xce\xee`~\xc0\xcb}\xd7\xe9\xea\x09\
+!\xa5\xa9^\xa9\xc7\xc3\x13\xcb\x9b\xd14\x0d\xddP\xe8\
+\xb7\xc5]\xc60\xeb\xdbd\xcc0\x0c\xfc\xfe\x1a\x16/\
+^df50\x1aK\xf0\xc9\xd9\xae\xfc\x80\xe7{B\
+\x84o\x0e[\x89Q__Guu5\x86a`(\
+\x18M\xea\xa4u\x85\xa1\x14)]1\x9a\xd2\x0b\xc9\x95\
+;_$%\x8d\x8d\x8b(*r\x99\x03\x02\xce_\x08\
+12\x1a\x9bx]O\xf02\x99\x8cy\x02\x08!\x98\
+5\xbb\x1e\x87\xc3nMH\xe9\x8a\xe1D\x86\x9bq\x9d\
+h\x22CFW\x08\x01N\x9b\xc4i\x93\x05\xc7\xa3R\
+\x8a\x9aj\x1f^\xaf\x17\xc30C\xa7\xff?\x83\x0c\x86\
+\x87'\x06\xec\xeb\xbfa)b\xb3\xd9\xf0UU!\xee\
+p\x0e\xba\xa1\xacy\x0eM\xe2qix\x5c\x1aNM\
+R\x88)\xa5\x982e\x0a\x95\x95^K\x8c\xd1X\x9c\
+\xa1H\x1e\xc0\xe8\xad\x11\x10&\x84\xddn\xa7\xa4d\x0a\
+\xf7\xb0\x83\x05\x99\xa6i\xb8\xddnr\x87W&\xa3\xe7\
+\xddb\x9bn\x9d\x0c\xe6\x91\x94;?'\xb2\x94n\x10\
+M|\xf1<)\xc81\xfe\x15\x0a=\xcf\xc9d+r\
+:A\x81\x90fWRH\x85W\x0a\x92\x99\xc9\x81\x99\
+\xeb\xd4\xb8r&\xa5\xc4\xe5tL\xb8FVz\xcb\xac\
+\x7f\xd2\xe94\xe1p\xe4\x8e\x9e\xefAY*\x95\x22\x12\
+\x89X\x15\xc3\xe9pP\xe6)\x99\x18\xb0>\xe0GJ\
+\x13H\xd7uz{\xaf\xe4m\x08\xee\xc5\xa4\x94\x84#\
+\x11n\x0c\x0cZ\x80\x15e\x1e*+\xca'^\xb7\xa0\
+a\x16\xc5E.+1.^\x0c\x12\x8d\xde\xfaRT\
+\x0c^\x0c\x11\x8dF-\xc0\xba\x195T\x94\xb9'\x06\
+\x9c7{&3j\xaaP\x86\x81\x94\x92k\xd7\xae\xd3\
+\xdd\xdd\xf3@\x01\x85\x10\xc4b1>k\xef\xb0\xban\
+M\xd3hj\x9c\x87\xcdf\x9b\x18\xd0[^\xca\x8a\xa6\
+Fk \x9dNs\xe4\xe8q\x86\x87\x87\x1f\x18\xa4\x10\
+\x82\x8e\x8es\x04\x83\xe6\x91j(E\xd54/\xcdK\
+\x16\xe4]+\x01\xd6\xaej\xc67}\xaay\x87\x90\x92\
+K\x97>\xa7\xf5p[\xb67\xbc?8)%}}\
+W\xf9\xf0\xc3V2\x99\x8c9\xa8\x14k\x9el\x22\xe0\
+\xcf\xdf\xb4J\x80\xba@\x0d\x1b\xbf\xb6\xca\xeaR\x94R\
+\x1ci;\xc6\xf1\x13\xa7P\x8a{VRJ\xc9\xc0\xc0\
+ \x7f\xf8\xe3\xfb\x5c\xef\xef\xb7z\xcd\x80\xdf\xc7\xa6\xf5\
+O\x15\xe6#\xf7\xf0\xfc\xd7\x9f\xe6\xf1\x85\x0d\xe8\xba\x81\
+\x10\x82D2\xc9\xfe\xfd\x07hm\xfd\x98T*UP\
+\x01\xcfY\xae\xe0\xf7\xf5]e\xe7\xce]tw_\xb0\
+\xd6\xbb\x9cN^\xd9\xbc\xa1 \xf5\xc6\x01V\x94y\xd8\
+\xf2\xbd\x17\x09\xf8}\xd9\xc6R\x10\x8b\xc5\xd9\xb7\xffO\
+\xbc\xd3\xf2{z{\xafX\xaa\xdcMQ)%RJ\
+\xe2\xf18'O\xfe\x95m\xdbw\xf0\xcf1\x09\xa7\x14\
+h\x9a\xc4f\xd3\x0a\xff\xb1\xb7_;?=\xdb\xc5\xcf\
+\x7f\xf9[\xae\xfd{\xc0:\x96\x0c\xc3\xc0[Q\xc1c\
+\x8b\x1by\xacq!55\xd5\x14\x17\x17[\x19\xa8\x94\
+\x22\x99J\x11\x09G\x08\x06C|\xd6\xdeA(\x18\x22\
+\x9d\xc9\x8c\x81Sc\xea_)\xaf\xbf\xf6-\xd6\xadn\
+\x9e< @{g7\xbfx{7\x17.\xf5\x22\xb3\
+\xb7\xb4\xdc\xa5\xdd\xe5r\xe1\xf5VPYY\x89\xc7]\
+\x82\xd44\x12\x89\x04\x91\xc8M\x06\x06\x06\x89F\xa3\xe8\
+\xba\xfe\xc5\xe5\xdd0p9\x9dh\x9a$\x16OXq\
+\xe8-/\xe5\xf5\xd7^\xce\x0by\xd7O\x1f\xbdW\xfb\
+\xd9\xb1\xe7\x00m\xa7\xda\x89\xc7\x93\xd9\xad\xc5ziN\
+\x95\xb1q\x97\xfb\x83l\xd7\xad\x14\x01\xbf\x8fW6o\
+\xc0n\xb7\xf1\xd6\xaf\xf70\x18\xbei\x96\x9a\x02!'\
+\xfcx\x94L\xa59y\xe6\x1c\xfb\x0e\x1e\xa1\xab'\xc8\
+h,\x01\x22{'\x11b\x5c\xdf\x98SX)\x85\xa6\
+iTM\xf3\xb2\xe6\xc9&6\xad\x7f\xcaJ\x88\xc3'\
+\xce\xf0\xc6\xb6\x16\x86&\x01\x99\xf7\xeb\x16\x98\xf7\x87\xae\
+\x9e \x9f\x9c\xed\xe4|\xcf%\xfao\x0c22\x1a'\
+\xa3\x9b_\xb9\xa4\x14\xb8\x1c\x0e\xca\xcb<\xd4\x05jh\
+j|\x94\xe6\xc5\x0b\x08\xf8\xab\xee\xf0\xd5z\xfc\x0co\
+n\xff\x1f\x90?|\x99u\xab\x9a\xef\x0dp\xac\x8d\xc4\
+\xe2\x0c\x85o2\x14\x19fd4\x86n\x18fWR\
+\xeafjE\x19\xe5\xa5\x9e\xbcYz\xf8\xc4\x19\xde\xdc\
+\xd6R\xd0vO\x1a\xf0AY\xa1\x90\xb6\xfbx\xc7}\
+\xd9WW.\x03\x18\x079\x14\x19f\xeb\xf6\x16\x00\x0b\
+\xf2\xa1\x01\xe6\x83\x14\xc2\xec\x11\x1e*\xe0X\xc8\xb1\xd9\
+=\x14\x19f\xeb\xdb\xbb\xcd\xd2\xf6\xb0b\xf0v\xbb=\
+&u\xc3\xa0y\xf1\xfc\x87\xaf`\xcerJn\xdd\xbe\
+\x8b\x1b\x83\x114M\xe2\xf7M\xff\xffQ0gm\xa7\
+\xda\xd9{\xb0\x0d\x7f\xd54\xbe\xb3y\x03\xff\x05\xc8C\
+\xdb\xc2\xa6D\x97\x9f\x00\x00\x00%tEXtda\
+te:create\x002023-0\
+3-08T17:27:26+00\
+:00\x9e\x98\xa5\x85\x00\x00\x00%tEXtd\
+ate:modify\x002023-\
+03-08T17:27:26+0\
+0:00\xef\xc5\x1d9\x00\x00\x00(tEXt\
+date:timestamp\x002\
+023-03-09T06:18:\
+41+00:00=\x00\x9f\x01\x00\x00\x00\x00\
+IEND\xaeB`\x82\
+\x00\x00\x08\x13\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x06\xdeIDATX\xc3\
+\xbd\x99[\x88\x5cI\x1d\x87\xbf\xaa:\xd3\xb7\xe9\x99\xe9\
+\xd9\xb9dHv\xc8\xc4\xc9\x5c7\x9b\xc4,AE\x05\
+/\xe8\x8a\x88\x0f\xee\x83\xe2\x93\xc8\x8a\x0f>\x88\xf8 \
+>\x89O>\xac\x18\x17E\x05aA\x03Y\x08^`\
+\x1f\xd4\x87 \x0b\xca**&lb2n4\x99\xec\
+e2\xd7\xccL\xf7\xe9>}\xce\xa9S\xe5C_\xe6\
+\xf4\xbd;\xa8\x05\xcd9]}N\xd5W\xbf\xff\xa5.\
+-|\xdf\xb7\xf9|\x9e(\x8a\xf8\x7f\x15\xc7q\xc8\xe5\
+r(\xa5z?\x9b\xcf\xe7\xb9}\xfb6\xa9T\x0a)\
+\xe5\xff\x1c.\x8a\x22\xc20\xe4\xc2\x85\x0bd2\x99\xde\
+\x80Q\x14\x91J\xa5X]]\xc5\x18\x0b\x02D\xedW\
+Q\xb9\xeb\xf8\xbdS]\x87\x22\x84 \xd4\x9a[7o\
+b\x8c\xe9Om\x00)%\xc6Z\x820\xa8t$\x04\
+\xa2\xd6\xb1\x10\x88\xea\x95\xea}\xfc\x8a\xb5\x08\xc0\xc6`\
+;\x01\xdbj_\x22\xd6~_\x80\xf1\x11\xb6\x03\xb4\xd6\
+R.\x97\xb1\xc6\x1c\xc15\x83\xf6\xa1\xa2\x10\x02c\x0c\
+\xa5R\x09\xd7u+\xd0\xd6\xb6}VJI2\x99<\
+\x02\xac\xab\xd4\x06\xd4/\x97\xf9\xc7\xad[\x08!\x1a\xfd\
+4\x0e\xdbJ\xd3Z\x05\x18k\xd9\xd9\xd9amm\x8d\
+D\x22\xd1\xf6Uk-\xc6\x18VVVb\x0a6A\
+\xc5\xef\x8d\xb5\x08!8\x7f\xfe<J)lL-)\
+$\x08\x81\xb5m|\xaa\xc3\x00l\xb5=:\x98\xd9Z\
+\xcb\x9d\xdb\xb7\xd1Z7*X\x87k\x86\x15\x02\xa9\x14\
+J)t5\x1dI!16\xe2\x8d\xbd5\x0e\xcb\x87\
+\x5c|\xf2\xbd8\xd29\x0a\xb2x\xe7M. \x9a\xb2\
+E3f\xad/\xa0I\xc1\x0ep\xa2I\x09)$n\
+P\xe0w\xff|\x85\xfb\x87\xf7\xc9{\x05V\xa7\xcf\x90\
+K\xe7\x1a\x82\xa5!\xa0\xda\x80vr\x85xi\xf1\xc1\
+\xe6\x88m\x84\x13(\xa9x\xb0\x7f\x8f\x97o\x5cf\xa3\
+\xf8&\xa7&\xe7(\x17\xca\x15\xe5\x84\x04lK\xa4\x0f\
+\x0a\x18\x8f\xf0\xc6(\x8e\xc3\xc5\x01\x85\xa8\x98\x14\xc3k\
+\xeb\x7f\xe0\xea\xebW\xc8\x8d$\xf8\xf2\x87>\xcd\x83\xdd\
+=\xd6\xb77\x01Q\xedKt\x05\x14\xad4\xfd)X\
+O-\xcdp\x80\x92\x8aBX\xe0g\x7f}\x89\xd7\xde\
+|\x95g\xe6\xde\xc5\x07\x17\xcf0\x92R\x14\xfd\x22\x91\
+\xb1T^mc\xda\x01\x01\xeb~Z\xado\x1f$q\
+\xe5\xa4\xe4\xde\xc1=~\xb9~\x15\x7f\xe8\x11\xcf\x9eY\
+\xe1\xd4\xd4\x14\xe5`\x1f?\xccS*\xbb\x95\x19\x081\
+8`\x97(oo\xe2\x18\x9c\x94\x920\x0a\xb9v\xe7\
+\x1aWo\x5caz\xdc\xf2\xd1S\xf3dSP\xf0\xb6\
+\xb1\xb6\xe2s\xe5\xa0\x88\xd6\xa61\x8fv\x00l\x9e=\
+\xfa\x99M\x1aM\x5c\xcdOJ*6\xf3[\x5c\xfe\xf3\
+e\xfe\xfe\xce\xef9wj\x8c\xf9\xe91\x84<\xa0\x18\
+\x00X*\x83T\x84\xda\x10\xfa\xba\x92\x1b\x07\x04\xecV\
+:\x9aX\x0a\xc9\xcd\xb7o\xf1\xf3?\xbd\xc4~p\x83\
+\xf7,d\x19\x1f\x09\xf0\xcd\x0e\x0aA%\x85I\x8c\xb1\
+\x18#(\x94=\x02?}\xd4h-\x09\xc7\x81\x1fC\
+\xb9\xb6&\xae\xf9\xe0\xda\xc6\x1b\xec\x1c\xbe\xc5\xd4\xd4\x0e\
+n\xf86\xa1\xabH'\x15\x89!\x89\x90\x16c4\xa1\
+\xd6\x94\xfc\x12[yA\xe8_\xac\x0eRT\xd3M+\
+\xd8 P]Ml\xb1|\xea\xdd\x9f$\x8a\x22.\xff\
+\xf1GLN\xaf\xb1<\xef\x13I\x88\x94`\xc8\x01\x05\
+XeIIH\x0d\x0d\xa1\x83\x08\xec``\xfd\x02\xd7\
+\xe7\x9cx\x0e\xcc$\xd3|\xfe\x03\x9f\xe5\xdb\xcf]B\
+\x1f|\x8c\xbf\xfcm\x04\xab\x15\xe3\xa3\x82\xdc\xa8d|\
+T26\xaa\x18\xc9\x0aR\x09\x8b\x8e\xf9`7\xb8\xf8\
+\xcc\xd4\xab\xd4\x9eiYB\xc7_}\xdf\xd2E\xbe\xff\
+\xc5K\xacN<\xcf\xaf^\x99\xe0\xdf\xff\x92\x8c\x0d\xc3\
+xN0:,\xc8\xa4\x04\x09\xc7\x10\x85\x15\x05k/\
+\xb7\x03\xed\xa6d\xf3\xa7\xad\x82\xedFh\xac\xe1\xf8\xc4\
+\x0c\xdf|\xeek<{\xfay~\xfb\x9bE~\xf1k\
+E\xe4C:\x0dJ\x09\x94\xb0DaT\xe5\xeb\x0d\xd6\
+\x0d\xa6\xab\x89\xbb-}\x86\x9c!>q\xf6\xc3\x5c\xfa\
+\xd2\x0fx\xf4\xd6\xc7\xf9\xcew\x87\xb8s\xd3\x10i\x83\
+\x8d,&4T\x12c\xab\x89\xfaQ\xb1\xdbs->\
+\xd8)5Xkyf\xf1\x1c?\xfe\xea%\xde?\xff\
+\x15~\xf2\xd3'x\xf5\x9a\xc5\x04\x06\xa3\x8f\x14\xec\x04\
+\xd7\xcb\xac\x9d\xfc\xb71\xcd\xf4(\xc6\x18\xa6r\x93|\
+\xeb\x0b\xdf\xe0\xdc\xe93\xbcp\xe5\x05\x90\xf7\x88BS\
+\x9f\x9ez\x05\xc9\xa0\xc5i\xd7@\xbbNj\xd7\x8a\xc9\
+\x1d>\xf7\x91\xcf\xb0zr\x81\xef\xbd\xfc\x22\xf9b\x99\
+\xe1T\x86\xa3\xddS\x7f*v3s[\xc0n\x0f\xc6\
+*+jZ\xc3\xd9\xd3O\xf3\xc3\xaf\xbfH9(\xf3\
+\xc4Hn`\x15\xad\xb5-\x9b\xa6\xda&\x8d\xc711\
+4\xa6!k-\xd9\xcc0\xd9\xccp_\x03\xacg\x07\
+c\xeap\xb5\xfdqm\x9f\x22\x04\x18{\xb4\xa2q\x9a\
+_\x1e$\x89\xc6\x1b\xee\xa6z\xdc=\x8c1\x18c\x88\
+\xa2\xa8\xfe\xa9\x01\xd7w\x8dR\x11\x99&\xc0n\x10\x0d\
+\xdb\xd1>}\xa7]\x160\xc6\xa0\xb5FkM\x10\x04\
+\xf8\xbe\x8f\xef\x07h\x1db\x8cA\x08I\x221\x84\x91\
+\x0eA\x10\xe08\xce\xe0&\xeeG\xf1f\xd08\x5c\x10\
+\x04x\x9e\x87\xeb\xba\xb8n\x11\xcf\xf3\xf0}\x1f\x8b%\
+\x93\xce \x1c\x07\xd7-\xf2\xe4\xf1\x19r\xb9\xdc\xe3\x03\
+v\x83j\xae\xab\x994\x08\x02\x5c\xb7\xc8\xfe\xfe>{\
+{{\x14\x0a\x05\x820$\x9dN359\x81\xb1\x86\
+G{{\xcc\x9f\x9acnn\x0e\xa5\xd4\x7f\x0f\xb0\x97\
+i\xc30\xc4\xf3<\x0e\x0f\x0f\xd8\xdc\xda$\xd2\x11\xc7\
+\x8e\x1dc|<G6\x9begw\x87w66X\
+\x98?\xcd\xc9\x93'\xeb'\x18\x83Gq\x9f\xc96\xae\
+^\x0d\xd0u\x8b\x1c\x1c\x1crpp\xc8\xd3g\x9eb\
+a\xe14\x9e\xe7q\x7f\xfd>\x1b\x1b\x1b,-,2\
+;;\xdbp\xbc\xd2\xba'\xe9\xb1\x1a\x1e\x04\xb6\x96J\
+\xb4\xd6\xf8\xbeO\xb1X$_(\xa0\xb5ftt\x94\
+R\xc9c\xbd\x06\xb7\xb8\xc4\xec\xeclK\x9b\x0eT\x0e\
+\x15\xcb\xe5r\xdb\x13O!\x04\x9e\xe7\x11\x86\xe1c\x81\
+\xd6L\x5c\x0b\x8eR\xa9\xc4H6K\x22\x99`\xfd\xc1\
+}\x1e>|\xc8\xf2\xf2\x0a'N\x9ch\xdb\x8e\x93H\
+$PJq\xf7\xee\xdd\x8e\x8e^,\x16\xd9\xde\xde\xee\
+xT\xd6KAc\x0c\xa1\xd6\x84a\x88\x89\x22ff\
+\x8e\xb1\xf9\xf0![[\xdb\xac\xac\xacr\xfc\xf8\xf1\x8e\
+m8ccc\x9c={\xb6c\xe7B\x08\x5c\xd7m\
+I\xc8\xcd \xbdT\xb4\xc6\x12\x19\xc3\xe8\xd8\x18\x16\xcb\
+\xee\xee.+\xcb+\xcc\xcc\xcct\x1d\xa4#\xa5\xecx\
+NW+\xc9d\x12\xc7q\xba.\x22\xba%v!$\
+R\x0a\x92\x99aT\x22\x89\xe7y,--3==\
+\xdd\xd3\x0a}G\xb11\x06\xdf\xf7\xeb\x8a4C5\xab\
+\x0e\x95\xc3J\xad#|\xad\x89T\x02\xab\x86\x10\x81\xcf\
+\xd2\xd2\x12\x13\x13\x93}\xf5\xdb\x17\xa0\x10\x02\xad5\xd7\
+\xaf_\x1f\xe8\x9f\x80\x8a\x0fBd+\xe6M%\x93<\
+\xb5\xb2\xdc7\x1c\xc0\x7f\x00\xb3\xfd\x1fpe\x8eW\xd4\
+\x00\x00\x00%tEXtdate:cre\
+ate\x002023-03-08T1\
+7:27:26+00:00\x9e\x98\xa5\
+\x85\x00\x00\x00%tEXtdate:mo\
+dify\x002023-03-08T\
+17:27:26+00:00\xef\xc5\
+\x1d9\x00\x00\x00(tEXtdate:t\
+imestamp\x002023-03\
+-09T06:18:41+00:\
+00=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x08\x01\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x06\xccIDATX\xc3\
+\xcd\xd8\xdbO\x1b\xd9\x1d\xc0\xf1\xef93\xe31\x17\x13\
+c\x1bc|\xc1\x10\x08\xe4\x02A u\xd3\xa4/]\
+u\xb7/\x9b\xedk\xab\xb6\x7f]U\xf5\xa9j\x1f\xda\
+\x97UUU\xda$\x0d!\x81\x84\x84\x90&\xdc\xcd\xc5\
+\x06\xdb\xc4\xd8\xd8xf<s\xfa`\xd8n\xa4\x96\xcb\
+\x06J~\x925\x925:\xe7s\xee\xbf3B)\xa5\
+\xf8\x84C?\x8fB\xbe\xdfF!\xc4\xe5\x03\x95R\xb8\
+n\x03\xc7q\xb0m\x07\xdb\xb1\xf1\x5c\x17\x84\xc0\xd0u\
+\x0c\xc3\x87i\x9a\xf8|\xbe\xff?\xd0ql\xaa\xd5*\
+\x85\xe2.\xab\x99M\xb2\xb9<\xfb\xfb\xfbx\xae\x83\xd4\
+t\xdaZ[\xe8\xea\x0a\x91\xe8\xe9&\x12\x0e\x13\x0eG\
+0M\xf3\x07\x03\xc5i\xe7\xa0R\x8aju\x9f|\xbe\
+\xc0\xec\xeb\x05\xd66\x0b\x84\xc3a\xd2\xbd\x09:\x83W\
+0t\x1d\xcb\xb6\xd8\xc9\x17Y^Y\xa3\xf4\xbeHo\
+<\xcc\xb5\x814\xfd}\xfd\x84B\xa1\x8b\x03*\xa5(\
+\x95J\xac\xado05\xbbH\xe0J\x98\xf1\xb1[\x84\
+\x82\x1dH\xf9\xe1\x9c\x13B\xd0h\xb8lfw\x98\x9c\
+\x9a\xc6\xae\xedr\xebZ\x92\xd1\xd1Qb\xdd\xb1\x8b\x01\
+\x96\xf6J,\xaf\xac\xf1l.\xc3\xd5\xc1!\x86\x06\xfa\
+\xd04\x81\x10 \x85\x00\x01B\x00\x0a\x14\xa0<\x85B\
+P\xb7\x1c\x9e\xcd\xcc\x92Y\x9e\xe7z\x7f7\xf7\xee\xde\
+%\x14\x0a\x9f\x09(Oz\xa1^\xaf\xb3\xb9\xb1\xc9\xcb\
+\xb7\x9b\xa4\xaf\x0e\xd3\xd7\x97\xc6U\x0aO\x81\x12M\x99\
+8|\x22\x9bO%\x04\xaeRHMc||\x8cd\
+\xff-\xde\xadd\x99\x9d\x9d\xc5q\x9c\xf3\x05\xe6\xf3;\
+,\xacf\x11\xfe0=\x898\xae\xa7\x9a0)\x90R\
+ 5\x81\xd4\x05\x9a&1\x0c\x0d\xf0\x10\x92\xe6\xd0\x0b\
+\x81B2|\xe3\x06fG\x0f[\xdbyr\xb9\xec\xf9\
+\x01-\xcb\x22\xb7\xbdM&o\x11\xedIp4\x96B\
+J\xa4&\xd04\x81\xa6\x0b|\x86\x86\x94\x1e\xaf\xe6^\
+\xf3\xbb\xdf\xff\x81\xbaUC\xd3\x9b\xefHM\xe07}\
+$\xd3\xd7\xa85\x0c\xf6\xca{\xb8\xae{j\xe0\xb1\xdb\
+L\xa5Ra\xa7P\xa2\xea\xb5!}-(!\x10\x9a\
+D\xd7\x04\xba.0\x0c\x89& \x93Y\xe7\xef\xffx\
+\xc4\xd4\xf4+4\xe1\xf1\xab_\x82a4\xdb\xae\x89\xe6\
+/\xd2\x15au\xc5\x8fm7\xa8\xd5\xaa\x04\x02\x1d\xe7\
+\x01,\xb3[>@\x9a!\xf6-\x85\xd5\x80V\x09\xba\
+\xd1\xec\xb5\xddb\x81o\x1fL2\xfd\xe2\x0d\xfe\xb6 \
+\xa9\xfe!\xaa\xa5m\xb4\xc3\x06\x80@\x13\x12\xa5@h\
+\x06\x8e\xf0\xb3\x9a\xd9 \xd6\x1d=\x1f`\xb5V\xe5\xc0\
+r\xf1\xb5\xf8\x11Rby\xd0@P\xde\xdf\xe7\xdb\x99\
+\xe7L>yN\x03\x83d\xffu\xae\x04\x83\x14\xf3y\
+\xaa{\xdb\xcd\xb9#AH\x81\x8b\xe2\xa0\xa1p\x94@\
+\x1a-,\xaf\xcesch\x80x<\xf1\xf1@\xc7v\
+h\xb8\x1e\xba\xae\xa3k\x12C\x17\x18\xba \xb3\xbe\xc5\
+\xcb\xf9%:\xbb{\xe9\x0cE0\x0c\x1d\x81@\xf1\xe1\
+\x8e%$x\x02l\x0f\x1a\x1e\x08!\xd9\xd9\xd9\xa1n\
+\xd5O\x85;\x11(\xa5\xc0sm@!\x04hR\xa0\
+ip{t\x98k\x03iV2yV3yj\x07\
+\xf5\xe6\xfa\x01P\xcd\xa7\xe0p\xd7\xd1\x04\xeap/W\
+^\x03\xc7\xb60}\xa7?\xfa\x8e\x05\x06\x02\x01Z}\
+\x82J\xc3B)\x0e\x17\xb1@\x0aA0\xd0\xca\xc4\xad\
+4}\x89\x08\xef\x96\xb3ll\x15QJ\x1d\xc2\x04R\
+\x80\xa7\x04\x9aPhB\x80RX\xb5\x12\x9a\xf0\xe8\xec\
+\xec<\x1f`(\x14&\x19\x0b\xb3Q\xb5h3\xc0\x90\
+\xcd\x8a8\xec))\xa0\xbb\xb3\x8d\xf0\xf8\x00\xb9T\x17\
+\x93\xd2\xa1\x5c\xd8@\xa0\x0e{\xf1h\xc8\x15\x07u\x9b\
+\xbd\xfc\x1a\x91p\xe7\x99N\x93c\x81\xed\xed\x01R\xc9\
+\x04F\xc9\x01\xcfFy&NC~\xd7\x9bGP\x9f\
+\x14\xa4\xa3W\x88|y\x87\x1b\x83=\xf8M?G6\
+\xe5\x81m+\x8a;\xdb\xec\xe5\x16\xf8\xe9W??S\
+vs\xecF\xad\xeb:\xe1p\x04\x1f5\xb2[\xebl\
+\x15\xeaTj\x1evC}\xb0 \x84j\x16\x14\xf0\x19\
+\x8c\x0d\x0f\xd0\xe2\xf75\xcfd\xc0v\x14\xc5\xf75V\
+\xdfL\x12\xea\xf0\xf3\xd9\x8f\xee\x9c\x1aw\x22\x10 \x12\
+\x89`h\x82\x9d\x95\x19V\x96\x16\xc9\xbf\xb7\xd9\xdbw\
+q\x5c\xf0\x0e\x99G\xd4\xef:\xf5\xf0\x0f\xcbVl\xef\
+Z\xbc{=C~\xed\x05\xf7\xef\x7f}\xe6\xb4\xeb\xc4\
+\x84UJ\xc9\xc8\xc8m\xde-,03\xfd\x17\x5c\xc7\
+Bp\x1b![\x09\x074\xfc\xdf+A\x00\x1e\xe0\x02\
+\xfb\x96b=Wcvz\x8a\xf9\xc9\xbf\xf2\xb3\xcf?\
+\xe7\xceg?>\x13\xae9:\xa7LX\xcb\xe52\x7f\
+\xfa\xf3\x1fy2=KW\xdf\x047'\xeeq\xb5/\
+A4\xe4\xa7\xcd/1\xe4\x7f\x86\xf4}\xb9\xceZf\
+\x8b\xb9g\x8fXz=\xc9\x97_|\xc1\xd7\xf7\x7f\x81\
+\xae\x9f\xfd\x86qj 4\x93\x87\x87\x0f\x1f\xf0\xcd\xdf\
+\xbea\xb7\x5c\xa7+9D\x22=H$\xdaM\xab\xdf\
+\xc4\xb1-J\xbbyr\xebK\xecl.\xd1bj\xa4\
+\xd3}\xdc\xbb\xfb\x13FGF\x11G\xa9\xd9E\x01\x8f\
+\xa2P(\xf0\xfc\xc5\x0c/_\xce\xb2\xbd\xbd\xc3\x81e\
+\x01\xcdE\xd5\xd6\xdaB2\x91`t\xe46J\x81\xe9\
+7\xc9\xe5\xb2\xd8\xb6\xcd\xc8\xad\x11\x86\x87\xaf_<\xf0\
+(\x5c\xd7\xa5R\xa9P\xab\xd5p]\x17\xc3\xd0ik\
+k\xa7\xbd\xbd\x1d!\x04+\xab+hR\xa3\xb8[\xe4\
+\xd9\xb3)\x84\x90\xfc\xe6\xd7\xbf\xa5\xa5\xa5\xe5\xd4u\x9c\
+\xb8\x8a\x8f\x0bM\xd3\x08\x06\x83\xc4\xe3qR\xa9\x14\xb1\
+X\x0f\x81@\xe0\xf0^\xd2\xe0\xed\xdb\xb7\xd4j5R\
+\xc9$\xe9t\x1f\xa5\xd2{\x9e?\x9f9S\x1d\x1f\x05\
+<.t]'\x95L177\x87\x94\x1a\xa9T/\
+==q\x9eN?\xa5X,^>\x10`xx\x18\
+\xd34y\xb7\xb0@w\xb4\x9bT*\x05J\xf1x\xf2\
+\x9f\x9cvf](P\xd7u\xc6\xc7\xc7YY^\xa6\
+vP#\x91H\x92L\xa6x\xf3\xaf7\xac\xae\xad^\
+>\x10 \x99L\x12\x8b\xc5\x98\x9f\x9f'x%H2\
+\x99$\xd0\x1e\xe0\xe1\xa3\x07\xa7\xba\xe1]8\x10`|\
+|\x9cb\xb1H\xa1P\xa0\xa7\xa7\xb9\xa0\xb2\xd9,s\
+\xaf_}\x1a\xc0`0\xc8\xf0\xd00\xf3o\xe61M\
+\x93x<A\xb4+\xca\xe3\xc7\x8f\xa9T\xca\x97\x0f\x04\
+\xb8y\xf3&\xcaSd2\x19\xa2]]\xa4R)\xea\
+\xf5:SO\xa7>\x0d\xa0\xdf\xefgll\x8c\xa5\xc5\
+%\x5c\xcf#\x16\x8b\x91H$\x98\x9d}A6\xbbu\
+\xf9@\x80\xfe\xfe~\x82\xc1 \x8b\x8b\x8b\x04\x83\x9d\xc4\
+\xe3qL\xd3\xcf\xe4\x93I\x94\xf2.\x1f(\xa5db\
+b\x82\x5c6G\xa5R!\xda\x15%\x1e\x8fS,\x16\
+888\xb8| @4\x1aepp\x90\xcd\xcdM\
+\x0c\x9f\x8f\xd6\xd6Vz{\xd3\xf8\xfe\xc7M\xef\xa3\x92\
+\x85\x1f\x1a\xb6mS(\x16\xf0<\x8fJ\xa5B\x22\x9e\
+\xa0\xa3\xe3\xbf\x7fi\xb8\x14\xe0Qx\x9ewb\x8ex\
+\xa9\xc0\xd3\xc4\xbf\x01\x1d\xed\xef\xf6__\x8c\xd9\x00\x00\
+\x00%tEXtdate:creat\
+e\x002023-03-08T17:\
+27:27+00:008\xef\xae1\x00\
+\x00\x00%tEXtdate:modi\
+fy\x002023-03-08T17\
+:27:27+00:00I\xb2\x16\x8d\
+\x00\x00\x00(tEXtdate:tim\
+estamp\x002023-03-0\
+9T06:18:41+00:00\
+=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB`\x82\
+\
+\x00\x00\x07&\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x05\xf1IDATX\xc3\
+\xd5\x98\xddo\x1cW\x19\xc6\x7f\xe7\x9cY\xc7\x01\x7f%\
+n\xb1[\xd26n%\xabNh\x8a\x89\xd44\xadI\
+\xd5F\xc8\x154\x84P `\xa9A\xa8\xa2\x22\x01A\
+*@\x88\x1b\x90(\xfd\x03\xf8\x10\x14\xa9B\x0a\x5c\xc1\
+E\xa8@\x5cp\x11\xa9i\x9b\x08Jp\xd5$u\x08\
+\xa4\x0eI[\x9c8\xb1\x1d\x7f\xee\xce\x9cs\xde\x97\x8b\
+\x19\xaf\x9b^t7\xb1\xe3\x84\x95^\xcd\xce\xec\xae\xce\
+O\xcf\xf3>\xef\x99\x1d\xa3\xaa\xca\x0d\xfc\xb2\xd7\x1b\xe0\
+\xff\x1e0\xa9\xf5\x85r\x1a8{~\x9a\xd1\x89\x0aQ\
+\x94\xcd\xeb;X\xd1\xe0\x96\x0d\xb0\xa6\x82\xfb\x0f\x9e\xe2\
+\xafC\xe70\xce\xf0\xf3\xfdG\xf9\xd1\xbe#\xfc\xf7\xe2\
+\xec\x8d\x01\x18E9\xf5\xd68\xad\xab\x9bh\xeaX\xcd\
+\x13;\xee\xe5\xec\xe8\x0cO\xff\xec\x15^~}\x84\xe5\
+HWM\x05\x9d\x01509\x97\xb2\xea\xa6f\xbe\xf2\
+\xf9^\xee\xbe\xab\x9dg\x7fs\x84\xe7^8\xce\xcc\x9c\
+\xbf\xa6\x805{PU\xa8\xa4\x9e4\xcd\x10Q\x8c\x81\
+-\xf7wq\xfb\xad-\xfc\xf9\xc57y\xe3\xf48\xdf\
+\xfc\xdc\x06\xbaok\xbb\x1e\x0a*\xaaBZ\x00fi\
+FZ\xc9\x98-\xa7tv43\xb0m\x1dX\xf8\xde\
+/\x0f\xf3\xa7C\xff!DY~\x05E\x22\x954#\
+K=\xaazY\xdf\x19\x0b[\x1f\xbc\x83\x93o\x8e\xf1\
+\xab?\x1e\xe7\xd8\xf0\x18Om[\xcfMm\x8d\xcb\xa5\
+ H\x14\xcaiF\x96\x05\xb2,\xe03\x8f\xcf<Y\
+\xe6IS\xcf\x5c%c\xedm-l\x7f\xa4\x8b\xa13\
+c|\xf7\x17\x878\xf2\xcf\xd1e\x04\x94H\x9az\xbc\
+\x0fd>\x90\xf9H\x96E|\x16\xf2kY\xa0\x5c\xf1\
+$%\xcb\xd6\xfb\xd7\xd0\xbe\xaa\x81g\xf6\xfd\x9d\xdf\xfe\
+\xe5$\xe54,\x1a\xb0\xa6\xc5Q\x84J\x9a\xc3\xbc{\
+\xdb\xce\xdf\xea\xbb\x8eyuw\xb5\xd2\xde\xd6\xc0\x1f^\
+\xfa7o\x0c\x8f\xb1{\xc7=\xdc\xd1\xd9|\xed\x14T\
+\x89d\x99\xc7\x87\x88\x0f\x92\x97\x8f\x84\x10\xf1>\xe2C\
+\xccU\xf5\xf9y\xb9\x92\xf1\xc1\x95\x96\xbe\xde\x9b\x19\x9d\
+\x9c\xe6\xfb\xcf\x1d\xe2\xc0?\xdeF\xae\xf2\x9e\xa4\xf6\x98\
+\x11\xc1\x87H\x08Bu\x8d\xf9\xb0\xe8\x82r\xf3%\x22\
+\xa8\x0a*B\xcf\xda&\xce],\xf3\xd3\xdf\x0f2t\
+z\x8c]\x8f\xf6\xd0\xda\xd4\xb0\xc4\x80*\xc4\xa8\x84\xa0\
+\xef\xb1X\x17\xac\x95\xcb\xe1D\x16\xaa\xb5\xc9\xf2\x91;\
+Wr\xf0\xb5\xd3\x9c<;\xc1\xee\x1d\x1bX\xdf\xb5z\
+\x09-.\x16\x8c\xa2\xc4\x98W\x88\x0b\xd0!\xe4\xe7>\
+H\xf5\xe8\x0b\xbb\xbd\x0fTRO\x14\xcf]\xb7\x96\x98\
+\x9a\x9e\xe0\x87\xcf\x1f\xe6\x85\x97\x86\xf1\xa1\xbe\x99Y\xfb\
+vK\x05Q\x88\x11\xa2\x14\x15y\x0f\xb0\x12\xa3\x10\xe6\
+{4H\xb57sP!\x84@G\x9b\xa1%\x99\xe6\
+\xc7\xcf\x1f\xe0\xd9}\xafr~|n\xf1\x16\xe7\x8dg\
+\x10\x01\xa9\xf6`~Y\x15D\x15\x15\x10\xd1\xa2\x04\x89\
+\x82\x8a\x22Q\x08>P\x9e\xab011\xc5\xe8\x85q\
+F/N0=S\xe1\xd7\xbf\x1b\xe1\xc4\xf09\x9e\x1e\
+\xd8\xc4\xa6u\xb7`\xcc\xd5\x02\x22\xa0 \x02Z\x1d+\
+\x05`\x01-\x85\x921\xe4\x89\xafTRfg+L\
+\xcf\xcc255\xc3\xf4\xcc\x1c\xe5r\x99,M\xf1>\
+\x80\x06\xb2\xc9\xb7y\xf1\xe0%F.L\xf1\x93\xef\xf4\
+\xd3\xdb\xddqu\x80\xaa\xb9u\xe54\x16\x8d\x9f\xab\x94\
+\xdb\x9b[\x98\xf9\x90\x83\xa5\x19i\x9a\x92VR\xb2,\
+\xc3\xfb\x8c\xe0\x03\xaa`\xad\xc59K\x0c\x060\x18c\
+p\x16\xd0\xc0\xfbM\xa0\xba\xc6\xccT9\x03WA\xe6\
+\xd3Z$6\x07\x15b\x8c\xc5\x11\xc0b\x9d\xc39\x87\
+\x88C\xa2\xc3X\x8b5\x16S\xf8\x98$%\x1a\x9b\xda\
+\xd8x\xef:\xbe5\xd0Go\xf7\x87\x16\xd1\x83(\xd6\
+9\xacu\x18\xa3(\x8a\x15E\xad!J\xae\x841\x14\
+=\xa4\x88\x0a6\xba\xfc7\xd1\x16\x9f\x1b0\x8014\
+46\xf2\x81\xa6f>\xfdp/_}|\x13\x9d\xed\
+\xef\xbf\xcb$5\xd8P\x94\xa4\xe4(\x95\x92|\xde\xb1\
+\x10\x06c\x04\x88\xd5\x9e\xb4V\xb1V\xb0\xd6b\x8d\xa9\
+\xc2\x19\x0c\xce9\x1a\x1aW\xd0ys\x1bO=\xde\xc7\
+\xa7\xb6\xac\xa7\x94\xd4\xfeoSW\x8a]\xc9\x91$\x09\
+*\x92[l\x15\x11SX\x9a\xc7Y\xad\x22\xd6\xe6p\
+\xd6b\xcc\x82z\xaedQ\xe0\xbe\x9e\xb5|c\xe0a\
+z\xee\xbc\xa5\xb6q\xf5[,\xb8$\xa1\x94$\xc5N\
+Q\x84\xa4\xe8\xa7\xcb\x94\xab\xda\x99[j\xac\x05'\x94\
+\xf0|\xe9\x13\x1byb\xfb\x83\xb44\xad\xac\x1b\xae~\
+\x05\x93\x5cA\x91X\x00\x9a\xdcYU\xd4)\x22\x16c\
+-\xc6.\xd8j\x13Cev\x8a\x8e&\xc3\x9e\x9d\x9f\
+\xe4\xa1\xfbz\xaa!YR@E\xb0\x89\xa5Tr\xc4\
+h\xd0B=UP\xa7X\x11\xac5\xcc/m\xad!\
+\x9a\x8cK\xe3o\xf1\xf1\xf5k\xd8=\xd0\xcf\x9a\xce\xf6\
++\x06\xab\x1fP\x15S(hL\xac\xaa\x97+Y(\
+W\xcc5\x93X&&G\x89\x93#\xec\xf9\xcc\x03|\
+\xb6\x7f3\x0d\x0d\xa5\xab\x86\xab\x030O\xadq\x05 \
+T\xd5\xb3VsK\x01\xac!\xb3\x9e3g\x86\xe8j\
+\xb6\xec\xdd\xbb\x93\x0d=]\x8b\x02\xbb\x02\x05\x05\x9b8\
+J\x89\xc3\xa0\x10@\xac\x1490hb\x19\x9d\xba\xc0\
+\xb9S\xc7\xd8\xbe\xe9n\x9e\xdc\xd9O[K\xd3\x92\xc0\
+\xd5\x07\x88Bug\xd0\x5c9\xf2\xa4\x96]\xe4\xc4\xf0\
+Q\x1a/\x8d\xf0\x83/?\xca\xd6\xbe\x8f]U\x10\x16\
+\xad \xce\xe2\x9c#\xe6\x83\x0fq\x86w\xa6\xc7\x19\x1a\
+|\x85\xcdkV\xb1\xf7\xebOr\xfb\x87;j.v\
+\x8d\x00\x15\x8a\xd1\xa1\x0a\xb3&2x\xfa(\x97N\x0c\
+\xb2\xe7\xb1>\xbe\xb0\xed\x11V\xac\xb8\xb2\xdb\xf8%\x05\
+\xb4\x062\x89\xcc\x06\xcf\xd9\x99\x0b\xbcz\xf8\x00]\xae\
+\xc23\xdf\xde\xc5G\xef\xe9\xbef`u\x01Zki\
+_\xd5\xcak\x13\xef0\xfc\xaf\xe3\x8c\xbc~\x84/n\
+\xe9\xe5k\xbbv\xb0\xaa\xad\xe5\x9a\xc3\x01\x98Z\xcf\xa8\
+\x07O\x0c\xb3\xffo\xc7\x88\xe7\xcf\xd3\xdf\xb7\x91\x87\x1e\
+X\xfa ,\x0a\x10 \xcb<I\xe2\xb0v\xf9\x9f\x18\
+\xd7\x05x=_7\xfcC\xf4\xff\x01;\x99\xc4\xf6\x95\
+\xaa\xb1\xc6\x00\x00\x00%tEXtdate:\
+create\x002023-03-0\
+8T17:27:26+00:00\
+\x9e\x98\xa5\x85\x00\x00\x00%tEXtdate\
+:modify\x002023-03-\
+08T17:27:26+00:0\
+0\xef\xc5\x1d9\x00\x00\x00(tEXtdat\
+e:timestamp\x002023\
+-03-09T06:18:41+\
+00:00=\x00\x9f\x01\x00\x00\x00\x00IEN\
+D\xaeB`\x82\
+\x00\x00\x07\xb3\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x06~IDATX\xc3\
+\xcd\xd8[S\x1b\xc9\x15\xc0\xf1\x7f\xf7\x8c4\xe2\x22\x18\
+tCW\x046\x86\xb5\x0dfqU\xd6\xb1\xf3\xb4\x95\
+\xdd\xa7x\xf7\x03$\xf9\x88\xa9\xe4!y\xd9\xca\xe3\xda\
+1\x8bm\xb0\x1c\x8ccs\xc7\xc6\x12\xba\x80@ 4\
+3\x9a\xe9<\x08yq*\x01\xb16\xc1\xa7\xaa\xd5z\
+P\xcd\xfc\xa6\xa7O\xf7i\x09\xa5\x94\xe23\x0e\xfdS\
+\x5c\xe4\xe43\x0a!.\x1f\xa8\x94\xc2u\x9b8\x8e\x83\
+m;\xd8\x8e\x8d\xe7\xba \x04>]\xc7\xe7\xf3c\x18\
+\x06~\xbf\xff\xff\x0ft\x1c\x9b\xc3\xc3C\xca\x95\x1d\xd6\
+7\xb7\xc8\x17J\x1c\x1c\x1c\xe0\xb9\x0eR\xd3\xe9\xe9\xee\
+\x22\x1a\x0d\x91J\x0c\x12\x09\x87\x09\x87#\x18\x86\xf1\x8b\
+\x81\xa2\xd39\xa8\x94\xe2\xf0\xf0\x80R\xa9L\xee\xc5\x12\
+\x1b[e\xc2\xe10\xd9\xa1\x14\x03f?>]\xc7\xb2\
+-\x8a\xa5\x0a\xabk\x1bTw+\x0c%\xc3\x5c\xbb\x9a\
+edx\x84P(tq@\xa5\x14\xd5j\x95\x8d7\
+o\x99\xcd-\x13\xec\x0f3=u\x93\x90\xd9\x87\x94\x1f\
+\xce9!\x04\xcd\xa6\xcbV\xbe\xc8\xcc\xecS\xec\xfa\x0e\
+7\xaf\xa5\x99\x9c\x9c$>\x18\xbf\x18`u\xaf\xca\xea\
+\xda\x06O\x166\xb92:\xc6\xd8\xd5a4M \x04\
+H!@\x80\x10\x80\x02\x05(O\xa1\x104,\x87'\
+s96W\x17\xf9bd\x90{w\xef\x12\x0a\x85\xcf\
+\x05\x94g\xfd\xa0\xd1h\xb0\xf5v\x8b\xe7\xaf\xb6\xc8^\
+\x19gx8\x8b\xab\x14\x9e\x02%Z2q\xdc#[\
+\xbd\x12\x02W)\xa4\xa61==Ez\xe4&\xaf\xd7\
+\xf2\xe4r9\x1c\xc7\xf9\xb4\xc0R\xa9\xc8\xd2z\x1e\x11\
+\x08\x93H%q=\xd5\x82I\x81\x94\x02\xa9\x09\xa4.\
+\xd0\xb4V\x93\xc7M\xb4\xb1H\xc6\xaf_\xc7\xe8K\xf0\
+n\xbbD\xa1\x90\xfft@\xcb\xb2(lo\xb3Y\xb2\
+\x88%R\xb4\xdf\xa5\x90\x12y\x0c\xd2t\x81\xae\x09t\
+\xfd\xf8\xfb\xfb&\xdfc\x03\x86\x9ft\xf6\x1a\xf5\xa6\x8f\
+\xbd\xfd=\x5c\xd7\xed\x18x\xea2S\xab\xd5(\x96\xab\
+\x1cz=H\x7f\x17J\x08\x84&\xf1\xfb5\x0c\xbf\xc4\
+\xa7\x81&A\x9exL\xa5\xc0S\xa0\xb9 5\x89.\
+],\x01\x91h\x84\xf5\xb5\x00\xb6\xdd\xa4^?$\x18\
+\xec\xfb\x14\xc0}v\xf6\x8f\x90F\x88\x03Ka\xb9\x10\
+\xc0\xe3\xd9\xfc<\x07\x07\xfb\x18>\x1d)\xa1\x95\xc7\xad\
+O\x85B)p=E\xb3\xe92q\xe3:\xfdf\x04\
+\xa1\xf9pD\x80\xf5\xcd\xb7\xc4\x07c\x9f\x06xX?\
+\xe4\xc8r\xf1w\x05\x10Rb\xb9`{\x1e\x0f\x1e>\
+\xa2X\xda\xa5\xdf\x1c\xe0C^+\x8b\x11\xd0t\x1cv\
+\xcb\xdb\xf4\xf5\x9b\x8c\xf5Eq\x94@\xfa\xbaX]_\
+\xe4\xfa\xd8U\x92\xc9\xd4\xc7\x03\x1d\xdb\xa1\xe9z\xe8\xba\
+\x8e\xaeI|\xba@\xd7\xa0\xaf\xbf\x8f\xae`\x94X\x22\
+M{\x19\xfc\x00\x88\xc0\xb2\x1al,/\xa2\xf9\xfd\xd8\
+\x9e\xa2\xe9\x81\x10\x92b\xb1H\xc3jt\x84;\x13(\
+\xa5\xc0sm@!\x04hR\x1004~w\xff[\
+t\xa9\x11\x08\x04\x10\x88\xf7\xb8\xe3\xa5\x10\x05\xb8\x9e\xcb\
+\xc1\x9dk\x04M\x93\x86\xdbb+\xaf\x89c[\x18\xfe\
+\xce\xb7\xbeS\x81\xc1`\x90n\xbf\xa0\xd6\xb4P\xaa%\
+\x90R2\x94N\xa0\xcb\xd6\x12 \x8fa'\xf7\x13\xaf\
+\xddb&G\x8e\xc2\xae6A)\xacz\x15Mx\x0c\
+\x0c\x0ct\x0c<u\x99\x09\x85\xc2\xa4\xe3a\x82\xbaE\
+\x8f\x0f|R\xa0\x94\xc2S\x0a\xe5\xfd<\x5c\xe2D\xe3\
+D\xaf<u\x5c\x8a)\x8e\x1a6{\xa5\x0d\x22\xe1\x81\
+s\xed&\xa7\x02{{\x83d\xd2)F\x06\x0d\xa4g\
+\xa3<\x0f\xa7\xd9\xca\xd2v\xb4G\xaf\xbd\x91\xfc\xe7\x88\
+*\x0fl[Q)n\xb3WX\xe2\xcb/\xa7\xcfU\
+\xdd\x9c\x0a\xd4u\x9dp8\x82\x9f:\xf9woxW\
+nP\xab{\xd8M\x85\xe2D\x91z\xf2U\x1f\xcb\xda\
+s\xd1v\x14\x95\xdd:\xeb/g\x08\xf5\x05\xf8\xeaW\
+w:\xc6\x9d\x09\x04\x88D\x22\xf84Aqm\x8e\xb5\
+\x95eJ\xbb6{\x07.\x8e\x0b\xde1\xb3M\xfd0\
+\x93\xc1\xb2\x15\xdb;\x16\xaf_\xccQ\xdax\xc6\xfd\xfb\
+\xdf\x9d\xbb\xec:\xb3`\x95R21q\x8b\xd7KK\
+\xcc=\xfd+\xaec!\xb8\x85\x90\xdd\x84\x83\x1a\x81\x13\
+W\x10\xb4\x92\xc3\x05\x0e,\xc5\x9bB\x9d\xdc\xd3Y\x16\
+g\xfe\xc6o\xbf\xfe\x9a;_\xfd\xfa\x5c88G\xc1\
+\xba\xbf\xbf\xcf\x9f\xff\xf2'~z\x9a#:|\x9b\x1b\
+\xb7\xefqe8E,\x14\xa0' \xf1\xc9\x9f_\xe9\
+\xee~\x83\x8d\xcdw,<y\xc8\xca\x8b\x19\xbe\xfd\xe6\
+\x1b\xbe\xbb\xff=\xba~\xfe\x13F\xc7@h\x15\x0f\x0f\
+\x1e\xfc\xc8\x0f\x7f\xff\x81\x9d\xfd\x06\xd1\xf4\x18\xa9\xec(\
+\x91\xd8 \xdd\x01\x03\xc7\xb6\xa8\xee\x94(\xbcY\xa1\xb8\
+\xb5B\x97\xa1\x91\xcd\x0es\xef\xeeo\x98\x9c\x98D\xb4\
+K\xb3\x8b\x02\xb6\xa3\x5c.3\xffl\x8e\xe7\xcfsl\
+o\x179\xb2,\xa0\x95T=\xdd]\xa4S)&'\
+n\xa1\x14\x18\x01\x83B!\x8fm\xdbL\xdc\x9c`|\
+\xfc\x8b\x8b\x07\xb6\xc3u]j\xb5\x1a\xf5z\x1d\xd7u\
+\xf1\xf9tzzz\xe9\xed\xedE\x08\xc1\xda\xfa\x1a\x9a\
+\xd4\xa8\xecTx\xf2d\x16!$\x7f\xf8\xfd\x1f\xe9\xea\
+\xea\xea\xf8\x1egf\xf1i\xa1i\x1a\xa6i\x92L&\
+\xc9d2\xc4\xe3\x09\x82\xc1\xe0\xf1\xb9\xa4\xc9\xabW\xaf\
+\xa8\xd7\xebd\xd2i\xb2\xd9a\xaa\xd5]\xe6\xe7\xe7\xce\
+u\x8f\x8f\x02\x9e\x16\xba\xae\x93IgXXX@J\
+\x8dLf\x88D\x22\xc9\xe3\xa7\x8f\xa9T*\x97\x0f\x04\
+\x18\x1f\x1f\xc70\x0c^/-1\x18\x1b$\x93\xc9\x80\
+R<\x9a\xf9\x07\x9d\xce\xac\x0b\x05\xea\xba\xce\xf4\xf44\
+k\xab\xab\xd4\x8f\xea\xa4Ri\xd2\xe9\x0c/\xff\xf5\x92\
+\xf5\x8d\xf5\xcb\x07\x02\xa4\xd3i\xe2\xf18\x8b\x8b\x8b\x98\
+\xfd&\xe9t\x9a`o\x90\x07\x0f\x7f\xec\xe8\x84w\xe1\
+@\x80\xe9\xe9i*\x95\x0a\xe5r\x99D\xa2\x95P\xf9\
+|\x9e\x85\x17\xff\xfc<\x80\xa6i2>6\xce\xe2\xcb\
+E\x0c\xc3 \x99L\x11\x8b\xc6x\xf4\xe8\x11\xb5\xda\xfe\
+\xe5\x03\x01n\xdc\xb8\x81\xf2\x14\x9b\x9b\x9b\xc4\xa2Q2\
+\x99\x0c\x8dF\x83\xd9\xc7\xb3\x9f\x070\x10\x08055\
+\xc5\xca\xf2\x0a\xae\xe7\x11\x8f\xc7I\xa5R\xe4r\xcf\xc8\
+\xe7\xdf]>\x10`dd\x04\xd34Y^^\xc64\
+\x07H&\x93\x18F\x80\x99\x9ffP\xca\xbb|\xa0\x94\
+\x92\xdb\xb7oS\xc8\x17\xa8\xd5j\xc4\xa21\x92\xc9$\
+\x95J\x99\xa3\xa3\xa3\xcb\x07\x02\xc4b1FGG\xd9\
+\xda\xda\xc2\xe7\xf7\xd3\xdd\xdd\xcd\xd0P\x16\xff\xff8\xe9\
+}T\xb1\xf0K\xc3\xb6m\xca\x952\x9e\xe7Q\xab\xd5\
+H%S\xf4\xf5\xfd\xf7\x7f\x1a.\x05\xd8\x0e\xcf\xf3\xce\
+\xac\x11/\x15\xd8I\xfc\x1b\xf8\xd3\xe2\x07\xbf\x88\x01N\
+\x00\x00\x00%tEXtdate:cre\
+ate\x002023-03-08T1\
+7:27:27+00:008\xef\xae\
+1\x00\x00\x00%tEXtdate:mo\
+dify\x002023-03-08T\
+17:27:27+00:00I\xb2\
+\x16\x8d\x00\x00\x00(tEXtdate:t\
+imestamp\x002023-03\
+-09T06:18:41+00:\
+00=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x06\x9c\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x05gIDATX\xc3\
+\xcd\x98\xdbR\xdbF\x1c\x87\xbf]I6v\x8c\x09\x81\
+`\x93\xd0\xa6!\x99\x98So;\x9d6\x9d\xf4\xb6\xcf\
+\xd1\x9b>`\x1f\xa0w\xbdKho\x80\x90\x03I\x98\
+6q0\x03\xc6\x08[\xd2\xee\xf6B\x07\xdb\xb2%\xdb\
+t\x9afgd\xaddi\xf7\xdb\xdf\xff\xb0\xbb\x12\xc6\
+\x18\xc3g\x5c\xec\xf4\x8dN\xa7\x83\xeb\xba\xff\x0bL\xb9\
+\x5c\xa6R\xa9d\x03j\xady\xfa\xf4)\x85B\x81R\
+\xa9\xf4\xc9\xc0\x84\x10\xb8\xae\x8b\xe7y<~\xfc\x18)\
+\xe5x@c\x0c\xbe\xef\xb3\xb5\xbdM\xb1PD\x1b\x8d\
+\x14\x02\x84@ @\x00\xc6D\xd7\x80\x10a\x07\xfd\x9e\
+\xc6^\x0f\xdd\x1b\x07(%\x97\x9d\x0e\xcf\x9e=#\xed\
+q#&\x16B\xa0\x94\xc2\xbd\xba\xa2\xdb\xbd\xa2\xd7\xf3\
+\x08\x19\x05\xc5b\x91R\xa9\x84\x88A\xd2\xe74d\x06\
+\xb0\x14\xe18c\x14)\x04Z\xeb\xa4\x9d\x5c\xc0\xb8A\
+iI\xf6\xf6\xf6x\xf5\xf2%\x95J\x85n\xb7\xcb\xd2\
+\xf22?>y\x82\x14\x22\xec+Rr\x148l%\
+:\x11c\x0a\x01\xda\xc0\xfbs(\xd8\xb0X\x06K\x82\
+A\x92U\xc6*\x18\x9e%\xbe\xe7\xf1\xa8\xd1`gg\
+\x87w\xef\xde\xb2\xf7\xfc\x88\xf7\x97\x0e`\xa1\x8cAi\
+\x816\xa0ttDu=PW\xda\x8c\xfc\xf7\xc7\xb1\
+a\xf7\xd8\xb0Y\x17|\xff\x00\xbe]\x07G\x88\xe9\x00\
+\xe3\x22#\xb3:\xb6M\xb1X\xc4q\x0a\x18\x04\x9e\x92\
+\x18$\x81\x86@\x83R&\xa9\x07\x11h\xa0\xa2k5\
+p/\xbaV\x1a\xda]8\xe9\x18\x8e\xcf\xe0}[\x10\
+h\x81\x93\xe1\xa4\xa3\x80Q\x10 \x04\x96e\xb1\xbf\x7f\
+\xc0\x87\x8f-:\x17\xe7\xccW*\xdc_\xf4\x91R\x0d\
+\x997\xf6\xb7\xecs\xdf\xf4\xda\xc0\x8b\xa6\xe1\x97'\x92\
+\xbb7\x05s\x0e\x80\xa4\xd3\x99\x12\xd0\x0c8\xf5\xe6\xd6\
+\x16\xf5;k\xfc\xed\x96yP\xeer\xabZF\x08\x19\
+=!\xa2\xe7\x13\x0f\x8b\xde\x15\x91\xf3\x8b\xa4A\x13\xc1\
+\xc6\x01\xfa\xa8&\x92\xbe\x8c\x01!\xb3c<\xd3\xc4\x00\
+\x0b\x0b\x0bT\xaa\x8b\xb8's\xac-y\x94\x1d\x831\
+&\xf4\xd3\x94RI\x04\xa6\x15L\xd7\xe9Go\xac\xaa\
+\x10\xd7\x00\x14B`L\x08\xe4\x07:\xcaO\x03p\x19\
+&\x1d\x01\x1d\x038\xae/f\x0d\x92\xc1\x14\xeb\xa9\xc8\
+\x94\x22J\x1e\x830S\x00\xe6)\x14\xf7\x95\xf5D\xb6\
+\x89E?\x8f\xf9j\xb0\xa3\x81\xe0\x18\xcc\x7f\x199q\
+\xacZS\xdc\xcb\x07\x8c:\x8f\x9d>\x04\x1c\xa3\xd4\x18\
+\xa8tg\x93\xd4\x9bT2g\x92X=\x22@\x13\xcd\
+\x04i\x90\x11%\xa7\x04\x9b\x16<\xdf\xc4Q\xce\xf1U\
+8q&SX\x0an\x12\xd8\xbf\x81\xb5'\xbf\x94V\
+pz\xb8i\xc0&\xad\x97s\xa3\xd8D\xed\xc7A2\
+-\xdc,*\x8e\xf3\xdb\xa9\x00\x13\x13'\x0a\x0e\x9b8\
++Z?I\x90$ZE\xd1\xd27\xb1 \xed\x83\xb3\
+\x80]G\xc5\x5c\x05\x89\x14\xf4\x828\xe1\x08LF\x07\
+\xff\x95\x8a\xd9y0\x91\xb1\xaf`2\x93LP\xe3:\
+*\xce\x048\xbc\x93\x10\xfd4\x13\xafX\xa6\x08\x8e<\
+\x15\xd3\x91{\xfd \x89\x0c\x1a\x07I\x18\xda\x93\xd5\x1a\
+W\x8f\x17\x1e\xf1\x91\x06TZ3.\xe3d/\xf9\x07\
+~}e\xc2 \x89\xc0\xb3\xde\xc9\xaak\x1d\xae\x86\xb4\
+\xd6(\xa5\xd0Z\xa3\xb5N\x9e\x93R\xe2\xf9\x01f\x8c\
+\x87OH3ap\xf8\x01#\xa3\x9b\xe4_\x83pJ\
+)\x94R\xf8\xbe\x9f\x1cA\x10$;9\xc7)\xd0\xee\
+\x5cb\xb4\x19\x19\xff\xe44\x03\xd1r\x8b\x81\xc8\x9e\x0e\
+4\x86\xf3}\x9f^\xaf\x87\xeb\xba\x5c\xba.\xdd\xab.\
+\x9e\xe7\xa1\x94\xc2v\x1c\x0as%\xde\xbc9b\xfd\xde\
+\x97HiM\xa9`\xda\xc4\xa6\xef\x82\xd3Dg\x0c\xe7\
+y\x1e\xddn\x97v\xbb\xcd\xd9\xd9\x19\xe7\xed\x0b\x5c\xd7\
+\xc5hMe\xbe\xc2|\xc1\xe1\xf8\xed\x11_\xdcY\xa5\
+\xd1h\xcc`b\xfa@\x89\x82\x13\xa0\xe2{\xb1\xbf\x05\
+A@\xaf\xd7\xa3\xddn\xf3\xa1\xd9\xa4\xd5jaY\x16\
+\xd5j\x95\x95\x95\xdb\xd8\x96\xe4\xf5\xd1k\xd6\xd6\xee\xb2\
+\xbd\xb5=\xf4\xc9c2\xa0\x08\xf1\x84\x08\xb7\x8b\xb9\x03\
+I\x81\xc6\x80\xbe\xef\xe3\xba.gg\xe7\x9c\x9c\xb4X\
+]\xad\xb3\xbd\xb5\x85\x10\x82\xd3\xd3\x16\xfb\x07\xfb\xd4k\
+\xablnn\x8e\x85\x03\xb2\xb7\xf4\x82p\x0f\xfb\xa1\x1d\
+\x026\xdb\x06Oen\x1d\x86@\x8d1\x89y;\x9d\
+K\xda\xed6\xae\xeb\xb2xs\x11\xdb\xb6i\xb5N\xd8\
+?\xd8\xa7\xb6R\xcb\x85\xcb\x05\x84\xf0\xb3\xc4o\x07\x8a\
+\xdf_)~\xfd3\x88\x229\x7f&\x88\xf3\x5c\x08\xe8\
+suuE\xe7\xf222\xed<\x1f?69x~\
+@m\xa5\xce\xc6F>\x5c\xae\x89\x0d`K\xc1\xc3\x15\
+\xc9E\xd7poI2\xe7\x84\x1b\xefI\xa5\xef\x83>\
+=\xaf\x87\xd7\xebq\xfb\xf62\xdd^\x97\x17\x87\x87\xd4\
+ku\x1a\x8d\x8d\x89p\xb9\x80\x82\xf0;\xca\xce\x1d\xc9\
+w\x0f,\xbe\xb9?\x1c\xfeY\xd3\xd3\xe0l\xa1\x94F\
+\x05\x0a\xa7P`\xa1Z\xe5\xc5\xe1!\xb5Z\x9dF\xa3\
+1\x15\x5c\xae\x89\x0d\xa0\x8d\xe1\xd6\x0d\xf8\xf9\x07\x87\xaf\
+\x96\x04\x03\xc9?\xb3\x0co\x07@Z\x16\xd5\x9b\x8b4\
+\x9b\xcd\x99\xe1\xc6*h\x8cAJ\x89c\xdb\xc9W\x84\
+\x9f\xbe\x06\xdb\x1a\xde|\xe7\xadf\xa4e!-\x0b\xbb\
+P\xc4.\xdd\xa0\xf5\xd71\xeb\xf7\xbe\x9c\x19n\x040\
+\xcea\xbb\xbb\xbb\x94J\xa5\x10\x10\x86\xa7\x95h\x10y\
+&\xd6\xc6\xe0+\x8d\xe7\x07t:\x1d\x1a\x0f\xd7\xd9\xd8\
+\x98\xce\xe7F\x06\x9c\xfe\xca\x7fzz\xca\xc5\xc5\xc5\xcc\
+\x0de\x95R\xa9\xc4\xf2\xf2\xf2\xb5\xe0\xc6\x02~n\xe5\
+\x1f\x17\xf7\x97\x9b\xb7\xd04\x1a\x00\x00\x00%tEX\
+tdate:create\x00202\
+3-03-08T17:27:26\
++00:00\x9e\x98\xa5\x85\x00\x00\x00%tE\
+Xtdate:modify\x0020\
+23-03-08T17:27:2\
+6+00:00\xef\xc5\x1d9\x00\x00\x00(t\
+EXtdate:timestam\
+p\x002023-03-09T06:\
+18:41+00:00=\x00\x9f\x01\x00\
+\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x03\x89\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01G\
+PLTE\xff\xff\xff\xfe\xfe\xfe\xf2\xf2\xf2\xef\xef\xef\
+\xf8\xf8\xf8\xd5\xd5\xd5\xe2\xe2\xe2\xe3\xe3\xe3\xe3\xe4\xe4\xe4\
+\xe4\xe4\xe5\xe5\xe5\xe6\xe5\xe5\xe6\xe6\xe5\xd6\xd6\xd6\xf7\xf7\
+\xf7\xd9\xd9\xd9\xea\xeb\xec\xe2\xe7\xec\xdd\xe4\xed\xe9\xec\xee\
+\xec\xee\xf0\xea\xec\xed\xcb\xcc\xcc\xc6\xc6\xc6\xe0\xe2\xe2\xf1\
+\xf3\xf4\xf0\xf2\xf3\xe8\xed\xf4\xe5\xeb\xf3\xf2\xf3\xf5\xda\xda\
+\xda\xe0\xe5\xed\x93\xb9\xee\x82\xb0\xf1\xb4\xce\xf1\xbe\xd4\xf3\
+\xc1\xd3\xec\xb8\xbc\xc1\xc8\xc8\xc8\xbf\xc9\xd6\xce\xdf\xf7\xc3\
+\xd8\xf5\xa1\xc4\xf4{\xad\xf3\xcb\xdc\xf5\xdb\xdb\xda\xec\xee\
+\xee\xe0\xe6\xee\xd9\xe3\xf0\xe9\xed\xf1\xec\xef\xf2\xea\xec\xef\
+\xc9\xc9\xca\xc4\xc5\xc5\xe0\xe1\xe3\xf2\xf5\xf7\xf1\xf3\xf6\xe7\
+\xee\xf7\xe1\xea\xf6\xf3\xf5\xf7\xdb\xdb\xdb\xef\xf0\xef\xf1\xf1\
+\xf1\xf3\xf3\xf3\xf4\xf5\xf5\xf5\xf5\xf5\xf6\xf6\xf6\xf9\xf9\xf9\
+\xfa\xfa\xf9\xf0\xf1\xf1\xf3\xf4\xf4\xfa\xfa\xfa\xfb\xfb\xfb\xf1\
+\xf2\xf2\xf6\xf7\xf7\xfc\xfc\xfc\xf9\xfa\xfa\xdc\xdc\xdc\xf4\xf4\
+\xf4\xfd\xfd\xfd\xda\xdb\xdb\xfe\xff\xff\xd8\xd9\xd9\xf7\xf8\xf8\
+\xee\xee\xee\xde\xde\xdf\xd8\xd9\xda\xcf\xd1\xd1\xea\xea\xea\xcc\
+\xcd\xcd\xc3\xc4\xc4\xc9\xca\xca\xed\xee\xee\xf2\xf3\xf3\xd0\xd0\
+\xd1\xc5\xc6\xc6\xe9\xe9\xe9\xd4\xd5\xd4\xf8\xf9\xf9\xbf\xc0\xc0\
+\xcb\xcb\xcc\xd7\xd7\xd6\xe8\xe8\xe8\xc1\xc2\xc3\xcb\xcb\xcb\xf0\
+\xf0\xf0\xd3\xd3\xd3\xd5\xd7\xd8\xc8\xca\xcc\xe5Kfq\x00\
+\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x09\
+pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\
+\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\
+\x1f\xc9N\xa7\x00\x00\x01\x06IDAT\x18\xd3U\xd0\
+WS\x021\x10\xc0\xf1\xcd]$\x1eJ\xec]Q\xb0\
+\xf7\x86\x1d\x85\xbb\x08wr\xc0YP\xb1c\xaf\xdf\xff\
+\xd9$\x1b\x1f\xfc?\xfefwvf\x01\x88e\xff\xcb\
+\x22\x00@\x9bb1\xc6\x9a\x9d\xb8\xaa\xa55\x9e\xa0\x12\
+y[{GgWwOo_\xff\xc0\xe0\xd0\xf0H\
+\x92k\x1c\x1dK\xa5\xc7'&\xa7\xa6gf\xe7\xe6\x17\
+\x16\x11\x97\x96WV\xd7\xd63\x1b\x9b[\xdb;\xbb{\
+YD{\xff\xc0\xca\xe5]\x8fS*\x0e\x05b\xb2P\
+\xb0\x8a\x8a\x84/\x0b\x0c\x1eI+)\x0ad\xa1\xc1\x5c\
+\xd1\xe5\xb4,)TU\x10\xab\xf9\x12\xd5\x14\xa9\x10\x8f\
+]O\xeej#2\xc4\xac\xc7\xd1\x08\x01BN\x0cr\
+j\x8c\x84\xc2\xe3\xa7\x88gjP\x99\xa8\x9d'..\
+}\x8dT\x04\x1a\xfd\xfa\xd5u\xe6\xe6\x16\x10\xcb\x81:\
+\x12\xdd\xdd?4\x1em@|\xd2\xdb\x10\xd6\x9f_^\
+\xd1\x80W\xd0 z{\xff\xf8D\x03\xfa\xc5\x98\xe3\xc8\
+\x1f\xb3\xef\x1f3\x07@j\x8d\xbf\xaa\x86~\x01v\xa9\
+<\x5c=\x11\xff\x81\x00\x00\x00%tEXtda\
+te:create\x002023-0\
+3-08T17:27:26+00\
+:00\x9e\x98\xa5\x85\x00\x00\x00%tEXtd\
+ate:modify\x002023-\
+03-08T17:27:26+0\
+0:00\xef\xc5\x1d9\x00\x00\x00(tEXt\
+date:timestamp\x002\
+023-03-09T06:20:\
+50+00:00\xdd\xe8\xb2\xde\x00\x00\x00\x00\
+IEND\xaeB`\x82\
+\x00\x00\x03\xf2\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xe0\
+PLTE\xff\xff\xff\xf9\xfa\xfc\xde\xe5\xf1\xed\xf1\xf7\
+\xfd\xfd\xfe\xe9\xee\xf6\x97\xb0\xd4\xa0\xbe\xdd\xab\xc3\xde\xea\
+\xee\xf5\xcd\xd8\xea\x83\xa2\xcc\x9a\xbb\xda\xbb\xda\xec\x93\xb3\
+\xd2\xe3\xe9\xf2\xf5\xf8\xfb\xab\xbe\xdb}\x9e\xc7\x9f\xbf\xd8\
+\xac\xcb\xdf\xaa\xc9\xde\x8a\xa9\xc8\xe4\xe9\xf1\xe0\xe7\xf2\x8b\
+\xa7\xcc\x7f\xa0\xc5\x9c\xba\xd2\x9e\xbd\xd4\x9e\xbc\xd4\x83\xa1\
+\xc1\xe3\xe9\xf0\xfd\xfe\xfe\xc0\xcf\xe4u\x95\xbf\x80\xa0\xc0\
+\x92\xaf\xc9\x93\xb1\xcb\x94\xb3\xcd\x95\xb4\xce\x95\xb4\xcf}\
+\x9b\xbd\xe3\xe8\xf0\xef\xf3\xf9\x9a\xb1\xd1f\x88\xb1{\x99\
+\xb7\x84\xa2\xbe\x87\xa6\xc3\x8b\xaa\xc8\x8e\xad\xcb\x8f\xaf\xcd\
+\x8f\xaf\xcey\x97\xba\xe3\xe8\xef\xd5\xdf\xecu\x92\xba[\
+|\xa2m\x8b\xa9u\x93\xb2{\x9a\xba\x81\xa0\xc1\x86\xa6\
+\xc8\x8a\xaa\xcc\x8c\xad\xcf\x8c\xad\xd0x\x96\xba\xe3\xe7\xee\
+\xc5\xd1\xe4Tu\xa2Qo\x92[x\x98c\x81\xa3l\
+\x8b\xadt\x94\xb8|\x9c\xc2\x83\xa4\xca\x88\xa9\xcf\x8b\xac\
+\xd2\x8c\xad\xd4y\x97\xbc\xd0\xd9\xe8]z\xa26U}\
+A_\x85Sr\x99a\x81\xa8m\x8e\xb7x\x9a\xc3\x82\
+\xa3\xcc\x8a\xab\xd3\x8f\xb0\xd8\x91\xb2\xda}\x9a\xbf\xe3\xe7\
+\xed\xe4\xe9\xf0x\x8f\xae,Lz0R\x83Df\x98\
+Vy\xacf\x8a\xbdu\x98\xca\x82\xa4\xd4\x8b\xad\xdc\x8e\
+\xb0\xe0z\x98\xc0\xe3\xe7\xec\xf9\xfa\xfb\xaa\xb8\xcbIh\
+\x92>c\x96Pv\xac`\x86\xbcp\x95\xca\x7f\xa3\xd7\
+\x8e\xb1\xe3\x94\xb7\xea|\x9c\xc4\xe3\xe6\xeb\xd2\xd9\xe2l\
+\x86\xa5Ls\xa4a\x87\xbdq\x95\xca\x80\xa4\xd7\x8e\xb1\
+\xe4\x95\xb8\xea{\x9d\xc4\xe2\xe6\xeb\xee\xf0\xf3\x93\xa5\xb9\
+U{\xa5g\x8f\xc3y\x9d\xd2\x83\xa6\xda\x86\xaa\xdet\
+\x99\xbf\xe2\xe6\xea\xfd\xfd\xfd\xbc\xc5\xd0b\x83\xa3`\x8c\
+\xbcu\x9a\xcfv\x9b\xd0k\x93\xb8\xdf\xe3\xe7{\x94\xaa\
+R\x7f\xa9c\x8d\xc2a\x8c\xb2\xe2\xe6\xe9\xf7\xf7\xf8\xa2\
+\xb0\xbcQ{\x9b`\x8f\xac\xe6\xea\xec\xda\xdf\xe3\xcf\xdc\
+\xe2\xfb\xfc\xfcO\xd6>\xde\x00\x00\x00\x01bKGD\
+\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\x00\
+`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07tI\
+ME\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\x00\
+\xd6IDAT\x18\xd3c` \x04\x18\x99\x98Y\xd0\
+\xc5X\xd9\xd898\xd1\xc4\xb8\xb8yx\xf9\xf8Q\x84\
+\x04\x04\x85\x84ED\xc5\xc4\x91\xc5$$\xa5\xa4ed\
+e\xe4\xe4\x11B\x0a\x8aJ\xca*\xaaj\xea\x1a\x9aZ\
+p1m\x1d]=}\x03C#c\x13S3\x98\x98\
+\xb9\x85\xa5\x95\xb5\x8d\xad\x9d\xbd\x83\xa3\x933T\xcc\xc5\
+\xd5\xcd\xdd\xc3\xd3\xcb\xdb\xc7\xd7\xcf?\x00&\x18\x18\x14\
+\x1c\x12\x1a\x16\x1e\x11\x19\x15\x1d\x13\x1b\x07\xd3\x1e\x9f\x90\
+\x98\x94\x9c\x92\x9a\x96\x9e\x91\x99\x95\x0d\xb7('7/\
+\xbf\xa0\xb0\xa8\xb8\xa4\xb4\xac\x1c\xc9\x99\x15\x95U\xd55\
+\xb5u\xf5\x0d\x8d\xc8\x8eojnimk\xef\xe8\xec\
+B\xf1fwOo_\xff\x84\x89\xa8\x82\x0c\x0c\x93&\
+O\x99:m:z\xd0\xcd\x989k\xf6\x1c\xccP\x9e\
+;o>\xce\x18\x00\x00\xa7\xd82\x9a\xd1%ng\x00\
+\x00\x00%tEXtdate:crea\
+te\x002023-03-08T17\
+:27:26+00:00\x9e\x98\xa5\x85\
+\x00\x00\x00%tEXtdate:mod\
+ify\x002023-03-08T1\
+7:27:26+00:00\xef\xc5\x1d\
+9\x00\x00\x00(tEXtdate:ti\
+mestamp\x002023-03-\
+09T06:20:50+00:0\
+0\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\xaeB`\
+\x82\
+\x00\x00\x08\x19\
+\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
+\x00\x00\x00\x19tEXtSoftware\
+\x00Adobe ImageRead\
+yq\xc9e<\x00\x00\x07\xabIDATX\xc3\xad\
+W[P\x93g\x1a\xf6\xca\xce\xec\xcc\xf6b/\xbc\xd9\
+\xe9\xce\xecn\xbd\xda\xd9\x9b\xb5\xce\xba;{\xb0\xad\xcc\
+z\xb1\xce\xce:\xb3vTpu\xdb\xe2\x81\xd6\xb6T\
+\x04\xbb\xa5 m\xc1\x82\x06\x08\x07QB\x80\x80\x80\x02\
+!\x81\x10\x92@H\x10s$!gr\x80\x04B \
+\x9c\x09G\xb5Tx\xf6\xfb~\x13\x160X\x8b}g\
+\x9e\xf9/\x92\xfc\xcf\xfb>\xcf\xfb\xbe\xdf\x97]\x00v\
+\xfd\x98 \xf1\x0b\x82\x14\x02\x03\xc1u\x82\x03\xcf\xfd\xfe\
+\x8fH\xbc\x9b \xe1W\xaf\xef\xb5*\x8c\xd6e\xdb\x02\
+`\x19\x1e[\x09'\xf13\xfa\x19\x81\x22\xfc\xdc>v\
+H~\x8a\xa0\xb9\xb6Y\x1c2\xcf\xadB9\xfe\x1dD\
+\xf6Q\xd8\xc7\xe6\xe8\x87\x86={\xf6XSR\xae,\
+\xca::\x10N\xe2\xe5I\xc3\xc41\x04\xb7>I\xf9\
+,`\x9b]YSM\x03M\xb6\x114\xeb\xfb 1\
+y`\x19\x9d\xc5\xbb\xef\xbe?\xc5\xab\xbe\x83\xf1\x89)\
+LO\xcf\xae\x92\xef\xd7\xbct\x02\x11\x9f\x0f\xbe\x1d\xe3\
+\xb2\x04CO\xb43@\x8b{\x06\xcd=.4\xeb\xec\
+\xa8W\xf6 \x87S\x852^5C\xbc\xb0\xf4\x90\x81\
+\xc1`\x5c&\xbfK|\xe1\x04H\x1c$8A\xfd\xdd\
+\xeas'\xf1\xb9'\x04H\x87\x97\xc1\xd7\xbb \x22U\
+7\xdc7\xa2\xb8N\x88,V>\xccV\xdb:q\x04\
+,\x16k,\xfc\xce\xe7'\x10\x916\x93\x95?F}\
+\xa5\xfe\x12\xc4o\xf4Y1\xb6\x02~\xef Z{\x9c\
+\xe0?0\xa1L(CF\x0e\x1b\xb2\x0e\xf9&\xd2\xf9\
+\xc5e\xcc-,!4\xbf\x88\xbd{\xf7Z\xc9;~\
+\xbam\x02$~C\x90F=5\x13iu\xb3\x80\xd2\
+?\x0f\xcb\xc4\xe2\x9aP\xa1Z\xb4l\xf1Y\xa0\xb6\xa0\
+\xa6]\x8d/\xb2sq\xb7\x9e\xff\x0c1%\x9d\x09\xcd\
+cbj\x06\x83C\x81'\xe4\xdd\xbc-\xd3\xb0;\x92\
+\x033&\xd4S\xb5\xd3\xfbXO\x88\xc5\x03!\x88,\
+CP\xbaF\xd0\xed\x09B\xe5\x9bB\x9bs\xfc\xa9\xcf\
+Z\x1b\xee*t\xc8\xbc\xc9E\x09\xa7l\x93\xcf\x9b\x88\
+'\xa7\x11\x18\x1d\xc3\x80o\x08\xa2\xd6\xd6%\xc2Q\xdb\
+(\x12\x87\xc6\x1f\xaf\x82/b\x94M\x89$\x90\x22\xea\
+R-\x9aB\xab\xe8\x18y\x04\xa1\xc5\xcf\x10St\xf6\
+\x0d\xa3\xd3\xe1\x87\xd4<\x80\x16\xbd\x03\x0d]\x06\x14\xd5\
+\x0a\x90\x91\x95\x0d/y\xf1\xc6\xaa\xa9\xd4\xb3s\x0bL\
+\xc5\x94\xd8\xdd\xef\x85\xc9b\x05\xb7\xbc\x12\xa5\xe5\x95K\
+\x13\xf3\xcb\xab#\x0f\x017\xd9\x11\xe6\xd9\x15\x84\x97\x15\
+\x13\x06\xcb<\xd0h\xf2\xa3\xdd\xee_'\x96;\x86 \
+\xb3x\xd7}\xe6\x08\xa4\xf8<3\x1b*\x8d6\xaa\xdc\
+S3!\x8c\x8e\x8d3\x15\xd3&\xe47\x09\xf1\xc1\xc5\
+\x8fQs\xaf\x01\xbee`\xfc\x11\xa0#\x13#\xf2\xce\
+\xa1\xbe]\xb9\xb8Q\x01\x83\x81ttM\xa7\x1e\x0ag\
+\x80\xa9\xb8\xdd\xea\x83\xd8\xe8B\x93\xca\xcc\xf8|\xe5\xcb\
+,\x88\xda$Q\x89\xa7g\xe7\x18\x1b\x86\x86G`w\
+8I\x82:$|\xf8!\xae\xb3\x0b\xe1\x99\x5c\x80o\
+\x09\xd0\x90\xde\xe1\x0f,\x81\xab\x1f\xc4}\xef\x04\xdd\x07\
+\x1da\xeb\xff\x9f\xc0\x1d\xb9\x16\x1d\xf6!H\xcc\xfdO\
+}\xee\xd4\x22\x9dU\x84\xaa\x9a\xbaM>G\xe4\x8e\xf8\
+<<\x12\x84\xd3\xdd\x0f\xbd\xc1\x88\xc2\xe2b\x9c~/\
+\x1e=\x03\x01\xf4/\x02\x83\x84\xbc\xc5\xff-\xee:C\
+(Q\x91\xf7\xf6\x05\xf1N\xdc\xbf}\x843i\xe3 \
+\x18\xf43\xab\xe0\xc9Th58\xd1\xd8\xdd\x0b\x9eX\
+\x89\xac\x5c\xf63>G\xaa\x9e\x9c\x9ee\xe4\xee\xf7\x0e\
+\xa2\xd7lAC\x03\x1f'b\xe3 \xe9\xd6\xc0E\xcf\
+\x01R\x90$\xb8\x86\xb2\x9e\x00n\xb4\xdbP\xd1\x1bD\
+\x85\xce\x8bJ~\x0bm\xbe\x9b['\xd1\xa0\x99\xf8\x16\
+e\x22\x05\xee)\xf4(\x13\xc8\x90x5\x0b\x1a\xad>\
+\xaa\xdcc\x13\x93\xf0\x0d\x0d\xc3f\xef\x83\xb4]\x8e\xc4\
+K\x97\x90\xc3\xca\xc3\xd4c\xc0NzI1N\xfa\x89\
+\x94\x7f[;\x84|\x85\x13%j\x1fJ\xd5\x03\xe8\xf2\
+0\xa3(\x22\xf8\xf93\x09t\x8f.\xa1\xa8\xbe\x15\xa5\
+|\x09\xb2J*\xf0\xcf\xe3qQ\xe5\xf6\x07F\xd1\xe7\
+\xf2@\xab7 \xfdj\x06\x92\xbfH\x83\xcd7\x02'\
+\xa9\xda@\x1aL\xe0{\x88R\x9d\x1fE\xdd\xfd\x0cq\
+A\x97\x1b\xc5\xdd\x1e\x88\x9cA\xfc\xf9\xcd\xb7]\x84\xeb\
+l\xb4C\xd0(\xf7N#\xa7\xfc\x1e\xb2K\xab\xf1Q\
+\xeaWH\xfeo\xea\xfaXQ\xb9G\x82\xe3\xf0\x0c\xf8\
+`4\x99Q\xc9\xab\xc2\xfbg\xcfA\xfe@\x03?\xe9\
+n\xb2\x8d\x19\xb9oi\x06\x19\xd2\x9b*/r\xe5\x0e\
+\xe4u\xf6\xa1\xf0\xbe\x1b\x1c\x95\x1b\xf9\x9c\xca)\xc2S\
+\xb8\xdd)\xdc+v\x04\x90Q\xc8\xc5\x95ky8\x11\
+\x9f\x80\x9b\xb7n3c\x15\x91\xdbjs@\x22m\xc7\
+\x85\x84\x0fPt\xbb\x0c\xf3+\x80\x9f4X\xf7$ \
+\x1c|\x84J\xd3\x188\xfaa\x86\x9cV\xfdU\xb3\x1e\
+\xac\x0e;\xb8:\x1f\xd9!\x1ez/\xe0\x13\xbc\xba]\
+\x02&\xbe\xc1\x83\x94o\xd88\x9f\x9c\x8a\x03\x7f=\x04\
+c\xaf\x99\xe9n*\xb7F\xd7\x83\xa4\xcb\xc9H\xff:\
+\x8b\x8c\xd5<S\xb5q\xf6\xa9\xdc5\xf6i\x5c\x97Y\
+\x19\xd9\xbfn!\xa7\xa0\xd4\x82t\xbe\x1aW\x9b4`\
+\xc9\xcc\x10\xbb\x82\xf8\xe5\xaf_\xa7g\xc0;\xe1u\x1f\
+5\xcc5\xddf|\x94\x96\x85\xb8s\x17\xf1\x97C1\
+L\xd5t\x99\xf0\xaa\xaaq\xfa\xf4\x19h\xcc\x0e\x8c\x92\
+-6\x14\x1e\xabZ\xc7\x0cx\xe6qp\x0d#L\xa3\
+e\x8a\x0c\x8c\xec\xb4\xfa\x9c\xb6^\x94t9\xd0f\xf7\
+\xaf\x1e=\x11KG.o\xc3y\x135,\x5c\x99\x1a\
+\xf1\x97>\xc7\xd1\xd83\xf881\x09\x86^\x13\x1a\x9b\
+\x04\xf8\xdd\x1b\xfbQO\xd4\xf1\x90\x99\xee\x9a\x00\xaa\xad\
+\x93`+]\x0c9\xf5\xbc\xf0\xbeg\xbd\xea\xcc\x16=\
+JU\x1e\x08m\x01\x94\xd4\xf1C\xe1eS@\xf0\xca\
+\xf7%`+nj\xc7\xa9\x84D\xc4\x1c9\x8a\xdc|\
+6ZZ\xc58\x14\x13\x83/95\xc8\x14j\x98\xe6\
+\xa2\xd5\xd2'\xf5\x9azL\x13\xa1Id\xb7\x99\x90\xdb\
+nF\xb9\xda\x8d\x06\xa5v9,9=\xf9N\x13\xec\
+\xd9r\xd4G\x0d;\xabF\x88c\xff9\x8f\xdf\xee\xfb\
+=\x1a\xf9\x02\x9c\xbf\x90\x80\x93\xf1\x17p\xa3\xad\x07\x19\
+\xc4OJ\x14\xe9n\xbaX\xa8\xef,\xfa\x94\x98P(\
+\xb7@\xe9\x0e<\xf9W\xec)*w-\xc1g\x04\xfb\
+\xb6\xb9\xe4D\x8d\xbe\xcc\xb2Z\xfc\xe3\xe4\x19\x1c<\xf4\
+7\xb0r\xf3\xb0\xef\xc0\x1fP \xd1!\x89'e*\
+\xa6K\x85>\xbf!\xd5F\xe4.\x90[!\xb0\x0c\xae\
+\xe5\xdc\xe2\xd2\x11\x13\x13\xe4\x87o<\xaf<\xe7\x96\x15\
+5\x9ciE\xe5\xf8\xfb\xb1X\x1c?\x19\x877\xf6\xef\
+\xc7\x8d:\x11\x92\xab\xa4\x0c!\xedp\xea5U!\x8b\
+4[\xc9\x037*4n\xd4I:\x17\xc3rs\x08\
+\x8em\x95\xfb\x87$\xe0Jesp\xe4\xf8)\x1c>\
+|\x98\x8cc.2\x05*\x5c\x22\xd5\xd3]~M\xdc\
+\x0b6\xe9tv\xa7\x1dw\x8c\xe4\x88\xb6\xf9\x9e\x84\xb7\
+\x1a\x95\xfb\x22\xbdI\xfd\x80\x0bm\xf4\x042JxL\
+\x0f\x9cKI\xc3\xb5\xa6.|\xc2me6Y\xf1\x83\
+\x01\x5c\x97\x9a\xc1Q{ \xf3\x04\xd7\xce%&\x056\
+\xc8\xfd\xc7\x9d\xc8\x1d\xd5\x82\xdc\x1a\x01\xce^NE\x81\
+X\x85x\xf6]\x5c\xa9U\x90\xaa\xfb\xc0\x96\xdbP\xad\
+u\xe3\xaeTA/\x10\xca\x0dr\xbf\xba\xd3j\xa3\x05\
+\xb7\xa2Q\xf8\x1d\xafC\x8dO\xb9-\x88\xcb\xe6\xe1\x9a\
+H\x8f\xaa\x1e/\x9a5\xe6\xc7\x7fz\xf3-Wx\xac\
+\xa8\xdc\xaf\xbd\xac\xdc\xd1\xe2\x08\xdd\x05\x5cu\x1f\xde\xcb\
+\xafE\xb9v\x002g`\xf5\xc2\xa7\x97\xa9\xdc\xf7\x08\
+\xd2\xa9\xdc;\xf8\x03\xf3\xc2\xf1\x13\x82\xca\x1c\xee\x9dP\
+\x0b9\x94\xb8\x0d\xc2\xc8\x16\xa3\x17\x87\xc3/\x22\xf7\x0e\
+\xff\xdam\x8a\xdda\x99\xd5\x1b\xb6\xd8k\xbb^2\xbe\
+/\x89\xff\x01f\xb9_\xfc\x11\x80=\xcf\x00\x00\x00\x00\
+IEND\xaeB`\x82\
+\x00\x00\x09\x08\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x07\xd3IDATX\xc3\
+\xbd\x99Ol\x1cW\x1d\xc7?\xef\xcd\xec\xec\x1f\xaf\xbd\
+v\x9c\xc4n\x12\xb7n\x1dg\xbdN\x9c\x16\xb7\xa5!\
+9\x00\x95(\xff\x0e\x15\x08h/\x15E\x02\x89\xaa\x1c\
+\xe1\xc0\x01!\x0e\x5cz\xe2\xc8\x81\x03\xe2R\xa9\xaa\x80\
+\x22\x04\xad\x90\xf8s)T\xfd\x13\xb5\xa9\xd2\xd6N\xeb\
+\x06\x928\x7f\xbc\xde\xf5\xfe\xf1xf\xde\x1f\x0e\xeb]\
+\xef\x8ew\xecMD\xf9I\xa3\x99y\xde}\xf3\x99\xef\
+\xef\xfb{\xef\xed\xb3\x08\x82\xc0\xd6j5\xb4\xd6\xfc\xbf\
+\xc2u]FGGq\x1cg\xff\xcf\xd6j5.^\
+\xbcH&\x93AJ\xf9\x89\xc3i\xad\x89\xa2\x88\xc5\xc5\
+Er\xb9\xdc\xfe\x80Zk2\x99\x0c\xf3\xf3\xf3\x18c\
+A\x80h\xffU\xb4\xae\x12\xef\x93\xda\x12B\x08A\xa4\
+\x14\xef^\xb8\x801f0\xb5\x01\xa4\x94\x18k\x09\xa3\
+\xb0\xf5 !\x10\xed\x07\x0b\xb1\xd3\x16;w\xc0\xf6\x00\
+\xefi\xb2\x16\xb9\xdd\xb7\x10\x83\xbc\xd26`\xf7\x1b~\
+\xa2\x80\x80\xb5\x960\x0ci4\x1a\xb0}\xdf/\xa4\x94\
+\xa4\xd3\xe9\x1d@\xb1\x0d\xd3\x0f\xf4\x7f\x0d\xb8\xba\xba\x8a\
+R*\xd1\x83\xd6Z\x8c1\x94J\xa5.\x05\x93\xa0\x92\
+\x00\xbb^\xa8\xbb\xad\xa3\x80\x90\x08!0\xd6b\xb1\x1d\
+@)%\x93\x93\x93\xcc\xcd\xcd\x91\xcf\xe7\xb1\x09\x80\xef\
+]\xbc\x88R\xaaWA\xba\xfc!\x92\xa0cJ\xf7\x03\
+\x94B\xf2\xd1\xfa\x87\x5c\xae\xac\xf0\xe0\xb1\x87)dF\
+1v\xa7(\x84\x10\x08)w\xfa\x89\xbf\x9c\xe3t\x86\
+\xa0^\x05\xf7Q/\x0e\x96\x08(\x1d\xfeu\xf9U^\
+\xfb\xf7\xab\x9c\xbf\xf2:_\x9d\x7f\x9c\xb9\x89\x93=\xa9\
+\x17}\xbe\xd7/:\x03_\x1b\xa0s\x8e_\xdf\xc6!\
+\x85@\x19E\xa4\x03\xaa\xe1M\x9e\xfb\xeb\xcf\xf9\xcb\xfb\
+\x7fF\x19\x85\x14\xb2\xf3\xf2r\x8f>\xda\xd1[\xc5m\
+Ub\x80\x0c\xaadWG\xc6X<\xd7\xe5\xa9\xb3_\
+\xe0o\xef\xbd\xc1\xaf\xdf\xf8\x15KkK|c\xe1\x09\
+\x9cm\x7f\x0e2\xd4\xecJq\x12\xdc\xae\xeb>`\xdd\
+\xed\xc6B\x10\x85\x04a\x83s\xc7g94\x9c\xe7\xe5\
+\x0b\xffd\xe9\xfa\x12gF\xce1/\xe7\x91B\xb6\xbc\
+\xd9\xd5O\xdc\xe7=)f\xbf\xd4\xc6\xda{L\x1f\x83\
+\xb5\xc6\x12\xaa\x88\xf5\xc6\x1a\xeb\x8d\x9bL\x0c\xa7\xf8\xda\
+\xe2\xa7\x18\x1a\xf2y\xe9\xca\x8b\xbc\xb2\xfc\x0a\x81\x0ep\
+\x1c\xa7\xc7\x1a\xf1~\xfa\x0e\xd4\xa2[\xcd\x18@\x1c\xac\
+_\x08!0\x06\xa2(\xa4\xbe\xb5\x06\x22\xd8\x06\x80\xb3\
+3wq(\x9f\xe2\xb7\xef\xfe\x86Kk\xcb|\xfb\x91\
+\xa79:z\xb4\xa7\xca\x93Sl\xed@p\xfd\xc0\xe2\
+mJ\x19\xc2 \xc0\x8f\xca\x18|\x1c\xb9\x93\xc0\xa9\x03\
+\x82\xa1\xf4\x01^\xff\xf0\x1f\xfc\xf4\x0f\x97\xf8\xce\xd9\xef\
+qf\xe6\x11$rg\xcc\xec\x97\xe26\x18\x09iN\
+\xf2]\xbf\x22\xd1\x91e\xd3\xaf\xd0\x0cW\x09\xcd:\x91\
+\xad\x10\xd92\xa1Y\xc3\xd77\xf1R\x1b<4\x93a\
+$\xbf\xc2/\xff\xfe\x1c/\xbc\xf6\x22\x91\x89\xf6\xae\xe2\
+\x0e\xd8\x1e^\xdb+\xbd\xddmF[\xea\x9bW\xb9R\
+[&\x9b\xce\x90\xf5<\x1c\xc7\x05\xeb\x10)K\x10j\
+\xb6\x02M:mI\xbb\x967?z\x8b\xaf\xdc\xff%\
+\xbc\x9c\xd73?'\xa68\xa9B\x93T\x8c\x87V\x16\
+a4\xb9l@\xca\x0bpR;\xde\xd4\xc6b\xa4e\
+\xbd\xe6p\xe1\x83\x83<p\xec\x0c?x\xecY\x0a\xb9\
+\xc2\xae\xfevMu\xf4\x01\xb9\x13\x15Md@k\xf2\
+9A&#H\xa7\x05R\x82\xd6\xb0\xd9\xb4\xbc\xb9\xe4\
+\xf2\xfe\xd2}<q\xf6\xfb<\xf5\xb9'\x19\xce\xe4w\
+M\x87\xbbS\x1cK\xef\xed\xc0\xc5\xef\xb56\x08\xad\xc9\
+d\x04CC\x82|N\xe0:p\xf5\xaa\xe5O\xafd\
+P\xcd\xcf\xf0\xb3o\xfe\x88\xcf.\x9cC \xb0\xd8N\
+\x1f\xfdS\xdc\xc7w\x83\xc0%\x0d5V\x83\xd5\x06G\
+\x80\x97\x12\xe4\xd2\xf0\xe6[\xf0\xc2\xefG\x98\x1ey\x94\
+\xe7\xbe\xfb\x13\x8aw\xcfb\x8c\xe9Tn\xbf\xe8\xf1`\
+R\xdc\x89\x8aF\x19Ld\xd0\xdaR\xab\x18^\xfa\x9d\
+\xe0\xed\xb7\xef\xe3\xe9\xc7\x9e\xe1\x81\xc3E\x8e\x1d\xb8\xab\
+\xefb5\xb1\x8a\xf7J\xed\xa0\xa0\xd6Z\xac\xb5H!\
+\xd1\x91\x02m\xb8\xb2b\xf8\xe3\xcbi\xf2\xceY~\xf1\
+\xcc\x8fy\xe8\xc4\x22\x17\xdey\x07cm\xa2\xfa\xdd}\
+\xba\xec\x13\x83\x82\xb6W\xc1Zk\xb0\xadY\xe4\xca\x7f\
+<\x9e\x7f>\xc7\xd9\xf9\xc7\xf9\xe1\x93\xcfr\xf7\xc4\x14\
+~\x10\xa2\x8d\x19\xe8G\xd6.\xc0\xdb\x99)\xba\xdb\x8c\
+1\x18cPJ\x11\x86!Z)F\xb3#\x1c\xcc\x9e\
+\xe0[\x9f\xff:_\xfe\xf4\x17I\x19\x97[\xe52~\
+\xa8p\x5c\x077\x95\x1a\xe8Y{*8\x08h[\xb9\
+(\x8a\x08\x82\x80f\xb3I\xbd\xde\xe0\x5c\xf1a\xe6&\
+\x8e3\x9c\x19\xa2\xba^%\xcaG\x84Jc\xb4\xa68\
+{\x9ct:\xdd\xb1D\xbc\x7fkm\xa7&\xf6M\xf1\
+^\xa0m8\xa5\x14A\x10P\xab\xd5)\x97\xd7X+\
+\x97\xf1}\x1f)$\x9e\xe7166Juc\x03\xa5\
+5'K%\xc6\xc7\xc7[\xd5\xbb\xfd\xfd\xf6\x8b\xb6j\
+\x00\x8c\xdd\x19j\xdc8\xc4 \xc6\xedV\xaf\xbdS\xd0\
+l6\xa9T+\x5c\xbfq\x83Ba\x84{\xa7\xefe\
+\xa40\x02XVVV\xd0Zsj~\x9e\xb1\xb11\
+\x94Rh\xad;G\x1bV\x08\xd1\xda\xdd\x90\x0e\xda\xc4\
+\x00\x07Q-\xae^\x1b0\x08\x02\xea\x8d\x06\xd5\xea\x06\
+\xbe\xef\xf3\xe0\xe2\x22\x87\x0e\x1d\xa2Z\xad\xb0\xb4\xf4\x01\
+a\xa88u\xf2$\x85B\x01\xa5\x14Q\x14\x11\x86!\
+A\x10\x10\x04!JE\x18c\x10B\xe2y)\x8ct\
+\x09\xc3\x10\xd7u\xef\xac\x8a\xdb\xd1N\xefV\x10\xd0l\
+4\xa9\xd7\xea8\x8eK6\x9b\xa5Ri\xc1EJ\xb1\
+\xb0\xb0\xc0\xf0\xf0p\xa7\x88|\xdf\xa7\xd1h\xd0h4\
+\xf1}\x9f \x08\xb0Xr\xd9\x1c\xc2ui4\x9a\x1c\
+;2\xc9\xe8\xe8\xe8`\x1e\xec\x17m\x05\x8di\xad\xfb\
+\xb6\xb6\xb6\xf0\xb7|\x8e\x1e9B\x18\x86,_ZB\
+i\xcd\xfd\xa7\xef'\x9f\xcfw\xe0\x1a\x8d&\x95J\x85\
+r\xb9L\xbd^'\x8c\x22\xb2\xd9,\x87\x0e\x8ec\xac\
+a\xbd\x5cf\xe6\xdei\xa6\xa7\xa7q\x1cgp\xc0~\
+Jv\x17I\x14E\xa4\xdc\x14\x07\x0e\x8c\xf1\xe1G\x97\
+\xb0\x96\x1e\xb8(\x8a\xf0}\x9f\x8d\x8d*\xd7o\x5cG\
++\xcd\xc4\xc4\x04cc\xa3\xe4\xf3yn\xad\xdd\xe2\xea\
+\xb5k\xcc\xce\x1c\xe7\x9e{\xee\xe9\xec\xb4\xdd\xb1\x82\xdd\
+*Z\xdb\x9aM\x0f\x1c\x1c\xa7\xbaQ\xc5\x91\x92\x85S\
+\x0b\xe4\xf3\xf9N\x11DQD\xa3\xd1\xa4Z\xdd\xa0Z\
+\xdd`\xe1\xd4Ifg\x8f\xe3\xfb>+\x1f\xafp\xed\
+\xda5\x8a\xb3'\x98\x9a\x9a\xea\xd9\x06\xec\xbbX\x88+\
+\x96\xd4&\xa5DH\x89t\x5cr\x851B\xbf\x89\x97\
+\xf2(\x16\x8b\x0c\x0d\x0du\xe0\xdb\xc3P\xb3\xd9\xa4V\
+\xaf\xa3\x94bdd\x84\xcdM\x9f\x8f\xdbp'\x8aL\
+MM\xf5\xff\xd1\xa4\xb5fkk\xab\xef\x8eg\xbbb\
+\x95R\xb8\xae\xbb3{XK\xa44[\xa1\xc28\x1e\
+aX!\x9f\xcbR<q\xa2gS\xa8\xe3\xd3\xed\xe2\
+\xd8\xdc\xdcd8\x9f\xc7K{||y\x85\xd5\xd5U\
+\xe6\xe6J\x1c=z\xb4\xaf\x8d\x5c\xcf\xf3p\x1c\x87\xe5\
+\xe5\xe5\xc4\x01\xd9\xf7}._\xbe\xcc\xf4\xf44\xae\xdb\
+-zk\x1dg\xacelh\x88\x99\x99\xfb\xc8d2\
+\x9d1\xad\xbb\x90\xa2m\x1f\x1a\xad\x99\x9c\x9c\xe0\xfa\xea\
+*7n\xdc\xa4T\x9a\xe7\xc8\x91#\x896r\x0b\x85\
+\x02\xa7O\x9fN\xdc\xa7\x13B\xd0h4\x88\xa2\x88R\
+\xa9D*6\x87\xb6\xcf\xa9T*q\xcf\xd9Z\x8b5\
+\x16m\x0c#\x85\x02\x16\xcb\xda\xda\x1a\xa5\xb9\x12\x93\x93\
+\x93{\xfa\xdc\x95\xb25\x1d\xed\x15\xe9t\x1a\xcf\xf3:\
+\xe7\xb87\xf7\xf2l\xeb\x90H)H\xe7\x86p\xbc4\
+\xbe\xefS,\xceq\xf8\xf0a\xf6\x8b\x81\xab\xd8\x18C\
+\x10\x04\x1dE\xe2Pq\xd5\xa1\xe5S\xa54\x81Rh\
+\xc7\xc3:)D\x18P,\x16\x19\x1f?8\xd0s\x07\
+^,(\xa58\x7f\xfe\xfcm\xfd'\xa0\xe5A\xd0\xb6\
+\x95\xdeL:\xcd\xc9\xd2\xdc\xc0p\x00\xff\x05\x08\xf9\xe8\
+\xd3M\x9b\x8d\x0f\x00\x00\x00%tEXtdat\
+e:create\x002023-03\
+-08T17:27:26+00:\
+00\x9e\x98\xa5\x85\x00\x00\x00%tEXtda\
+te:modify\x002023-0\
+3-08T17:27:26+00\
+:00\xef\xc5\x1d9\x00\x00\x00(tEXtd\
+ate:timestamp\x0020\
+23-03-09T06:18:4\
+1+00:00=\x00\x9f\x01\x00\x00\x00\x00I\
+END\xaeB`\x82\
+\x00\x00\x03\xb8\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xad\
+PLTE\xff\xff\xff\xfe\xfe\xfe\xf6\xf6\xf6\xdc\xde\xe0\
+\xca\xcd\xcf\xc6\xc8\xcb\xce\xcf\xd1\xe5\xe6\xe6\xfc\xfc\xfc\xe7\
+\xe8\xe9\xbc\xc2\xca\xc0\xc8\xd3\xd4\xdb\xe4\xd9\xe0\xe8\xc8\xcf\
+\xd9\xae\xb5\xbf\xc2\xc5\xc7\xf6\xf6\xf5\xea\xeb\xeb\xb7\xbf\xcb\
+\xce\xd9\xe9\xe6\xef\xfb\xda\xe4\xee\xc0\xca\xd6\xe2\xeb\xf6\xdc\
+\xe7\xf4\xaf\xb9\xc7\xbe\xc1\xc3\xfb\xfb\xfb\xfb\xfb\xfa\xbf\xc5\
+\xcc\xbb\xcb\xe0\xdd\xeb\xfa\xea\xf6\xff\xc5\xd2\xdd\x82\x93\xa6\
+\xdb\xe6\xef\xe7\xf3\xfe\xd3\xe1\xf2\xa8\xb2\xbf\xe8\xe8\xe8\xee\
+\xee\xed\xa1\xaf\xc4\xbc\xd2\xee\xcc\xdb\xe8\xc1\xce\xd6\xb0\xbd\
+\xc8\x8f\xa1\xb5\xbb\xc7\xd0\xc1\xce\xd7\xd8\xe4\xf1\xae\xbe\xd4\
+\xcf\xd1\xd4\xe4\xe4\xe3\x98\xab\xc4\xbb\xd5\xf3\xa7\xb9\xc7{\
+\x8c\x9e\x8b\x9e\xb2\x92\xa6\xbc\x89\x9b\xafz\x8b\x9b\xc0\xd1\
+\xdf\xa7\xbc\xd8\xad\xb2\xb6\xff\xfe\xfe\xe7\xe7\xe6\x9c\xac\xc2\
+\xbd\xd7\xf2\xda\xec\xf3\xe1\xee\xf3\xc7\xd4\xde\x95\xa7\xba\xd8\
+\xe4\xea\xdf\xec\xf1\xd9\xeb\xf7\x9f\xb5\xce\xab\xae\xb0\xbe\xc5\
+\xcf\xc2\xd7\xef\xe5\xf6\xfd\xf7\xff\xff\xd5\xde\xe4\x9b\xaa\xbc\
+\xe9\xf0\xf2\xf6\xff\xff\xdc\xef\xfb\x91\xa0\xb2\xc9\xca\xc9\xdb\
+\xdc\xdd\xa3\xb4\xc8\xce\xe4\xf6\xf2\xfe\xff\xf0\xf7\xf9\xe5\xec\
+\xf0\xf7\xfc\xfd\xf1\xfc\xff\xba\xcb\xdc\x98\x9e\xa2\xf4\xf3\xf3\
+\xf9\xf9\xf8\xaf\xb3\xb5\x90\xa0\xb1\xc5\xd8\xe7\xe8\xf5\xfb\xf2\
+\xfa\xfe\xe4\xef\xf6\xb8\xc5\xd1\x94\x9a\x9f\xe2\xe2\xe1\xf9\xf9\
+\xf9\xc4\xc5\xc4\x96\x9d\xa1\x96\xa0\xa8\x9d\xa6\xad\x9d\xa4\xa8\
+\xa2\xa4\xa5\xb7\xb8\xb6\xde\xdf\xde\xf8\xf8\xf7\xe9\xe9\xe8\xe6\
+\xe6\xe5\xf3\xf3\xf2\xe5\xe5\xe4\xd3\xd3\xd3\xaa\xab\xa9\xed\xed\
+\xed\xcc\xcc\xcc\xc5\xc5\xc4\xb6\xb7\xb6\xd5\xd5\xd5\xd2\xd2\xd1\
+\xad\xae\xac\xd9\xda\xd9\xf5\xf5\xf5\xc9\xc9\xc9\xca\xcb\xca\xb3\
+\xb4\xb3\xca\xca\xca\xaf\xb0\xae\xd5\xd6\xd4\xfa\xfa\xfa\xfd\xfd\
+\xfdV$\xaa\xcb\x00\x00\x00\x01bKGD\x00\x88\x05\
+\x1dH\x00\x00\x00\x09pHYs\x00\x00\x00`\x00\x00\
+\x00`\x00\xf0kB\xcf\x00\x00\x00\x07tIME\x07\
+\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\x00\xcfID\
+AT\x18\xd3c`\x80\x01F\x06t\xc0\xc4\xcc\xc2\xca\
+\xc6\xce\x81\x22\xc6\xc9\xc5\xcd\xc3\xcb\xc7/ \x88$&\
+$,\x22*&.!)%-\x03\x17\x93\x95\x93W\
+PTRVQUS\xd7\x80\x0bjji\xeb\xe8\xea\
+\xe9\x1b\x18\x1a\x19\x9b\xc0\x05M\xcd\xcc-,\xad\xacm\
+l\xed\xec\x1d\x1ca\x82N\xce.\xaen\xee\x1e\x9e^\
+\xde>\xbe\x08\xf7\xf8\xf9\x07\x04\x06\x05\x87\x84\x86\x85G\
+ l\x8f\x8c\x8a\x8e\x89\x8d\x8bOHLJF\x08\xa6\
+\xa4\xa6\xa5gdfe\xe7\xe4\x22;>/\xbf\xa0\xb0\
+\xa8\xb8\xa4\xb4\x0c\xd5\xaf\xe5\x15\x95U\xd55\xb5N\x18\
+\x01PW\xdf\xd0\xc8\x81!\xda\xd4\xdc\xd2\x8a!\xd8\xd6\
+\xde\xd1\xc2\x84!ZV\xdf\xd9\x81!(\xd3\xd5\xd1\x8d\
+\xa9\xb4\xae\xb5\xa7\x17C\x90\xa1\x0f\xe4R\x00|(+\
+\xa4\xc7\xdcu\xa2\x00\x00\x00%tEXtdat\
+e:create\x002023-03\
+-08T17:27:26+00:\
+00\x9e\x98\xa5\x85\x00\x00\x00%tEXtda\
+te:modify\x002023-0\
+3-08T17:27:26+00\
+:00\xef\xc5\x1d9\x00\x00\x00(tEXtd\
+ate:timestamp\x0020\
+23-03-09T06:20:5\
+0+00:00\xdd\xe8\xb2\xde\x00\x00\x00\x00I\
+END\xaeB`\x82\
+\x00\x00\x07\xd5\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x06\xa0IDATX\xc3\
+\xcd\xd7MS\x1b\xc9\x19\xc0\xf1\x7f\xf7\xf4h$@X\
+H \xf4\x8a\x84\xd76k\x1b\xec\xc2U\xd9\x8d\x9dK\
+\xb6\xb2\xc9e\xbd\xf9\x00I>b*9$\x97\xad=\
+\xe4`o\xccb\x1b[^\x8c\xd7\xe6\x1d\x1bK\xe8\x05\
+\x04BB3\xa3\x99\xceA\xc0:\x87\xf0\xb2\x0b\xc1O\
+\xd5\x94\x0e#\xcd\xfc\xf4t?\xddO\x0b\xad\xb5\xe6#\
+\x0eu\x16\x0f\xf9\xf0?\x0a!.\x1e\xa8\xb5\xc6\xf3:\
+\xb8\xae\x8b\xe3\xb88\xae\x83\xefy \x04\xa6R\x98f\
+\x00\xcb\xb2\x08\x04\x02\xff\x7f\xa0\xeb:4\x9bM\xaa\xb5\
+MV\xd6\xd6)\x96*\xec\xee\xee\xe2{.\xd2P\xf4\
+\xf6\x84\x18\x1a\x8a\x92N\x0e3\x18\x8b\x11\x8b\x0dbY\
+\xd6\xcf\x06\x8a\x93\xceA\xad5\xcd\xe6.\x95J\x95\xc2\
+\xcbyV\xd7\xab\xc4b1r#i\x06\x22\x970\x95\
+\xc2vl\xca\x95\x1aK\xcb\xab\xd4\xb7j\x8c\xa4b\x5c\
+\xfd$\xc7h~\x94h4z~@\xad5\xf5z\x9d\
+\xd5\xb7\xef\x98.,\x10\xbe\x14c\xf2\xf6M\xa2\x91~\
+\xa4\xfc\xef9'\x84\xa0\xd3\xf1X/\x96\x99\x9a~\x8a\
+\xd3\xda\xe4\xe6\xd5\x0c\x13\x13\x13$\x86\x13\xe7\x03\xaco\
+\xd7YZ^\xe5\xc9\xec\x1a\x97\xaf\x5c\xe3\xda'y\x0c\
+C \x04H!@\x80\x10\x80\x06\x0dh_\xa3\x11\xb4\
+m\x97'3\x05\xd6\x96\xe6\xf8tt\x98{w\xef\x12\
+\x8d\xc6N\x05\x94\xc7}\xa1\xddn\xb3\xfen\x9d\x17\xaf\
+\xd7\xc9]\x1e#\x9f\xcf\xe1i\x8d\xdfM\x17\x08\xd1\xad\
+\xdc\xae\x16\x84@\x0b\x81\xaf5\xd2\x90LN\xde&3\
+z\x937\xcbE\x0a\x85\x02\xae\xeb\x9e-\xb0R)3\
+\xbfRD\x04c$\xd3)<_\x83\x10\xb8n\x07\x04\
+HC \x95\xc00~\xba\x10\x9a\x8e\xe7\x81\x90h$\
+c\xd7\xafc\xf5'y\xbfQ\xa1T*\x9e\x1d\xd0\xb6\
+mJ\x1b\x1b\xacUl\xe2\xc94 \x10R\xd2h\xee\
+\xf1\xed\xbf\x1e`\xb7[(S\xa2\x0c\x81R\x02C\x09\
+\x94\x92\xbc\x98\xfd\x91\x17\xb3s\x98\xa6\x814\x04A+\
+@&w\x95V\xc7d{g\x1b\xcf\xf3\xce\x06\xd8h\
+4(W\xeb4\xfd^d \x84\x16\x02aHv\x9b\
+\xbbt00\x03\x0a\xa5\xc04%JILC\xe0\xeb\
+\x0e[\x8d\x16\x81`\x90\x80)\x08\x05$\xc1\x80dp\
+h\x90V'\x88\xe3th\xb5\x9ag\x05\xdcasg\
+\x0fi\x85\xd9\xb55v\x07\x100\x92\x1eb4\x9fE\
+)\x03%\x05\xa5R\x99\xcd\xda\x16J\x0az-\x93\xf1\
+\xb1\xcb\xf4_\xea\xc7T\x82\x80)1\x95D\x18&\xae\
+\x08\xb2\xb2\xf6\x8eF\xa3q6\xc0f\xab\xc9\x9e\xed\x11\
+\xb0\x82\x08)\xb1}\xe8\x00\x9e\x14h\x0ek\x84b\xa9\
+J\xb5V?\xa8\x11\xf4\xfe\xa70\x04\x1e\xb0\xd7\xd1\xb8\
+Z \xcd\x10K+\xabloo\x9f\x18x\xe4N\xe2\
+:.\x1d\xcfG)\x852$\xa6\x12(\x83\xee\xda'\
+\xbaK\x8a\xaf\xe1\xfa\xb5<\xa6a\xe0\xe9.N\xd3\xbd\
+\x10\xe0\x0bp|\xe8\xf8 \x84\xa4\x5c.\xd3\xb6\xdbg\
+\x03\x94R\xe0{\x0e\xa0\x11\x02\x0c\xd9-\x02e\xf8\x88\
+\xfd\xf4\x0b\x01J)\xa4\x10\x88\xae\xe9\xf0\x9e\xdc\xcf\xa2\
+\xde_\xcb\xb5\xdf\xc1ul\xac\xc0\xc9\xb7\xbe#\x878\
+\x1c\x0e\xd3\x13\x10\xf8\x1d\x1b\xad\xbbo>\x1c\xc6\xfd\xe5\
+\xdd\x00~x\xf9\x86\x85\xa55\x0c\xb1\x8f\xa6\x9bB)\
+\xc0\x10`\xec\xff\xc0n\xd51\x84\xcf\xc0\xc0\xc0\xd9d\
+0\x1a\x8d\x91I\xc4x\xd7\xb4\xe95\xc1\x94\x02\xad\xc1\
+\x90\x92t<B\xc04\x10@6\x19\xc7\x0a\x98\x1cl\
+z\x03}AB\x96\xe2\xa7=J\xb3\xd7v\xd8\xae\xac\
+2\x18\x1b8\xd5nr$\xb0\xaf/L6\x93\xc6\xac\
+\xbb\xe0;h\xdf\xc2\xed\xf8\xc8\x90\xe2j~\x18\xb9\x9f\
+\xc9L2\xcea\x1b\xa8a8\xd6\x8f\x87\xc6\xd5\x1a\xed\
+\x83\xe3hj\xe5\x0d\xb6K\xf3\xfc\xf6\xab?\x9c\xaa\xbb\
+9r\x88\x95R\xc4b\x83\x04hQ|\xff\x96\xf7\xd5\
+6\x8d\x96\x8f\xd3\xd1xh>\xdc\xc4\x0f\xe7\xdfA\xf1\
+\xd0\xadt\xc7\xd5\xd4\xb6Z\xac\xbc\x9a\x22\xda\x1f\xe4\xb3\
+_}~b\xdc\xb1@\x80\xc1\xc1ALCP^\x9e\
+ayq\x81\xca\x96\xc3\xf6\xae\x87\xeb\x81\xbf\x8f<\x80\
+~\x90D\x00lG\xb3\xb1i\xf3\xe6\xe5\x0c\x95\xd5\xe7\
+\xdc\xbf\xff\xf5\xa9\xdb\xaec\x1bV)%\xe3\xe3\xb7x\
+3?\xcf\xcc\xd3\x7f\xe0\xb96\x82[\x08\xd9C,l\
+\x10\xfc\xe0\x09\x02\xf0\x01\x0f\xd8\xb55oK-\x0aO\
+\xa7\x99\x9b\xfa'\xbf\xfb\xe2\x0b>\xff\xec\xd7\xa7\xc2\xc1\
+)\x1a\xd6\x9d\x9d\x1d\xfe\xf6\xf7\xbf\xf2\xfd\xd3\x02C\xf9\
+;\xdc\xb8s\x8f\xcb\xf94\xf1h\x90\xde\xa0\xc4\x94\x1c\
+\x0e\xe9\xd6N\x9b\xd5\xb5\xf7\xcc>\xf9\x8e\xc5\x97S\xfc\
+\xfe\xcb/\xf9\xfa\xfe\x1fQ\xea\xf4'\x8c\x13\x03\xa1\xdb\
+<<|\xf8\x80o\xbe\xfd\x86\xcd\x9d6C\x99k\xa4\
+sW\x18\x8c\x0f\xd3\x13\xb4p\x1d\x9b\xfaf\x85\xd2\xdb\
+E\xca\xeb\x8b\x84,\x83\x5c.\xcf\xbd\xbb\xbfab|\
+\x02q\xd0\x9a\x9d\x17\xf0 \xaa\xd5*\xcf\x9e\xcf\xf0\xe2\
+E\x81\x8d\x8d2{\xb6\x0dt\x8b\xaa\xb7'D&\x9d\
+fb\xfc\x16Z\x83\x15\xb4(\x95\x8a8\x8e\xc3\xf8\xcd\
+q\xc6\xc6>=\x7f\xe0Ax\x9eG\xa3\xd1\xa0\xd5j\
+\xe1y\x1e\xa6\xa9\xe8\xed\xed\xa3\xaf\xaf\x0f!\x04\xcb+\
+\xcb\x18\xd2\xa0\xb6Y\xe3\xc9\x93i\x84\x90\xfc\xf9O\x7f\
+!\x14\x0a\x9d\xf8\x1d\xc7V\xf1Qa\x18\x06\x91H\x84\
+T*E6\x9b%\x91H\x12\x0e\x87\xf7\xcf%\x1d^\
+\xbf~M\xab\xd5\x22\x9b\xc9\x90\xcb\xe5\xa9\xd7\xb7x\xf6\
+l\xe6T\xef\xf8E\xc0\xa3B)E6\x93evv\
+\x16)\x0d\xb2\xd9\x11\x92\xc9\x14\x8f\x9f>\xa6V\xab]\
+<\x10`ll\x0c\xcb\xb2x3?\xcfp|\x98l\
+6\x0bZ\xf3h\xea\xdf\x9ctf\x9d+P)\xc5\xe4\
+\xe4$\xcbKK\xb4\xf6Z\xa4\xd3\x192\x99,\xaf~\
+|\xc5\xca\xea\xca\xc5\x03\x012\x99\x0c\x89D\x82\xb9\xb9\
+9\x22\x97\x22d2\x19\xc2}a\x1e~\xf7\xe0D'\
+\xbcs\x07\x02LNNR\xab\xd5\xa8V\xab$\x93\xdd\
+\x82*\x16\x8b\xcc\xbe\xfc\xe1\xe3\x00F\x22\x11\xc6\xae\x8d\
+1\xf7j\x0e\xcb\xb2H\xa5\xd2\xc4\x87\xe2<z\xf4\x88\
+Fc\xe7\xe2\x81\x007n\xdc@\xfb\x9a\xb5\xb55\xe2\
+CCd\xb3Y\xda\xed6\xd3\x8f\xa7?\x0e`0\x18\
+\xe4\xf6\xed\xdb,.,\xe2\xf9>\x89D\x82t:M\
+\xa1\xf0\x9cb\xf1\xfd\xc5\x03\x01FGG\x89D\x22,\
+,,\x10\x89\x0c\x90J\xa5\xb0\xac S\xdfO\xa1\xb5\
+\x7f\xf1@)%w\xee\xdc\xa1T,\xd1h4\x88\x0f\
+\xc5I\xa5R\xd4jU\xf6\xf6\xf6.\x1e\x08\x10\x8f\xc7\
+\xb9r\xe5\x0a\xeb\xeb\xeb\x98\x81\x00===\x8c\x8c\xe4\
+\x08\xfc\x8f\x93\xde/j\x16~n8\x8eC\xb5V\xc5\
+\xf7}\x1a\x8d\x06\xe9T\x9a\xfe\xfe\xfe\x8f\x07x\x10\xbe\
+\xef\x1f\xdb#^(\xf0$\xf1\x1f\xb6J\xe0\xf90\x86\
+\x1f6\x00\x00\x00%tEXtdate:c\
+reate\x002023-03-08\
+T17:27:26+00:00\x9e\
+\x98\xa5\x85\x00\x00\x00%tEXtdate:\
+modify\x002023-03-0\
+8T17:27:26+00:00\
+\xef\xc5\x1d9\x00\x00\x00(tEXtdate\
+:timestamp\x002023-\
+03-09T06:18:41+0\
+0:00=\x00\x9f\x01\x00\x00\x00\x00IEND\
+\xaeB`\x82\
+\x00\x00\x03\xb2\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xa7\
+PLTE\xff\xff\xff\xfe\xfe\xfe\xf6\xf6\xf6\xdc\xde\xe0\
+\xca\xcd\xcf\xc6\xc8\xcb\xce\xcf\xd1\xe5\xe6\xe6\xfc\xfc\xfc\xe7\
+\xe8\xe9\xbc\xc2\xca\xc0\xc8\xd3\xd4\xda\xe3\xd7\xdd\xe6\xc8\xcf\
+\xd9\xae\xb5\xbf\xc2\xc5\xc7\xf6\xf6\xf5\xea\xeb\xeb\xb7\xbf\xcb\
+\xce\xd9\xe9\xe5\xef\xfa\xe9\xf2\xfc\xeb\xf4\xfd\xea\xf3\xfd\xdc\
+\xe6\xf4\xaf\xb9\xc7\xbe\xc1\xc3\xfb\xfb\xfb\xfb\xfb\xfa\xbf\xc5\
+\xcc\xbb\xcb\xe0\xdc\xea\xf9\xe6\xf2\xfc\xed\xf7\xfe\xd6\xe0\xe8\
+\xcd\xd8\xe1\xe4\xf0\xfb\xd2\xe0\xf2\xa8\xb2\xbf\xe8\xe8\xe8\xee\
+\xee\xed\xa1\xaf\xc4\xbb\xd2\xee\xde\xee\xfa\xdf\xeb\xf1\xb4\xc0\
+\xc9{\x8b\x9c\xa5\xb2\xbb\xee\xf9\xff\xe2\xee\xfb\xad\xbe\xd4\
+\xcf\xd1\xd4\xe4\xe4\xe3\x98\xab\xc4\xb9\xd4\xf2\xd0\xe2\xed\x96\
+\xa6\xb4\x86\x9a\xb0\x84\x98\xaf\xab\xb8\xc1\xf2\xfe\xff\xdf\xf0\
+\xfc\xa5\xba\xd6\xad\xb2\xb6\xff\xfe\xfe\xe7\xe7\xe6\x9c\xac\xc2\
+\xbd\xd7\xf2\xe2\xf3\xfa\xda\xe9\xee\xb9\xcb\xda\x8d\xa0\xb5\xab\
+\xb7\xc0\xf4\xff\xff\xde\xf1\xfc\x9f\xb4\xce\xab\xae\xb0\xbe\xc5\
+\xcf\xc2\xd7\xef\xe4\xf5\xfd\xf5\xff\xff\xf6\xfd\xfe\xd3\xde\xe4\
+\xcb\xd5\xda\xdc\xee\xfb\x91\xa0\xb2\xc9\xca\xc9\xdb\xdc\xdd\xa3\
+\xb4\xc8\xce\xe4\xf6\xf1\xfd\xff\xfa\xff\xff\xfe\xff\xff\xf1\xfc\
+\xff\xba\xcb\xdc\x98\x9e\xa2\xf4\xf3\xf3\xf9\xf9\xf8\xaf\xb3\xb5\
+\x90\xa0\xb1\xc5\xd8\xe7\xe8\xf4\xfb\xf0\xf9\xfd\xe4\xef\xf6\xb8\
+\xc5\xd1\x94\x9a\x9f\xe2\xe2\xe1\xf9\xf9\xf9\xc4\xc5\xc4\x96\x9d\
+\xa1\x96\xa0\xa8\x9d\xa6\xad\x9d\xa4\xa8\xa2\xa4\xa5\xb7\xb8\xb6\
+\xde\xdf\xde\xf8\xf8\xf7\xe9\xe9\xe8\xe6\xe6\xe5\xf3\xf3\xf2\xe5\
+\xe5\xe4\xd3\xd3\xd3\xaa\xab\xa9\xed\xed\xed\xcc\xcc\xcc\xc5\xc5\
+\xc4\xb6\xb7\xb6\xd5\xd5\xd5\xd2\xd2\xd1\xad\xae\xac\xd9\xda\xd9\
+\xf5\xf5\xf5\xc9\xc9\xc9\xca\xcb\xca\xb3\xb4\xb3\xca\xca\xca\xaf\
+\xb0\xae\xd5\xd6\xd4\xfa\xfa\xfa\xfd\xfd\xfd\xe1 B\xa9\x00\
+\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x09\
+pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\
+\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\
+\x1f\xc9N\xa7\x00\x00\x00\xcfIDAT\x18\xd3c`\
+\x80\x01F\x06t\xc0\xc4\xcc\xc2\xca\xc6\xce\x81\x22\xc6\xc9\
+\xc5\xcd\xc3\xcb\xc7/ \x88$&$,\x22*&.\
+!)%-\x03\x17\x93\x95\x93WPTRVQU\
+S\xd7\x80\x0bjji\xeb\xe8\xea\xe9\x1b\x18\x1a\x19\x9b\
+\xc0\x05M\xcd\xcc-,\xad\xacml\xed\xec\x1d\x1ca\
+\x82N\xce.\xaen\xee\x1e\x9e^\xde>\xbe\x08\xf7\xf8\
+\xf9\x07\x04\x06\x05\x87\x04\x86\x86\x85#l\x8f\x88\x8c\x8a\
+\x8e\x89\x8d\x89\x8bOHD\x08&%\xa7\xa4\xa6\xa5g\
+dfe#;>'7/\xbf\xa0\xb0\xa8\xb8\x04\xd5\
+\xaf\xa5e\xe5\x15\x95U\xd5N\x18\x01PS[W\xcf\
+\x81!\xda\xd0\xd8\xd4\x8c!\xd8\xd2\xda\xd6\xc4\x84!Z\
+R\xdb\xde\x86!(\xd3\xd1\xd6\x89\xa9\xb4\xa6\xb9\xab\x1b\
+C\x90\xa1\x07\xe4R\x003\x9b+(8\xcb\x02\xf6\x00\
+\x00\x00%tEXtdate:crea\
+te\x002023-03-08T17\
+:27:27+00:008\xef\xae1\
+\x00\x00\x00%tEXtdate:mod\
+ify\x002023-03-08T1\
+7:27:27+00:00I\xb2\x16\
+\x8d\x00\x00\x00(tEXtdate:ti\
+mestamp\x002023-03-\
+09T06:20:50+00:0\
+0\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\xaeB`\
+\x82\
+\x00\x00\x0bH\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x07tIME\x07\xe7\x03\x09\x07\x08)r\x19\xb0\
+!\x00\x00\x0a\x03IDATX\xc3\xed\x98Yl\x9c\
+W\x15\xc7\x7f\xdf|\xb3{\xf6\x19\xdb3\xe3\x19\xdb\xf1\
+\xbe%v\xbc$M\x9c44\xa1iJR\xdaB\x00\
+\xb54@YT\x90\x80\x0aU*\xe2\x0d\x1e\x80\x17P\
+\xc5\x03\x0fe\x17\x15B\x94\x82\xba\xd0(!K\xeb\xa4\
+i\x9b\xd5\xf1\x1a\xc7\x1e'\x1e\xef\xdb\xec\xfb\xf6}<\
+\xd8\x11iP\x13;\x0d\xf0\xd2\xff\xcb\xf7p\xefw\xce\
+O\xe7\xde{\xce=\x17>\xd6G\x93\xb0\xce\xf9\x16\xa0\
+\x0cp\x02z`\x19\x98_\xfd\xc6\xfe\x1b\x80\xcau\xcc\
+5a\xb2t`qt\xa1/j@P\x98)\xe4\xfd\
+\xa4SW\x89GG\x08.N\x00a \xf4\xff\x02l\
+B\xa3\xff\x16\xd5M\x1d4n6c\xb4(\x99\x9dH\
+\xe1\x1b\x8a\xe3\x1f\xf3\x01\xe7\x81\x1e\xe0\xd8\xbd\x04\x5c\xcf\
+\x12\xff\x8cC\xcf~\x8a\xfa\xd6\x0d\xd8K\xb5\x1f\x18\x09\
+\x07\xb2\x8c\x5c\x0e\xf3\xb7\xdf\xcc\x12\x8f\xde\x0f$\x81\xc2\
+\xbd\x00\x14\xd71\xf7y\xf6<^\x85\xbeH\xcd\xd8@\
+\x84\xbf\xfdv\x9a\x81s!dI\xc2`Rb0\xab\
+H'\xf3L\x8e\xf7\x03\x01 u/\x00\x15\xeb\x98\x9b\
+\x22\x9f\xcb\x13\x09\xe5\x98\x9f^d\xf0\xfc\xdb\x0c_\xfc\
+#\x97\xdf\x1dey.\x8d\xc9\xa2\xa2\xaa\xd1\x00\xb4\x00\
+E\xf7\x02n\xad\x80J\xc0J\x911A.\x9b\x15\xd3\
+\x89\xbcJ\x96b\xc0\x00\xc9\xf8K\x0c]\xb8\xa2\x5c\x9c\
+\x8d\xa9\x04A\xc0\xee\xd4\x00\x1e@\xf3\xbf\x044\x02\x9d\
+\x94\x96m\xc2l\xb3i\xed%\x1a\x93\xd5\xaa\x03\xec\xc0\
+\x04\xa9\xa4\xdf\x90K\x07\xcc\xd9d\x0e\xb5F\xc1J\xfa\
+Y\xcf\xca\xdc1:wR1p\x90\xcf=\xe3\xc1\xe9\
+\xd5Y\x0c:\x85\x99te\xc0`z\x8cx\x14\xc0\xab\
+W*\x04\xb5J\x14\x96\xef\x15\xd5MZ\xcb!Q\x02\
+\x0aT\xeam\xe42\x92^\xa3V\x98\x1d%jo\xfb\
+\x16S\xa6\xa2\xce\xad\xdb\xd8Y\xedj\xda\xe4\xb2\x98L\
+\x9a\x99\x91\xa18\xfd\xef\xbf\x0e\xf4\x01\xd1up\x18\x80\
+&\xe0q\xa0\x0e\xd0\xb1\x92\x09\xd2k\x89`\x148\xcb\
+\xf4\xb5?\x93I\x95\xa7\x12\xe1\x86dUmMmk\
+\x9b\xa9\xc8l\xad\x050\x98L\xcad./\xa3R)\
+\x005kO_\xd5^\xaf\xb7fcGg\xdd\xf6=\
+\x0f\xb6\x94\x18t-\xe9\x82\x9c\x18\x1a\x188\xf3\xe2/\
+^\x008\xb7\x16\xc0\x140\x8eo\xe8\x0f\xf8\x86jb\
+\xfe\xca\x07\xd4\xd1\x90\x98\xd8P\xd5ls\x97i\x05 \
++\xcb\xc4\xa2\x89\x02\xc5n\x0dP\x09hocO\x0d\
+\xd8\x9a\x9a\x9aJ\xbd\xf5\x8d\xbbJm\x96\xed5\xd55\
+\xad\xf7m\xddZ\xbe\xb9\xb9Q7:\xbfTX\x8a'\
+d`l\xad\x8074\x0a\x8c\xca\xb3\x13\xd1\xe5\x22\x83\
+\xae\xe7\xf4i\xb7}\xd7\xbe\x12\x95B -\xc9$r\
+\x0a\x05N\xaf~u\x89\xf4\xb7\xb1c\x03\xf6\x02_\xda\
+\xbd\xff@\xe3\x83;w8\x9a*\xbcj\x80p6\xcf\
+\xd9\xc9\xb9\xc2\xf9kSZ\xc0\x0c\xeb+u74\xc0\
+\xd45\x13s\x93\xdd\x81\xa6\x0e\x0b\x06\x93\x0a\xa5J\x00\
+A@\xab\x13\xd9\xba\xdb\xcb\xac\x7f+S\xe3a`b\
+\xf5\x1f#\xb0\xb3\xac\xacl\xcf\xe6\xfb\xb6\xed|\xfc\xe9\
+\xaf7\x1e\xdcq\x9fA^\x1d\x5c\x8c\xc6\x18\x5c\x8eJ\
+\xdfy\xee9\xff\xc2\xe5\x0b\xa7\xe3s\xd3=\xc0\xfbw\
+\x0b\x98 \x9f\x1b\xa5\x90\xff+'_{\x86]\x07J\
+k\xaa*5;\x9cV\x1e\xf2\x16\xf3\xa3\xbe\xf6\xa2\xc5\
+t\xcc\x19\x9c\x1a/\x06$\xa0\xbd\xaae\xe3g\x1f\xfd\
+\xc2\x13\x95]-\xcde\x1b\x5c\xa5\x0e\x93\xdd\xa1\xcbd\
+2\xa4\x0a2\xc3\xb3\x0b\xf9S\x97z\xe3\xc7_yy\
+yy\xf4\xeaK\xf9L\xea8p\x9d\xd5Cv7\x80\
+y\xf2\xb9y\xe0M\xae\xf4\xb6n\x7fx\xff\xceOx\
+\xac\xa5\xbb+]\x8a6\xbb\x89c[\xb7\x1aN\x0f^\
+\xac\xca\xdb\xec\xfb\x1a6\xb7\xab7vm\xddT\xeb\xb0\
+n\xabk\xd9XT\xe9)\xd38LF\x11`<\x10\
+\x91/\x0d\x0c\xc6\x07}\xbe\x89\xfe\x81\xc1\xc1\xf3\xef\x9c\
+\xea#\x19?\x02\x8c\x03\xf1\x1b\xce\xee\x06\x90U\x03}\
+L_;\xda\x91\x0dy\xf6\xe8dcw\x89\xd9\x00\xb0\
+\xbbu\xa3.P_\xd7T\xac\x16[j\xeb\xeb]\xdb\
+\xba\xbbm\xf7\xd7Uid\x01d\x19\x82\xb1\xb8\x1cJ\
+$\xa5s#cKo\x1c=v\xfdJ\xef\xc53\x81\
+\xe1\xbe\x93\xc0%`\xe1VGw\x0bxC\xc7\xff\xf9\
+\xfb_u\xb4\x15[\x5c[\x1aj\x0c\x00\x0fT\x94\x8a\
+\xb1\xfd\xfb[\xf3\xb1\xa8\xa2\xdcb\x14\x8b\xedfR\xe9\
+4\x00YIf2\x14\x95\xde\xec\x1bN\xfc\xee{\xdf\
+|\x8b\x95\xeb\xd9)\xe0\xca\x879\xf8\xa8\x80\x5c\xbdz\
+\xf5\xe2\xe5k\xfe*g\xdf\xa0{K}\x8d\xe6\xf8\xe1\
+\xc3D\xe6\xe6Tf\xb3\x99\x8cN\xcd\xc8\xc8\x08\x0e\x87\
+\x83%A%\xf5\xbc\x7f6t\xec\xf0\x9b\xc3s\xbd\xe7\
+\x0f\x03\xbfb\xe5\x16\x9e\xbb\x9d\xfd\xbb\x01\xb4\x03U@\
+\x93\xd1\xe9\xde\xfb\xc0#\x8f\xd6\x95:\x9d\x15R,\xa2\
+J$\x12\xbc\xfe\xfa\xeb\xf8\xfd~\x0e\x1c8@\xd7\x96\
+-LN\xcfp\xa4\xffJ\xc2\xd7\xd7{\xc9\x7fm\xfc\
+Bbq\xe1,\xf0\xde*\x5c\xfeN\xce\xd6\x02(\xb2\
+\x92\xd7\xea\x00/\xc5\xeeJeYE\x95\xbe\xac\xbc\xf6\
+\xa1\xf2\x92\xf6\xc6\xda\x1a\x93Y\xa3R\x87\x96\x16\x15W\
+\x15\x02\xe5\xe5\xe5X\xadV***0\x1a\x0c\xb8\x9c\
+\xa54d\x0abo\xcfI\xd5\xcc\xb8/\x16\x0f\x87&\
+\x81\xc9\xb5F\xe3v\xb5\xd8\x04\x94\xa0\xd6V`\xb24\
+\xe1\xd9\xf00\xae\x8aO\xe1\xd9\xf0\x80bCC\x97\xa6\
+\xb9\xa3fGg\xbb\xd5k\xd0)U\x85\xbc\x10\x8dE\
+\xe5\xa9\xb9\x05\xc9S^NMC\xa3\xec-/G\xaf\
+\xd7\x0bz\xad\x16\x8f\xdb-\xc6\xa3Q\xa5J\xabM+\
+4\xda@pa~\xcdM\xd6\xed\x00;\x80}(U\
+\x9f\xa7\xc8\xf4E\xb6\xee\xde\xce\xae\x03\x8dt\xde\xef\x92\
+\xaa\x9b\x8c\x19\x93M\xdd\x9f\x15p+$\xacJ\x81l\
+./\xf5\xfb\xa7\x93\x81pX\xce(\xd5\xb2Fo\x10\
+\xf4Z\xadB\x90\x0a\x88rA\xb8\x7f\xe7N\xa3\xc9^\
+\xec\x99Y\x0ex}\x03}9Vz\x98;\xea\xe6\xa2\
+\xae\x03*\x80=\xc0A:v:i\xee\xb4Q\xddd\
+\xa4\xc4\xadC\x92d\x14\x0a\x01\xa9 k\x91)\xd1k\
+\x84\xc9t\x01\x9b \xc1\xb9\xb7\x96R\xff\xfc{_j\
+z\xe2\xd5\xd5\xe5\xf3\xe8K\xcb\xba\x1b:\xbb\xb6?\xf9\
+\xd5\xafy\xba\xddv\x15\x08\x98,\x16r\x08\x85S\x97\
+\x07\x12\xdf}\xe2s?\x05\xfe\xc1J\xde\xfb\xd0\xf6\xe0\
+F\x047\xa1\xd5\x1f\xc4]\xfee\xb6\xef\xdd\xc9c_\
+ifc\x97\x9d\xb2\xca\x22\x8a\x8cJ\x90\x05|Cs\
+L\x8c\xf6\xf0\xfe\x891\xd5\x94/\xa7\xafnt\xe4\x15\
+\xa2\x90\x91!\x13\x8bM\xe4\x03\x0b'\xe5\xb9\xc9\x97X\
+\xa9\x02>A\xa9\xba^H'\xa7&G\x86c\xf39\
+\xdcF\x83A%\xca\xb2B\xce\xe7\x04\xb7\xc3\xae\xda\xf3\
+\xe9\xc7*c\xd1\xa84>6ZX\x05\x8c\x7f\x18`\
+5Z\xdd#4w\xec\xa3\xad\xbb\x8b\xfaV\x0f\x15\xb5\
+&4:%\xd9L\x8cH`\x94X\xf8\x18\x81\x85W\
+8\xf7\xd6\x02\xe1\x80YW\xec\xf4\xb8\x9a[\x1d\x81\x02\
+B6\x1a^,$\xe3G\xe5\xe5\xf9W\xf0\x8f\x0d\xaf\
+:\x8b\x152\xa9P<\x18\x98\x9f\x9d\xb8~=\xab\xd6\
+Z\x12\xa1\xa0&\x95/hD\xadVeR+E\xa3\
+\xd1hu\xba\xdcf\x93\xddQ:\xbb\xb4dNF#\
+qV\x1e\x00\xfe\x03\xf0a\xaa\x9b>\xc3\xe6\xee6a\
+s\xb7M,q\xe9\xe4D,@*1\xc6\xd2\xdc\x05\
+|C'9\xfa\xd7\xbf\xf3\xf6\x1bg\x11\x14\x8d\x9a\xe6\
+\xf6vcK{\x9d\xcb\xed\xd6N\x86\xe3q)\xb0x\
+\x82t\xf2/\xbc\xfc\xe2\xc9[lgX\xe9\xee\xc6\x17\
+\xaf\x8d\xe5\xc7'&\x84\x9cR-(\xb4ze6\x95\
+\xd2\x18\xb5j\xa5\xdb\xebu\x0a\x0a\xb12\x95J9\x13\
+\x89\xb8\x14\x8fD\xfc@\x96\x9bZV\x11\xf86\x07\xbf\
+\xd1IC[\xa9h0\xaa\x8a\xe4\x82\x949}\xe4\x18\
+g\x8e\xfc\x9cW\xff\xf0k\xae\xf4\xbeM$X\x00\x9e\
+\xa4\xa5\xf3Q[g\xf7F{K\x9b\xb9 \xcb\xcc\x8d\
+\x8d\x0cS\xc8\x7f\x9f\x17~\xd0s\x87\xbd>\x92K&\
+'\xae\x0f\x0d,\x8d\xfa\xc6\x98\x91DW\x9d\xc7m\xc8\
+\xc6cB]M\x8d\xae\xb5\xb5\xcd\xd1\xdb\xdf_;\xe3\
+\x9f\x88\xac\xee\xe1\xd8\xbf\x01K=?a\xc7>\xaf\xdb\
+S\xa6-\x8b/\x87f\xfe\xf8\xcb\xf78\xf1\xea\xf3\xcc\
+O_f%)?\x08\x1c\xe2\xd0\xb3O\xbb\xf6>V\
+\xed\xad\xae.\xd2I\xf9\xfcE\xffl\x84\x1f\x7f\xfb\x11\
+\xde=\xe6\xe3\x0e\xd5`UK@o2\x14<7\xd5\
+wq\xf1\xc4;g\xbc\x19k\x89&\x97\xcb\xab\xacj\
+Qs\xe8\xa9\xa7\xecs\xd9B\xabB\x14\x83\xcb\xb33\
+\x01V\x9fPD\x94\xcag\xe8\xdaeu\x16;\xd46\
+A\x12\xa6\x93\x19\x0d\xd1P\x1d\x99\xf4\x1e4\xdaOR\
+\xdf\xda\xcd\x81\xa7:\xca\xdb\xb787\xd8-\xcaD^\
+\xe2z 4\x9d>\x7f\xfa%\xae\x5cz\x0dH\x03\xf2\
+\x1a\x00o(\x0bL*D\xf1z2\x12ZX\x0c\x04\
+\x85\xa5D\xca\xa4T\xaa4\xa5\x1e\x8f\xda7\xd8\x1f\x9a\
+\xf1\xfb'V\x0f\x1bJ2\xa9\x8b\x14\xf2\x8e\x5c6\xab\
+\x17\xb4zu\xfd\xa6\xb6\x92`\x91\xfe\x13\xb1X,\x9f\
+C@ku\xa8\x8bk\xeb\x0d\xde\x12\x87*\x9a\xcd\xc9\
+\xcb\xc1\xc0xxz\xf28c\x03/\x03\x89u\x80\xdd\
+\xbc7g\xd2\x91p\x8f\xef\xd2\x85\x89h\x229\x13\x89\
+D\x82\xe1\x85\xf9-6\x8fW\x1dK$\x83\xac4L\
+\xac\x00\xa6So\x10\x8f\xd8c\xe1PW\xc8f+\xb1\
+{*t\xb9\x12\x8f.\x9f\x97\x10%\xd0\x8b\x02v\x8d\
+\x88 I\xf2t0<\x13\x9c\x9e:!_:\xf32\
+\xfdg/\xdc\x05\xdc\xcdZ\x04\x16\x17\xaf\x0e\x07CS\
+\xfe\x80\xcfh\x0c\xd96\xd4\x9a\xa3KK\xefpS)\
+\x5cI\xd4\xee\x8a\xbd\xec=xH\xa8n: \x14;\
+-V\xa5\x02\x95B@\x01\x14dHI2\xd1d*\
+\xcb\xe5\xf7~\xc3\xb5\xe1?\xd1\xf3\xe6\xbb\x1f\x11\xeeV\
+\xdd\xa8\xf7.`\xfa\xe6\x08~\xb0=\xdc\xff\x84\x05\xd8\
+\x8c\xc3\xf5\x05\xf4\x06\x07\xa2\xa8A\x96Rd\xb3\xb3\xb4\
+\xef\xf8!\x10\xe7[\xfb\xefx\x03\xb9\x97\xba\xf56\x13\
+\x07z\x81\x19@\xc5\xca\x13\x86\xc4\xca\xbe\x89s\x8f\x9e\
+\xd4>\xd6\xc7\xbaI\xff\x02R\x96 <_\xfdR\xba\
+\x00\x00\x00%tEXtdate:cre\
+ate\x002023-03-09T0\
+7:06:53+00:00B\xac!\
+#\x00\x00\x00%tEXtdate:mo\
+dify\x002023-03-09T\
+07:06:53+00:003\xf1\
+\x99\x9f\x00\x00\x00(tEXtdate:t\
+imestamp\x002023-03\
+-09T07:08:41+00:\
+00!\x18\x99D\x00\x00\x00\x19tEXtSo\
+ftware\x00Adobe Ima\
+geReadyq\xc9e<\x00\x00\x00\x00I\
+END\xaeB`\x82\
+\x00\x00\x084\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x06\xffIDATX\xc3\
+\xcd\xd8\xd9S\x1b\xc9\x1d\xc0\xf1o\xf7\x8cfF\x17\x08\
+]\x08\x1d\x08l\x0c>\xc0\xb6\xd8\xca:\xde\xecn\xd5\
+\x1ey\xca\xf1\xb4/\x9b\xcd\x9f\x98J\x1e\x92\x97\xad\xad\
+\x1cU\xeb\x0d,\xc6\xd8\xd8\x18\x07cN\x1bsIX\
+ \x9043\x9a\xe9<\x08\x97\x9d\xaa\x84cm\x07\xff\
+\xaa\xa6\xe6eJ\xfdQ\xff~=\xfd\xeb\x11J)\xc5\
+{\x1c\xfa\xdb\xf8\x91\xd7\xff\xa3\x10\xe2\xec\x81J)<\
+\xaf\x85\xeb\xba8\x8e\x8b\xe3:\xf8\x9e\x07B\x10\xd0u\
+\x02\x01\x03\xd341\x0c\xe3\xff\x0ft]\x87\x83\x83\x03\
+\xca\x95\x1d\x96W\xd7X\xdf\xd8f\x7f\x7f\x1f\xdfs\x91\
+\x9aN8\x14$\x95\x8a\x93\xeb\xe9&\x99H\x90H$\
+1M\xf3'\x03\xc5IkP)\xc5\xc1\xc1>\xdb\xdb\
+e\xa6\x1f\xce\xb3\xb2V&\x91HP\xec\xcd\xd1\x15\xeb\
+$\xa0\xeb\xd8\x8e\xcd\xd6v\x85\xc5\xa5\x15\xaa/*\xf4\
+f\x13\x5c8_\xa4\xbf\xaf\x9fx<\xfe\xee\x80J)\
+\xaa\xd5*+O\x9f11\xfd\x84hg\x82\xd2\xb5+\
+\xc4c\x1dH\xf9\x9f5'\x84\xa0\xd5\xf2X[\xdfb\
+|\xe2\x0eN}\x87+\x17\xf2\x8c\x8c\x8c\x90\xe9\xce\xbc\
+\x1b`u\xb7\xca\xe2\xd2\x0a\x933\xab\x9c\x1b\x18d\xf0\
+|\x1f\x9a&\x10\xa2\x0d\xd2\xa4@J\x89\x02<\xcfC\
+\xf9\x0a\x85\xa0i\xbbLNM\xb3\xba8\xcb\xc5\xfen\
+>\xbay\x93x<q*\xe0\xb15\xd8l6Y{\
+\xb6\xc6\xfd\xb95\x8a\xe7\x86\xe8\xeb\xeb\xc5S\x0a\xa1\x04\
+R\x0a4M2}\x7f\x86\xd9Gs\x04\x83A\xbe\xf8\
+\xfcSB\xa10\x9e\xe7#5\x8dR\xe9\x1a\xbe\x12<\
+^z@84\xcd\xc7\x1f\x7fB \x1081P\x1e\
+\xf7\xc0\xf6\xf6\x16\xf3\xcb\xeb\x08+AO.\x8b\xe7+\
+\x94\x10 _\x01W\x9e\xad\xf1`n\x95\xe9\x87\xf3\xd8\
+\x8e\x83\xd4$B\x0a\x10\x02\x85d\xe8\xd2%\xcc\x8e\x1e\
+\x9eon\xb3\xb1\xb1~\xaa\x19<\x12h\xdb6\x1b\x9b\
+\x9b\xacn\xdb\xa4{r@{P!%R\x13h\x9a\
+@\xd3\xa1\xa3\xb3\x93\x81\x8b\x97\xc9\x17\x8b\x18\x86\x86\xae\
+\x83\xae\xb7\x9f\x91\x9a\xc02\x0d\xf2\xc5\x0b\xd4[\x01v\
+\xf7v\xf1<\xef\xed\x00k\xb5\x1a[\xe5*\x07~\x18\
+i\x04QB 4\x89ah\x98\xa6\x8eijX\x86\
+ \xa0\xcb6V\x93\x98\x86\x86iJLS#h\x05\
+\x08\x99\x1a\x96!I\xa6\x92\xd4[\x16\x8e\xd3\xa2^?\
+81\xf0\xc8\x1a\xac\xd5\xf6\xd8\xd9k \xcd8\xfb\xb6\
+\xc2\xf6\xc0\xc2g|l\x8c\xda\xde.\x96e\x22%\xac\
+>+cD\x92\x1c4\x0e\xf8\xdb\xdfo\x11\x0cZ\xb4\
+<\x1f\xdbv\xf8`\xf4:\x89D7B\x0b\xe0\x0a\x8b\
+\xe5\xd5gd\xba\xd3D\xa3\x1do\x0e<\xa8\x1f\xd0\xb0\
+=\x8c\xa0\x85\x90\x12\xdb\x03\xc7\xf3\xf8\xee\xaf\xff`\xbb\
+\xbcK2\xdd\x8dR\x8aT\xaa\x9bB\xc2\xa4\x15\x8e0\
+1=G\xab\xd5\xc2u\x1c\x9e?[!\x9eL\x12\xed\
+\xca\xe0*\x81\x0c\x04Y\x5c\x9e\xe5\xd2\xe0y\xb2\xd9\xdc\
+\x9b\x03]\xc7\xa5\xe5\xf9\xe8\xba\x8e\xaeI\x02\xba@\xd7\
+\x05\xc9T\x8aD\xa6H\xa1\xb7\x0f!@\xa9\xf6\xeb&\
+\x1c\x8ep\xb5\xf4\x01 h4\x1a\x18\x01\x1d\xc3\xb2\xb0\
+}E\xcb\x07!$[[[4\xed\xe6\xdbI\xb1\x94\
+\x02\xdfs\x00\x85\x10\xa0I\x81ej|\xf5\xd5\xaf\x08\
+h\x01\x82A\x0bMJ&&\x1f\xb0^\xae\xe3\xb7\x9a\
+|\xfeI\x89\x8e\x8e(-\xcf\xe3\xd3\x1b\x83ttu\
+a\xfb\xedW\xad\xf2[\xb8\x8e\x8di\x9c|\xeb;\x12\
+\x18\x8dF\x09\x19\x82Z\xcbF)@\x80\x94\x92\xbe\xde\
+\x1c\xbal\xaf0\x1d0$\xb8\xae\x8br\x1d\x92\x9d!\
+\x12\xf1(\x1e\x90I\xc7h\xb8\x8ar\xb5\x05Ja\xd7\
+\xabh\xc2\xa7\xab\xab\xeb\xc4\xc0#Wq<\x9e \x9f\
+I\x10\xd5m\xc2\x01\x08H\x81R\x0a_)\x94\x0f\x1c\
+\xeeA\xe2\xf5K\xb5\xef(\xda;\x8aR\x80\xa2\xd1t\
+\xd8\xdd^!\x99\xe8:\xd5nr$0\x12\x89R\xc8\
+\xe7\xe8\xef6\x91\xbe\x83\xf2}\xdc\x96\xe2\xf5\xcdQ\x1c\
+\x96\x82&%\x9a&\x91\xe2\x15\xb6\x9dVp\x1cEe\
+k\x93\xdd\x8dy\xae_/\x9d\xaa\xbb92\xc5\xba\xae\
+\x93H$\xd9\xa9.\xb0\xf2\xfc)u\xff\x1c=2D\
+$\xa8\x08\x18m\x9e\x02F\x86/\xd0w\xbe\x05\xca#\
+\x1a\x09\xa3\x0e'W\x01\x8e\xab\xa8\xbc\xa8\xb3\xfch\x9c\
+x\x87\xc5\x87?\xbbqb\xdc\xb1@\x80d2\xc9\xf2\
+\xf2\x22[KS\xec\x1c\xf8\xe8\x81\x8b\x84L\x89\xd1\xa9\
+#u\x85\xaf\x04\xd9t\x02y8k\x0ah\x1d\xce\xb0\
+\xed(6wl\x1e?\x9cb{\xe5\x1e\xdf|\xfd\xf5\
+\xa9\xdb\xaec\x81RJ\x86\x87\xaf\xf2x~\x9e\xa9;\
+\x7f\xc6sm\x04W\x112D\x22\xaaa\xe9\xed\xba\xe3\
+0\xb5>\xe0\x01\xfb\xb6\xe2\xe9F\x9d\xe9;\x13\xcc\x8e\
+\xff\x85/>\xfb\x8c\x1b\x1f\xfe\xfcT88E\xc3\xba\
+\xb7\xb7\xc7\x1f\xff\xf4\x07~\xbc3M\xaao\x94\xcb\xa3\
+\x1fq\xae/G:n\x11\xb6$\x01\xf9*\xa5/\xf6\
+\x9a\xac\xac>gf\xf2\x07\x16\x1e\x8e\xf3\xcb/\xbf\xe4\
+7\xbf\xfe-\xba~\xfa\x13\xc6\x89\x81\xd0n\x1en\xdd\
+\xfa\x9eo\xbf\xfb\x96\x9d\xbd&\xa9\xfc \xb9\xe2\x00\xc9\
+t7!\xcb\xc4ul\xaa;\xdbl<]`km\
+\x81\xa0\xa9Q,\xf6\xf1\xd1\xcd_02<\x82\x10\xe2\
+\xd4\x87\xaaS\x01_F\xb9\x5c\xe6\xee\xbd)\xee\xdf\x9f\
+fss\x8b\x86m\x03\xedE\x15\x0e\x05\xc9\xe7r\x8c\
+\x0c_E)0-\x93\x8d\x8du\x1c\xc7a\xf8\xca0\
+CC\x17\xdf=\xf0ex\x9eG\xadV\xa3^\xaf\xe3\
+y\x1e\x81\x80N8\x1c!\x12\x89 \x84`iy\x09\
+MjTv*LNN \x84\xe4\x9b\xdf\xfd\x9e`\
+0x\xe21\x8emX\x8f\x0aM\xd3\x88\xc5bd\xb3\
+Y\x0a\x85\x02\x99L\x0f\xd1h\xf4\xf0\x5c\xd2bnn\
+\x8ez\xbdN!\x9f\xa7X\xec\xa3Z}\xc1\xdd\xbbS\
+\xa7\x1a\xe3\x8d\x80G\x85\xae\xeb\x14\xf2\x05fff\x90\
+R\xa3P\xe8\xa5\xa7'\xcb\xed;\xb7\xa9T*g\x0f\
+\x04\x18\x1a\x1a\xc24M\x1e\xcf\xcf\xd3\x9d\xee\xa6P(\
+\x80R\x8c\x8d\xff\x93\x93V\xd6;\x05\xea\xbaN\xa9T\
+biq\x91z\xa3N.\x97'\x9f/\xf0\xe8_\x8f\
+X^Y>{ @>\x9f'\x93\xc90;;K\
+\xac3F>\x9f'\x1a\x89r\xeb\x87\xefq]\xf7\xec\
+\x81\x00\xa5R\x89J\xa5B\xb9\x5c\xa6\xa7\xa7\xbd\xa0\xd6\
+\xd7\xd7\x99y\xf8\xe0\xfd\x00\xc6b1\x86\x06\x87\x98}\
+4\x8bi\x9ad\xb39\xd2\xa94ccc\xd4j{\
+g\x0f\x04\xb8|\xf92\xcaW\xac\xae\xae\x92N\xa5(\
+\x14\x0a4\x9bM&nO\xbc\x1f@\xcb\xb2\xb8v\xed\
+\x1a\x0bO\x16\xf0|\x9fL&C.\x97cz\xfa\x1e\
+\xeb\xeb\xcf\xcf\x1e\x08\xd0\xdf\xdfO,\x16\xe3\xc9\x93'\
+\xc4b]d\xb3YL\xd3b\xfc\xc7q\x94\xf2\xcf\x1e\
+(\xa5dtt\x94\x8d\xf5\x0dj\xb5\x1a\xe9T\x9al\
+6K\xa5R\xa6\xd1h\x9c=\x10 \x9dN300\
+\xc0\xda\xda\x1a\x01\xc3 \x14\x0a\xd1\xdb[\xc4\xf8\x1f'\
+\xbd7j\x16~j8\x8eC\xb9R\xc6\xf7}j\xb5\
+\x1a\xb9l\x8e\x8e\x8e\xff\xfe\xa5\xe1L\x80/\xc3\xf7\xfd\
+c{\xc43\x05\x9e$\xfe\x0d\xc8]\xfaY2[\xe8\
+n\x00\x00\x00%tEXtdate:cr\
+eate\x002023-03-08T\
+17:27:26+00:00\x9e\x98\
+\xa5\x85\x00\x00\x00%tEXtdate:m\
+odify\x002023-03-08\
+T17:27:26+00:00\xef\
+\xc5\x1d9\x00\x00\x00(tEXtdate:\
+timestamp\x002023-0\
+3-09T06:18:41+00\
+:00=\x00\x9f\x01\x00\x00\x00\x00IEND\xae\
+B`\x82\
+\x00\x00\x03\xde\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xd1\
+PLTE\xff\xff\xff\xee\xf2\xf8\xdd\xe5\xf1\xf7\xf8\xfb\
+\xf5\xf7\xfb\xaf\xc3\xdd\xa4\xc0\xdf\x93\xae\xd3\xe4\xea\xf3\xf2\
+\xf4\xf8\xa1\xbb\xd8\xbc\xdb\xee\xa1\xc2\xdf\x84\xa4\xcd\xc6\xd3\
+\xe7\xfe\xfe\xff\x99\xb3\xd1\xad\xcc\xe2\xaf\xcf\xe4\xa6\xc5\xde\
+\x83\xa4\xcc\xa5\xba\xd9\xf2\xf5\xf9\x90\xab\xc9\xa1\xc0\xd9\xa2\
+\xc1\xda\xa1\xc0\xd8\x87\xa8\xcc\x8a\xa6\xcd\xda\xe3\xef\x87\xa2\
+\xc2\x96\xb6\xd2\x98\xb8\xd3\x98\xb7\xd2\x96\xb6\xd1\x96\xb5\xd0\
+\x89\xa9\xc9z\x9a\xc3\xba\xca\xe1\xfb\xfc\xfd\xf2\xf4\xf7\x82\
+\x9d\xbe\x8e\xae\xcd\x90\xb0\xcf\x8f\xaf\xce\x8d\xad\xcc\x8b\xaa\
+\xc9\x89\xa8\xc6\x83\xa2\xc1p\x92\xba\x98\xaf\xd0\xeb\xf0\xf7\
+\x7f\x9a\xbb\x89\xaa\xcd\x88\xa8\xcb\x86\xa6\xc9\x83\xa3\xc5\x7f\
+\x9f\xc1{\x9a\xbcw\x96\xb6h\x87\xadn\x8c\xb6\xcc\xd7\
+\xe8~\x98\xba\x82\xa4\xcb\x84\xa5\xcc\x82\xa3\xca~\x9f\xc7\
+x\x99\xc1p\x91\xb9e\x85\xaeTs\x9e>^\x8a#\
+Du2T\x87\xb1\xc1\xd9\xf2\xf4\xf6w\x92\xb6p\x94\
+\xc5o\x93\xc4k\x8f\xc1d\x89\xbc[\x80\xb4Pt\xa8\
+Cg\x9b7Z\x8e,M\x7f\x1f@r5U\x84\xb1\
+\xc1\xd8u\x91\xb5j\x90\xc6j\x8f\xc5g\x8c\xc2a\x86\
+\xbdY\x7f\xb6Qv\xacFj\x9f:]\x910S\x84\
+`z\xa1\xce\xd7\xe4\xf2\xf3\xf5y\x95\xb8t\x99\xces\
+\x97\xccn\x93\xc8^\x84\xbaTy\xb0Fk\xa0Cf\
+\x94\x8d\xa0\xba\xec\xef\xf4{\x99\xba|\xa1\xd5z\x9e\xd2\
+t\x98\xcdk\x90\xc6a\x87\xbdQx\xac\x5c{\xa1\xb8\
+\xc4\xd2\xfc\xfc\xfd\xf2\xf3\xf4|\x9c\xbc\x84\xa9\xdc\x81\xa4\
+\xd8y\x9d\xd1l\x92\xc7X\x80\xaf|\x95\xb0\xdc\xe1\xe7\
+\xf1\xf2\xf4}\x9f\xbd\x8b\xb0\xe3\x86\xa9\xddr\x9b\xcd_\
+\x86\xad\xa1\xb2\xc2\xf4\xf5\xf6\xf1\xf2\xf3}\xa1\xbf\x8d\xb4\
+\xe7o\x9b\xc9n\x90\xac\xc8\xd1\xd8\xf4\xf4\xf5x\xa0\xb8\
+a\x8f\xb4\x8a\xa1\xb3\xe8\xea\xed\xfe\xfe\xfe\xcd\xd9\xe0\xc6\
+\xcf\xd6\xfa\xfa\xfa\x8f\x8b'\x1a\x00\x00\x00\x01bKG\
+D\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\
+\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07t\
+IME\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\
+\x00\xd1IDAT\x18\xd3c`\xc0\x05\x18\x99\x981\
+\xc4XX\xd9\xd89\xd0\x059\xb9\xb8yx\xf9\xf8\xd1\
+\x04\x05\x04\x85\x84ED\xc5P\x05\xc5%$%\xa5\xa4\
+edQ\x04\xe5\xe4\x15\x14\x95\x94UT\xd5\xd4\x11\x82\
+\x1a\x9aZ\xda:\xbaz\xfa\x06\x86F\xc6\x08A\x13i\
+S3s\x0bK+k\x1b[;\xb8\xa0\xbd\x83\xa3\x93\
+\xb3\x8b\xab\x9b\xbb\x87\xa7\x977T\xd0\xc7\xd7\xcf? \
+0(8$4,<\x22\x12&\x18\x15\x1d\x13\x1b\x17\
+\x9f\x90\x98\x94\x9c\x92\x0a\xd3\x9e\x96\x9e\x91\x99\x15\x9b\x9d\
+\x93\x9b\x97_\x00\xb7(\xad\xb0\xa8\xb8\xa4\xb4\xac\xbc\xa2\
+\xb2\x0a\xe1\xa4\xea\x9a\xda\xba\xfa\x86\xc6\xa6fd\xc7\xb7\
+\xb4\xb6\xb5wtvu\xa3x\xb3\xa7\xb7\xaf\x7f\xc2D\
+\xb4P\x9a4y\xca\xd4i\xe8A7}\xc6\xccY\x0c\
+\x04\x01\x00\x16~0NH\xc6\xd0\xc2\x00\x00\x00%t\
+EXtdate:create\x002\
+023-03-08T17:27:\
+26+00:00\x9e\x98\xa5\x85\x00\x00\x00%\
+tEXtdate:modify\x00\
+2023-03-08T17:27\
+:26+00:00\xef\xc5\x1d9\x00\x00\x00\
+(tEXtdate:timest\
+amp\x002023-03-09T0\
+6:20:50+00:00\xdd\xe8\xb2\
+\xde\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x03\xf9\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x04\x00\x00\x00&\xf7p\xe6\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x01bT\x00\x01bT\x01\x80\xc1\xe1l\x00\
+\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6\
+ \xcd\x00\x00\x02\xc8IDATH\xc7\xad\xd7\xcdo\
+TU\x14\x00\xf0\xdf\xb43\xfd\x80*\xd0\xaa\xc1D\x89\
+JH\x91\xaa DK\xa3X\x87\x8e.\x5c41\xb8\
+5a\xa1\x0b\x12\x09\xd1\xb8!1\xb1,\x8c\x0d\x89$\
+.\x8cnH\x8c\x7f\x82\x1a]8\xa5i\xbbhT4\
+R\x81\xc8G\x95\x85DT\xa8\xd5\xb6R\xa6v\x9e\x8b\
+\xa9\xb6\xf3\xde+\x99;z\xcej\xce\xe4\xfe\xeey\xf7\
+\xce}\xef\x0d\xb4:`BI\xb4\x94eokVw\
+\xb4\x1at\xfd_\xac\x92\xf3\x064\xd5\xc7e\xedwH\
+\x8b\xef}\xeaW\xd0d\x9fN\x87e\xbc\xa1T\x0f9\
+!2\xa9{E\xa5\xc7Y\x91\x1b\x8e\xd4\xd3eFI\
+\xce;^\xaa\xaa\xf68\xee~%G\x1dW\x96I\x19\
+\x17\x99s-\x1d\x8c0\xe0H\xac^!\x17\x5cQ^\
+\xa5\x95i#\xde\xf5]\xda\x5c\x91\xd7S\x06\xf4\x98\x88\
+mU2OW-\xd5\xd2\xa6T\xfaL\xc6\xb8\xe7<\
+c}\xea\xe5\x92\xf3\xb0\x82.\x83\xf6\x99Jv8\x10\
+\xbe\xf8Z\xbd%R\xd2_]n\xa8\x83\xaa\xc4u\xef\
+\xbb*\xa7\xeb\xff\x02\x995O\xfcL\xfd\x17pQD\
+\xfcW\x90\xbd\xc9\x80\x0d\x1e\xf1\x90\xb6U\xbe-k\xb7\
+\x0e\xf9\xa5\xb6\x16M\x1a\xf2\xf3j\x9b\xd2\xe8Yc\x89\
+3~\xf3,\x1b\xb53\xbd\xc3\x9cW\xbc\xa6\x0d\xbf\x99\
+\x11\xd5\xb4\x00\xcdn\xb7\xc7\x9b\xe9\xe0\xf3\x06\xb4\xf8\xc9\
+{>N?`\x89\x88\xb4:\xaa\xdfci\xe0\xdd^\
+\xd5\xe2\x92\x17\x0c\x05m\xd2\x0c\x16\xd2vy\xaf\xad\x16\
+\x0d\x06r\x9bt\xe3d\x12l\xf4\xb4\x8c\x1f|\x12\xc4\
+\xf1\xa8{,\xfa0\x09\xdee7\xc6]\x0e\x04\x0b\xb2\
+.;\x91\x04\xbbmR\xf6\xd9\xaa\xb7\xad\xf4\xd8h\x0f\
+N\xba\x10\x073\x9e\x92\xf5\xa3\xf1\xc0\xfev\xd9\x8c\xa2\
+R\x1c\xbc\xd3\xe3\xf8\xc2\xa5\xe0\x0bn\xf6\x8b\xb1\xe4Y\
+\xde\xe5>\x14\xfd\x15\xc4ux\x02_\xbb\x98\x04\x0b\x9a\
+\x5c1\x16\xd8\xdfv\x9d(\x9a\x8f\x83\xb7\xe9\xc5W&\
+\x03\xc1>kM\x19!\x0e\xee\xb0\x05E7\x82\xb8u\
+\xf2\xf8\xb6\xf2\xc0\xaa\x06\xfb\xacq\xcdh`\x7f]\xba\
+0d6\x0e\xae\x97\xc7)\xe7\x02\xc1\xbc[\xcd\x18\xae\
+|X\x09>`\x1b\x86\xcc\x05qk\xed\xc5Y\xa7\x93\
+`\xde-~\xffg\xa6\x9a\xa3\xd3v\x0c\x9b\x8e\x83m\
+\xfap\xc6\x99@\xb0W\x87?\x97\xefL\xcb\xe0V\x0f\
+b\xd8\x1fA\x5c\xb3\x02\xce\xfb\xa6\x1a\x8c\xd0\xab\xdd\x9c\
+\x13\x81\xfdm\xb6\x13\xa3\xae\xc6;\xcc)\xe0\x9cS\x81\
+\xe0\x936*).\x17\x1a,\xa0\xc3\xbdv`\xa4\xc6\
+'\xc8r\x7f/\xe2\xbc/W\x16'D.:lV\
+d\x7f\x00\xd6h\xb7a\x91\xb2\x97W\x963\x0e8\xa6\
+\xc5\xac5\x1a|\xaeh!\xf5]\xac:\x22Y[\xe4\
+\xdd\x81\x0f\x1c\xac\xde\xc8\xb4\x97\xf6Zs\xda1\xed\xc9\
+\xf9\xe2\x7f+j\xcb)\x1f\xe9\x97\x8bc\x7f\x03\xa8M\
+ \xd2E{(\x13\x00\x00\x00%tEXtda\
+te:create\x002023-0\
+3-08T17:21:49+00\
+:00\xa3\xa1\xac\xac\x00\x00\x00%tEXtd\
+ate:modify\x002023-\
+03-08T17:21:49+0\
+0:00\xd2\xfc\x14\x10\x00\x00\x00(tEXt\
+date:timestamp\x002\
+023-03-09T06:18:\
+41+00:00=\x00\x9f\x01\x00\x00\x00\x00\
+IEND\xaeB`\x82\
+\x00\x00\x03\xac\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xa1\
+PLTE\xff\xff\xff\xfe\xfe\xfe\xf6\xf6\xf6\xdc\xde\xe0\
+\xca\xcd\xcf\xc6\xc8\xcb\xce\xcf\xd1\xe5\xe6\xe6\xfc\xfc\xfc\xe7\
+\xe8\xe9\xbc\xc2\xca\xc0\xc8\xd3\xd4\xda\xe3\xd7\xdd\xe6\xc8\xcf\
+\xd9\xae\xb5\xbf\xc2\xc5\xc7\xf6\xf6\xf5\xea\xeb\xeb\xb7\xbf\xcb\
+\xce\xd9\xe9\xe5\xef\xfa\xe9\xf2\xfc\xe9\xf3\xfc\xe8\xf1\xfc\xdc\
+\xe6\xf4\xaf\xb9\xc7\xbe\xc1\xc3\xfb\xfb\xfb\xfb\xfb\xfa\xbf\xc5\
+\xcc\xbb\xcb\xe0\xdd\xeb\xfa\xe8\xf3\xfd\xee\xf8\xfe\xef\xf9\xff\
+\xec\xf7\xfe\xe5\xf1\xfd\xd3\xe1\xf2\xa8\xb2\xbf\xe8\xe8\xe8\xee\
+\xee\xed\xa1\xaf\xc4\xbc\xd2\xee\xce\xdd\xeb\xc5\xd1\xd9\xcb\xd6\
+\xdc\xcc\xd7\xdc\xca\xd6\xdb\xc6\xd2\xda\xd9\xe5\xf2\xae\xbe\xd4\
+\xcf\xd1\xd4\xe4\xe4\xe3\x98\xab\xc4\xba\xd5\xf3\xad\xbf\xcc\x84\
+\x94\xa5\x8f\xa0\xb2\x8f\x9f\xb2\x8e\x9f\xb2\x84\x94\xa4\xc4\xd4\
+\xe2\xa6\xbc\xd7\xad\xb2\xb6\xff\xfe\xfe\xe7\xe7\xe6\x9c\xac\xc2\
+\xbd\xd7\xf2\xdb\xec\xf4\xe0\xed\xf2\xe6\xf1\xf6\xe7\xf1\xf6\xdf\
+\xec\xf0\xd9\xec\xf7\x9f\xb5\xce\xab\xae\xb0\xbe\xc5\xcf\xc2\xd7\
+\xef\xe5\xf6\xfd\xf5\xff\xff\xfa\xff\xff\xfc\xff\xff\xdc\xef\xfb\
+\x91\xa0\xb2\xc9\xca\xc9\xdb\xdc\xdd\xa3\xb4\xc8\xce\xe4\xf6\xf1\
+\xfd\xff\xfd\xff\xff\xfb\xff\xff\xf1\xfc\xff\xba\xcb\xdc\x98\x9e\
+\xa2\xf4\xf3\xf3\xf9\xf9\xf8\xaf\xb3\xb5\x90\xa0\xb1\xc5\xd8\xe7\
+\xe8\xf4\xfb\xf0\xf9\xfd\xe4\xef\xf6\xb8\xc5\xd1\x94\x9a\x9f\xe2\
+\xe2\xe1\xf9\xf9\xf9\xc4\xc5\xc4\x96\x9d\xa1\x96\xa0\xa8\x9d\xa6\
+\xad\x9d\xa4\xa8\xa2\xa4\xa5\xb7\xb8\xb6\xde\xdf\xde\xf8\xf8\xf7\
+\xe9\xe9\xe8\xe6\xe6\xe5\xf3\xf3\xf2\xe5\xe5\xe4\xd3\xd3\xd3\xaa\
+\xab\xa9\xed\xed\xed\xcc\xcc\xcc\xc5\xc5\xc4\xb6\xb7\xb6\xd5\xd5\
+\xd5\xd2\xd2\xd1\xad\xae\xac\xd9\xda\xd9\xf5\xf5\xf5\xc9\xc9\xc9\
+\xca\xcb\xca\xb3\xb4\xb3\xca\xca\xca\xaf\xb0\xae\xd5\xd6\xd4\xfa\
+\xfa\xfa\xfd\xfd\xfd\xec\x02\x5c\xcb\x00\x00\x00\x01bKG\
+D\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\
+\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07t\
+IME\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\
+\x00\xcfIDAT\x18\xd3c`\x80\x01F\x06t\xc0\
+\xc4\xcc\xc2\xca\xc6\xce\x81\x22\xc6\xc9\xc5\xcd\xc3\xcb\xc7/\
+ \x88$&$,\x22*&.!)%-\x03\x17\
+\x93\x95\x93WPTRVQUS\xd7\x80\x0bjj\
+i\xeb\xe8\xea\xe9\x1b\x18\x1a\x19\x9b\xc0\x05M\xcd\xcc-\
+,\xad\xacml\xed\xec\x1d\x1ca\x82N\xce.\xaen\
+\xee\x1e\xee\x9e^\xde>\x08\xf7\xf8\xfa\xf9\x07\x04\x06\x05\
+\x06\x04\x87\x84\x22l\x0f\x0b\x8f\x88\x0c\x8c\x8a\x8e\x89\x8d\
+\x8bG\x08&$&%\xa7\xa4\xa6\xa5gd\x22;>\
++;'7/\xbf\xa0\xb0\x08\xd5\xaf\xc5%\xa5e\xe5\
+\x15\x95N\x18\x01PU]S\xcb\x81!ZW\xdf\xd0\
+\x88!\xd8\xd4\xdc\xd2\xc0\x84!ZT\xdd\xda\x82!(\
+\xd3\xd6\xd2\x8e\xa9\xb4\xaa\xb1\xa3\x13C\x90\xa1\x0b\xe4R\
+\x00\xd0-*\x8fg\x18\x97\xc5\x00\x00\x00%tEX\
+tdate:create\x00202\
+3-03-08T17:27:27\
++00:008\xef\xae1\x00\x00\x00%tE\
+Xtdate:modify\x0020\
+23-03-08T17:27:2\
+7+00:00I\xb2\x16\x8d\x00\x00\x00(t\
+EXtdate:timestam\
+p\x002023-03-09T06:\
+20:50+00:00\xdd\xe8\xb2\xde\x00\
+\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x04e\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xc5\
+PLTE\xe1\xe1\xe1\xda\xda\xda\xdc\xdc\xdc\xdb\xdb\xdb\
+\xf8\xf8\xf8\xff\xff\xff\xe7\xe8\xe8\xeb\xeb\xeb\xeb\xec\xec\xec\
+\xed\xed\xed\xed\xed\xee\xee\xee\xee\xef\xef\xef\xef\xef\xef\xf0\
+\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xdd\xdd\xdd\xf4\xf4\xf4\xed\xee\xee\
+\xf0\xf1\xf1\xf1\xf2\xf2\xf2\xf3\xf3\xf3\xf3\xf3\xf4\xf5\xf5\xf5\
+\xf5\xf5\xf6\xf6\xf7\xf6\xf6\xf6\xf6\xf7\xf7\xd8\xd8\xd8\xe2\xe2\
+\xe2\xe8\xe8\xe8\xec\xec\xec\xf2\xf2\xf2\xdd\xe9\xdc\xee\xf2\xed\
+\xfa\xfa\xfa\xe3\xe4\xe4\xea\xea\xea\xf7\xf7\xf7\xf9\xf9\xf9\xf4\
+\xf6\xf4\x90\xbf\x8b\x95\xc2\x8d\xdd\xea\xdc\xe0\xdf\xe0\xf5\xf6\
+\xf6\xf4\xf7\xf5\x90\xbf\x88\xaa\xd2\x89\x8c\xbe{\xaa\xc3\xa9\
+\xec\xed\xec\xf9\xf8\xf9\xf8\xf9\xf9\xfb\xfb\xfb\xf5\xf8\xf6\x88\
+\xb9\x81\xad\xd3}\xae\xd4y\x83\xb8c\xa0\xc6\x9a\xeb\xf1\
+\xec\xfc\xfc\xfc\xf6\xf8\xf7}\xafs\x95\xc5P\xa3\xcfU\
+\xa1\xceT\x83\xb9I\x85\xb5q\xdc\xe7\xdc\xf7\xf6\xf7\xfd\
+\xfd\xfd\xf7\xf9\xf9r\xa4_\x81\xbd\x12\x95\xcd\x19\x93\xcb\
+\x1az\xb5\x17k\xa1G\xcc\xdd\xcd\xf8\xf7\xf8\xfe\xfe\xfe\
+r\xa0Z\x93\xce\x09\xaa\xe2\x0b\x81\xba\x0fy\xa6\x5c\xd4\
+\xe1\xd5\xde\xde\xde\xf9\xfa\xfa\xf7\xf9\xfaq\x9bV\x90\xc8\
+\x06{\xaf\x1f\x82\xa1w\xe5\xe9\xe7\xf3\xf4\xf5a\x87S\
+X\x850\xaa\xbc\xac\xd9\xdb\xdd\x8e\x9f\x92\xa9\xb2\xac\xd3\
+\xd4\xd6\xd8\xda\xdb\xde\xdf\xdf\xe0\xe0\xe0\xc5\xc6\xc6\xcf\xcf\
+\xd0\xcf\xcf\xcf\xd1\xd2\xd1\xdc\xdd\xdd\xc5\xc5\xc6\xe5\xe5\xe4\
+\xd6\xd6\xd5\xdf\xdf\xdf\xd0\xd0\xd1\xd2\xd2\xd2\xd3\xd5\xd6\xe1\
+\xe2\xe3\xe4\xe4\xe4\xbc\xbc\xbd\xce\xce\xce\xd8\xd8\xd7\xf6\xf6\
+\xf5\xd4\xd4\xd5\xcc\xcd\xcd\xc8\xc9\xc9\xdd\xdd\xde\xfa\xfb\xfb\
+\xe2\xe3\xe4\xc3\xc6\xc7\xd7\xd8\xd7\xe0\xe1\xe1\xe3\xe4\xe3\xd9\
+\xda\xd9\xdf\xe0\xdf\xe5\xe5\xe5\xe2\xe2\xe3\xd7\xda\xdb\xda\xdc\
+\xdd\xbc\xbd\xbd\xd5\xd5\xd4\xd1\xd3\xd4\xc5\xc7\xc7\xd3\xd3\xd3\
+\xdf\xe0\xe0\xd9\xda\xdb\xca\xce\xd0\xfc\xfaE\x9c\x00\x00\x00\
+\x01bKGD\x05\xf8o\xe9\xc7\x00\x00\x00\x09pH\
+Ys\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\
+\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\x1f\xc9\
+N\xa7\x00\x00\x01dIDAT\x18\xd3M\xd0gS\
+\xc2@\x10\x06\xe0\xe3\xe4\xc4\x02\x18N$\x86(\x1a\x8d\
+&\xf6\x0e\xf6\xd85v={\xef\x15\xb1b\xc3.\xf6\
+^\x7f\xafw\x8c\xce\xf8~|fwv\xf6\x05\xc0\x00\
+\xff%\xcc\x88h\x80!\xdc\x14\x11\x19\x15m\xb6X\xac\
+11\x9c\x0d\x87\x10\x9ab-\xf68\x07\x8f\xe3\x05\xa7\
+\x98\x90\x98\xe8r\x81$\x00\x93\xadv\x89\xc7\x82\xe8\x14\
+SRe\x98f\x8eN7\x00\x18\xcb9()F5\
+#3+;\xc7$\xe6r\x10@3\x8fs)\xc9r\
+^~AaQq\x89\xc2S\xb4\xc6\x8b\x8a[\x96=\
+\x9e\xd2\xb2\xf2\x8a\xca\xaaj\x0f\xb6\x01h\x17\x15\x95\x92\
+\xa6\xd5\xd4\xd6\xd57465\xb7P\xe4~Mom\
+k\xef\xe8\xec\xea\xee\xe9\xa5(\xb9\x99\xe9:i\xed\xeb\
+\x1f\x18\x1c\x1a\xd6\x95\x11\x00\xf9Q9dhl|b\
+rj\x1a\x11#E\xfckdfvn~\x81~\xa4\
+2d\xcb\x84\xe8\x82kqiyE&\xc4\xbd\xcaP\
+c\xa8x\xd7|\xeb\x1bX\xc5\xbc}\x13@\x81\x0d\x22\
+-j\xcb\xb7\xbd\xc3K\xfe\xdd\xbd\xfd\x83?\xf4\x1c\x06\
+\x8e\x8eO\xa4\xd3\xad\xb3\xf3\x0b\x8d\x22=\x83\xd0e\xf0\
+\xeaZUo\x02\xb7w\xf7:\x026\xf0\xf0\x00\x0c\x87\
+\x8fO\xcf*\x12\xf6^^\x01\xa1}\xca\x8abL\x0e\
+\xc3X\xd4\x10\xc2o\xef\xdb\x84\x95\xcc\x9a\x16>\xfc~\
+\xaf\xd7\xfb\xf9\xf5\x1dd\x86~\x00\xa3\xa7N\xa9\x0f0\
+\xa84\x00\x00\x00%tEXtdate:c\
+reate\x002023-03-08\
+T17:27:26+00:00\x9e\
+\x98\xa5\x85\x00\x00\x00%tEXtdate:\
+modify\x002023-03-0\
+8T17:27:26+00:00\
+\xef\xc5\x1d9\x00\x00\x00(tEXtdate\
+:timestamp\x002023-\
+03-09T06:20:50+0\
+0:00\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\
+\xaeB`\x82\
+\x00\x00\x02\xa6\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x04\x00\x00\x00'\x80\xd5\x86\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\xb1*\x00\x00\xb1*\x01a\xa7,\xef\x00\
+\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\x1f\xc9\
+N\xa7\x00\x00\x01uIDAT(\xcf\x85\xd3\xb1k\
+SQ\x14\x06\xf0\xdf}}\xa1i2\x95b\x0dH2\
+i\xe8\xe0\xe4$os\xb4\x9bm\xf1\x1f\xd0\xa5E\xf0\
+\x1f\xa8\x93nBA\x10]\xc4\xbf@p\x15\xba:\x08\
+B!S\x87\x84.E\xb0\x95R\xb0$\xc6$}\xcf\
+\xe1\xbd$M\x11\xf2\xdd\xe5\x9es>\xee9\xf7\xbb\xdf\
+e\x8aD\xcb\xc0\xdfb\x0d\xb4$\xd3b\x98\xec\xaa>\
+)y\xa7/ S\xb6chK\xd754tl\xce\
+d6u4\xc6A4IgR\xbd\x19bO*\x1b\
+\x071\xaaVd\xeaJjn\x89\x8b\xcaHMI\x1d\
+\xc1\x99n\x90\xd8\xd5\x94\x8a\xddt\xae7\x99:S\xb1\
+\xec\xc4H\xa4\xede\xd0\xf2\xcb{}\xa4W\x06\xc9\x91\
+g\xca\xb6\xdd``\xc3<l\x18D2\xfd\xb9\xc4\xbe\
+,.\xb4,Y\x9d\x5c\xe3*FN\x0d\x05\x93\xe2]\
+\x1f-\xbb\x9c\xca\x81`\xc1\xb9'\x0e\xc6\xf2d\xe8\xd8\
+\xf3\xda\x07\xdf,\x14\xb4K\xf7=\xb5\xa7\x9d3bA\
+\x19\x17\xf6\xfd\xf6\xd5\x97\x99\xb6\x8f\xed\xbb@Y\x88\x1d\
+\xda\x11\xf4\xd4T\x045\xf7\x8a\xb7>\x10T<\xf4S\
+\xc5\xb6\xc3 \xf1\xc2\x1d\xa9\x92U\x8f\xacxU\x9c\xb7\
+\xeb\xccg\xa7\x86\xb9\xe0\xb9o\x1a\xea\x12G\xd6-j\
+Z\xb3\xa6i\xd1\xba#\x89\xba\x86j~\x99na\xa5\
+\x91\xd4\x03\xcf\x8b\xd6o\xa4F\x8e\x1dOM1\x16#\
+\xb2\xa4\xed\xbb\x08\xa9\x1fn\x8b\xa6~\xbdn\xdc\xb7\xfe\
+\x14\xf1\x92g\xff7\xee\x9c\xaf\xf0\x0fs(t\xfdd\
+$_B\x00\x00\x00%tEXtdate:\
+create\x002023-03-0\
+8T17:21:49+00:00\
+\xa3\xa1\xac\xac\x00\x00\x00%tEXtdate\
+:modify\x002023-03-\
+08T17:21:49+00:0\
+0\xd2\xfc\x14\x10\x00\x00\x00(tEXtdat\
+e:timestamp\x002023\
+-03-09T06:20:50+\
+00:00\xdd\xe8\xb2\xde\x00\x00\x00\x00IEN\
+D\xaeB`\x82\
+\x00\x00\x06\xbc\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x03\x00\x00\x00\xbb H_\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x02\xb2\
+PLTE\xff\xff\xff\xf3\xf3\xf3\xe9\xe9\xe9\xfd\xfd\xfd\
+\xc1\xc1\xc1\xc7\xc7\xc7\xd0\xd0\xd0\xd1\xd1\xd1\xfc\xfc\xfc\xbc\
+\xbc\xbc\xeb\xec\xec\xf2\xf2\xf3\xf0\xf1\xf1\xf1\xf1\xf2\xf1\xf2\
+\xf2\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf4\xf4\xf4\xf5\xf5\xf5\xf5\xf5\
+\xf5\xf6\xf6\xf6\xf7\xf7\xf7\xf7\xf7\xf8\xf9\xf9\xed\xed\xed\xbd\
+\xbd\xbd\xe8\xe9\xe9\xe8\xe9\xea\xe7\xe8\xe8\xe9\xea\xea\xea\xeb\
+\xea\xeb\xec\xeb\xec\xed\xed\xed\xee\xee\xee\xee\xee\xe5\xe6\xe6\
+\xe0\xe0\xe0\xe0\xe1\xe1\xf1\xf1\xf1\xf2\xf2\xf2\xeb\xeb\xeb\xea\
+\xea\xeb\xe9\xea\xe9\xec\xec\xea\xea\xeb\xeb\xda\xe2\xec\xdb\xe3\
+\xed\xef\xef\xed\xef\xef\xee\xf0\xf0\xee\xf0\xf0\xef\xf1\xf1\xef\
+\xae\xae\xae\x9a\x9a\x9a\xa3\xa3\xa3\x97\x97\x97\xf5\xf4\xf4\xf3\
+\xf3\xf2\xf4\xf4\xf2\xf4\xf4\xf3\xf5\xf4\xf3\xdf\xe7\xf3\xe4\xea\
+\xf3\xf6\xf5\xf3\xbe\xbe\xbe\xd6\xdf\xeb\xa4\xc3\xedu\xa8\xef\
+[\x99\xf1\x90\xb8\xf0\xb0\xcb\xf0\xb4\xce\xf1\xb9\xd1\xf2\xbd\
+\xd3\xf2\xc1\xd6\xf3\xc5\xd8\xf2\x9e\xa4\xad\xce\xce\xce\xc0\xc0\
+\xc0\xa9\xaf\xb8\xd4\xe3\xf8\xcd\xde\xf6\xc7\xdb\xf5\xc2\xd7\xf5\
+\xbc\xd4\xf4\xb6\xd0\xf4\x8a\xb6\xf3Z\x9a\xf2{\xac\xf2\xb0\
+\xcc\xf3\xe7\xed\xf5\xec\xec\xec\xd2\xdd\xec\x90\xb7\xee]\x9a\
+\xf0K\x90\xf2\x84\xb1\xf1\x9e\xc1\xf2\xa3\xc4\xf2\xa9\xc8\xf3\
+\xaf\xcc\xf4\xb5\xcf\xf4\xba\xd2\xf4\x9b\xa4\xaf\xd4\xd4\xd3\xc4\
+\xc4\xc4\xa5\xae\xb9\xcc\xdf\xf9\xc3\xd9\xf7\xbb\xd4\xf7\xb4\xcf\
+\xf6\xac\xcb\xf5\xa5\xc7\xf5~\xaf\xf4I\x8f\xf3a\x9d\xf2\
+\x9a\xbe\xf3\xe3\xeb\xf6\xbe\xbd\xbd\xed\xed\xec\xee\xee\xed\xe6\
+\xea\xee\xcf\xdd\xef\xd8\xe2\xf0\xf1\xf1\xf0\xf2\xf2\xf0\xf2\xf2\
+\xf1\xf3\xf3\xf1\xac\xac\xab\x9f\xa0\xa0\xad\xad\xad\xbb\xbb\xbb\
+\xf8\xf8\xf7\xf6\xf6\xf5\xf7\xf7\xf5\xf7\xf7\xf6\xf8\xf7\xf6\xd7\
+\xe4\xf6\xd8\xe5\xf6\xf0\xf3\xf6\xf8\xf8\xf6\xf8\xf8\xf8\xea\xea\
+\xea\xee\xef\xef\xf0\xf1\xf0\xe1\xe2\xe2\xd6\xd6\xd6\xd7\xd7\xd7\
+\xe7\xe7\xe7\xf6\xf6\xf6\xf9\xf8\xf7\xef\xef\xef\xef\xf0\xf0\xf7\
+\xf8\xf8\xf9\xfa\xfa\xf9\xf9\xf9\xfa\xfb\xfb\xf0\xf0\xf0\xfa\xfa\
+\xfa\xfb\xfb\xfb\xfe\xfe\xfe\xbc\xbc\xbd\xe5\xe7\xe8\xbb\xbc\xbc\
+\xe8\xe8\xe8\xe4\xe4\xe4\xe2\xe2\xe2\xe3\xe3\xe3\xe8\xe8\xe9\xd7\
+\xdb\xdd\xba\xba\xbb\xfd\xfd\xfc\xd9\xda\xda\xd1\xd1\xd2\xcb\xcb\
+\xcc\xc8\xc8\xc9\xcc\xcd\xce\xd2\xd5\xd7\xb3\xb6\xb7\xd5\xd5\xd5\
+\xde\xde\xde\xbf\xc0\xc1\xb5\xb6\xb7\xb4\xb4\xb5\xc0\xc1\xc1\xcd\
+\xce\xce\xb6\xb7\xb7\xc7\xc8\xc8\xb4\xb5\xb5\xb7\xb8\xb8\xc3\xc4\
+\xc5\xdd\xdd\xdc\xbc\xbd\xbb\xd2\xd3\xd2\xce\xce\xcf\xb3\xb4\xb5\
+\xcc\xcd\xcd\xe6\xe6\xe5\xbe\xbf\xbd\xd4\xd4\xd4\xd7\xd7\xd8\xc0\
+\xc0\xc1\xa9\xaa\xab\xdd\xdd\xde\xe5\xe5\xe4\xd5\xd5\xd4\xe5\xe5\
+\xe6\xb4\xb5\xb6\xac\xad\xae\xe1\xe1\xe1\xae\xae\xaf\xc1\xc2\xc2\
+\xe3\xe4\xe3\xbf\xbf\xbe\xd6\xd6\xd5\xde\xde\xdf\xc4\xc4\xc5\xdf\
+\xdf\xdf\xc6\xc7\xc8\xc1\xc4\xc6\xba\xba\xba\xfb\xfc\xfc\xe3\xe5\
+\xe6\xca\xd0\xd3\xc6\xc6\xc6\xc4\xc5\xc5\xbf\xc2\xc3\xb9\xbd\xbf\
+\xad\xb1\xb3\xd4\xd5\xd5\xe4\xab\x9e\xc5\x00\x00\x00\x01bK\
+GD\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\
+\x00\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07\
+tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6 \xcd\x00\
+\x00\x02\xceIDAT8\xcb\x95\xd4\x87S\xd3`\x18\
+\x06\xf0\xa4\xa1XjMGh\xda\xe2j+\xa6UQ\
+\x11EE\x1c\xb8'\xee]\xf7\xde{\x0f\x5c(\xae\x8a\
+\xa2Qj\xa5\xad\x03q\xe0\x02\x9c8Apoq\x0f\
+\x5c\xff\x87\xdf\x974\xc9\x97\xb6z\xfa\xdc\xf5z\x97\xfc\
+\xeey\xf3~\xcd\x15\xc3\xfe'\xb8\xe2\xaf\xc1\x05GD\
+)\xa3\xff\x9c\x1a\xca(\x22\x08U1\xea\x9a\x9aZ\xa4\
+V\x8b\xeb\xf4\x06\x03\x15k4\xd2F\x10\x9a\x8f\xc9\x1c\
+\xa3\x12\xa0%\xaev\x9d\xb8\xba\xf5\xea\xab\xadV\x9b\xcd\
+\xde >\xbe\xa1B\xa3a\x18\x92t8H\x12wZ\
+$\xa8h\xd4\xb8IB\xd3f\xcd\x13[$\xb5Ll\
+\x95\xdc\xba\x8d\xa5mJ\xbb\xd4\xf6\xa9\x1d:\xe2\x9d(\
+\x04\xa6)\xd4\x9d\xbbt\xed\xd6\xbdG\xcf^\xbd\xfb\xf4\
+\xed\x97n\xeb?`\xe0\xa0\xc1C\x86\x0e\x1b>b\xe4\
+\xa8\xd1.\x14Z\xc7\x8c\x1d7~\xc2\xc4I\x93\xa7L\
+\x9d6}\x065s\xd6\xec9s\xe7\xcd_\xb0p\xd1\
+\xe2%K\x11\xb8\xac\xaem\xf9\x8a\x95\xabV\xafY\x9b\
+\xb1.%c\xfd\x86\x8d\xc9\x9b27o\xc9\xda\x9a\xb5\
+m\xfb\x8e\x9dn\x04Zv\xd9\xad\xb6\xec\xa4\xdd\x1a\x86\
+th\xf1=99{\xf7\xb1,\xdc:s\x7f&m\
+\x92\xc1l[\xf6\x81\x5c\x0d\xe3\x80'\xc4\xd2\xb4\x1b \
+\x8f\x9b\xcfA\x19\xcc\x85\x0e\xd4\x81\x83\xa4bYN\x99\
+\xbc|\x0eYQ\x98\xc79P\x07\x98\x91\xe6\x94/\x18\
+\xbf\x19\x85\x1a\xc1\xb1,h\x13\x94\x1fF\x85\xc2\x04\x92\
+s\x06\x8a\xab\xf3\x8a(\x0c:\x1d\x0c\xef\xc0\x1e\x5c\x9d\
+\xa8@dPKj\xe1\x5c\xe8\xc4\x91\xc1\x102\xa8\x0b\
+u\x22\x0b\x81z\x5c\xcf=_\xd0\xf1\x86O@\x06\x0d\
+:\x038>\xe8\x84:\x82\x88\x08)88\xe8D\x16\
+\xe0\x82\xd9Q\x18\x0b\x06{x(\xb8@ \x02T\xb3\
+\xb2B\x81\xf1\xb7Q\xe8b#8p'\xac\xd1e\x14\
+\xa0\xe8\x80\x22T\xdcf\xea\xc3\xd2\x1b\xee\xa2\x8d\x1e\xa1\
+Pp\x84\xcaGS8\x8eSG\x8e\x22\xd0\x83\x16\xf2\
+\xceG;\xcc\xc7\xf2\x8f\x17\x9c8y\xea\xb4\x04\xdd\x10\
+J\x85\xd0Q\xf6\xfc\xc23g\xcf\x9d\xbfPT\x8c!\
+\xd0#/$|zWI\xfa\xc5K\x97\xaf\x5c\xbd\x16\
+\xadB\xa10\x99/\x0c\xf8YsI\xe9\xf5\x1b7\x0b\
+o\xdd\xbe#\xfc\xa1p\xd0$M\xe6\x0a\xbdLAY\
+\xf9]\xda]QyOr\x10zQ\x08\xae\xb0\xce\xfb\
+\x0f\x1e>\xf2?\xae|\x828\x09\x06\x1f\x11L\xc6\x9f\
+\x96={\xee\xac\xa8,F\x1d?\xda\x87B\x1f\xf3\xa2\
+\xf4\xe5\xab\xd7Uod\x8eoDw!\xbcyo\xdf\
+\x95GW\xe5\xc8\x9d\xb4\x8c\xb8\xb4\x97y\xff\xe1\xe3\xa7\
+P\x07~k;\xa1\x12_\x04\x98\xcf\xba/_\x8b\x8a\
+C\x1dF\xc4\xa4)\xab\xab\xab\x95J%\xfc\xc0\xafo\
+\xdf\x7f\xfc\xfc\x15\xe60\xcc\x1c\x1f\x1eE\x04\xf7O\xf9\
+\x0d]\x11i{\xdc\x1a\xbd\x00\x00\x00\x00%tEX\
+tdate:create\x00202\
+3-03-08T17:27:26\
++00:00\x9e\x98\xa5\x85\x00\x00\x00%tE\
+Xtdate:modify\x0020\
+23-03-08T17:27:2\
+6+00:00\xef\xc5\x1d9\x00\x00\x00(t\
+EXtdate:timestam\
+p\x002023-03-09T06:\
+18:41+00:00=\x00\x9f\x01\x00\
+\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x07\x8d\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x03\x00\x00\x00\xbb H_\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x02\x01\
+PLTE\x00\x00\x00A\xcdRA\xcdRA\xcdR\
+A\xcdRA\xcdRA\xcdRA\xcdRA\xcdRA\
+\xcdRA\xcdRA\xcdRA\xcdRA\xcdRA\xcd\
+RA\xcdRA\xcdRA\xcdRA\xcdRA\xcdR\
+A\xcdRA\xcdRA\xcdRA\xcdRA\xcdRA\
+\xcdRA\xcdRA\xcdRA\xcdRA\xcdR@\xcd\
+Q>\xccO?\xcdQS\xd2bu\xdb\x82\x8a\xe0\x95\
+\x8c\xe1\x97|\xdd\x88[\xd4jB\xcdS\x82\xde\x8d\xdc\
+\xf6\xdf\xfb\xfe\xfb\xff\xff\xff\xfe\xff\xfe\xe8\xf9\xea\x9c\xe5\
+\xa5H\xcfY\xa0\xe6\xa9\xb8\xec\xbeb\xd6p?\xcdP\
+?\xccPp\xd9}\xef\xfb\xf1\xd7\xf4\xda\xa8\xe8\xb0\xa3\
+\xe7\xab\xc9\xf1\xcd\xfc\xfe\xfc\xfd\xfe\xfd\x91\xe2\x9aL\xd0\
+\x5c\xd9\xf5\xdd=\xccOD\xceT\xb9\xed\xc0\xcb\xf1\xd0\
+T\xd2d\xac\xe9\xb3\xda\xf5\xddP\xd1`C\xceTW\
+\xd3f}\xdd\x89J\xcfZU\xd2d\xe3\xf8\xe5\x84\xdf\
+\x8f>\xccPd\xd6r\xf0\xfb\xf1\xf7\xfd\xf8m\xd9z\
+t\xda\x80\xe3\xf8\xe6\xbb\xed\xc1h\xd7v\xf4\xfc\xf5\xf2\
+\xfc\xf3N\xd0^\x86\xdf\x91w\xdb\x83\xe4\xf8\xe6\xf9\xfe\
+\xfa\xeb\xfa\xed\xe5\xf8\xe7\xc4\xf0\xc9M\xd0]t\xda\x81\
+\xe9\xf9\xebY\xd3gG\xcfW\xcd\xf2\xd2\x97\xe4\xa1D\
+\xceU^\xd5l\xdb\xf6\xde\x82\xde\x8eQ\xd1ax\xdb\
+\x84V\xd3eF\xceV\xc9\xf1\xce=\xccNK\xd0[\
+t\xdb\x81Y\xd3h\xcd\xf2\xd1M\xd0\x5c\xd7\xf5\xdbu\
+\xdb\x81e\xd6r\x89\xe0\x93o\xd9|\xd6\xf4\xdav\xdb\
+\x83\xd2\xf3\xd6_\xd5mC\xcdSQ\xd1`\xb3\xeb\xba\
+O\xd1_H\xcfX\xcf\xf2\xd3\x95\xe3\x9eN\xd1^l\
+\xd8y\xeb\xfa\xec\xe6\xf8\xe8\xc1\xef\xc7\xbd\xee\xc3\xda\xf5\
+\xde\xfd\xff\xfd\xf3\xfc\xf4\x83\xdf\x8f\xaf\xea\xb6\xe0\xf7\xe3\
+\xc5\xf0\xcav\xdb\x82\xf0\xfb\xf2\xfb\xfe\xfc\xba\xed\xc0`\
+\xd5o\xc2\xef\xc8\xe6\xf9\xe9\xbf\xee\xc5O\xd1^I\xcf\
+Za\xd6pr\xda~q\xda~\xe7\xf9\xeae\xd7s\
+B\xcdR\xd5\xf4\xd8\xb7\xec\xbeE\xceU@\xcdRz\
+\xdc\x86\xbc\xed\xc2\xc7\x19\xe3\xf1\x00\x00\x00\x1dtRN\
+S\x00\x00\x19SV\x15\x1c\xb2\xfe\xf5?\xb3\xf6\x16\xb1\
+>\xf7\xf3:\xfc\x8d\x0b\xe3\xec\xea\x8c)+\x09\xb3C\
+\xa1\x1d\x00\x00\x00\x01bKGD+$\xb9\xe4\x08\x00\
+\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6\
+ \xcd\x00\x00\x02UzTXtRaw pr\
+ofile type xmp\x00\x00\
+8\x8d\x95UK\xb2\xe30\x08\xdc\xeb\x14s\x04\x19\x10\
+H\xc7\xb1#k7U\xb3\x9c\xe3O\x83\x9c\xbc\xc4\xf1\
+{\xa9\x89\xabd\x0b!\x1a\x9aO\xd2\xdf\xdf\x7f\xd2/\
+\xffY\xd5\xc47\x1eV-\xeb\xa2\xac\x9b\x16\x13\xcaJ\
+Z\xd4\xb4\xe9\xce\x9dh\x1f\xdb\xb6\x0d\x22\xc8\x9b\x8aK\
+\x8aq\x91\xceY\xbaea\xe8VmI\xaa\xad\x86\x8b\
+\x85m\x95\xbd\x88\xe2\x0d\x83\xcc\xb8D\xc4\x83w\xca|\
+\xb3\xca+`qQ\xbb\x83\xe9B\xd9\xf7z\xd3\xdd\xd8\
+\xcf\x92#\xc0\x1b\xd1\xe1~\xf0:\x0f\x1e\xea\xe1\xc9\x97\
+\x19\xc86\xbf!\x8f\x1b\x94K\x95^r\x22wnX\
+\x88\xb8\xd0\xceJ\x1d\xfe,\xfe@b\xdc [\xb8p\
+ef\x82\xc6\x0dR\xe2\xec2\x1ax7\xac\x0b\xc3P\
+\x0f\xe1\xea\x1b\xacP\xa1\xf3CGx\x04/\x94\xd7B\
+\x22\xa2\xa7\xd0(\xc5\xa1\x87WM\xf0\xc0\x18\xc2\x19\x16\
+?\xda\x0dZ\xb4\x87\xc7\x16\xc8\xcd\x9f\xf0\x84\xb0\x12\xd6\
+>\x01\xe0\x91\xb1!?\xce\x88U\x84\x05\x04?\x7f\xf5\
+\x02. UH\x04i\x0b\xa6\x1a\x18\x82\xc6\xfd\x5c\x97\
+\x04\xc2\x86\x81X\xf7j\x12\xfb\x9c\x0b'X:R\xfd\
+\xe6s\x00\xee_iJ\xd8\x80l\xed\x08\xa7\xc2\xa7\xec\
+\x01\x80^\xbca\xb0\xdf\xb3\xf6d\x9cM\x8a\xd7\xd9\x89\
+\x90te\xfd{\xe3^\x89\x86\xe0\xa03\xdc\x9cq\xc4\
+C\xca\x09\xa5\x18\xf6\x9d\x97\xab\xc0>\x075a\xd3\x15\
+\xee\xdd\xe4\xc1\x8fS=@t\xf12\xf5\xb4H\x93\xe0\
+t\xea\xc0\x10\xc8G\xfa]Q\x08\xbd\x81\xca\x14Fj\
+\x1a\x92\xcb\x90DYJ\x09I\x8do\xe1\x06\xbdE\x16\
+\x992X\xc7.\x92\x94^\x90\xbd\x0c\xd8\x0bA\xdd\xdd\
+\xf2\x86\xdc\xa2m\xcf\xc0\xd5\x81\xd3\xff\x22\x1f\xcc\xad\xe2\
+5U@\x89x\xb9\x16I\x88~\xe8\xed\xad\x113F\
+\xc3\x08%\x8e\xaf\xec=)\xfc\xd4g\x99\xd0\xac1|\
+\xa2\x9c\xd3T\xa5\xf6\xa8\xe5W\x92K\xd4\xb2\x02\x1b\x1b\
+|{Q\xe4\xa7ZZ\xf5\xa0#}\xe2\x03\xa19\x13\
+Y\xd4GU\x84\x5c\xa5\x1c\x14\x14\x94\x15\x22\xc1\x5c(\
+\x09b\x09&\xd0\x90\xee<VWY\x82\x8fg\xe4\x0f\
+%\xe0\xdd\x7f\x81\x1c4_\x22\x7f\x07\x9c\xc8\xc7A\xb0\
+C\xe3\xe7^?k\xcd\xb9t\x97\xa6\xf7i<\x8f.\
+\xfe\x12\x10\xd2\x1c\xfeFs\x9a\xa7\x7fk\xe1n\xf4\xf3\
+\x1f\xe7\x80\x00\x00\x01\xb6IDAT8\xcbc`\x18\
+\x05\x84\x01#~\x80\xac\x8e\x89\x99\x05\x07`feD\
+R\xc7\xc6\xce\x81\x0bpr1\x22\xa9\xe3\x96\xc5\x09x\
+\x90\x14\xc2\xd4\xc9\xc9\x03\x81\x02n\x85\xbc|Pu\x8a\
+J\xca*\xaaj\xear@\xb6\xba:\x16\x85\xfc\x02`\
+\x11u\x0dM-mm\x1d]=}9Y}\x03C\
+#c\x0c\x85\x5c< \x01\x13S3ms\x0bK+\
+k\x1b[99;{m%\x079\xec\x0a\x1d\x9d\xb4\
+\x9d]LL\xf4]\xb5\xdd\xdc=<\xdd\xac\xbd\xbc\xbd\
+\xd5\xb1)\x94\xf3\xf1\xd5\xf1s\x90\x95\xf5\x0f\x08\x0c\x0a\
+\x0eq\x0b\xd2\x09u\x0b\xb3\x93\xc3\xa2\xd0$<\x222\
+\xc0\x04\xa8!\xcaM;:&6N;>!1\x09\
+\x9bB\xf9d\xad\x94TP\xd0\xa4\xa5kgdfe\
+[\xe7\xe4\xbac\xb5Z>\xcf&!\x1fdBA\xa1\
+\xb6^Q\xb1\xb9uI\x11v\xcf\x80L,\x05\x9bX\
+\xa6\x9d\xe1P^a])\x8f=x@n\xac\x82\xba\
+\xb1\xda!\x09\xb7B\xa0\xaf\xb5\xa3\x8b\x80&\x03}]\
+#oWkS\x87\xc3jY\x0fC\xed\xfa\x06\x8f\xc6\
+\xa6fm\xf3\x16\xb9\xd66\xed\xf6\xa8\x0eu\xec1\xd3\
+\xd9\xa5\xdd\xdd\xd3\xdb\xd7?a\x22\xd0\xa9\x93\xb4'L\
+\x9e\x12\x85-x\x80`\xaa\xf34\xad\xe9:\xda32\
+\x81\x0e\x999+a\xf6\x9c\xb9 \x85\x02\x98\x0a\x8d\xe7\
+\xcd_\xb0\xd0B{\xd1b\x90\x9f\xd2||\xbc\x97\x80\
+D\x05\x850\x14\xca\xca\x99\xc8\x17\xe5.\xd5^\xb6\x1c\
+\xcc\x91[\x01\xa2\x84ED1\x15\x82\x9d\xbar\xd5T\
+\x0f\x04\x17\xa4\x0e\xa1P\x009E\xabg\x9a\xa0\xa9\x83\
++\x14\x12\x13\xc7\x01$$E\x913\xab\xa8\x944\x0e\
+ %\x83\x96\xab\x89\xca\xfd\xa3\x007\x00\x00\xf0%\x81\
+`\xd6*Oa\x00\x00\x00%tEXtdat\
+e:create\x002023-03\
+-08T17:21:49+00:\
+00\xa3\xa1\xac\xac\x00\x00\x00%tEXtda\
+te:modify\x002023-0\
+3-08T17:21:49+00\
+:00\xd2\xfc\x14\x10\x00\x00\x00(tEXtd\
+ate:timestamp\x0020\
+23-03-09T06:18:4\
+1+00:00=\x00\x9f\x01\x00\x00\x00\x19t\
+EXtSoftware\x00Adob\
+e ImageReadyq\xc9e<\
+\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x05\xcb\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x012\
+PLTE\x00\x00\x00A\xcdRA\xcdRA\xcdR\
+A\xcdRA\xcdRA\xcdRA\xcdRA\xcdRA\
+\xcdRA\xcdRA\xcdRA\xcdRA\xcdRA\xcd\
+RA\xcdRA\xcdRA\xcdRA\xcdRA\xcdR\
+A\xcdR=\xccN?\xccPA\xcdRA\xcdRA\
+\xcdR?\xccPB\xcdRB\xcdS@\xcdQD\xce\
+U\x81\xde\x8c\xb7\xec\xbe\xb9\xed\xbf\x8b\xe0\x95G\xceW\
+F\xceWt\xda\x81N\xd0]t\xda\x80\xdf\xf6\xe2\x91\
+\xe2\x9b\x8a\xe0\x94\xdc\xf6\xdf\x83\xde\x8eZ\xd4h\xd7\xf4\
+\xdau\xdb\x82G\xcfX\xa7\xe8\xae\xb6\xec\xbd<\xccM\
+\xa4\xe7\xac\x9c\xe5\xa5\xf4\xfc\xf5\xd3\xf3\xd7|\xdd\x88\xa3\
+\xe7\xab>\xccP>\xccO\x90\xe2\x9a\xc5\xf0\xcab\xd6\
+p\xd9\xf5\xdcy\xdc\x85I\xcfY\xb2\xeb\xb9\xa8\xe8\xb0\
+=\xccN\x95\xe3\x9e\xc1\xef\xc7T\xd2ce\xd6s\xd0\
+\xf3\xd5X\xd3gQ\xd1a\xc1\xef\xc6\xa2\xe7\xaaN\xd1\
+^\xd6\xf4\xd9}\xdd\x89H\xcfY\xbd\xee\xc3\xd0\xf3\xd4\
+\xd5\xf4\xd9\xe1\xf7\xe4^\xd5mC\xceT\xaa\xe9\xb2\xd4\
+\xf4\xd8~\xdd\x8a`\xd5n~\xdd\x89\xdb\xf5\xdem\xd9\
+zH\xcfXW\xd3fJ\xcfZ@\xcdRp\xd9|\
+V\xd3e\xff\xff\xff@\xa6,*\x00\x00\x00\x19tR\
+NS\x00\x00\x01\x10\x80\xa4\xa3\xa5`\x9a\x99y\x96\x98\
+\xfbn\x86\x09Q\x8c\x89\x89\x89\x8bhy\xb4\xc5\xfa\x00\
+\x00\x00\x01bKGDe\xb5\xdd\x88\x9f\x00\x00\x00\x07\
+tIME\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\
+\x00\x02UzTXtRaw profi\
+le type xmp\x00\x008\x8d\x95\
+UK\xb2\xe30\x08\xdc\xeb\x14s\x04\x19\x10H\xc7\xb1\
+#k7U\xb3\x9c\xe3O\x83\x9c\xbc\xc4\xf1{\xa9\x89\
+\xabd\x0b!\x1a\x9aO\xd2\xdf\xdf\x7f\xd2/\xffY\xd5\
+\xc47\x1eV-\xeb\xa2\xac\x9b\x16\x13\xcaJZ\xd4\xb4\
+\xe9\xce\x9dh\x1f\xdb\xb6\x0d\x22\xc8\x9b\x8aK\x8aq\x91\
+\xceY\xbaea\xe8VmI\xaa\xad\x86\x8b\x85m\x95\
+\xbd\x88\xe2\x0d\x83\xcc\xb8D\xc4\x83w\xca|\xb3\xca+\
+`qQ\xbb\x83\xe9B\xd9\xf7z\xd3\xdd\xd8\xcf\x92#\
+\xc0\x1b\xd1\xe1~\xf0:\x0f\x1e\xea\xe1\xc9\x97\x19\xc86\
+\xbf!\x8f\x1b\x94K\x95^r\x22wnX\x88\xb8\xd0\
+\xceJ\x1d\xfe,\xfe@b\xdc [\xb8pef\x82\
+\xc6\x0dR\xe2\xec2\x1ax7\xac\x0b\xc3P\x0f\xe1\xea\
+\x1b\xacP\xa1\xf3CGx\x04/\x94\xd7B\x22\xa2\xa7\
+\xd0(\xc5\xa1\x87WM\xf0\xc0\x18\xc2\x19\x16?\xda\x0d\
+Z\xb4\x87\xc7\x16\xc8\xcd\x9f\xf0\x84\xb0\x12\xd6>\x01\xe0\
+\x91\xb1!?\xce\x88U\x84\x05\x04?\x7f\xf5\x02. \
+UH\x04i\x0b\xa6\x1a\x18\x82\xc6\xfd\x5c\x97\x04\xc2\x86\
+\x81X\xf7j\x12\xfb\x9c\x0b'X:R\xfd\xe6s\x00\
+\xee_iJ\xd8\x80l\xed\x08\xa7\xc2\xa7\xec\x01\x80^\
+\xbca\xb0\xdf\xb3\xf6d\x9cM\x8a\xd7\xd9\x89\x90te\
+\xfd{\xe3^\x89\x86\xe0\xa03\xdc\x9cq\xc4C\xca\x09\
+\xa5\x18\xf6\x9d\x97\xab\xc0>\x075a\xd3\x15\xee\xdd\xe4\
+\xc1\x8fS=@t\xf12\xf5\xb4H\x93\xe0t\xea\xc0\
+\x10\xc8G\xfa]Q\x08\xbd\x81\xca\x14Fj\x1a\x92\xcb\
+\x90DYJ\x09I\x8do\xe1\x06\xbdE\x16\x992X\
+\xc7.\x92\x94^\x90\xbd\x0c\xd8\x0bA\xdd\xdd\xf2\x86\xdc\
+\xa2m\xcf\xc0\xd5\x81\xd3\xff\x22\x1f\xcc\xad\xe25U@\
+\x89x\xb9\x16I\x88~\xe8\xed\xad\x113F\xc3\x08%\
+\x8e\xaf\xec=)\xfc\xd4g\x99\xd0\xac1|\xa2\x9c\xd3\
+T\xa5\xf6\xa8\xe5W\x92K\xd4\xb2\x02\x1b\x1b|{Q\
+\xe4\xa7ZZ\xf5\xa0#}\xe2\x03\xa19\x13Y\xd4G\
+U\x84\x5c\xa5\x1c\x14\x14\x94\x15\x22\xc1\x5c(\x09b\x09\
+&\xd0\x90\xee<VWY\x82\x8fg\xe4\x0f%\xe0\xdd\
+\x7f\x81\x1c4_\x22\x7f\x07\x9c\xc8\xc7A\xb0C\xe3\xe7\
+^?k\xcd\xb9t\x97\xa6\xf7i<\x8f.\xfe\x12\x10\
+\xd2\x1c\xfeFs\x9a\xa7\x7fk\xe1n\xf4\xf3\x1f\xe7\x80\
+\x00\x00\x00\xc7IDAT\x18\xd3c` \x010b\
+\x00\x88\x18\x13\x12\x00\x09\x0213\x0b+\x1b\x1c\xb0s\
+\x80\x05\x999%%\xa5\xa4e\xa4\x80\xa4\xac\xa4\xa4$\
+\x17X\x90[RRN^AQIYEU\x0d(\
+\xca\x03\x16\xe4\x95\x94R\xd7\xd0\xd4\xd2\xd6\xd1\xd5\xd37\
+\x80\x09\xf2HJ\x19\x1aI\x19\x9b(\x9a\x9a\x99[H\
+\xc1\x05e\x8d,\xad\xacml\xed\xec\x1d\x1c\x11*e\
+\x9d\x9c\xad]\x5c\xdd\xdc\xcd=\xac\x11\x82\xd6\x9a\x9e^\
+\xde>\xbe~\xfe\x01\x81H*\xdd\x83\x82CB\xc3\xc2\
+#\x22\xa3\xa4$y\xa1\x82\x92\xb2\x8e\xd11\xb1qV\
+\xf1\x09\x89\x92|\xfcP'IJ&II&\xa7\xc8\
+J\xcaJ\xf2\x09\x08\x82\x05\x85\x84E@@T\x0cH\
+\x88K\x08B\xfd\x8e\x19H\x94\x01\x00\x059\x1c\xdc\x03\
+n)\xb6\x00\x00\x00%tEXtdate:\
+create\x002023-03-0\
+8T17:21:49+00:00\
+\xa3\xa1\xac\xac\x00\x00\x00%tEXtdate\
+:modify\x002023-03-\
+08T17:21:49+00:0\
+0\xd2\xfc\x14\x10\x00\x00\x00(tEXtdat\
+e:timestamp\x002023\
+-03-09T06:20:50+\
+00:00\xdd\xe8\xb2\xde\x00\x00\x00\x19tEX\
+tSoftware\x00Adobe \
+ImageReadyq\xc9e<\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+\x00\x00\x16:\
+\x1f\
+\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed}[s[9\x92\
+\xe6\xfb\xfc\x0a\xad\xfa\xa5\x1dCB\x99\x89\xbc\xc1U\xee\
+\x89YW\xf4DGT\xc7l\xect\xc7\xee[\x07-\
+\x1e\xd9\xda\x96E\x05I\x95\xad\xfa\xf5\x0b\xc0$\x0f!\
+\xd2\xa4(SV\xd9S\x94\xabx\x90H\x5c\xf2C^\
+\x80\xc3\x03\x9c\x1f\xff\xed\xe3\xfb\xab\x93_\xba\xe9\xecr\
+r\xfd\xea\x14\x03\x9c\x9et\xd7\xe7\x93\xf1\xe5\xf5\xdbW\
+\xa7\x7f\xff\xdb\x9f\x87~z2\x9b\x8f\xae\xc7\xa3\xab\xc9\
+u\xf7\xea\xf4zr\xfao\x7f\xfa\x97\x1f\xff\xc7px\
+\xf2z\xda\x8d\xe6\xdd\xf8\xe4\xc3\xe5\xfc\xdd\xc9_\xae\xff\
+9;\x1f\xddt'\x7f|7\x9f\xdf\xbc<;\xfb\xf0\
+\xe1C\xb8\x5c\x10\xc3d\xfa\xf6\xec\xc5\xc9p\x98K\xce\
+~y\xfb/'''\xb9\xd9\xeb\xd9\xcb\xf1\xf9\xab\xd3\
+\x05\xff\xcd\xed\xf4\xaa\xf2\x8d\xcf\xcf\xba\xab\xee}w=\
+\x9f\x9da\xc0\xb3\xd3\x9e\xfd\xbcg?/\x8d_\xfe\xd2\
+\x9dO\xde\xbf\x9f\x5c\xcfj\xc9\xeb\xd9\x1f\xd6\x98\xa7\xe3\
+\x8b\xcc\xddw\xe6C\xacL\x98R:\x03:#\x1af\
+\x8e\xe1\xec\xeez>\xfa8l\x8b\xe6>n+J\x00\
+p\x96\xf3z\xce\x87q\xbd\xfcx\x95\x91\xf8lgj\
+\xeez\xeb\x19\xfd\x9b\xfc\xdf\xaa\xc0\x92\x10f\x93\xdb\xe9\
+yw\x91Kv\xe1\xba\x9b\x9f\xfd\xf4\xb7\x9fV\x99C\
+\x08\xe3\xf9x\xad\x9a%\xf8M\xbb\xcd\x88\x5c\x8f\xdew\
+\xb3\x9b\xd1y7;[\xd2K\xf9<\xa0\xe3\xf9\xbb\xac\
+\x0c\xe45\xf9\xae\xbb|\xfbn\xde\xa7/\xc7\xafN\xb3\
+|\xa4\xa05\xbd\xec\xc1\xcb\x95\x1aA\x88T\xb3\x96\xd5\
+\xaeg\xb1\xfc\xeb\xb8\xfb\xa5\xbb*\x0c\x8d\xe6\xb5\x95\x8d\
+'\xe7\xb9\x7f\xb9\xf7o'\xc3\x9bi\xf7\xcb\xe5\xe4v\
+\x16r\xb3\xbf\xb6\x15On\xe77\xb7\xf3\x7ft\x1f\xe7\
+\xdd\xf5\xa7\x9a\xb2dkb\xd6\xecZnE\xdch\xe7\
+\xcdh\x96\xdb9{7y\xdf\x9d\xdd\x5c^gy\xa7\
+\x93\xb3\xd1t\xfea2\xfd\xe7\xd9\x7f~\xbc{\xdb]\
+\x9f\xcd\xce\xa7\x97ong\xf9{t5zs\xd5\xb5\
+\xbd\xe8>\xdeL\xa6\xf3\xe1\xc5\xe5U\x97;\xbdQY\
+\xbe8\x9f\x9dMjM\xab\x1a\xceF\xe7\xf3\xdc\xe1\xd9\
+Y\x16\xf0:\xf7?\xdc\x5c\xbf\xddZ\xeb\xc7\xf1M\xd6\
+\x05t\xd8\x9a{\xb7\xca\xfdS\xce\xfeq\xdc]\xcc\xf2\
+\xf7b\x98J\x8a\x14\xbc\xe6\xe5\xdc\xaci\xddh\xfa\x1f\
+\xd3\xd1\xf82\xdbW\xa5\xadWy>\xb9\xba\xea\xce\xf3\
+P\x8f\xae>\x8c\xeef\xa5\xbdUUm\xd1\xc8\x9cj\
+\xa5\xb5\xda\xd9|r\xb3\xb8.\xd0\xce\xef\xae2\x04\x85\
+8\xcc5N\xa6/\xff\x00\xf5\xf3C%M\xb2\xc6]\
+\xce\xef^\xe2\x0f\xa7}\x99\xc9\xc5\xc5\xac\xcb\x0dW\x19\
+\xfbVk%\x91\x05OO\xce\xbe\xac5\xd8\xd6\x1an\
+o-\xaeZ\xfb\xf1\xac\x15{7\x8c\x9b(Q\x92\x03\
+P\xd2\xd8\xa5\x8b\x8b\xc7\xa2D\xc9\x0eB\xe9\xa2~\x1e\
+\x8b\x12\xa5\xb4\x03\xa5#+\x1b)\x9c\x1e(\xd8\xe3a\
+T\xfa\x9a0*\x1fO\xd9\x80\x0e@\xe9|\xdcu_\
+\x80\x12\xf0A(\x8d\xa9\xf3n\xfch\x94@\xf7\xa1T\
+R\xa3\xabm(\xfd\xdf\xbf\xfe\xfc\x97\x9f\xfe\xc1\xffX\
+U|\xfe\xf1\xd5){\x9f\xbc{u:\x84@\xb8F\
+\x9b\xbe:\x15\x09\xeb\x94\xb7\x8b\x9a\xff6\x1d]\xcfr\
+\x04~\xff\xea\xf4\xfdh>\xbd\xfc\xf8G\x08\xc9\x12\x0d\
+ \xff\x95K\x92\x01\xc6\x18\x00\x80\x06\x04\xc1M\xe9\xc5\
+F-\x7f\xbf\xbe\x9c\xe7\xc9\xc3\xed\xac\x9b\xfeW\x09\xc0\
+\xffy\xfd\xf7Y\xd7\x0e\xde\xb6\x81\xd8\x09\xb1\xd1O\x18\
+\x7f\xda\x06\x1fxQ\xe9}\xb5\x87(\x12w7\x11\xe5\
+\xdf_\xe3\xeb\xedM\xf0C\x9aPL\xbc\xbb\x09\xf8s\
+\x12\xf0\xedM\xe8C\x9a0\xb1}M\x80\xbf\x06\xd8\xde\
+\x84\xefo\x02\xf7\xd5n\xff\xfe\x99\xda\x13\xaciq\xa3\
+\xb0\xad\x16\x1f\xec2\xeb\x0c\xf2\xe5\xbbi\x97g\xbc\x7f\
+h\xf4}\xd9\x81\xb6\xe2\xa8\x94\x1e\xa8\x93\xfb\xf5\x1f\xab\
+\xeac\xc0\xe81Q\xca\xd7C\x84 h.\xf6\xa21\
+:4j\xac\xcet\x95\xbc\xb8\x97}Q\xb3\x1b{,\
+\xa3\x7fD\x98Z/\xa2\xf9\xb3\x0b0\xc2\xf8\x1b\x04\xcc\
+=\xb8\xa3\xc5\x066$\x0c\xcc\x84q\x03\xbd&\xa0\x1c\
+\x13\xbdH\x0a\xbb\xe2w\x04\xec\xab\xc1,G\x0c\xc6\xa4\
+\xb1\xb7\xf3\xbbLE\x091\xaa0\xf7\xbc\x94y=\xa4\
+\x98bL=o\xa6\xa2\x06\x1309T\x8dgy)\
+1\x1a\xff\xb5\x9b\xbf\x9b\xe4nfY\x8a\xa8\xbb\xc6l\
+^.\xaf\xf2:\xf7\x8f<\x80\x17=\x88y\xaa?\xef\
+\xa6\x0f\x07\xaf\xc6\x18\x04\x02\x03\x94\x05\xb9_i\x05\x02\
+F\x86\xd8\xcb\xf8\x89\x9f8\x89\xf6\xe0\xf5K\xb1\x90s\
+\x12R\x03\xfa\xa7.\xc5\xe8\xd6G\x92\x8b\xee?F\xb7\
+\xb3\xd9\xe5\xe8\xfa\x7f^\xdd\x96\xee\xee\xe9p\xe3\xdb\xc6\
+?\xe5e\xd7h\xbe\x5c\xb7\xb1\x8ah\xba\x1f\xa0\xdb\x16\
+b\xf4\xf5y\xe1\xa7.=\xa1\xde%\xd9\xa9w)\x1e\
+U?\xaa\xeer0i\xf5\x96\x03\x99\xb8\x1f\xa4\xb7O\
+i\x8e\xca\xc0\xbb`a8\xd0l\xaa\xe0\x988\xdc\x13\
+\x9cbH\xa0\x1e\x1b\xc1\x118\x904RK@\x91$\
+N\x0f\xf6\x8e\x10\x5c\x11Xc\xf1\x8d\x9e\xff\x178*\
+\xba\xda\x17\x9b \x8aE\x88\xbc\xc5\x04#\xb2\x82m\x98\
+\xa0IL\xa2\xdbLP\x04,\xe16\x1bd\x8eOb\
+\x83\x91\x84\x13\x8a\xef\xb6Af\xd9\xb0\xc1'\x8b\x9c\x91\
+9\xed\x8a\x9c,\xd2\xc44\xa3\x00\x04\x8e\xb1\x89l\xaa\
+\xa1t\x1bz\xde\x8b\xad\xbc\x17[y\xa7\x99\x08\x0f\x9a\
+\xaa\x8b\xa3\x92\xcb\x00\x83\xa9\x99Z7\xa4\xc1\x90\x82&\
+W\xe2TR\x10\xdc\xcd\x93\xd3 bH\x84\xce:\x90\
+\x90\x18\x13$~\xe0L\xfeic-\xd0.\xe3\x16\xc2\
+\x03\x8c\xfbp[\x0c\xa8\x18\x1b\x8b<\xc0y\x12\xc5\x10\
+\xc9\x90\x1a?\x221\x983J\xebG\x884\xa8\xb4\xee\
+S,\x90%\x8a\x94\x11~\xae\xa8BD\xfa\x18\xf7i\
+)\xa0\x11\xf1\x86\x0b5q\xe5\xd6\x85:\x04\x06\xe3V\
+x\xa5\x90$%M\xcf+\xbc?\xb5\xf0\x16Cr0\
+l\x85\x97\xc0\xec\x88\xf4\xb0\xc9Z\xb2<[{N3\
+$\x89\x0d\x02l!\xa9\xb3Q\x8b\x80\x06\x8c.h\x0d\
+\x02\x9c\x02a\xcb\x9b\xa9.\x81\xc9\xe4\x90)\xef~\x9c\
+\x86\x10d0t\x0b\xeaHL/\x9es\x95 \x11\xfe\
+{x\xaeg\x8b\xc5\x12\xf9h\x08C qU\xb0A\
+\xd1!Bb\xe3\x028\x19G.W\x8c\xa0\x808\x18\
+F\x09\x12\x13\xcb@ \x80\x88$iW\xb9C\xd4\xa0\
+\xea\xec\xed=9\xd7\xe0\xa4I\x9b)\xc16\xe6\x8b\x86\
+\xb9\x99\x13\xec\x06\xbb2q\x0cllJM}\x82A\
+\x12\xa5\x84M\xe3\xee\x01\x98\x1cR\xbb*\xe7\x90,1\
+j#\x95Y\x90\x18\xa3\xe1\x1e%\xdb\x8ft\xc6\x97\x93\
+\x22\x88\x16T\x19\x92S*\xa0[T\x90\xc4\xe5\xd2\xc9\
+AU\xb3nc0R\x8f2p\x0b\x9e\x0c\xc4\x1f8\
+g\xd9\xa57\x18\xd3C\xd4\x8fR\x82\xd3}z\xdc\x0f\
+\xc9\xf9\xd5\xe5\xcd\xff\x1a\xcd\xdf\xad7\xbc\xa4E\x8c\xfd\
+P.\x89{\xee\x9a\xde\x94\xba\xda[suB~\xf5\
+2\xff\xb4\xfc\xc7?l\xca\xf4\xe2\x87\x92\xbbv\xf7{\
+6\x9fN\xfe\xd9\xbd\xbc\x9e\x5cw\x8b\xeba]\x1e\xbc\
+D\x0d\x9c\x04\x00\x9c\x97\x19E\xf4,\xdf\xcb\xe9\xe4\xf6\
+z\xbcN\xfc\x7f\x93\xcb\xeb\x96\xfa\xfe2O\xc0\xaf.\
+\xf3\xd7\xcbU\xf1\xf1h\xf6n4\x9d\x8e\xee\x9a\xd6\x0a\
+\xb5\xdeg\xcc\x9c\x01eA\xee{\xb86\xef\xcf`\xfd\
+\xf5d\x98,DB\xc7\x01YH\x1cMN^\x9f\x0c\
+\x11,\x88\x22\xd2\x8a\x8a\x99\x88\x16,!\xc42\xafu\
+t4\xcdD\x92 \x22J\x83\x98\x828\x99R\xad \
+\xc6\x90\x89\xce\x03\xb6LN)\xc5B\xb4\xc0\xcc\xae\x03\
+\xf1@D\x8a\x0b\x22\xa6\xe2a\xd5\x83YbH'?\
+\xaf\x93S1L\x02\xc6RoOF\x80\xe0\x16\xcd?\
+5\x96\x18\xb0\x109 9\x15\x22\xa5\xe0\xee1\xf5\xc4\
+R\xafr0\x11\x8a\xd2\x93K\xbd\x0a\xc1 r\x92\xf5\
+*\xa4t\xd7S\x92Uc\x0d\xb1\xef\xd8\xcf\xeb\xe4^\
+\x8c\xd7\x95\x8c\xc9Me%2\xe5\xc60\x80\x83\x99\xac\
+\x83\xa3)\x13\x1d\x00[ M\x83\x0b\x89\xe3\x0a\xf4<\
+\x12n\x99\x978\xd9rx8m\x1b\xc8_O\xd6\xc7\
+\xb7\xb8)G@.\xd5&\x08\xc9\x89\xc5Wd\xc9\xd5\
+r\xd0\x18%c\xc3\x12\x12Z\xae\xd4R0e4-\
+\xae\xd7\xdcH\xb4\xf6\x8aC\x12\xb2,\x95\x04I\xae\x9e\
+I\x14\x04\x05-\x0d\x14\x83\xc7\x18\x9d*\x91\xc5R\xe4\
+\x82\x0a$\x00\xaa`!Q@\xb1hk\xe4\xd7\x0b2\
+\x00\xd2\xaa\x8a\xaau)h\x02\xd7e[\x96\x0a\x91\x83\
+[bn\xbb\x85\x08\x01\x10\xc0\x17\x02x\x92L\x84\x18\
+T\x11u%j\xdc\x8a\xca\xaf\xf7\x96\xc47\x9f\x9c\x88\
+\xf5\xe4\xfe'\xff\xeb\xc9\xb8\x9b\xdf\xddt\xb3\xecof\
+\xe7\xf53[\xfc[[7/\xbd\xce\x7f\xaf\x00\x92R\
+@F\xb6#\xc7\x8f\xf8\xd5\xe3\x07\xa5\xc7\xc4\x8f\xc3\xb4\
+e\xbb\xcemzit.`\xb3\xb4n:\x95q\x17\
+\xd3u7M \x81@\xd4\xd6\xdd4!eN m\
+\xbd\x0b\x11d\xb2\xb1\xaf{\x22\x22\x0e.\xa9wY\x0b\
+\x9aB\x22k\xbctOn\xbctO^w\x9cD\x18\
+\xa2\xb0\xd9\xba\x8b%\xb4\x10!\xff\xb5^\x1a\x85\x02j\
+rk\xbd4\xb2\x07D\xbf\xe7\xe8\x99\x83klBB\
+Ol\xbctOn\xbct%+F^s\xd2\xb5-\
+N\xc2\xd8D0\xd1 @\xca-\x8c\xa8\x1cH\x8dh\
+\xddI\xa3q\xe0d\x18{'\xdd\x8cc\xe3\xa5{z\
+\xe3\xa6\xd1<\xc4d\x90\xd6\xdd4\x1a\x05\xc8\x09\xeb\xdd\
+4\xaa\x054w\xbc\xe7\x0f\x95BT\x8bi\xcdO\xa3\
+\xa4\x90(9\xae\xf9\xe9J,\xa5\xac\xf5\xd3\x04)\x88\
+\x11`\xeb\xa7+\x19\xd9t\xddO\x13X@\x10Yw\
+\xd3\x04\x14(1\xf8\xbdn%\x0bLl\xd4\xb8\xe9\x84\
+\x01l- \xc5\x16\x95\xdeM\x1f2C\x8b\xdf\xde\x0c\
+\xed\x89\x03\x88\xc5 \x82\x84\xba-\x80\x807\x01Dc\
+\x00F3\xf9\xf2\x00\x82\x01\x81\xc1]\x06\xcbPQ\x97\
+}\xaal\x9c\xb4\x5c\xbak\x8cd\x83\xa2\x08\xccJq\
+\xa0\x14Hb\x02\xfd\xf2\x00B\x0cO\xb1\x00i\x0b\xaf\
+\xaf\xd6\x87\xc5\xb3*\x11\xb6\xb7\xa6\x14\x02\x03\x81\xb6?\
+\x83(\x070E^\xeb\x226\xbc\x07J\xbfy/\xc9\
+\x1f$\xbd\xaa\xf1\xa3\xa5?\xa8\x93\x15#o\xa0\x19*\
+4\xa0 H\xf0\x08\xc2\x0d(\x99+$\x16\x12\xdc)\
+/\xc7\x87\xddO\x8av\x94\xd1&\x0c\x10\x91\xa4\x11(\
+b\x88\xa6j\xde\x88e\x99\xd59\x817b!J\xe8\
+y\xf7\xdet\x83@1\x12Q\x1a`H(\x90\x18_\
+\x1cAK\x1e6\xc9Rf=\x0aj\xac\x81\xc8]R\
+\x03\x9b\xa4L\x15\x07ka\x0b\x9e,\x12Z\x03[\xcf\
+\xfbP\xd8\x94\xa2f\xd8\x8e\x03\x17~U\xb8\x9c\x02P\
+ri=\x8aq`6Sj\xe0\x1a\x92\x86\x14UI\
+[\x97\xc2_,69\xea\xe1b?\xbd/\xf1\xa01\
+\xb2\xb7\xe0`\xca(p\x8c\xda\x80#\x10\xc4A\xb9\xd1\
+\xa5\xc2\x0bI\xcdd\x87\xf0\xaa\x0e_\xc5\x91\xeeW\xe5\
+A<T\x85\xef\xa8\x8an\xe4\xc9\xa5\x8dJ\x14 \xba\
+!\xdd\x7f\x9a&a4lP\xeaYw\xa1\xc4\x0f\x0c\
+7\x1c\xbf\xa2\x8a\x18\x17y\x90\xadu7\x12\x22%\x88\
+\xb1\x95\x1d\xa9\x11\xbb\xe7\xda%v\xb4\x87\x89\x1d\xf18\
+Q\xc7\x03EI\xf7\x04\xc2\x18\x92\x22p#\x0fa@\
+\xc0\x94\x9a`Z\xa8\x908S\x8f\xf1t\x13\x04\xc6\x02\
+\x11\x0d (\xa6(\xec_\xeed\x95\x8d\x1e\xeam~\
+\xc7\xf4\xc1\x98\xc2\xd7\xc5\xd4\x02\x18@J-\xa6\x12\x90\
+\xd4\xdd\xbf\x13P\x95\xbf\x06\xa8\xfb\xc5\x1bZ\x107H\
+\xea\x83|\x19\x1d\x94\x85\xf6\xfcPy\x90\xf8\xfd\xa0\x82\
+\xb9\xf0\xc6\xa0z\xc2\x18\x0f\x19\xd4\x9d\xb0\x0a\xfd\x06g\
+\x1bCj\xc5\xa6\xd63\xb87\x82F\xda)!\xc2\x01\
+\x8f\xe4\xed\x95p\xef\xbd\x02\x0a\x22\x89]\xdbG~!\
+\x06q\x88=\xf1ce5\x80(\xcd#\xc4\x9b\xac\xe7\
+\x0d\xebv=\xbb\x19\x8d\x1f\xf2\x9b{\x88I\x85$\x0e\
+4$\x8f\xc8\x82\xdd0\x1656GNZ\x12\x18D\
+\x14=\xca\xa0\xfe^\xe0\xa4\x88\x83\xa1\xa6\xa0\xc9\x15\x8f\
+p\xab iz\x98\xca\xa9>^\xe5v\xe9\x03p\xbb\
+\x8fcs#\x802\xe8\xe9\xc1\xdb;\xc0\xb1\x8b\xdd\xc6\
+~\x9d\x93\xb3\xfd\xad\xf9\xe9\xc1\xbb\x18\xa2\xdb\xb8\xe3\x8d\
+\xd6\x8e\xb4UI\x99u?J\xec\x87\xa3tQ?\x8f\
+AI`\x1bJ\x87\xb7\x06\xc7CI#\xeeEIc\
+<\x1c%\x83h\x00\x8f@I\xa3\x1c\x8e\xd2\x9b\x0b\xe9\
+\x00\x9e\x0e%\x8e\xfbQby\x94.\x8d\xc7\xe7\x8fA\
+\x89\xed1\xba4\xbex\xf3\x84\xba\xa4z\xc0\xe6\xc0t\
+q\x8e\x17\xf4\xc8\xcd\x81\xaa\xea\xfbQ\x8a\xe8[57\
+\xc8n\x9c\xc4\xad{#\xfbFe\xb7+=\xa7\x91\xf8\
+\x03\xa4\xc3\xad\xd2\x19\x1coT\x8c\xf7\xeb\xae=&Z\
+\x00*\xfbct\xd7\x1e\x11-\x10G\xe9\xe9\xfc`\xa4\
+h\xfbP\xca<\xe9p\x94\xc6\xd2\xe9c\xa2E$\xc6\
+\xdfZ\xb4\xa0\x94`\x1fJ\x99\x87\x1e\x81\x92\x95\xbf\xc3\
+Q\xa2\x94\xf88(\xe1\x0e\x94\x0e\xdf\x0b\xd2n\xd5\xc0\
+'\xda.%*$N{\xb6j\xe0\x96\xedR\xcf\xb6\
+/\x88\xf0x\xbbB\x87\xf7\x9f\xc0\x8d&\xcd\xa3\xb7G\
+\xdeZ\xf4\xac\xcf\xd5\x12\xe1\xf1\x80\xdb\xfa`\xedp\xdb\
+\x93\xb5\x11\xe8\xd9\x9e\xac}\xce\x07\xc5\x89\x8e\xb8\xad\xef\
+\xf8[R\x9fu\xdb\x01\xc9\xef\xcf\xd0?\xb1\xad\xdb\xf1\
+l}\xfb\x86\xb4\xcf\xecG\xa3(\x01\xcc\xb1\xd9\x8f\xf6\
+U\xb6\xd6=\xe3\x86\xa3H\x94\xbe\x83=\xbc\xc7\xf7\x81\
+\x91~7\xf4\xa7=\xf2!\xf2\xb7\x7f\xe4\xc3W>W\
+\x84\xa2~{\xe7\x8a<\xabw\x8b~ `\xfb\xb7=\
+>\xd5\xce\xcbg\xc5\x89\xe1y\xb7\x9d>\xeb\xa4\x8e\xe9\
+\x98J\x82\xd1\x83d=y\x9a\xfd\xa9\xcf\x1c\x16\x99\x8f\
+\x0d\x95\xb9q\x0e\x84\xb0@\x22\xb5\xb8\xb9\x05Vg\xf5\
+\x06\xb7\xcc\x1d\x15\xd9I\xb4U2\xd4`\x05\xba\x069\
+\x04\xad\xd0\xe9\xf3\x22\xa7_\x179L\xfa\x09\xba\x069\
+\x8d\x9f\xb8\xa5\xc1M\xa8j\x9czk\x9c\x1a\x88\x98\xc5\
+\x9es\x9a\x00\x88\xcf\x11\xf2\x1cB\x8c\xc8\xdc\x04\xbe$\
+\xc1@\x10\xb0\x89\x7f\x9b\xbc\x17\x0do\x13\x0c\x9f\x17J\
+\xfb\x1d\xcacA\x99~\x87\xf2HP\xd2\xf1WZ\xc9\
+\x01\x19R\x01\xd2+\x9a\x9c\xc0\xf0+\x81\xf9\x9cS\x19\
+`h\xc3\xa7\x06aC\xdf\x98u\x08\xd3\xbd\xc5\xb8s\
+H\xac\xf7g\x1df!2D\xf3\xdf\xfe\xc9<p\xe0\
+\xdc\xe4p\x84<X\x8c\x9c\xeeO\xcb\xd4\xd0\x91\x9fW\
+t\x7fV\xd1\x1f\xf8P\xf6\xd00$\x89\x94\xe2\x8bg\
+EK\x8e\xe0qZ\xb9`)\xd7w\xa8[\xa2\xc7D\
+k\x88X\xc0J\x22\xdf9l\x0aG\x86\x0dW\xb0\xe1\
+\xf7\x0c\xdb\xb1\xb5\x8d\xd6`\xb3\xef\x176\xff\xddH\x0f\
+_\x94\x83\xe1W\x86\x8d\xa2\x7f\xe2hq\x13\x0e\xae\x22\
+\xd6\xe0\x86\x89\x83\x80\x89\xb4\xc0Y@\x06K\xf1Y\xf5\
+\xcd\xe2\xb1C(~\xcfjv\xdc{?\xc1I\x155\
+\x0db\x88\xec\x1a\xc9\x1f\x0f\x1a\x05\x92\xc4\xd8\xea\x98b\
+@WP]\xa0\xf6\xe3Yy\xd3I\xbd\xea\xcf\xed\x18\
+\xbd\xef\xc6\xbf\x5cv\x1f\xfa\xd7\xa1\xbc\x19\xadD\xb8\x19\
+\xbd\xed\xea\x13G\x19\xac\x8b\xfaYd\xbc\x99L\xc7\xdd\
+t\x99\xa5\xf5\xd3d-\x1eJZ\xbc\xb2\xa6\x1d\x9bR\
+\xeb*\x1f\xb6\xe7\xe7S\x05\xc6\x93\x0f\xd9\xd2\xeeg\xfe\
+:\x99\xbc/\xb5\xba\x1a\xba\xc9\xfd\xec\xba&\x14\x0b\x96\
+\x88\xe2Ffn\xcf\x83RL\xecx?s<9\xbf\
+-\xefS\x1a\xde~\x1a\xd3\x9b\x8f\x1b\xc5o\xa7\xd3\xc2\
+p5\xba\xeb\xb2\xd8\xf5\xabV\xd3\xbc\x0d\xa8\x96\xeb\xcf\
+\xc0mio\xa7\x97\xe3Y\x11\xfd\xfam\xee(o\xa3\
+\xdf\xdd\xa7w\xefo\x16Y=\x1c\xf9x\x85\x0f9\xaf\
+<*5\xba\x9au\xf7{Z\xb2\x867\xf9\x00\x87\x22\
+\xc8|z\xdb\xad\x17\xbb\xcdU\xf6\xe4\xa6\x5c\xcd\x1b\xbe\
+y3\xf9\xb8=\xff\xc3\xe5u\x1e\x95\xe1RX@\xfd\
+\x0c\xc7RvM\xf4\x19\x8e\xdcBT\xf8Lf\xc6@\
+m\xf9:\x9eF\xaa\xa5~\xcf\xef\xca;\x9b>\xde\x15\
+Zc\xb4\x85@\xb2f\xaa\x0d\xdc\xf7\xa9w-\xb5\x81\
+ze5\x9b\xc6R\xe9\xef\xbb\xf9h<\x9a\x8fz\xcb\
+YRH\x11\x97\xbd\x9f\x8e/^\xfe\xef\x9f\xfe\x5cR\
+5}~\xfe\xf2\xff\xe47&\xf5\xcf\xbb\x15\x86\xd1\x9b\
+\xc9mF\xab\x96Y\xf0\x8d\xcf_\x16\x8f1\x9a\xff\xe9\
+\xf2}\xb6\x87\xf2\xae\xac\x7f\xcd\xaf\xac\xca6\xbc\xcch\
+\x99\x0b \x99\xd0T;\xed>\xbd\x0bk\xdb\xeb\xc3\xf2\
+\xbf\xf7\x97\xa5\xd0\xd9\x7f\xcd\xf3\xe9!\x7f)\x8d\xac?\
+\xadx\xb6\xe8h&\xd4d/GN,\x05\xad\xa9\xb7\
+\xf7\xc60\xbf\xb7\xa9\xbbzu\xfa\xf3e~}\xd5\xd5\
+\xe4\x047\x95sr{\xf3~2\xee\x16&\xb4\xcc\x1f\
+/\xd2\x05\xba\xfe\xa8\xa1\x1d\x07\x0d\x95\xc3\x85\xfaO\xa3\
+\x05\xa5(\xb1{!\xf6G\x0cY\x0c\x84n2@\x0a\
+\xa8\xc2\xac\xf9\x04\x18\x09\x22\x98x\xa0\x18\x90\xcd\xbc\x1c\
+J\xc3A\xd4\xb5\x90\x5c\x22\x08\x9d\xe4l\x0a\x9e\x1cX\
+\xcbu\xe5\x19\xa8\xd4-_\xc2+~\x0d\xc8\x9a\xd8\xfb\
+J5\xb8\x10ai\xc8\xea\xc3z\x94[G\x09N\x12\
+1\xd7b\x1e\x80S\xb4L\xf4\xc0\x22v\xe2\x5c\xaf\x9d\
+\x9c\xe9S\x22\xe6\xc2\xf9\xc23k\xf9pN\x22j\x9f\
+\xce\xd5\x94\x12\xd1\x09\x96\x5c*~\x82\x04\xe5\xd22\xa7\
+\x06\x10\x04\xc1B\x1bx\xae\xa0^p)\xd9s1\x86\
+\xc4\x9ed\xad\xb2\x98BJ)2\xd7\x06\x19jGV\
+\xdf(!\xd5O\xae\xc6\xb9\x82\x0a\xee\x1aOL\xebI\
++>H\x81Y\x0d\xc4\xb7A_O\xb9\xd9~\xc6\xcd\
+\xc6Q\xec\x9b\xa7\x10\xd6\xe4\xf4\xf6\xaa{\xd9\xfd\xd2e\
+\xa5\x18\x7f\xfe\xd4\x1b\x0fJ\x18\xcd\xd3\x13\x9ey\xd3\x1e\
+ns\x1c\x05\x8e\x12\xe9w\x05\xfe\x86\x14x\xa9\x02\x10\
+HX\x1d\x95\x7f\xf8\xec\xb9M\x12\xf9\x1b\xd7\xe9\xfdv\
+\x1b\x01\xbfDF\xbc\xf9x_\xba7\xb7\xf3\xf9\x86p\
+U\x9e\x05\xb5m)\xd3?u\xac\x7f\xeb\xc7\x8b{\x16\
+\xa5\xa1\xe8s\x00%)\xda,\x81\x89\x12R\xa1!\x99\
+yV\x06\x0e\xd1\x15R*4\x8e\x89\x5c\x8b6\x14M\
+(F!\xa1jE\x0aJ\xb2P+\x0d\xacn\xab\xd4\
+B\xbbb\x00Ge?q\x08\xa0\x08\x96+\x84\xe0\xac\
+\xaam7~\xdd\xf4\x04d\xd4\x0e\xc1\x06\x87`j%\
++F\xaa\x82HE\xdd_\xd7dDT/f\x82\xc2\
+\xa2x\x82\xb5O\xae\x99E\x03\x92l\x10\xaa\xa1\xa6\xe0\
+qA\x82\x14\xad!V\x13\x5c$S\xf4\xd6\x04\x89@\
+\xb51AY\x94*\x06\xd8tp\x97-)2E\xa2\
+\x1f>\xabfB\xf8\x0d\x98\xd2.\xbdd^[0\xcf\
+?\xf3\x83\xd8\xb0|!y\x19\x98\x17\x0f4\xc8\xcd\xd7\
+L|CH\xfd\x1e\xfb\xbe\x81\xd8\xd7{ f;=\
+`\xb6\xb3\xdb\x9d\xc5t\x7f\xf8\xa9u\xd4\xb8\xc5Q\xc3\
+\xa6\xa3\xd6\xb4r\xd4x\xcfQC\xe3\xa8a\xe1\xa8a\
+\xcdQ\x9bn8\xea\xa6\x1b;\x9c\x96\x01#&\xb7\xcf\
+{\xad\x98\xe2o88n:!\x08\x1eQ\x19i\xe9\
+\x8c\xd0*z\xd5%\x993\xca\x8b\x9d\xc3\xbfk\xd87\
+[\x1bR\x09\x15\x84\xd1\xeb\xcbg\xa0(\xab$k.\
+\x1b\x16\xd4\x18\x10\x1dd\x80\xe2\x81]%\xbe\xd81<\
+\x9c@\xa3\xca\x8e\xa0\x12\xe1;\x08*\xdf\xfe\x94\xa0\x9d\
+\xe4\x90\xf7\xc4\xad/\x80N\xf0Y\x86\xbb\x05C\xaf\x82\
+o\x1f\x1a7\x01\xf1\x90CV\x9fd\xcc\xf7\xce\x0f \
+\xb8Br\x95\xb5\xb7\x81q2\xd3\x17\xed\x0d\xb2\xc8F\
+\x07\x9d\xe7\xdc.Q7\xdc\xbeq!7z\xe6\x18D\
+\x80y@\x18X9q\x1d[\x0b\x02\xc26\xb0\x18\x1c\
+4I\xaaz\xa2\x011i\xa6J`T\x01\xac\xb4\xc8\
+(\x03\xd3\xe0\xe4\x18\xad\x16\xe6z\x86\xb2y\xf5\xc9\x8a\
+\xa5\xca\xe2\xad)\x050W-\x95\xd5i6\x05U\xf0\
+\x9c\xf0\x9apAO5\xd5\xb3\xfe\x5c\xd3)\x05\xa2\x84\
+F9M\xc4\x1bi\xa1`\x06\x1e\x17\xfc\x1bi\x8a!\
+Q\xa2H\xb9\xe9\x9a\x86`\xd1\xa3\x94\xd688\x1b\xc6\
+\x12nH\x98<n\x82r\xd89\xbf\x80_\xb6\x96\xd2\
+\xaf\xbdP<{\xbb\xc3\xcaj\xe1\xed\x16\xb5\xcd\x1b\x13\
+\x91\xbe\xb8'P\x003\x83d\xcf\xe8a\xd1\xed\xc5!\
+fI \x81\xaa\x16\x0f1\xe8g\x8cS\xd3Q\x8d\x13\
+\xef\x1b'\x99o\x1a'!\x07\xdc0NB\x0a\xd6\x1a\
+g\xa5\xa5\xc68ka\xd80N2k\x8c\x93\x1c{\
+\xe3$\x97\xde8K\xaa1\xce\x9cn\x8c1\x12n\xa4\
+{c\xac\xfc\xf7\xd3\x8dq\x96to\x9c\xe4\xb8a\x9c\
+d\xbe\xdb8[\xcd\xddk\x88\xbbu\xd9\x9fU\x97[\
+#\xfd\xfc*\xb2\x11o\xfb$\x09\x98\x1f\x1e\x17!`\
+\xd5\x12\x16X~\xbe\xe6!\xe4\xb0\xfe\x89\xbao\xb5\xa9\
+ANP\x03\xca\xea\xc6\x90x2\x88\x95\xe8\xc5(N\
+\x8c\x83$2\xd4B\xe3\xa8,\x5c\x96J\xb5\x04Z\x80\
+\xf8i\xba\x94rMJ\x01k=\x1e\x10\x19S\xb2\x13\
+\xcd\x9c\x08\x964\xd3\x22\xb3\xd1\x89r\xb16\xc3\x94\x8b\
+0\x94ZT\x82\xf9b\xd2\x05\xa1^\xa9.HY\xad\
+3\xbf1\xc5\xba\xf2\x01\x8e,~\xc2\x10b\x04F^\
+\xacwN$\xf3\xcb2Q\x97\xac\x0a\x96\xe2\x8a\x94<\
+$6\xc0\xdc\xa1\x9c%\x80E\x1c\xd4\xdc\xbd~j\x97\
+\x93\xccu\x91\xb4\xfa\xc6\xb4\x84\xa6\xa6,\x90\x1a;\x95\
+\xfa\x05H\xa5\xe2\x04)\xa9\xd1=,\x7f=\xd9\xf2K\
+\x0d\x95Y\xd9\x97\xa9\xa2\xe9w\xac\x8a\x83\x02\x1f97\
+\xaaX\x89\xc2\xb8\xae\x8a\x85\xc6\xe0\xda\xab\xe2\xa0\xa8b\
+\x11\xb5\xaa\x22\xa7\xfc\xb1\x81R\x80dT\x15R\xd1S\
+\xac\xa4dN\xa2\x99\xc4\x80\x04\x99\x14\x83y\x82JR\
+\xe2Z\x90\x83D\xb6Z\xd08\xa2T\x12xD\xc6\x5c\
+=\x00\x8b\xd5\x82\x92H\x89\xfa\x16\xab>\x15\xef\xdd\xf7\
+\xcb\x17\xfdz\xbd\xde}[u\xbf\x17\xd3VbV(\
+l\x09E\xafmUHC7\xaa+x\x0f\x14\x93z\
+M\x10T\xf6\xe5\x22?-\x0b\xd7\x94-\x18\xd7k\xd2\
+EM\x0d\xf0\xf5\x1d\x16\xa5\x08\xc7\x90\x8c0\xa5O\xf5\
+1\xf7ID\xddH\xb7\xec}r\xdb\xea\x06X\xf7\xaf\
+\xa0\xfb\x9b(_n1\xf8\xbdZ\x8c\x07P \xb3:\
+z\xbd\xe3\x93\xa8\x5c\x07\xd8\xc0\xc0\xa8\xd0R23\xaf\
+j'\x86QW4+ZT\xfd\xdf\x1a\x89\x177\xc2\
+\x0a1-\x89\xbd+\xec\x89\xb5Bfd+4s\x82\
+j\x991&\xe7\xb8\xd1\xbf\xaa\x5c\x0b\xf5\xa9z\xc3\xb5\
+\x1a/6\x19\xb1\xd6\x9c,$fK<P\x0b\xae\xee\
+\x9a\xbbjAA\x0cy`\xb8p\xd6\x22\xc1\xb8t`\
+I*ec\x0a\x1e9!\xf5D\xe2\xcc\xe71\xe6&\
+R\x88\xa2\x1e\x8b\xd7\x0fHT\xca\xaa\x06Z\x86\x1d\x0c\
+\x8e\xb5\xab\xeb\x81G\x82\x19\x01\xf8`-\xf0`\x88\xe4\
+\x98p\xb0\x0c<\xb6\xecJ\x1f|R\xdf\xe1>\xf8T\
+\xa9\xd6\x83\x8f,\xa4oBP\xc1f\xab\xd1\x08\xef7\
+\x9aj5\xb3\x95\xc9\xd4i\xcf\x8f\xf9\x91\x8a\xfc\xf5\xff\
+\x01_\x1a\x15U\xdc\x8d\x00\x00\
+\x00\x00\x05:\
+\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
+\x00\x00\x00\x19tEXtSoftware\
+\x00Adobe ImageRead\
+yq\xc9e<\x00\x00\x04\xccIDATX\xc3\xb5\
+\x97]L[e\x1c\xc6wo\xbc\xd9\xe5\x12I q\
+\xd7&\xe3N\x13\xb8p\xd1\x85D\xbdP\xe3\x10\x18\xe5\
++.&J\x04'\x86\xaa\x8b\x99\xe0\xd0\xa2l\x19\x86\
+9\x17\xdc\x1a\x16\x98\x80@l\xa6C\xca +\x83\x1e\
+(\xcc\xda\xd1\x96\xd2\xd2J{\xfa\x01\xa5\xd0\xef\x16\x1e\
+\xdf\xff\xdb\x1d\xc7\xcc\x04*\x87\x93<9o!\x9c\xe7\
+\xf7<\xefG\x0f\x87\x00\x1c\xcaF\xcf\xbd\xfa\xe9\xbbL\
+Z&a\x0fj`\xca\xd9\xe9y\xd9\x9a?]P\xf2\
+\xa5\xc1\xe9\x8f\xa7W\xc3@0\x02\x84\xa2\x19\xad\xc72\
+\x8a'\x81X\x22s\xbfyk\xdaK\x10r\x02\x1c{\
+\xe7\xac\xda\x1c\xd8\xc8\x98\x12@\x84\x99\x85\xe3\x19\x911\
+)\x1aKa%\x94D8\x9aBs\x87\xc6\xbe\x13\xc4\
+\xff\x02\x90\x12\x93y$\xf1\xc8X\x92\xcf\x1f\x84]\x8c\
+\xc2\xe5\x09\x22\x12K\xa3\xf4\xc3\xefM4uY\x01\xb0\
+\xeb\xd86\xd5\x90\x9e:\xfc\xcc\xb9\xe7_.\x11?V\
+\x9eEEU\x0d*\x99\xde\xaf\xad\xc3\x9d\xb1\x89\xc7\x00\
+\xac\xb6%\xfc\xb9\xe8\x87k\x15X\xf6\x04\x10\x08\xc6\xd2\
+\xaf\x9c\xbep\x9fA\x1c\xd9\x15\x80]\x87\x99\x1a\x8a\x8a\
+\x8a\xcc\x92Z[[\xdd\xa4\xafU\xad\xfe\xafT\xdf\xa6\
+\x06\x06\x06195\x85\xd9\xb99\xe8&&PPP\
+\x80!\xcdo|\xdeI\xa6\xf9\x05\xcc\x98\x5c\x1c\xc0\xe1\
+OA\xf4\x85\xf0C\xaf\xce\xcd\x00j\xf6\x02PCf\
+\xd8\xe5\x8a\xc7\xe3\xf0z\xbdH\xa7\xd3\x98\x9c\x9cDe\
+e5fg\x8d\xbc\x81\x07f\x1bt\xd3\x16\x0e@2\
+-x\xf0\xdd\x8dQ\x8f\xac\x00\xe1p\x18F\xa3\x91\x8f\
+S\xa9\x14~\xea\xedE\xe3'\x9fa\x86A8\x96\xdc\
+Pwu\xe3LC#\xce5\x9d\xc7\xed\x91q\x5c\xbc\
+>,/\xc0\xc6\xc6\x06\xf4z\xfdc@}}\xfdP\
+2\x88\xd0F\x1cf\x9b\x0b\x82\xc1\x88\xa9\x19\x13\xac\x0e\
+\x11\x97\xbadn\x80\x00\xa6\xd8:\xd8~E\x22\x11\x94\
++*0\xae\x13@\xe7\x04mW\xda\xaa4\xbe|S\
+\xe65@f:\x9d\x0e\xc3\xc3\xc3\xe8e\xf5\xf7\xf7\xf7\
+C\xab\xd5\xa2\xaa\xba\x06cw\xf5\x90\x0e*w\x90\xed\
+\x04\xb6\x0e\xda\xbbe\x06\xa0y\xb7\xdb\xed\x18\x1a\x1aB\
+gg'zzz8PIi\x19ni\xf5\x10\xd7\
+\x00o\x08\xb0\xf9\x00g\x00\xb8\xd0%3\xc0\xd6\xd6\x16\
+\xdf\x09\x81@\x00\xa2(\xc2\xef\xf7cmm\x0d\xa7\x14\
+\x95\xd0\xfc\xae\xe7\xa9\xc9|\xc1\x0b\x98=@\x9b\xdc\x00\
+\xdbA677\xf9v\xa4V\x14\x15\xd5\xe8\xfbU\xe0\
+\xa9\x1d\x81G\x00\xe7;\x0f\x00\x80\xcc%\x80$3O\
+$\x12(+\xaf\xe2\x00\x7f\xb8\x00\x8b\x98\x01\xa06Z\
+\xd5\x070\x05\xff\x98'\x93<=MI\xc9\xa9J\x0e\
+\xa0\xb7\xb3\x03\x89=\xc5\xf8\x170\xb1\x00|q\xf5\x00\
+\x00\xa4\xea\xc9\x98\x14\x8b\xc5P\xa6\xa8\x82zH\xc0\x98\
+\x19\xb8k\x05\xe6\x9c\x99\xfb\xe7Wd\x04\x90\xd2Sj\
+\x02\x88F\xa3\xdc<\x14\x0a\xa1\xb8\xb4\x02\xd7\x06\x05\xdc\
+f\x87\xe4\xa0\x01\x1cd\xc4\x04(;d\x06H=\x9c\
+s\x12\x99\xd3\xb9@ \xc5eU\xb8\xd8-\xa0\x7f:\
+c\xae}\x90i\xe0\xa3v\x99\x00\xfe]=\xa5&\xad\
+\xae\xaer\x88\xb7J*p\xb9W\xc0=\x1b\xb8~\x9e\
+\x01\xee\xcc\x03g.\xed\x13@\xaa\x9dD\x8b\x8e\x92\xd3\
+qL\xdf\x01+++X__\xe7\x10'Y\x03\xdf\
+t\x09PO\x00\xbf\xcce\x1a\xb82\x064\xec\xa7\x01\
+\xc9X\xda\xebdNi)9\x1dD\x04@\xf5\xd3\xcf\
+\xde|[\x81\x96\xeb\x02O~u\x1c\xb8q\x0f\xf8q\
+,\x9e~\xbdNm\xa67\xaa\xac\x00\x9ed,m7\
+2%\x00\xd1#\xf2\xe4\x12\xcc\x1b'\x15h\xef\x11\xa0\
+\xbcf[\x7fO5\xe2<q\x9a\xbf\x8ei\xf7\xfcJ\
+&\x01\x90\xa9$i\xb5SB2\x0f\x06\x83p\xb9\x5c\
+\xdc\x90^J\xe8\xb3\xc7\xe3\x81\xdb\xed\xc6\xf1\x13\xaf%\
+\x9f}\xa1\x9cL;\x98\x8a\x99\x8e>\xc9xG\x00\x95\
+J\xc5\x01\xa4\x15.\xcd7\x19RR:\xf7)\xb5\xc3\
+\xe1\xe0\x22\xe3\xc5\xc5E\x0e\xf5\xe2\xf1\x97\x5c\xf4\x1e\xb9\
+\x93\xe9\xae\x00---n\xe9`\xa1\xd4\xd2\x97\x0d\x8d\
+\x97\x97\x97\xe1\xf3\xf9`\xb3\xd9\xf8}ii\x89C\x10\
+\x00\x8d\x0b\x0b\x0b\xcd\xb2\x00\xd0\xa2\x92R\x93\x11\x8d\xe9\
+N\xdfxT;5`\xb5Zy\xf5\xd4\x0a\xfd\xce`\
+0$\xf2\xf2\xf2\xee\xb3g\x1c\xd9\x17@SS\x93[\
+\x9agJO\x22\x13\xaa\x9a\xc6\x16\x8b\x997@\x9fG\
+GG#mmm\xde\xfc\xfc|\x13\xfb\xdbA\xa6\xb2\
+\xbd\x9a\xff'@ss3\x9f\x02JG\x10T?U\
+???\xcf\xeb\xd6h4\x91\xba\xba:\xe7\xc3\xb4]\
+L\x1f0\x1d\xcd\xc6xG\x00\xa5R\xe9v:\x9d\xbc\
+bJJo>\x94\xb4\xbe\xbe\xde\x99\x93\x93#\x99\x16\
+gSuV\x00\x8d\x8d\x8dn\x8b\xc5\x82\x81\x81\x81H\
+mm\xad377WV\xd3\xdd\x00\xf8\x7fFL\xc2\
+A\x99n\xd7\xdfC9V\x18\x85p\xc8\x04\x00\x00\x00\
+\x00IEND\xaeB`\x82\
+\x00\x00\x04_\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xc8\
+PLTE\xe1\xe1\xe1\xda\xda\xda\xdc\xdc\xdc\xdb\xdb\xdb\
+\xf8\xf8\xf8\xff\xff\xff\xe7\xe8\xe8\xeb\xeb\xeb\xeb\xec\xec\xec\
+\xed\xed\xed\xee\xee\xee\xee\xee\xee\xef\xef\xef\xef\xef\xef\xf0\
+\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xdd\xdd\xdd\xf4\xf4\xf4\xd7\xd7\xd7\
+\xd8\xd8\xd8\xd9\xd9\xd9\xdb\xda\xdb\xda\xdb\xdb\xcc\xcc\xcc\xe2\
+\xe2\xe2\xe8\xe8\xe8\xec\xec\xec\xf1\xf2\xf2\xd6\xd7\xd7\xea\xeb\
+\xeb\xe5\xe7\xe5\xd0\xdd\xcf\xe6\xe9\xe7\xed\xed\xed\xea\xea\xea\
+\xf3\xf3\xf3\xe9\xea\xea\xdd\xdd\xde\xde\xe6\xdf\x9e\xc5\x97\x82\
+\xb7{\xdf\xe8\xdf\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf5\xf5\xf5\xf5\
+\xf5\xf5\xf6\xf6\xed\xec\xed\xb8\xc9\xb7\x8c\xbc\x80\xa7\xd0\x87\
+\x8b\xbd}\xdd\xe7\xde\xf4\xf4\xf5\xf6\xf6\xf6\xf6\xf7\xf7\xf7\
+\xf7\xf7\xf5\xf5\xf6\xf0\xf2\xf1\xaf\xcd\xad~\xb4e\xa6\xd0\
+t\xb6\xd9\x81\x81\xb5r\xde\xe8\xdf\xf6\xf6\xf7\xf8\xf9\xf9\
+\xf9\xf9\xf9\xf1\xf1\xf2\xe5\xeb\xe6\x92\xbb\x85|\xb4I\x9d\
+\xcbR\xa3\xd0U\x9e\xcbSs\xa9\x5c\xde\xe7\xe1\xf8\xf8\
+\xf9\xfa\xfa\xfa\xfb\xfb\xfb\xf3\xf2\xf3\xdc\xe6\xdeu\xa7_\
+r\xad\x1a\x90\xc9\x19\x95\xcd\x1a\x8a\xc5\x13d\x9e;\xde\
+\xe7\xe2\xfc\xfc\xfc\xf2\xf2\xf2\xfa\xf9\xfa\xe1\xe9\xe3\x87\xad\
+uu\xae\x16\xa6\xdf\x0a\xa0\xd9\x0ag\x9d4\xde\xe6\xe2\
+\xfc\xfb\xfc\xfd\xfd\xfd\xde\xde\xde\xf9\xfa\xfa\xfd\xfc\xfd\xeb\
+\xed\xed\x92\xab\x8fu\xa6.\x95\xcc\x08i\x9b0\xdd\xe5\
+\xe2\xfe\xfe\xfe\xf2\xf1\xf2\xe1\xe0\xe2\xc5\xd2\xc9j\x92K\
+N|1\xdd\xe5\xe1\xe2\xe7\xe4\xa8\xbb\xac\xef\xf2\xf0\xde\
+\xdf\xdf\xf9\xf9\xf8\xf0\xf1\xf1\xdc\xdd\xdd\xdf\xdf\xdf\xd0\xd0\
+\xd1\xd2\xd2\xd2\xd3\xd5\xd6\xe1\xe2\xe3\xd4\xd4\xd5\xc5\xc5\xc6\
+\xcc\xcd\xcd\xc8\xc9\xc9\xe0\xe1\xe1\xbc\xbc\xbd\xe3\xe4\xe3\xd9\
+\xda\xd9\xdf\xe0\xdf\xe5\xe5\xe5\xe6\xe6\xe6\xbc\xbd\xbd\xd5\xd5\
+\xd4\xd1\xd3\xd4\xc5\xc7\xc7\xe5\xe5\xe4\xd3\xd3\xd3\xe0\xe0\xe0\
+\xdf\xe0\xe0\xd9\xda\xdb\xca\xce\xd0\xe2\xe3\xe4&\x9b[?\
+\x00\x00\x00\x01bKGD\x05\xf8o\xe9\xc7\x00\x00\x00\
+\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0k\
+B\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x14\
+2\x1f\xc9N\xa7\x00\x00\x01[IDAT\x18\xd3M\
+\xd0US\xc3@\x18\x85\xe1\xcd6\xa1JH\x03\x05J\
+\xd1\x12\x8a\x14\x82S\x82KpIq\x08\x0e](\x1a\
+\xdc\x83\xbb;\x7f\x97\xdd\x0c\x17\xbc\x97\xcf\xec|;s\
+\x00\xa0\xe0\xbfL4\x83\x03T\x84\xd9b\xb5\xd9\x1d\x91\
+,\x1b\x15\xc59y\x03\xa1\xd9\xc6Z\xa2c\x5c\xb1\x14\
+\x15\x17g\x8a\xa7\xdcn\x90\x00\xa0\x87M\x8cHJN\
+IM\xb3x\xac^\x8f\xd7nO\xa7\x00\xb4qB\x86\
+/3+;\xc7\xef\xcf\xe5\xf3D1\x9f\x83\x00:\x04\
+\xb1\xa0\xb0\xa8\xb8\xa4\xb4\x8c\x17\x03\xe5\x92$\x09\x18\xd9\
+\x8a\xca\xaa\xea\x9a\xda\xba\xfa\x86\x80D7\xca\xb2\xcc;\
+\x01ljnimk\xef\xe8\xec\xea\xa6e\x05\x17\x14\
+1\xf6\xf4\xf6\xf5\x0f\x0c\x0e\x0d\x8f\x10\xc0\xa9\x01\x8c\xa3\
+c\xe3\x13\x93S\xd33\xb3!\x0c8$\xcd\x01(\xcc\
+\x87\x17\x16\x97\x96WV\xc3D\x10\xd2h\x8c\xbc\xa2\xae\
+\xadolnm#L\x9a\xa612\xc1\xa0\xca\x81\xd0\
+\xce\xee\x1e!\x06k\xe3\xbe\x81\xa3@\x09i*y\x86\
+\x0ex\xe1\xf0\x08@\x91 \xf9\x03c\x90\xd3\x8fON\
+\xcf\x08\x22\x03\xb1!\xee\xfc\xe2\xf2\xca\xa7bT\x91\xf0\
+\x87\xf2\xf5\xcd\xed\xdd=b\x80\x13<<\x9a\x5c\xaa\x81\
+\xe2\xc9\xd33\xd0\xf0\x9e\x8a$\xd1\x1e\x9d\x5cd\x18\xfe\
+\xe5\xf5M##\x93\xa5\xc5w]\xd7?>>\xbf\xbe\
+\x7f\x881\xbf\xe0\xd3^\x99\xad\x16c\x0b\x00\x00\x00%\
+tEXtdate:create\x00\
+2023-03-08T17:27\
+:26+00:00\x9e\x98\xa5\x85\x00\x00\x00\
+%tEXtdate:modify\
+\x002023-03-08T17:2\
+7:26+00:00\xef\xc5\x1d9\x00\x00\
+\x00(tEXtdate:times\
+tamp\x002023-03-09T\
+06:20:50+00:00\xdd\xe8\
+\xb2\xde\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x08\xab\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x07tIME\x07\xe7\x03\x09\x07\x084\x11\x1f\xdc\
+\xf8\x00\x00\x07fIDATX\xc3\xbd\x98\xcb\x8f\x14\
+\xc7\x1d\xc7?]\xd5=\xd3\xc3\xee\xcc\x02\xfb\xf0.O\
+\x11;<\x9c\x04\x93\x98\x87,[\xa0$(\x87X\x16\
+9p\xc8\xe3\x1a)\x87\xdcsD\x11\xffD\xfe\x03K\
+Q\xe2(H\x91\xa5\x90\xd8rp\xf0\x22\xe2\xc4ka\
+X\x83\xd8\xdd\xc0\xbe`\x96\x9e}\xf5tOwuU\
+\xe5\xd0\xd3\xc3B\xd8e\x0d\x83K\xaa\xe9\xae\x9e\xee\xea\
+\x8f\xbe\xbfWU;<G;r\xe6\xdc\xaf\x81S\xc0\
+.\xa0\xb4\xc9\xc7\x16\x80\x7f\x00\xef\x02\xf5\xb1\x0b\xe7[\
+\x1b\xdd\xec>#\x98\x00\xfcjO\xe5\xed_\xfd\xfcG\
+\x87\xf6\xec\x18\xdc\xe6J!\x1c\x07\x1c'\xbf\xc7\xe9\xfc\
+\x80'\xc0\xda\xfc\xbf0j\xed\x1e\x9f\x98\x1d\xfa\xdd\xbb\
+\x1f\xd4\x81\xf7\x81{]\x07l\xab5\xf4\xd2@\xdf\x9e\
+\x97\xf7\x0e\x0f\xbc\xb2w\xb8\xc7\x95\xc2\x01p%`\xe9\
+P\x0a@\x0a\xb0\xc6b\xac\xa5\x19\xa7[\xfaz+=\
+c7\xee\xfcxz\xbe\x11q\xe6\xdc'c\x17\xce\xdf\
+]\xefE\xe2\x19\x01=\xa0\x7f`{\xcd\xab\x94K\xd2\
+\x95\xc2\x11\x0e\xfc_'?Z\x0bZg$i\x86#\
+\x84\xdc\xbb\xa3\xbf\xe7\xad\xa3\x07\xde\x1c\x1e\xec{\xa7\xd6\
+[9q\xe4\xcc\xb9\x81n+\xd8iR\xe4\xddiC\
+\xc1\xc3\xe3\xda\x96\xa4\x09-\x05\xca8H\xab\xf8\xc9\xe9\
+\xd7\x87\xa7\xe7\x1bo\xd6\x83\x95l%\x8c\x15\xf0\xe7'\
+\xcd\xef\xb0q\xdb\x01\x8c\xac\x19\xef\x07\xaa\xd2\xab\xf8~\
+u`\xe8\xc8\xf1S\xbf<rp\xd76\x89*\x85\xe1\
+*\x0e\xd0\xdb[\xe5\xe4\xc9S\xbcv\xe4\xbb\xec\xdc\xb9\
+\xab\xf3\xe0\xc2B\x9d(\xd58\xee\x16\xb6\xf4\xf6!\x92\
+\x00\xaf\xe4\x9b\x8b\x97\xaf\xd7\x7f\xff\xfe\x95\x0f\xa6\xe7\x83\
+\xdf\x8c]8?\xb7Y\x05k\xc0\x1b\x87\x0f\x1f>9\
+<<\xfc\xad\xe2\xe2\xa1C\x87\xb6V\xab\xd5\x92\xb5H\
+m\xf1\x91~m\xdf\xee\x11w\xeb\xd6\x1a\xbe_\xc6u\
+]\x94\xca\xf8\xe7\xc7\x1f3;;\xc3\xa9\xef\xff\x80\x03\
+\xfb\x0f\x00\xa0\xb5&S\x0a\x87\x0c\x80L\xf6 \x8cv\
+F\x06\xfb*G\xbf\xf3\x8d\x1d\xd3\xf3\xc1\x1b\xc0{\x9b\
+\x05<|\xf6\xec\xd9\xb7O\x9c8\xf1\xd6\xbe}\xfb\xf6\
+\x14\x17\x87\x86\x86\xbcr\xb9,\x00\xacEhc\xcb\xb5\
+Z\xaf\xa8T*\xb8R\xa2\xb5\xc6\xf5<\x16\xea\xf7\x09\
+\x82E\xc6>\xfb\x0f\xfd\xdb\xb6308\x08X\xb2L\
+\x03*7\x9d,\x91\xb4V\x1d\xc7qdO\xa5\xec\x03\
+\xfdO\x02Y\x0f\xf0\xd5\x83\x07\x0f\x1e=y\xf2\xe47\
+\x8f\x1f?\xde\xfb4?\xcc\xb2\x8c8\x8eYYYa\
+dd\x84\xd3\xa7O\xf3\xc9\xe8(w\xefL3~\xe3\
+:\xdf;z\x0ckA\xa5\x09\xcd\x95\x90V+FJ\
+\x17\x89&\x8a\x22\xb4\xd6\xeb\xba\xdas\x07I\xae\xa6%\
+MS\x96\x96\x96\xd8\xb9s'\xbbw\xef\xe6\xb5f\x13\
+\xa52>\xba\xf4\x11\xfd\x83\x83\xd4\xaa5\xe2\xe627\
+\xc7o011A\xadV\xe3\xd0\xab\xdfF9=h\
+\xad\xd7\x9d\xbbk\x80Zk\xe28\x06@J\xc9\xfe\xfd\
+\xfb\xe9\xeb\xeb\xa3V\xad\xf2\xa7\xf7\xfe\xc0O\x7f\xf6\x0b\
+^~e?\x03C/\xf1\xfa\xd1\xe3\x18+\xa8\xd6j\
+L\xdf[\xe6\xbf\xf5\xf5\x8bIW\x00\xd7\x82v&v\
+]\xfa\xfb\xfb9v\xec\x18\x7f\xbdx\x91;w\xee\xe2\
+o\xa9\xb1\xbd\x7f\x98\xdaV\x8dp\x1c\x84\x94<X\xc9\
+\x90R\xda\xf5\xe6\xec\x0a\xa0\xd3\xaeo\xc6\x18\xa6\xa6\xa6\
+H\xd3\x14k-\x8e\xe3 \x84@)E3\x8a\xc8\xb4\
+\xa6T\xf6)\x01\xda\xe4\x09\x1c\xc7e\xa3z\xd15@\
+)%\xae\xebr\xfb\xf6m\xc20\xc4\x18C\xb9\x5c\xa6\
+V\xab\xb5\xe1-\xc6<\xac\xc9I\x9em\xc8\x0ch\xbb\
+\xfe\xdc\xcfZ\xea\x1e\x9dD\x08|\xdf\xa7\xbf\xbf\x9f$\
+I\x88\xe3\x98$I0\xc6\xe0y\x1ei\x9a\x92d\x96\
+T\x83j\xf7$\xcbU,\xfa\x0bUP\x08A\xa5R\
+add\x84j\xb5\x8a\xd6\xbac^\xd7u\x91\xae\x87\
+\xb5\x0e\xc6\xe4p\xa9\xce\x95L\xb2|\xbcQ\xebZ\x90\
+8\x8e\x83\xe7y\xd4j5\x8cyT\x12\xe1\x082\xe3\
+\xa0\xda`B@\x91Y\xb4\x814\xfb\x9a\x00\x8bn\xad\
+}\xac\x83\xb1\xa0\xb4C\x92\xe5\x80\x00n\xb1\xd0\xd8\xc8\
+:\xdd\x02\x84G\xd3\x8cm\x8f\xad\xcd\xd7\x81\x90\x07D\
+K\xe5Gc\xdbQ\x0c\x1b\x12vM\xc1\x02\xceZ\xfb\
+\x88\x89\xd7\x02j\x03\xca\x80\xdb\x0e\x8c(\x85\xe5\xf8k\
+2q\x01S\x00\xae\x05\x16\x227m\x94B)\x818\
+\x85\xder\xae\xe2\x0b\x8f\xe2\xb5 \x8f\x9f\x17%P\xeb\
+<\x07j\x03\xad47\xb1\xc3\xc3\x94\xb3\x11`W|\
+p=\xe5\x8ajb\x81D\xe7\xfe\xa7t\x1e\x18Ez\
+\xb1\xe4\xc0/\x14p-P\x96e\x18c:\xb0B\x08\
+\xc0!\xd3y\xc5\x90\x02\xb6\x94\xf2\x5cX\xec\x00\xcd\x06\
+\x95\xe4\xb9L\xbc\xd6\xac\xc58M\xd3\xce9\x80\xe7y\
+\x18cpE\xeew}[\xf2{\x9ba\xbew)\xca\
+_W\x01\xd7B\x15J\x19cPJ!\xa5D)\xd5\
+\xc9\x89I\x92\xe0\x08AK\xc1\xfd\x15\x88=\xa8\x96\xa1\
+\xaf\x02sK\xd0h>y\x93\xf5L\x80\x8f\xab\xb5\xd6\
+\xf7\xb4\xd6XkQJa\xad\xc5u]\x5c\xd7E\x08\
+\x811\x86\xccX\x84\x03\xd2\x81\xc5(\xf7G\xa33\x13\
+\x85+i}\xe1\xc1\x22p\xabk\x80\x8fG\xac\xd6\x9a\
+,ko\x86\xb2\x0c)eG]c\x0c\xd6X\xa4\x03\
+q\x1c\xeb\xb8\xfe@\x85\xcdf\xda\x5c]N\xe38\x8a\
+\x1e,\xd4\xa7\x1a\x8d\xe0_\xcf\x0c\xf8\xb8jk_\x5c\
+\xa8\xa3\x94\x22\xcb2\x5c\xd7\xc5\x18\x83lo\xa0\x8a\xae\
+Tf\x97\x17\x83\xa4\xc9\xf4r\xac\xfd \x8d\x16\x17\xee\
+\xdc\xbd[_l\x04\x0f\x92\xa4u\x0b\xf8\xf4I[\xce\
+\xa7\x02>\xa9*\x14\x8b\xd3b\x1f\x91\xa6)I\x92t\
+\x14,|\xafP\xd5ZK\x18\xaef\x7f\xff\xf0\xc3{\
+\x99\xfb\xd9\xcdR\xcf\xb6\xcf\x81\xcf\x81O\x81\x99\xb1\x0b\
+\xe7\xa3\x8d\x18\xd6\x05|\xdc\xc7\x0a\xc5rE\x14J)\
+<\xcf\xeb('\x84 \x0c\xc3\x8e\x99\x0b\xf5\x5c\xd7\xc5\
+f\xf1}u\xff\xd6o\xc7\xc7o\xfc\x0d\x98\xe3+\xb4\
+u\x01\x0b\xa8\x22J\xad\xb5DQ\x841\x06\xc7q(\
+\x97\xcb4\x9bM\x84\x10\x1d \xdf\xf7;j>\xe2\xa3\
+\x99J\xb4\xde\xa8^<\xa3\x82k!\xb3,#\xcb\xb2\
+\xceBTk\x8d\x94\xb2\x93\x8c\x8b\x08\x96Rv\xae+\
+\xa5l\x14Efii1M\xd3\xa4\x09$]\x07,\
+\xc0\x0aU\xa4\x94\x9d\xfc\xe6y^\x9e\xe7\x9c|U\x9d\
+$\x09\x80\xd1Z\xeb0\x0cu\x10\x04\xe9\xc2\xc2B\x14\
+\x04\xc1T\xab\xd5\xaa\x03q\xd7\x00\x0b\xb8b\x8f\xe1y\
+\xde#\xe0E\xde\x8b\xe3\xa8\x1d\xb9.\xbe\xefs\xeb\xd6\
+\xad\xd6\xc4\xc4D<::\x1a^\xbe|9\x18\x1f\x1f\
+\x9f\x05\xfe\x08\xdc\x06\xa2\xcdam\x02\xb0X\xc2\xfb\xbe\
+\xdf\x09\x8aR\xa9\x84R\x8a4M\x89\xa2\x08\xdf\xf7\x99\
+\x9e\x9eI\xae_\xbf\x1e^\xbdzu\xe5\xd2\xa5K\xe1\
+\xe4\xe4\xe4\x17\xc0M\xe0\xdf\xe4\xd1:\xfdU\xa16\x05\
+\xd8j\xb5\x08\xc3\x90\xe5\xe5\xe5\xb6\x19\x1d\x8c14\x9b\
+\xcdlnn.\xb9v\xedZstt4\x0c\x82`\
+.\x8a\xa2/\xe38\xfe\x22\x8e\xe3\x9b\xc0\xdd\xb6R\xab\
+@\xf8<pO5q\x92$\xac\xae\xae\xdaF\xa3\x91\
+\xcd\xcc\xcc\xc4\xf7\xee\xddK\x83 X\x0a\x82`~~\
+~\xfe\xf6\x95+W\xbel4\x1asq\x1c\xcf\x00\xb3\
+\xe4\xdf\x9bW\x9f\x17j3\x80\x0bKKKs\x93\x93\
+\x93\xc3q\x1c\xabz\xbd\xbez\xed\xda\xb5\xa9\xb1\xb1\xb1\
+\x85\xa9\xa9\xa9\xd9F\xa31\x09\xdc\x00\xc6\x80e\x8ao\
+j/\xa0\xad\xb7\x8e\xd8\x0e\xbc\x03\xfc\x10\xd8G\xeeG\
+\x7fi\x03Mw[\xa5\x8d\xda\xff\x00\xaa\xf0\xe8\xc2\xf9\
+\xe1sN\x00\x00\x00%tEXtdate:\
+create\x002023-03-0\
+9T07:07:05+00:00\
+\x86^qC\x00\x00\x00%tEXtdate\
+:modify\x002023-03-\
+09T07:07:05+00:0\
+0\xf7\x03\xc9\xff\x00\x00\x00(tEXtdat\
+e:timestamp\x002023\
+-03-09T07:08:52+\
+00:00\xdcZ\x83G\x00\x00\x00\x19tEX\
+tSoftware\x00Adobe \
+ImageReadyq\xc9e<\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+\x00\x00\x07S\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\
+bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\
+\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\
+kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\
+\x12)\xc3\xf6 \xcd\x00\x00\x06\x1eIDATX\xc3\
+\xed\x98\x7flUg\x19\xc7?\xcf\xf3\x9eso\xdb{\
+KK\x91\x14j\x99\xb0\x91at\x85L\xcc\xdc\x92\xa9\
+Yf\xf6\x07\x84\xb9\xbf\x88#F\xe3\x8cN\x83\xc6D\
+\x13g\x8cQ4j\xfckJ\xc4-Y\xd0?dK\
+\x8c\x083\xcb\xe6\x88\x8e\xe8\x00\x8dc6\x94Z\x0b\x12\
+f\xa1\xb2\x8c\x1f\xa3\xf4\xd7\xed=\xe7=\xe7\xbc\xaf\x7f\
+\x9c\xd3R\x12\xe7nK)\xfb\xc3\xe7\xe6\xc9\xbd77\
+\xe7\xbd\x9f|\x9f\x1f\xef\xf3\xbe\xe2\xbd\xf7\xbc\x83Mo\
+6\xc0;\x0a\xd0y\xcf\x5c\xe3\x15,\x06X\xe6<G\
+\x07/b\xd3\x8c\xd5+Z)\x85\xca\xcae\x95\x86\x9e\
+\xbd\xe1\x0a\x8eN\xc6\xec\xdc\xdb\xcf\x0f\xf6\xfc\x8d\xf1(\
+\xe1\x8d+S\xfc\xf2w\x83\x8cN\xc4\x0d=\x7fC\x15\
+\x1c\xf8\xd7\x08;\xf7\x1e\xe7\xe2h\x9dOl~\x1f+\
+WwR\xab[\xce\x5c\x18c\xb2nio-\xdf\x1c\
+@\x9b:\x9e;<\xc4/^\x18d\xcd\xaa6\x1e\xdd\
+\xb6\x91\xd6%-L\xd4-\xb5\xa9\x88\xc8\xda\x86\xd7Z\
+p\xc07.O\xf1\xe4\xb3\x03\x1c=q\x9e\xfb\xef\xbe\
+\x85\x0d\xef_\x89\x13a\xa26\x85\x00qd\xc9\xb2\xf4\
+\xe6\x00\xfee\xe0<\xbb~\xd3\x8f\x17\xc7\xc3\x9b\xd7\xd1\
+\xb1\xacB-\x8a\x99\xddj\xa3\xc8\xe2\xb2\x0ch\xac\x9c\
+\x17\x04\xb0VOx\xe6\x0f\xa7\xd8\xff\xf2i\xeeX\xdb\
+\xc1\x07{:\x91@\x99\xacE\x00\x08\xbe\xc0\x11l\x9c\
+\x14\x80\x8b\xa4\xe0\xe9sc\xec\xda\xd7\xcf\xe9\xd7\xafp\
+\xff\x87\xba\xe8\xea\xac\x12\xa7)\xa4 \xc8\x8cNR\xbc\
+[\x9b\xe0\xdc\x22(\x989\xcf\xef\x8f\x0e\xf3\xd4s\x03\
+,\xa9*\x9b>\xfcnJeC-\x8a\x11\x04\x11\x01\
+\x91\x19\xb8i\x9c$\xc9\xf0\xce5\xca7?\xc0\xcbc\
+\x11?\x7f~\x90\x83\xbdg\xe9\xb9m\x09k\xba[q\
+>#\x8a\x1c\xa2\x8a\x88\x5c\x85\x9c-\x1f\x90\xa6Y\xa1\
+\xe0\x0d\x02<v\xea\x12\xbb\xf6\x1d\xe7\xd2\xe88\xf7\xf4\
+\xb4\xd3Z\x0d\xa9\xc7\x16UETQ\xf19\xa0* \
+\x85\x88W\x09\xd3\xd4\xe5\x0a.t\x88#\x9b\xb1\xff\xe5\
+\xd7x\xfa\xc0?\xe8X\x02\x1b\xd7U\x09\x8c#\x8eR\
+\xc4(\xaa\xcc\xb8\x88\xa2\xce\x81\xe8t\x94g\x183\xe7\
+q\xce-\xac\x82\xff\xbe8\xc9\x93\xcf\xf6\xf3\xca\xc00\
+k\xbbB\xde\xd5^\x22I\x122\x17\xa0\x06\x8c\x07U\
+AU0\xc6#\xea\xf1\x92\xab7\x93\x8b\x85`\xce\x09\
+\xf9\xc4\xb0\x00\x0az\x0f\x87\xfa^\xe7g\xfb\xfb\x18\x1f\
+\x1f\xe1\xf6\xae\x80\xa6rF\x14'\x98 \xc0x\xc1x\
+\xc1{\xc1\x18\xcd\x9b\x89\x14\x1b\xbc\x16yX\xbcf\xd6\
+D\xf0~\x01\x8ad\xbcf\xd9s\xe0\x04{\x0f\x0e\xd0\
+\xd6\x14\xb1\xaa\xc3\xe0RGLH\x10\x08\xce\x0b\xde+\
+xG\x8e\x94\xc3\xe5!U\x10AE\x8a\xdffU\x09\
+z\xcd\xb7y\x01\x9e<;\xc2O\x7f\xddK\xdf\xc9!\
+:\xdb2*e!\x8aRL\x10\x10x\xc1\xa1\x84(\
+\x88\xc3\x8b\x07\xc9\xe1p \x85r\x1e\xc5\xa3\x85z\xb3\
+\x90D\x11\xf13\xad{\xce\x80c\x931\xdf\xdd}\x84\
+3\xc3\xc3t-\xcdP\x0f\xf5:\x88*\xa1\xa3\xf8\xe3\
+\x0c0 \x0e\xc4#\x9a3\xaaW\x9cW\xd4\x0bL\xbb\
+\x5c\xadf\xef\xf3\x02\x12\xfc\xfcC\x1c\x06J\xf7\xf2\x0a\
+Cga|\x22B\xc8\x08\x03C\x10\x96@\x0cH\x06\
+d\xb3\xe0r@5\x82\xfa\xdc\xbd\x17<ExE\x8b\
+~8]4Y\x91\x06\xf3T\xb0\xa5)\xe4[\x8f\xdc\
+\xcb\x9e\xce*\xcf<\xffW\xde\xbcp\x0e\x9f\xd4\xa9\xb6\
+Vhkk\xa3\xb5\xb5JKEh\x96\x00c<\xde\
+K\xa1\x8a\xe6\xedE\x14U\x83QC\x10\x18\x02c0\
+F\xd1\xa2\xaaK\x81`T\xe6\x0f\x08\xd0V-\xb3}\
+\xeb]\xf4\xac]\xce\xce\xa7\xff\xc4\xf1\xc1S\x8c\x8c\xd6\
+\x18\x1d\x8f\xa9T\xa6h\xa9T\xa8T\xabT\xabU\x9a\
+[\x12ZZ2ZZ\xa0\xb9I(\x97\x85rY\x01\
+S\xec(\x0a(\x19\x82s\x9e8\xf1d\x0b\xb1\xd5\x89\
+\x08\x1f\xddx+\xb7uw\xf0\xc4\xaf\x0e\xf1\xe2\xe1~\
+\x12\x1b\x83\x172\x076q\xd4\xa3\x14/)\x8e\x94\xd4\
+'\xd8T('\x85\x97\x94rY\x09CO\x10\x08F\
+\x15\xef=\x89\xd3\x86\x0b\xe4\x7f\x02N[wg;\xdf\
+\xf9\xe2&z\xd6\xae`\xf7\xbeC\x8cNYJA\x09\
+UE\x8d\xa2\xc6\xa0\xc6`4\xffl\x8c\x12\x98\x22\xbc\
+A@)\x0c)\x95B\x8c1\xf9\x5ch}\xd1\xb7\x1b\
+\x83l\xe8\xd0T.\x05<\xbc\xf9.\x1e\xff\xfaV\xd6\
+\xafYN\xea\x22J\xcd!\xaaff\xb7\xb8\xc6UP\
+\xd5\x99<\x0c\xc3\x90r\xb9D)\x0c\x09\xc2\xe0\xda\xf1\
+f!\x00\xa7m\xc3{o\xe1\xf1olc\xeb}=\
+D\x13\x17\xc9$\xc6\x84\xc1[7\xde\xd9\xed\xaf\x98n\
+<y\xf7i\x94p\xce\xc7\xce\xa5m\x15\xbe\xf6\xd9-\
+\xecxt\x0bMv\x84\xcb#\xe7\x90R\x0e\xe3\xbd\xcf\
+\xbd\x18\x08\x5c\xe6\xc82G\x9a\xa4\xc4\xb1\xc5ZK\xea\
+\xdc\x9crp^\xe7bU\xe1\x81\x8f\xdc\xc9\xaeo?\
+\xc2\x07\xba+\x0c\x9d:\xc6T6\x09*9\x94sd\
+YF\x9a\xa6\xd8$!\xb6\x96(\x8e\x89b\x8bMS\
+\x9co|\xdc\xba\xae\x83\xfb\xea\xeeN~\xf4\xd8\xa7\xf9\
+\xc2\x83ws\xfe\x9f\xbd\x0c_8M\x1a@\x96f$\
+\x05\x9c\xb5I\x0e\x17\xe5n\x93\x147\x87\x9d\xe4\xbao\
+\x16\x9a\xca%>\xb3\xf5\x01~\xf2\xd8\xa7h\xaf_\xa0\
+\xaf\xef0c~\x8a\xd4y\xacM\x88cK\x14O+\
+\x18\x13'\x09\x19\x8d\x8f[\x0bv\xf5\xb1q\xfd\xed<\
+\xf1\xbd\xedlY\xdfM\xff\x91\x03\xbcv\xf9\x0c\x91:\
+\xe2\xd8\x12GW\x15\x8c\xec4\xe0\x22)8\xdb\x96-\
+]\xc27\xbf\xbc\x8d\xef\x7f\xfe!j'^\xe5\xd5\xfe\
+?sE-q\x9a\xe6pqL\x94$d\x8b\x95\x83\
+\xffuAU6}\xec\x1ev\xff\xf0+\xdcY\xcdx\
+\xe5\x8f\xbfe\xb8~\x89H<q\x14\x13\xc7\x16\xe7\x17\
+1\x07\xdf\xcan}O\x17?\xde\xf1%\xbe\xfa\xe0\xbd\
+\x0c\x1fy\x91\xbf\x0f\x1dc\xb2\xecIBA\xb2\x94F\
+\xa7\xd6\x1bz\xfd\xd6\xdcT\xe6s\x9f\xfc8O\xed\xd8\
+N\xe7\x95aN\xf6\xbe\xc4\xb8\x8c\xd0Q-\xd1T~\
+\xfb\x9b-\x00Y\xac;\xea7GF9x\xa4\x97\xf3\
+^\xb9c\xd5\x0a\xee\xdb\xb0\x0e5o\xaf\xcf\xa2\x01\xce\
+\xd7\xfe\x7f\x89~\xbd\xf6\x1fu\xd4\xd8/\x83\xf6\xd6\x89\
+\x00\x00\x00%tEXtdate:cre\
+ate\x002023-03-08T1\
+7:27:26+00:00\x9e\x98\xa5\
+\x85\x00\x00\x00%tEXtdate:mo\
+dify\x002023-03-08T\
+17:27:26+00:00\xef\xc5\
+\x1d9\x00\x00\x00(tEXtdate:t\
+imestamp\x002023-03\
+-09T06:18:41+00:\
+00=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x04+\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xbf\
+PLTE\xff\xff\xff\xfe\xfe\xfe\xf2\xf2\xf2\xef\xef\xef\
+\xf8\xf8\xf8\xd5\xd5\xd5\xe2\xe3\xe3\xe3\xe4\xe4\xe3\xe3\xe3\xe4\
+\xe4\xe4\xe4\xe5\xe5\xe5\xe5\xe5\xe6\xe6\xe5\xd6\xd6\xd6\xf7\xf7\
+\xf7\xd9\xda\xda\xdc\xdc\xdd\xc1\xc2\xc2\xc8\xc9\xc9\xe9\xea\xeb\
+\xed\xee\xf0\xed\xef\xf0\xee\xf0\xf1\xee\xf0\xf2\xef\xf1\xf3\xe7\
+\xec\xf3\xe4\xea\xf3\xf2\xf3\xf5\xda\xda\xda\xda\xda\xdb\xcf\xd0\
+\xd0\xc4\xc5\xc4\xb8\xbb\xbe\xd0\xdc\xec\xd2\xe0\xf4\xcc\xdd\xf4\
+\xc7\xda\xf4\xc2\xd7\xf4\xbd\xd4\xf4\xb7\xd0\xf4\xb3\xce\xf4\x94\
+\xbb\xf3q\xa7\xf2\xca\xdc\xf4\xdb\xdb\xda\xdc\xdd\xdc\xb9\xbd\
+\xc3\xc3\xc5\xc8\xea\xec\xed\xee\xf1\xf3\xef\xf1\xf4\xef\xf2\xf5\
+\xef\xf2\xf6\xe5\xec\xf6\xdf\xe9\xf6\xf2\xf5\xf7\xdb\xdb\xdb\xd9\
+\xd9\xd9\xef\xf0\xef\xd8\xe4\xf3\xe7\xec\xf2\xf2\xf3\xf2\xf3\xf3\
+\xf3\xf4\xf5\xf5\xf5\xf6\xf6\xf6\xf7\xf7\xf9\xf9\xf8\xfa\xf9\xf9\
+\xfa\xfa\xf9\xf9\xf9\xf9\xf1\xf1\xf1\xd5\xe2\xf4\xf4\xf4\xf4\xf5\
+\xf5\xf5\xf6\xf6\xf6\xfa\xfa\xfa\xfb\xfb\xfb\xd1\xe0\xf4\xe6\xec\
+\xf5\xf6\xf6\xf5\xfc\xfc\xfc\xf4\xf4\xf3\xcd\xde\xf5\xf8\xf7\xf7\
+\xf9\xfa\xfa\xdc\xdc\xdc\xf5\xf5\xf4\xc9\xdb\xf5\xe4\xec\xf7\xfd\
+\xfd\xfd\xda\xdb\xdb\xc5\xd9\xf5\xe3\xec\xf8\xfb\xfa\xf9\xf7\xf7\
+\xf6\xc0\xd7\xf6\xe2\xec\xf8\xfc\xfb\xfa\xfe\xff\xff\xf8\xf8\xf7\
+\xbc\xd4\xf6\xe1\xeb\xf9\xfc\xfc\xfb\xd8\xd9\xd9\xb7\xd1\xf6\xdf\
+\xea\xf9\xfd\xfc\xfb\xee\xee\xee\xde\xde\xdf\xd8\xd9\xda\xcf\xd1\
+\xd1\xfa\xf9\xf8\xb3\xcf\xf6\xde\xe9\xf9\xea\xea\xea\xcc\xcd\xcd\
+\xc3\xc4\xc4\xc9\xc9\xca\xc9\xca\xca\xed\xee\xee\xa3\xc5\xf6\xd3\
+\xe3\xf9\xfe\xfd\xfc\xf2\xf3\xf3\xd0\xd0\xd1\xc5\xc6\xc6\xe9\xe9\
+\xe9\xd4\xd5\xd4\xf6\xf7\xf9u\xa9\xf4\xb9\xd3\xf8\xff\xfe\xfc\
+\xbf\xc0\xc0\xcb\xcb\xcc\xd7\xd7\xd6\xfb\xfb\xfa\xbf\xd7\xf8\xe2\
+\xec\xfb\xff\xfe\xfd\xe8\xe8\xe8\xc1\xc2\xc3\xcb\xcb\xcb\xf0\xf0\
+\xf0\xd3\xd3\xd3\xe2\xe2\xe3\xe3\xe3\xe4\xd5\xd7\xd8\xc8\xca\xcc\
+\xea\xe9\xe9E\x17\xd3\x0b\x00\x00\x00\x01bKGD\x00\
+\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\x00`\
+\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07tIM\
+E\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\x010\
+IDAT\x18\xd3U\xd0WS\xc2@\x14\x86\xe1]\
+X\x81\x18\x88]@c\xef\xbd\x00\x8a\x05\x114\x8a\x04\
+%\x16\xd4\xd8{\xc3\x8a\xbd\xc5\xde{\xf9\xc1nvs\
+\xe3{\xf9\xcc\x99\xf9f\x0e\x00P\xa7\xff\x97\x0e\x02\x00\
+P\x9c\xc1`4\x19\x19&\x9eeY\xb3\x99\xb5 \x8c\
+\x5cBbRrJjZ\xba\xd5f\xb3\xd932y\
+N\xc5\xac\xec\x9c\xdc\xbc\xfc\x82\xc2\xa2\xe2\x92\xd2\xb2\xf2\
+\x8aJ\x82|UuM\xad\xb5\xae\xde\xe1p:]\x0d\
+\x8dn\x82M\xcd-\xad\x9e6o\xbb\x8fC\xfe\x8eN\
+\x81\x22\xdf\xd5m\x0f\xf4\x049$\x88\xb8\x90\x86\x9e\xde\
+\xbe\xb0O\xa5\x10N\xd2\xb0\x7f\xc05\x88\x860Ij\
+\x11\x8a\xc3#\xa3~B\xb2\x1a\xc5\xb1\xf0\xf8\xc4\xa4H\
+\x0c\xe2(\xba\xa7\xa6gfU\x83\x10@8\xa7\xe1\xfc\
+\xc2\xe2\x121(\x09An\x99\xa2\x7feu\x8d\x98\x10\
+]\xb7ll\x8a\x04\xb7\xb6w\x08\x8a\xb1\xdd\xbd\xfd\x83\
+C@\x10\x1d\x1d\x9f\xe0\x11\xf9\xf4\xec\x5c\xb9\xd0\x03\x8a\
+\x97W\xd772\x04R\xec\xf6\xee\x9e\x1a\xe0\x22\x0f\x8f\
+O\xcf\xf8\xb3\xf2\xcb\xeb\xdb;5\x80>L\x9f_\x0c\
+\xc3\xb2\xa6\xef\x1f\xed\x0e\x00\x18U~\x15Z@\xa3?\
+\x8f\x22K\x03\xe7l\x909\x00\x00\x00%tEXt\
+date:create\x002023\
+-03-08T17:27:26+\
+00:00\x9e\x98\xa5\x85\x00\x00\x00%tEX\
+tdate:modify\x00202\
+3-03-08T17:27:26\
++00:00\xef\xc5\x1d9\x00\x00\x00(tE\
+Xtdate:timestamp\
+\x002023-03-09T06:2\
+0:50+00:00\xdd\xe8\xb2\xde\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+\x00\x00\x03\xa3\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00(\x00\x00\x00(\x08\x04\x00\x00\x00&\xf7p\xe6\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x01bT\x00\x01bT\x01\x80\xc1\xe1l\x00\
+\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6\
+ \xcd\x00\x00\x02rIDATH\xc7\xad\x97\xbfo\
+\x131\x14\xc7?w\xbd\xa0\xdc\x80rBJ:4]\
+*\x90\xf8\xf1\x07Db\x00\x81`\xc8\x8e\x98\xb21e\
+a({\xd35K\xc4\x10\xa9\x13[\x84\x00\xc1L\x85\
+X\xca@\xf7\xa0\xa2J\xa4(R\xc4\x90FB\xad\xa0\
+\xbd4Ic\x86\xbb\xfc\xb8\x8b\xed\xb8\x0d_/\x96\xfd\
+\xfc\xbd\xaf\x9f\xfd\xde\xf3\x81\x1c.E\xea\xf4\x10\x92\xd6\
+\xa3N\x11\x97\x0b I\x19_J6j>e\x92\xb2\
+\xa5\x96\x94\xb0\xc0+\xae\xf0\x93\x8ftb6\x02H\x93\
+g\x8d\x1e\xcf\xa8\x99\xe9s\xd9Fp@Ni\x91\xe3\
+\x00\xc1\xb6\xe9\xb6Wh \xa8jm\xaa\x08\x1a\xac\xcc\
+N\xd8\x12c\x87\x04\xd0\xd1\x12v\x80\x04\x8e\x19a\xe0\
+\xa9\xf9\x90Z\xd9Js\x0b\x1d\x94\xb32\xc2\xa1\x81J\
+1e\x19\xf3W\x00\x97ka\x7f\xc8*\x0e\xe0\x91e\
+IAw\x8e\x078\xac\x8e%\x0d\xf8\x8d?\x92\x9e\xe4\
+\x09\x05\xae\x93\x08\xbf\xeb\xb0\x8c\xc31G\x1a\x85\x1e)\
+\x06\xb4\x19\x84\x0e\xe8\xd3\xa0\xc6{\xba\xe0R\xe6L\x1b\
+\x15\xa6\xed\x8c2\xaeE\x91\x0aIiT\x98b\x12=\
+]\xd6\xa1>'*L\x11DO\x1dzs\xa3\xc2\x14\
+U\x04=\xdb *L\xd1\x01\x12\xf6\xc241\x04\x84\
+\x979\x8aYX\x13\xc2\xff\x88x\xbe\xb0\xc8\xc83\xb1\
+\x02]\x0e\xa3A\x1a'|L\x85\xab\x86\xd9\x06,\xfe\
+\xb0\xce'\x1d\xe1M\xee\x5cp\x8f\xb7\xf4\x84o\xb1\xb8\
+A\x81\x14_\xd8Qzx\xc8}\xeeqL\x8d\x1f\xbc\
+\x89O\x0a\x04\xa5\xc8H\x96&\x82\x0d\xad\xae\x0d\x04M\
+\xb2\x91\xb1\x12\x02!\xd3\xb0\x14\xfaG\xef\xbd\x89e\x04\
+6} \xad\x5c\xa2'\x8c\x22\x0d\xf4m\xf6\x81|$\
+9\x9cc\x8ai\xcb\x1cy`\xdfa\x8b\x0ak\xbc\x1e\
+\xa7/\x81\x877\xd6\xf1\x88\xbb\xb1B\xff\x95\xcf\xe1\xb5\
+\xf2x\xc1\x11V$}m\xa9\x13\xec&\x90aof\
+|\x8fe`S\x95`\x1d|J|\x93\x94\x00\x80\x13\
+vIE\xae\xb9\xc5.\x7f\xc3\xbe\xb4\x048@\x97\x1a\
+\x1f\x22E\xea]\xf8&8\xe19\x99\xc8m\x1cr\xc8\
+i\xd8o\xf3\x94V\xbcH\x8d.\xb6\xcf\xaf\xa9e\x83\
+q\xef\x94\xa6\xf2H\x06\xb4h\xc5\x07%\x8f\x89)E\
+\xfaC\x91\xe6*\x19\xe1h)\xa4y\xc9\xed\xd8\xf8w\
+\x1e\xd2V\xa7\x0f9\xe1H\x93\xfeP,S\xc2\xc18\
+z\xd4\x87\x92\x06\xfaS\xbe\xd6b\xa1\x07\xa7L\xa1O\
+\x8d\x07\x91\xe8\x89{6x\x12\xd7\x82\x8bb\x82\x05\x1e\
+\xed\xeam_\xf2\xb7\xe2\x1f\xabI7!\x97\xa94\x84\
+\x00\x00\x00%tEXtdate:cre\
+ate\x002023-03-08T1\
+7:21:49+00:00\xa3\xa1\xac\
+\xac\x00\x00\x00%tEXtdate:mo\
+dify\x002023-03-08T\
+17:21:49+00:00\xd2\xfc\
+\x14\x10\x00\x00\x00(tEXtdate:t\
+imestamp\x002023-03\
+-09T06:18:41+00:\
+00=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB\
+`\x82\
+\x00\x00\x06m\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
+\x00\x00\x064IDATx^\xad\x97[lT\xc7\
+\x1d\xc6\x7fs\xce\xd9\x8b\xbd\xf6\xfa\x16\xa0\xbe\x00\x0e\xb2\
+ic$BJ!\x22\xa1-\x95b\xa5/\xeeKh\
++\x95\xa6U\xa5\xc6`U\xaa\xda\xb4\xaa\xfaV\x09U\
+\xca\x03\x94'\xda\x07\x84\x14)\xad\xc4\x8b\xa5R\x83y\
+\x08\xc5\x189\x0ei\xd3\x84\x9a\x9bcj\xec\xb2\x04\x1b\
+;\xbb\xf6z\x8f\xbd\xbb\xde\xb3g\xa6\xc3h\x85\xe5r\
+l\x88\xc9'}\xfa\x9f\x9d\x87\xfd~\xf3\x9f\x99s\x11\
+J)\x82$\x84x\x05x\x9e\xc7kH)\xf5w\xd6\
+(' \xb8C\xbb\x01h\x97R\xbe\xc6cdY\xd6\
+\x07\x1a\xf6\xbb@\xb7\x069\xff\x14\x00&\xfc\xb7\xed\xf5\
+\xe2`]DDn\xce\x89\x8a+W\xaeP]S\x8d\
+@\x00\xa0P\x08e(A)f\xd3i^\xa9\x17/\
+\xbc\xb4Nl;\xf1\x1f\xb9G\x83|[CL<M\
+\x07\xf6\xff`\x8b\xdd,%\xf8J2<<Lee\
+%+\xc9u]\x1e\xc0n\xa9\xb0\x22\x1b\xa2*r?\
+\xa7\xea\x81\xb5\x03\x08-\x05H\xa1\x0d\xf4]\xbcH.\
+\x97\xc3/\x16QJ\x91\xcf\xe7Y\x5c\x5c\xa4P(P\
+\xd4c\xb5\xb5\xb5\x94\x01X\x80\xf8\x82\xf6\x80\x01\x006\
+D\x05\x1f\x0f\xbcK>;\x8f\x85D\x952\xe2\xb6\xc4\
+\xb6\x04!!p>Sl\x8c;\x80D*\x04\xf0\x9c\
+\x10\x02\xe0\xcb@\x05P\x0f4`\xc4Hi\x9f$\x02\
+\x01N\x9c8!\x00\x81\x05\xd2\x87\x96\x96g\x09em\
+\x14\xe5(\xa5\xb4A\x08XW\x19%\xe2\xd8DB\x16\
+\xc3\x13s\x5c\xbc=A\xf7X\x8e\x5c$\xbe\xa9\xbd}\
+\xf7\xef-\xcbZ\xdc\xb1cGYUU\x95\xd3\xd8\xd8\
+\x18~\xe0\x86\x86\x86\xd0\xa5K\x97\xdc\xae\xae\xae\x08\xf0\
+\xd6\xaa\x1d\x00\x13DU,\xc2s\xd51\xf2\x9eO\xa1\
+(\x91Ja\x09A\xd8\xb1\x88\x86l\xe6r\x05\x12\xa2\
+\x8e?\x9f\xff+\x0dM\x1b\x01\x22\xc0f\x96\x84\xef\xfb\
+x\x9eGuu\xb5\x9ePK\xf4\xea\xd5\xab\x87\x84\x10\
+(\xa5\xdeZ\x11\xc0\xb2A\x00\xb6-\x90\xda\xb6\x148\
+\x08\xa4\x12X\xc2\x8c\x1b\x8fL\xb9\xec{\xf5;\xd47\
+6\x11|/\xc1\x84g2\x19\xca\xcb\xcb\xcdf>v\
+\xec\xd8&\xbd\x7f\x0e.A,\x01\xd0\xd9\xd9\xa9\x0e\x1d\
+:\xa4l!\x08Y\x10\xb6-\x1c\xc7\xc6BP\xb4\xcd\
+\x1a\x1b\x00\xc7\xb2\x888\x96\xae\x02`Yx\x10\xc0\xdc\
+\xdc\x1c555\x06 \x1a\x8dr\xe4\xc8\x91\xcd\xc0\x03\
+\x88\x1b\x1a\xa2\xc7b\xb9\xb0mt0f\x8d\xcb#6\
+\xb1\xa8\xa3\xc7,2\x8b\x1e\x93\x99\x1cc\xa9y\xee\xcc\
+.\xe8\xdfEr\xf9<\xab\xc8,A6\x9b5\xa7f\
+\xe9\xffm\x0e\x1c8\xb0\x1e\xe8\x00X\x06\xa0\xb4t\x16\
+\x8e\x0d\xe1\x90\xc0S\x8a\xb1\xa4\xcb\x8d\x8c\x83\xd3\xb2\x97\
+\xa6}\xaf\xb3\xb5\xe3\x17\xac\xdb\xfb:\x0d/\xb4s\xfb\
+\xce$\xfd\xfd\xfd$\x93I\x94R\xe6\xfa\xf8\xf1\xe3\xe8\
+\xba\xac3\xe7\xce\x9d\xe3\xe8\xd1\xa3\x1c>|\x98\xde\xde\
+^\x12\x89\x84\x04,\xa1\x15\xdc\x01\xed\xff\xce\xe6\xf8\xe7\
+\x94Ok\xc7\xcf\xf8\xe6/\xdf&\xf6\xf57\x99|\xa6\
+\x83k\xfe.\xae\xf1-dk\x17\xad{\x7fN^V\
+s\xfaog\xd1wM\xee\xdc\x9d\xe2\x1b\xafvr\xfd\
+\xfau\x03\xa0gk\xd6?\x16\x8b\x99\xebx<\x8e\xe3\
+8%8\x04\xc0#\x00\x96%\x98\xcaA:\xde\xca\xfe\
+\xdf\xbdM\xd5\xae\xd7(\x84b\x08\xdbBY\x82lA\
+r\x7ff\x91O\xeef\x18\xb8\xear\xfa\x1fad\xd5\
+^\xae\x8f\xdcg2\xd7\xc6\x85\x0f\xee\x9b\x00\xed\x87\xa1\
+\xcd\xcd\xcd\xb4\xb5\xb5\x19755\xa1\xa1\x14 \x83\x1f\
+F\x16\xdcq\x15\xdf\xff\xe9o\xa8l\xd8H\xe2\xec;\
+L\x8f^\xc3\x89\x94\xb1\xb5y\x07\x9b[\xb6\xf3Iy\
+%c\x09\x97\xcff\xf2\xdc\x9d\xce2\xa1\xed\x88\x0dL\
+'\xe7\xd8\xb7+\xca\xfa%\x003{=k\xea\xea\xea\
+\x00\xccu*\x952\x00J+\x10\xa0\xb9Zp\xe1\x9d\
+c(,\xca\xe6\xc6\xd9\x10\x8fR\x94\x92{\xc3}$\
+e\x05\xdb\xda\x7fLM\xdb\xcb|<\x9cf\xd2_\xc0\
+\xcdx,\xcck/x \x00\xb5t:B\xa1\x90\x09\
+-\xdd\xea\x1f\x8e\x01*\xf8>`\xc1\xc6\xb8\xa0P\x1c\
+#\x1c\x8bS\xb7\xa5\x96\x92xv}\x05\xe9\xac\xc7h\
+\xff\x9f\x98\xae\xbcL\xcb\xf6\x83\xb8\x0ba\xbc\x82\xa4X\
+\x94x\xda!\xc7B-\xaa\x80\xe3i\xa0\x96\xd5\x15\x01\
+\x00\xd6\xc7C\x84\xca#\xfc\xbfjc!\x9e\xa9\x0cs\
+\xe1\xdf\x83\xec\xd9\xf9\x13\xca\xa3\x0e\xb92G\x03(\x03\
+ak\x00\x16K!\xa5\x1c%0*\x15\xa4\x5c\x05@\
+X\xa5*\xcc\xf5#\xfapl\x86\xf1Y\x8f\xef\xfd\xfa\
+\x8f\xdc\xca\xd4\xe0D\x5c\xa2\x11\x1b\xcf\x93\x14=\x07\xd3\
+\x01\xa5\x90R\xf2PjY\x01V\x05\x10\x08L\x0d\x04\
+\x18\x9dv\xf9\xd5_\x86\x18\xbd\xb7\x80=\x93g\xd3\xba\
+2\xf2y_\xbbh\xea\xce\xaf\xd4p\xf9\xdd\xe0%\x00\
+\x9ex\x09L\xb8\x10<\xa2\xd6/U\xf2\x87\x1f>\xcf\
+\xf5O3D\x1b\xb7\xb1\xf3\xc5\x97Y\x12\x5cN`\x8e\
+\xdbS\x01(\xc0\x12%\x00m\xd4R}\xb1\xb5\x96\xdd\
+[\xe2t\xbf\x97\xa5j\xf7W\xf9\xd1\x1bo\x10\xa0\xb5\
+\x03\x98\xb57\xd5\xd8\x08\x01\xd2\xcbSpSx\xf33\
+\x14\xb3i\x0a\x19\x1f%\xfd\xd5\x82\xd6\x08\xf0\xf0)\xe7\
+\xe3\xe73\x14\xe6u\xa8\x0e\xd6\x00\xcb\xf7\x89\x10\xc13\
+}\xfa\xd7r\x8c\xb2\x137\x03\xc7\x01\xb2\x1e\xfe\xad\x94\
+\xcco\xf7DT\x03\xd8_p\x07\x08\x92\x09\xfd\xd7=\
+?\xfd~B\xa6\xcf\xdf\xf6\xef\x02\xeev;\xfc\x92\x06\
+\xa8\xe3s\xcau]\x1fpW\xed\x00@2\xab\x0a\x1f\
+~*\xd3\xbd\xb7\xfc\xd4\xcdi9\x05\xf4\x03\x97th\
+\xbf\x10\xa2\xd3\xb6\xed\xaf}\x9e%XXX\xf0\x07\x06\
+\x06\xd2'O\x9e\x9c\x06\xba\x83\x00>\x1aI\xca\xad\xe3\
+\xb3*\xd7;\xe2\xa7nL\xcb\xd1R\xe8Y\x1dt\x8b\
+\x00=\x09\xc0\xd0\xd0\x90\xdb\xd3\xd3\x93\xd2N\xcf\xce\xce\
+\x9e.\xbd\x1d\xdf\x08\x02\xe8\xee\xea)\x00\x8c\x04\x84\x06\
+\x85\xaf\x08055U\xd0/\x22\xa9S\xa7N%\xc7\
+\xc7\xc7/\x03g\x81~\x1d\xec\xae\xb8\x09K\xdfv\xda\
+O&\x85\x01@\x08@aZ\xfc\xde\xe0`\xba\xbb\xbb\
+;\xa5\xdf\x8a\xcc$\xd0^\xeds\xcda\xed\x9aw3\
+n\x11`p\xf0\xfdt___\xfa\xcc\x993\xa6\xc5\
+\xa5\xd0\x8fx\x02\x89\xb5\x9ec!D\x18x\x13\xd8O\
+is\x06\xb4\xf8\xb1\xfa\x1f\xbd\xfa*_\xf2\xd8\x15\x9d\
+\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x05+\
+\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
+\x00\x00\x00\x19tEXtSoftware\
+\x00Adobe ImageRead\
+yq\xc9e<\x00\x00\x04\xbdIDATX\xc3\xed\
+WkL\x93W\x18>#q\xc92\xe9\x16\x97\xa8T\
+e8\x9d\x02\x15\xf6\x03\x872\x93\x01f,[p\xc4\
+0\xff`\xa2.\x1a:\x1dN\x03\xba1\x89[\xb3\x80\
+\xd9\x0c\x84\x02\x19X\x1c\x14\x8b\x85\xb2\x82\x95^\xe4f\
+\x0b\x8e1\xf8\xc3F\xcb-\x81\x15\xdc\xa8\xc2\x1c\x1b\xb7\
+ji\x91\xf2\xee\xbc\x87\xaf\x0c\xdc\xb8\x0da\xd9\xb2\x93\
+<\xed\x97\xf3}\xfd\xde\xe7\xbc\xef\xf3^J\x00\x80\xfc\
+\x93 \xff\x0a\x02t\x09(D\x14\xd9\x14q\x14\x01+\
+F\x80\xae\xddd\xdd\xc6f\x22L\xf8\x95\xc4\x8bG\xc8\
+\xa1\xd3\xf7\xc8\x8e\x97;82a+A \x85\x9c\xbe\
+0H.\xdd\x80\x19@2\xabyM\xf4\xbe\xfbr\x13\
+hd\x06\x91\x04^\xa3Q\xf4\x06\xee\x85G\xf5\xd0\xbd\
+\x83\xcbM \x9b\x9d\xf6@t/\xbd\x162= \x89\
+?H\xa5,\x1b\x01\x8c1y\xc1\xbb\x9d\x88K\xc6\xd7\
+\xc6&\x0e\xa0\x10\xb9\xfdB\xfe\xc5+6F\x8c\x12\x5c\
+N\x02\x93\xa7\xa7\xa7\x0d\xcc\xd39\xb9\x98c6\x14\x0a\
+\xd2\xe4\xa3+A \x8c)\x9e*\xdf7G\xeb\xdc{\
+\xb5\xcc\x89\x9e@D\x96T\x83+,\x0b6FH\x08\
+\x13\xf5d*{.T\x03\x01\xf8\x037\xbf\xc0\x0e4\
+*T\xdfb\x88R\xd5,X\x03t\x1d\x16\x08\x04z\
+EU\xf5\xc8\xa0mt\xc2\xd4s\xf7!\xbesQ\x95\
+\x90\xae\x8f\xd0\x13\xcf\xe5\x94\x83\x87\xb4\x02\x9e\xcc.\x03\
+\xd4\x06\xdd\xaf\x99\xcb\xb0\xaf\xaf\xaf>\xbf\xd2`\xb5\xdb\
+\xed\x80\xf8y\xe4>\xc4^\xab\xb4\xb9\x88/\x86\x80'\
+\xd3\xc0g\xf9\x8e\x19\xf5`\xd7^3\xbav\xdas\xee\
+h\xd8\xc7\xc7G\x9f\xab\xab\xb0\x0e\x0f\x0d\xc1\x10\x87\xb2\
+\xf6.\xe7\x967\xf7wsa\xd8\xbd\xe8^\x80/f\
+\x9a\xa0\x86\xdf\xa96B\xf7\xf0\x03\xd8\x19\x9f\xd4\xcf\xa5\
+\xe7\x1a\x8a\x98-~\xfem\x97T\x1ak__\x1f\xb8\
+\xd0\xd1s\x07br\x15VN\xc4\x87\x97\xd4\x8c0\x14\
+\xe9\x15\xb7\x1e8\x1c\x0e@\xa4\xd6\x191\x9e\x85\x9b\x05\
+~m\xa9%\x1a[\x97\xd9\x0c\xe6.\x0a\xf3$\x14\xdf\
+6\x8e{\xbd\x1e\xd1\xcdB\xc8\x09o\xa9\x04<\xd1\xbd\
+V\xab\x15\x10w\x7f\x1b\x84\xf3\x92\x5c\xbbR\xa9\x84\xfa\
+\xfaz0\x99L\x0cu\xdf5\xc1Q\xb1d\x18\xc9Q\
+D>\xb6v\xcc\xb4@O\x93_~\xd3\xd6\xdf\xdf\x0f\
+2\x99\x0cD\x22\x11\xa8T*\x90J\xa5\xa0\xd1h \
+K[9\xbe\xe9\x95\xe0\x1f\xb8S\xafy,\xf3\x00\x97\
+\x8e\x22\x9e\xc7\x86\xe6S)\x19\xf6\x82\x82\x02\xe6\xe2\xa0\
+\xa0 \xe0\xf1x`\xb1X@[^\x01\xfb\xcf&\x0c\
+-\xa6S\xceg\x94\xcf\x09L\x83\xe2[{\xe6\xc2`\
+\x9a\xb2\x14\x14\x0a\x05\x88\xc5b\xc8\xcc\xcc\x84\xa2\xa2\x22\
+P\xab\xd5\xd0\xd9\xd9\xc9`\xec\xfe\xc9\xb9\xc9\xdb\xa7u\
+.\xb7\xcfK\x80\xae\xb7\xd8)p\x0e\xc0j\x97\xacx\
+\x88\xca\x7f\x82\xe2)\x89\x0e>\x97+![\x96\x0f\x07\
+c\xe3G\x84\x1f&\xd8\x92rd\x8eo\x1a\xbf\x07\xa3\
+\xd1\x08-\xad-\xf0\xcb\xc0 \x1c8\xf1\xbe\x05\xb3b\
+\xc1\x04\x5ci\x84\x85\x85\x84F\xdc&\xe72\xac,\xcf\
+3\xb5\x13\xec;\xe3\xba\xd33\xaf\x82\xe5\xfez\x89\x06\
+\x9e\xde\xfcb\x1b\xf7<\x92\x8d{f\xabO[\xca5\
+\xedXCC=444\x80\xa5\xb7\x172\x14\xc5\xc3\
+\xf3\xe9\xc0e<\x92\xe5(\x9e6]\xe5\x9c*2x\
+}\xf4\x83.Zl\x121\x0c\x1b%\xeaq\xf7/\xcb\
+'\xef\x05\x87_\xfe\xd3\xe4D\x0bLh\xf4\xc9>u\
+\x95\x1e\x0c\x06\x03\xb4\xb7\xb7\xc3\xd7\xc6\x961\xae\x81\x09\
+f\xf16m8h<I::e\xf8b\x81\x83D\
+\xbdWC\xb6\x0a^\x9b*\xc3\x94\x5c\xb0B\x0f\xab$\
+\xb4\x04\x9fJ\xaa\x9bC71(\xd4O\xf2\x0a\xc7t\
+:\x1d\xd4\xd6\xd6\x82\xc9|\xdb\xb9a\x9b\xf7_\xeab\
+\xb2\xe5~\x9cu\x1f\x0d\xf3\xb2\xd4N\xf2\xf6\xb1\xeb.\
+\xb6\xae\x94\xc3\x90l\x97U\xc1KW\xab\x80\x9cMn\
+Z\xd0\x1cI\xbd\xb1\xe7\x88\xb0\xef\xcaW\xc5PZZ\
+\x0a\x1d?\xf6L\x04\x06\x87t<\xaa\x0b\xc2\x84F\x8d\
+\x07\xc8o\x02\xd9\xf9\xaa~\x9a\xf10F\x8e6 \xaf\
+\xbcJxCi\x00\x92(\x1d\x98\xcd\x95\xb3y\xc3}\
+=\xbf\xf9Dj\xa6].\x97CSK+D\x1c{\
+\xf7\xce\xf4\x14%\xae\xf1\x8a\xf5w\x9c\xf5p\x02\xc2\xd9\
+\x0f\x89\xd1\x81\x03O\x8e\xf7\xdc\xd2i\xe7\xf3\xdfu\xfc\
+o\x14.6\xd2\xef\xd8\x17iI\xbe,\x9d\xc8\xd3\x96\
+;\xa7\x0f1\x8c%\xc6\xdf\x9f\xbaw_q5\xa0A\
+l\xb5\x08\x8c\xf9\x94\xf1\xe0\xf03K\x9a|h\x13Z\
+\xbd\xce\xa3\xd9kOH\xf7\x0c\x0f\xb0\x0f\xfe\xf3\x87\xc8\
+\xf9/\xee\xb9In\x00\xf6{>\xed\xf7\x08\x1e*>\
+]\xe5X\xaa\xf1GZ\xf5\xb6Y\x0b\x11\x1d\xb3C\xc9\
+\x918\x099\xf9\xa9\x96!\xfa\x5c\x1a\x0d\xcf\xb3\xff\xff\
+7\xfcO\x13\xf8\x1d\xe7\x87\x19\xb9D\xc3\x01\xcf\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+\x00\x00\x120\
+\x1f\
+\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed\x5cms\x1b7\x92\
+\xfe\xbc\xfe\x15<\xe6\xcb\xaa\x96\x04\xd1h\xbc*\xb6\xb7\
+l\xc9N\xa5*\xb9M\x9d\x93\xdd\xfb\xe6\xa2\xc8\x91\xcc\
+\x0dE\xaaH\xca\x92\xfc\xeb\xb7\x1b\xa3\x11\xe6\x85 %\
+\x99\xde\xad\xca\x1d\xc7\x96\x80\x06\xd0@?\xddh\x00\xc3\
+\x86^\xfe\xf5\xf6r\xde\xfb\x5c\xac\xd6\xb3\xe5\xe2U\x1f\
+\x84\xec\xf7\x8a\xc5d9\x9d-.^\xf5\x7f\xfb\xf5\xfd\
+\xd0\xf7{\xeb\xcdx1\x1d\xcf\x97\x8b\xe2U\x7f\xb1\xec\
+\xff\xf5\xf5\x8b\x97\xff5\x1c\xf6~(\x16\xc5j\xbcY\
+\xae\x8e{o\xa6\xcb\xb3\xa2\xf7\xe3|~\xbd\xdeDR\
+\x0f\x94\x90B\x0ez\x1f\xfe\xfeC\xef\xdd\xed\xd5r\xb5\
+\xe9\xfd2\xbf\xbe\x18\xfe\xb8\xe8\x89H\xfc{\xd9\xe7q\
+\xcf\x0a){o\xafg\xf3i\xcf\x80\xd6\xfe\xa8\xd7\x1b\
+\x0e\xa9\x8b\xf5\xe7\x8b\x17\xbd^\x8f\xc6\xb7X\x1f/\xd6\
+\xaf\xfa\x9f6\x9b\xab\xe3\xd1h\xb1\x16c\xeeNL\x96\
+\x97\xa3\x0f\xe3\xcf\xc5\xfb\xe5\xea\x1f\xc5\xd9\x08\x84\x1c\xf5\
+S\x8b\xe9\xe4\xa1\xc5\xd5\xf5j.\x96\xab\x8b\xd1t2\
+*\xe6\xc5e\xb1\xd8\xac\xa9:\xd4\xabOR\xf5\xc9\xaa\
+\x18of\x9f\x0b\xe2\x7f\xb9\x5c\xac\xb9%u\xfa]\xad\
+\xf2jz\xfeP\xfb\xe6\xe6F\xdc`\xac\x04!\x84\x91\
+T#\xa5\x86Tc\xb8\xbe[l\xc6\xb7\xc3fS\x92\
+j[S%\xa5\x1cQY\xaa\xf9\xb8Z\xc7\xb7\xf3\xd9\
+\xe2\xf7\xec`bi\xbdwR\xec\x15\xfd\x7fhP\x11\
+\xc4zy\xbd\x9a\x14\xe7\xd4\xb2\x10\x8bb3:\xfd\xf5\
+\xf4\xa1p(\xc5t3\xad\xb1!\xa6\xeb\xc9\xf8\xaah\
+\xf4[\x11K\xbc\xc6\x97\xc5\xfaj<)\xd6\xa3\x8a\xce\
+\xed\xeb\x96\x06\x910\x9b\xbe\xea\xff4\xbe+V\x1f\xcb\
+\xfc\xcdl\xba\xf9D\xc5\xca\xc7\xec\xa7bv\xf1i\x93\
+\xf2\x9fg\xc5\xcd\xdb\xe5\xed\xab\xbe\xec\xc9\x1e\x11{U\
+\xc1\x928\x9f\xcf\x977\xaf\xfa\x9fg\xeb\xd9\xd9\xbc\xec\
+\xaeX\x8c)9<\x1bO~\xbfX-\xaf\x17\xd4\xd9\
+\xa2\xb8\xe9\xb5\x1a\xb3T\xc7<Z\x12\xe8jU\xac\x8b\
+\xd5\xe7\xb2y\x05\xc0\xf1\xc3\xb0\xa5@\x15\x8b*\xa9\xea\
+E\xda\xfceZ|.\xe6\xcd\xb6\xd3\xe5\x84\xd0 \xd6\
+\x94\xb8f\xdb\x1b.\xaf\x8a\xc5pUL(-H\x9b\
+_:\xf5\xcf\xc6k\xaa?\xfamM\xbcG\xd3\xf1\xe7\
+\xd9ttZ\xac\x7f\xdf,\xafFk\x9a\x8ag\xcb\xdb\
+\xe6\x18\x96\xd7\x9b\xab\xeb\xcd\xc7\xe2vS,\xca\xc1\x90\
+\x0e\x92Bbq\xd9\xd5\x03\xb1\xc9\xa0\x883tx>\
+\x9b\x174\xd6=}\x8f\x1e\x04!,\x95\xb8Z\x5cl\
+ev;\xbd\x22;SJ\x98\xad\xc5w\xa9\xf8\xf5\xcb\
+iq\xbe\xae\xac\x81\xd3 \x99J\xc6[\x8cW?\xac\
+\xc6\xd3\x19\xf5\xc6\xe5e\x8d&\x1d\xd19\xaa\xbc\xa6\x11\
+\xc6*\x8c\xe5\xe6nNB0i8Y\xce\xc9C}\
+\x87\x86\x9f\xef#iI\xaa\x9em\xee\x8e\xe1\xfb~\xd5\
+by~\xbe.\xc8\xce$S\xaa\x8e\x22\x03b\x1f\xfa\
+\xbd\xd1\xde\x0ed\xfc\xec\xed\x00\xb6t\xe0!v0j\
+\xca\xb5]\xfe;\xf5\xaa\x1f\xd8eJu\xcf\xe9\x96(\
+\xde\xd7)w@\xfdH]\x95\x03\xb7\xb8\xcf\x5c\xdc3\
+\xfbm1\xdb\x90\x8b\xb9&-\x7f`\xc3\xff\xdb\x82\x14\
+\xde\xcfC\x0c\xd8\x7f\xfd\xe2O%\x06\x8d\xb1\x03iw\
+; \xde\xfb7>\x96&tI\xce\x0c\x1b\x97es\
+\xe2\xdf\xf9\xd0d#\xa4Q\xc6hc\xf3\xfcB\x8e\xdf\
+\x9b\xb7oN\xdet\xf8Y\x87Y^\x0ar\xbcN\xcc\
+\x89=\xd1-^\x80\xda\xe5ya\x8e\xd7\xe9[z\xde\
+\xb4x)\x00\x93\xe7\x95\x85\xfe\xdd[~Z\xbcP\x82\
+\xca\xf3\xca\xe2\xff\xde\xd1c[\xbc4\xa8\x1d\xbcB\x96\
+\xd7)?-^\xc6\xba\x90\xe5\x85\x90\xe5\x15?\x0d^\
+\x10\xd9\xec\x9bG9#\xb7N\xcb\xfe#\xa6\xb9\x1e\xeb\
+is\x9a\xcb\xc7\xf8\x11b\xaf\x92\x1f\xe9\x16\xfa.\x0f\
+r\x8d;G\x82AO\xdb\x0e\xe7q\xae\x0a\xa6\x8f\x90\
+\x01\xb6\xc9\xa0\x1f\xeb\xaa\x92\xaf\xf9u5^\xacig\
+q\xf9\xaa\x7f9\xde\xacf\xb7\x7f\x96\x03\x18\x0ca \
+\x07C\x0c\x22\x04o\x06\xa0\xa5\x90R\x85\xa3~rs\
+C\xf4H\xa5\xce\xd4\x1c\xddP\xa10`A\x85\xaa\x22\
+\xa4\x8aPUd\x9a\xd7\xccPK\xfc:\xcf\xe7\x00\xfa\
+\xdbuFE\xb8C?7\x9ff\x9bb\xffzCp\
+f\xd9\x9b}\xec\xf7\xabp\xbb\xb6h\x9d\xdf\x14\xabX\
+9\xad\xcb\xc4w^L\xa8\xd1x~3\xbe[\xd7 \
+)\xab;v\xd2\xd4\xb6\xf8a|\xbd^\xcf\xc6\x8b\xb7\
+\xf3k\xe6\xb1\x9bK\x92`zZ|\x9e\x8d7q{\
+\xa2\x84\xf7\xd2\xf8\xe0\x1a&\xd6d\xed\x10B9\xfc\xb2\
+\xff\xd7/'\xf3\xd9\xd5/\xe3\xcd\xa7\xb2M\x95\xdb\xa7\
+\xd0\xaa\x9e\x01\x8b}b2[M\xe6E,\xae\xef\xba\
+Vw\xaf\xfah\xfb]\xfa\xedv\xfa\x84\xea\x07\xb5\x85\
+\xce\xf55\xb2U\x87\xd0\xd2\x1f\x039?\xa6\xc3\xc8\x9f\
+\xbfkj\xc4\x80uG\xdfsi\xb2\x95\x87\xb6\xab\xc6\
+\x00x<\xbc\xd8\x83\x94R?P\xb7\xf7\xda\x1a#\x8b\
+\xd8m\xca\x10\x95\x98\x18\xb0\xa6\x04\xbc\x82\xec\xb1\xaeS\
+\xab`3\xb3\x84\x8a\xb6x\xb6\xdd~m2>?\xcf\
+\xf9\xb5n\x07(e\xc6ue;P\xdav\xb6\x82\x8f\
+\xf4k\xfbm=\x9e\xb9\x8e?\xad\x0a:#~\xb7\x05\
+\xa7<\x88N=\xc1W\xb1\x93C\xe5\x05(\xad}}\
+\xe3\xa7\xa4\xf0\x00\xc6\xd7\xbc&\xa2\x15\xc1K\xe7j\xde\
+\xd5;\xe1L\xd0\x06I\xf0o\xeb\xbf\xa1\xee\xbfAQ\
+\xbd\x7f\x97\xf7\xd6\xce\xc9\x9c]:\xa7v\x19\xc9y\xfc\
+<\xdf\x7fs\x07z\xb7\x15\x9e\x9bb\xfa\x5c\x0f\xce\x89\
+\xf1\xfc`v\xc98%\x10\x9b\xcc}P\xf2\xb1Z\xe8\
+\xba\xa2\xae#:\x8f\x15\xacP:X[\xd1\xd85\xa1\
+\xf0DA6\xd2\xe4\xf7\xf6\x99\x22\x08\x1d\x14\xa2\x0c\x03\
+)\x94r\xca\xbb0\x18J\x81\xc4\xca(;P\xc2\x18\
+\x8f\xde#\xd9+\xa20\x80H\xa6\xab\x140\xdd\xa8\xa3\
+?\xb0\xf1\x9b`|\xc6\xf8M\xb0{\x5c\xb0\xd6~\xf2\
+|\xe3\xe7\x0e\xd4\xee\x0elq\xb6\x7fv\xc1\xb7u\xca\
+\x09\xa7,\x88>\xc0\xe3\x8f\xce\xcf\xb7\x9a\xa4z+@\
+I4\xbai# d\x08\xaaaK\xc6\x0a4\xce(\
+h\x18\x9dC^\x10\xd8\xa7?s\x97\xa7UP\x87\xdb\
+\xe5\x81\x95Z+\xcc\xef\xf2\xb8?]\xdf\xe5\xe5\xdfz\
+\xc8\x9a\xf4\xaa\xbe\xe2\x19'L\xc2\x90\xca\xbej\xda\x80\
+\xb7\xd6\xf3\xcb\x8e?u\xcf\xa3T\xc6\x8e2\xbbbL\
+&[l\xba5a\xf2\x9ci\xc6<q\xab\x9f\xe6I\
+\xc9\xf7\xf1G\xdf\xff\xfd\xf9\xa7\x1fO?\xaa\x8fO\xda\
+pdqO:I\xcaJr6\x10\xc8\x1f\xe7\xdf\xbd\
+\x91\xb14a\xa2\xdap%\x81\xf3lNN\xdalt\
+B\xe7\xc0\x8e\xa4D1|\xcc\x1a\x93R\x01\x9e\x08q\
+\xd0\x025\xd6q\x06\xa9\x88\xa4]\x0dko\x85Ac\
+\x13\xe0\xdc\xcc\xa0\xb5$\xea\xa1\xc4\x02\xb9S.|\xaa\
+\x5c\xa6!\x12\xb6\xa4\xf1\xd64\xa5a\xca!\xa5\x81\x9d\
+\xd2\x98\x03J\xe3DD'\x09c\x98pHY\xd4N\
+Y\xdcSe\xb1\x0dYt\xfe\xbdrz\xf7|@i\
+v\x0b\x13\xbe\x99\x87\x22\x11\xd2>\xfa\xebD\xf0\x1f\xb3\
+\xbbg\xde\x93\x1ef\x03!\x82\xf3N\xa1\x1eH~D\
+\xf0@\x8f\x1a\x80@\x0bNjC)\xb0H\x8f>\xaa\
+m\xc5A\xaa\xda&\x1c@\x09\x94\xba\xb6\xc5\x06\x0cq\
++\x1c\x0e\x08\x87\xdb\x09\x87\xf9:86\x9c\x9c\x8f7\
+\xc5\x9f\x95\xd0\xc5\xd0\x0e\xe4s\xc4=\x88\xb4[6\x0e\
+Y\xc1Qa8\x88\x1d@\xd4?\x08\x15\xc0\x82\xd4\x83\
+!\xfd\x03\x12\xca\x05\xf4u$P\x0a\x09\x12\xb0~\x06\
+C-\x9c\x91\xa0+\xda\xf9\x96z\xe7\xddz\x0c\x9c\x92\
+\xc2yP\xe6\xf9;KT\x1a\x0e\xb5\xb3\x04!1h\
+\x03:\xbf\xb3\xe4\xfe0\xb3\xb3<\x88\xbe\xd1\x01\xe4=\
+\x97\x06u\xa0s\x03\x01\x8f\xde\x83\xf5\x83aJ\xd29\
+\xc2\x08\xa3\x945\xc6\x0d\xac\x13\xca\x18\x1bZG\x89x\
+\xbch8\xf7! \x88\x80\x00\xaa~\x92\xd0F\xc8`\
+\x8d\xd7\xc9;\xa6\x9a\x87\xf1\xf4\xddoZ\xb2\xc8\xa1\x94\
+X\x17C\x0b\x08\xfc\xf1u9\x94\xaf\x09\x00\xca\x0bU\
+\x1f\xfb#O\x00\x87\x17LG\xb8\xf3\x82\x99\xff\xc8\xfb\
+\xbd\xe7\xcfX\xa9\xe5\xa1f\xac\x14A\x928\xde\xc3\xae\
+)+\xb5\xaaO\xd9g{\x9atT&s\x90\xc28\
+\x0d\x01M\x84?\xc5\x98(\x01\xda\xa3wP\xa1\xd6\xa9\
+\x9b\x02PR\xe5\xc3\xb9\xb0\x00\x18\x9c7;]\x98\xc2\
+\x83\xe0\xe1\xcc\xe1\xce\xf4\xcaxi\xf5\xeea;w\x90\
+a\x87p\xc0\x05\xc3j\x87z\xa7\xf5\xa1\x84\xfa\xb0\x0f\
+\xbdA@\x04\xcc\xef\x0f\x10\xa0\xb6z\x07%$\x00\x1a\
+\xac\xaf\xdeF\x04\xef\x94L\xabw\xb7\xdey\xb3^Z\
+\xbd\x0f\xb3\xf7\x10F*\x04\xe3\x06RxT\xa8iM\
+b\x1a\x06\x0bj\xa0\x84\x03m\xc0\x87\x81\x13A\x05c\
+%R1z\xa1h8p\xf4\x15~\x08\x1d\x1e\xce\x10\
+x\xd2\xe9\xb0\xd3~\xd1\x99ok\x08\xce\xed0\x04\x8f\
+\x8d\x0d\xadL&Pn\xd6\xbc3\x95\xb6\x1b\x15\xce\xbb\
+\x15x\xc7\xab\x1e\xb9\xa5d\x95\x82s\xd2S\xda\x08i\
+x\xf3\x80G\x07XI\x0f\xbe\xb9B\x0c\xfa\xff7W\
+\xcfD\xce\xfd\x87\x91\xb3A\xa03\xe8\xa1\x09\x9d\x13\x1a\
+\x94\x0eu\xe8\x94\x15\xd2*\xef\xd3\xae\xa6Q3NP\
+\x0e\x16|\xfd\xf2\xe1\xdbw\x0e^\x9cr\x8ch#\xe2\
+\xf0f\xb6\x98.o\x86\xd5R\xce\xefp\xb7\x95W\xa1\
+\xa7R\xa9f\xf9\xd5\xf8\xa2X\x7f\x1aO9\xbatK\
+\xd1\xfdKY\xde\xe3\x94\x8c/\xaeg\xd3b\xb3\x9c\x17\
+\x04\x1b\xc7\x94\xc2C\xc1j6\xddF_\x9e\xfd\x93t\
+\xbc\xad\xe4l\xb9\x9a\x16\xab\x87\x1e\xa0A\x8e\xef<\xc9\
+\x00l\xfc\xc4\x02\x1eOE>\x8f\x9f\x87w\xa1\x1cT\
+\xda\x1c\xfc\x97\xe5\xf2\xb2#Q\xe9t\x9c\x90\xd2j\xdf\
+*\xa2\x118)\xac\x0aA\xda\xad\x10R[\xcc\x14E\
+\xe7dZ\x0c\xafW+\x8e#\x9ds\x00p3\x0ex\
+\xfdiy\xc3p\xf1\x81\xff\xbax@o\xcdH,.\
+\xa8\x1f}u\xdb\xa6\xde5\xa9\xc5\xe5\xd5}\x01\x09\x99\
+\x98\xb2r\xd6\x89m\x1aM,\x19\x9eQ\x84kYJ\
+KN*#~\xa5\x15n\xee8\xee\xf9\xf6\x8e)\xb5\
+y\xc6Y\xa5e\xe5\x10:\xe3L\xb4\xbb:\xad9\xc6\
+h\xd2]c\xa6\x98\xb6\xcbb3\x9e\x8e7\xe3J\x99\
+U\xbe\x0c\xcb\xe4\xc8\xee\xf3\x1bJ\x95\xc9\xf9\x8c\xe2\xae\
+9\x97\xb2\xdc\x8e-\xb4\x0cw\xa6D7\xba\xba\x1b|\
+\x1d[\xfexJ\x04\x04\xeb4*L\xaf\xd2S?\xf5\
+^?\xc4(\xf2\xb7\x1ci\xbd~V\x97gK\x9a\x06\
+\x97?\x15\xe7\x9b\xbf\xadf\x17\xb3E\xa9\x09\xea\xb5\xea\
+fy\xb5\x99]\xce\xbe\xc4\xd5\xfcC\xb1\xd9\x10rI\
+\xd4\xcd\x98\xc2\xd77\x15\x99\xf95),K9\x0c\x8e\
+t~O\x0emLC\xf9\xe5\xbf\x7fP\xba\xcc0\x9a\
+\xf7\xbcj\xd4\xc8\x88f&i\x8a\xccurW3\x9e\
+r\x83P\xf0\x16b<_\x974j\xb1)N\xcaI\
+X\x0fS\x5c,\x7fN%\xa9>\x19 \xb1\x98\x8f'\
+\x89K\x1c\x05\xa3\xdc\x1c\x07\x91\x99\x98\x04}\x8e\xfc`\
+\x83uh\x94\x81?\x22\x10L\xceZ\x09\x95U3\xe5\
+%]\xd08\xfe\x9f\xd3\xf7\x14\x0f69\xfe\xc7r\xf5\
+{9\x1d\x99:>[^\x13\x18\x1c\x95>9>/\
+\xfb\x9b]\x92_\xe5\x9b\x17\x7f\xa1\xab\x02/G\xa9\x80\
+\xeb\xb0G\xe0\xe6\x15\x83UQ^\xa6\xd8v\xff\x84\xfe\
+]\xce\xb8\xc1\xe8\xc3\x86\xc2\xbd~d\xbe\xe5\xb4\xbf\x1f\
+\x07\xa5\x1e\x866\xaaf\xf9\xeb\x17/\xba[\xd0\xe8\x07\
+\xd2\x0b\xd7\xfa\x0b\xd0\xce\xeb\xcf\xd6\xcb\xcf\x17{\xd6\x7f\
+\x06\xfe\x09\xdf\xddIi\xd0\x9d6\xbft\xab\x87f\xb7\
+b\xad\xc1@\x8e\x13x\x8b>49\xf9<'\x12&\
+\xc7\x89\x8aN\xde\xf8\x06'%\xf3\x9c\x82\xd59N>\
+\xbcy\x7f\x82MN*\xcb\x09\xd0\xb8\x1c\xa7\xb7xr\
+r\xda\x1a\x93\xcesr\x98\xe5t\xaa\xdf\xa9w\xa1\xc9\
+)\x8f\xb8\x92!dC\xc5O\xde\xab\xf7\xa6\xc9\xc9\xe7\
+9i4\xd9\xaf^\x09\xa6\xf7M+@\x99\xe7\xe4\x94\
+\xda\x1f\xda\x9d8\xa9\xfb\xd8\xee\xe64\xd871\xfc\xd3\
+&F8\xf4\xcc0h\x8cqMI\x5c\x89\xc9v\x9d\
+\x03Z\x0b\xda\xe4m\xd1\xbf\xf1'\x0d~\x1a*~\x19\
+\xdd\xab`\xad\xf7y\xfd\xd3\xd3\xe2\xe8\xf2ZC\x8by\
+\xfd\xf3\xd3\xe4\x14\x0e\xa5\x7f\x03YN\x06\xe1)c2\
+\x98\xe5\xe4\xb5\x0e\xf9+\x15\x1d\x9c\x8c\xc9p\x82,\x13\
+\xa0i\x8bM&.k\xd8\xdds!7J1\x07\xfb\
+\x0fq\xed\x90\x82N@A;\x9c\xa0\x15L\xb0\x15\xa6\
+'h\xcd\xaa\xa7\x22\xc4\xd3\xc5`\x93\x89n\x5c\xebx\
+,B \x9f\x04\x91\xa9\xa1C\xfd\xb7#\x13\x9aq\x09\
+_\x0fL8\x000\x0e\x9e\x07\x0c\x1c\x02\x98\x14\xe4\x90\
+B\x1c\xbe\x1a\x16g\x9f\x0a\x0b\x06|\x8b\xbe\xc9\xc4?\
+\x0f\x16\xf5$X,'S\xc4D;^\xa2\x19-\x91\
+\x07&\x7fq\xae!\x93\xcf;-)\x9d\xda\x7fw.\
+q2yN\xd6\xe1\xfe[s\x89\x93\xdb\xb1\x09\xd2n\
+\xff\x9d\xb9\xc4i\xc72\x01`\xf6\xde\x98K\x9c\x02d\
+9\xa1\x04\xb5\xff\xbe\x5c\xe2\x94G\x5c\x83R\xfbo\xcb\
+%Ny\xc4\x8du{\xee\xca59\xe5\x10\x87'\xcc\
+\xb1\x102\xd3\xe3\xe5\x15\xdd\xf8\xa8jr\xda\x9aP\xea\
+\x89\xf2?\xf7\x14\x0eB\xef'\xfa%\xa4\x96\x1e\xec\x00\
+\x14P\xdek\x01N\xc5\xcc\x09g\x9c,3\xde\xb0\xee\
+(\xc9q\x03\x81\xb3\xc6\xdb\x98\xd5\xa0\xa9\x1d\x80\xe7\xd4\
+\xc0;.\xa0\xb6D\xe0\x9aD\xe0\x86\x94\xa5\xa4ev\
+U\x92\xba\x89\xed\x02\x8f\xa3\x1c\xce\x97^]\xf0\x87\xe0\
+Ha\xe2]\x9a\xeaNn\xebb\x0de\xf9\xa8Z\xde\
+\xc3I\xa1\x09G\x91\xd5f\xcbw\x1eR\x83\xb5\xd6\xdd\
+\xc7]H4\x0e4\x0e\x86A\x04\xe3\x11\x91\x92(P\
+\x81A\xadJ&\xe9\x0d\xcerZ\xf0a\x8f\xfc\xc8\xe4\
+\xfe\xc3\xe0WHw\xaf\x05u\x83e\x8e\x92\x06 :\
+\x13I\x00{\x02 \xe5\x08T\xca;\xc7\x08U\xf9\x13\
+\xce;\xf9\x90w\x9eQ\x8d9\x08\x8c3Q\x18\xf8\x8a\
+R)\x05J\xa5\xd8\x07\xa5@\xa9\x14{\xaf\x14\xea\xdb\
+0\xdf*Y)\xa5=&\xcf\xba\xa9\x1b\x13\xeaCC\
+\xa3:7\xa6\x12R\xbe\x0c\x962\x03`(\xd8\xd40\
+h\x1d8\x0b\xfe\x017)\xb4\x96\x1em\x93\x16\x03\xab\
+\xa24\x89\x0aN\xc8H=iQ\x8d\x95`\xea\x1d\x80\
+\xaf\xf7\x1e\xf98+|\x00\xa7\x1d\xe7K\xcd\x80\x0e`\
+\x80\xf3\x9c\xd3\x12\xac-\xf9\xf9`\x022\xcdxtR\
+G\x9a\x03\x85\xc8\xe3\x91\x81\xd2\xa8\x90\x81\xd7\x1e\x14\xf1\
+b\x9a\x0fVY\xa6\xa9\xe0H\x16\x90\x9c\x96\xda(\x87\
+eN\x0b\x1a\x8e4&jJ\xd6d<\xe1|\xc2!\
+r3\x16\x0c\x12-f\xa4\x02\xe59\xf3S\x03\xd3\xb6\
+rMH\xba\xab+\xb9|W:^M\xb6MS\xd8\
+1I\xa5\xb0^j\x87\x18(\xeey\xb5\xfc\xbd {\
+Y\x14\xf7\xe9\xf2E=\xd5\xd1eS\x90\xf7\x1fUU\
+`\xd7F\xafm\x8f\xe3\x9f|\xa8\x13\xff\xb9\x9c-\x9a\
+\xd4\xcb\x19\xbfs\x9a\xd1\xafc]\xd1\xa6cz\xdb\xbf\
+Z\x8d\xef\x1a\xbd25:\x5c\xaa)\xc0\xc8\xfa\x07m\
+Uk\xa7\x9b\xe1\xf9\xdc\x9c\x15J\x99\xe6\xbcH\xdf\xf9\
+\xd5\x89\xe9rb\xe3b\xa2\xf2-\xda\x1d\xd3\xd2<`\
+\xe5\x91Z\xdf\xf4\xe8\xb7\xf2=\xd2)=NuhU\
+\xc5/Y\x1f\x08\xe8}\xb0\xa5\x0b\xac2C\x05\xc2\xb3\
+\xc1\xc6o\x81\x81>\xd6\xf9\xf2k\xde\x8bJ\xc8\x0b\x08\
+\x0e\x13\xd7|\x08_\xb2\x9e-\xfe\x1c\xbeO~\xa0\x1b\
+`\x1d!\xad\xaf[\x11\xa9\x0a\x83\xad>\xcd\xdaH@\
+\x929 \xbb\xf4A\xc0\x9e\xf7\xf4\x93*p\x81\xb6\x91\
+Bt\xcd\x99\x98\xb2\x5c\xc6\xe6\x0f\xdc\x82\x1c(\xb7g\
+\xef\xc9\xec\xee\xfdi\xde\x99~\xe9\x112\xcf\x97\x12\xdb\
+R:\xecJ\x99\xf7\xdc\xc1\x09\xad5I\x13\xa5\xd4\xa5\
+\x94>\xb8H\xb1\xf4\x9331ec\x19Ii\xb8\xc5\
+\xfd\xc2\x91V\x8do+\xa5iK\xe9\xe5~]\x1ad\
+\x02j\xc7RzI2\x99(\xa5aIX\xee\x92\xc2\
+?9\x13S6\x96\xb1\x94\xdc\x22\xea2\xfa\xe7\xc0\xec\
+\x9e-\xe5\x0e\xc9\x5c[2\x90P\x13\xad\xdd\xe5I\x9b\
+\x10\xa40\x83\xe8\x9f{\x81\xb5\xc3U\x88\x05\x11\x03U\
+l\x0d\xb0\x03T\x9b@mT\xb4h\xef\xef\xb1`/\
+o\x89\xd8\xea\xb7\x95\xad$\x1f]\xd0q*]\xa5\xee\
+^\xa3\xce\x5c\xa1\xee^\x9f\xce_\x9d\xde\x0f\xab\x0f`\
+\xbb\x9b\x80tup\xebu\xe9n/i,\xed+\xd2\
+\x8d\xeb\xd1h<P\xb0z\xc6E\x0e\xa5\xf0\xc6kB\
+U\x0a\x85R\xc6-bJ\xa6b-\xbd\xd0\x04\xa9\x19\
+\xa0!\x9a\xd4\xda\x1d=c\xf5\xe4ppc4j_\
+\x9bO\xdd\x90\xa26<\x7f\xa8\xe5\x14\x1d\xb6\x96S\x94\
+\xd8\xb5\xa9\xa0\x1e\xb9\x9a\xca\xeej\xca\xb4\xe4}\xaa\x95\
+S\x0e\x94L\xabi\x9b\xc6\x15\xf7\xac\xa6!\x18\xfa\x17\
+\x97\xd3\x94\x1b\x82'\x1dX\xcb\xeb\xa9#H\x8c2J\
+'\xdb\xc8l\xa1r\xba\xf7\xf8\x87\xd1}R\x81\xf7\x84\
+\xb2\x90\xe8h/\xcb\x19-\xcb\xd3GJ\xa0\xa2D\xb0\
+\xd5\xeff\xf5*\xf3\xe5\xc1jV\xc5\x84\x03s\xdd\xbe\
+\xc3\x09k!\xf9\xbb\xf8\xb7'\x86\xac\x94W\xfd\x08~\
+\xfdoL\x1c\xed\xf5\x10\xc3\xad.b\x9b\x87\xb0|R\
+0>\x1c\xb5|\x91v\xcef\x1cV\xf2f]W\xd7\
+u\x87\xc9Uv\x1dL\xe9Y\xa2\xe22\x96\x14e\xef\
+F9\x1f5\xad\xcb\x0ag\x00=x\xf7\x14K\xd9?\
+\xff\x1d\x82;\xda\xb1\x84dW\x9c\xfc\xca\x94V\xb0\xfc\
+\xaccd\xf2{\x18\x0d;\xce\xa5A\x89h\xe2n\xe0\
+\x05h\xaf\xa4S\xbc\xf6Ja\x8dF\x055\xaa\x03\x01\
+\x0e\x83V\x03\xb0\x02L\xf0\x80=\xeb\x85\xf7>(\x1c\
+(+\xbc\xb2Zqk\x87Bk4^\x0f\x10\x045\
+Ep=O\xfdx\xc3/i\x90\xb0@g\x10R\xdf\
+\x89v\xbf\x85\x08\xa0U\x9d\x0a \x851V\x87\x1aG\
+\x00\x13w\xf8\xa6\xd17\x80\xe2\x0c\xe8\xda(A\xa2@\
+m\x9cI\xd2l\x95\xfbK\xcb\xa4\xc1:g\x0fp\x8c\
+|\xb2\xcb\xfbwz\xba\xc7\x9d\x12\x15\xb4\x965\xa9\xdd\
+\x96em\xcb\x96\xcaw\xed[w\xcd[\xd7\xd64)\
+\x07\x9e\x96/=\xd0\xf7\x8bWP-B\xac\x92]\xcc\
+\x08Gt\xe5Jv\x9f4r`\x85I\xeb\xd6\x1e\xcf\
+\xdaZ\xc0\xbd\xf9\x06\xde\xfe\xff\xee\xd2\xb9\xc7\x8f\xc9\xe6\
+Gg\xfd\x1abpy\xbf\xe6}/\x82\x0d\xaa\x84\xbe\
+\x87X\xde\x16c\xef\x16\x04J\xf4\xda1\xd1{#=\
+\xf2a\xc6j\x00`R`\x15\x05U%\xa9E\xb0\x22\
+H'S);)\x1d\x8c\x8c|\x95B\x17,\xd1\x8c\
+\xd0\x5cx\xaf\xfa\xeaws )\xd7~\x1d\x89AF\
+l8\xa8\xe7\xf5\xbf\x00\x1d$M\x88\x06W\x00\x00\
+\x00\x00\x03\xee\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\x8f\
+PLTE\xff\xff\xff\xdd\xe1\xe4\x97\xa2\xad`p\x80\
+FYlCWjWhy\x86\x92\x9f\xcc\xd1\xd7\xfc\
+\xfc\xfd\xf8\xf9\xfa\xa8\xb1\xbaN`r0EZ3H\
+]<Qf=Rg5J_/DYATh\
+\x8c\x98\xa4\xec\xee\xf0\x90\x9b\xa74I^7Lai\
+~\x91\x9b\xaf\xc1\xb1\xc5\xd6\xb3\xc7\xd8\xa3\xb7\xc9x\x8d\
+\x9fAVkn|\x8b\xe9\xeb\xedBWk\x9a\xad\xbf\
+\xce\xe1\xf0\xd1\xe5\xf5\xd0\xe4\xf4\xcf\xe3\xf3\xad\xc1\xd2T\
+i}.CY\x7f\x8c\x99\xdd\xe0\xe4\x9a\xae\xbf\xdf\xec\
+\xf7\xe1\xed\xf7\xd4\xe6\xf4\xce\xe2\xf2\xd1\xe5\xf4\xb2\xc6\xd7\
+H]q7K`\xbe\xc5\xcb\x96\xa1\xac\xe4\xee\xf7\xd7\
+\xe7\xf4\xcd\xe2\xf2\x8b\x9f\xb1/EZv\x84\x92\xfb\xfc\
+\xfc_p\x80\xd4\xe6\xf3\xd1\xe4\xf3\xd2\xe4\xf3\xcf\xe2\xf2\
+\xd0\xe4\xf3\xb7\xcb\xdcM_q\xe8\xeb\xedFYk\xdf\
+\xec\xf6\xcf\xe3\xf2\xc6\xda\xeaSh|>Qe\xd8\xdc\
+\xe0CVi\xb3\xc8\xd8\xc7\xdb\xebUj~<Pd\
+\xd6\xda\xdeVgy\xbd\xd1\xe2F[pGZm\xe3\
+\xe6\xe9\x85\x92\x9e\x9a\xae\xc02H]hx\x87\xf7\xf8\
+\xf9\xcc\xd1\xd6AUh\xac\xc1\xd2\xc1\xd5\xe5Xm\x80\
+1F[\xaa\xb3\xbc\xfc\xfd\xfd\x8d\x98\xa4Ti|l\
+\x81\x94cs\x83\xf0\xf1\xf3n}\x8c\x8a\x9f\xb1Xl\
+\x80_o\x80\xe9\xeb\xee\x7f\x8d\x9a7L`/DZ\
+AVjSg{fv\x85\xf9\xf9\xfa\xbf\xc5\xcci\
+x\x87\xe4\xe6\xe9fv\x86-CXgv\x86fu\
+\x85gw\x86\xde\xe2\xe5hw\x87eu\x85\xdf\xe2\xe6\
+w\x84\x92x\xf3\xbaI\x00\x00\x00\x01bKGD\x00\
+\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\x00`\
+\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07tIM\
+E\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\x01#\
+IDAT\x18\xd3c`\x00\x02F&f\x16V6\
+v\x0eN\x068\xe0\xe2\xe6\xe1\xe5\xe3\x17\x10\x14\x12\x16\
+\x11\x85\x8b\x89\x89KHJI\xcb\xc8\xca\xc9\x0b)(\
+B\x05\xb9\xc5\x95\x94UT\xd5\xd4\xd4\xd454\xb5\xb4\
+\xb9\xc0b:<\x12\xbaz\xfa\x06\x86@`dlb\
+j\x06\x164\xe7\x95T\xd1\xb7\xb0\xb4\x02\x89\xaaY\xdb\
+\xd8\xda\x81\x04\xed\xf9\xa4T\x1d,\x1d\x9d\x9c\x81\x82.\
+\xae\xf2n\xee A\x0f~i5C+'O'\xa0\
+\xa0\x97\xb7\x8f\xaf\x1fH\xd0_ @\xcd\xd0\xd0\x19\xac\
+\xd200(8\x04$\x18*(\xabf\x08\x05\xeaa\
+\xe1\x11\x91 \xc1(!9u\x98\xa0jtLl\x1c\
+H0>A>\xd1\x08\x22\xa6\x96\x94\x9c\x92\x0avR\
+Z\xbaP\x86\xb1\x9a\x8b\xba\xa1\xbajR&oV6\
+\xc4G\xa29Z&\xb9\xae\xde\x81a\xd1y\xbcZ\xf9\
+\x91P\x7f\x16\x14\x16\x15\x97\x94\x06\x85\xc7\xa4d\xd9\x0b\
+\x95\xc1D\xcb+l\xdd|\x83#*S\xb3\xab\xaak\
+\xe0\xa2\x0cv\x8a~!\x91 {#kk\xea\x22\x19\
+\xd0\x01P\xb4\xbe\x01C\xb4\xaaQ\xa8\x09C\x90\xa1\xb9\
+\xae\x05\x00\xcf\xbe>\xfb#\xb9\xea4\x00\x00\x00%t\
+EXtdate:create\x002\
+023-03-08T17:27:\
+26+00:00\x9e\x98\xa5\x85\x00\x00\x00%\
+tEXtdate:modify\x00\
+2023-03-08T17:27\
+:26+00:00\xef\xc5\x1d9\x00\x00\x00\
+(tEXtdate:timest\
+amp\x002023-03-09T0\
+6:20:50+00:00\xdd\xe8\xb2\
+\xde\x00\x00\x00\x00IEND\xaeB`\x82\
+"
+
+qt_resource_name = b"\
+\x00\x05\
+\x00j\xc4c\
+\x00d\
+\x00e\x00m\x00o\x00s\
+\x00\x0e\
+\x00\x97\xbf\xa2\
+\x00d\
+\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00v\x00i\x00e\x00w\x00e\x00r\
+\x00\x06\
+\x07\x03}\xc3\
+\x00i\
+\x00m\x00a\x00g\x00e\x00s\
+\x00\x0c\
+\x0dJ\x02g\
+\x00p\
+\x00a\x00s\x00t\x00e\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x11\
+\x0e\xab3G\
+\x00z\
+\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\
+\
+\x00\x0c\
+\x02\xb1%j\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00.\x00s\x00v\x00g\x00z\
+\x00\x0f\
+\x0a\x12\xe5*\
+\x00h\
+\x00e\x00l\x00p\x00-\x00a\x00b\x00o\x00u\x00t\x00.\x00s\x00v\x00g\x00z\
+\x00\x10\
+\x0a~p\x07\
+\x00m\
+\x00a\x00g\x00n\x00i\x00f\x00i\x00e\x00r\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x1c\
+\x02\x8c\xf4'\
+\x00g\
+\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00-\
+\x00p\x00a\x00g\x00e\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x14\
+\x03\xba\xd1'\
+\x00z\
+\x00o\x00o\x00m\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00@\x002\x00x\x00.\
+\x00p\x00n\x00g\
+\x00\x13\
+\x05\x93\x9e\x87\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00@\x002\x00x\x00.\x00p\
+\x00n\x00g\
+\x00\x0f\
+\x0f\x91\x8b\xc7\
+\x00z\
+\x00o\x00o\x00m\x00-\x00o\x00u\x00t\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x14\
+\x04jv\xe7\
+\x00z\
+\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00b\x00e\x00s\x00t\x00@\x002\x00x\x00.\
+\x00p\x00n\x00g\
+\x00\x12\
+\x01\xfcL\x07\
+\x00z\
+\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00w\x00i\x00d\x00t\x00h\x00.\x00p\x00n\
+\x00g\
+\x00\x14\
+\x06\x10\x9a\xe7\
+\x00g\
+\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00.\
+\x00p\x00n\x00g\
+\x00\x08\
+\x06\xc1Y\x87\
+\x00o\
+\x00p\x00e\x00n\x00.\x00p\x00n\x00g\
+\x00\x18\
+\x0d\xd6\xb5G\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00-\x00p\x00a\x00g\x00e\
+\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x0b\
+\x03\x03\x9bG\
+\x00z\
+\x00o\x00o\x00m\x00-\x00i\x00n\x00.\x00p\x00n\x00g\
+\x00\x14\
+\x05\xda\x8d\xc7\
+\x00z\
+\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00@\x002\x00x\x00.\
+\x00p\x00n\x00g\
+\x00\x11\
+\x0c2UG\
+\x00z\
+\x00o\x00o\x00m\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00.\x00p\x00n\x00g\
+\
+\x00\x0a\
+\x03\x96\x0eG\
+\x00c\
+\x00u\x00t\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x0e\
+\x0f[7G\
+\x00z\
+\x00o\x00o\x00m\x00-\x00i\x00n\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x10\
+\x06O7\xc7\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00.\x00p\x00n\x00g\
+\x00\x0b\
+\x03w\x8f\x07\
+\x00o\
+\x00p\x00e\x00n\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x0c\
+\x06\xeb\x97\xe7\
+\x00z\
+\x00o\x00o\x00m\x00-\x00o\x00u\x00t\x00.\x00p\x00n\x00g\
+\x00\x15\
+\x0f!\xb3\x87\
+\x00g\
+\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00-\x00p\x00a\x00g\x00e\
+\x00.\x00p\x00n\x00g\
+\x00\x09\
+\x00W\xb8g\
+\x00p\
+\x00r\x00i\x00n\x00t\x00.\x00p\x00n\x00g\
+\x00\x15\
+\x02/h'\
+\x00z\
+\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00w\x00i\x00d\x00t\x00h\x00@\x002\x00x\
+\x00.\x00p\x00n\x00g\
+\x00\x0e\
+\x0d\x1d\xfd'\
+\x00q\
+\x00t\x00-\x00l\x00o\x00g\x00o\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x0b\
+\x05R\xbf'\
+\x00q\
+\x00t\x00-\x00l\x00o\x00g\x00o\x00.\x00p\x00n\x00g\
+\x00\x10\
+\x03g\xb5*\
+\x00g\
+\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00.\x00s\x00v\x00g\x00z\
+\x00\x08\
+\x06|Z\x07\
+\x00c\
+\x00o\x00p\x00y\x00.\x00p\x00n\x00g\
+\x00\x19\
+\x0d\x16F'\
+\x00g\
+\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00-\
+\x00p\x00a\x00g\x00e\x00.\x00p\x00n\x00g\
+\x00\x0b\
+\x03O\x98\xe7\
+\x00c\
+\x00o\x00p\x00y\x00@\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x17\
+\x0fA\x96\xe7\
+\x00g\
+\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00@\
+\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x11\
+\x09\x02(G\
+\x00z\
+\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00b\x00e\x00s\x00t\x00.\x00p\x00n\x00g\
+\
+\x00\x0b\
+\x07tw\xe7\
+\x00p\
+\x00r\x00i\x00n\x00t\x002\x00x\x00.\x00p\x00n\x00g\
+\x00\x09\
+\x0a\xa8\xbaG\
+\x00p\
+\x00a\x00s\x00t\x00e\x00.\x00p\x00n\x00g\
+\x00\x07\
+\x0a\xc7W\x87\
+\x00c\
+\x00u\x00t\x00.\x00p\x00n\x00g\
+\x00\x19\
+\x08\xc3Z\xea\
+\x00d\
+\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00o\x00p\x00e\x00n\x00-\x00r\x00e\x00c\
+\x00e\x00n\x00t\x00.\x00s\x00v\x00g\x00z\
+\x00\x0d\
+\x099\xc9\x07\
+\x00m\
+\x00a\x00g\x00n\x00i\x00f\x00i\x00e\x00r\x00.\x00p\x00n\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x10\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x002\x00\x02\x00\x00\x00&\x00\x00\x00\x04\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\xb9n\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x01\xdc\x00\x00\x00\x00\x00\x01\x00\x00m\xfb\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x00\xbc\x18\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x00\xf2\x00\x00\x00\x00\x00\x01\x00\x00H^\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0e\x98\
+\x00\x00\x01\x88\xd8-C\xa1\
+\x00\x00\x02\x80\x00\x00\x00\x00\x00\x01\x00\x00\x86\xa7\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04\xb8\x00\x00\x00\x00\x00\x01\x00\x00\xf0\x17\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04D\x00\x00\x00\x00\x00\x01\x00\x00\xd08\
+\x00\x00\x01\x88\xd8-C\xa1\
+\x00\x00\x03T\x00\x00\x00\x00\x00\x01\x00\x00\xadX\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x02\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x95\xf2\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x010\x00\x00\x00\x00\x00\x01\x00\x00Pu\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x01\xae\x00\x00\x00\x00\x00\x01\x00\x00g[\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04(\x00\x00\x00\x00\x00\x01\x00\x00\xcai\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x01^\x00\x00\x00\x00\x00\x01\x00\x00Xz\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x02\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x8ac\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x02\x06\x00\x00\x00\x00\x00\x01\x00\x00q\x88\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x03.\x00\x00\x00\x00\x00\x01\x00\x00\xa9v\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04j\x00\x00\x00\x00\x00\x01\x00\x00\xe6v\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x024\x00\x00\x00\x00\x00\x01\x00\x00u~\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x03p\x00\x00\x00\x00\x00\x01\x00\x00\xb1U\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x050\x00\x00\x00\x00\x00\x01\x00\x01\x04L\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x05x\x00\x00\x00\x00\x00\x01\x00\x01\x13\x93\
+\x00\x00\x01\x88\xd8-C\xa1\
+\x00\x00\x05\x08\x00\x00\x00\x00\x00\x01\x00\x01\x00\x1d\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x05\xb0\x00\x00\x00\x00\x00\x01\x00\x01%\xc7\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00#\xfd\
+\x00\x00\x01\x88\xd8-C\xa1\
+\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00?\x91\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x05L\x00\x00\x00\x00\x00\x01\x00\x01\x07\xf3\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x05d\x00\x00\x00\x00\x00\x01\x00\x01\x0ed\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x02\xca\x00\x00\x00\x00\x00\x01\x00\x00\x92<\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00\xeb\xb4\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04\x06\x00\x00\x00\x00\x00\x01\x00\x00\xc2\xd8\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x02J\x00\x00\x00\x00\x00\x01\x00\x00}\x9b\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x00b\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xe2\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x03\x8e\x00\x00\x00\x00\x00\x01\x00\x00\xb5\x05\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x04\xd4\x00\x00\x00\x00\x00\x01\x00\x00\xf8\xc6\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x03\x0c\x00\x00\x00\x00\x00\x01\x00\x00\xa1>\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+\x00\x00\x01\x8a\x00\x00\x00\x00\x00\x01\x00\x00_\xa4\
+\x00\x00\x01\x87\xdc\x8a\xff\x8e\
+"
+
+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/demos/documentviewer/recentfilemenu.py b/examples/demos/documentviewer/recentfilemenu.py
new file mode 100644
index 000000000..36c7dc9f3
--- /dev/null
+++ b/examples/demos/documentviewer/recentfilemenu.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import QMenu
+from PySide6.QtCore import Signal, Slot
+
+
+class RecentFileMenu(QMenu):
+ fileOpened = Signal(str)
+
+ def __init__(self, parent, recent):
+ super().__init__(parent)
+ self._recentFiles = recent
+ self._recentFiles.changed.connect(self.updateList)
+ self._recentFiles.destroyed.connect(self.deleteLater)
+ self.updateList()
+
+ @Slot()
+ def updateList(self):
+ for a in self.actions():
+ del a
+
+ if not self._recentFiles:
+ self.addAction("<no recent files>")
+ return
+
+ for fileName in self._recentFiles.recentFiles():
+ action = self.addAction(fileName)
+ action.triggered.connect(self._emitFileOpened)
+
+ @Slot()
+ def _emitFileOpened(self):
+ action = self.sender()
+ self.fileOpened.emit(action.text())
diff --git a/examples/demos/documentviewer/recentfiles.py b/examples/demos/documentviewer/recentfiles.py
new file mode 100644
index 000000000..fdfc56d40
--- /dev/null
+++ b/examples/demos/documentviewer/recentfiles.py
@@ -0,0 +1,159 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from enum import Enum, auto
+
+from PySide6.QtCore import QFileInfo, QObject, QSettings, Signal, Slot
+
+
+DEFAULT_MAX_FILES = 10
+
+
+# Test if file exists and can be opened
+def testFileAccess(fileName):
+ return QFileInfo(fileName).isReadable()
+
+
+class RemoveReason(Enum):
+ Other = auto()
+ Duplicate = auto()
+
+
+class EmitPolicy(Enum):
+ EmitWhenChanged = auto(),
+ NeverEmit = auto()
+
+
+s_maxFiles = "maxFiles"
+s_openMode = "openMode"
+s_fileNames = "fileNames"
+s_file = "file"
+
+
+class RecentFiles(QObject):
+
+ countChanged = Signal(int)
+ changed = Signal()
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._maxFiles = DEFAULT_MAX_FILES
+ self._files = []
+
+ # Access to QStringList member functions
+ def recentFiles(self):
+ return self._files
+
+ def isEmpty(self):
+ return not self._files
+
+ # Properties
+ def maxFiles(self):
+ return self._maxFiles
+
+ def setMaxFiles(self, maxFiles):
+ self._maxFiles = maxFiles
+
+ def addFile(self, fileName):
+ self._addFile(fileName, EmitPolicy.EmitWhenChanged)
+
+ def removeFile(self, fileName):
+ idx = self._files.find(fileName)
+ self._removeFile(idx, RemoveReason.Other)
+
+ @Slot()
+ def clear(self):
+ if self.isEmpty():
+ return
+ self._files.clear()
+ self.countChanged.emit(0)
+
+ def _addFile(self, fileName, policy):
+ if not testFileAccess(fileName):
+ return
+
+ # Remember size, as cleanup can result in a change without size change
+ c = len(self._files)
+
+ # Clean dangling and duplicate files
+ i = 0
+ while i < len(self._files):
+ file = self._files[i]
+ if not testFileAccess(file):
+ self._removeFile(file, RemoveReason.Other)
+ elif file == fileName:
+ self._removeFile(file, RemoveReason.Duplicate)
+ else:
+ i += 1
+
+ # Cut tail
+ while len(self._files) > self._maxFiles:
+ self.removeFile((len(self._files) - 1), RemoveReason.Other)
+
+ self._files.insert(0, fileName)
+
+ if policy == EmitPolicy.NeverEmit:
+ return
+
+ if policy == EmitPolicy.EmitWhenChanged:
+ self.changed.emit()
+ if c != len(self._files):
+ self.countChanged.emit(len(self._files))
+
+ @Slot(list)
+ def addFiles(self, files):
+ if files.isEmpty():
+ return
+
+ if len(files) == 1:
+ self.addFile(files[0])
+ return
+
+ c = len(self._files)
+
+ for file in files:
+ self.addFile(file, EmitPolicy.NeverEmit)
+
+ self.changed.emit()
+ if len(self._files) != c:
+ self.countChanged.emit(len(self._files))
+
+ def _removeFile(self, p, reason):
+ index = p
+ if isinstance(p, str):
+ index = self._files.index(p) if p in self._files else -1
+ if index < 0 or index >= len(self._files):
+ return
+ del self._files[index]
+
+ # No emit for duplicate removal, add emits changed later.
+ if reason != RemoveReason.Duplicate:
+ self.changed.emit()
+
+ @Slot(QSettings, str)
+ def saveSettings(self, settings, key):
+ settings.beginGroup(key)
+ settings.setValue(s_maxFiles, self.maxFiles())
+ if self._files:
+ settings.beginWriteArray(s_fileNames, len(self._files))
+ for index, file in enumerate(self._files):
+ settings.setArrayIndex(index)
+ settings.setValue(s_file, file)
+ settings.endArray()
+ settings.endGroup()
+
+ @Slot(QSettings, str)
+ def restoreFromSettings(self, settings, key):
+ settings.beginGroup(key)
+ self.setMaxFiles(settings.value(s_maxFiles, DEFAULT_MAX_FILES, int))
+ self._files.clear() # clear list without emitting
+ numberFiles = settings.beginReadArray(s_fileNames)
+ for index in range(0, numberFiles):
+ settings.setArrayIndex(index)
+ absoluteFilePath = settings.value(s_file)
+ self._addFile(absoluteFilePath, EmitPolicy.NeverEmit)
+ settings.endArray()
+ settings.endGroup()
+ if self._files:
+ self.changed.emit()
+ return True
diff --git a/examples/demos/documentviewer/txtviewer/txtviewer.py b/examples/demos/documentviewer/txtviewer/txtviewer.py
new file mode 100644
index 000000000..ef5a38fcd
--- /dev/null
+++ b/examples/demos/documentviewer/txtviewer/txtviewer.py
@@ -0,0 +1,144 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import (QDialog, QFileDialog,
+ QPlainTextEdit)
+from PySide6.QtGui import QAction, QGuiApplication, QIcon, QKeySequence
+from PySide6.QtCore import QDir, QFile, QTextStream, Qt, Slot
+
+from abstractviewer import AbstractViewer
+
+
+class TxtViewer(AbstractViewer):
+ def __init__(self):
+ super().__init__()
+ self.uiInitialized.connect(self.setupTxtUi)
+
+ def init(self, file, parent, mainWindow):
+ self._textEdit = QPlainTextEdit(parent)
+ super().init(file, self._textEdit, mainWindow)
+
+ def viewerName(self):
+ return "TxtViewer"
+
+ def supportedMimeTypes(self):
+ return ["text/plain"]
+
+ @Slot()
+ def setupTxtUi(self):
+ editMenu = self.addMenu("Edit")
+ editToolBar = self.addToolBar("Edit")
+ cutIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut,
+ QIcon(":/demos/documentviewer/images/cut.png"))
+ cutAct = QAction(cutIcon, "Cut", self)
+ cutAct.setShortcuts(QKeySequence.Cut)
+ cutAct.setStatusTip("Cut the current selection's contents to the clipboard")
+ cutAct.triggered.connect(self._textEdit.cut)
+ editMenu.addAction(cutAct)
+ editToolBar.addAction(cutAct)
+
+ copyIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy,
+ QIcon(":/demos/documentviewer/images/copy.png"))
+ copyAct = QAction(copyIcon, "Copy", self)
+ copyAct.setShortcuts(QKeySequence.Copy)
+ copyAct.setStatusTip("Copy the current selection's contents to the clipboard")
+ copyAct.triggered.connect(self._textEdit.copy)
+ editMenu.addAction(copyAct)
+ editToolBar.addAction(copyAct)
+
+ pasteIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste,
+ QIcon(":/demos/documentviewer/images/paste.png"))
+ pasteAct = QAction(pasteIcon, "Paste", self)
+ pasteAct.setShortcuts(QKeySequence.Paste)
+ pasteAct.setStatusTip("Paste the clipboard's contents into the current selection")
+ pasteAct.triggered.connect(self._textEdit.paste)
+ editMenu.addAction(pasteAct)
+ editToolBar.addAction(pasteAct)
+
+ self.menuBar().addSeparator()
+
+ cutAct.setEnabled(False)
+ copyAct.setEnabled(False)
+ self._textEdit.copyAvailable.connect(cutAct.setEnabled)
+ self._textEdit.copyAvailable.connect(copyAct.setEnabled)
+
+ self.openFile()
+
+ self._textEdit.textChanged.connect(self._textChanged)
+ self._uiAssets_back.triggered.connect(self._back)
+ self._uiAssets_forward.triggered.connect(self._forward)
+
+ @Slot()
+ def _textChanged(self):
+ self.maybeSetPrintingEnabled(self.hasContent())
+
+ @Slot()
+ def _back(self):
+ bar = self._textEdit.verticalScrollBar()
+ if bar.value() > bar.minimum():
+ bar.setValue(bar.value() - 1)
+
+ @Slot()
+ def _forward(self):
+ bar = self._textEdit.verticalScrollBar()
+ if bar.value() < bar.maximum():
+ bar.setValue(bar.value() + 1)
+
+ def openFile(self):
+ type = "open"
+ file_name = QDir.toNativeSeparators(self._file.fileName())
+ if not self._file.open(QFile.ReadOnly | QFile.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)
+ if self._textEdit.toPlainText():
+ self._textEdit.clear()
+ self.disablePrinting()
+
+ self._textEdit.setPlainText(in_str.readAll())
+ QGuiApplication.restoreOverrideCursor()
+
+ self.statusMessage(f"File {file_name} loaded.", type)
+ self.maybeEnablePrinting()
+
+ def hasContent(self):
+ return bool(self._textEdit.toPlainText())
+
+ def printDocument(self, printer):
+ if not self.hasContent():
+ return
+
+ self._textEdit.print_(printer)
+
+ def saveFile(self, file):
+ file_name = QDir.toNativeSeparators(self._file.fileName())
+ errorMessage = ""
+ QGuiApplication.setOverrideCursor(Qt.WaitCursor)
+ if file.open(QFile.WriteOnly | QFile.Text):
+ out = QTextStream(file)
+ out << self._textEdit.toPlainText()
+ else:
+ error = file.errorString()
+ errorMessage = f"Cannot open file {file_name} for writing:\n{error}."
+ QGuiApplication.restoreOverrideCursor()
+
+ if errorMessage:
+ self.statusMessage(errorMessage)
+ return False
+
+ self.statusMessage(f"File {file_name} saved")
+ return True
+
+ def saveDocumentAs(self):
+ dialog = QFileDialog(self.mainWindow())
+ dialog.setWindowModality(Qt.WindowModal)
+ dialog.setAcceptMode(QFileDialog.AcceptSave)
+ if dialog.exec() != QDialog.Accepted:
+ return False
+
+ files = dialog.selectedFiles()
+ self._file.setFileName(files[0])
+ return self.saveDocument()
diff --git a/examples/demos/documentviewer/ui_mainwindow.py b/examples/demos/documentviewer/ui_mainwindow.py
new file mode 100644
index 000000000..5773ae5b8
--- /dev/null
+++ b/examples/demos/documentviewer/ui_mainwindow.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'mainwindow.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
+ QCursor, QFont, QFontDatabase, QGradient,
+ QIcon, QImage, QKeySequence, QLinearGradient,
+ QPainter, QPalette, QPixmap, QRadialGradient,
+ QTransform)
+from PySide6.QtWidgets import (QApplication, QMainWindow, QMenu, QMenuBar,
+ QScrollArea, QSizePolicy, QSplitter, QStatusBar,
+ QTabWidget, QToolBar, QVBoxLayout, QWidget)
+import rc_documentviewer
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ if not MainWindow.objectName():
+ MainWindow.setObjectName(u"MainWindow")
+ MainWindow.resize(983, 602)
+ icon = QIcon()
+ icon.addFile(u":/demos/documentviewer/images/qt-logo.png", QSize(), QIcon.Normal, QIcon.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)
+ self.actionOpen.setIcon(icon1)
+ self.actionAbout = QAction(MainWindow)
+ self.actionAbout.setObjectName(u"actionAbout")
+ icon2 = QIcon()
+ iconThemeName = u"help-about"
+ if QIcon.hasThemeIcon(iconThemeName):
+ icon2 = QIcon.fromTheme(iconThemeName)
+ else:
+ icon2.addFile(u":/demos/documentviewer/images/help-about.svgz", QSize(), QIcon.Normal, QIcon.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)
+ 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)
+ self.actionBack.setIcon(icon4)
+ self.actionPrint = QAction(MainWindow)
+ self.actionPrint.setObjectName(u"actionPrint")
+ self.actionPrint.setEnabled(False)
+ icon5 = QIcon()
+ iconThemeName = u"document-print"
+ if QIcon.hasThemeIcon(iconThemeName):
+ icon5 = QIcon.fromTheme(iconThemeName)
+ else:
+ icon5.addFile(u":/demos/documentviewer/images/print2x.png", QSize(), QIcon.Normal, QIcon.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)
+ 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)
+ 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)
+
+ self.actionQuit.setIcon(icon8)
+ self.centralwidget = QWidget(MainWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.centralwidget.setEnabled(True)
+ self.verticalLayout = QVBoxLayout(self.centralwidget)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.viewArea = QWidget(self.centralwidget)
+ self.viewArea.setObjectName(u"viewArea")
+ self.verticalLayout_2 = QVBoxLayout(self.viewArea)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.splitter = QSplitter(self.viewArea)
+ self.splitter.setObjectName(u"splitter")
+ self.splitter.setOrientation(Qt.Horizontal)
+ self.tabWidget = QTabWidget(self.splitter)
+ self.tabWidget.setObjectName(u"tabWidget")
+ self.tabWidget.setTabPosition(QTabWidget.West)
+ self.bookmarkTab = QWidget()
+ self.bookmarkTab.setObjectName(u"bookmarkTab")
+ self.tabWidget.addTab(self.bookmarkTab, "")
+ self.pagesTab = QWidget()
+ self.pagesTab.setObjectName(u"pagesTab")
+ self.tabWidget.addTab(self.pagesTab, "")
+ self.splitter.addWidget(self.tabWidget)
+ self.scrollArea = QScrollArea(self.splitter)
+ self.scrollArea.setObjectName(u"scrollArea")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth())
+ self.scrollArea.setSizePolicy(sizePolicy)
+ self.scrollArea.setMinimumSize(QSize(800, 0))
+ self.scrollArea.setWidgetResizable(True)
+ self.scrollAreaWidgetContents = QWidget()
+ self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
+ self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 798, 479))
+ self.scrollArea.setWidget(self.scrollAreaWidgetContents)
+ self.splitter.addWidget(self.scrollArea)
+
+ self.verticalLayout_2.addWidget(self.splitter)
+
+
+ self.verticalLayout.addWidget(self.viewArea)
+
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(MainWindow)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 983, 23))
+ self.qtFileMenu = QMenu(self.menubar)
+ self.qtFileMenu.setObjectName(u"qtFileMenu")
+ self.menuHelp = QMenu(self.menubar)
+ self.menuHelp.setObjectName(u"menuHelp")
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(MainWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+ self.mainToolBar = QToolBar(MainWindow)
+ self.mainToolBar.setObjectName(u"mainToolBar")
+ MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar)
+
+ self.menubar.addAction(self.qtFileMenu.menuAction())
+ self.menubar.addAction(self.menuHelp.menuAction())
+ self.qtFileMenu.addAction(self.actionOpen)
+ self.qtFileMenu.addAction(self.actionRecent)
+ self.qtFileMenu.addAction(self.actionPrint)
+ self.qtFileMenu.addAction(self.actionQuit)
+ self.menuHelp.addAction(self.actionAbout)
+ self.menuHelp.addAction(self.actionAboutQt)
+ self.mainToolBar.addAction(self.actionOpen)
+ self.mainToolBar.addAction(self.actionRecent)
+ self.mainToolBar.addAction(self.actionPrint)
+ self.mainToolBar.addSeparator()
+ self.mainToolBar.addAction(self.actionBack)
+ self.mainToolBar.addAction(self.actionForward)
+ self.mainToolBar.addSeparator()
+
+ self.retranslateUi(MainWindow)
+ self.actionQuit.triggered.connect(MainWindow.close)
+
+ self.tabWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(MainWindow)
+ # setupUi
+
+ def retranslateUi(self, MainWindow):
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Document Viewer Demo", None))
+ self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"Open", None))
+#if QT_CONFIG(shortcut)
+ self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"about documentviewer", None))
+#if QT_CONFIG(tooltip)
+ self.actionAbout.setToolTip(QCoreApplication.translate("MainWindow", u"Show information about the Document Viewer deomo.", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionAbout.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+H", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionForward.setText(QCoreApplication.translate("MainWindow", u"actionForward", None))
+#if QT_CONFIG(tooltip)
+ self.actionForward.setToolTip(QCoreApplication.translate("MainWindow", u"One step forward", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionForward.setShortcut(QCoreApplication.translate("MainWindow", u"Right", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionBack.setText(QCoreApplication.translate("MainWindow", u"actionBack", None))
+#if QT_CONFIG(tooltip)
+ self.actionBack.setToolTip(QCoreApplication.translate("MainWindow", u"One step back", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionBack.setShortcut(QCoreApplication.translate("MainWindow", u"Left", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionPrint.setText(QCoreApplication.translate("MainWindow", u"Print", None))
+#if QT_CONFIG(tooltip)
+ self.actionPrint.setToolTip(QCoreApplication.translate("MainWindow", u"Print current file", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionPrint.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+P", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionAboutQt.setText(QCoreApplication.translate("MainWindow", u"About Qt", None))
+#if QT_CONFIG(tooltip)
+ self.actionAboutQt.setToolTip(QCoreApplication.translate("MainWindow", u"Show Qt license information", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionAboutQt.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+I", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionRecent.setText(QCoreApplication.translate("MainWindow", u"Recently opened...", None))
+#if QT_CONFIG(shortcut)
+ self.actionRecent.setShortcut(QCoreApplication.translate("MainWindow", u"Meta+R", None))
+#endif // QT_CONFIG(shortcut)
+ self.actionQuit.setText(QCoreApplication.translate("MainWindow", u"Quit", None))
+#if QT_CONFIG(tooltip)
+ self.actionQuit.setToolTip(QCoreApplication.translate("MainWindow", u"Quit the application", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+ self.actionQuit.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None))
+#endif // QT_CONFIG(shortcut)
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.bookmarkTab), QCoreApplication.translate("MainWindow", u"Pages", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.pagesTab), QCoreApplication.translate("MainWindow", u"Bookmarks", None))
+ self.qtFileMenu.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
+ self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
+ self.mainToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
+ # retranslateUi
+
diff --git a/examples/demos/documentviewer/viewerfactory.py b/examples/demos/documentviewer/viewerfactory.py
new file mode 100644
index 000000000..ecae6770b
--- /dev/null
+++ b/examples/demos/documentviewer/viewerfactory.py
@@ -0,0 +1,114 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from enum import Enum, auto
+
+from PySide6.QtWidgets import (QMessageBox)
+from PySide6.QtCore import (QFileInfo, QMimeDatabase, QTimer)
+
+from txtviewer.txtviewer import TxtViewer
+from jsonviewer.jsonviewer import JsonViewer
+from pdfviewer.pdfviewer import PdfViewer
+
+
+class DefaultPolicy(Enum):
+ NeverDefault = auto()
+ DefaultToTxtViewer = auto()
+ DefaultToCustomViewer = auto()
+
+
+class ViewerFactory:
+
+ def __init__(self, displayWidget, mainWindow,
+ policy=DefaultPolicy.NeverDefault):
+ self._viewers = {}
+ self._defaultViewer = None
+ self._defaultWarning = True
+ self._defaultPolicy = policy
+ self._displayWidget = displayWidget
+ self._mainWindow = mainWindow
+ self._mimeTypes = []
+ for v in [PdfViewer(), JsonViewer(), TxtViewer()]:
+ self._viewers[v.viewerName()] = v
+ if v.isDefaultViewer():
+ self._defaultViewer = v
+
+ def defaultPolicy(self):
+ return self._defaultPolicy
+
+ def setDefaultPolicy(self, policy):
+ self._defaultPolicy = policy
+
+ def defaultWarning(self):
+ return self._defaultWarning
+
+ def setDefaultWarning(self, on):
+ self._defaultWarning = on
+
+ def viewer(self, file):
+ info = QFileInfo(file)
+ db = QMimeDatabase()
+ mimeType = db.mimeTypeForFile(info)
+
+ viewer = self.viewerForMimeType(mimeType)
+ if not viewer:
+ print(f"Mime type {mimeType.name()} not supported.")
+ return None
+
+ viewer.init(file, self._displayWidget, self._mainWindow)
+ return viewer
+
+ def viewerNames(self, showDefault=False):
+ if not showDefault:
+ return self._viewers.keys()
+
+ list = []
+ for name, viewer in self._viewers.items():
+ if ((self._defaultViewer and viewer.isDefaultViewer())
+ or (not self._defaultViewer and name == "TxtViewer")):
+ name += "(default)"
+ list.append(name)
+ return list
+
+ def viewers(self):
+ return self._viewers.values()
+
+ def findViewer(self, viewerName):
+ for viewer in self.viewers():
+ if viewer.viewerName() == viewerName:
+ return viewer
+ print(f"Plugin {viewerName} not loaded.")
+ return None
+
+ def viewerForMimeType(self, mimeType):
+ for viewer in self.viewers():
+ for type in viewer.supportedMimeTypes():
+ if mimeType.inherits(type):
+ return viewer
+
+ viewer = self.defaultViewer()
+
+ if self._defaultWarning:
+ mbox = QMessageBox()
+ mbox.setIcon(QMessageBox.Warning)
+ name = mimeType.name()
+ viewer_name = viewer.viewerName()
+ m = f"Mime type {name} not supported. Falling back to {viewer_name}."
+ mbox.setText(m)
+ mbox.setStandardButtons(QMessageBox.Ok)
+ QTimer.singleShot(8000, mbox.close)
+ mbox.exec()
+ return viewer
+
+ def defaultViewer(self):
+ if self._defaultPolicy == DefaultPolicy.NeverDefault:
+ return None
+ if self._defaultPolicy == DefaultPolicy.DefaultToCustomViewer and self._defaultViewer:
+ return self._defaultViewer
+ return self.findViewer("TxtViewer")
+
+ def supportedMimeTypes(self):
+ if not self._mimeTypes:
+ for viewer in self.viewers():
+ self._mimeTypes.extend(viewer.supportedMimeTypes())
+ return self._mimeTypes
diff --git a/examples/designer/taskmenuextension/doc/taskmenuextension.rst b/examples/designer/taskmenuextension/doc/taskmenuextension.rst
index a1b584fb9..dd7fe1679 100644
--- a/examples/designer/taskmenuextension/doc/taskmenuextension.rst
+++ b/examples/designer/taskmenuextension/doc/taskmenuextension.rst
@@ -1,5 +1,7 @@
-Task Menu Extension (Designer)
-==============================
+.. _task-menu-extension-example:
+
+Task Menu Extension Example
+===========================
This example shows how to add custom widgets to Qt Designer,
which can be launched with `pyside6-designer`, and to extend
diff --git a/examples/designer/taskmenuextension/registertictactoe.py b/examples/designer/taskmenuextension/registertictactoe.py
index 4f163e374..2b014c790 100644
--- a/examples/designer/taskmenuextension/registertictactoe.py
+++ b/examples/designer/taskmenuextension/registertictactoe.py
@@ -1,7 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from tictactoe import TicTacToe
+from tictactoe import TicTacToe # noqa: F401
from tictactoeplugin import TicTacToePlugin
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
diff --git a/examples/designer/taskmenuextension/tictactoe.py b/examples/designer/taskmenuextension/tictactoe.py
index 9155d2309..aa1c3158c 100644
--- a/examples/designer/taskmenuextension/tictactoe.py
+++ b/examples/designer/taskmenuextension/tictactoe.py
@@ -89,27 +89,27 @@ class TicTacToe(QWidget):
for position in range(0, 8, 3):
if (self._state[position] != EMPTY
- and self._state[position + 1] == self._state[position]
- and self._state[position + 2] == self._state[position]):
+ and self._state[position + 1] == self._state[position]
+ and self._state[position + 2] == self._state[position]):
y = self._cell_rect(position).center().y()
painter.drawLine(0, y, self.width(), y)
self._turn_number = 9
for position in range(3):
if (self._state[position] != EMPTY
- and self._state[position + 3] == self._state[position]
- and self._state[position + 6] == self._state[position]):
+ and self._state[position + 3] == self._state[position]
+ and self._state[position + 6] == self._state[position]):
x = self._cell_rect(position).center().x()
painter.drawLine(x, 0, x, self.height())
self._turn_number = 9
if (self._state[0] != EMPTY and self._state[4] == self._state[0]
- and self._state[8] == self._state[0]):
+ and self._state[8] == self._state[0]):
painter.drawLine(0, 0, self.width(), self.height())
self._turn_number = 9
if (self._state[2] != EMPTY and self._state[4] == self._state[2]
- and self._state[6] == self._state[2]):
+ and self._state[6] == self._state[2]):
painter.drawLine(0, self.height(), self.width(), 0)
self._turn_number = 9
diff --git a/examples/designer/taskmenuextension/tictactoeplugin.py b/examples/designer/taskmenuextension/tictactoeplugin.py
index 0d538443f..f9c925133 100644
--- a/examples/designer/taskmenuextension/tictactoeplugin.py
+++ b/examples/designer/taskmenuextension/tictactoeplugin.py
@@ -5,7 +5,7 @@ from tictactoe import TicTacToe
from tictactoetaskmenu import TicTacToeTaskMenuFactory
from PySide6.QtGui import QIcon
-from PySide6.QtDesigner import QDesignerCustomWidgetInterface
+from PySide6.QtDesigner import QDesignerCustomWidgetInterface
DOM_XML = """
diff --git a/examples/external/matplotlib/widget3d/widget3d.py b/examples/external/matplotlib/widget3d/widget3d.py
index bed6c7ef5..8bfcc4ca2 100644
--- a/examples/external/matplotlib/widget3d/widget3d.py
+++ b/examples/external/matplotlib/widget3d/widget3d.py
@@ -32,12 +32,12 @@ class ApplicationWindow(QMainWindow):
# Main menu bar
self.menu = self.menuBar()
self.menu_file = self.menu.addMenu("File")
- exit = QAction("Exit", self, triggered=qApp.quit)
+ exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821
self.menu_file.addAction(exit)
self.menu_about = self.menu.addMenu("&About")
about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents),
- triggered=qApp.aboutQt)
+ triggered=qApp.aboutQt) # noqa: F821
self.menu_about.addAction(about)
# Figure (Left)
diff --git a/examples/external/opencv/webcam_pattern_detection.py b/examples/external/opencv/webcam_pattern_detection.py
index a3a1e3782..0c55a1333 100644
--- a/examples/external/opencv/webcam_pattern_detection.py
+++ b/examples/external/opencv/webcam_pattern_detection.py
@@ -74,12 +74,12 @@ class Window(QMainWindow):
# Main menu bar
self.menu = self.menuBar()
self.menu_file = self.menu.addMenu("File")
- exit = QAction("Exit", self, triggered=qApp.quit)
+ exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821
self.menu_file.addAction(exit)
self.menu_about = self.menu.addMenu("&About")
about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents),
- triggered=qApp.aboutQt)
+ triggered=qApp.aboutQt) # noqa: F821
self.menu_about.addAction(about)
# Create a label for the display camera
diff --git a/examples/external/scikit/staining_colors_separation.py b/examples/external/scikit/staining_colors_separation.py
index b0aa143e2..94fdc3bdc 100644
--- a/examples/external/scikit/staining_colors_separation.py
+++ b/examples/external/scikit/staining_colors_separation.py
@@ -32,12 +32,12 @@ class ApplicationWindow(QMainWindow):
# Main menu bar
self.menu = self.menuBar()
self.menu_file = self.menu.addMenu("File")
- exit = QAction("Exit", self, triggered=qApp.quit)
+ exit = QAction("Exit", self, triggered=qApp.quit) # noqa: F821
self.menu_file.addAction(exit)
self.menu_about = self.menu.addMenu("&About")
about = QAction("About Qt", self, shortcut=QKeySequence(QKeySequence.HelpContents),
- triggered=qApp.aboutQt)
+ triggered=qApp.aboutQt) # noqa: F821
self.menu_about.addAction(about)
# Create an artificial color close to the original one
diff --git a/examples/graphs/2d/hellographs/HelloGraphs/Main.qml b/examples/graphs/2d/hellographs/HelloGraphs/Main.qml
new file mode 100644
index 000000000..b1844aec4
--- /dev/null
+++ b/examples/graphs/2d/hellographs/HelloGraphs/Main.qml
@@ -0,0 +1,153 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtGraphs
+
+Item {
+ id: mainView
+ width: 1280
+ height: 720
+
+ RowLayout {
+ id: graphsRow
+
+ readonly property real margin: mainView.width * 0.02
+
+ anchors.fill: parent
+ anchors.margins: margin
+ spacing: margin
+
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ color: "#262626"
+ border.color: "#4d4d4d"
+ border.width: 1
+ radius: graphsRow.margin
+ //! [bargraph]
+ GraphsView {
+ anchors.fill: parent
+ anchors.margins: 16
+ theme: GraphTheme {
+ colorTheme: GraphTheme.ColorThemeDark
+ }
+ //! [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 {
+ values: [82, 50, 75]
+ borderWidth: 2
+ color: "#373F26"
+ borderColor: "#DBEB00"
+ }
+ //! [barset]
+ }
+ }
+ }
+
+ Rectangle {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ color: "#262626"
+ border.color: "#4d4d4d"
+ border.width: 1
+ radius: graphsRow.margin
+
+ //! [linegraph]
+ GraphsView {
+ anchors.fill: parent
+ anchors.margins: 16
+ theme: GraphTheme {
+ 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
+ }
+ //! [linegraph]
+
+ //! [linemarker]
+ component Marker : Rectangle {
+ width: 16
+ height: 16
+ color: "#ffffff"
+ radius: width * 0.5
+ border.width: 4
+ border.color: "#000000"
+ }
+ //! [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 { }
+ XYPoint { x: 0; y: 0 }
+ XYPoint { x: 1; y: 2.1 }
+ XYPoint { x: 2; y: 3.3 }
+ XYPoint { x: 3; y: 2.1 }
+ XYPoint { x: 4; y: 4.9 }
+ XYPoint { x: 5; y: 3.0 }
+ }
+ //! [lineseries1]
+
+ //! [lineseries2]
+ LineSeries {
+ id: lineSeries2
+ theme: seriesTheme
+ width: 4
+ pointMarker: Marker { }
+ XYPoint { x: 0; y: 5.0 }
+ XYPoint { x: 1; y: 3.3 }
+ XYPoint { x: 2; y: 7.1 }
+ XYPoint { x: 3; y: 7.5 }
+ XYPoint { x: 4; y: 6.1 }
+ XYPoint { x: 5; y: 3.2 }
+ }
+ //! [lineseries2]
+ }
+ }
+ }
+}
diff --git a/examples/graphs/2d/hellographs/HelloGraphs/qmldir b/examples/graphs/2d/hellographs/HelloGraphs/qmldir
new file mode 100644
index 000000000..007f5fb11
--- /dev/null
+++ b/examples/graphs/2d/hellographs/HelloGraphs/qmldir
@@ -0,0 +1,2 @@
+module HelloGraphs
+Main 1.0 Main.qml
diff --git a/examples/graphs/2d/hellographs/doc/hellographs.rst b/examples/graphs/2d/hellographs/doc/hellographs.rst
new file mode 100644
index 000000000..d0820c3b7
--- /dev/null
+++ b/examples/graphs/2d/hellographs/doc/hellographs.rst
@@ -0,0 +1,51 @@
+HelloGraphs Example
+===================
+
+The example shows how to make a simple 2D bar graph and line graph.
+
+BarGraph
+--------
+
+The first graph in the example is a bar graph. Creating it starts with a GraphsView
+component and setting the theme to one which is suitable on
+dark backgrounds. This theme adjusts the graph background grid and axis lines and
+labels.
+
+To make this a bar graph, add a ``BarSeries.`` The X axis of the series is a
+``BarCategoryAxis`` with 3 categories. We hide both the vertical grid and the
+axis lines. The Y axis of the series is ``ValueAxis`` with visible range
+between 20 and 100. Major ticks with labels will be shown on every 10 values
+using the ``tickInterval`` property. Minor ticks will be shown on every 1
+values setting the ``minorTickCount`` propertyt to 9, which means that between
+every major ticks there will be 9 minor ones.
+
+Then data is added into ``BarSeries`` using ``BarSet.`` There are 3 bars, and we define
+custom bars color and border properties. These properties will override the possible
+theme set for the ``AbstractSeries.``
+
+LineGraph
+---------
+
+The second graph of the example is a line graph. It also starts by defining a
+``GraphsView`` element. A custom ``GraphTheme`` is created to get a custom appearance.
+``GraphTheme`` offers quite a wide range of customization possibilities for the background
+grid and axis, which get applied after the ``colorTheme``.
+
+A custom ``Marker`` component is used to visualize the data points.
+
+The previous bar graph didn't define a separate ``SeriesTheme``, so it uses the
+default theme. This line graph uses a custom theme with the desired line colors.
+
+To make this a line graph, add a ``LineSeries.`` The first series defines
+``axisX`` and ``axisY`` for this graph. It also sets the ``pointMarker`` to use
+the custom ``Marker`` component that was created earlier. Data points are added
+using ``XYPoint`` elements.
+
+The second line series is similar to the first. The ``axisX`` and ``axisY``
+don't need to be defined as the graph already contains them. As this is the
+second ``LineSeries`` inside the ``GraphsView``, second color from the
+``seriesTheme`` gets automatically picked.
+
+.. image:: hellographs.webp
+ :width: 1293
+ :alt: HelloGraphs Screenshot
diff --git a/examples/graphs/2d/hellographs/doc/hellographs.webp b/examples/graphs/2d/hellographs/doc/hellographs.webp
new file mode 100644
index 000000000..3e7666411
--- /dev/null
+++ b/examples/graphs/2d/hellographs/doc/hellographs.webp
Binary files differ
diff --git a/examples/graphs/2d/hellographs/hellographs.pyproject b/examples/graphs/2d/hellographs/hellographs.pyproject
new file mode 100644
index 000000000..e8e8cb228
--- /dev/null
+++ b/examples/graphs/2d/hellographs/hellographs.pyproject
@@ -0,0 +1,3 @@
+{
+ "files": ["main.py", "HelloGraphs/Main.qml", "HelloGraphs/qmldir"]
+}
diff --git a/examples/graphs/2d/hellographs/main.py b/examples/graphs/2d/hellographs/main.py
new file mode 100644
index 000000000..acc349beb
--- /dev/null
+++ b/examples/graphs/2d/hellographs/main.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the Qt Hello Graphs example from Qt v6.x"""
+
+from pathlib import Path
+import sys
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQuick import QQuickView
+
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+
+ viewer = QQuickView()
+ viewer.engine().addImportPath(Path(__file__).parent)
+ viewer.setColor("black")
+ viewer.loadFromModule("HelloGraphs", "Main")
+ viewer.show()
+ r = app.exec()
+ del viewer
+ sys.exit(r)
diff --git a/examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst b/examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst
new file mode 100644
index 000000000..bfc7a044d
--- /dev/null
+++ b/examples/graphs/3d/minimalsurfacegraph/doc/minimalsurfacegraph.rst
@@ -0,0 +1,4 @@
+Minimal Surface Example
+=======================
+
+The example shows the minimal code to create a surface.
diff --git a/examples/graphs/3d/minimalsurfacegraph/main.py b/examples/graphs/3d/minimalsurfacegraph/main.py
new file mode 100644
index 000000000..5fb4b4472
--- /dev/null
+++ b/examples/graphs/3d/minimalsurfacegraph/main.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtCore import QSize
+from PySide6.QtGui import QVector3D
+from PySide6.QtGraphs import (Q3DSurface, QSurfaceDataItem,
+ QSurface3DSeries)
+from PySide6.QtWidgets import QApplication
+from PySide6.QtQuickWidgets import QQuickWidget
+
+
+DESCRIPTION = """Minimal Qt Graphs Surface Example
+
+Use the mouse wheel to zoom. Rotate using the right mouse button.
+"""
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+
+ print(DESCRIPTION)
+
+ surface = Q3DSurface()
+ axis = surface.axisX()
+ axis.setTitle("X")
+ axis.setTitleVisible(True)
+ axis = surface.axisY()
+ axis.setTitle("Y")
+ axis.setTitleVisible(True)
+ axis = surface.axisZ()
+ axis.setTitle("Z")
+ axis.setTitleVisible(True)
+
+ data = []
+ data_row1 = [QSurfaceDataItem(QVector3D(0, 0.1, 0.5)),
+ QSurfaceDataItem(QVector3D(1, 0.5, 0.5))]
+ data.append(data_row1)
+ data_row2 = [QSurfaceDataItem(QVector3D(0, 1.8, 1)),
+ QSurfaceDataItem(QVector3D(1, 1.2, 1))]
+ data.append(data_row2)
+
+ series = QSurface3DSeries()
+ series.dataProxy().resetArray(data)
+ surface.addSeries(series)
+
+ available_height = app.primaryScreen().availableGeometry().height()
+ width = available_height * 4 / 5
+ surface.resize(QSize(width, width))
+ surface.setResizeMode(QQuickWidget.SizeRootObjectToView)
+ surface.show()
+
+ sys.exit(app.exec())
diff --git a/examples/graphs/3d/widgetgallery/axesinputhandler.py b/examples/graphs/3d/widgetgallery/axesinputhandler.py
new file mode 100644
index 000000000..4c4202974
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/axesinputhandler.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from enum import Enum
+from math import sin, cos, degrees
+
+from PySide6.QtCore import Qt
+from PySide6.QtGraphs import QAbstract3DGraph, Q3DInputHandler
+
+
+class InputState(Enum):
+ StateNormal = 0
+ StateDraggingX = 1
+ StateDraggingZ = 2
+ StateDraggingY = 3
+
+
+class AxesInputHandler(Q3DInputHandler):
+
+ def __init__(self, graph, parent=None):
+ super().__init__(parent)
+ self._mousePressed = False
+ self._state = InputState.StateNormal
+ self._axisX = None
+ self._axisZ = None
+ self._axisY = None
+ self._speedModifier = 15.0
+
+ # Connect to the item selection signal from graph
+ graph.selectedElementChanged.connect(self.handleElementSelected)
+
+ def setAxes(self, axisX, axisZ, axisY):
+ self._axisX = axisX
+ self._axisZ = axisZ
+ self._axisY = axisY
+
+ def setDragSpeedModifier(self, modifier):
+ self._speedModifier = modifier
+
+ def mousePressEvent(self, event, mousePos):
+ super().mousePressEvent(event, mousePos)
+ if Qt.LeftButton == event.button():
+ self._mousePressed = True
+
+ def mouseMoveEvent(self, event, mousePos):
+ # Check if we're trying to drag axis label
+ if self._mousePressed and self._state != InputState.StateNormal:
+ self.setPreviousInputPos(self.inputPosition())
+ self.setInputPosition(mousePos)
+ self.handleAxisDragging()
+ else:
+ super().mouseMoveEvent(event, mousePos)
+
+ def mouseReleaseEvent(self, event, mousePos):
+ super().mouseReleaseEvent(event, mousePos)
+ self._mousePressed = False
+ self._state = InputState.StateNormal
+
+ def handleElementSelected(self, type):
+ if type == QAbstract3DGraph.ElementAxisXLabel:
+ self._state = InputState.StateDraggingX
+ elif type == QAbstract3DGraph.ElementAxisYLabel:
+ self._state = InputState.StateDraggingY
+ elif type == QAbstract3DGraph.ElementAxisZLabel:
+ self._state = InputState.StateDraggingZ
+ else:
+ self._state = InputState.StateNormal
+
+ def handleAxisDragging(self):
+ distance = 0.0
+ # Get scene orientation from active camera
+ xRotation = self.cameraXRotation()
+ yRotation = self.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 = self.inputPosition() - self.previousInputPos()
+
+ # 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:
+ distance = (move.x() * xMulX - yMove * xMulY) / self._speedModifier
+ self._axisX.setRange(self._axisX.min() - distance,
+ self._axisX.max() - distance)
+ elif self._state == InputState.StateDraggingZ:
+ distance = (move.x() * zMulX + yMove * zMulY) / self._speedModifier
+ self._axisZ.setRange(self._axisZ.min() + distance,
+ self._axisZ.max() + distance)
+ elif self._state == InputState.StateDraggingY:
+ # No need to use adjusted y move here
+ distance = move.y() / self._speedModifier
+ self._axisY.setRange(self._axisY.min() + distance,
+ self._axisY.max() + distance)
diff --git a/examples/graphs/3d/widgetgallery/bargraph.py b/examples/graphs/3d/widgetgallery/bargraph.py
new file mode 100644
index 000000000..822acb4a9
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/bargraph.py
@@ -0,0 +1,272 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from graphmodifier import GraphModifier
+
+from PySide6.QtCore import QObject, Qt
+from PySide6.QtGui import QFont
+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)
+
+
+class BarGraph(QObject):
+
+ def __init__(self, minimum_graph_size, maximum_graph_size):
+ super().__init__()
+ self._barsGraph = Q3DBars()
+ 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)
+
+ 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.setCurrentIndex(0)
+
+ labelButton = QPushButton(self._barsWidget)
+ labelButton.setText("Change label style")
+
+ smoothCheckBox = QCheckBox(self._barsWidget)
+ smoothCheckBox.setText("Smooth bars")
+ smoothCheckBox.setChecked(False)
+
+ barStyleList = QComboBox(self._barsWidget)
+ barStyleList.addItem("Bar", QAbstract3DSeries.Mesh.Bar)
+ barStyleList.addItem("Pyramid", QAbstract3DSeries.Mesh.Pyramid)
+ barStyleList.addItem("Cone", QAbstract3DSeries.Mesh.Cone)
+ barStyleList.addItem("Cylinder", QAbstract3DSeries.Mesh.Cylinder)
+ barStyleList.addItem("Bevel bar", QAbstract3DSeries.Mesh.BevelBar)
+ barStyleList.addItem("Sphere", QAbstract3DSeries.Mesh.Sphere)
+ barStyleList.setCurrentIndex(4)
+
+ cameraButton = QPushButton(self._barsWidget)
+ cameraButton.setText("Change camera preset")
+
+ zoomToSelectedButton = QPushButton(self._barsWidget)
+ 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("Bar and Row", sel)
+ selectionModeList.addItem("Column", QAbstract3DGraph.SelectionColumn)
+ sel = QAbstract3DGraph.SelectionItemAndColumn
+ selectionModeList.addItem("Bar and Column", sel)
+ sel = QAbstract3DGraph.SelectionRowAndColumn
+ selectionModeList.addItem("Row and Column", sel)
+ sel = QAbstract3DGraph.SelectionItemRowAndColumn
+ selectionModeList.addItem("Bar, Row and Column", sel)
+ sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionRow
+ selectionModeList.addItem("Slice into Row", sel)
+ sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionItemAndRow
+ selectionModeList.addItem("Slice into Row and Item", sel)
+ sel = QAbstract3DGraph.SelectionSlice | QAbstract3DGraph.SelectionColumn
+ selectionModeList.addItem("Slice into Column", sel)
+ sel = (QAbstract3DGraph.SelectionSlice
+ | QAbstract3DGraph.SelectionItemAndColumn)
+ selectionModeList.addItem("Slice into Column and Item", sel)
+ sel = (QAbstract3DGraph.SelectionItemRowAndColumn
+ | QAbstract3DGraph.SelectionMultiSeries)
+ selectionModeList.addItem("Multi: Bar, Row, Col", sel)
+ sel = (QAbstract3DGraph.SelectionSlice
+ | QAbstract3DGraph.SelectionItemAndRow
+ | QAbstract3DGraph.SelectionMultiSeries)
+ selectionModeList.addItem("Multi, Slice: Row, Item", sel)
+ sel = (QAbstract3DGraph.SelectionSlice
+ | QAbstract3DGraph.SelectionItemAndColumn
+ | QAbstract3DGraph.SelectionMultiSeries)
+ selectionModeList.addItem("Multi, Slice: Col, Item", sel)
+ selectionModeList.setCurrentIndex(1)
+
+ backgroundCheckBox = QCheckBox(self._barsWidget)
+ backgroundCheckBox.setText("Show background")
+ backgroundCheckBox.setChecked(False)
+
+ gridCheckBox = QCheckBox(self._barsWidget)
+ gridCheckBox.setText("Show grid")
+ gridCheckBox.setChecked(True)
+
+ seriesCheckBox = QCheckBox(self._barsWidget)
+ seriesCheckBox.setText("Show second series")
+ seriesCheckBox.setChecked(False)
+
+ reverseValueAxisCheckBox = QCheckBox(self._barsWidget)
+ 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.setTickInterval(30)
+ rotationSliderX.setTickPosition(QSlider.TicksBelow)
+ rotationSliderX.setMinimum(-180)
+ rotationSliderX.setValue(0)
+ rotationSliderX.setMaximum(180)
+ rotationSliderY = QSlider(Qt.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.setTickInterval(10)
+ fontSizeSlider.setTickPosition(QSlider.TicksBelow)
+ fontSizeSlider.setMinimum(1)
+ fontSizeSlider.setValue(30)
+ fontSizeSlider.setMaximum(100)
+
+ fontList = QFontComboBox(self._barsWidget)
+ fontList.setCurrentFont(QFont("Times New Roman"))
+
+ shadowQuality = QComboBox(self._barsWidget)
+ shadowQuality.addItem("None")
+ shadowQuality.addItem("Low")
+ shadowQuality.addItem("Medium")
+ shadowQuality.addItem("High")
+ shadowQuality.addItem("Low Soft")
+ shadowQuality.addItem("Medium Soft")
+ shadowQuality.addItem("High Soft")
+ shadowQuality.setCurrentIndex(5)
+
+ rangeList = QComboBox(self._barsWidget)
+ rangeList.addItem("2015")
+ rangeList.addItem("2016")
+ rangeList.addItem("2017")
+ rangeList.addItem("2018")
+ rangeList.addItem("2019")
+ rangeList.addItem("2020")
+ rangeList.addItem("2021")
+ rangeList.addItem("2022")
+ rangeList.addItem("All")
+ rangeList.setCurrentIndex(8)
+
+ axisTitlesVisibleCB = QCheckBox(self._barsWidget)
+ axisTitlesVisibleCB.setText("Axis titles visible")
+ axisTitlesVisibleCB.setChecked(True)
+
+ axisTitlesFixedCB = QCheckBox(self._barsWidget)
+ axisTitlesFixedCB.setText("Axis titles fixed")
+ axisTitlesFixedCB.setChecked(True)
+
+ axisLabelRotationSlider = QSlider(Qt.Horizontal, self._barsWidget)
+ axisLabelRotationSlider.setTickInterval(10)
+ axisLabelRotationSlider.setTickPosition(QSlider.TicksBelow)
+ axisLabelRotationSlider.setMinimum(0)
+ axisLabelRotationSlider.setValue(30)
+ axisLabelRotationSlider.setMaximum(90)
+
+ modeGroup = QButtonGroup(self._barsWidget)
+ modeWeather = QRadioButton("Temperature Data", self._barsWidget)
+ modeWeather.setChecked(True)
+ modeCustomProxy = QRadioButton("Custom Proxy Data", self._barsWidget)
+ modeGroup.addButton(modeWeather)
+ modeGroup.addButton(modeCustomProxy)
+
+ vLayout.addWidget(QLabel("Rotate horizontally"))
+ vLayout.addWidget(rotationSliderX, 0, Qt.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(backgroundCheckBox)
+ vLayout.addWidget(gridCheckBox)
+ vLayout.addWidget(smoothCheckBox)
+ vLayout.addWidget(reflectionCheckBox)
+ vLayout.addWidget(seriesCheckBox)
+ vLayout.addWidget(reverseValueAxisCheckBox)
+ vLayout.addWidget(axisTitlesVisibleCB)
+ vLayout.addWidget(axisTitlesFixedCB)
+ vLayout.addWidget(QLabel("Show year"))
+ vLayout.addWidget(rangeList)
+ vLayout.addWidget(QLabel("Change bar style"))
+ vLayout.addWidget(barStyleList)
+ vLayout.addWidget(QLabel("Change selection mode"))
+ vLayout.addWidget(selectionModeList)
+ vLayout.addWidget(QLabel("Change theme"))
+ vLayout.addWidget(themeList)
+ vLayout.addWidget(QLabel("Adjust shadow quality"))
+ vLayout.addWidget(shadowQuality)
+ vLayout.addWidget(QLabel("Change font"))
+ vLayout.addWidget(fontList)
+ 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)
+
+ self._modifier = GraphModifier(self._barsGraph, self)
+
+ rotationSliderX.valueChanged.connect(self._modifier.rotateX)
+ rotationSliderY.valueChanged.connect(self._modifier.rotateY)
+
+ labelButton.clicked.connect(self._modifier.changeLabelBackground)
+ cameraButton.clicked.connect(self._modifier.changePresetCamera)
+ zoomToSelectedButton.clicked.connect(self._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)
+
+ self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked)
+ self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked)
+
+ rangeList.currentIndexChanged.connect(self._modifier.changeRange)
+
+ barStyleList.currentIndexChanged.connect(self._modifier.changeStyle)
+
+ selectionModeList.currentIndexChanged.connect(self._modifier.changeSelectionMode)
+
+ themeList.currentIndexChanged.connect(self._modifier.changeTheme)
+
+ shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality)
+
+ self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex)
+ self._barsGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual)
+
+ fontSizeSlider.valueChanged.connect(self._modifier.changeFontSize)
+ fontList.currentFontChanged.connect(self._modifier.changeFont)
+
+ self._modifier.fontSizeChanged.connect(fontSizeSlider.setValue)
+ self._modifier.fontChanged.connect(fontList.setCurrentFont)
+
+ axisTitlesVisibleCB.stateChanged.connect(self._modifier.setAxisTitleVisibility)
+ axisTitlesFixedCB.stateChanged.connect(self._modifier.setAxisTitleFixed)
+ axisLabelRotationSlider.valueChanged.connect(self._modifier.changeLabelRotation)
+
+ modeWeather.toggled.connect(self._modifier.setDataModeToWeather)
+ modeCustomProxy.toggled.connect(self._modifier.setDataModeToCustom)
+ modeWeather.toggled.connect(seriesCheckBox.setEnabled)
+ modeWeather.toggled.connect(rangeList.setEnabled)
+ modeWeather.toggled.connect(axisTitlesVisibleCB.setEnabled)
+ modeWeather.toggled.connect(axisTitlesFixedCB.setEnabled)
+ modeWeather.toggled.connect(axisLabelRotationSlider.setEnabled)
+
+ def barsWidget(self):
+ return self._barsWidget
diff --git a/examples/graphs/3d/widgetgallery/custominputhandler.py b/examples/graphs/3d/widgetgallery/custominputhandler.py
new file mode 100644
index 000000000..15fe00e70
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/custominputhandler.py
@@ -0,0 +1,177 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from enum import Enum
+from math import sin, cos, degrees
+
+from PySide6.QtCore import Qt
+from PySide6.QtGraphs import (QAbstract3DGraph, Q3DInputHandler)
+
+
+class InputState(Enum):
+ StateNormal = 0
+ StateDraggingX = 1
+ StateDraggingZ = 2
+ StateDraggingY = 3
+
+
+class CustomInputHandler(Q3DInputHandler):
+
+ def __init__(self, graph, parent=None):
+ super().__init__(parent)
+ self._highlight = None
+ self._mousePressed = False
+ self._state = InputState.StateNormal
+ self._axisX = None
+ self._axisY = None
+ self._axisZ = None
+ self._speedModifier = 20.0
+ self._aspectRatio = 0.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
+
+ # Connect to the item selection signal from graph
+ graph.selectedElementChanged.connect(self.handleElementSelected)
+
+ def setAspectRatio(self, ratio):
+ self._aspectRatio = ratio
+
+ def setHighlightSeries(self, series):
+ self._highlight = series
+
+ def setDragSpeedModifier(self, modifier):
+ self._speedModifier = modifier
+
+ def setLimits(self, min, max, minRange):
+ self._areaMinValue = min
+ self._areaMaxValue = max
+ self._axisXMinValue = self._areaMinValue
+ self._axisXMaxValue = self._areaMaxValue
+ self._axisZMinValue = self._areaMinValue
+ self._axisZMaxValue = self._areaMaxValue
+ self._axisXMinRange = minRange
+ self._axisZMinRange = minRange
+
+ def setAxes(self, axisX, axisY, axisZ):
+ self._axisX = axisX
+ self._axisY = axisY
+ self._axisZ = axisZ
+
+ def mousePressEvent(self, event, mousePos):
+ if Qt.LeftButton == event.button():
+ self._highlight.setVisible(False)
+ self._mousePressed = True
+ super().mousePressEvent(event, mousePos)
+
+ def wheelEvent(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) * self._aspectRatio
+
+ self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue)
+ self._axisY.setRange(100.0, y)
+ self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue)
+
+ def mouseMoveEvent(self, event, mousePos):
+ # Check if we're trying to drag axis label
+ if self._mousePressed and self._state != InputState.StateNormal:
+ self.setPreviousInputPos(self.inputPosition())
+ self.setInputPosition(mousePos)
+ self.handleAxisDragging()
+ else:
+ super().mouseMoveEvent(event, mousePos)
+
+ def mouseReleaseEvent(self, event, mousePos):
+ super().mouseReleaseEvent(event, mousePos)
+ self._mousePressed = False
+ self._state = InputState.StateNormal
+
+ def handleElementSelected(self, type):
+ if type == QAbstract3DGraph.ElementAxisXLabel:
+ self._state = InputState.StateDraggingX
+ elif type == QAbstract3DGraph.ElementAxisZLabel:
+ self._state = InputState.StateDraggingZ
+ else:
+ self._state = InputState.StateNormal
+
+ def handleAxisDragging(self):
+ distance = 0.0
+
+ # Get scene orientation from active camera
+ xRotation = self.scene().cameraXRotation()
+
+ # Calculate directional drag multipliers based on rotation
+ xMulX = cos(degrees(xRotation))
+ xMulY = sin(degrees(xRotation))
+ zMulX = xMulY
+ zMulY = xMulX
+
+ # Get the drag amount
+ move = self.inputPosition() - self.previousInputPos()
+
+ # Adjust axes
+ if self._state == InputState.StateDraggingX:
+ distance = (move.x() * xMulX - move.y() * xMulY) * self._speedModifier
+ 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._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/data/layer_1.png b/examples/graphs/3d/widgetgallery/data/layer_1.png
new file mode 100644
index 000000000..9138c710a
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/layer_1.png
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/layer_2.png b/examples/graphs/3d/widgetgallery/data/layer_2.png
new file mode 100644
index 000000000..61631ae8b
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/layer_2.png
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/layer_3.png b/examples/graphs/3d/widgetgallery/data/layer_3.png
new file mode 100644
index 000000000..066ffbe75
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/layer_3.png
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/license.txt b/examples/graphs/3d/widgetgallery/data/license.txt
new file mode 100644
index 000000000..749daf31f
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/license.txt
@@ -0,0 +1,77 @@
+License information regarding the data obtained from National Land Survey of
+Finland http://www.maanmittauslaitos.fi/en
+- topographic model from Elevation model 2 m (U4421B, U4421D, U4422A and
+ U4422C) 08/2014
+- map image extracted from Topographic map raster 1:50 000 (U442) 08/2014
+
+National Land Survey open data licence - version 1.0 - 1 May 2012
+
+1. General information
+
+The National Land Survey of Finland (hereinafter the Licensor), as the holder
+of the immaterial rights to the data, has granted on the terms mentioned below
+the right to use a copy (hereinafter data or dataset(s)) of the data (or a part
+of it).
+
+The Licensee is a natural or legal person who makes use of the data covered by
+this licence. The Licensee accepts the terms of this licence by receiving the
+dataset(s) covered by the licence.
+
+This Licence agreement does not create a co-operation or business relationship
+between the Licensee and the Licensor.
+
+2. Terms of the licence
+
+2.1. Right of use
+
+This licence grants a worldwide, free of charge and irrevocable parallel right
+of use to open data. According to the terms of the licence, data received by
+the Licensee can be freely:
+ - copied, distributed and published,
+ - modified and utilised commercially and non-commercially,
+ - inserted into other products and
+ - used as a part of a software application or service.
+
+2.2. Duties and responsibilities of the Licensee
+
+Through reasonable means suitable to the distribution medium or method which is
+used in conjunction with a product containing data or a service utilising data
+covered by this licence or while distributing data, the Licensee shall:
+ - mention the name of the Licensor, the name of the dataset(s) and the time
+ when the National Land Survey has delivered the dataset(s) (e.g.: contains
+ data from the National Land Survey of Finland Topographic Database 06/2012)
+ - provide a copy of this licence or a link to it, as well as
+ - require third parties to provide the same information when granting rights
+ to copies of dataset(s) or products and services containing such data and
+ - remove the name of the Licensor from the product or service, if required to
+ do so by the Licensor.
+
+The terms of this licence do not allow the Licensee to state in conjunction
+with the use of dataset(s) that the Licensor supports or recommends such use.
+
+2.3. Duties and responsibilities of the Licensor
+
+The Licensor shall ensure that
+ - the Licensor has the right to grant rights to the dataset(s) in accordance
+ with this licence.
+
+The data has been licensed "as is" and the Licensor
+ - shall not be held responsible for any errors or omissions in the data,
+ disclaims any warranty for the validity or up to date status of the data and
+ shall be free from liability for direct or consequential damages arising
+ from the use of data provided by the Licensor,
+ - and is not obligated to ensure the continuous availability of the data, nor
+ to announce in advance the interruption or cessation of availability, and
+ the Licensor shall be free from liability for direct or consequential
+ damages arising from any such interruption or cessation.
+
+3. Jurisdiction
+
+Finnish law shall apply to this licence.
+
+4. Changes to this licence
+
+The Licensor may at any time change the terms of the licence or apply a
+different licence to the data. The terms of this licence shall, however, still
+apply to such data that has been received prior to the change of the terms of
+the licence or the licence itself.
diff --git a/examples/graphs/3d/widgetgallery/data/maptexture.jpg b/examples/graphs/3d/widgetgallery/data/maptexture.jpg
new file mode 100644
index 000000000..ae5d66ebe
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/maptexture.jpg
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh b/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh
new file mode 100644
index 000000000..288867b1e
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/narrowarrow.mesh
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/oilrig.mesh b/examples/graphs/3d/widgetgallery/data/oilrig.mesh
new file mode 100644
index 000000000..4a7baeddf
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/oilrig.mesh
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/pipe.mesh b/examples/graphs/3d/widgetgallery/data/pipe.mesh
new file mode 100644
index 000000000..984b6d443
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/pipe.mesh
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/raindata.txt b/examples/graphs/3d/widgetgallery/data/raindata.txt
new file mode 100644
index 000000000..d95589219
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/raindata.txt
@@ -0,0 +1,158 @@
+# Rainfall per month from 2010 to 2022 in Northern Finland (Oulu)
+# Format: year, month, rainfall
+2010,1, 0,
+2010,2, 3.4,
+2010,3, 52,
+2010,4, 33.8,
+2010,5, 45.6,
+2010,6, 43.8,
+2010,7, 104.6,
+2010,8, 105.4,
+2010,9, 107.2,
+2010,10,38.6,
+2010,11,17.8,
+2010,12,0,
+2011,1, 8.2,
+2011,2, 1.6,
+2011,3, 27.4,
+2011,4, 15.8,
+2011,5, 57.6,
+2011,6, 85.2,
+2011,7, 127,
+2011,8, 72.2,
+2011,9, 82.2,
+2011,10,62.4,
+2011,11,31.6,
+2011,12,53.8,
+2012,1, 0,
+2012,2, 5,
+2012,3, 32.4,
+2012,4, 57.6,
+2012,5, 71.4,
+2012,6, 60.8,
+2012,7, 109,
+2012,8, 43.6,
+2012,9, 79.4,
+2012,10,117.2,
+2012,11,59,
+2012,12,0.2,
+2013,1, 28,
+2013,2, 19,
+2013,3, 0,
+2013,4, 37.6,
+2013,5, 44.2,
+2013,6, 104.8,
+2013,7, 84.2,
+2013,8, 57.2,
+2013,9, 37.2,
+2013,10,64.6,
+2013,11,77.8,
+2013,12,92.8,
+2014,1, 23.8,
+2014,2, 23.6,
+2014,3, 15.4,
+2014,4, 13.2,
+2014,5, 36.4,
+2014,6, 26.4,
+2014,7, 95.8,
+2014,8, 81.8,
+2014,9, 13.8,
+2014,10,94.6,
+2014,11,44.6,
+2014,12,31,
+2015,1, 37.4,
+2015,2, 21,
+2015,3, 42,
+2015,4, 8.8,
+2015,5, 82.4,
+2015,6, 150,
+2015,7, 56.8,
+2015,8, 67.2,
+2015,9, 131.2,
+2015,10,38.4,
+2015,11,83.4,
+2015,12,47.8,
+2016,1, 12.4,
+2016,2, 34.8,
+2016,3, 29,
+2016,4, 40.4,
+2016,5, 32.4,
+2016,6, 80.2,
+2016,7, 102.6,
+2016,8, 95.6,
+2016,9, 40.2,
+2016,10,7.8,
+2016,11,39.6,
+2016,12,8.8,
+2017,1, 9.4,
+2017,2, 6.6,
+2017,3, 29,
+2017,4, 46.2,
+2017,5, 43.2,
+2017,6, 25.2,
+2017,7, 72.4,
+2017,8, 58.8,
+2017,9, 68.8,
+2017,10,45.8,
+2017,11,36.8,
+2017,12,29.6,
+2018,1, 19.8,
+2018,2, 0.8,
+2018,3, 4,
+2018,4, 23.2,
+2018,5, 13.2,
+2018,6, 62.8,
+2018,7, 33,
+2018,8, 96.6,
+2018,9, 72.6,
+2018,10,48.8,
+2018,11,31.8,
+2018,12,12.8,
+2019,1, 0.2,
+2019,2, 24.8,
+2019,3, 32,
+2019,4, 8.8,
+2019,5, 71.4,
+2019,6, 65.8,
+2019,7, 17.6,
+2019,8, 90,
+2019,9, 50,
+2019,10,77,
+2019,11,27,
+2019,12,43.2,
+2020,1, 28.8,
+2020,2, 45,
+2020,3, 18.6,
+2020,4, 13,
+2020,5, 30.8,
+2020,6, 21.4,
+2020,7, 163.6,
+2020,8, 12,
+2020,9, 102.4,
+2020,10,133.2,
+2020,11,69.8,
+2020,12,40.6,
+2021,1, 0.4,
+2021,2, 21.6,
+2021,3, 24,
+2021,4, 51.4,
+2021,5, 76.4,
+2021,6, 29.2,
+2021,7, 36.4,
+2021,8, 116,
+2021,9, 72.4,
+2021,10,93.4,
+2021,11,21,
+2021,12,10.2,
+2022,1, 8.6,
+2022,2, 6.6,
+2022,3, 5.2,
+2022,4, 15.2,
+2022,5, 37.6,
+2022,6, 45,
+2022,7, 67.4,
+2022,8, 161.6,
+2022,9, 22.8,
+2022,10,75.2,
+2022,11,21.8,
+2022,12,0.2
diff --git a/examples/graphs/3d/widgetgallery/data/refinery.mesh b/examples/graphs/3d/widgetgallery/data/refinery.mesh
new file mode 100644
index 000000000..a7e249353
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/refinery.mesh
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/data/topography.png b/examples/graphs/3d/widgetgallery/data/topography.png
new file mode 100644
index 000000000..9349cdb31
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/data/topography.png
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst b/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst
new file mode 100644
index 000000000..1470001d6
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/doc/widgetgallery.rst
@@ -0,0 +1,11 @@
+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/doc/widgetgallery.webp b/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp
new file mode 100644
index 000000000..eb5767264
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/doc/widgetgallery.webp
Binary files differ
diff --git a/examples/graphs/3d/widgetgallery/graphmodifier.py b/examples/graphs/3d/widgetgallery/graphmodifier.py
new file mode 100644
index 000000000..2eaafa792
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/graphmodifier.py
@@ -0,0 +1,391 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+from math import atan, degrees
+import numpy as np
+
+from PySide6.QtCore import QObject, QPropertyAnimation, Signal, Slot
+from PySide6.QtGui import QFont, QVector3D
+from PySide6.QtGraphs import (QAbstract3DGraph, QAbstract3DSeries,
+ QBarDataItem, QBar3DSeries, QCategory3DAxis,
+ QValue3DAxis, Q3DTheme)
+
+from rainfalldata import RainfallData
+
+# Set up data
+TEMP_OULU = np.array([
+ [-7.4, -2.4, 0.0, 3.0, 8.2, 11.6, 14.7, 15.4, 11.4, 4.2, 2.1, -2.3], # 2015
+ [-13.4, -3.9, -1.8, 3.1, 10.6, 13.7, 17.8, 13.6, 10.7, 3.5, -3.1, -4.2], # 2016
+ [-5.7, -6.7, -3.0, -0.1, 4.7, 12.4, 16.1, 14.1, 9.4, 3.0, -0.3, -3.2], # 2017
+ [-6.4, -11.9, -7.4, 1.9, 11.4, 12.4, 21.5, 16.1, 11.0, 4.4, 2.1, -4.1], # 2018
+ [-11.7, -6.1, -2.4, 3.9, 7.2, 14.5, 15.6, 14.4, 8.5, 2.0, -3.0, -1.5], # 2019
+ [-2.1, -3.4, -1.8, 0.6, 7.0, 17.1, 15.6, 15.4, 11.1, 5.6, 1.9, -1.7], # 2020
+ [-9.6, -11.6, -3.2, 2.4, 7.8, 17.3, 19.4, 14.2, 8.0, 5.2, -2.2, -8.6], # 2021
+ [-7.3, -6.4, -1.8, 1.3, 8.1, 15.5, 17.6, 17.6, 9.1, 5.4, -1.5, -4.4]], # 2022
+ np.float64)
+
+
+TEMP_HELSINKI = np.array([
+ [-2.0, -0.1, 1.8, 5.1, 9.7, 13.7, 16.3, 17.3, 12.7, 5.4, 4.6, 2.1], # 2015
+ [-10.3, -0.6, 0.0, 4.9, 14.3, 15.7, 17.7, 16.0, 12.7, 4.6, -1.0, -0.9], # 2016
+ [-2.9, -3.3, 0.7, 2.3, 9.9, 13.8, 16.1, 15.9, 11.4, 5.0, 2.7, 0.7], # 2017
+ [-2.2, -8.4, -4.7, 5.0, 15.3, 15.8, 21.2, 18.2, 13.3, 6.7, 2.8, -2.0], # 2018
+ [-6.2, -0.5, -0.3, 6.8, 10.6, 17.9, 17.5, 16.8, 11.3, 5.2, 1.8, 1.4], # 2019
+ [1.9, 0.5, 1.7, 4.5, 9.5, 18.4, 16.5, 16.8, 13.0, 8.2, 4.4, 0.9], # 2020
+ [-4.7, -8.1, -0.9, 4.5, 10.4, 19.2, 20.9, 15.4, 9.5, 8.0, 1.5, -6.7], # 2021
+ [-3.3, -2.2, -0.2, 3.3, 9.6, 16.9, 18.1, 18.9, 9.2, 7.6, 2.3, -3.4]], # 2022
+ np.float64)
+
+
+class GraphModifier(QObject):
+
+ shadowQualityChanged = Signal(int)
+ backgroundEnabledChanged = Signal(bool)
+ gridEnabledChanged = Signal(bool)
+ fontChanged = Signal(QFont)
+ fontSizeChanged = Signal(int)
+
+ def __init__(self, bargraph, parent):
+ super().__init__(parent)
+ self._graph = bargraph
+ self._temperatureAxis = QValue3DAxis()
+ self._yearAxis = QCategory3DAxis()
+ self._monthAxis = QCategory3DAxis()
+ self._primarySeries = QBar3DSeries()
+ self._secondarySeries = QBar3DSeries()
+ self._celsiusString = "°C"
+
+ self._xRotation = float(0)
+ self._yRotation = float(0)
+ self._fontSize = 30
+ self._segments = 4
+ self._subSegments = 3
+ self._minval = float(-20)
+ self._maxval = float(20)
+ self._barMesh = QAbstract3DSeries.Mesh.BevelBar
+ self._smooth = False
+ self._animationCameraX = QPropertyAnimation()
+ self._animationCameraY = QPropertyAnimation()
+ self._animationCameraZoom = QPropertyAnimation()
+ self._animationCameraTarget = QPropertyAnimation()
+ self._defaultAngleX = float(0)
+ self._defaultAngleY = float(0)
+ self._defaultZoom = float(0)
+ self._defaultTarget = []
+ self._customData = None
+
+ self._graph.setShadowQuality(QAbstract3DGraph.ShadowQuality.SoftMedium)
+ theme = self._graph.activeTheme()
+ theme.setBackgroundEnabled(False)
+ theme.setFont(QFont("Times New Roman", self._fontSize))
+ theme.setLabelBackgroundEnabled(True)
+ self._graph.setMultiSeriesUniform(True)
+
+ self._months = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November",
+ "December"]
+ self._years = ["2015", "2016", "2017", "2018", "2019", "2020",
+ "2021", "2022"]
+
+ self._temperatureAxis.setTitle("Average temperature")
+ self._temperatureAxis.setSegmentCount(self._segments)
+ 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.setTitleVisible(True)
+
+ self._yearAxis.setTitle("Year")
+ self._yearAxis.setLabelAutoRotation(30.0)
+ self._yearAxis.setTitleVisible(True)
+ self._monthAxis.setTitle("Month")
+ self._monthAxis.setLabelAutoRotation(30.0)
+ self._monthAxis.setTitleVisible(True)
+
+ self._graph.setValueAxis(self._temperatureAxis)
+ self._graph.setRowAxis(self._yearAxis)
+ self._graph.setColumnAxis(self._monthAxis)
+
+ format = "Oulu - @colLabel @rowLabel: @valueLabel"
+ self._primarySeries.setItemLabelFormat(format)
+ self._primarySeries.setMesh(QAbstract3DSeries.Mesh.BevelBar)
+ self._primarySeries.setMeshSmooth(False)
+
+ format = "Helsinki - @colLabel @rowLabel: @valueLabel"
+ self._secondarySeries.setItemLabelFormat(format)
+ self._secondarySeries.setMesh(QAbstract3DSeries.Mesh.BevelBar)
+ self._secondarySeries.setMeshSmooth(False)
+ self._secondarySeries.setVisible(False)
+
+ self._graph.addSeries(self._primarySeries)
+ self._graph.addSeries(self._secondarySeries)
+
+ self.changePresetCamera()
+
+ self.resetTemperatureData()
+
+ # Set up property animations for zooming to the selected bar
+ self._defaultAngleX = self._graph.cameraXRotation()
+ self._defaultAngleY = self._graph.cameraYRotation()
+ self._defaultZoom = self._graph.cameraZoomLevel()
+ self._defaultTarget = self._graph.cameraTargetPosition()
+
+ self._animationCameraX.setTargetObject(self._graph)
+ self._animationCameraY.setTargetObject(self._graph)
+ self._animationCameraZoom.setTargetObject(self._graph)
+ self._animationCameraTarget.setTargetObject(self._graph)
+
+ self._animationCameraX.setPropertyName(b"cameraXRotation")
+ self._animationCameraY.setPropertyName(b"cameraYRotation")
+ self._animationCameraZoom.setPropertyName(b"cameraZoomLevel")
+ self._animationCameraTarget.setPropertyName(b"cameraTargetPosition")
+
+ duration = 1700
+ self._animationCameraX.setDuration(duration)
+ self._animationCameraY.setDuration(duration)
+ self._animationCameraZoom.setDuration(duration)
+ self._animationCameraTarget.setDuration(duration)
+
+ # The zoom always first zooms out above the graph and then zooms in
+ zoomOutFraction = 0.3
+ self._animationCameraX.setKeyValueAt(zoomOutFraction, 0.0)
+ self._animationCameraY.setKeyValueAt(zoomOutFraction, 90.0)
+ self._animationCameraZoom.setKeyValueAt(zoomOutFraction, 50.0)
+ self._animationCameraTarget.setKeyValueAt(zoomOutFraction,
+ QVector3D(0, 0, 0))
+ self._customData = RainfallData()
+
+ def resetTemperatureData(self):
+ # Create data arrays
+ dataSet = []
+ dataSet2 = []
+
+ for year in range(0, len(self._years)):
+ # Create a data row
+ dataRow = []
+ dataRow2 = []
+ for month in range(0, len(self._months)):
+ # Add data to the row
+ item = QBarDataItem()
+ item.setValue(TEMP_OULU[year][month])
+ dataRow.append(item)
+ item = QBarDataItem()
+ item.setValue(TEMP_HELSINKI[year][month])
+ dataRow2.append(item)
+
+ # Add the row to the set
+ dataSet.append(dataRow)
+ dataSet2.append(dataRow2)
+
+ # Add data to the data proxy (the data proxy assumes ownership of it)
+ self._primarySeries.dataProxy().resetArray(dataSet, self._years, self._months)
+ self._secondarySeries.dataProxy().resetArray(dataSet2, self._years, self._months)
+
+ @Slot(int)
+ def changeRange(self, range):
+ if range >= len(self._years):
+ self._yearAxis.setRange(0, len(self._years) - 1)
+ else:
+ self._yearAxis.setRange(range, range)
+
+ @Slot(int)
+ def changeStyle(self, style):
+ comboBox = self.sender()
+ if comboBox:
+ self._barMesh = comboBox.itemData(style)
+ self._primarySeries.setMesh(self._barMesh)
+ self._secondarySeries.setMesh(self._barMesh)
+ self._customData.customSeries().setMesh(self._barMesh)
+
+ def changePresetCamera(self):
+ self._animationCameraX.stop()
+ self._animationCameraY.stop()
+ self._animationCameraZoom.stop()
+ self._animationCameraTarget.stop()
+
+ # 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._graph.setCameraPreset(QAbstract3DGraph.CameraPreset(self._preset))
+
+ self._preset += 1
+ if self._preset > QAbstract3DGraph.CameraPreset.DirectlyBelow.value:
+ self._preset = QAbstract3DGraph.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())
+
+ def changeLabelBackground(self):
+ theme = self._graph.activeTheme()
+ theme.setLabelBackgroundEnabled(not theme.isLabelBackgroundEnabled())
+
+ @Slot(int)
+ def changeSelectionMode(self, selectionMode):
+ comboBox = self.sender()
+ if comboBox:
+ flags = comboBox.itemData(selectionMode)
+ self._graph.setSelectionMode(QAbstract3DGraph.SelectionFlags(flags))
+
+ def changeFont(self, font):
+ newFont = font
+ self._graph.activeTheme().setFont(newFont)
+
+ def changeFontSize(self, fontsize):
+ self._fontSize = fontsize
+ font = self._graph.activeTheme().font()
+ font.setPointSize(self._fontSize)
+ self._graph.activeTheme().setFont(font)
+
+ @Slot(QAbstract3DGraph.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))
+
+ @Slot(bool)
+ def setAxisTitleVisibility(self, enabled):
+ self._temperatureAxis.setTitleVisible(enabled)
+ self._monthAxis.setTitleVisible(enabled)
+ self._yearAxis.setTitleVisible(enabled)
+
+ @Slot(bool)
+ def setAxisTitleFixed(self, enabled):
+ self._temperatureAxis.setTitleFixed(enabled)
+ self._monthAxis.setTitleFixed(enabled)
+ self._yearAxis.setTitleFixed(enabled)
+
+ @Slot()
+ def zoomToSelectedBar(self):
+ self._animationCameraX.stop()
+ self._animationCameraY.stop()
+ self._animationCameraZoom.stop()
+ self._animationCameraTarget.stop()
+
+ currentX = self._graph.cameraXRotation()
+ currentY = self._graph.cameraYRotation()
+ currentZoom = self._graph.cameraZoomLevel()
+ currentTarget = self._graph.cameraTargetPosition()
+
+ self._animationCameraX.setStartValue(currentX)
+ self._animationCameraY.setStartValue(currentY)
+ self._animationCameraZoom.setStartValue(currentZoom)
+ self._animationCameraTarget.setStartValue(currentTarget)
+
+ selectedBar = (self._graph.selectedSeries().selectedBar()
+ if self._graph.selectedSeries()
+ else QBar3DSeries.invalidSelectionPosition())
+
+ if selectedBar != QBar3DSeries.invalidSelectionPosition():
+ # Normalize selected bar position within axis range to determine
+ # target coordinates
+ endTarget = QVector3D()
+ xMin = self._graph.columnAxis().min()
+ xRange = self._graph.columnAxis().max() - xMin
+ zMin = self._graph.rowAxis().min()
+ zRange = self._graph.rowAxis().max() - zMin
+ endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0 - 1.0)
+ endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0 - 1.0)
+
+ # Rotate the camera so that it always points approximately to the
+ # graph center
+ endAngleX = 90.0 - degrees(atan(float(endTarget.z() / endTarget.x())))
+ if endTarget.x() > 0.0:
+ endAngleX -= 180.0
+ proxy = self._graph.selectedSeries().dataProxy()
+ barValue = proxy.itemAt(selectedBar.x(), selectedBar.y()).value()
+ endAngleY = 30.0 if barValue >= 0.0 else -30.0
+ if self._graph.valueAxis().reversed():
+ endAngleY *= -1.0
+
+ self._animationCameraX.setEndValue(float(endAngleX))
+ self._animationCameraY.setEndValue(endAngleY)
+ self._animationCameraZoom.setEndValue(250)
+ self._animationCameraTarget.setEndValue(endTarget)
+ else:
+ # No selected bar, so return to the default view
+ self._animationCameraX.setEndValue(self._defaultAngleX)
+ self._animationCameraY.setEndValue(self._defaultAngleY)
+ self._animationCameraZoom.setEndValue(self._defaultZoom)
+ self._animationCameraTarget.setEndValue(self._defaultTarget)
+
+ self._animationCameraX.start()
+ self._animationCameraY.start()
+ self._animationCameraZoom.start()
+ self._animationCameraTarget.start()
+
+ @Slot(bool)
+ def setDataModeToWeather(self, enabled):
+ if enabled:
+ self.changeDataMode(False)
+
+ @Slot(bool)
+ def setDataModeToCustom(self, enabled):
+ if enabled:
+ self.changeDataMode(True)
+
+ def changeShadowQuality(self, quality):
+ sq = QAbstract3DGraph.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)
+
+ def rotateY(self, rotation):
+ self._yRotation = rotation
+ camera = self._graph.scene().activeCamera()
+ camera.setCameraPosition(self._xRotation, self._yRotation)
+
+ def setBackgroundEnabled(self, enabled):
+ self._graph.activeTheme().setBackgroundEnabled(bool(enabled))
+
+ def setGridEnabled(self, enabled):
+ self._graph.activeTheme().setGridEnabled(bool(enabled))
+
+ def setSmoothBars(self, smooth):
+ self._smooth = bool(smooth)
+ 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 setReverseValueAxis(self, enabled):
+ self._graph.valueAxis().setReversed(enabled)
+
+ def setReflection(self, enabled):
+ self._graph.setReflection(enabled)
+
+ def changeDataMode(self, customData):
+ # Change between weather data and data from custom proxy
+ if customData:
+ self._graph.removeSeries(self._primarySeries)
+ self._graph.removeSeries(self._secondarySeries)
+ self._graph.addSeries(self._customData.customSeries())
+ self._graph.setValueAxis(self._customData.valueAxis())
+ self._graph.setRowAxis(self._customData.rowAxis())
+ self._graph.setColumnAxis(self._customData.colAxis())
+ else:
+ self._graph.removeSeries(self._customData.customSeries())
+ self._graph.addSeries(self._primarySeries)
+ self._graph.addSeries(self._secondarySeries)
+ self._graph.setValueAxis(self._temperatureAxis)
+ self._graph.setRowAxis(self._yearAxis)
+ self._graph.setColumnAxis(self._monthAxis)
diff --git a/examples/graphs/3d/widgetgallery/highlightseries.py b/examples/graphs/3d/widgetgallery/highlightseries.py
new file mode 100644
index 000000000..8c7b91633
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/highlightseries.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QPoint, Qt, Slot
+from PySide6.QtGui import QLinearGradient, QVector3D
+from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem, Q3DTheme)
+
+
+DARK_RED_POS = 1.0
+RED_POS = 0.8
+YELLOW_POS = 0.6
+GREEN_POS = 0.4
+DARK_GREEN_POS = 0.2
+
+
+class HighlightSeries(QSurface3DSeries):
+
+ def __init__(self):
+ super().__init__()
+ self._width = 100
+ self._height = 100
+ self._srcWidth = 0
+ self._srcHeight = 0
+ self._position = {}
+ self._topographicSeries = None
+ self._minHeight = 0.0
+ self.setDrawMode(QSurface3DSeries.DrawSurface)
+ self.setFlatShadingEnabled(True)
+ self.setVisible(False)
+
+ def setTopographicSeries(self, series):
+ self._topographicSeries = series
+ array = self._topographicSeries.dataProxy().array()
+ self._srcWidth = len(array[0])
+ self._srcHeight = len(array)
+ self._topographicSeries.selectedPointChanged.connect(self.handlePositionChange)
+
+ def setMinHeight(self, height):
+ self. m_minHeight = height
+
+ @Slot(QPoint)
+ def handlePositionChange(self, position):
+ self._position = position
+
+ if position == self.invalidSelectionPosition():
+ self.setVisible(False)
+ return
+
+ halfWidth = self._width / 2
+ halfHeight = self._height / 2
+
+ startX = position.y() - halfWidth
+ if startX < 0:
+ startX = 0
+ endX = position.y() + halfWidth
+ if endX > (self._srcWidth - 1):
+ endX = self._srcWidth - 1
+ startZ = position.x() - halfHeight
+ if startZ < 0:
+ startZ = 0
+ endZ = position.x() + halfHeight
+ if endZ > (self._srcHeight - 1):
+ endZ = self._srcHeight - 1
+
+ srcProxy = self._topographicSeries.dataProxy()
+ srcArray = srcProxy.array()
+
+ dataArray = []
+ for i in range(int(startZ), int(endZ)):
+ newRow = []
+ srcRow = srcArray[i]
+ for j in range(startX, endX):
+ pos = srcRow.at(j).position()
+ pos.setY(pos.y() + 0.1)
+ item = QSurfaceDataItem(QVector3D(pos))
+ newRow.append(item)
+ dataArray.append(newRow)
+ self.dataProxy().resetArray(dataArray)
+ self.setVisible(True)
+
+ @Slot(float)
+ def handleGradientChange(self, value):
+ 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)
+
+ self.setBaseGradient(gr)
+ self.setColorStyle(Q3DTheme.ColorStyle.RangeGradient)
diff --git a/examples/graphs/3d/widgetgallery/main.py b/examples/graphs/3d/widgetgallery/main.py
new file mode 100644
index 000000000..7bb2238a7
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/main.py
@@ -0,0 +1,41 @@
+# 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/rainfalldata.py b/examples/graphs/3d/widgetgallery/rainfalldata.py
new file mode 100644
index 000000000..d74f45a8b
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/rainfalldata.py
@@ -0,0 +1,125 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from pathlib import Path
+
+from PySide6.QtCore import QFile, QIODevice, QObject
+from PySide6.QtGraphs import (QBar3DSeries, QCategory3DAxis, QValue3DAxis)
+
+from variantbardataproxy import VariantBarDataProxy
+from variantbardatamapping import VariantBarDataMapping
+from variantdataset import VariantDataSet
+
+
+MONTHS = ["January", "February", "March", "April",
+ "May", "June", "July", "August", "September", "October",
+ "November", "December"]
+
+
+class RainfallData(QObject):
+
+ def __init__(self):
+ super().__init__()
+ self._columnCount = 0
+ self._rowCount = 0
+ self._years = []
+ self._numericMonths = []
+ self._proxy = VariantBarDataProxy()
+ self._mapping = None
+ self._dataSet = None
+ self._series = QBar3DSeries()
+ self._valueAxis = QValue3DAxis()
+ self._rowAxis = QCategory3DAxis()
+ self._colAxis = QCategory3DAxis()
+
+ # In data file the months are in numeric format, so create custom list
+ for i in range(1, 13):
+ self._numericMonths.append(str(i))
+
+ self._columnCount = len(self._numericMonths)
+
+ self.updateYearsList(2010, 2022)
+
+ # Create proxy and series
+ self._proxy = VariantBarDataProxy()
+ self._series = QBar3DSeries(self._proxy)
+
+ self._series.setItemLabelFormat("%.1f mm")
+
+ # Create the axes
+ self._rowAxis = QCategory3DAxis(self)
+ self._colAxis = QCategory3DAxis(self)
+ self._valueAxis = QValue3DAxis(self)
+ self._rowAxis.setAutoAdjustRange(True)
+ self._colAxis.setAutoAdjustRange(True)
+ self._valueAxis.setAutoAdjustRange(True)
+
+ # Set axis labels and titles
+ self._rowAxis.setTitle("Year")
+ self._colAxis.setTitle("Month")
+ self._valueAxis.setTitle("rainfall (mm)")
+ self._valueAxis.setSegmentCount(5)
+ self._rowAxis.setLabels(self._years)
+ self._colAxis.setLabels(MONTHS)
+ self._rowAxis.setTitleVisible(True)
+ self._colAxis.setTitleVisible(True)
+ self._valueAxis.setTitleVisible(True)
+
+ self.addDataSet()
+
+ def customSeries(self):
+ return self._series
+
+ def valueAxis(self):
+ return self._valueAxis
+
+ def rowAxis(self):
+ return self._rowAxis
+
+ def colAxis(self):
+ return self._colAxis
+
+ def updateYearsList(self, start, end):
+ self._years.clear()
+ for i in range(start, end + 1):
+ self._years.append(str(i))
+ self._rowCount = len(self._years)
+
+ def addDataSet(self):
+ # Create a new variant data set and data item list
+ self._dataSet = VariantDataSet()
+ itemList = []
+
+ # 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):
+ data = dataFile.readAll().data().decode("utf8")
+ for line in data.split("\n"):
+ if line and not line.startswith("#"): # Ignore comments
+ tokens = line.split(",")
+ # Each line has three data items: Year, month, and
+ # rainfall value
+ if len(tokens) >= 3:
+ # Store year and month as strings, and rainfall value
+ # as double into a variant data item and add the item to
+ # the item list.
+ newItem = []
+ newItem.append(tokens[0].strip())
+ newItem.append(tokens[1].strip())
+ newItem.append(float(tokens[2].strip()))
+ itemList.append(newItem)
+ else:
+ print("Unable to open data file:", dataFile.fileName(),
+ file=sys.stderr)
+
+ # Add items to the data set and set it to the proxy
+ self._dataSet.addItems(itemList)
+ self._proxy.setDataSet(self._dataSet)
+
+ # Create new mapping for the data and set it to the proxy
+ self._mapping = VariantBarDataMapping(0, 1, 2,
+ self._years, self._numericMonths)
+ self._proxy.setMapping(self._mapping)
diff --git a/examples/graphs/3d/widgetgallery/scatterdatamodifier.py b/examples/graphs/3d/widgetgallery/scatterdatamodifier.py
new file mode 100644
index 000000000..15064b412
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/scatterdatamodifier.py
@@ -0,0 +1,149 @@
+# 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/scattergraph.py b/examples/graphs/3d/widgetgallery/scattergraph.py
new file mode 100644
index 000000000..79e8933eb
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/scattergraph.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+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 scatterdatamodifier import ScatterDataModifier
+
+
+class ScatterGraph(QObject):
+
+ def __init__(self, minimum_graph_size, maximum_graph_size):
+ super().__init__()
+ self._scatterGraph = Q3DScatter()
+ 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)
+
+ vLayout = QVBoxLayout()
+ hLayout.addLayout(vLayout)
+
+ cameraButton = QCommandLinkButton(self._scatterWidget)
+ cameraButton.setText("Change camera preset")
+ cameraButton.setDescription("Switch between a number of preset camera positions")
+ cameraButton.setIconSize(QSize(0, 0))
+
+ itemCountButton = QCommandLinkButton(self._scatterWidget)
+ itemCountButton.setText("Toggle item count")
+ itemCountButton.setDescription("Switch between 900 and 10000 data points")
+ itemCountButton.setIconSize(QSize(0, 0))
+
+ rangeButton = QCommandLinkButton(self._scatterWidget)
+ rangeButton.setText("Toggle axis ranges")
+ rangeButton.setDescription("Switch between automatic axis ranges and preset ranges")
+ rangeButton.setIconSize(QSize(0, 0))
+
+ backgroundCheckBox = QCheckBox(self._scatterWidget)
+ backgroundCheckBox.setText("Show background")
+ backgroundCheckBox.setChecked(True)
+
+ gridCheckBox = QCheckBox(self._scatterWidget)
+ gridCheckBox.setText("Show grid")
+ gridCheckBox.setChecked(True)
+
+ smoothCheckBox = QCheckBox(self._scatterWidget)
+ smoothCheckBox.setText("Smooth dots")
+ smoothCheckBox.setChecked(True)
+
+ itemStyleList = QComboBox(self._scatterWidget)
+ itemStyleList.addItem("Sphere", QAbstract3DSeries.Mesh.Sphere)
+ itemStyleList.addItem("Cube", QAbstract3DSeries.Mesh.Cube)
+ itemStyleList.addItem("Minimal", QAbstract3DSeries.Mesh.Minimal)
+ itemStyleList.addItem("Point", QAbstract3DSeries.Mesh.Point)
+ itemStyleList.setCurrentIndex(0)
+
+ themeList = QComboBox(self._scatterWidget)
+ 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.setCurrentIndex(3)
+
+ shadowQuality = QComboBox(self._scatterWidget)
+ shadowQuality.addItem("None")
+ shadowQuality.addItem("Low")
+ shadowQuality.addItem("Medium")
+ shadowQuality.addItem("High")
+ shadowQuality.addItem("Low Soft")
+ shadowQuality.addItem("Medium Soft")
+ shadowQuality.addItem("High Soft")
+ shadowQuality.setCurrentIndex(6)
+
+ vLayout.addWidget(cameraButton)
+ vLayout.addWidget(itemCountButton)
+ vLayout.addWidget(rangeButton)
+ vLayout.addWidget(backgroundCheckBox)
+ vLayout.addWidget(gridCheckBox)
+ vLayout.addWidget(smoothCheckBox)
+ vLayout.addWidget(QLabel("Change dot style"))
+ vLayout.addWidget(itemStyleList)
+ vLayout.addWidget(QLabel("Change theme"))
+ vLayout.addWidget(themeList)
+ vLayout.addWidget(QLabel("Adjust shadow quality"))
+ vLayout.addWidget(shadowQuality, 1, Qt.AlignTop)
+
+ self._modifier = ScatterDataModifier(self._scatterGraph, self)
+
+ cameraButton.clicked.connect(self._modifier.changePresetCamera)
+ itemCountButton.clicked.connect(self._modifier.toggleItemCount)
+ rangeButton.clicked.connect(self._modifier.toggleRanges)
+
+ backgroundCheckBox.stateChanged.connect(self._modifier.setBackgroundEnabled)
+ gridCheckBox.stateChanged.connect(self._modifier.setGridEnabled)
+ smoothCheckBox.stateChanged.connect(self._modifier.setSmoothDots)
+
+ self._modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked)
+ self._modifier.gridEnabledChanged.connect(gridCheckBox.setChecked)
+ itemStyleList.currentIndexChanged.connect(self._modifier.changeStyle)
+
+ themeList.currentIndexChanged.connect(self._modifier.changeTheme)
+
+ shadowQuality.currentIndexChanged.connect(self._modifier.changeShadowQuality)
+
+ self._modifier.shadowQualityChanged.connect(shadowQuality.setCurrentIndex)
+ self._scatterGraph.shadowQualityChanged.connect(self._modifier.shadowQualityUpdatedByVisual)
+
+ def scatterWidget(self):
+ return self._scatterWidget
diff --git a/examples/graphs/3d/widgetgallery/surfacegraph.py b/examples/graphs/3d/widgetgallery/surfacegraph.py
new file mode 100644
index 000000000..4052da821
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/surfacegraph.py
@@ -0,0 +1,256 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from surfacegraphmodifier import SurfaceGraphModifier
+
+from PySide6.QtCore import QObject, Qt
+from PySide6.QtGui import QBrush, QIcon, QLinearGradient, QPainter, QPixmap
+from PySide6.QtWidgets import (QGroupBox, QCheckBox, QLabel, QHBoxLayout,
+ QPushButton, QRadioButton, QSizePolicy, QSlider,
+ QVBoxLayout, QWidget)
+from PySide6.QtQuickWidgets import QQuickWidget
+from PySide6.QtGraphs import Q3DSurface
+
+
+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)
+ pm = QPixmap(24, 100)
+ with QPainter(pm) as pmp:
+ pmp.setBrush(QBrush(grBtoY))
+ pmp.setPen(Qt.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)
+ pm = QPixmap(24, 100)
+ with QPainter(pm) as pmp:
+ pmp.setBrush(QBrush(grGtoR))
+ pmp.setPen(Qt.NoPen)
+ pmp.drawRect(0, 0, 24, 100)
+ return pm
+
+
+def highlightPixmap():
+ HEIGHT = 400
+ 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)
+ pmHighlight = QPixmap(WIDTH, HEIGHT)
+ pmHighlight.fill(Qt.transparent)
+ with QPainter(pmHighlight) as pmpHighlight:
+ pmpHighlight.setBrush(QBrush(gr))
+ pmpHighlight.setPen(Qt.NoPen)
+ pmpHighlight.drawRect(BORDER, BORDER, 35, HEIGHT - 2 * BORDER)
+ pmpHighlight.setPen(Qt.black)
+ step = (HEIGHT - 2 * BORDER) / 5
+ for i in range(0, 6):
+ yPos = i * step + BORDER
+ pmpHighlight.drawLine(BORDER, yPos, 55, yPos)
+ HEIGHT = 550 - (i * 110)
+ pmpHighlight.drawText(60, yPos + 2, f"{HEIGHT} m")
+ return pmHighlight
+
+
+class SurfaceGraph(QObject):
+
+ def __init__(self, minimum_graph_size, maximum_graph_size):
+ super().__init__()
+ self._surfaceGraph = Q3DSurface()
+ 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)
+ vLayout = QVBoxLayout()
+ hLayout.addLayout(vLayout)
+ vLayout.setAlignment(Qt.AlignTop)
+ # Create control widgets
+ modelGroupBox = QGroupBox("Model")
+ sqrtSinModelRB = QRadioButton(self._surfaceWidget)
+ sqrtSinModelRB.setText("Sqrt and Sin")
+ sqrtSinModelRB.setChecked(False)
+ heightMapModelRB = QRadioButton(self._surfaceWidget)
+ heightMapModelRB.setText("Multiseries\nHeight Map")
+ heightMapModelRB.setChecked(False)
+ texturedModelRB = QRadioButton(self._surfaceWidget)
+ texturedModelRB.setText("Textured\nTopography")
+ texturedModelRB.setChecked(False)
+ modelVBox = QVBoxLayout()
+ modelVBox.addWidget(sqrtSinModelRB)
+ modelVBox.addWidget(heightMapModelRB)
+ modelVBox.addWidget(texturedModelRB)
+ modelGroupBox.setLayout(modelVBox)
+ selectionGroupBox = QGroupBox("Graph Selection Mode")
+ modeNoneRB = QRadioButton(self._surfaceWidget)
+ modeNoneRB.setText("No selection")
+ modeNoneRB.setChecked(False)
+ modeItemRB = QRadioButton(self._surfaceWidget)
+ modeItemRB.setText("Item")
+ modeItemRB.setChecked(False)
+ modeSliceRowRB = QRadioButton(self._surfaceWidget)
+ modeSliceRowRB.setText("Row Slice")
+ modeSliceRowRB.setChecked(False)
+ modeSliceColumnRB = QRadioButton(self._surfaceWidget)
+ modeSliceColumnRB.setText("Column Slice")
+ modeSliceColumnRB.setChecked(False)
+ selectionVBox = QVBoxLayout()
+ selectionVBox.addWidget(modeNoneRB)
+ selectionVBox.addWidget(modeItemRB)
+ selectionVBox.addWidget(modeSliceRowRB)
+ selectionVBox.addWidget(modeSliceColumnRB)
+ selectionGroupBox.setLayout(selectionVBox)
+ axisGroupBox = QGroupBox("Axis ranges")
+ axisMinSliderX = QSlider(Qt.Horizontal)
+ axisMinSliderX.setMinimum(0)
+ axisMinSliderX.setTickInterval(1)
+ axisMinSliderX.setEnabled(True)
+ axisMaxSliderX = QSlider(Qt.Horizontal)
+ axisMaxSliderX.setMinimum(1)
+ axisMaxSliderX.setTickInterval(1)
+ axisMaxSliderX.setEnabled(True)
+ axisMinSliderZ = QSlider(Qt.Horizontal)
+ axisMinSliderZ.setMinimum(0)
+ axisMinSliderZ.setTickInterval(1)
+ axisMinSliderZ.setEnabled(True)
+ axisMaxSliderZ = QSlider(Qt.Horizontal)
+ axisMaxSliderZ.setMinimum(1)
+ axisMaxSliderZ.setTickInterval(1)
+ axisMaxSliderZ.setEnabled(True)
+ axisVBox = QVBoxLayout(axisGroupBox)
+ axisVBox.addWidget(QLabel("Column range"))
+ axisVBox.addWidget(axisMinSliderX)
+ axisVBox.addWidget(axisMaxSliderX)
+ axisVBox.addWidget(QLabel("Row range"))
+ axisVBox.addWidget(axisMinSliderZ)
+ axisVBox.addWidget(axisMaxSliderZ)
+ # Mode-dependent controls
+ # sqrt-sin
+ colorGroupBox = QGroupBox("Custom gradient")
+
+ pixmap = gradientBtoYPB_Pixmap()
+ gradientBtoYPB = QPushButton(self._surfaceWidget)
+ gradientBtoYPB.setIcon(QIcon(pixmap))
+ gradientBtoYPB.setIconSize(pixmap.size())
+
+ pixmap = gradientGtoRPB_Pixmap()
+ gradientGtoRPB = QPushButton(self._surfaceWidget)
+ gradientGtoRPB.setIcon(QIcon(pixmap))
+ gradientGtoRPB.setIconSize(pixmap.size())
+
+ colorHBox = QHBoxLayout(colorGroupBox)
+ colorHBox.addWidget(gradientBtoYPB)
+ colorHBox.addWidget(gradientGtoRPB)
+ # Multiseries heightmap
+ showGroupBox = QGroupBox("Show Object")
+ showGroupBox.setVisible(False)
+ checkboxShowOilRigOne = QCheckBox("Oil Rig 1")
+ checkboxShowOilRigOne.setChecked(True)
+ checkboxShowOilRigTwo = QCheckBox("Oil Rig 2")
+ checkboxShowOilRigTwo.setChecked(True)
+ checkboxShowRefinery = QCheckBox("Refinery")
+ showVBox = QVBoxLayout()
+ showVBox.addWidget(checkboxShowOilRigOne)
+ showVBox.addWidget(checkboxShowOilRigTwo)
+ showVBox.addWidget(checkboxShowRefinery)
+ showGroupBox.setLayout(showVBox)
+ visualsGroupBox = QGroupBox("Visuals")
+ visualsGroupBox.setVisible(False)
+ checkboxVisualsSeeThrough = QCheckBox("See-Through")
+ checkboxHighlightOil = QCheckBox("Highlight Oil")
+ checkboxShowShadows = QCheckBox("Shadows")
+ checkboxShowShadows.setChecked(True)
+ visualVBox = QVBoxLayout(visualsGroupBox)
+ visualVBox.addWidget(checkboxVisualsSeeThrough)
+ visualVBox.addWidget(checkboxHighlightOil)
+ visualVBox.addWidget(checkboxShowShadows)
+ labelSelection = QLabel("Selection:")
+ labelSelection.setVisible(False)
+ labelSelectedItem = QLabel("Nothing")
+ labelSelectedItem.setVisible(False)
+ # Textured topography heightmap
+ enableTexture = QCheckBox("Surface texture")
+ enableTexture.setVisible(False)
+
+ label = QLabel(self._surfaceWidget)
+ label.setPixmap(highlightPixmap())
+ heightMapGroupBox = QGroupBox("Highlight color map")
+ colorMapVBox = QVBoxLayout()
+ colorMapVBox.addWidget(label)
+ heightMapGroupBox.setLayout(colorMapVBox)
+ heightMapGroupBox.setVisible(False)
+ # Populate vertical layout
+ # Common
+ vLayout.addWidget(modelGroupBox)
+ vLayout.addWidget(selectionGroupBox)
+ vLayout.addWidget(axisGroupBox)
+ # Sqrt Sin
+ vLayout.addWidget(colorGroupBox)
+ # Multiseries heightmap
+ vLayout.addWidget(showGroupBox)
+ vLayout.addWidget(visualsGroupBox)
+ vLayout.addWidget(labelSelection)
+ vLayout.addWidget(labelSelectedItem)
+ # Textured topography
+ vLayout.addWidget(heightMapGroupBox)
+ vLayout.addWidget(enableTexture)
+ # Create the controller
+ modifier = SurfaceGraphModifier(self._surfaceGraph, labelSelectedItem, self)
+ # Connect widget controls to controller
+ heightMapModelRB.toggled.connect(modifier.enableHeightMapModel)
+ sqrtSinModelRB.toggled.connect(modifier.enableSqrtSinModel)
+ texturedModelRB.toggled.connect(modifier.enableTopographyModel)
+ modeNoneRB.toggled.connect(modifier.toggleModeNone)
+ modeItemRB.toggled.connect(modifier.toggleModeItem)
+ modeSliceRowRB.toggled.connect(modifier.toggleModeSliceRow)
+ modeSliceColumnRB.toggled.connect(modifier.toggleModeSliceColumn)
+ axisMinSliderX.valueChanged.connect(modifier.adjustXMin)
+ axisMaxSliderX.valueChanged.connect(modifier.adjustXMax)
+ axisMinSliderZ.valueChanged.connect(modifier.adjustZMin)
+ axisMaxSliderZ.valueChanged.connect(modifier.adjustZMax)
+ # 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)
+ # Connections to disable features depending on mode
+ sqrtSinModelRB.toggled.connect(colorGroupBox.setVisible)
+ heightMapModelRB.toggled.connect(showGroupBox.setVisible)
+ heightMapModelRB.toggled.connect(visualsGroupBox.setVisible)
+ heightMapModelRB.toggled.connect(labelSelection.setVisible)
+ heightMapModelRB.toggled.connect(labelSelectedItem.setVisible)
+ texturedModelRB.toggled.connect(enableTexture.setVisible)
+ texturedModelRB.toggled.connect(heightMapGroupBox.setVisible)
+ modifier.setAxisMinSliderX(axisMinSliderX)
+ modifier.setAxisMaxSliderX(axisMaxSliderX)
+ modifier.setAxisMinSliderZ(axisMinSliderZ)
+ modifier.setAxisMaxSliderZ(axisMaxSliderZ)
+ sqrtSinModelRB.setChecked(True)
+ modeItemRB.setChecked(True)
+ enableTexture.setChecked(True)
+
+ def surfaceWidget(self):
+ return self._surfaceWidget
diff --git a/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py b/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py
new file mode 100644
index 000000000..b2706c6fa
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/surfacegraphmodifier.py
@@ -0,0 +1,641 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import os
+from math import sqrt, sin
+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,
+ QCustom3DLabel, QHeightMapSurfaceDataProxy,
+ QValue3DAxis, QSurfaceDataItem,
+ QSurfaceDataProxy, QSurface3DSeries,
+ Q3DInputHandler, Q3DTheme)
+
+
+from highlightseries import HighlightSeries
+from topographicseries import TopographicSeries
+from custominputhandler import CustomInputHandler
+
+
+SAMPLE_COUNT_X = 150
+SAMPLE_COUNT_Z = 150
+HEIGHTMAP_GRID_STEP_X = 6
+HEIGHTMAP_GRID_STEP_Z = 6
+SAMPLE_MIN = -8.0
+SAMPLE_MAX = 8.0
+
+AREA_WIDTH = 8000.0
+AREA_HEIGHT = 8000.0
+ASPECT_RATIO = 0.1389
+MIN_RANGE = AREA_WIDTH * 0.49
+
+
+class SurfaceGraphModifier(QObject):
+
+ def __init__(self, surface, label, parent):
+ super().__init__(parent)
+ self._data_path = Path(__file__).resolve().parent / "data"
+ self._graph = surface
+ self._textField = label
+ self._sqrtSinProxy = None
+ self._sqrtSinSeries = None
+ self._heightMapProxyOne = None
+ self._heightMapProxyTwo = None
+ self._heightMapProxyThree = None
+ self._heightMapSeriesOne = None
+ self._heightMapSeriesTwo = None
+ self._heightMapSeriesThree = None
+
+ self._axisMinSliderX = None
+ self._axisMaxSliderX = None
+ self._axisMinSliderZ = None
+ self._axisMaxSliderZ = None
+ self._rangeMinX = 0.0
+ self._rangeMinZ = 0.0
+ self._stepX = 0.0
+ self._stepZ = 0.0
+ self._heightMapWidth = 0
+ self._heightMapHeight = 0
+
+ self._selectionAnimation = None
+ self._titleLabel = None
+ self._previouslyAnimatedItem = None
+ self._previousScaling = {}
+
+ self._topography = None
+ self._highlight = None
+ 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._x_axis = QValue3DAxis()
+ self._y_axis = QValue3DAxis()
+ self._z_axis = QValue3DAxis()
+ self._graph.setAxisX(self._x_axis)
+ self._graph.setAxisY(self._y_axis)
+ self._graph.setAxisZ(self._z_axis)
+
+ #
+ # Sqrt Sin
+ #
+ self._sqrtSinProxy = QSurfaceDataProxy()
+ self._sqrtSinSeries = QSurface3DSeries(self._sqrtSinProxy)
+ self.fillSqrtSinProxy()
+
+ #
+ # Multisurface heightmap
+ #
+ # Create the first surface layer
+ heightMapImageOne = QImage(self._data_path / "layer_1.png")
+ self._heightMapProxyOne = QHeightMapSurfaceDataProxy(heightMapImageOne)
+ self._heightMapSeriesOne = QSurface3DSeries(self._heightMapProxyOne)
+ self._heightMapSeriesOne.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
+ self._heightMapProxyOne.setValueRanges(34.0, 40.0, 18.0, 24.0)
+
+ # Create the other 2 surface layers
+ heightMapImageTwo = QImage(self._data_path / "layer_2.png")
+ self._heightMapProxyTwo = QHeightMapSurfaceDataProxy(heightMapImageTwo)
+ self._heightMapSeriesTwo = QSurface3DSeries(self._heightMapProxyTwo)
+ self._heightMapSeriesTwo.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
+ self._heightMapProxyTwo.setValueRanges(34.0, 40.0, 18.0, 24.0)
+
+ heightMapImageThree = QImage(self._data_path / "layer_3.png")
+ self._heightMapProxyThree = QHeightMapSurfaceDataProxy(heightMapImageThree)
+ self._heightMapSeriesThree = QSurface3DSeries(self._heightMapProxyThree)
+ self._heightMapSeriesThree.setItemLabelFormat("(@xLabel, @zLabel): @yLabel")
+ self._heightMapProxyThree.setValueRanges(34.0, 40.0, 18.0, 24.0)
+
+ # The images are the same size, so it's enough to get the dimensions
+ # from one
+ self._heightMapWidth = heightMapImageOne.width()
+ self._heightMapHeight = heightMapImageOne.height()
+
+ # 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)
+ self._heightMapSeriesOne.setBaseGradient(grOne)
+ self._heightMapSeriesOne.setColorStyle(Q3DTheme.ColorStyle.RangeGradient)
+
+ grTwo = QLinearGradient()
+ grTwo.setColorAt(0.39, Qt.blue)
+ grTwo.setColorAt(0.4, Qt.white)
+ self._heightMapSeriesTwo.setBaseGradient(grTwo)
+ self._heightMapSeriesTwo.setColorStyle(Q3DTheme.ColorStyle.RangeGradient)
+
+ grThree = QLinearGradient()
+ grThree.setColorAt(0.0, Qt.white)
+ grThree.setColorAt(0.05, Qt.black)
+ self._heightMapSeriesThree.setBaseGradient(grThree)
+ self._heightMapSeriesThree.setColorStyle(Q3DTheme.ColorStyle.RangeGradient)
+
+ # Custom items and label
+ self._graph.selectedElementChanged.connect(self.handleElementSelected)
+
+ self._selectionAnimation = QPropertyAnimation(self)
+ self._selectionAnimation.setPropertyName(b"scaling")
+ self._selectionAnimation.setDuration(500)
+ self._selectionAnimation.setLoopCount(-1)
+
+ titleFont = QFont("Century Gothic", 30)
+ titleFont.setBold(True)
+ self._titleLabel = QCustom3DLabel("Oil Rigs on Imaginary Sea", titleFont,
+ QVector3D(0.0, 1.2, 0.0),
+ QVector3D(1.0, 1.0, 0.0),
+ QQuaternion())
+ self._titleLabel.setPositionAbsolute(True)
+ self._titleLabel.setFacingCamera(True)
+ self._titleLabel.setBackgroundColor(QColor(0x66cdaa))
+ self._graph.addCustomItem(self._titleLabel)
+ self._titleLabel.setVisible(False)
+
+ # Make two of the custom object visible
+ self.toggleItemOne(True)
+ self.toggleItemTwo(True)
+
+ #
+ # Topographic map
+ #
+ self._topography = TopographicSeries()
+ file_name = os.fspath(self._data_path / "topography.png")
+ self._topography.setTopographyFile(file_name, AREA_WIDTH, AREA_HEIGHT)
+ self._topography.setItemLabelFormat("@yLabel m")
+
+ self._highlight = HighlightSeries()
+ self._highlight.setTopographicSeries(self._topography)
+ self._highlight.setMinHeight(MIN_RANGE * ASPECT_RATIO)
+ 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)
+
+ def fillSqrtSinProxy(self):
+ stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1)
+ stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1)
+
+ dataArray = []
+ for i in range(0, SAMPLE_COUNT_Z):
+ newRow = []
+ # Keep values within range bounds, since just adding step can
+ # cause minor drift due to the rounding errors.
+ z = min(SAMPLE_MAX, (i * stepZ + SAMPLE_MIN))
+ for j in range(0, SAMPLE_COUNT_X):
+ x = min(SAMPLE_MAX, (j * stepX + SAMPLE_MIN))
+ R = sqrt(z * z + x * x) + 0.01
+ y = (sin(R) / R + 0.24) * 1.61
+ item = QSurfaceDataItem(QVector3D(x, y, z))
+ newRow.append(item)
+ dataArray.append(newRow)
+ self._sqrtSinProxy.resetArray(dataArray)
+
+ @Slot(bool)
+ def enableSqrtSinModel(self, enable):
+ if enable:
+ self._sqrtSinSeries.setDrawMode(QSurface3DSeries.DrawSurfaceAndWireframe)
+ self._sqrtSinSeries.setFlatShadingEnabled(True)
+
+ 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.removeSeries(self._heightMapSeriesOne)
+ self._graph.removeSeries(self._heightMapSeriesTwo)
+ self._graph.removeSeries(self._heightMapSeriesThree)
+ self._graph.removeSeries(self._topography)
+ self._graph.removeSeries(self._highlight)
+
+ self._graph.addSeries(self._sqrtSinSeries)
+
+ self._titleLabel.setVisible(False)
+ self._graph.axisX().setTitleVisible(False)
+ self._graph.axisY().setTitleVisible(False)
+ self._graph.axisZ().setTitleVisible(False)
+
+ self._graph.axisX().setTitle("")
+ 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
+ self._stepX = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_X - 1)
+ self._stepZ = (SAMPLE_MAX - SAMPLE_MIN) / float(SAMPLE_COUNT_Z - 1)
+ self._axisMinSliderX.setMinimum(0)
+ self._axisMinSliderX.setMaximum(SAMPLE_COUNT_X - 2)
+ self._axisMinSliderX.setValue(0)
+ self._axisMaxSliderX.setMinimum(1)
+ self._axisMaxSliderX.setMaximum(SAMPLE_COUNT_X - 1)
+ self._axisMaxSliderX.setValue(SAMPLE_COUNT_X - 1)
+ self._axisMinSliderZ.setMinimum(0)
+ self._axisMinSliderZ.setMaximum(SAMPLE_COUNT_Z - 2)
+ self._axisMinSliderZ.setValue(0)
+ self._axisMaxSliderZ.setMinimum(1)
+ self._axisMaxSliderZ.setMaximum(SAMPLE_COUNT_Z - 1)
+ self._axisMaxSliderZ.setValue(SAMPLE_COUNT_Z - 1)
+
+ @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._graph.axisX().setLabelFormat("%.1f N")
+ self._graph.axisZ().setLabelFormat("%.1f E")
+ self._graph.axisX().setRange(34.0, 40.0)
+ self._graph.axisY().setAutoAdjustRange(True)
+ self._graph.axisZ().setRange(18.0, 24.0)
+
+ self._graph.axisX().setTitle("Latitude")
+ self._graph.axisY().setTitle("Height")
+ self._graph.axisZ().setTitle("Longitude")
+
+ self._graph.removeSeries(self._sqrtSinSeries)
+ self._graph.removeSeries(self._topography)
+ self._graph.removeSeries(self._highlight)
+ self._graph.addSeries(self._heightMapSeriesOne)
+ 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)
+ self._graph.axisZ().setTitleVisible(True)
+
+ # Reset range sliders for height map
+ mapGridCountX = self._heightMapWidth / HEIGHTMAP_GRID_STEP_X
+ mapGridCountZ = self._heightMapHeight / HEIGHTMAP_GRID_STEP_Z
+ self._rangeMinX = 34.0
+ self._rangeMinZ = 18.0
+ self._stepX = 6.0 / float(mapGridCountX - 1)
+ self._stepZ = 6.0 / float(mapGridCountZ - 1)
+ self._axisMinSliderX.setMinimum(0)
+ self._axisMinSliderX.setMaximum(mapGridCountX - 2)
+ self._axisMinSliderX.setValue(0)
+ self._axisMaxSliderX.setMinimum(1)
+ self._axisMaxSliderX.setMaximum(mapGridCountX - 1)
+ self._axisMaxSliderX.setValue(mapGridCountX - 1)
+ self._axisMinSliderZ.setMinimum(0)
+ self._axisMinSliderZ.setMaximum(mapGridCountZ - 2)
+ self._axisMinSliderZ.setValue(0)
+ self._axisMaxSliderZ.setMinimum(1)
+ self._axisMaxSliderZ.setMaximum(mapGridCountZ - 1)
+ self._axisMaxSliderZ.setValue(mapGridCountZ - 1)
+
+ @Slot(bool)
+ def enableTopographyModel(self, enable):
+ if enable:
+ self._graph.axisX().setLabelFormat("%i")
+ self._graph.axisZ().setLabelFormat("%i")
+ 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.removeSeries(self._heightMapSeriesOne)
+ self._graph.removeSeries(self._heightMapSeriesTwo)
+ self._graph.removeSeries(self._heightMapSeriesThree)
+ self._graph.addSeries(self._topography)
+ self._graph.addSeries(self._highlight)
+
+ self._titleLabel.setVisible(False)
+ self._graph.axisX().setTitleVisible(False)
+ self._graph.axisY().setTitleVisible(False)
+ self._graph.axisZ().setTitleVisible(False)
+
+ self._graph.axisX().setTitle("")
+ 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
+ self._stepX = 1.0
+ self._stepZ = 1.0
+ self._axisMinSliderX.setMinimum(0)
+ self._axisMinSliderX.setMaximum(AREA_WIDTH - 200)
+ self._axisMinSliderX.setValue(0)
+ self._axisMaxSliderX.setMinimum(200)
+ self._axisMaxSliderX.setMaximum(AREA_WIDTH)
+ self._axisMaxSliderX.setValue(AREA_WIDTH)
+ self._axisMinSliderZ.setMinimum(0)
+ self._axisMinSliderZ.setMaximum(AREA_HEIGHT - 200)
+ self._axisMinSliderZ.setValue(0)
+ self._axisMaxSliderZ.setMinimum(200)
+ self._axisMaxSliderZ.setMaximum(AREA_HEIGHT)
+ self._axisMaxSliderZ.setValue(AREA_HEIGHT)
+
+ def adjustXMin(self, min):
+ minX = self._stepX * float(min) + self._rangeMinX
+
+ max = self._axisMaxSliderX.value()
+ if min >= max:
+ max = min + 1
+ self._axisMaxSliderX.setValue(max)
+
+ maxX = self._stepX * max + self._rangeMinX
+
+ self.setAxisXRange(minX, maxX)
+
+ def adjustXMax(self, max):
+ maxX = self._stepX * float(max) + self._rangeMinX
+
+ min = self._axisMinSliderX.value()
+ if max <= min:
+ min = max - 1
+ self._axisMinSliderX.setValue(min)
+
+ minX = self._stepX * min + self._rangeMinX
+
+ self.setAxisXRange(minX, maxX)
+
+ def adjustZMin(self, min):
+ minZ = self._stepZ * float(min) + self._rangeMinZ
+
+ max = self._axisMaxSliderZ.value()
+ if min >= max:
+ max = min + 1
+ self._axisMaxSliderZ.setValue(max)
+
+ maxZ = self._stepZ * max + self._rangeMinZ
+
+ self.setAxisZRange(minZ, maxZ)
+
+ def adjustZMax(self, max):
+ maxX = self._stepZ * float(max) + self._rangeMinZ
+
+ min = self._axisMinSliderZ.value()
+ if max <= min:
+ min = max - 1
+ self._axisMinSliderZ.setValue(min)
+
+ minX = self._stepZ * min + self._rangeMinZ
+
+ self.setAxisZRange(minX, maxX)
+
+ def setAxisXRange(self, min, max):
+ self._graph.axisX().setRange(min, max)
+
+ def setAxisZRange(self, min, max):
+ self._graph.axisZ().setRange(min, max)
+
+ def setBlackToYellowGradient(self):
+ gr = QLinearGradient()
+ gr.setColorAt(0.0, Qt.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)
+
+ def setGreenToRedGradient(self):
+ gr = QLinearGradient()
+ gr.setColorAt(0.0, Qt.darkGreen)
+ gr.setColorAt(0.5, Qt.yellow)
+ gr.setColorAt(0.8, Qt.red)
+ gr.setColorAt(1.0, Qt.darkRed)
+
+ self._sqrtSinSeries.setBaseGradient(gr)
+ self._sqrtSinSeries.setColorStyle(Q3DTheme.ColorStyle.RangeGradient)
+
+ @Slot(bool)
+ def toggleItemOne(self, show):
+ positionOne = QVector3D(39.0, 77.0, 19.2)
+ 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)
+ file_name = os.fspath(self._data_path / "oilrig.mesh")
+ item = QCustom3DItem(file_name, positionOne,
+ QVector3D(0.025, 0.025, 0.025),
+ QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 45.0),
+ color)
+ self._graph.addCustomItem(item)
+ file_name = os.fspath(self._data_path / "pipe.mesh")
+ item = QCustom3DItem(file_name, positionOnePipe,
+ QVector3D(0.005, 0.5, 0.005), QQuaternion(),
+ color)
+ item.setShadowCasting(False)
+ self._graph.addCustomItem(item)
+
+ label = QCustom3DLabel()
+ label.setText("Oil Rig One")
+ label.setPosition(positionOneLabel)
+ label.setScaling(QVector3D(1.0, 1.0, 1.0))
+ self._graph.addCustomItem(label)
+ else:
+ self.resetSelection()
+ self._graph.removeCustomItemAt(positionOne)
+ self._graph.removeCustomItemAt(positionOnePipe)
+ self._graph.removeCustomItemAt(positionOneLabel)
+
+ @Slot(bool)
+ def toggleItemTwo(self, show):
+ positionTwo = QVector3D(34.5, 77.0, 23.4)
+ 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)
+ item = QCustom3DItem()
+ file_name = os.fspath(self._data_path / "oilrig.mesh")
+ item.setMeshFile(file_name)
+ item.setPosition(positionTwo)
+ item.setScaling(QVector3D(0.025, 0.025, 0.025))
+ item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 25.0))
+ item.setTextureImage(color)
+ self._graph.addCustomItem(item)
+ file_name = os.fspath(self._data_path / "pipe.mesh")
+ item = QCustom3DItem(file_name, positionTwoPipe,
+ QVector3D(0.005, 0.5, 0.005), QQuaternion(),
+ color)
+ item.setShadowCasting(False)
+ self._graph.addCustomItem(item)
+
+ label = QCustom3DLabel()
+ label.setText("Oil Rig Two")
+ label.setPosition(positionTwoLabel)
+ label.setScaling(QVector3D(1.0, 1.0, 1.0))
+ self._graph.addCustomItem(label)
+ else:
+ self.resetSelection()
+ self._graph.removeCustomItemAt(positionTwo)
+ self._graph.removeCustomItemAt(positionTwoPipe)
+ self._graph.removeCustomItemAt(positionTwoLabel)
+
+ @Slot(bool)
+ def toggleItemThree(self, show):
+ 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.fill(Qt.darkMagenta)
+ item = QCustom3DItem()
+ file_name = os.fspath(self._data_path / "refinery.mesh")
+ item.setMeshFile(file_name)
+ item.setPosition(positionThree)
+ item.setScaling(QVector3D(0.04, 0.04, 0.04))
+ item.setRotation(QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, 75.0))
+ item.setTextureImage(color)
+ self._graph.addCustomItem(item)
+
+ label = QCustom3DLabel()
+ label.setText("Refinery")
+ label.setPosition(positionThreeLabel)
+ label.setScaling(QVector3D(1.0, 1.0, 1.0))
+ self._graph.addCustomItem(label)
+ else:
+ self.resetSelection()
+ self._graph.removeCustomItemAt(positionThree)
+ self._graph.removeCustomItemAt(positionThreeLabel)
+
+ @Slot(bool)
+ def toggleSeeThrough(self, seethrough):
+ s0 = self._graph.seriesList()[0]
+ s1 = self._graph.seriesList()[1]
+ if seethrough:
+ s0.setDrawMode(QSurface3DSeries.DrawWireframe)
+ s1.setDrawMode(QSurface3DSeries.DrawWireframe)
+ else:
+ s0.setDrawMode(QSurface3DSeries.DrawSurface)
+ s1.setDrawMode(QSurface3DSeries.DrawSurface)
+
+ @Slot(bool)
+ def toggleOilHighlight(self, highlight):
+ s2 = self._graph.seriesList()[2]
+ if highlight:
+ grThree = QLinearGradient()
+ grThree.setColorAt(0.0, Qt.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)
+ s2.setBaseGradient(grThree)
+
+ @Slot(bool)
+ def toggleShadows(self, shadows):
+ sq = (QAbstract3DGraph.ShadowQualityMedium
+ if shadows else QAbstract3DGraph.ShadowQualityNone)
+ self._graph.setShadowQuality(sq)
+
+ @Slot(bool)
+ def toggleSurfaceTexture(self, enable):
+ if enable:
+ file_name = os.fspath(self._data_path / "maptexture.jpg")
+ self._topography.setTextureFile(file_name)
+ else:
+ self._topography.setTextureFile("")
+
+ def handleElementSelected(self, type):
+ self.resetSelection()
+ if type == QAbstract3DGraph.ElementCustomItem:
+ item = self._graph.selectedCustomItem()
+ text = ""
+ if isinstance(item, QCustom3DItem):
+ text += "Custom label: "
+ else:
+ file = item.meshFile().split("/")[-1]
+ text += f"{file}: "
+
+ text += str(self._graph.selectedCustomItemIndex())
+ self._textField.setText(text)
+ self._previouslyAnimatedItem = item
+ self._previousScaling = item.scaling()
+ self._selectionAnimation.setTargetObject(item)
+ self._selectionAnimation.setStartValue(item.scaling())
+ self._selectionAnimation.setEndValue(item.scaling() * 1.5)
+ self._selectionAnimation.start()
+ elif type == QAbstract3DGraph.ElementSeries:
+ text = "Surface ("
+ series = self._graph.selectedSeries()
+ if series:
+ point = series.selectedPoint()
+ text += f"{point.x()}, {point.y()}"
+ text += ")"
+ self._textField.setText(text)
+ elif (type.value > QAbstract3DGraph.ElementSeries.value
+ and type < QAbstract3DGraph.ElementCustomItem.value):
+ index = self._graph.selectedLabelIndex()
+ text = ""
+ if type == QAbstract3DGraph.ElementAxisXLabel:
+ text += "Axis X label: "
+ elif type == QAbstract3DGraph.ElementAxisYLabel:
+ text += "Axis Y label: "
+ else:
+ text += "Axis Z label: "
+ text += str(index)
+ self._textField.setText(text)
+ else:
+ self._textField.setText("Nothing")
+
+ def resetSelection(self):
+ self._selectionAnimation.stop()
+ if self._previouslyAnimatedItem:
+ self._previouslyAnimatedItem.setScaling(self._previousScaling)
+ self._previouslyAnimatedItem = None
+
+ def toggleModeNone(self):
+ self._graph.setSelectionMode(QAbstract3DGraph.SelectionNone)
+
+ def toggleModeItem(self):
+ self._graph.setSelectionMode(QAbstract3DGraph.SelectionItem)
+
+ def toggleModeSliceRow(self):
+ sm = (QAbstract3DGraph.SelectionItemAndRow
+ | QAbstract3DGraph.SelectionSlice
+ | QAbstract3DGraph.SelectionMultiSeries)
+ self._graph.setSelectionMode(sm)
+
+ def toggleModeSliceColumn(self):
+ sm = (QAbstract3DGraph.SelectionItemAndColumn
+ | QAbstract3DGraph.SelectionSlice
+ | QAbstract3DGraph.SelectionMultiSeries)
+ self._graph.setSelectionMode(sm)
+
+ def setAxisMinSliderX(self, slider):
+ self._axisMinSliderX = slider
+
+ def setAxisMaxSliderX(self, slider):
+ self._axisMaxSliderX = slider
+
+ def setAxisMinSliderZ(self, slider):
+ self._axisMinSliderZ = slider
+
+ def setAxisMaxSliderZ(self, slider):
+ self._axisMaxSliderZ = slider
diff --git a/examples/graphs/3d/widgetgallery/topographicseries.py b/examples/graphs/3d/widgetgallery/topographicseries.py
new file mode 100644
index 000000000..4f286a222
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/topographicseries.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QImage, QVector3D
+from PySide6.QtGraphs import (QSurface3DSeries, QSurfaceDataItem)
+
+
+# Value used to encode height data as RGB value on PNG file
+PACKING_FACTOR = 11983.0
+
+
+class TopographicSeries(QSurface3DSeries):
+
+ def __init__(self):
+ super().__init__()
+ self._sampleCountX = 0.0
+ self._sampleCountZ = 0.0
+ self.setDrawMode(QSurface3DSeries.DrawSurface)
+ self.setFlatShadingEnabled(True)
+ self.setBaseColor(Qt.white)
+
+ def sampleCountX(self):
+ return self._sampleCountX
+
+ def sampleCountZ(self):
+ return self._sampleCountZ
+
+ def setTopographyFile(self, file, width, height):
+ heightMapImage = QImage(file)
+ bits = heightMapImage.bits()
+ imageHeight = heightMapImage.height()
+ imageWidth = heightMapImage.width()
+ widthBits = imageWidth * 4
+ stepX = width / float(imageWidth)
+ stepZ = height / float(imageHeight)
+
+ dataArray = []
+ for i in range(0, imageHeight):
+ p = i * widthBits
+ z = height - float(i) * stepZ
+ newRow = []
+ for j in range(0, imageWidth):
+ aa = bits[p + 0]
+ rr = bits[p + 1]
+ gg = bits[p + 2]
+ color = (gg << 16) + (rr << 8) + aa
+ y = float(color) / PACKING_FACTOR
+ item = QSurfaceDataItem(QVector3D(float(j) * stepX, y, z))
+ newRow.append(item)
+ p += 4
+ dataArray.append(newRow)
+
+ self.dataProxy().resetArray(dataArray)
+
+ self._sampleCountX = float(imageWidth)
+ self._sampleCountZ = float(imageHeight)
diff --git a/examples/graphs/3d/widgetgallery/variantbardatamapping.py b/examples/graphs/3d/widgetgallery/variantbardatamapping.py
new file mode 100644
index 000000000..50bdefa6a
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/variantbardatamapping.py
@@ -0,0 +1,67 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QObject, Signal
+
+
+class VariantBarDataMapping(QObject):
+
+ rowIndexChanged = Signal()
+ columnIndexChanged = Signal()
+ valueIndexChanged = Signal()
+ rowCategoriesChanged = Signal()
+ columnCategoriesChanged = Signal()
+ mappingChanged = Signal()
+
+ def __init__(self, rowIndex, columnIndex, valueIndex,
+ rowCategories=[], columnCategories=[]):
+ super().__init__(None)
+ self._rowIndex = rowIndex
+ self._columnIndex = columnIndex
+ self._valueIndex = valueIndex
+ self._rowCategories = rowCategories
+ self._columnCategories = columnCategories
+
+ def setRowIndex(self, index):
+ self._rowIndex = index
+ self.mappingChanged.emit()
+
+ def rowIndex(self):
+ return self._rowIndex
+
+ def setColumnIndex(self, index):
+ self._columnIndex = index
+ self.mappingChanged.emit()
+
+ def columnIndex(self):
+ return self._columnIndex
+
+ def setValueIndex(self, index):
+ self._valueIndex = index
+ self.mappingChanged.emit()
+
+ def valueIndex(self):
+ return self._valueIndex
+
+ def setRowCategories(self, categories):
+ self._rowCategories = categories
+ self.mappingChanged.emit()
+
+ def rowCategories(self):
+ return self._rowCategories
+
+ def setColumnCategories(self, categories):
+ self._columnCategories = categories
+ self.mappingChanged.emit()
+
+ def columnCategories(self):
+ return self._columnCategories
+
+ def remap(self, rowIndex, columnIndex, valueIndex,
+ rowCategories=[], columnCategories=[]):
+ self._rowIndex = rowIndex
+ self._columnIndex = columnIndex
+ self._valueIndex = valueIndex
+ self._rowCategories = rowCategories
+ self._columnCategories = columnCategories
+ self.mappingChanged.emit()
diff --git a/examples/graphs/3d/widgetgallery/variantbardataproxy.py b/examples/graphs/3d/widgetgallery/variantbardataproxy.py
new file mode 100644
index 000000000..5ab2a2cd2
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/variantbardataproxy.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot
+from PySide6.QtGraphs import QBarDataProxy, QBarDataItem
+
+
+class VariantBarDataProxy(QBarDataProxy):
+
+ def __init__(self):
+ super().__init__()
+ self._dataSet = None
+ self._mapping = None
+
+ def setDataSet(self, newSet):
+ if self._dataSet:
+ self._dataSet.itemsAdded.disconnect(self.handleItemsAdded)
+ self._dataSet.dataCleared.disconnect(self.handleDataCleared)
+
+ self._dataSet = newSet
+
+ if self._dataSet:
+ self._dataSet.itemsAdded.connect(self.handleItemsAdded)
+ self._dataSet.dataCleared.connect(self.handleDataCleared)
+ self.resolveDataSet()
+
+ def dataSet(self):
+ return self._dataSet.data()
+
+ # Map key (row, column, value) to value index in data item (VariantItem).
+ # Doesn't gain ownership of mapping, but does connect to it to listen for
+ # mapping changes. Modifying mapping that is set to proxy will trigger
+ # dataset re-resolving.
+ def setMapping(self, mapping):
+ if self._mapping:
+ self._mapping.mappingChanged.disconnect(self.handleMappingChanged)
+
+ self._mapping = mapping
+
+ if self._mapping:
+ self._mapping.mappingChanged.connect(self.handleMappingChanged)
+
+ self.resolveDataSet()
+
+ def mapping(self):
+ return self._mapping.data()
+
+ @Slot(int, int)
+ def handleItemsAdded(self, index, count):
+ # Resolve new items
+ self.resolveDataSet()
+
+ @Slot()
+ def handleDataCleared(self):
+ # Data cleared, reset array
+ self.resetArray(None)
+
+ @Slot()
+ def handleMappingChanged(self):
+ self.resolveDataSet()
+
+ # Resolve entire dataset into QBarDataArray.
+ def resolveDataSet(self):
+ # If we have no data or mapping, or the categories are not defined,
+ # simply clear the array
+ if (not self._dataSet or not self._mapping
+ or not self._mapping.rowCategories()
+ or not self._mapping.columnCategories()):
+ self.resetArray()
+ return
+
+ itemList = self._dataSet.itemList()
+
+ rowIndex = self._mapping.rowIndex()
+ columnIndex = self._mapping.columnIndex()
+ valueIndex = self._mapping.valueIndex()
+ rowList = self._mapping.rowCategories()
+ columnList = self._mapping.columnCategories()
+
+ # Sort values into rows and columns
+ itemValueMap = {}
+ for item in itemList:
+ key = str(item[rowIndex])
+ v = itemValueMap.get(key)
+ if not v:
+ v = {}
+ itemValueMap[key] = v
+ v[str(item[columnIndex])] = float(item[valueIndex])
+
+ # Create a new data array in format the parent class understands
+ newProxyArray = []
+ for rowKey in rowList:
+ newProxyRow = []
+ for i in range(0, len(columnList)):
+ item = QBarDataItem(itemValueMap[rowKey][columnList[i]])
+ newProxyRow.append(item)
+ newProxyArray.append(newProxyRow)
+
+ # Finally, reset the data array in the parent class
+ self.resetArray(newProxyArray)
diff --git a/examples/graphs/3d/widgetgallery/variantdataset.py b/examples/graphs/3d/widgetgallery/variantdataset.py
new file mode 100644
index 000000000..752bc3887
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/variantdataset.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QObject, Signal
+
+
+class VariantDataSet(QObject):
+
+ itemsAdded = Signal(int, int)
+ dataCleared = Signal()
+
+ def __init__(self):
+ super().__init__()
+ self._variantData = []
+
+ def clear(self):
+ for item in self._variantData:
+ item.clear()
+ del item
+
+ self._variantData.clear()
+ self.dataCleared.emit()
+
+ def addItem(self, item):
+ self._variantData.append(item)
+ addIndex = len(self._variantData)
+
+ self.itemsAdded.emit(addIndex, 1)
+ return addIndex
+
+ def addItems(self, itemList):
+ newCount = len(itemList)
+ addIndex = len(self._variantData)
+ self._variantData.extend(itemList)
+ self.itemsAdded.emit(addIndex, newCount)
+ return addIndex
+
+ def itemList(self):
+ return self._variantData
diff --git a/examples/graphs/3d/widgetgallery/widgetgallery.pyproject b/examples/graphs/3d/widgetgallery/widgetgallery.pyproject
new file mode 100644
index 000000000..581b21483
--- /dev/null
+++ b/examples/graphs/3d/widgetgallery/widgetgallery.pyproject
@@ -0,0 +1,29 @@
+{
+ "files": ["main.py",
+ "axesinputhandler.py",
+ "bargraph.py",
+ "custominputhandler.py",
+ "graphmodifier.py",
+ "highlightseries.py",
+ "rainfalldata.py",
+ "scatterdatamodifier.py",
+ "scattergraph.py",
+ "surfacegraph.py",
+ "surfacegraphmodifier.py",
+ "topographicseries.py",
+ "variantbardatamapping.py",
+ "variantbardataproxy.py",
+ "variantdataset.py",
+ "data/layer_1.png",
+ "data/layer_2.png",
+ "data/layer_3.png",
+ "data/license.txt",
+ "data/maptexture.jpg",
+ "data/narrowarrow.mesh",
+ "data/oilrig.mesh",
+ "data/pipe.mesh",
+ "data/raindata.txt",
+ "data/refinery.mesh",
+ "data/topography.png"
+]
+}
diff --git a/examples/gui/analogclock/main.py b/examples/gui/analogclock/main.py
index 12303cd1d..c0e00dd93 100644
--- a/examples/gui/analogclock/main.py
+++ b/examples/gui/analogclock/main.py
@@ -4,8 +4,7 @@
import sys
from PySide6.QtCore import QPoint, QTimer, QTime, Qt
-from PySide6.QtGui import (QColor, QGradient, QGuiApplication, QPainter,
- QPolygon, QRasterWindow)
+from PySide6.QtGui import QGuiApplication, QPainter, QPalette, QPolygon, QRasterWindow
"""Simplified PySide6 port of the gui/analogclock example from Qt v6.x"""
@@ -21,53 +20,69 @@ class AnalogClockWindow(QRasterWindow):
self._timer.timeout.connect(self.update)
self._timer.start(1000)
- self._hour_hand = QPolygon([QPoint(7, 8), QPoint(-7, 8), QPoint(0, -40)])
- self._minute_hand = QPolygon([QPoint(7, 8), QPoint(-7, 8), QPoint(0, -70)])
+ self._hour_hand = QPolygon([QPoint(5, 14), QPoint(-5, 14),
+ QPoint(-4, -71), QPoint(4, -71)])
+ self._minute_hand = QPolygon([QPoint(4, 14), QPoint(-4, 14),
+ QPoint(-3, -89), QPoint(3, -89)])
+ self._seconds_hand = QPolygon([QPoint(1, 14), QPoint(-1, 14),
+ QPoint(-1, -89), QPoint(1, -89)])
- self._hour_color = QColor(127, 0, 127)
- self._minute_color = QColor(0, 127, 127, 191)
+ 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)
def paintEvent(self, e):
- with QPainter(self) as p:
- self.render(p)
+ with QPainter(self) as painter:
+ self.render(painter)
- def render(self, p):
+ def render(self, painter):
width = self.width()
height = self.height()
- p.fillRect(0, 0, width, height, QGradient.NightFade)
-
- p.setRenderHint(QPainter.Antialiasing)
- p.translate(width / 2, height / 2)
side = min(width, height)
- p.scale(side / 200.0, side / 200.0)
- p.setPen(Qt.NoPen)
- p.setBrush(self._hour_color)
+
+ painter.fillRect(0, 0, width, height, self._background_color)
+ painter.setRenderHint(QPainter.Antialiasing)
+ painter.translate(width / 2, height / 2)
+ painter.scale(side / 200.0, side / 200.0)
+
time = QTime.currentTime()
- p.save()
- p.rotate(30.0 * ((time.hour() + time.minute() / 60.0)))
- p.drawConvexPolygon(self._hour_hand)
- p.restore()
- p.setPen(self._hour_color)
-
- for i in range(0, 12):
- p.drawLine(88, 0, 96, 0)
- p.rotate(30.0)
-
- p.setPen(Qt.NoPen)
- p.setBrush(self._minute_color)
-
- p.save()
- p.rotate(6.0 * (time.minute() + time.second() / 60.0))
- p.drawConvexPolygon(self._minute_hand)
- p.restore()
- p.setPen(self._minute_color)
-
- for j in range(0, 60):
- if (j % 5) != 0:
- p.drawLine(92, 0, 96, 0)
- p.rotate(6.0)
+ painter.setPen(Qt.NoPen)
+ painter.setBrush(self._hour_color)
+
+ painter.save()
+ painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)))
+ painter.drawConvexPolygon(self._hour_hand)
+ painter.restore()
+
+ for _ in range(0, 12):
+ painter.drawRect(73, -3, 16, 6)
+ painter.rotate(30.0)
+
+ painter.setBrush(self._minute_color)
+
+ painter.save()
+ painter.rotate(6.0 * time.minute())
+ painter.drawConvexPolygon(self._minute_hand)
+ painter.restore()
+
+ painter.setBrush(self._seconds_color)
+
+ painter.save()
+ painter.rotate(6.0 * time.second())
+ painter.drawConvexPolygon(self._seconds_hand)
+ painter.drawEllipse(-3, -3, 6, 6)
+ painter.drawEllipse(-5, -68, 10, 10)
+ painter.restore()
+
+ painter.setPen(self._minute_color)
+
+ for _ in range(0, 60):
+ painter.drawLine(92, 0, 96, 0)
+ painter.rotate(6.0)
if __name__ == "__main__":
diff --git a/examples/gui/rhiwindow/doc/rhiwindow.rst b/examples/gui/rhiwindow/doc/rhiwindow.rst
new file mode 100644
index 000000000..defbc46ef
--- /dev/null
+++ b/examples/gui/rhiwindow/doc/rhiwindow.rst
@@ -0,0 +1,49 @@
+.. _rhi-window-example:
+
+RHI Window Example
+==================
+
+This example shows how to create a minimal ``QWindow``-based
+application using ``QRhi``.
+
+Qt 6.6 starts offering its accelerated 3D API and shader abstraction layer for
+application use as well. Applications can now use the same 3D graphics classes
+Qt itself uses to implement the ``Qt Quick`` scenegraph or the ``Qt Quick`` 3D
+engine. In earlier Qt versions ``QRhi`` and the related classes were all
+private APIs. From 6.6 on these classes are in a similar category as QPA family
+of classes: neither fully public nor private, but something in-between, with a
+more limited compatibility promise compared to public APIs. On the other hand,
+``QRhi`` and the related classes now come with full documentation similarly to
+public APIs.
+
+There are multiple ways to use ``QRhi``, the example here shows the most
+low-level approach: targeting a ``QWindow``, while not using ``Qt Quick``, ``Qt
+Quick 3D``, or Widgets in any form, and setting up all the rendering and
+windowing infrastructure in the application.
+
+In contrast, when writing a QML application with ``Qt Quick`` or ``Qt Quick
+3D``, and wanting to add ``QRhi``-based rendering to it, such an application is
+going to rely on the window and rendering infrastructure ``Qt Quick`` has
+already initialized, and it is likely going to query an existing ``QRhi``
+instance from the ``QQuickWindow``. There dealing with ``QRhi::create()``,
+platform/API specifics or correctly handling ``QExposeEvent`` and resize events
+for the window are all managed by Qt Quick. Whereas in this example, all that
+is managed and taken care of by the application itself.
+
+.. note:: For ``QWidget``-based applications, see the :ref:`rhi-widget-example`.
+
+Shaders
+-------
+
+Due to being a Qt GUI/Python module example, this example cannot have a
+dependency on the ``Qt Shader Tools`` module. This means that ``CMake`` helper
+functions such as ``qt_add_shaders`` are not available for use. Therefore, the
+example has the pre-processed ``.qsb`` files included in the
+``shaders/prebuilt`` folder, and they are simply included within the executable
+via a resource file}. This approach is not generally recommended for
+applications.
+
+
+.. image:: rhiwindow.webp
+ :width: 800
+ :alt: RHI Window Example
diff --git a/examples/gui/rhiwindow/doc/rhiwindow.webp b/examples/gui/rhiwindow/doc/rhiwindow.webp
new file mode 100644
index 000000000..7ab3514af
--- /dev/null
+++ b/examples/gui/rhiwindow/doc/rhiwindow.webp
Binary files differ
diff --git a/examples/gui/rhiwindow/main.py b/examples/gui/rhiwindow/main.py
new file mode 100644
index 000000000..cfb73b10b
--- /dev/null
+++ b/examples/gui/rhiwindow/main.py
@@ -0,0 +1,80 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from argparse import ArgumentParser, RawDescriptionHelpFormatter
+import sys
+
+from PySide6.QtCore import QCoreApplication
+from PySide6.QtGui import QGuiApplication, QRhi, QSurfaceFormat
+
+from rhiwindow import HelloWindow
+import rc_rhiwindow # noqa: F401
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+
+ # Use platform-specific defaults when no command-line arguments given.
+ graphicsApi = QRhi.OpenGLES2
+ if sys.platform == "win32":
+ graphicsApi = QRhi.D3D11
+ elif sys.platform == "darwin":
+ graphicsApi = QRhi.Metal
+
+ parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
+ description="QRhi render example")
+ parser.add_argument("--null", "-n", action="store_true", help="Null")
+ parser.add_argument("--opengl", "-g", action="store_true", help="OpenGL")
+ parser.add_argument("--d3d11", "-d", action="store_true",
+ help="Direct3D 11")
+ parser.add_argument("--d3d12", "-D", action="store_true",
+ help="Direct3D 12")
+ parser.add_argument("--metal", "-m", action="store_true",
+ help="Metal")
+ args = parser.parse_args()
+ if args.null:
+ graphicsApi = QRhi.Null
+ elif args.opengl:
+ graphicsApi = QRhi.OpenGLES2
+ elif args.d3d11:
+ graphicsApi = QRhi.D3D11
+ elif args.d3d12:
+ graphicsApi = QRhi.D3D12
+ elif args.metal:
+ graphicsApi = QRhi.Metal
+
+ # graphicsApi = QRhi.Vulkan? detect? needs QVulkanInstance
+
+ # For OpenGL, to ensure there is a depth/stencil buffer for the window.
+ # With other APIs this is under the application's control
+ # (QRhiRenderBuffer etc.) and so no special setup is needed for those.
+ fmt = QSurfaceFormat()
+ fmt.setDepthBufferSize(24)
+ fmt.setStencilBufferSize(8)
+ # Special case macOS to allow using OpenGL there.
+ # (the default Metal is the recommended approach, though)
+ # gl_VertexID is a GLSL 130 feature, and so the default OpenGL 2.1 context
+ # we get on macOS is not sufficient.
+ if sys.platform == "darwin":
+ fmt.setVersion(4, 1)
+ fmt.setProfile(QSurfaceFormat.CoreProfile)
+ QSurfaceFormat.setDefaultFormat(fmt)
+
+ window = HelloWindow(graphicsApi)
+
+ window.resize(1280, 720)
+ title = QCoreApplication.applicationName() + " - " + window.graphicsApiName()
+ window.setTitle(title)
+ window.show()
+
+ ret = app.exec()
+
+ # RhiWindow::event() will not get invoked when the
+ # PlatformSurfaceAboutToBeDestroyed event is sent during the QWindow
+ # destruction. That happens only when exiting via app::quit() instead of
+ # the more common QWindow::close(). Take care of it: if the QPlatformWindow
+ # is still around (there was no close() yet), get rid of the swapchain
+ # while it's not too late.
+ if window.isVisible():
+ window.releaseSwapChain()
+
+ sys.exit(ret)
diff --git a/examples/gui/rhiwindow/rc_rhiwindow.py b/examples/gui/rhiwindow/rc_rhiwindow.py
new file mode 100644
index 000000000..67f6adeb9
--- /dev/null
+++ b/examples/gui/rhiwindow/rc_rhiwindow.py
@@ -0,0 +1,319 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.6.1
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x03\xd6\
+\x00\
+\x00\x0b\xa0x\x9c\xd5VMo\xdbF\x10]}8\x8e\
+\xe94i\x9a\xaf&q\xddM\xdd&\x94\xe3\xa8\xb4\xe0\
+\x04Ee\xb9-\x92\x221\x10\xc0n\x5c\x18\x05\x04\x83\
+P$\xca! \x93\x86H\x0a*\x02\x03\x05r\xcf\xb1\
+\xa7\xde\xf2\x0f\xdaC\xff@\x81\xfe\xa5^\x8avf\xf7\
+Q\xbb\xfapN\xb9d\x01\x8a\x9c\xb73\xf3f\xe6-\
+i\x0b!\x16\x84Y\x05\xba\xce\x8a\x81\xf0EF\xbfB\
+\x14\x19\xfc\x0fK\xbc\xc3\xc5D\xabt\x9d\xb7l^e\
+\xdc\xcf\xe0\xde1!\xa5\xdfJ\xc5\x95yvu\xa8\xc6\
+\xbb@/R\x8d\x1c\xbcH!|\x7f\xfct\xefi5\
+I;\xd5\x8d\xfb\x9eP\x04\xa5\x11\xc1\x05\x8a\xcbI\x8e\
+Za\xc4\xcf\xdc\xfd9\xba>\xe1X\xdaa{\x19\xb9\
+\xd9>\xa7r\xebb\x9f\x889q\x13{\x8b\xb8\xe7X\
+\x01X\xc1\xc2\x8a\xc0J*Wi\x84]\xa2_\xbe\xdf\
+\x22\xac\x04\xec*=q\xd3\x92\xaek\xc4;\x8f!\x14\
+\x15VV\x95\xb3/\xe3u\xd8\x0b\xc0\xae\x90\xed \xb6\
+\x00\xff\xbc\x16\x07\xfe\x8b\xe8\x93\xb1\xbb\xf0??a\x7f\
+\x08>\xe6\xbf\x06\xfe2\xf2\x7f\x8c\xfc\x02\xfel_G\
+\xfc\x12\xd97\xe0\x7f]\xcdO\xf7\x9f\xe7\xb8\x81\x9a\x96\
+P\xefM\xd4\xb4\x84\xb9\x97\xac\x1a\x96-\x8e3\x86\xf3\
+\xdb\xdc\xfeL\x99\xbf|\x93\xdb_\xc0\x9f\xf3W\x90\x8b\
+y\x1fP\x0dEa\x0e\x14\xe3\xff\x102G\xf7\x06\xb8\
+>\xc0L\xfe\x22\x94\xed\x0b\xc0x.\x7f\x03\xbb\x08\x9c\
+g\x13\x83\xf3#\xe0y\x9eK\xc8\x93\xc7\x5c\x06f\xc7\
+\x5c\x01\xbeK>\xf3Jo\x9d\x87\xf1-\xaan\x01X\
+\x03\xda\x7f\x0a}_\xc3\xff\x160i\xe5X\xc1<\xf8\
+z\x05\xecs\xf8\xf2\xde\x0f\x841\xf7m\xe0\xc2\xc2\xee\
+\x00+\xa8|\xf3jf.|\xef`\xae\x9c\xf7;\xf2\
+\xe7\xb9\xaeB\xabe\xd4\xbb\x0a\xff\x7f)\xc3W\xfa\xf5\
+-\xe3\x9db\xa3#\xcc;\xfd\xe7\xca \xe8'a\x1c\
+\xc9u\xcfs\x9cA\xab\xffs\x18\x1d\xcaA\xd0\xae\xc9\
+\x81\x9f\x0d\xea\x84\xc5aGr\xb8[q^:\x92\x16\
+o\xc8\x86rr\xbb\xbd\xb8\x95\xba\xeea\xcf\xdf\x0f\xfa\
+i0\xdc~$77\xe5zE\xde\x96\xb5\xca\x9a\xd4\
+\xdb\xf6.\xe3\x95\xba\xcaC\xf0n\x9c\x84)\xd3\xabt\
+\x1b\xae\xabr\xaf\xcaZ\xd5\xab\xc8{\x9ab\x9d\x9e\xd7\
+\xa4W\xf5\xd6$?\xd6\x9d\x13\xc7\x99hi(\xcc\xb2\
+Z\xaa\xbd\xc7-\xfdj\xb5\xf4\x87i\xe9>\xb5\x14g\
+\xe9{\xd5\x0e\xbf\xeb5\xd3M\xf1M\x92\xb6\xd2\xb0\xad\
+\xa97l\x92\xba\x83\xad0J\xa5\xa9)\xea\x04\xc3\xd1\
+\x96\x8a\x1a\xf5\x9e\xa4\xfd\xac\x9d\xca\xbd\xdd\xedg\xfb\xfe\
+\xc3~\x9c$\xfevt\x9c\xa5\x18D6\x9dH~-\
+\xf7\xf6G\xddR\xad\xb3\xb3\xecd\xa9IcqR\xf8\
+\x8f\xdf\xff\xf4pg\xe7\xd9#\xafn6\xc7\xda\xd0\x14\
+\xa6\xa9\x93\x5c$\x921\xf5g)\xa5\x09\xa6\xb5R\x05\
+\xbfM.\xe5p\xaab\xba4[\xb3.\x8bF\x22u\
+\xab\xc3\xa1\x92\xac\xab4\xebj\xd1\xa6\x07\xa0\xcf\xd5\xd4\
+x%\x89q\x18\xf8!?\xe7\xadL\x14\xd5`\x11]\
+\xcb\xaf:\xee\x80\x82\xad\x91h`F\x0d:I\xac\x0c\
+\xedd#\xd5\xf1\x9e\xc7\x8e\xd3\x94o\xfef\xa8\xd3\xc3\
+\xbb\xfd \xcd\xfa\xd1\x04\xc5\x89}|\xcb\xf8\x13\x82U\
+\xf8}%\x8c\xda\xbd\xac\x13\xc8\xcd\xa3 m\xf5|\xfa\
+\x97\xa6\x17>\xdfr\x0c\x9e\x84G\x9d/\xf9\xa7\xfab\
+\xcbq\xb2\x84?@Q\xeb(H\x8e[\xed@\xaa(\
+s\xea\x98\xc6c\xea\x19\x87\xad\xd9\xcc\x92\xa0\xef\xf6\xe2\
+v\xe4U\x0e\x0eN=p\xcd\xe61\x1e\xd9I\x9d7\
+5i\x93\x5c?\xb9\xb3^\x88fS;\xfba\xe7\xe0\
+ W\xd3\x04\xf2\xd5\x90/O4yl\x868~h\
+Y\xed\x09\x85g\x9c\xdcY^\xd6\xf1\x8d\xa7\xe4\xcc\x8f\
+\xf0\x88w\xf4\xe9\x01\xfd\xd4\xc7\xc7R5\xd6b\xb2\x9a\
+s\xaa!|\x8d&\xbeHjM\xc8l\xf4>m\xef\
+\x7f\xf6^=\xc8\
+\x00\x00\x04\x0b\
+\x00\
+\x00\x0b\x95x\x9c\xd5Vmo\x1bE\x10\x1e\xbf$n\
+.%I\x9bR\x0a\x05\xb44\x12\xb2Ke.\xc5\xa5\
+\x88\x90((\x01Z\xa9RJS*$\xcb:]\xce\
+gg\xc1wg\xdd\xed\x99FU\xfe\x02\x1f\xf9)H\
+|\xe6\xaf\xc0\x7f\xe0\x0b\x82\x99\xdd9\xdf\xc6qi%\
+\xfa\xa5+\xado\xe7\xd9yyffo\xcf\x00\xb0\x04\
+\x00u\x9c\x15\x9c+0\x01\x0f\x02H`\x843E\xa4\
+\x86\x13\xfe\xe1\x01s\x06\x99]\x86\x01*\xfb0\x84=\
+\xcb\xb4\xfe2\xa6\x8bp\x049\x9a\xd3\xca\x83M\xb8\x8b\
+\xab}K\xa7\xaaw\x22\xe45\xc6\xd52\xa3\xbb\x96\xc6\
+Z\xb1X\xc1\xb8cd\x11\x80\x04\x05'\x1c`\xb7`\
+\xf2*\xc7\x22?\xfb\x16\xd1?j\xd5\x8d\x06\xc5t\xe0\
+\x02\xbc\xcf\xe8%L\xa0\xa2\x89/\xea\xe77\x0f\x0e\x1f\
+\xb43\xd5ow\xee\xb8\x86\xb3.\xb0\xde[\x85\x86f\
+J3\xf2eL85\xe7\xa2N\xb2\xa6\xf1\x06\xf9\xc0\
+\x15\xe1E\x0c\x92/Z\xf2=\x94Wy\xbd\xa0\xe5\x85\
+\xa9\xbc1\xd5)\xb1\x06\x98\x22\x16X\x85\xf5v\xb5\xef\
+\x9a\xc6\xaa\x1c\xe7\x12>oXqI\xfe\x80\xe5u\xd4\
+\xaaj\xb9\xa6\xb3\xa2\xf5U\x5cQ\xb1\x04\xce\xb7P\xbf\
+\xc1\xc5\xabk\xac\x8e\x952G\x8c\xf0-\x96\x97\x18#\
+}\x87\xf5k\xac\xbf\xcc\xfc\x1c\xd6_\xe6\xfa\x10v\x0d\
+\xe5\x15\xf6U\xd7\xf50uXa\x1fd\xbf\xc6\xbcV\
+\xd9~M\xf7\xc8`o\xa2|\x99\xb9\x92\xbf\x8fX^\
+g\x99\xec\xaf@q$\x01>\xc5zU\xa1<]\xc4\
+\xf1/D\xa8\xe6\xdb\xcc\xfd\x0d\xe6\xf7%\xa2Wt\x0c\
+\x13o\x9du\x16u\x8d\x0c\xfe3\xea8:ocw\
+\xd5\xb2\xbb6\xc7\xeem\xc6\xbfE\x1d\x92\xdfa[\xb0\
+\xb0\xeb\x8cU,\xec]\xc6\x88\xfbC\xac\x16\xd5\xeb=\
+\xb6\xbf\xce\xfb\xe4{\x073Z\xe2\xbd\xbf\xd1\xc3g\xda\
+\x0b\xd4\xf9l\x92\xd0\xe7'\x8d?7&a\x9a\xc9$\
+\x16\x9b\xae\xeb\x8c\xd30\x90Z:\x96\xc3\xe3\xb1\x18\x8c\
+\x12_m\x9d\x83e\x8c\xa0\x93\xa94\x0f\x948\xca\x07\
+\xce3G\xe0\x88|\xd5\x11\xd1d\xbc\xa5%m+\x92\
+\xb1\x1fHu\xb2\xe5\x9c\xa2E\x1e\xcbA\x92Fd\x22\
+\xbc\xcd\xbb\x88L\xfc\xf4D\xc6C1\x09\x83O\xc4\xc4\
+\x0b\x92Q\x92\x12\x9c\xc8\xbe \xc6\xcd\x16\xfb\x1e\x8e\xbc\
+\xafS\x7f\xb8\xef+\xbf\xeb\xf6\xc46Yt\x9al!\
+n\x92\xbb6\xc7\xbae\x0b-\x8c\xec83\x05x\x0a\
+\xe5\xf8\xad,\xc0m\xf7uN\xea\x17+\xa9\xdf\xcb\xa4\
+\xee\xbc\xa2\xa4\x92\x5cizb\x80\x8c\xf7LJ2~\
+q\x8eS\xf5\xff\x91\x1e\x9d\xf9\xdbev\xd5\xef\x02\xe4\
+5\x08SM\xefs\x91\x86C\x99\xa90m\x1e\xb9E\
+\xd44\xf9\xc9\x8b\xfc\x1f0\x8cN\xae\xf3\xb4C\x01<\
+L\x19\xf51\xca\x8f\xc9`\x90\x85\xaa\x19\xb8-\xbb\x06\
+\xa4\xc3$f\xf4:-S\x18\xac\xa5\xafd\xc0n\xed\
+b\xd8\x1bvI\xb8\xf8\x87\x0f\xef?z\xe2\xed\xa5I\
+\x96y\xf7\xe3q\xae\x8a\xfa\x9c\xd1\xc7\xa8\x8f\xbf\xfa~\
+\xef\xe0\xe0\xd1\xbek\x02\xce1?\xc8\xd5\x8c\xbdE\x04\
+=\x1c>\xf1\x1e\xfb\xe90T\xecB7\x85\x14\xbc\xe7\
+v\xc6x9\xdb\x1b\xcf\xee\x8dw\xa67\xe7\xd9\x98\xa6\
+\x9fKR`U\x86\xa1'i]\xc4-\x82l\xdb\x9b\
+\xedi\xc1\x0ajL\xd6\x00s\x02\x1a\xe3D\x0bF\xc9\
+F\xdag\xb2+\xbb\xa4\x0fG\xa8\xf24\x9eqpj\
+\x1f\xb8:\x7f\x00xT~\xdd\x90q0\xca\xfb\xa1\xf8\
+\x22\x0a\x95?\xf2\xf0\xcf\xc0H\x1e\xed8%\x9e\xc9\xa8\
+\xff1\xfd\xb4\x8fw\xf0\xfd\xc9\xe8\x02\x88\xfd(\xcc\xb0\
+n\xa1\xd0V\xf3\xee\xcc\xe9\xf1\xfc\xaf\xb7\x91\xad\x88\x9c\
+K\x84\x9f\xd7\xfanW\xd7\xb0\xe9\xb6z\xbd9\x962\
+\x9e\x7f\xe6\xba\xdd<\xc3\xb7g\x94\x04qiI^\xa3\
+0\xb6\xa2\x9aU\xb3\xf0\x85_\x01\xb4,Z\xd8\xeb\xdd\
+\x12A\x12\xa3\x18\xeb\xf4>\xa4C\x83\xfb\xe6E\xd5\x8c\
+Z\xd3\xcb\xa7pHs[<;5\x89'\xb3]3\
+gR\xc6\xed\x97\xba2\xac\xce&\xa6\xa1\xd4\xd1\x05\x1d\
+\x8d\xef\x90\x99{d\xfa\x11\x04\xfe\xaf;\xd3\xf6s\xfb\
+\x056\xab\xa7\xc7\xbf\xc3\x1a`\x81\
+\x00\x00\x03\xff\
+\x00\
+\x00\x0b\x1ax\x9c\xcdV[o\xdcD\x14\x9e\xbd\xa4K\
+\x9c\x96\x86\x96$\xa5\xe52M@r\xca\xca\x98U\x8a\
+\x10\x9b\x8d\x84\x12@\x95*\xa5\xedF-\xd2je\x19\
+\xefdc\xc9k\xaf\xec\xf1\x92R\xe5/\xf0\xc8\x0b\x8f\
+\xfc\x0c~\x03\xff\x87\x17\x04\xe7\xcc\x1c\xaf'\xdeM\x95\
+GF\x9a\xf5\x9co\xce\xe5;\x17;a\x8c\xad2\xc6\
+\x9a\xb0k\xb0\xdfa3\xe6\xb1\x1c~\x19\xab\xc3f\xff\
+\xd2bK\x16\x1a\xdca\xa7,e>\x1b\xb3C\x96\xb0\
+\x08vJ\xee\xdejZus\x83I&\xd89\x9c\xec\
+\xeb\x98\x5cw\xdd\xa0\xe7\xa8\x84\x1a\xedF}\xa7\x85Q\
+-\xc8\xf63B\xdf\x83l\x91\xc7\x1a\x98\xe0\xf3\x87\xa7\
+\xfd\xa7N&G\xce\xdec\x17\xef\xdfe\x8d9\xd7\xdb\
+\xac\xa5\x12\xc4=\xf1\xc3X\xdb3\xb6\x01{\x1d\xf4\x10\
+o\xa1\x0f8\xdd\x82\xe76\xc5(\xe4\x87\x86\x8cv\x1f\
+\x1b\xf2\x86!\xdf\x05Nu\xa5\xdfP\xd1\xf1\xbc\x09'\
+L\x8a\xc3\xde\x02\xfd\x16%\xd9TX\x132\xd2\xb1q\
+\x7f\x00\x9d\xb5\xe8\xbeV)L!?\x00\x7fk\xf0\xb4\
+\xc8\xfe&\xe1\x88uI\xbeE\x18\xc6\xbbM\xfe\xea\xa4\
+\xbf>\xaf\x89\xd6_\xa7Z\xd4\xe8\xfe}\xac8\xf1\xe9\
+\x92\xbcA\x18\xfa\xdb$\x7f\x0d\x857\x81\xb3\xce\x0d\xd7\
+\xe7$\xdf\xa7{\xf4\xf7\x80|\xa1\xcdWlE\xf1h\
+\x16\x9d\x85\xfd7 +\x14\x0bk\xb1J\xfa=\x90\xd7\
+T\x1fu>=\xca\xe5\x0e\xf1}\x05V-Us\xad\
+\x83\xf8\x01x\x5c%\xacG\xb5\xde\x22\x9f\xc7\xe0\x1d\xb9\
+\xdf#l\xcb\xa8+r\xfa\x16\xfc!\xd7\x0fI\xff>\
+\xf9@\xde\x1f\x11\xfe+\xe8lR\xbf\xef\x11^\xd8}\
+\xb2\xc4\x8e\x13\xfe\x1ctP~\xc8\xcaY)\xb0m\xc2\
+j\x06\xb6C\x18\xf2z\x06Y`\x1e\x9f\x92\xfd6\xdd\
+s\xcaw\x83\xee\xfe\x01\x0f_\xeb)i\xd2\x8c\xa30\
+b\xe5\xe8\xfc\xb53\x13i\x16&1\xff\xd2u\xadi\
+*\x82PIg\xe1\xf8l\xcaO\xa3\xc4\x97\xdd\x058\
+\x8c\x01\xb4\xf28<M\xd2\x09a\x99?\x99F\x22\xed\
+\x1cq)\xce\xe1v\xe6\xa7\xaf\xc3x\xccg\x22\xe8\xf0\
+\x99\x97\xcf\x10K\xc2\x11G\x22\xf6\xae\xf5\xc6\xe2\xb0\xe0\
+v\x8f\x07\xbc\x87F2OE\xe7\xc8\x86S[\x19\xec\
+v\x95\xca8\xf2\xbeO\xfd\xf1\x91/\xfd\x81;\x04U\
+\xb4\xb1\x03\xe7\xfc\xf5/\xfc\x11\x0f\x9c\x9f\xdb\xf8\x03\xca\
+\x17\x96UI\xf4\x9c\x95\xeb\xf72\xd1\x8e[\x92\xff?\
+\xd2\xfe\xcd\xa0\xfdGI\xfb\xf1[h\x87\xb1\xc98\xc9\
+\xa5\xa6x\x0a\x0c\x0e\x93(I\xaf\x95\xc5B\x0es\xf3\
+k\xd3\xc7\xe9\xec\x94\xec\xeb\xfb'E\x81\xf6\xd5,\xed\
+\x1d`0\xfe\x0dO\xc58\xcc\xa4Hm\xe9\x82\x93\xbe\
+N\xa7/})\xb8\x07\x1a\x1e%hjf\xa8ie\
+\xa0\x13\x06z2\x8b|M\xecr\xd2\x99L\xf3@\xf2\
+\xfe\xb3'/^z\x87i\x92e\xde\x93x\x9aK\xaa\
+\x80\xe1\x05\x22\x9d|\xf7\xe3\xe1\xf1\xf1\x8b#\x17\xb2Z\
+n{\x9c\xcb\x8a\xb1\x11\x0e<\xf4_z'~:\x16\
+\x92\x5c\xa8\x9a\xa3\x82w\xa9\xf0dI\xa5wt\xf6\xb6\
+\x99\xf7\x95m\xd0\xa6W4b\x91\xaan\xf8B\xfa\x1c\
+*6\x16^\x88\xe7\xf94`\x11z\xe6\x8d\xa3\xab[\
+P\xa0\x1c4\xb0$\x94\xb6L\x94\xa0\x95L\xc4\xb9\x94\
+E\xd9\x22\xd4K\x05\xccH\x5cqpa\xce\x15\xfeu\
+\xb8Y\xceU\xed\xcf\x9d0\x0e\xa2|$\xf8\xfeDH\
+?\xf2\xe0\xaf|\x14\xfet`\x95x\x16NF_\xe0\
+\x8fsv\x00/N\x86ou\xecOD6\xf5\x03\xc1\
+\x95U\xd9d\x0c\xe3b\xe8\xabz;\x18\x04\xf8\xb4\xdd\
+\xdd\xe1\xf0\xd2xh\xcb0^2Q\x83A\x9e\xc1\xdc\
+FI\x10\x97f\xe8r\x22b#\xa4>\xd9\x85#\xf8\
+\xba\x82e\xd1\x86\xe1\xb0=\xff\xc8\x8c\xf4;\xa4_\xa1\
+\xc1\xa0xk\xd1w\xbb\xf8\x22\xe0]\x1fN\xc8\x98 \
+\xa5Pt\xb9\x8c\x8a\xbb\xc7\xdf\x5ct\x97\x8d\xa46\xb5\
+\x0bgm \xe5\x18\x13\x99T\xfby\xe5T\x1a\xedM\
+tW\xb1\xad+\x8a\x06}/*\xdf\x8c\x85\xffr*\
+\xbd_\xb8/\xb0\xaa\x9eZ\xff\x018\x196d\
+\x00\x00\x04k\
+\x00\
+\x00\x0d\xafx\x9c\xe5Wmo\x1bE\x10^\xfb\x9c\x94\
+\x5c\xc9K\x936i\xa1-\x9bZB6T\xc6\x09\x0e\
+B5\x89\x82ZD+UJ\xd5\xa0\x0a\xc9\x8aN\xe7\
+\xf39\x1c\xf8\xee\xac\xbb=\x93\xa8\xb2\xc4_\xe0\x0b?\
+\x8c\xdf\xc0\x8f\xe0\x0b\x82\x99\xddY\xdf\xde\xd9)\xaaT\
+\xf1\x85\x95\xd6\xdey\xd9y\x9e\x99\x9d\xdd8\x8c\xb1\x15\
+\xa6F\x15\xa6\xcd<\x16\xb3\x11\xcc\x04$\x0bf\xe5o\
+\x1al\xc1\xd8`cpMY\xc0\x04\xcc\x98E\xa0\xab\
+\xa1\xe1M\x9b*0\xd7\xd8\x849sX\xff\xbam\x99\
+\xf5Y\xc6\x86r\xe5\xb0}v\x00\xab'\x86OUZ\
+B\x08>\x86\xd5u\xd2\x1e\x17\x18\xd3X\x03\xdc1s\
+\x81\x03\x92\xbf$\x80c\x9d\xc0\xbb\x1a\x15\xfa\xd6A\x97\
+\xe9{\x90\xbbX\xbfX\xd5\xfa5t\xb5\xd9{\xacN\
+\xda\x1b\x90LE&\xb1,\xbf\xbf}~\xfa\xbc\x95\x8a\
+A\xabs\xd0V\xfc\xad\x19\xc0:\x9d!\x82\x84n\x80\
+\xa7 5X\x80-\x98\xf7q?XWh\xcdH\xbe\
+N2\xc6x\xca\x96\x00S\x0d]8\xad\xab\x90\xce\xf4\
+\xab\x92\xce\x92\xb1\xac\x99\xee)\xc4\xdd\xa1\xfdK\xe4\xaf\
+\xe5\xba\x11W\xeb qy(ZW!\xbfc\x8a\xbb\
+Cq\x91\xef\x1d\xf8~`\xf0Gy\xd7\x90\xef\x1b\xf9\
+m\xc1\xae\xaa\xb4[\x92#\xae\xb7a\x85\x07\xc0a\xee\
+\x80\xff5:\x10K\xeajP}\xb5F}\x97\xe4\x15\
+\xc3n\x13?m\xb7\x8d\xba`\xbcU\x8a\x87\xe7p\x0b\
+>\xd7\x08\x0b\xc7\xa7$\xaf\x93\xff]\x907\xc8\x7f]\
+\xf2Vu\xd516\x08s\x93\xf0o\x10\xe6&\x9d\xa9\
+E\x187\x09\xa3B\x187\xa5^\x8d\xdb oS\xcc\
+\x9a\xc4Pg\xb3M\x18\x18\xff6\xd5f\x87\xe2\xa3|\
+\x87th\xff`V;%\xdf#\xacU\xf2\xbf\xc7\xf2\
+\x1eB\xfb.qC\xfb\x17\x90S\x95\xe5\xcd\x8f\xfa?\
+A\x83}qH\xf5\x7f\x9fjx\x04\xd6\x15\x92\xbf\x06\
+\x0f\xc4\xfd\x90\xb8\xdc\x22\xffmY7\xa5?\xa4z\x7f\
+D\xf8\xbf\xc2\x9eU\xaa\xc5]\xd2c\x9c]\xea\x99-\
+\x8a\x838\x0f\xc8\xef/`\xfd\xa5\xba\xa25\xba7(\
+\x0cX~o\xff\xa8O\xfc$\x0d\xe2\x88\xef\xb5\xdb\xb6\
+\x9d\x8a$\xf3\x04\xefgC\xfb\xb5\xcda\x84\xae\xe8\xf0\
+p2\xeeJi8\x8a]\xc1\xe3\xb1\xeb\x05\xe2\xb2k\
+O\xbb\xb6\x9dE\xc10NB\xdc\xc2\x9d\xfd\x03\xd0L\
+\xdc\xe42\x88\xce\xf9\xc4\xf7>\xe7\x13\xc7\x8bGq\xd2\
+\xb5]!\x92\xa0\x9f\x09_\xe9\x17i;|\x1c\xa7\x81\
+\x002\x18%\x0e\x06\x1cI7\x9aD\x85B\xf1C\xbd\
+\x19\x95\xe7#\xe7\x05m\x02\x03\x10h\x01Y\xfe\x89\x11\
+ij\xdb\xa5\xfc/X>\x8c\xfc\xf7\xff/\xf9\xfff\
+\xe4\xff{\x9e\xff\xc1;\xca?\xceD)\xf7 *$\
+\xad\xc4\xff\x22[\xbc\x9c\xfby\xb2\xd6\xa6\x074\x87~\
+\x22\xd9>\xe2\x89\x7f\x1e\xa4\xc2O\x1a\xfd\xb6\x86M\xe2\
+\x9f\x9d\xd0\xfd\x11\x80e\xae\x9d\x8b\x0e\xc28\x08\xf3\x88\
+C\xde?\xc5\xc3a\xea\x8b\x86\xd7n\x9a%A\x1f*\
+K\xc9\xaf\xd3Tu\x82\xd2\xba\x22\xf0(\xac\x99I\xb7\
+`2\xcaVT/P\x16JHGw\xfa\xe2\xd9\xcb\
+W\xce\xe3$NS\xe7Y4\xce\x04%V\xda\x00,\
+\xbf\xfb\xe6\xfb\xc7''/\x9f\xb4\x8dD\x08\xc6\xb0\xee\
+)\xfa\x0b\xa2\x9fd\xa2\x14~\xc6\xfd\x8a\xe8\x85\xb4\xc1\
+\xe7\xf4\x95Q\x84\xa9n\x03hH\xe1\xbcm/\x84\xd9\
+\xa8\xa1s{\xa8\x8f\xac)\xfba\x9e\xb3\xea\xb4\xb9J\
+q\xa8\xed\xb9\xef\x04\xb8\xd6\xd0\x1a\xd80\xb5\x0c\x12\xe3\
+\x9c\x81\xe9\x91\x1f\x8b\xa4\x9f'\xa4\x14\x0b\x18\xa9\xdd\xb1\
+\x14\x94\x93\xa9i\x15\x93-4\xcf\x9co^\xadY'\
+\xc9\xce\xf6E\x96D%\xa0\xa9y[j\xf4\xe7\x89F\
+u\xa9\x1eD\xde(\x1b\xf8\xfc\xab\xd0\x17\xee\xc8\x81\x9f\
+g\xa3\xa0\x7fd\xe7\xfa4\x08\x07\x9f\xe1G\xeb\x87#\
+x\x0bR|\xf7\x227\xf4S\xb8\x02>\x97\xbb\xba\x0b\
+^\x95\xd9\xddz\xd3\xcbB\xbb\x90\x5c\x1b\x09/\xee\xb4\
+^/K\xe1\x06\x8fb/j7\xcf\xce\xae\xec\xb6^\
+O\x1f\x0a:\xcd\xc5\x0f\xa2+\xeeI\xaf7{\xa3\x1b\
+%\x00}YL\x97\xbd\xa6\x0e\x8f\xa7\xee_\xe4\xf4\xd5\
+\xaa\xa1\xe1x\x80\xb1u\xd3\x9c\x9d=\x84`\x11\x88\x91\
+\xac\xd3\xc7\xd8\xc2`W\xcf\x95\x04n\xce^d\x1d\x10\
+\xe7!\x7f=U\x94\xe2\xc2\xd1\x07\x91\xd9\xa6\xf1\x5c\x0b\
+\xe5o'x\x16\xdb\x95\x1a%V\xfd\x81\x0d\xb2$1\
+\xe9=-\xbd\xa9\xb3_\x12\x8c\xfe\xb1)u\xd1\x9c]\
+\xeb\xca~r\xfc\x03\x0d.\xdb\xc1\
+"
+
+qt_resource_name = b"\
+\x00\x0d\
+\x0d\xf9\xb2\xf2\
+\x00q\
+\x00u\x00a\x00d\x00.\x00v\x00e\x00r\x00t\x00.\x00q\x00s\x00b\
+\x00\x0e\
+\x04\x16\xeb\xb2\
+\x00c\
+\x00o\x00l\x00o\x00r\x00.\x00f\x00r\x00a\x00g\x00.\x00q\x00s\x00b\
+\x00\x0d\
+\x09\x18\xb0\xd2\
+\x00q\
+\x00u\x00a\x00d\x00.\x00f\x00r\x00a\x00g\x00.\x00q\x00s\x00b\
+\x00\x0e\
+\x00\xfb\xe9\x92\
+\x00c\
+\x00o\x00l\x00o\x00r\x00.\x00v\x00e\x00r\x00t\x00.\x00q\x00s\x00b\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00b\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xec\
+\x00\x00\x01\x8b8N2\x22\
+\x00\x00\x00 \x00\x00\x00\x00\x00\x01\x00\x00\x03\xda\
+\x00\x00\x01\x8b8N2\x22\
+\x00\x00\x00B\x00\x00\x00\x00\x00\x01\x00\x00\x07\xe9\
+\x00\x00\x01\x8b8N2\x22\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x8b8N2\x22\
+"
+
+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/gui/rhiwindow/rhiwindow.py b/examples/gui/rhiwindow/rhiwindow.py
new file mode 100644
index 000000000..fe054af48
--- /dev/null
+++ b/examples/gui/rhiwindow/rhiwindow.py
@@ -0,0 +1,420 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import numpy
+import sys
+
+from PySide6.QtCore import (QEvent, QFile, QIODevice, QPointF, QRectF, QSize,
+ qFatal, qWarning, Qt)
+from PySide6.QtGui import (QColor, QFont, QGradient, QImage, QMatrix4x4,
+ QPainter, QPlatformSurfaceEvent, QSurface, QWindow)
+from PySide6.QtGui import (QRhi, QRhiBuffer,
+ QRhiDepthStencilClearValue,
+ QRhiGraphicsPipeline, QRhiNullInitParams,
+ QRhiGles2InitParams, QRhiRenderBuffer,
+ QRhiSampler, QRhiShaderResourceBinding,
+ QRhiShaderStage, QRhiTexture,
+ QRhiVertexInputAttribute, QRhiVertexInputBinding,
+ QRhiVertexInputLayout, QRhiViewport,
+ QShader)
+from PySide6.support import VoidPtr
+
+if sys.platform == "win32":
+ from PySide6.QtGui import QRhiD3D11InitParams, QRhiD3D12InitParams
+elif sys.platform == "darwin":
+ from PySide6.QtGui import QRhiMetalInitParams
+
+
+# Y up (note clipSpaceCorrMatrix in m_viewProjection), CCW
+VERTEX_DATA = numpy.array([
+ 0.0, 0.5, 1.0, 0.0, 0.0,
+ -0.5, -0.5, 0.0, 1.0, 0.0,
+ 0.5, -0.5, 0.0, 0.0, 1.0], dtype=numpy.float32)
+
+
+UBUF_SIZE = 68
+
+
+def getShader(name):
+ f = QFile(name)
+ if f.open(QIODevice.ReadOnly):
+ result = QShader.fromSerialized(f.readAll())
+ f.close()
+ return result
+ return QShader()
+
+
+class RhiWindow(QWindow):
+
+ def __init__(self, graphicsApi):
+ super().__init__()
+ self.m_graphicsApi = QRhi.Null
+ self.m_initialized = False
+ self.m_notExposed = False
+ self.m_newlyExposed = False
+
+ self.m_fallbackSurface = None
+ self.m_rhi = None
+ self.m_sc = None
+ self.m_ds = None
+ self.m_rp = None
+ self.m_hasSwapChain = False
+ self.m_viewProjection = QMatrix4x4()
+
+ self.m_graphicsApi = graphicsApi
+
+ if graphicsApi == QRhi.OpenGLES2:
+ self.setSurfaceType(QSurface.SurfaceType.OpenGLSurface)
+ elif graphicsApi == QRhi.Vulkan:
+ self.setSurfaceType(QSurface.SurfaceType.VulkanSurface)
+ elif graphicsApi == QRhi.D3D11 or graphicsApi == QRhi.D3D12:
+ self.setSurfaceType(QSurface.SurfaceType.Direct3DSurface)
+ elif graphicsApi == QRhi.Metal:
+ self.setSurfaceType(QSurface.SurfaceType.MetalSurface)
+ elif graphicsApi == QRhi.Null:
+ pass # RasterSurface
+
+ def __del__(self):
+ # destruction order matters to a certain degree: the fallbackSurface
+ # must outlive the rhi, the rhi must outlive all other resources.
+ # The resources need no special order when destroying.
+ del self.m_rp
+ self.m_rp = None
+ del self.m_ds
+ self.m_ds = None
+ del self.m_sc
+ self.m_sc = None
+ del self.m_rhi
+ self.m_rhi = None
+ if self.m_fallbackSurface:
+ del self.m_fallbackSurface
+ self.m_fallbackSurface = None
+
+ def graphicsApiName(self):
+ if self.m_graphicsApi == QRhi.Null:
+ return "Null (no output)"
+ if self.m_graphicsApi == QRhi.OpenGLES2:
+ return "OpenGL"
+ if self.m_graphicsApi == QRhi.Vulkan:
+ return "Vulkan"
+ if self.m_graphicsApi == QRhi.D3D11:
+ return "Direct3D 11"
+ if self.m_graphicsApi == QRhi.D3D12:
+ return "Direct3D 12"
+ if self.m_graphicsApi == QRhi.Metal:
+ return "Metal"
+ return ""
+
+ def customInit(self):
+ pass
+
+ def customRender(self):
+ pass
+
+ def exposeEvent(self, e):
+ # initialize and start rendering when the window becomes usable
+ # for graphics purposes
+ is_exposed = self.isExposed()
+ if is_exposed and not self.m_initialized:
+ self.init()
+ self.resizeSwapChain()
+ self.m_initialized = True
+
+ surfaceSize = self.m_sc.surfacePixelSize() if self.m_hasSwapChain else QSize()
+
+ # stop pushing frames when not exposed (or size is 0)
+ if ((not is_exposed or (self.m_hasSwapChain and surfaceSize.isEmpty()))
+ and self.m_initialized and not self.m_notExposed):
+ self.m_notExposed = True
+
+ # Continue when exposed again and the surface has a valid size. Note
+ # that surfaceSize can be (0, 0) even though size() reports a valid
+ # one, hence trusting surfacePixelSize() and not QWindow.
+ if is_exposed and self.m_initialized and self.m_notExposed and not surfaceSize.isEmpty():
+ self.m_notExposed = False
+ self.m_newlyExposed = True
+
+ # always render a frame on exposeEvent() (when exposed) in order to
+ # update immediately on window resize.
+ if is_exposed and not surfaceSize.isEmpty():
+ self.render()
+
+ def event(self, e):
+ if e.type() == QEvent.UpdateRequest:
+ self.render()
+ elif e.type() == QEvent.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:
+ self.releaseSwapChain()
+
+ return super().event(e)
+
+ def init(self):
+ if self.m_graphicsApi == QRhi.Null:
+ params = QRhiNullInitParams()
+ self.m_rhi = QRhi.create(QRhi.Null, params)
+
+ if self.m_graphicsApi == QRhi.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:
+ 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:
+ 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:
+ params = QRhiMetalInitParams()
+ self.m_rhi.reset(QRhi.create(QRhi.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,
+ QSize(), 1,
+ QRhiRenderBuffer.UsedWithSwapChainOnly)
+ self.m_sc.setWindow(self)
+ self.m_sc.setDepthStencil(self.m_ds)
+ self.m_rp = self.m_sc.newCompatibleRenderPassDescriptor()
+ self.m_sc.setRenderPassDescriptor(self.m_rp)
+ self.customInit()
+
+ def resizeSwapChain(self):
+ self.m_hasSwapChain = self.m_sc.createOrResize() # also handles self.m_ds
+ outputSize = self.m_sc.currentPixelSize()
+ self.m_viewProjection = self.m_rhi.clipSpaceCorrMatrix()
+ r = float(outputSize.width()) / float(outputSize.height())
+ self.m_viewProjection.perspective(45.0, r, 0.01, 1000.0)
+ self.m_viewProjection.translate(0, 0, -4)
+
+ def releaseSwapChain(self):
+ if self.m_hasSwapChain:
+ self.m_hasSwapChain = False
+ self.m_sc.destroy()
+
+ def render(self):
+ if not self.m_hasSwapChain or self.m_notExposed:
+ return
+
+ # If the window got resized or newly exposed, resize the swapchain.
+ # (the newly-exposed case is not actually required by some platforms,
+ # but is here for robustness and portability)
+ #
+ # This (exposeEvent + the logic here) is the only safe way to perform
+ # resize handling. Note the usage of the RHI's surfacePixelSize(), and
+ # never QWindow::size(). (the two may or may not be the same under the
+ # hood, # depending on the backend and platform)
+ if self.m_sc.currentPixelSize() != self.m_sc.surfacePixelSize() or self.m_newlyExposed:
+ self.resizeSwapChain()
+ if not self.m_hasSwapChain:
+ return
+ self.m_newlyExposed = False
+
+ result = self.m_rhi.beginFrame(self.m_sc)
+ if result == QRhi.FrameOpSwapChainOutOfDate:
+ self.resizeSwapChain()
+ if not self.m_hasSwapChain:
+ return
+ result = self.m_rhi.beginFrame(self.m_sc)
+
+ if result != QRhi.FrameOpSuccess:
+ qWarning(f"beginFrame failed with {result}, will retry")
+ self.requestUpdate()
+ return
+
+ self.customRender()
+
+ self.m_rhi.endFrame(self.m_sc)
+
+ # Always request the next frame via requestUpdate(). On some platforms
+ # this is backed by a platform-specific solution, e.g. CVDisplayLink
+ # on macOS, which is potentially more efficient than a timer,
+ # queued metacalls, etc.
+ self.requestUpdate()
+
+
+class HelloWindow(RhiWindow):
+
+ def __init__(self, graphicsApi):
+ super().__init__(graphicsApi)
+ self.m_vbuf = None
+ self.m_ubuf = None
+ self.m_texture = None
+ self.m_sampler = None
+ self.m_colorTriSrb = None
+ self.m_colorPipeline = None
+ self.m_fullscreenQuadSrb = None
+ self.m_fullscreenQuadPipeline = None
+ self.m_initialUpdates = None
+
+ self.m_rotation = 0
+ self.m_opacity = 1
+ self.m_opacityDir = -1
+
+ def ensureFullscreenTexture(self, pixelSize, u):
+ if self.m_texture and self.m_texture.pixelSize() == pixelSize:
+ return
+
+ if not self.m_texture:
+ self.m_texture = self.m_rhi.newTexture(QRhiTexture.RGBA8, pixelSize)
+ else:
+ self.m_texture.setPixelSize(pixelSize)
+ self.m_texture.create()
+ image = QImage(pixelSize, QImage.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.drawRoundedRect(QRectF(QPointF(20, 20), pixelSize - QSize(40, 40)),
+ 16, 16)
+ painter.setPen(Qt.black)
+ font = QFont()
+ font.setPixelSize(0.05 * min(pixelSize.width(), pixelSize.height()))
+ painter.setFont(font)
+ name = self.graphicsApiName()
+ t = (f"Rendering with QRhi to a resizable QWindow.\nThe 3D API is {name}."
+ "\nUse the command-line options to choose a different API.")
+ painter.drawText(QRectF(QPointF(60, 60), pixelSize - QSize(120, 120)), 0, t)
+
+ if self.m_rhi.isYUpInNDC():
+ image = image.mirrored()
+
+ u.uploadTexture(self.m_texture, image)
+
+ def customInit(self):
+ self.m_initialUpdates = self.m_rhi.nextResourceUpdateBatch()
+
+ vertex_size = 4 * VERTEX_DATA.size
+ self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Immutable, QRhiBuffer.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.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.create()
+
+ self.m_colorTriSrb = self.m_rhi.newShaderResourceBindings()
+ visibility = (QRhiShaderResourceBinding.VertexStage
+ | QRhiShaderResourceBinding.FragmentStage)
+ bindings = [
+ QRhiShaderResourceBinding.uniformBuffer(0, visibility, self.m_ubuf)
+ ]
+ self.m_colorTriSrb.setBindings(bindings)
+ self.m_colorTriSrb.create()
+
+ self.m_colorPipeline = self.m_rhi.newGraphicsPipeline()
+ # Enable depth testing; not quite needed for a simple triangle, but we
+ # have a depth-stencil buffer so why not.
+ self.m_colorPipeline.setDepthTest(True)
+ self.m_colorPipeline.setDepthWrite(True)
+ # Blend factors default to One, OneOneMinusSrcAlpha, which is convenient.
+ premulAlphaBlend = QRhiGraphicsPipeline.TargetBlend()
+ premulAlphaBlend.enable = True
+ self.m_colorPipeline.setTargetBlends([premulAlphaBlend])
+ stages = [
+ QRhiShaderStage(QRhiShaderStage.Vertex, getShader(":/color.vert.qsb")),
+ QRhiShaderStage(QRhiShaderStage.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)
+ inputLayout.setAttributes(attributes)
+ self.m_colorPipeline.setVertexInputLayout(inputLayout)
+ self.m_colorPipeline.setShaderResourceBindings(self.m_colorTriSrb)
+ self.m_colorPipeline.setRenderPassDescriptor(self.m_rp)
+ self.m_colorPipeline.create()
+
+ self.m_fullscreenQuadSrb = self.m_rhi.newShaderResourceBindings()
+ bindings = [
+ QRhiShaderResourceBinding.sampledTexture(0, QRhiShaderResourceBinding.FragmentStage,
+ self.m_texture, self.m_sampler)
+ ]
+ self.m_fullscreenQuadSrb.setBindings(bindings)
+ self.m_fullscreenQuadSrb.create()
+
+ self.m_fullscreenQuadPipeline = self.m_rhi.newGraphicsPipeline()
+ stages = [
+ QRhiShaderStage(QRhiShaderStage.Vertex, getShader(":/quad.vert.qsb")),
+ QRhiShaderStage(QRhiShaderStage.Fragment, getShader(":/quad.frag.qsb"))
+ ]
+ self.m_fullscreenQuadPipeline.setShaderStages(stages)
+ layout = QRhiVertexInputLayout()
+ self.m_fullscreenQuadPipeline.setVertexInputLayout(layout)
+ self.m_fullscreenQuadPipeline.setShaderResourceBindings(self.m_fullscreenQuadSrb)
+ self.m_fullscreenQuadPipeline.setRenderPassDescriptor(self.m_rp)
+ self.m_fullscreenQuadPipeline.create()
+
+ def customRender(self):
+ resourceUpdates = self.m_rhi.nextResourceUpdateBatch()
+
+ if self.m_initialUpdates:
+ resourceUpdates.merge(self.m_initialUpdates)
+ self.m_initialUpdates = None
+
+ self.m_rotation += 1.0
+ modelViewProjection = self.m_viewProjection
+ modelViewProjection.rotate(self.m_rotation, 0, 1, 0)
+ projection = numpy.array(modelViewProjection.data(),
+ dtype=numpy.float32)
+ resourceUpdates.updateDynamicBuffer(self.m_ubuf, 0, 64,
+ projection.tobytes())
+
+ self.m_opacity += self.m_opacityDir * 0.005
+ if self.m_opacity < 0.0 or self.m_opacity > 1.0:
+ self.m_opacityDir *= -1
+ self.m_opacity = max(0.0, min(1.0, self.m_opacity))
+
+ opacity = numpy.array([self.m_opacity], dtype=numpy.float32)
+ resourceUpdates.updateDynamicBuffer(self.m_ubuf, 64, 4,
+ opacity.tobytes())
+
+ cb = self.m_sc.currentFrameCommandBuffer()
+ outputSizeInPixels = self.m_sc.currentPixelSize()
+
+ # (re)create the texture with a size matching the output surface size,
+ # when necessary.
+ self.ensureFullscreenTexture(outputSizeInPixels, resourceUpdates)
+
+ cv = QRhiDepthStencilClearValue(1.0, 0)
+ cb.beginPass(self.m_sc.currentFrameRenderTarget(), Qt.black,
+ cv, resourceUpdates)
+
+ cb.setGraphicsPipeline(self.m_fullscreenQuadPipeline)
+ viewport = QRhiViewport(0, 0, outputSizeInPixels.width(),
+ outputSizeInPixels.height())
+ cb.setViewport(viewport)
+ cb.setShaderResources()
+ cb.draw(3)
+
+ cb.setGraphicsPipeline(self.m_colorPipeline)
+ cb.setShaderResources()
+ vbufBinding = (self.m_vbuf, 0)
+ cb.setVertexInput(0, [vbufBinding])
+ cb.draw(3)
+ cb.endPass()
diff --git a/examples/gui/rhiwindow/rhiwindow.pyproject b/examples/gui/rhiwindow/rhiwindow.pyproject
new file mode 100644
index 000000000..a807b49bf
--- /dev/null
+++ b/examples/gui/rhiwindow/rhiwindow.pyproject
@@ -0,0 +1,5 @@
+{
+ "files": ["main.py", "rhiwindow.py", "rhiwindow.qrc",
+ "shaders/color.frag", "shaders/color.vert",
+ "shaders/quad.frag", "shaders/quad.vert"]
+}
diff --git a/examples/gui/rhiwindow/rhiwindow.qrc b/examples/gui/rhiwindow/rhiwindow.qrc
new file mode 100644
index 000000000..1009ec5dd
--- /dev/null
+++ b/examples/gui/rhiwindow/rhiwindow.qrc
@@ -0,0 +1,8 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file alias="color.vert.qsb">shaders/prebuilt/color.vert.qsb</file>
+ <file alias="color.frag.qsb">shaders/prebuilt/color.frag.qsb</file>
+ <file alias="quad.vert.qsb">shaders/prebuilt/quad.vert.qsb</file>
+ <file alias="quad.frag.qsb">shaders/prebuilt/quad.frag.qsb</file>
+</qresource>
+</RCC>
diff --git a/examples/gui/rhiwindow/shaders/color.frag b/examples/gui/rhiwindow/shaders/color.frag
new file mode 100644
index 000000000..6e0a3bc91
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/color.frag
@@ -0,0 +1,15 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ float opacity;
+};
+
+void main()
+{
+ fragColor = vec4(v_color * opacity, opacity);
+}
diff --git a/examples/gui/rhiwindow/shaders/color.vert b/examples/gui/rhiwindow/shaders/color.vert
new file mode 100644
index 000000000..70852ab86
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/color.vert
@@ -0,0 +1,17 @@
+#version 440
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ float opacity;
+};
+
+void main()
+{
+ v_color = color;
+ gl_Position = mvp * position;
+}
diff --git a/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb b/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb
new file mode 100644
index 000000000..b4db470e5
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb
Binary files differ
diff --git a/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb b/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb
new file mode 100644
index 000000000..ab046b77f
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb
Binary files differ
diff --git a/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb b/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb
new file mode 100644
index 000000000..c2ea3cf25
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb
Binary files differ
diff --git a/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb b/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb
new file mode 100644
index 000000000..f0b64f750
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb
Binary files differ
diff --git a/examples/gui/rhiwindow/shaders/quad.frag b/examples/gui/rhiwindow/shaders/quad.frag
new file mode 100644
index 000000000..65882a429
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/quad.frag
@@ -0,0 +1,11 @@
+#version 440
+
+layout(location = 0) in vec2 v_uv;
+layout(location = 0) out vec4 fragColor;
+layout(binding = 0) uniform sampler2D tex;
+
+void main()
+{
+ vec4 c = texture(tex, v_uv);
+ fragColor = vec4(c.rgb * c.a, c.a);
+}
diff --git a/examples/gui/rhiwindow/shaders/quad.vert b/examples/gui/rhiwindow/shaders/quad.vert
new file mode 100644
index 000000000..359896d08
--- /dev/null
+++ b/examples/gui/rhiwindow/shaders/quad.vert
@@ -0,0 +1,10 @@
+#version 440
+
+layout (location = 0) out vec2 v_uv;
+
+void main()
+{
+ // https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/
+ v_uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+ gl_Position = vec4(v_uv * 2.0 - 1.0, 0.0, 1.0);
+}
diff --git a/examples/installer_test/hello.py b/examples/installer_test/hello.py
index a9819477b..3921b5d51 100644
--- a/examples/installer_test/hello.py
+++ b/examples/installer_test/hello.py
@@ -31,7 +31,7 @@ class MyWidget(QWidget):
super().__init__()
self.hello = ["Hallo Welt", "你好,世界", "Hei maailma",
- "Hola Mundo", "Привет мир"]
+ "Hola Mundo", "Привет мир"]
self.button = QPushButton("Click me!")
self.text = QLabel(f"Hello World auto_quit={auto_quit}")
diff --git a/examples/location/mapviewer/mapviewer.qml b/examples/location/mapviewer/MapViewer/Main.qml
index daa28d763..f4ae7ea04 100644
--- a/examples/location/mapviewer/mapviewer.qml
+++ b/examples/location/mapviewer/MapViewer/Main.qml
@@ -5,9 +5,7 @@ import QtQuick
import QtQuick.Controls
import QtLocation
import QtPositioning
-import "map"
-import "menus"
-import "helper.js" as Helper
+import MapViewer
ApplicationWindow {
id: appWindow
diff --git a/examples/location/mapviewer/forms/Geocode.qml b/examples/location/mapviewer/MapViewer/forms/Geocode.qml
index 885357dd3..885357dd3 100644
--- a/examples/location/mapviewer/forms/Geocode.qml
+++ b/examples/location/mapviewer/MapViewer/forms/Geocode.qml
diff --git a/examples/location/mapviewer/forms/GeocodeForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/GeocodeForm.ui.qml
index cb56370ea..cb56370ea 100644
--- a/examples/location/mapviewer/forms/GeocodeForm.ui.qml
+++ b/examples/location/mapviewer/MapViewer/forms/GeocodeForm.ui.qml
diff --git a/examples/location/mapviewer/forms/Locale.qml b/examples/location/mapviewer/MapViewer/forms/Locale.qml
index 9ba7dd7f0..9ba7dd7f0 100644
--- a/examples/location/mapviewer/forms/Locale.qml
+++ b/examples/location/mapviewer/MapViewer/forms/Locale.qml
diff --git a/examples/location/mapviewer/forms/LocaleForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/LocaleForm.ui.qml
index 9e1ec1807..9e1ec1807 100644
--- a/examples/location/mapviewer/forms/LocaleForm.ui.qml
+++ b/examples/location/mapviewer/MapViewer/forms/LocaleForm.ui.qml
diff --git a/examples/location/mapviewer/forms/Message.qml b/examples/location/mapviewer/MapViewer/forms/Message.qml
index 583bc2dda..583bc2dda 100644
--- a/examples/location/mapviewer/forms/Message.qml
+++ b/examples/location/mapviewer/MapViewer/forms/Message.qml
diff --git a/examples/location/mapviewer/forms/MessageForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml
index 426c72757..426c72757 100644
--- a/examples/location/mapviewer/forms/MessageForm.ui.qml
+++ b/examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml
diff --git a/examples/location/mapviewer/forms/ReverseGeocode.qml b/examples/location/mapviewer/MapViewer/forms/ReverseGeocode.qml
index 31122a2e9..31122a2e9 100644
--- a/examples/location/mapviewer/forms/ReverseGeocode.qml
+++ b/examples/location/mapviewer/MapViewer/forms/ReverseGeocode.qml
diff --git a/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/ReverseGeocodeForm.ui.qml
index 1d937ee90..1d937ee90 100644
--- a/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml
+++ b/examples/location/mapviewer/MapViewer/forms/ReverseGeocodeForm.ui.qml
diff --git a/examples/location/mapviewer/forms/RouteAddress.qml b/examples/location/mapviewer/MapViewer/forms/RouteAddress.qml
index 3676c1374..3676c1374 100644
--- a/examples/location/mapviewer/forms/RouteAddress.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteAddress.qml
diff --git a/examples/location/mapviewer/forms/RouteAddressForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/RouteAddressForm.ui.qml
index ee9227013..ee9227013 100644
--- a/examples/location/mapviewer/forms/RouteAddressForm.ui.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteAddressForm.ui.qml
diff --git a/examples/location/mapviewer/forms/RouteCoordinate.qml b/examples/location/mapviewer/MapViewer/forms/RouteCoordinate.qml
index 003556c51..003556c51 100644
--- a/examples/location/mapviewer/forms/RouteCoordinate.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteCoordinate.qml
diff --git a/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml b/examples/location/mapviewer/MapViewer/forms/RouteCoordinateForm.ui.qml
index 88ff94dc1..88ff94dc1 100644
--- a/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteCoordinateForm.ui.qml
diff --git a/examples/location/mapviewer/forms/RouteList.qml b/examples/location/mapviewer/MapViewer/forms/RouteList.qml
index 8dbda7c01..8dbda7c01 100644
--- a/examples/location/mapviewer/forms/RouteList.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteList.qml
diff --git a/examples/location/mapviewer/forms/RouteListDelegate.qml b/examples/location/mapviewer/MapViewer/forms/RouteListDelegate.qml
index 680318ac3..680318ac3 100644
--- a/examples/location/mapviewer/forms/RouteListDelegate.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteListDelegate.qml
diff --git a/examples/location/mapviewer/forms/RouteListHeader.qml b/examples/location/mapviewer/MapViewer/forms/RouteListHeader.qml
index 4f8308091..4f8308091 100644
--- a/examples/location/mapviewer/forms/RouteListHeader.qml
+++ b/examples/location/mapviewer/MapViewer/forms/RouteListHeader.qml
diff --git a/examples/location/mapviewer/helper.js b/examples/location/mapviewer/MapViewer/helper.js
index a42040518..a42040518 100644
--- a/examples/location/mapviewer/helper.js
+++ b/examples/location/mapviewer/MapViewer/helper.js
diff --git a/examples/location/mapviewer/map/MapComponent.qml b/examples/location/mapviewer/MapViewer/map/MapComponent.qml
index 987455287..987455287 100644
--- a/examples/location/mapviewer/map/MapComponent.qml
+++ b/examples/location/mapviewer/MapViewer/map/MapComponent.qml
diff --git a/examples/location/mapviewer/map/MapSliders.qml b/examples/location/mapviewer/MapViewer/map/MapSliders.qml
index d9c8381b0..d9c8381b0 100644
--- a/examples/location/mapviewer/map/MapSliders.qml
+++ b/examples/location/mapviewer/MapViewer/map/MapSliders.qml
diff --git a/examples/location/mapviewer/map/Marker.qml b/examples/location/mapviewer/MapViewer/map/Marker.qml
index c7494cf57..c7494cf57 100644
--- a/examples/location/mapviewer/map/Marker.qml
+++ b/examples/location/mapviewer/MapViewer/map/Marker.qml
diff --git a/examples/location/mapviewer/map/MiniMap.qml b/examples/location/mapviewer/MapViewer/map/MiniMap.qml
index f8fc51547..f8fc51547 100644
--- a/examples/location/mapviewer/map/MiniMap.qml
+++ b/examples/location/mapviewer/MapViewer/map/MiniMap.qml
diff --git a/examples/location/mapviewer/menus/ItemPopupMenu.qml b/examples/location/mapviewer/MapViewer/menus/ItemPopupMenu.qml
index d559aca6c..d559aca6c 100644
--- a/examples/location/mapviewer/menus/ItemPopupMenu.qml
+++ b/examples/location/mapviewer/MapViewer/menus/ItemPopupMenu.qml
diff --git a/examples/location/mapviewer/menus/MainMenu.qml b/examples/location/mapviewer/MapViewer/menus/MainMenu.qml
index 3523b5c1a..3523b5c1a 100644
--- a/examples/location/mapviewer/menus/MainMenu.qml
+++ b/examples/location/mapviewer/MapViewer/menus/MainMenu.qml
diff --git a/examples/location/mapviewer/menus/MapPopupMenu.qml b/examples/location/mapviewer/MapViewer/menus/MapPopupMenu.qml
index 335788df8..335788df8 100644
--- a/examples/location/mapviewer/menus/MapPopupMenu.qml
+++ b/examples/location/mapviewer/MapViewer/menus/MapPopupMenu.qml
diff --git a/examples/location/mapviewer/menus/MarkerPopupMenu.qml b/examples/location/mapviewer/MapViewer/menus/MarkerPopupMenu.qml
index 338f23859..338f23859 100644
--- a/examples/location/mapviewer/menus/MarkerPopupMenu.qml
+++ b/examples/location/mapviewer/MapViewer/menus/MarkerPopupMenu.qml
diff --git a/examples/location/mapviewer/MapViewer/qmldir b/examples/location/mapviewer/MapViewer/qmldir
new file mode 100644
index 000000000..359ca02af
--- /dev/null
+++ b/examples/location/mapviewer/MapViewer/qmldir
@@ -0,0 +1,27 @@
+module MapViewer
+typeinfo mapviewer.qmltypes
+Main 1.0 Main.qml
+Helper 1.0 helper.js
+MapComponent 1.0 map/MapComponent.qml
+MapSliders 1.0 map/MapSliders.qml
+Marker 1.0 map/Marker.qml
+MiniMap 1.0 map/MiniMap.qml
+ItemPopupMenu 1.0 menus/ItemPopupMenu.qml
+MainMenu 1.0 menus/MainMenu.qml
+MapPopupMenu 1.0 menus/MapPopupMenu.qml
+MarkerPopupMenu 1.0 menus/MarkerPopupMenu.qml
+Geocode 1.0 forms/Geocode.qml
+GeocodeForm 1.0 forms/GeocodeForm.ui.qml
+Message 1.0 forms/Message.qml
+MessageForm 1.0 forms/MessageForm.ui.qml
+ReverseGeocode 1.0 forms/ReverseGeocode.qml
+ReverseGeocodeForm 1.0 forms/ReverseGeocodeForm.ui.qml
+RouteCoordinate 1.0 forms/RouteCoordinate.qml
+Locale 1.0 forms/Locale.qml
+LocaleForm 1.0 forms/LocaleForm.ui.qml
+RouteAddress 1.0 forms/RouteAddress.qml
+RouteAddressForm 1.0 forms/RouteAddressForm.ui.qml
+RouteCoordinateForm 1.0 forms/RouteCoordinateForm.ui.qml
+RouteList 1.0 forms/RouteList.qml
+RouteListDelegate 1.0 forms/RouteListDelegate.qml
+RouteListHeader 1.0 forms/RouteListHeader.qml
diff --git a/examples/location/mapviewer/resources/marker.png b/examples/location/mapviewer/MapViewer/resources/marker.png
index 2116dfdf5..2116dfdf5 100644
--- a/examples/location/mapviewer/resources/marker.png
+++ b/examples/location/mapviewer/MapViewer/resources/marker.png
Binary files differ
diff --git a/examples/location/mapviewer/resources/marker_blue.png b/examples/location/mapviewer/MapViewer/resources/marker_blue.png
index 70f0c2538..70f0c2538 100644
--- a/examples/location/mapviewer/resources/marker_blue.png
+++ b/examples/location/mapviewer/MapViewer/resources/marker_blue.png
Binary files differ
diff --git a/examples/location/mapviewer/resources/scale.png b/examples/location/mapviewer/MapViewer/resources/scale.png
index c4f08122a..c4f08122a 100644
--- a/examples/location/mapviewer/resources/scale.png
+++ b/examples/location/mapviewer/MapViewer/resources/scale.png
Binary files differ
diff --git a/examples/location/mapviewer/resources/scale_end.png b/examples/location/mapviewer/MapViewer/resources/scale_end.png
index 94510b125..94510b125 100644
--- a/examples/location/mapviewer/resources/scale_end.png
+++ b/examples/location/mapviewer/MapViewer/resources/scale_end.png
Binary files differ
diff --git a/examples/location/mapviewer/doc/mapviewer.rst b/examples/location/mapviewer/doc/mapviewer.rst
index d30544991..418e2e77e 100644
--- a/examples/location/mapviewer/doc/mapviewer.rst
+++ b/examples/location/mapviewer/doc/mapviewer.rst
@@ -1,6 +1,8 @@
Map Viewer Example
==================
+.. tags:: Android
+
The Map Viewer example shows how to display and interact with a map,
search for an address, and find driving directions.
diff --git a/examples/location/mapviewer/main.py b/examples/location/mapviewer/main.py
index cd0d13776..24ae1623f 100644
--- a/examples/location/mapviewer/main.py
+++ b/examples/location/mapviewer/main.py
@@ -10,7 +10,7 @@ from pathlib import Path
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtGui import QGuiApplication
from PySide6.QtNetwork import QSslSocket
-from PySide6.QtCore import QCoreApplication, QMetaObject, QUrl, Q_ARG
+from PySide6.QtCore import QCoreApplication, QMetaObject, Q_ARG
HELP = """Usage:
plugin.<parameter_name> <parameter_value> - Sets parameter = value for plugin"""
@@ -59,9 +59,8 @@ if __name__ == "__main__":
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("supportsSsl",
QSslSocket.supportsSsl())
- engine.addImportPath(":/imports")
- qml_file = Path(__file__).parent / "mapviewer.qml"
- engine.load(QUrl.fromLocalFile(qml_file))
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("MapViewer", "Main")
engine.quit.connect(QCoreApplication.quit)
items = engine.rootObjects()
diff --git a/examples/location/mapviewer/mapviewer.pyproject b/examples/location/mapviewer/mapviewer.pyproject
index 868657d8b..8e2cadd2b 100644
--- a/examples/location/mapviewer/mapviewer.pyproject
+++ b/examples/location/mapviewer/mapviewer.pyproject
@@ -1,27 +1,33 @@
{
"files": ["main.py",
- "forms/Geocode.qml",
- "forms/GeocodeForm.ui.qml",
- "forms/Locale.qml",
- "forms/LocaleForm.ui.qml",
- "forms/Message.qml",
- "forms/MessageForm.ui.qml",
- "forms/ReverseGeocode.qml",
- "forms/ReverseGeocodeForm.ui.qml",
- "forms/RouteAddress.qml",
- "forms/RouteAddressForm.ui.qml",
- "forms/RouteCoordinate.qml",
- "forms/RouteCoordinateForm.ui.qml",
- "forms/RouteList.qml",
- "forms/RouteListDelegate.qml",
- "forms/RouteListHeader.qml",
- "map/MapComponent.qml",
- "map/MapSliders.qml",
- "map/Marker.qml",
- "map/MiniMap.qml",
- "mapviewer.qml",
- "menus/ItemPopupMenu.qml",
- "menus/MainMenu.qml",
- "menus/MapPopupMenu.qml",
- "menus/MarkerPopupMenu.qml"]
+ "MapViewer/forms/Geocode.qml",
+ "MapViewer/forms/GeocodeForm.ui.qml",
+ "MapViewer/forms/Locale.qml",
+ "MapViewer/forms/LocaleForm.ui.qml",
+ "MapViewer/forms/Message.qml",
+ "MapViewer/forms/MessageForm.ui.qml",
+ "MapViewer/forms/ReverseGeocode.qml",
+ "MapViewer/forms/ReverseGeocodeForm.ui.qml",
+ "MapViewer/forms/RouteAddress.qml",
+ "MapViewer/forms/RouteAddressForm.ui.qml",
+ "MapViewer/forms/RouteCoordinate.qml",
+ "MapViewer/forms/RouteCoordinateForm.ui.qml",
+ "MapViewer/forms/RouteList.qml",
+ "MapViewer/forms/RouteListDelegate.qml",
+ "MapViewer/forms/RouteListHeader.qml",
+ "MapViewer/map/MapComponent.qml",
+ "MapViewer/map/MapSliders.qml",
+ "MapViewer/map/Marker.qml",
+ "MapViewer/map/MiniMap.qml",
+ "MapViewer/menus/ItemPopupMenu.qml",
+ "MapViewer/menus/MainMenu.qml",
+ "MapViewer/menus/MapPopupMenu.qml",
+ "MapViewer/menus/MarkerPopupMenu.qml",
+ "MapViewer/resources/marker_blue.png",
+ "MapViewer/resources/marker.png",
+ "MapViewer/resources/scale_end.png",
+ "MapViewer/resources/scale.png",
+ "MapViewer/helper.js",
+ "MapViewer/Main.qml",
+ "MapViewer/qmldir"]
}
diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py
index e0634e751..06d52f68a 100644
--- a/examples/multimedia/audiooutput/audiooutput.py
+++ b/examples/multimedia/audiooutput/audiooutput.py
@@ -40,24 +40,23 @@ class Generator(QIODevice):
sample_size = fmt.bytesPerSample() * 8
if sample_size == 8:
if fmt.sampleFormat() == QAudioFormat.UInt8:
- scaler = lambda x: ((1.0 + x) / 2 * 255)
+ scaler = lambda x: ((1.0 + x) / 2 * 255) # noqa: E731
pack_format = 'B'
elif fmt.sampleFormat() == QAudioFormat.Int16:
- scaler = lambda x: x * 127
+ 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:
- scaler = lambda x: (1.0 + x) / 2 * 65535
+ scaler = lambda x: (1.0 + x) / 2 * 65535 # noqa: E731
pack_format = '<H' if little_endian else '>H'
elif fmt.sampleFormat() == QAudioFormat.Int16:
- scaler = lambda x: x * 32767
+ scaler = lambda x: x * 32767 # noqa: E731
pack_format = '<h' if little_endian else '>h'
- assert(pack_format != '')
+ assert pack_format != ''
channel_bytes = fmt.bytesPerSample()
- sample_bytes = fmt.channelCount() * channel_bytes
length = (fmt.sampleRate() * fmt.channelCount() * channel_bytes) * durationUs // 100000
@@ -133,16 +132,14 @@ class AudioTest(QMainWindow):
layout.addWidget(self.m_modeButton)
- self.m_suspendResumeButton = QPushButton(
- clicked=self.toggle_suspend_resume)
+ self.m_suspendResumeButton = QPushButton(clicked=self.toggle_suspend_resume)
self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
layout.addWidget(self.m_suspendResumeButton)
volume_box = QHBoxLayout()
volume_label = QLabel("Volume:")
- self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100,
- singleStep=10)
+ self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10)
self.m_volumeSlider.valueChanged.connect(self.volume_changed)
volume_box.addWidget(volume_label)
@@ -167,8 +164,8 @@ class AudioTest(QMainWindow):
qWarning("Default format not supported - trying to use nearest")
self.m_format = info.nearestFormat(self.m_format)
- self.m_generator = Generator(self.m_format,
- self.DURATION_SECONDS * 1000000, self.TONE_SAMPLE_RATE_HZ, self)
+ self.m_generator = Generator(self.m_format, self.DURATION_SECONDS * 1000000,
+ self.TONE_SAMPLE_RATE_HZ, self)
self.create_audio_output()
@@ -180,11 +177,18 @@ class AudioTest(QMainWindow):
self.m_audioSink.start(self.m_generator)
self.m_volumeSlider.setValue(self.m_audioSink.volume() * 100)
- @Slot(int)
- def device_changed(self, index):
+ def closeEvent(self, e):
+ self.stop()
+ e.accept()
+
+ def stop(self):
self.m_pullTimer.stop()
self.m_generator.stop()
self.m_audioSink.stop()
+
+ @Slot(int)
+ def device_changed(self, index):
+ self.stop()
self.m_device = self.m_deviceBox.itemData(index)
self.create_audio_output()
diff --git a/examples/multimedia/audiooutput/doc/audiooutput.rst b/examples/multimedia/audiooutput/doc/audiooutput.rst
index bbd71c5d8..fac7e33e1 100644
--- a/examples/multimedia/audiooutput/doc/audiooutput.rst
+++ b/examples/multimedia/audiooutput/doc/audiooutput.rst
@@ -1,6 +1,8 @@
Audio Output Example
====================
+.. tags:: Android
+
Audio Output demonstrates the basic use cases of QAudioOutput.
This example provides a tone generator to supply continuous audio playback. The
diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py
index 5b476af9b..a78beb584 100644
--- a/examples/multimedia/audiosource/audiosource.py
+++ b/examples/multimedia/audiosource/audiosource.py
@@ -12,27 +12,21 @@ Note: This Python example is not fully complete as compared to its C++ counterpa
Only the push mode works at the moment. For the pull mode to work, the class
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
+from PySide6.QtCore import QByteArray, QMargins, Qt, Slot, qWarning
from PySide6.QtGui import QPainter, QPalette
-from PySide6.QtMultimedia import (
- QAudio,
- QAudioDevice,
- QAudioFormat,
- QAudioSource,
- QMediaDevices,
-)
-from PySide6.QtWidgets import (
- QApplication,
- QComboBox,
- QPushButton,
- QSlider,
- QVBoxLayout,
- QWidget,
-)
+from PySide6.QtMultimedia import QAudio, QAudioDevice, QAudioFormat, QAudioSource, QMediaDevices
+from PySide6.QtWidgets import (QApplication, QComboBox, QPushButton, QSlider, QVBoxLayout,
+ QWidget, QLabel)
+
+is_android = os.environ.get('ANDROID_ARGUMENT')
+
+if is_android or sys.platform == "darwin":
+ from PySide6.QtCore import QMicrophonePermission
class AudioInfo:
@@ -95,6 +89,28 @@ class InputTest(QWidget):
super().__init__()
self.m_devices = QMediaDevices(self)
self.m_pullMode = False
+ self.initialize()
+
+ @Slot()
+ def initialize(self):
+ if is_android or sys.platform == "darwin":
+ is_nuitka = "__compiled__" in globals()
+ if not is_nuitka and sys.platform == "darwin":
+ print("This example does not work on macOS when Python is run in interpreted mode."
+ "For this example to work on macOS, package the example using pyside6-deploy"
+ "For more information, read `Notes for Developer` in the documentation")
+ sys.exit(0)
+ permission = QMicrophonePermission()
+ permission_status = qApp.checkPermission(permission) # noqa: F821
+ if permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(permission, self, self.initialize) # noqa: F821
+ return
+ if permission_status == Qt.PermissionStatus.Denied:
+ qWarning("Microphone permission is not granted!")
+ self.initializeErrorWindow()
+ return
+ elif permission_status == Qt.PermissionStatus.Granted:
+ print("[AudioSource] Microphone permission granted")
self.initialize_window()
self.initialize_audio(QMediaDevices.defaultAudioInput())
@@ -132,6 +148,13 @@ class InputTest(QWidget):
self.m_suspend_resume_button.clicked.connect(self.toggle_suspend)
self.layout.addWidget(self.m_suspend_resume_button)
+ def initializeErrorWindow(self):
+ self.layout = QVBoxLayout(self)
+ error_label = QLabel(self.tr("Microphone permission is not granted!"))
+ error_label.setWordWrap(True)
+ error_label.setAlignment(Qt.AlignCenter)
+ self.layout.addWidget(error_label)
+
def initialize_audio(self, device_info: QAudioDevice):
format = QAudioFormat()
format.setSampleRate(8000)
diff --git a/examples/multimedia/audiosource/doc/audiosource.rst b/examples/multimedia/audiosource/doc/audiosource.rst
index b1dcd61a8..3a247c503 100644
--- a/examples/multimedia/audiosource/doc/audiosource.rst
+++ b/examples/multimedia/audiosource/doc/audiosource.rst
@@ -1,6 +1,8 @@
Audio Source Example
====================
+.. tags:: Android
+
A Python application that demonstrates the analogous example in C++
`Audio Source Example <https://doc-snapshots.qt.io/qt6-dev/qtmultimedia-multimedia-audiosource-example.html>`_
diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py
index 95d33243e..fa379c807 100644
--- a/examples/multimedia/camera/camera.py
+++ b/examples/multimedia/camera/camera.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
+import sys
from pathlib import Path
from PySide6.QtMultimedia import (QAudioInput, QCamera, QCameraDevice,
@@ -10,12 +11,19 @@ from PySide6.QtMultimedia import (QAudioInput, QCamera, QCameraDevice,
QMediaRecorder)
from PySide6.QtWidgets import QDialog, QMainWindow, QMessageBox
from PySide6.QtGui import QAction, QActionGroup, QIcon, QImage, QPixmap
-from PySide6.QtCore import QDateTime, QDir, QTimer, Qt, Slot
+from PySide6.QtCore import QDateTime, QDir, QTimer, Qt, Slot, qWarning
-from ui_camera import Ui_Camera
from metadatadialog import MetaDataDialog
from imagesettings import ImageSettings
-from videosettings import VideoSettings
+from videosettings import VideoSettings, is_android
+
+if is_android or sys.platform == "darwin":
+ from PySide6.QtCore import QMicrophonePermission, QCameraPermission
+
+if is_android:
+ from ui_camera_mobile import Ui_Camera
+else:
+ from ui_camera import Ui_Camera
class Camera(QMainWindow):
@@ -23,13 +31,10 @@ class Camera(QMainWindow):
super().__init__()
self._video_devices_group = None
-
self.m_devices = QMediaDevices()
self.m_imageCapture = None
self.m_captureSession = QMediaCaptureSession()
self.m_camera = None
- self.m_audioInput = QAudioInput()
- self.m_captureSession.setAudioInput(self.m_audioInput)
self.m_mediaRecorder = None
self.m_isCapturingImage = False
@@ -42,7 +47,8 @@ class Camera(QMainWindow):
self._ui.setupUi(self)
image = Path(__file__).parent / "shutter.svg"
self._ui.takeImageButton.setIcon(QIcon(os.fspath(image)))
- self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt)
+ if not is_android:
+ self._ui.actionAbout_Qt.triggered.connect(qApp.aboutQt) # noqa: F821
# disable all buttons by default
self.updateCameraActive(False)
@@ -53,6 +59,47 @@ class Camera(QMainWindow):
self._ui.metaDataButton.setEnabled(False)
# try to actually initialize camera & mic
+ self.initialize()
+
+ @Slot()
+ def initialize(self):
+ if is_android or sys.platform == "darwin":
+ is_nuitka = "__compiled__" in globals()
+ if not is_nuitka and sys.platform == "darwin":
+ print("This example does not work on macOS when Python is run in interpreted mode."
+ "For this example to work on macOS, package the example using pyside6-deploy"
+ "For more information, read `Notes for Developer` in the documentation")
+ sys.exit(0)
+
+ # camera
+ cam_permission = QCameraPermission()
+ cam_permission_status = qApp.checkPermission(cam_permission) # noqa: F821
+ if cam_permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(cam_permission, self, self.initialize) # noqa: F821
+ return
+ if cam_permission_status == Qt.PermissionStatus.Denied:
+ qWarning("Camera permission is not granted!")
+ return
+ elif cam_permission_status == Qt.PermissionStatus.Granted:
+ print("[AudioSource] Camera permission granted")
+
+ # microphone
+ microphone_permission = QMicrophonePermission()
+ microphone_permission_status = qApp.checkPermission(microphone_permission) # noqa: F821
+ if microphone_permission_status == Qt.PermissionStatus.Undetermined:
+ qApp.requestPermission(microphone_permission, self, self.initialize) # noqa: F821
+ return
+ if microphone_permission_status == Qt.PermissionStatus.Denied:
+ qWarning("Microphone permission is not granted!")
+ self.initializeErrorWindow()
+ return
+ elif microphone_permission_status == Qt.PermissionStatus.Granted:
+ print("[AudioSource] Microphone permission granted")
+
+ self.m_audioInput = QAudioInput()
+ self.m_captureSession.setAudioInput(self.m_audioInput)
+
+ # Camera devices
self._video_devices_group = QActionGroup(self)
self._video_devices_group.setExclusive(True)
diff --git a/examples/multimedia/camera/camera.pyproject b/examples/multimedia/camera/camera.pyproject
index 9b4171d9a..9067b1dfa 100644
--- a/examples/multimedia/camera/camera.pyproject
+++ b/examples/multimedia/camera/camera.pyproject
@@ -2,9 +2,11 @@
"files": ["main.py",
"camera.py",
"camera.ui",
+ "camera_mobile.ui",
"imagesettings.py",
"imagesettings.ui",
"metadatadialog.py",
"videosettings.py",
- "videosettings.ui"]
+ "videosettings.ui",
+ "videosettings_mobile.ui"]
}
diff --git a/examples/multimedia/camera/camera_mobile.ui b/examples/multimedia/camera/camera_mobile.ui
new file mode 100644
index 000000000..7f269b17b
--- /dev/null
+++ b/examples/multimedia/camera/camera_mobile.ui
@@ -0,0 +1,504 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Camera</class>
+ <widget class="QMainWindow" name="Camera">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>668</width>
+ <height>429</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Camera</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="1" colspan="2">
+ <widget class="QTabWidget" name="captureWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Image</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="4" column="0">
+ <widget class="QSlider" name="exposureCompensation">
+ <property name="minimum">
+ <number>-4</number>
+ </property>
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>2</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Exposure Compensation:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="takeImageButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Capture Photo</string>
+ </property>
+ <property name="icon">
+ <iconset>
+ <normaloff>:/images/shutter.svg</normaloff>:/images/shutter.svg</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Video</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPushButton" name="recordButton">
+ <property name="text">
+ <string>Record</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pauseButton">
+ <property name="text">
+ <string>Pause</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stopButton">
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="muteButton">
+ <property name="text">
+ <string>Mute</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="metaDataButton">
+ <property name="text">
+ <string>Set metadata</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </active>
+ <inactive>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </inactive>
+ <disabled>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>145</red>
+ <green>145</green>
+ <blue>145</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </disabled>
+ </palette>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="viewfinderPage">
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <widget class="QVideoWidget" name="viewfinder" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="previewPage">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lastImagePreviewLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>668</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionStartCamera"/>
+ <addaction name="actionStopCamera"/>
+ <addaction name="separator"/>
+ <addaction name="actionSettings"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuDevices">
+ <property name="title">
+ <string>Devices</string>
+ </property>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuDevices"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionExit">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </action>
+ <action name="actionStartCamera">
+ <property name="text">
+ <string>Start Camera</string>
+ </property>
+ </action>
+ <action name="actionStopCamera">
+ <property name="text">
+ <string>Stop Camera</string>
+ </property>
+ </action>
+ <action name="actionSettings">
+ <property name="text">
+ <string>Change Settings</string>
+ </property>
+ </action>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QVideoWidget</class>
+ <extends>QWidget</extends>
+ <header>qvideowidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>recordButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>record()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>149</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>61</x>
+ <y>238</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>stopButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>stop()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>225</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>140</x>
+ <y>236</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pauseButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>pause()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>187</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>234</x>
+ <y>237</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionExit</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>154</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>takeImageButton</sender>
+ <signal>clicked()</signal>
+ <receiver>Camera</receiver>
+ <slot>takeImage()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>625</x>
+ <y>132</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>603</x>
+ <y>169</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>muteButton</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Camera</receiver>
+ <slot>setMuted(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>647</x>
+ <y>377</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>5</x>
+ <y>280</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>exposureCompensation</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>Camera</receiver>
+ <slot>setExposureCompensation(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>559</x>
+ <y>367</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>665</x>
+ <y>365</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionSettings</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>configureCaptureSettings()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionStartCamera</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>startCamera()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionStopCamera</sender>
+ <signal>triggered()</signal>
+ <receiver>Camera</receiver>
+ <slot>stopCamera()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>333</x>
+ <y>210</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+ <slots>
+ <slot>record()</slot>
+ <slot>pause()</slot>
+ <slot>stop()</slot>
+ <slot>enablePreview(bool)</slot>
+ <slot>configureCaptureSettings()</slot>
+ <slot>takeImage()</slot>
+ <slot>startCamera()</slot>
+ <slot>toggleLock()</slot>
+ <slot>setMuted(bool)</slot>
+ <slot>stopCamera()</slot>
+ <slot>setExposureCompensation(int)</slot>
+ </slots>
+</ui>
diff --git a/examples/multimedia/camera/doc/camera.rst b/examples/multimedia/camera/doc/camera.rst
index 8ef9f6700..7fc75a387 100644
--- a/examples/multimedia/camera/doc/camera.rst
+++ b/examples/multimedia/camera/doc/camera.rst
@@ -1,6 +1,8 @@
Camera Example
===============
+.. tags:: Android
+
The Camera Example shows how to use the API to capture a still image or video.
The Camera Example demonstrates how you can use Qt Multimedia to implement some
diff --git a/examples/multimedia/camera/ui_camera.py b/examples/multimedia/camera/ui_camera.py
index 92e115c77..690cf3352 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -47,7 +47,7 @@ class Ui_Camera(object):
self.tab_2.setObjectName(u"tab_2")
self.gridLayout = QGridLayout(self.tab_2)
self.gridLayout.setObjectName(u"gridLayout")
- self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer_2 = QSpacerItem(20, 161, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.gridLayout.addItem(self.verticalSpacer_2, 3, 0, 1, 1)
@@ -92,7 +92,7 @@ class Ui_Camera(object):
self.gridLayout_2.addWidget(self.stopButton, 2, 0, 1, 1)
- self.verticalSpacer = QSpacerItem(20, 76, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 76, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.gridLayout_2.addItem(self.verticalSpacer, 3, 0, 1, 1)
@@ -114,7 +114,7 @@ class Ui_Camera(object):
self.stackedWidget = QStackedWidget(self.centralwidget)
self.stackedWidget.setObjectName(u"stackedWidget")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
@@ -147,7 +147,7 @@ class Ui_Camera(object):
self.gridLayout_4.setObjectName(u"gridLayout_4")
self.lastImagePreviewLabel = QLabel(self.previewPage)
self.lastImagePreviewLabel.setObjectName(u"lastImagePreviewLabel")
- sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth())
diff --git a/examples/multimedia/camera/ui_camera_mobile.py b/examples/multimedia/camera/ui_camera_mobile.py
new file mode 100644
index 000000000..5cdd81f1e
--- /dev/null
+++ b/examples/multimedia/camera/ui_camera_mobile.py
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'camera_mobile.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
+ QCursor, QFont, QFontDatabase, QGradient,
+ QIcon, QImage, QKeySequence, QLinearGradient,
+ QPainter, QPalette, QPixmap, QRadialGradient,
+ QTransform)
+from PySide6.QtMultimediaWidgets import QVideoWidget
+from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QHBoxLayout,
+ QLabel, QMainWindow, QMenu, QMenuBar,
+ QPushButton, QSizePolicy, QSlider, QSpacerItem,
+ QStackedWidget, QStatusBar, QTabWidget, QVBoxLayout,
+ QWidget)
+
+class Ui_Camera(object):
+ def setupUi(self, Camera):
+ if not Camera.objectName():
+ Camera.setObjectName(u"Camera")
+ Camera.resize(668, 429)
+ self.actionExit = QAction(Camera)
+ self.actionExit.setObjectName(u"actionExit")
+ self.actionStartCamera = QAction(Camera)
+ self.actionStartCamera.setObjectName(u"actionStartCamera")
+ self.actionStopCamera = QAction(Camera)
+ self.actionStopCamera.setObjectName(u"actionStopCamera")
+ self.actionSettings = QAction(Camera)
+ self.actionSettings.setObjectName(u"actionSettings")
+ self.centralwidget = QWidget(Camera)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.gridLayout_3 = QGridLayout(self.centralwidget)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.captureWidget = QTabWidget(self.centralwidget)
+ self.captureWidget.setObjectName(u"captureWidget")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.captureWidget.sizePolicy().hasHeightForWidth())
+ self.captureWidget.setSizePolicy(sizePolicy)
+ self.tab_2 = QWidget()
+ self.tab_2.setObjectName(u"tab_2")
+ self.gridLayout = QGridLayout(self.tab_2)
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.exposureCompensation = QSlider(self.tab_2)
+ self.exposureCompensation.setObjectName(u"exposureCompensation")
+ self.exposureCompensation.setMinimum(-4)
+ self.exposureCompensation.setMaximum(4)
+ self.exposureCompensation.setPageStep(2)
+ self.exposureCompensation.setOrientation(Qt.Horizontal)
+ self.exposureCompensation.setTickPosition(QSlider.TicksAbove)
+
+ self.gridLayout.addWidget(self.exposureCompensation, 4, 0, 1, 1)
+
+ self.label = QLabel(self.tab_2)
+ self.label.setObjectName(u"label")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
+ self.label.setSizePolicy(sizePolicy1)
+
+ self.gridLayout.addWidget(self.label, 3, 0, 1, 1)
+
+ self.takeImageButton = QPushButton(self.tab_2)
+ self.takeImageButton.setObjectName(u"takeImageButton")
+ self.takeImageButton.setEnabled(False)
+ icon = QIcon()
+ icon.addFile(u":/images/shutter.svg", QSize(), QIcon.Normal, QIcon.Off)
+ self.takeImageButton.setIcon(icon)
+
+ self.gridLayout.addWidget(self.takeImageButton, 0, 0, 1, 1)
+
+ self.captureWidget.addTab(self.tab_2, "")
+ self.tab = QWidget()
+ self.tab.setObjectName(u"tab")
+ self.gridLayout_2 = QGridLayout(self.tab)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.recordButton = QPushButton(self.tab)
+ self.recordButton.setObjectName(u"recordButton")
+
+ self.verticalLayout.addWidget(self.recordButton)
+
+ self.pauseButton = QPushButton(self.tab)
+ self.pauseButton.setObjectName(u"pauseButton")
+
+ self.verticalLayout.addWidget(self.pauseButton)
+
+ self.stopButton = QPushButton(self.tab)
+ self.stopButton.setObjectName(u"stopButton")
+
+ self.verticalLayout.addWidget(self.stopButton)
+
+
+ self.horizontalLayout.addLayout(self.verticalLayout)
+
+ self.verticalLayout_2 = QVBoxLayout()
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalSpacer = QSpacerItem(20, 10, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.verticalLayout_2.addItem(self.verticalSpacer)
+
+ self.muteButton = QPushButton(self.tab)
+ self.muteButton.setObjectName(u"muteButton")
+ self.muteButton.setCheckable(True)
+
+ self.verticalLayout_2.addWidget(self.muteButton)
+
+ self.metaDataButton = QPushButton(self.tab)
+ self.metaDataButton.setObjectName(u"metaDataButton")
+ self.metaDataButton.setCheckable(True)
+
+ self.verticalLayout_2.addWidget(self.metaDataButton)
+
+
+ self.horizontalLayout.addLayout(self.verticalLayout_2)
+
+
+ self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 1)
+
+ self.captureWidget.addTab(self.tab, "")
+
+ self.gridLayout_3.addWidget(self.captureWidget, 1, 1, 1, 2)
+
+ self.stackedWidget = QStackedWidget(self.centralwidget)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy2.setHorizontalStretch(1)
+ sizePolicy2.setVerticalStretch(0)
+ sizePolicy2.setHeightForWidth(self.stackedWidget.sizePolicy().hasHeightForWidth())
+ self.stackedWidget.setSizePolicy(sizePolicy2)
+ palette = QPalette()
+ brush = QBrush(QColor(255, 255, 255, 255))
+ brush.setStyle(Qt.SolidPattern)
+ palette.setBrush(QPalette.Active, QPalette.Base, brush)
+ brush1 = QBrush(QColor(145, 145, 145, 255))
+ brush1.setStyle(Qt.SolidPattern)
+ palette.setBrush(QPalette.Active, QPalette.Window, brush1)
+ palette.setBrush(QPalette.Inactive, QPalette.Base, brush)
+ palette.setBrush(QPalette.Inactive, QPalette.Window, brush1)
+ palette.setBrush(QPalette.Disabled, QPalette.Base, brush1)
+ palette.setBrush(QPalette.Disabled, QPalette.Window, brush1)
+ self.stackedWidget.setPalette(palette)
+ self.viewfinderPage = QWidget()
+ self.viewfinderPage.setObjectName(u"viewfinderPage")
+ self.gridLayout_5 = QGridLayout(self.viewfinderPage)
+ self.gridLayout_5.setObjectName(u"gridLayout_5")
+ self.viewfinder = QVideoWidget(self.viewfinderPage)
+ self.viewfinder.setObjectName(u"viewfinder")
+ sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ sizePolicy3.setHorizontalStretch(0)
+ sizePolicy3.setVerticalStretch(0)
+ sizePolicy3.setHeightForWidth(self.viewfinder.sizePolicy().hasHeightForWidth())
+ self.viewfinder.setSizePolicy(sizePolicy3)
+
+ self.gridLayout_5.addWidget(self.viewfinder, 0, 0, 1, 1)
+
+ self.stackedWidget.addWidget(self.viewfinderPage)
+ self.previewPage = QWidget()
+ self.previewPage.setObjectName(u"previewPage")
+ self.gridLayout_4 = QGridLayout(self.previewPage)
+ self.gridLayout_4.setObjectName(u"gridLayout_4")
+ self.lastImagePreviewLabel = QLabel(self.previewPage)
+ self.lastImagePreviewLabel.setObjectName(u"lastImagePreviewLabel")
+ sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding)
+ sizePolicy4.setHorizontalStretch(0)
+ sizePolicy4.setVerticalStretch(0)
+ sizePolicy4.setHeightForWidth(self.lastImagePreviewLabel.sizePolicy().hasHeightForWidth())
+ self.lastImagePreviewLabel.setSizePolicy(sizePolicy4)
+ self.lastImagePreviewLabel.setFrameShape(QFrame.Box)
+
+ self.gridLayout_4.addWidget(self.lastImagePreviewLabel, 0, 0, 1, 1)
+
+ self.stackedWidget.addWidget(self.previewPage)
+
+ self.gridLayout_3.addWidget(self.stackedWidget, 0, 2, 1, 1)
+
+ Camera.setCentralWidget(self.centralwidget)
+ self.menubar = QMenuBar(Camera)
+ self.menubar.setObjectName(u"menubar")
+ self.menubar.setGeometry(QRect(0, 0, 668, 22))
+ self.menuFile = QMenu(self.menubar)
+ self.menuFile.setObjectName(u"menuFile")
+ self.menuDevices = QMenu(self.menubar)
+ self.menuDevices.setObjectName(u"menuDevices")
+ Camera.setMenuBar(self.menubar)
+ self.statusbar = QStatusBar(Camera)
+ self.statusbar.setObjectName(u"statusbar")
+ Camera.setStatusBar(self.statusbar)
+
+ self.menubar.addAction(self.menuFile.menuAction())
+ self.menubar.addAction(self.menuDevices.menuAction())
+ self.menuFile.addAction(self.actionStartCamera)
+ self.menuFile.addAction(self.actionStopCamera)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionSettings)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionExit)
+
+ self.retranslateUi(Camera)
+ self.recordButton.clicked.connect(Camera.record)
+ self.stopButton.clicked.connect(Camera.stop)
+ self.pauseButton.clicked.connect(Camera.pause)
+ self.actionExit.triggered.connect(Camera.close)
+ self.takeImageButton.clicked.connect(Camera.takeImage)
+ self.muteButton.toggled.connect(Camera.setMuted)
+ self.exposureCompensation.valueChanged.connect(Camera.setExposureCompensation)
+ self.actionSettings.triggered.connect(Camera.configureCaptureSettings)
+ self.actionStartCamera.triggered.connect(Camera.startCamera)
+ self.actionStopCamera.triggered.connect(Camera.stopCamera)
+
+ self.captureWidget.setCurrentIndex(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(Camera)
+ # setupUi
+
+ def retranslateUi(self, Camera):
+ Camera.setWindowTitle(QCoreApplication.translate("Camera", u"Camera", None))
+ self.actionExit.setText(QCoreApplication.translate("Camera", u"Close", None))
+ self.actionStartCamera.setText(QCoreApplication.translate("Camera", u"Start Camera", None))
+ self.actionStopCamera.setText(QCoreApplication.translate("Camera", u"Stop Camera", None))
+ self.actionSettings.setText(QCoreApplication.translate("Camera", u"Change Settings", None))
+ self.label.setText(QCoreApplication.translate("Camera", u"Exposure Compensation:", None))
+ self.takeImageButton.setText(QCoreApplication.translate("Camera", u"Capture Photo", None))
+ self.captureWidget.setTabText(self.captureWidget.indexOf(self.tab_2), QCoreApplication.translate("Camera", u"Image", None))
+ self.recordButton.setText(QCoreApplication.translate("Camera", u"Record", None))
+ self.pauseButton.setText(QCoreApplication.translate("Camera", u"Pause", None))
+ self.stopButton.setText(QCoreApplication.translate("Camera", u"Stop", None))
+ self.muteButton.setText(QCoreApplication.translate("Camera", u"Mute", None))
+ self.metaDataButton.setText(QCoreApplication.translate("Camera", u"Set metadata", None))
+ self.captureWidget.setTabText(self.captureWidget.indexOf(self.tab), QCoreApplication.translate("Camera", u"Video", None))
+ self.lastImagePreviewLabel.setText("")
+ self.menuFile.setTitle(QCoreApplication.translate("Camera", u"File", None))
+ self.menuDevices.setTitle(QCoreApplication.translate("Camera", u"Devices", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/ui_imagesettings.py b/examples/multimedia/camera/ui_imagesettings.py
index aa7505f8f..a3fba7789 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -65,7 +65,7 @@ class Ui_ImageSettingsUi(object):
self.gridLayout.addWidget(self.groupBox_2, 0, 0, 1, 1)
- self.verticalSpacer = QSpacerItem(20, 14, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 14, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.gridLayout.addItem(self.verticalSpacer, 1, 0, 1, 1)
diff --git a/examples/multimedia/camera/ui_videosettings.py b/examples/multimedia/camera/ui_videosettings.py
index c84c93d78..eec626f27 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -84,7 +84,7 @@ class Ui_VideoSettingsUi(object):
self.widget = QWidget(VideoSettingsUi)
self.widget.setObjectName(u"widget")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
@@ -151,7 +151,7 @@ class Ui_VideoSettingsUi(object):
self.gridLayout_3.addWidget(self.widget, 2, 0, 1, 1)
- self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.gridLayout_3.addItem(self.verticalSpacer, 3, 0, 1, 1)
diff --git a/examples/multimedia/camera/ui_videosettings_mobile.py b/examples/multimedia/camera/ui_videosettings_mobile.py
new file mode 100644
index 000000000..50fb8e081
--- /dev/null
+++ b/examples/multimedia/camera/ui_videosettings_mobile.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'videosettings_mobile.ui'
+##
+## Created by: Qt User Interface Compiler version 6.7.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QComboBox, QDialog,
+ QDialogButtonBox, QGridLayout, QGroupBox, QHBoxLayout,
+ QLabel, QSizePolicy, QSlider, QSpinBox,
+ QVBoxLayout, QWidget)
+
+class Ui_VideoSettingsUi(object):
+ def setupUi(self, VideoSettingsUi):
+ if not VideoSettingsUi.objectName():
+ VideoSettingsUi.setObjectName(u"VideoSettingsUi")
+ VideoSettingsUi.resize(329, 591)
+ self.gridLayout_3 = QGridLayout(VideoSettingsUi)
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.widget = QWidget(VideoSettingsUi)
+ self.widget.setObjectName(u"widget")
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
+ self.widget.setSizePolicy(sizePolicy)
+ self.verticalLayout_3 = QVBoxLayout(self.widget)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.groupBox_3 = QGroupBox(self.widget)
+ self.groupBox_3.setObjectName(u"groupBox_3")
+ self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.label_2 = QLabel(self.groupBox_3)
+ self.label_2.setObjectName(u"label_2")
+
+ self.verticalLayout_2.addWidget(self.label_2)
+
+ self.audioCodecBox = QComboBox(self.groupBox_3)
+ self.audioCodecBox.setObjectName(u"audioCodecBox")
+
+ self.verticalLayout_2.addWidget(self.audioCodecBox)
+
+ self.label_5 = QLabel(self.groupBox_3)
+ self.label_5.setObjectName(u"label_5")
+
+ self.verticalLayout_2.addWidget(self.label_5)
+
+ self.audioSampleRateBox = QSpinBox(self.groupBox_3)
+ self.audioSampleRateBox.setObjectName(u"audioSampleRateBox")
+
+ self.verticalLayout_2.addWidget(self.audioSampleRateBox)
+
+
+ self.verticalLayout_3.addWidget(self.groupBox_3)
+
+ self.groupBox = QGroupBox(self.widget)
+ self.groupBox.setObjectName(u"groupBox")
+ self.verticalLayout = QVBoxLayout(self.groupBox)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.label_3 = QLabel(self.groupBox)
+ self.label_3.setObjectName(u"label_3")
+
+ self.verticalLayout.addWidget(self.label_3)
+
+ self.qualitySlider = QSlider(self.groupBox)
+ self.qualitySlider.setObjectName(u"qualitySlider")
+ self.qualitySlider.setMaximum(4)
+ self.qualitySlider.setOrientation(Qt.Horizontal)
+
+ self.verticalLayout.addWidget(self.qualitySlider)
+
+ self.label_4 = QLabel(self.groupBox)
+ self.label_4.setObjectName(u"label_4")
+
+ self.verticalLayout.addWidget(self.label_4)
+
+ self.containerFormatBox = QComboBox(self.groupBox)
+ self.containerFormatBox.setObjectName(u"containerFormatBox")
+
+ self.verticalLayout.addWidget(self.containerFormatBox)
+
+
+ self.verticalLayout_3.addWidget(self.groupBox)
+
+
+ self.gridLayout_3.addWidget(self.widget, 2, 0, 1, 1)
+
+ self.groupBox_2 = QGroupBox(VideoSettingsUi)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.gridLayout_2 = QGridLayout(self.groupBox_2)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.label = QLabel(self.groupBox_2)
+ self.label.setObjectName(u"label")
+
+ self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1)
+
+ self.videoCodecBox = QComboBox(self.groupBox_2)
+ self.videoCodecBox.setObjectName(u"videoCodecBox")
+
+ self.gridLayout_2.addWidget(self.videoCodecBox, 6, 0, 1, 2)
+
+ self.label_8 = QLabel(self.groupBox_2)
+ self.label_8.setObjectName(u"label_8")
+
+ self.gridLayout_2.addWidget(self.label_8, 0, 0, 1, 2)
+
+ self.label_6 = QLabel(self.groupBox_2)
+ self.label_6.setObjectName(u"label_6")
+
+ self.gridLayout_2.addWidget(self.label_6, 5, 0, 1, 2)
+
+ self.videoFormatBox = QComboBox(self.groupBox_2)
+ self.videoFormatBox.setObjectName(u"videoFormatBox")
+
+ self.gridLayout_2.addWidget(self.videoFormatBox, 1, 0, 1, 2)
+
+ self.buttonBox = QDialogButtonBox(self.groupBox_2)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.gridLayout_2.addWidget(self.buttonBox, 7, 0, 1, 1)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.fpsSpinBox = QSpinBox(self.groupBox_2)
+ self.fpsSpinBox.setObjectName(u"fpsSpinBox")
+ self.fpsSpinBox.setMinimum(8)
+ self.fpsSpinBox.setMaximum(30)
+
+ self.horizontalLayout.addWidget(self.fpsSpinBox)
+
+ self.fpsSlider = QSlider(self.groupBox_2)
+ self.fpsSlider.setObjectName(u"fpsSlider")
+ self.fpsSlider.setOrientation(Qt.Horizontal)
+
+ self.horizontalLayout.addWidget(self.fpsSlider)
+
+
+ self.gridLayout_2.addLayout(self.horizontalLayout, 3, 0, 1, 1)
+
+
+ self.gridLayout_3.addWidget(self.groupBox_2, 3, 0, 1, 1)
+
+
+ self.retranslateUi(VideoSettingsUi)
+ self.buttonBox.accepted.connect(VideoSettingsUi.accept)
+ self.buttonBox.rejected.connect(VideoSettingsUi.reject)
+
+ QMetaObject.connectSlotsByName(VideoSettingsUi)
+ # setupUi
+
+ def retranslateUi(self, VideoSettingsUi):
+ VideoSettingsUi.setWindowTitle(QCoreApplication.translate("VideoSettingsUi", u"Video Settings", None))
+ self.groupBox_3.setTitle(QCoreApplication.translate("VideoSettingsUi", u"Audio", None))
+ self.label_2.setText(QCoreApplication.translate("VideoSettingsUi", u"Audio Codec:", None))
+ self.label_5.setText(QCoreApplication.translate("VideoSettingsUi", u"Sample Rate:", None))
+ self.label_3.setText(QCoreApplication.translate("VideoSettingsUi", u"Quality:", None))
+ self.label_4.setText(QCoreApplication.translate("VideoSettingsUi", u"File Format:", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("VideoSettingsUi", u"Video", None))
+ self.label.setText(QCoreApplication.translate("VideoSettingsUi", u"Frames per second:", None))
+ self.label_8.setText(QCoreApplication.translate("VideoSettingsUi", u"Camera Format:", None))
+ self.label_6.setText(QCoreApplication.translate("VideoSettingsUi", u"Video Codec:", None))
+ # retranslateUi
+
diff --git a/examples/multimedia/camera/videosettings.py b/examples/multimedia/camera/videosettings.py
index 5aab973b6..a88cb39ed 100644
--- a/examples/multimedia/camera/videosettings.py
+++ b/examples/multimedia/camera/videosettings.py
@@ -1,11 +1,17 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+import os
from PySide6.QtMultimedia import (QCameraFormat, QMediaFormat, QMediaRecorder,
QVideoFrameFormat)
from PySide6.QtWidgets import QDialog
-from ui_videosettings import Ui_VideoSettingsUi
+is_android = os.environ.get('ANDROID_ARGUMENT')
+
+if is_android:
+ from ui_videosettings_mobile import Ui_VideoSettingsUi
+else:
+ from ui_videosettings import Ui_VideoSettingsUi
def box_value(box):
diff --git a/examples/multimedia/camera/videosettings_mobile.ui b/examples/multimedia/camera/videosettings_mobile.ui
new file mode 100644
index 000000000..6584f07f9
--- /dev/null
+++ b/examples/multimedia/camera/videosettings_mobile.ui
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VideoSettingsUi</class>
+ <widget class="QDialog" name="VideoSettingsUi">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>329</width>
+ <height>591</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Video Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Audio</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Audio Codec:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="audioCodecBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Sample Rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="audioSampleRateBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Quality:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="qualitySlider">
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>File Format:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="containerFormatBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Video</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Frames per second:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0" colspan="2">
+ <widget class="QComboBox" name="videoCodecBox"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Camera Format:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Video Codec:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QComboBox" name="videoFormatBox"/>
+ </item>
+ <item row="7" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QSpinBox" name="fpsSpinBox">
+ <property name="minimum">
+ <number>8</number>
+ </property>
+ <property name="maximum">
+ <number>30</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="fpsSlider">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>VideoSettingsUi</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>322</x>
+ <y>272</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>44</x>
+ <y>230</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>VideoSettingsUi</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>405</x>
+ <y>262</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>364</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py
index 88be50cc3..d28f2887e 100644
--- a/examples/multimedia/player/player.py
+++ b/examples/multimedia/player/player.py
@@ -7,7 +7,7 @@ import sys
from PySide6.QtCore import QStandardPaths, Qt, Slot
from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
- QMainWindow, QSlider, QStyle, QToolBar)
+ QMainWindow, QSlider, QStyle, QToolBar)
from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat,
QMediaPlayer)
from PySide6.QtMultimediaWidgets import QVideoWidget
@@ -44,43 +44,43 @@ class MainWindow(QMainWindow):
self.addToolBar(tool_bar)
file_menu = self.menuBar().addMenu("&File")
- icon = QIcon.fromTheme("document-open")
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen)
open_action = QAction(icon, "&Open...", self,
shortcut=QKeySequence.Open, triggered=self.open)
file_menu.addAction(open_action)
tool_bar.addAction(open_action)
- icon = QIcon.fromTheme("application-exit")
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit)
exit_action = QAction(icon, "E&xit", self,
shortcut="Ctrl+Q", triggered=self.close)
file_menu.addAction(exit_action)
play_menu = self.menuBar().addMenu("&Play")
style = self.style()
- icon = QIcon.fromTheme("media-playback-start.png",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStart,
style.standardIcon(QStyle.SP_MediaPlay))
self._play_action = tool_bar.addAction(icon, "Play")
self._play_action.triggered.connect(self._player.play)
play_menu.addAction(self._play_action)
- icon = QIcon.fromTheme("media-skip-backward-symbolic.svg",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaSkipBackward,
style.standardIcon(QStyle.SP_MediaSkipBackward))
self._previous_action = tool_bar.addAction(icon, "Previous")
self._previous_action.triggered.connect(self.previous_clicked)
play_menu.addAction(self._previous_action)
- icon = QIcon.fromTheme("media-playback-pause.png",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackPause,
style.standardIcon(QStyle.SP_MediaPause))
self._pause_action = tool_bar.addAction(icon, "Pause")
self._pause_action.triggered.connect(self._player.pause)
play_menu.addAction(self._pause_action)
- icon = QIcon.fromTheme("media-skip-forward-symbolic.svg",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaSkipForward,
style.standardIcon(QStyle.SP_MediaSkipForward))
self._next_action = tool_bar.addAction(icon, "Next")
self._next_action.triggered.connect(self.next_clicked)
play_menu.addAction(self._next_action)
- icon = QIcon.fromTheme("media-playback-stop.png",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackStop,
style.standardIcon(QStyle.SP_MediaStop))
self._stop_action = tool_bar.addAction(icon, "Stop")
self._stop_action.triggered.connect(self._ensure_stopped)
@@ -99,8 +99,9 @@ class MainWindow(QMainWindow):
self._volume_slider.valueChanged.connect(self._audio_output.setVolume)
tool_bar.addWidget(self._volume_slider)
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)
about_menu = self.menuBar().addMenu("&About")
- about_qt_act = QAction("About &Qt", self, triggered=qApp.aboutQt)
+ about_qt_act = QAction(icon, "About &Qt", self, triggered=qApp.aboutQt) # noqa: F821
about_menu.addAction(about_qt_act)
self._video_widget = QVideoWidget()
@@ -168,8 +169,7 @@ class MainWindow(QMainWindow):
@Slot("QMediaPlayer::PlaybackState")
def update_buttons(self, state):
media_count = len(self._playlist)
- self._play_action.setEnabled(media_count > 0
- and state != QMediaPlayer.PlayingState)
+ self._play_action.setEnabled(media_count > 0 and state != QMediaPlayer.PlayingState)
self._pause_action.setEnabled(state == QMediaPlayer.PlayingState)
self._stop_action.setEnabled(state != QMediaPlayer.StoppedState)
self._previous_action.setEnabled(self._player.position() > 0)
diff --git a/examples/multimedia/screencapture/doc/screencapture.rst b/examples/multimedia/screencapture/doc/screencapture.rst
index 69b1ec166..116d7773b 100644
--- a/examples/multimedia/screencapture/doc/screencapture.rst
+++ b/examples/multimedia/screencapture/doc/screencapture.rst
@@ -1,35 +1,42 @@
Screen Capture Example
======================
-The Screen Capture Example demonstrates how to capture a screen or window using
-QScreenCapture. It shows a list of screens and and displays a live preview of
-the selected item using a ``QMediaCaptureSession`` and a ``QVideoWidget``.
-There is a button to start and stop the capturing.
+Screen Capture demonstrates how to capture a screen or window using
+``QScreenCapture`` and ``QWindowCapture``. The example shows a list of screens
+and windows and displays a live preview of the selected item using a
+``QMediaCaptureSession`` and a ``QVideoWidget``. Capturing can be started and
+stopped with a ``QPushButton``.
Application Structure
+++++++++++++++++++++
-The example consists of two custom classes. The UI and all screen capture
+The example consists of three custom classes. The UI and all screen capture
functionality is implemented in the class ``ScreenCapturePreview``. The classes
-``ScreenListModel`` serves as model behind the ``QListView``. The main function
-creates a ``ScreenCapturePreview`` object, which in turn creates an instance of
-``QScreenCapture``, ``QMediaCaptureSession`` and ``QVideoWidget`` in addition
-to all the UI widgets.
-
-The list model is populated with the return values of ``QGuiApplication.screens()``.
-
-When a list item is selected it is connected to the ``QScreenCapture`` object
-with ``QScreenCapture.setScreen()``. The ``QScreenCapture`` object is connected
-to the ``QMediaCaptureSession`` object with
-``QMediaCaptureSession.setScreenCapture()``, which in turn is connected to the
-``QVideoWidget`` object with ``QMediaCaptureSession.setVideoOutput()`` Thus the
-screen capture output is previewed in the video widget on the right hand side
-of the UI.
-
-The start/stop button calls ``QScreenCapture.start()`` and ``QScreenCapture.stop()``.
-
-A ``QMessageBox`` pops up if the ``QScreenCapture.errorOccurred()`` signal is emitted.
-
-.. image:: screencapture.webp
+``ScreenListModel`` and ``WindowListModel`` only serve as models behind the two
+``QListView`` widgets. The main function creates a ``ScreenCapturePreview``
+object, which in turn creates instances of ``QScreenCapture`` and
+``QWindowCapture``, and a ``QMediaCaptureSession`` and ``QVideoWidget``, in
+addition to all the UI widgets.
+
+The screen and window models are populated with the return values of
+``QGuiApplication.screens()`` and ``QWindowCapture.capturableWindows()``,
+respectively.
+
+When a list item is selected, it is connected to the ``QScreenCapture`` object
+with ``QScreenCapture.setScreen()``, or to the ``QWindowCapture`` object with
+``QWindowCapture.setWindow().`` The capture object is connected to the
+``QMediaCaptureSession`` object with
+``QMediaCaptureSession.setScreenCapture()`` and
+``QMediaCaptureSession.setWindowCapture()``, respectively. The capture session
+in turn is connected to the ``QVideoWidget`` object with
+``QMediaCaptureSession.setVideoOutput()``. Thus, the capture output is
+previewed in the video widget on the right hand side of the UI.
+
+The start/stop button calls ``QScreenCapture.start()`` and ``QScreenCapture.stop()``,
+or ``QWindowCapture.start()`` and ``QWindowCapture.stop()``.
+
+A QMessageBox pops up if an ``errorOccurred`` signal is emitted.
+
+.. image. screencapture.webp
:width: 600
:alt: screen capture example
diff --git a/examples/multimedia/screencapture/doc/screencapture.webp b/examples/multimedia/screencapture/doc/screencapture.webp
index 2723b1d53..58ad36c7f 100644
--- a/examples/multimedia/screencapture/doc/screencapture.webp
+++ b/examples/multimedia/screencapture/doc/screencapture.webp
Binary files differ
diff --git a/examples/multimedia/screencapture/main.py b/examples/multimedia/screencapture/main.py
index dce30186c..f445bac03 100644
--- a/examples/multimedia/screencapture/main.py
+++ b/examples/multimedia/screencapture/main.py
@@ -5,6 +5,7 @@
import sys
+from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
from screencapturepreview import ScreenCapturePreview
@@ -12,6 +13,8 @@ from screencapturepreview import ScreenCapturePreview
if __name__ == "__main__":
app = QApplication(sys.argv)
+ QCoreApplication.setApplicationName("screencapture")
+ QCoreApplication.setOrganizationName("QtProject")
screen_capture_preview = ScreenCapturePreview()
screen_capture_preview.show()
sys.exit(app.exec())
diff --git a/examples/multimedia/screencapture/screencapture.pyproject b/examples/multimedia/screencapture/screencapture.pyproject
index 9b18fa093..dfec6c901 100644
--- a/examples/multimedia/screencapture/screencapture.pyproject
+++ b/examples/multimedia/screencapture/screencapture.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["main.py", "screencapturepreview.py", "screenlistmodel.py"]
+ "files": ["main.py", "screencapturepreview.py", "screenlistmodel.py", "windowlistmodel.py"]
}
diff --git a/examples/multimedia/screencapture/screencapturepreview.py b/examples/multimedia/screencapture/screencapturepreview.py
index 72fe77c64..c7e0c596a 100644
--- a/examples/multimedia/screencapture/screencapturepreview.py
+++ b/examples/multimedia/screencapture/screencapturepreview.py
@@ -1,14 +1,23 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from enum import Enum, auto
+
from PySide6.QtMultimediaWidgets import QVideoWidget
-from PySide6.QtMultimedia import QScreenCapture, QMediaCaptureSession
+from PySide6.QtMultimedia import (QCapturableWindow, QMediaCaptureSession,
+ QScreenCapture, QWindowCapture)
from PySide6.QtWidgets import (QGridLayout, QLabel, QListView,
QMessageBox, QPushButton, QWidget)
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtCore import Slot
+from PySide6.QtGui import QAction, QGuiApplication
+from PySide6.QtCore import QItemSelection, Qt, Slot
from screenlistmodel import ScreenListModel
+from windowlistmodel import WindowListModel
+
+
+class SourceType(Enum):
+ Screen = auto()
+ Window = auto()
class ScreenCapturePreview(QWidget):
@@ -16,58 +25,138 @@ class ScreenCapturePreview(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
+ self._source = SourceType.Screen
+
self._screen_capture = QScreenCapture(self)
self._media_capture_session = QMediaCaptureSession(self)
self._video_widget = QVideoWidget(self)
self._screen_list_view = QListView(self)
- self._screen_label = QLabel("Double-click screen to capture:", self)
- self._video_widget_label = QLabel("QScreenCapture output:", self)
- self._start_stop_button = QPushButton("Stop screencapture", self)
+ self._screen_label = QLabel("Select screen to capture:", self)
+ self._video_widget_label = QLabel("Capture output:", self)
+ self._start_stop_button = QPushButton(self)
+ self._status_label = QLabel(self)
self._screen_list_model = ScreenListModel(self)
# Setup QScreenCapture with initial source:
- self.set_screen(QGuiApplication.primaryScreen())
+ self.setScreen(QGuiApplication.primaryScreen())
self._screen_capture.start()
self._media_capture_session.setScreenCapture(self._screen_capture)
self._media_capture_session.setVideoOutput(self._video_widget)
self._screen_list_view.setModel(self._screen_list_model)
+ self._window_list_view = QListView(self)
+ self._window_capture = QWindowCapture(self)
+ self._media_capture_session.setWindowCapture(self._window_capture)
+ self._window_label = QLabel("Select window to capture:", self)
+
+ self._window_list_model = WindowListModel(self)
+ self._window_list_view.setModel(self._window_list_model)
+ 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)
+
grid_layout = QGridLayout(self)
grid_layout.addWidget(self._screen_label, 0, 0)
grid_layout.addWidget(self._screen_list_view, 1, 0)
- grid_layout.addWidget(self._start_stop_button, 2, 0)
+ grid_layout.addWidget(self._start_stop_button, 4, 0)
grid_layout.addWidget(self._video_widget_label, 0, 1)
- grid_layout.addWidget(self._video_widget, 1, 1, 2, 1)
+ grid_layout.addWidget(self._video_widget, 1, 1, 4, 1)
+ grid_layout.addWidget(self._window_label, 2, 0)
+ grid_layout.addWidget(self._window_list_view, 3, 0)
+ grid_layout.addWidget(self._status_label, 5, 0, 1, 2)
grid_layout.setColumnStretch(1, 1)
grid_layout.setRowStretch(1, 1)
grid_layout.setColumnMinimumWidth(0, 400)
grid_layout.setColumnMinimumWidth(1, 400)
+ grid_layout.setRowMinimumHeight(3, 1)
+
+ selection_model = self._screen_list_view.selectionModel()
+ selection_model.selectionChanged.connect(self.on_current_screen_selection_changed)
+ selection_model = self._window_list_view.selectionModel()
+ selection_model.selectionChanged.connect(self.on_current_window_selection_changed)
- self._screen_list_view.activated.connect(self.on_screen_selection_changed)
self._start_stop_button.clicked.connect(self.on_start_stop_button_clicked)
- self._screen_capture.errorOccurred.connect(self.on_screen_capture_error_occured)
+ self._screen_capture.errorOccurred.connect(self.on_screen_capture_error_occured,
+ Qt.QueuedConnection)
+ self._window_capture.errorOccurred.connect(self.on_window_capture_error_occured,
+ Qt.QueuedConnection)
+ self.update_active(SourceType.Screen, True)
+
+ @Slot(QItemSelection)
+ def on_current_screen_selection_changed(self, selection):
+ self.clear_error_string()
+ indexes = selection.indexes()
+ if indexes:
+ self._screen_capture.setScreen(self._screen_list_model.screen(indexes[0]))
+ self.update_active(SourceType.Screen, self.is_active())
+ self._window_list_view.clearSelection()
+ else:
+ self._screen_capture.setScreen(None)
+
+ @Slot(QItemSelection)
+ def on_current_window_selection_changed(self, selection):
+ self.clear_error_string()
+ indexes = selection.indexes()
+ if indexes:
+ window = self._window_list_model.window(indexes[0])
+ if not window.isValid():
+ m = "The window is no longer valid. Update the list of windows?"
+ answer = QMessageBox.question(self, "Invalid window", m)
+ if answer == QMessageBox.Yes:
+ self.update_active(SourceType.Window, False)
+ self._window_list_view.clearSelection()
+ self._window_list_model.populate()
+ return
+ self._window_capture.setWindow(window)
+ self.update_active(SourceType.Window, self.is_active())
+ self._screen_list_view.clearSelection()
+ else:
+ self._window_capture.setWindow(QCapturableWindow())
- def set_screen(self, screen):
- self._screen_capture.setScreen(screen)
- self.setWindowTitle(f"Capturing {screen.name()}")
+ @Slot(QWindowCapture.Error, str)
+ def on_window_capture_error_occured(self, error, error_string):
+ self.set_error_string("QWindowCapture: Error occurred " + error_string)
- @Slot()
- def on_screen_selection_changed(self, index):
- self.set_screen(self._screen_list_model.screen(index))
+ @Slot(QScreenCapture.Error, str)
+ def on_screen_capture_error_occured(self, error, error_string):
+ self.set_error_string("QScreenCapture: Error occurred " + error_string)
- @Slot()
- def on_screen_capture_error_occured(self, error, errorString):
- QMessageBox.warning(self, "QScreenCapture: Error occurred",
- errorString)
+ def set_error_string(self, t):
+ self._status_label.setStyleSheet("background-color: rgb(255, 0, 0);")
+ self._status_label.setText(t)
+
+ def clear_error_string(self):
+ self._status_label.clear()
+ self._status_label.setStyleSheet("")
@Slot()
def on_start_stop_button_clicked(self):
- if self._screen_capture.isActive():
- self._screen_capture.stop()
- self._start_stop_button.setText("Start screencapture")
- else:
- self._screen_capture.start()
- self._start_stop_button.setText("Stop screencapture")
+ self.clear_error_string()
+ self.update_active(self._source_type, not self.is_active())
+
+ def update_start_stop_button_text(self):
+ active = self.is_active()
+ if self._source_type == SourceType.Window:
+ m = "Stop window capture" if active else "Start window capture"
+ self._start_stop_button.setText(m)
+ elif self._source_type == SourceType.Screen:
+ m = "Stop screen capture" if active else "Start screen capture"
+ self._start_stop_button.setText(m)
+
+ def update_active(self, source_type, active):
+ self._source_type = source_type
+ self._screen_capture.setActive(active and source_type == SourceType.Screen)
+ self._window_capture.setActive(active and source_type == SourceType.Window)
+
+ self.update_start_stop_button_text()
+
+ def is_active(self):
+ if self._source_type == SourceType.Window:
+ return self._window_capture.isActive()
+ if self._source_type == SourceType.Screen:
+ return self._screen_capture.isActive()
+ return False
diff --git a/examples/multimedia/screencapture/screenlistmodel.py b/examples/multimedia/screencapture/screenlistmodel.py
index d3553aa27..72bb306e3 100644
--- a/examples/multimedia/screencapture/screenlistmodel.py
+++ b/examples/multimedia/screencapture/screenlistmodel.py
@@ -9,7 +9,7 @@ class ScreenListModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
- app = qApp
+ app = qApp # noqa: F821
app.screenAdded.connect(self.screens_changed)
app.screenRemoved.connect(self.screens_changed)
app.primaryScreenChanged.connect(self.screens_changed)
diff --git a/examples/multimedia/screencapture/windowlistmodel.py b/examples/multimedia/screencapture/windowlistmodel.py
new file mode 100644
index 000000000..079040ec2
--- /dev/null
+++ b/examples/multimedia/screencapture/windowlistmodel.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import QAbstractListModel, Qt, Slot
+from PySide6.QtMultimedia import QWindowCapture
+
+
+class WindowListModel(QAbstractListModel):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._window_list = QWindowCapture.capturableWindows()
+
+ def rowCount(self, QModelIndex):
+ return len(self._window_list)
+
+ def data(self, index, role):
+ if role == Qt.DisplayRole:
+ window = self._window_list[index.row()]
+ return window.description()
+ return None
+
+ def window(self, index):
+ return self._window_list[index.row()]
+
+ @Slot()
+ def populate(self):
+ self.beginResetModel()
+ self._window_list = QWindowCapture.capturableWindows()
+ self.endResetModel()
diff --git a/examples/network/blockingfortuneclient/blockingfortuneclient.py b/examples/network/blockingfortuneclient/blockingfortuneclient.py
index f467cf8d4..d0dd7e0ad 100644
--- a/examples/network/blockingfortuneclient/blockingfortuneclient.py
+++ b/examples/network/blockingfortuneclient/blockingfortuneclient.py
@@ -5,12 +5,13 @@
"""PySide6 port of the network/blockingfortunclient example from Qt v5.x, originating from PyQt"""
from PySide6.QtCore import (Signal, QDataStream, QMutex, QMutexLocker,
- QThread, QWaitCondition)
+ QThread, QWaitCondition)
from PySide6.QtGui import QIntValidator
from PySide6.QtWidgets import (QApplication, QDialogButtonBox, QGridLayout,
- QLabel, QLineEdit, QMessageBox, QPushButton, QWidget)
+ QLabel, QLineEdit, QMessageBox, QPushButton,
+ QWidget)
from PySide6.QtNetwork import (QAbstractSocket, QHostAddress, QNetworkInterface,
- QTcpSocket)
+ QTcpSocket)
class FortuneThread(QThread):
@@ -109,7 +110,7 @@ class BlockingClient(QWidget):
port_label.setBuddy(self._port_line_edit)
self._status_label = QLabel(
- "This example requires that you run the Fortune Server example as well.")
+ "This example requires that you run the Fortune Server example as well.")
self._status_label.setWordWrap(True)
self._get_fortune_button = QPushButton("Get Fortune")
@@ -144,7 +145,7 @@ class BlockingClient(QWidget):
def request_new_fortune(self):
self._get_fortune_button.setEnabled(False)
self.thread.request_new_fortune(self._host_line_edit.text(),
- int(self._port_line_edit.text()))
+ int(self._port_line_edit.text()))
def show_fortune(self, nextFortune):
if nextFortune == self._current_fortune:
@@ -158,22 +159,22 @@ class BlockingClient(QWidget):
def display_error(self, socketError, message):
if socketError == QAbstractSocket.HostNotFoundError:
QMessageBox.information(self, "Blocking Fortune Client",
- "The host was not found. Please check the host and port "
- "settings.")
+ "The host was not found. Please check the host and port "
+ "settings.")
elif socketError == QAbstractSocket.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 "
- "and port settings are correct.")
+ "The connection was refused by the peer. Make sure the "
+ "fortune server is running, and check that the host name "
+ "and port settings are correct.")
else:
QMessageBox.information(self, "Blocking Fortune Client",
- f"The following error occurred: {message}.")
+ f"The following error occurred: {message}.")
self._get_fortune_button.setEnabled(True)
def enable_get_fortune_button(self):
- self._get_fortune_button.setEnabled(self._host_line_edit.text() != '' and
- self._port_line_edit.text() != '')
+ self._get_fortune_button.setEnabled(self._host_line_edit.text() != ''
+ and self._port_line_edit.text() != '')
if __name__ == '__main__':
diff --git a/examples/network/downloader/downloader.py b/examples/network/downloader/downloader.py
index f989adf30..fba0cb980 100644
--- a/examples/network/downloader/downloader.py
+++ b/examples/network/downloader/downloader.py
@@ -35,7 +35,8 @@ class DownloaderWidget(QWidget):
self.link_box.setPlaceholderText("Download Link ...")
self._open_folder_action = self.dest_box.addAction(
- qApp.style().standardIcon(QStyle.SP_DirOpenIcon), QLineEdit.TrailingPosition
+ qApp.style().standardIcon(QStyle.SP_DirOpenIcon), # noqa: F821
+ QLineEdit.TrailingPosition
)
self._open_folder_action.triggered.connect(self.on_open_folder)
diff --git a/examples/network/fortuneclient/fortuneclient.py b/examples/network/fortuneclient/fortuneclient.py
index b695c2ea8..e88e5e35b 100644
--- a/examples/network/fortuneclient/fortuneclient.py
+++ b/examples/network/fortuneclient/fortuneclient.py
@@ -31,7 +31,7 @@ class Client(QDialog):
port_label.setBuddy(self._port_line_edit)
self._status_label = QLabel("This examples requires that you run "
- "the Fortune Server example as well.")
+ "the Fortune Server example as well.")
self._get_fortune_button = QPushButton("Get Fortune")
self._get_fortune_button.setDefault(True)
@@ -40,8 +40,7 @@ class Client(QDialog):
quit_button = QPushButton("Quit")
button_box = QDialogButtonBox()
- button_box.addButton(self._get_fortune_button,
- QDialogButtonBox.ActionRole)
+ button_box.addButton(self._get_fortune_button, QDialogButtonBox.ActionRole)
button_box.addButton(quit_button, QDialogButtonBox.RejectRole)
self._tcp_socket = QTcpSocket(self)
@@ -69,7 +68,7 @@ class Client(QDialog):
self._block_size = 0
self._tcp_socket.abort()
self._tcp_socket.connectToHost(self._host_line_edit.text(),
- int(self._port_line_edit.text()))
+ int(self._port_line_edit.text()))
def read_fortune(self):
instr = QDataStream(self._tcp_socket)
@@ -99,23 +98,23 @@ class Client(QDialog):
pass
elif socketError == QAbstractSocket.HostNotFoundError:
QMessageBox.information(self, "Fortune Client",
- "The host was not found. Please check the host name and "
- "port settings.")
+ "The host was not found. Please check the host name and "
+ "port settings.")
elif socketError == QAbstractSocket.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 "
- "and port settings are correct.")
+ "The connection was refused by the peer. Make sure the "
+ "fortune server is running, and check that the host name "
+ "and port settings are correct.")
else:
reason = self._tcp_socket.errorString()
QMessageBox.information(self, "Fortune Client",
- f"The following error occurred: {reason}.")
+ f"The following error occurred: {reason}.")
self._get_fortune_button.setEnabled(True)
def enable_get_fortune_button(self):
- self._get_fortune_button.setEnabled(bool(self._host_line_edit.text() and
- self._port_line_edit.text()))
+ self._get_fortune_button.setEnabled(bool(self._host_line_edit.text()
+ and self._port_line_edit.text()))
if __name__ == '__main__':
diff --git a/examples/network/fortuneserver/fortuneserver.py b/examples/network/fortuneserver/fortuneserver.py
index d84c9dcfd..a94a49f42 100644
--- a/examples/network/fortuneserver/fortuneserver.py
+++ b/examples/network/fortuneserver/fortuneserver.py
@@ -27,21 +27,21 @@ class Server(QDialog):
if not self._tcp_server.listen():
reason = self._tcp_server.errorString()
QMessageBox.critical(self, "Fortune Server",
- f"Unable to start the server: {reason}.")
+ f"Unable to start the server: {reason}.")
self.close()
return
port = self._tcp_server.serverPort()
status_label.setText(f"The server is running on port {port}.\nRun the "
- "Fortune Client example now.")
+ "Fortune Client example now.")
self.fortunes = (
- "You've been leading a dog's life. Stay off the furniture.",
- "You've got to think about tomorrow.",
- "You will be surprised by a loud noise.",
- "You will feel hungry again in another hour.",
- "You might have mail.",
- "You cannot kill time without injuring eternity.",
- "Computers are not intelligent. They only think they are.")
+ "You've been leading a dog's life. Stay off the furniture.",
+ "You've got to think about tomorrow.",
+ "You will be surprised by a loud noise.",
+ "You will feel hungry again in another hour.",
+ "You might have mail.",
+ "You cannot kill time without injuring eternity.",
+ "Computers are not intelligent. They only think they are.")
quit_button.clicked.connect(self.close)
self._tcp_server.newConnection.connect(self.send_fortune)
diff --git a/examples/network/googlesuggest/googlesuggest.py b/examples/network/googlesuggest/googlesuggest.py
index 3cb9f757d..d3fc67c05 100644
--- a/examples/network/googlesuggest/googlesuggest.py
+++ b/examples/network/googlesuggest/googlesuggest.py
@@ -120,12 +120,11 @@ class GSuggestCompletion(QObject):
@Slot(QNetworkReply)
def handle_network_data(self, network_reply: QNetworkReply):
- url = network_reply.url()
if network_reply.error() == QNetworkReply.NoError:
choices: List[str] = []
response: QByteArray = network_reply.readAll()
- xml = QXmlStreamReader(response)
+ xml = QXmlStreamReader(str(response))
while not xml.atEnd():
xml.readNext()
if xml.tokenType() == QXmlStreamReader.StartElement:
diff --git a/examples/network/threadedfortuneserver/threadedfortuneserver.py b/examples/network/threadedfortuneserver/threadedfortuneserver.py
index 60900708e..c75e2bc57 100644
--- a/examples/network/threadedfortuneserver/threadedfortuneserver.py
+++ b/examples/network/threadedfortuneserver/threadedfortuneserver.py
@@ -7,11 +7,11 @@
import random
from PySide6.QtCore import (Signal, QByteArray, QDataStream, QIODevice,
- QThread, Qt)
+ QThread, Qt)
from PySide6.QtWidgets import (QApplication, QDialog, QHBoxLayout, QLabel,
- QMessageBox, QPushButton, QVBoxLayout)
+ QMessageBox, QPushButton, QVBoxLayout)
from PySide6.QtNetwork import (QHostAddress, QNetworkInterface, QTcpServer,
- QTcpSocket)
+ QTcpSocket)
class FortuneThread(QThread):
@@ -75,7 +75,7 @@ class Dialog(QDialog):
if not self.server.listen():
reason = self.server.errorString()
QMessageBox.critical(self, "Threaded Fortune Server",
- f"Unable to start the server: {reason}.")
+ f"Unable to start the server: {reason}.")
self.close()
return
@@ -89,7 +89,7 @@ class Dialog(QDialog):
port = self.server.serverPort()
status_label.setText(f"The server is running on\n\nIP: {ip_address}\nport: {port}\n\n"
- "Run the Fortune Client example now.")
+ "Run the Fortune Client example now.")
quit_button.clicked.connect(self.close)
diff --git a/examples/networkauth/redditclient/redditmodel.py b/examples/networkauth/redditclient/redditmodel.py
index de81ceb19..fd6f9bc97 100644
--- a/examples/networkauth/redditclient/redditmodel.py
+++ b/examples/networkauth/redditclient/redditmodel.py
@@ -51,7 +51,7 @@ class RedditModel(QAbstractTableModel):
document = QJsonDocument.fromJson(json)
root_object = document.object()
kind = root_object["kind"]
- assert(kind == "Listing")
+ assert kind == "Listing"
data_object = root_object["data"]
children_array = data_object["children"]
if not children_array:
diff --git a/examples/networkauth/redditclient/redditwrapper.py b/examples/networkauth/redditclient/redditwrapper.py
index f889a9366..ed4c3d2c7 100644
--- a/examples/networkauth/redditclient/redditwrapper.py
+++ b/examples/networkauth/redditclient/redditwrapper.py
@@ -19,6 +19,7 @@ NEW_URL = "https://oauth.reddit.com/new"
HOT_URL = "https://oauth.reddit.com/hot"
LIVE_THREADS_URL = "https://oauth.reddit.com/live/XXXX/about.json"
+
class RedditWrapper(QObject):
authenticated = Signal()
@@ -78,7 +79,7 @@ class RedditWrapper(QObject):
json = reply.readAll()
document = QJsonDocument.fromJson(json)
- assert(document.isObject())
+ assert document.isObject()
root_object = document.object()
data_object = root_object["data"]
websocketUrl = QUrl(data_object["websocket_url"])
diff --git a/examples/opengl/contextinfo/contextinfo.py b/examples/opengl/contextinfo/contextinfo.py
index 37c5f5d43..311d5b765 100644
--- a/examples/opengl/contextinfo/contextinfo.py
+++ b/examples/opengl/contextinfo/contextinfo.py
@@ -15,15 +15,14 @@ from PySide6.QtGui import (QMatrix4x4, QOpenGLContext, QSurfaceFormat, QWindow)
from PySide6.QtOpenGL import (QOpenGLBuffer, QOpenGLShader,
QOpenGLShaderProgram, QOpenGLVertexArrayObject)
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMessageBox, QPlainTextEdit,
- QWidget)
+ QWidget)
from PySide6.support import VoidPtr
try:
from OpenGL import GL
except ImportError:
app = QApplication(sys.argv)
message_box = QMessageBox(QMessageBox.Critical, "ContextInfo",
- "PyOpenGL must be installed to run this example.",
- QMessageBox.Close)
+ "PyOpenGL must be installed to run this example.", QMessageBox.Close)
message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate")
message_box.exec()
sys.exit(1)
@@ -74,7 +73,10 @@ colors = numpy.array([1, 0, 0, 0, 1, 0, 0, 0, 1], dtype=numpy.float32)
def print_surface_format(surface_format):
- profile_name = 'core' if surface_format.profile() == QSurfaceFormat.CoreProfile else 'compatibility'
+ if surface_format.profile() == QSurfaceFormat.CoreProfile:
+ profile_name = 'core'
+ else:
+ profile_name = 'compatibility'
major = surface_format.majorVersion()
minor = surface_format.minorVersion()
return f"{profile_name} version {major}.{minor}"
@@ -104,11 +106,13 @@ class RenderWindow(QWindow):
# 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
- and fmt.minorVersion() <= 1):
+ and fmt.minorVersion() <= 1):
use_new_style_shader = not fmt.testOption(QSurfaceFormat.DeprecatedFunctions)
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
+ fragment_shader = (fragment_shader_source
+ if use_new_style_shader
+ else fragment_shader_source_110)
if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertex_shader):
log = self.program.log()
raise Exception("Vertex shader could not be added: {log} ({vertexShader})")
diff --git a/examples/opengl/hellogl2/doc/hellogl2.rst b/examples/opengl/hellogl2/doc/hellogl2.rst
index 1223e138c..3471ebf30 100644
--- a/examples/opengl/hellogl2/doc/hellogl2.rst
+++ b/examples/opengl/hellogl2/doc/hellogl2.rst
@@ -4,6 +4,20 @@ Hello GL2 Example
The Hello GL2 example demonstrates the basic use of the OpenGL-related classes
provided with Qt.
+In this example the widget's corresponding top-level window can change several
+times during the widget's lifetime. Whenever this happens, the QOpenGLWidget's
+associated context is destroyed and a new one is created, requiring us to clean
+up the GL resources.
+
+The equivalent C++ example does this cleanup on emission of the
+QOpenGLContext.aboutToBeDestroyed() signal. However, in Qt for Python, we
+cannot rely on this signal when it is emitted from the destructor.
+
+Therefore, we do the cleanup in GLWidget.hideEvent().
+
+This will be followed by an invocation of initializeGL() where we can recreate
+all resources.
+
.. image:: hellogl2.png
:width: 400
:alt: Hello GL2 Screenshot
diff --git a/examples/opengl/hellogl2/glwidget.py b/examples/opengl/hellogl2/glwidget.py
new file mode 100644
index 000000000..bbf200a6b
--- /dev/null
+++ b/examples/opengl/hellogl2/glwidget.py
@@ -0,0 +1,272 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# Copyright (C) 2013 Riverbank Computing Limited.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import ctypes
+from PySide6.QtCore import Signal, Slot, Qt, QSize, QPointF
+from PySide6.QtGui import (QVector3D, QOpenGLFunctions,
+ QMatrix4x4, QOpenGLContext, QSurfaceFormat)
+from PySide6.QtOpenGL import (QOpenGLVertexArrayObject, QOpenGLBuffer,
+ QOpenGLShaderProgram, QOpenGLShader)
+from PySide6.QtOpenGLWidgets import QOpenGLWidget
+
+from OpenGL import GL
+
+from shiboken6 import VoidPtr
+from logo import Logo
+
+FRAGMENT_SHADER_SOURCE_CORE = """#version 150
+in highp vec3 vert;
+in highp vec3 vertNormal;
+out highp vec4 fragColor;
+uniform highp vec3 lightPos;
+void main() {
+ highp vec3 L = normalize(lightPos - vert);
+ highp float NL = max(dot(normalize(vertNormal), L), 0.0);
+ highp vec3 color = vec3(0.39, 1.0, 0.0);
+ highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
+ fragColor = vec4(col, 1.0);
+}"""
+
+
+FRAGMENT_SHADER_SOURCE = """varying highp vec3 vert;
+varying highp vec3 vertNormal;
+uniform highp vec3 lightPos;
+void main() {
+ highp vec3 L = normalize(lightPos - vert);
+ highp float NL = max(dot(normalize(vertNormal), L), 0.0);
+ highp vec3 color = vec3(0.39, 1.0, 0.0);
+ highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
+ gl_FragColor = vec4(col, 1.0);
+}"""
+
+
+VERTEX_SHADER_SOURCE_CORE = """#version 150
+in vec4 vertex;
+in vec3 normal;
+out vec3 vert;
+out vec3 vertNormal;
+uniform mat4 projMatrix;
+uniform mat4 mvMatrix;
+uniform mat3 normalMatrix;
+void main() {
+ vert = vertex.xyz;
+ vertNormal = normalMatrix * normal;
+ gl_Position = projMatrix * mvMatrix * vertex;
+}"""
+
+
+VERTEX_SHADER_SOURCE = """attribute vec4 vertex;
+attribute vec3 normal;
+varying vec3 vert;
+varying vec3 vertNormal;
+uniform mat4 projMatrix;
+uniform mat4 mvMatrix;
+uniform mat3 normalMatrix;
+void main() {
+ vert = vertex.xyz;
+ vertNormal = normalMatrix * normal;
+ gl_Position = projMatrix * mvMatrix * vertex;
+}"""
+
+
+class GLWidget(QOpenGLWidget, QOpenGLFunctions):
+ x_rotation_changed = Signal(int)
+ y_rotation_changed = Signal(int)
+ z_rotation_changed = Signal(int)
+
+ _transparent = False
+
+ def __init__(self, parent=None):
+ QOpenGLWidget.__init__(self, parent)
+ QOpenGLFunctions.__init__(self)
+
+ self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.CoreProfile
+
+ self._x_rot = 0
+ self._y_rot = 0
+ self._z_rot = 0
+ self._last_pos = QPointF()
+ self.logo = Logo()
+ self.vao = QOpenGLVertexArrayObject()
+ self._logo_vbo = QOpenGLBuffer()
+ self.program = QOpenGLShaderProgram()
+ self._proj_matrix_loc = 0
+ self._mv_matrix_loc = 0
+ self._normal_matrix_loc = 0
+ self._light_pos_loc = 0
+ self.proj = QMatrix4x4()
+ self.camera = QMatrix4x4()
+ self.world = QMatrix4x4()
+ if self._transparent:
+ fmt = self.format()
+ fmt.setAlphaBufferSize(8)
+ self.setFormat(fmt)
+
+ @staticmethod
+ def set_transparent(t):
+ GLWidget._transparent = t
+
+ @staticmethod
+ def is_transparent():
+ return GLWidget._transparent
+
+ def x_rotation(self):
+ return self._x_rot
+
+ def y_rotation(self):
+ return self._y_rot
+
+ def z_rotation(self):
+ return self._z_rot
+
+ def minimumSizeHint(self):
+ return QSize(50, 50)
+
+ def sizeHint(self):
+ return QSize(400, 400)
+
+ def normalize_angle(self, angle):
+ while angle < 0:
+ angle += 360 * 16
+ while angle > 360 * 16:
+ angle -= 360 * 16
+ return angle
+
+ @Slot(int)
+ def set_xrotation(self, angle):
+ angle = self.normalize_angle(angle)
+ if angle != self._x_rot:
+ self._x_rot = angle
+ self.x_rotation_changed.emit(angle)
+ self.update()
+
+ @Slot(int)
+ def set_yrotation(self, angle):
+ angle = self.normalize_angle(angle)
+ if angle != self._y_rot:
+ self._y_rot = angle
+ self.y_rotation_changed.emit(angle)
+ self.update()
+
+ @Slot(int)
+ def set_zrotation(self, angle):
+ angle = self.normalize_angle(angle)
+ if angle != self._z_rot:
+ self._z_rot = angle
+ self.z_rotation_changed.emit(angle)
+ self.update()
+
+ @Slot()
+ def cleanup(self):
+ if self.program:
+ self.makeCurrent()
+ self._logo_vbo.destroy()
+ del self.program
+ self.program = None
+ self.doneCurrent()
+
+ def initializeGL(self):
+ self.initializeOpenGLFunctions()
+ self.glClearColor(0, 0, 0, 0 if self._transparent else 1)
+
+ self.program = QOpenGLShaderProgram()
+
+ if self._core:
+ self._vertex_shader = VERTEX_SHADER_SOURCE_CORE
+ self._fragment_shader = FRAGMENT_SHADER_SOURCE_CORE
+ else:
+ self._vertex_shader = VERTEX_SHADER_SOURCE
+ self._fragment_shader = FRAGMENT_SHADER_SOURCE
+
+ self.program.addShaderFromSourceCode(QOpenGLShader.Vertex,
+ self._vertex_shader)
+ self.program.addShaderFromSourceCode(QOpenGLShader.Fragment,
+ self._fragment_shader)
+ self.program.bindAttributeLocation("vertex", 0)
+ self.program.bindAttributeLocation("normal", 1)
+ self.program.link()
+
+ self.program.bind()
+ self._proj_matrix_loc = self.program.uniformLocation("projMatrix")
+ self._mv_matrix_loc = self.program.uniformLocation("mvMatrix")
+ self._normal_matrix_loc = self.program.uniformLocation("normalMatrix")
+ self._light_pos_loc = self.program.uniformLocation("lightPos")
+
+ self.vao.create()
+ with QOpenGLVertexArrayObject.Binder(self.vao):
+ self._logo_vbo.create()
+ self._logo_vbo.bind()
+ float_size = ctypes.sizeof(ctypes.c_float)
+ self._logo_vbo.allocate(self.logo.const_data(),
+ self.logo.count() * float_size)
+
+ self.setup_vertex_attribs()
+
+ self.camera.setToIdentity()
+ self.camera.translate(0, 0, -1)
+
+ self.program.setUniformValue(self._light_pos_loc,
+ QVector3D(0, 0, 70))
+ self.program.release()
+
+ def setup_vertex_attribs(self):
+ self._logo_vbo.bind()
+ f = QOpenGLContext.currentContext().functions()
+ f.glEnableVertexAttribArray(0)
+ f.glEnableVertexAttribArray(1)
+ float_size = ctypes.sizeof(ctypes.c_float)
+
+ null = VoidPtr(0)
+ pointer = VoidPtr(3 * float_size)
+ f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE),
+ 6 * float_size, null)
+ f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE),
+ 6 * float_size, pointer)
+ self._logo_vbo.release()
+
+ def paintGL(self):
+ self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
+ self.glEnable(GL.GL_DEPTH_TEST)
+ self.glEnable(GL.GL_CULL_FACE)
+
+ self.world.setToIdentity()
+ self.world.rotate(180 - (self._x_rot / 16), 1, 0, 0)
+ self.world.rotate(self._y_rot / 16, 0, 1, 0)
+ self.world.rotate(self._z_rot / 16, 0, 0, 1)
+
+ with QOpenGLVertexArrayObject.Binder(self.vao):
+ self.program.bind()
+ self.program.setUniformValue(self._proj_matrix_loc, self.proj)
+ self.program.setUniformValue(self._mv_matrix_loc,
+ self.camera * self.world)
+ normal_matrix = self.world.normalMatrix()
+ self.program.setUniformValue(self._normal_matrix_loc, normal_matrix)
+
+ self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertex_count())
+ self.program.release()
+
+ def resizeGL(self, width, height):
+ self.proj.setToIdentity()
+ self.proj.perspective(45, width / height, 0.01, 100)
+
+ def hideEvent(self, event):
+ self.cleanup()
+ super().hideEvent(event)
+
+ def mousePressEvent(self, event):
+ self._last_pos = event.position()
+
+ def mouseMoveEvent(self, event):
+ pos = event.position()
+ dx = pos.x() - self._last_pos.x()
+ dy = pos.y() - self._last_pos.y()
+
+ if event.buttons() & Qt.LeftButton:
+ self.set_xrotation(self._x_rot + 8 * dy)
+ self.set_yrotation(self._y_rot + 8 * dx)
+ elif event.buttons() & Qt.RightButton:
+ self.set_xrotation(self._x_rot + 8 * dy)
+ self.set_zrotation(self._z_rot + 8 * dx)
+
+ self._last_pos = pos
diff --git a/examples/opengl/hellogl2/hellogl2.py b/examples/opengl/hellogl2/hellogl2.py
deleted file mode 100644
index ce871cda1..000000000
--- a/examples/opengl/hellogl2/hellogl2.py
+++ /dev/null
@@ -1,439 +0,0 @@
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-"""PySide6 port of the opengl/hellogl2 example from Qt v5.x"""
-
-from argparse import ArgumentParser, RawTextHelpFormatter
-import ctypes
-import math
-import sys
-from PySide6.QtCore import Signal, Slot, Qt, QSize, QPointF
-from PySide6.QtGui import (QVector3D, QOpenGLFunctions,
- QMatrix4x4, QOpenGLContext, QSurfaceFormat, QVector3DList)
-from PySide6.QtOpenGL import (QOpenGLVertexArrayObject, QOpenGLBuffer,
- QOpenGLShaderProgram, QOpenGLShader)
-from PySide6.QtWidgets import (QApplication, QWidget, QMessageBox, QHBoxLayout,
- QSlider)
-from PySide6.QtOpenGLWidgets import QOpenGLWidget
-
-from shiboken6 import VoidPtr
-
-try:
- from OpenGL import GL
-except ImportError:
- app = QApplication(sys.argv)
- message_box = QMessageBox(QMessageBox.Critical, "OpenGL hellogl",
- "PyOpenGL must be installed to run this example.",
- QMessageBox.Close)
- message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate")
- message_box.exec()
- sys.exit(1)
-
-
-class Window(QWidget):
- def __init__(self, transparent, parent=None):
- QWidget.__init__(self, parent)
-
- if transparent:
- self.setAttribute(Qt.WA_TranslucentBackground)
- self.setAttribute(Qt.WA_NoSystemBackground, False)
-
- self._gl_widget = GLWidget(transparent)
-
- self._x_slider = self.create_slider()
- self._x_slider.valueChanged.connect(self._gl_widget.set_xrotation)
- self._gl_widget.x_rotation_changed.connect(self._x_slider.setValue)
-
- self._y_slider = self.create_slider()
- self._y_slider.valueChanged.connect(self._gl_widget.set_yrotation)
- self._gl_widget.y_rotation_changed.connect(self._y_slider.setValue)
-
- self._z_slider = self.create_slider()
- self._z_slider.valueChanged.connect(self._gl_widget.set_zrotation)
- self._gl_widget.z_rotation_changed.connect(self._z_slider.setValue)
-
- main_layout = QHBoxLayout()
- main_layout.addWidget(self._gl_widget)
- main_layout.addWidget(self._x_slider)
- main_layout.addWidget(self._y_slider)
- main_layout.addWidget(self._z_slider)
- self.setLayout(main_layout)
-
- self._x_slider.setValue(15 * 16)
- self._y_slider.setValue(345 * 16)
- self._z_slider.setValue(0 * 16)
-
- self.setWindowTitle(self.tr("Hello GL"))
-
- def create_slider(self):
- slider = QSlider(Qt.Vertical)
-
- slider.setRange(0, 360 * 16)
- slider.setSingleStep(16)
- slider.setPageStep(15 * 16)
- slider.setTickInterval(15 * 16)
- slider.setTickPosition(QSlider.TicksRight)
- return slider
-
- def keyPressEvent(self, event):
- if event.key() == Qt.Key_Escape:
- self.close()
- else:
- super(Window, self).keyPressEvent(event)
-
-
-class Logo():
- def __init__(self):
- self.m_data = QVector3DList()
- self.m_data.reserve(5000)
-
- x1 = +0.06
- y1 = -0.14
- x2 = +0.14
- y2 = -0.06
- x3 = +0.08
- y3 = +0.00
- x4 = +0.30
- y4 = +0.22
-
- self.quad(x1, y1, x2, y2, y2, x2, y1, x1)
- self.quad(x3, y3, x4, y4, y4, x4, y3, x3)
-
- self.extrude(x1, y1, x2, y2)
- self.extrude(x2, y2, y2, x2)
- self.extrude(y2, x2, y1, x1)
- self.extrude(y1, x1, x1, y1)
- self.extrude(x3, y3, x4, y4)
- self.extrude(x4, y4, y4, x4)
- self.extrude(y4, x4, y3, x3)
-
- NUM_SECTORS = 100
-
- for i in range(NUM_SECTORS):
- angle = (i * 2 * math.pi) / NUM_SECTORS
- x5 = 0.30 * math.sin(angle)
- y5 = 0.30 * math.cos(angle)
- x6 = 0.20 * math.sin(angle)
- y6 = 0.20 * math.cos(angle)
-
- angle = ((i + 1) * 2 * math.pi) / NUM_SECTORS
- x7 = 0.20 * math.sin(angle)
- y7 = 0.20 * math.cos(angle)
- x8 = 0.30 * math.sin(angle)
- y8 = 0.30 * math.cos(angle)
-
- self.quad(x5, y5, x6, y6, x7, y7, x8, y8)
-
- self.extrude(x6, y6, x7, y7)
- self.extrude(x8, y8, x5, y5)
-
- def const_data(self):
- return self.m_data.constData()
-
- def count(self):
- return len(self.m_data) * 3
-
- def vertex_count(self):
- return self.count() / 6
-
- def quad(self, x1, y1, x2, y2, x3, y3, x4, y4):
- n = QVector3D.normal(QVector3D(x4 - x1, y4 - y1, 0), QVector3D(x2 - x1, y2 - y1, 0))
-
- self.add(QVector3D(x1, y1, -0.05), n)
- self.add(QVector3D(x4, y4, -0.05), n)
- self.add(QVector3D(x2, y2, -0.05), n)
-
- self.add(QVector3D(x3, y3, -0.05), n)
- self.add(QVector3D(x2, y2, -0.05), n)
- self.add(QVector3D(x4, y4, -0.05), n)
-
- n = QVector3D.normal(QVector3D(x1 - x4, y1 - y4, 0), QVector3D(x2 - x4, y2 - y4, 0))
-
- self.add(QVector3D(x4, y4, 0.05), n)
- self.add(QVector3D(x1, y1, 0.05), n)
- self.add(QVector3D(x2, y2, 0.05), n)
-
- self.add(QVector3D(x2, y2, 0.05), n)
- self.add(QVector3D(x3, y3, 0.05), n)
- self.add(QVector3D(x4, y4, 0.05), n)
-
- def extrude(self, x1, y1, x2, y2):
- n = QVector3D.normal(QVector3D(0, 0, -0.1), QVector3D(x2 - x1, y2 - y1, 0))
-
- self.add(QVector3D(x1, y1, 0.05), n)
- self.add(QVector3D(x1, y1, -0.05), n)
- self.add(QVector3D(x2, y2, 0.05), n)
-
- self.add(QVector3D(x2, y2, -0.05), n)
- self.add(QVector3D(x2, y2, 0.05), n)
- self.add(QVector3D(x1, y1, -0.05), n)
-
- def add(self, v, n):
- self.m_data.append(v)
- self.m_data.append(n)
-
-
-class GLWidget(QOpenGLWidget, QOpenGLFunctions):
- x_rotation_changed = Signal(int)
- y_rotation_changed = Signal(int)
- z_rotation_changed = Signal(int)
-
- def __init__(self, transparent, parent=None):
- QOpenGLWidget.__init__(self, parent)
- QOpenGLFunctions.__init__(self)
-
- self._transparent = transparent
- self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.CoreProfile
-
- self._x_rot = 0
- self._y_rot = 0
- self._z_rot = 0
- self._last_pos = QPointF()
- self.logo = Logo()
- self.vao = QOpenGLVertexArrayObject()
- self._logo_vbo = QOpenGLBuffer()
- self.program = QOpenGLShaderProgram()
- self._proj_matrix_loc = 0
- self._mv_matrix_loc = 0
- self._normal_matrix_loc = 0
- self._light_pos_loc = 0
- self.proj = QMatrix4x4()
- self.camera = QMatrix4x4()
- self.world = QMatrix4x4()
- if transparent:
- fmt = self.format()
- fmt.setAlphaBufferSize(8)
- self.setFormat(fmt)
-
- def x_rotation(self):
- return self._x_rot
-
- def y_rotation(self):
- return self._y_rot
-
- def z_rotation(self):
- return self._z_rot
-
- def minimumSizeHint(self):
- return QSize(50, 50)
-
- def sizeHint(self):
- return QSize(400, 400)
-
- def normalize_angle(self, angle):
- while angle < 0:
- angle += 360 * 16
- while angle > 360 * 16:
- angle -= 360 * 16
- return angle
-
- @Slot(int)
- def set_xrotation(self, angle):
- angle = self.normalize_angle(angle)
- if angle != self._x_rot:
- self._x_rot = angle
- self.x_rotation_changed.emit(angle)
- self.update()
-
- @Slot(int)
- def set_yrotation(self, angle):
- angle = self.normalize_angle(angle)
- if angle != self._y_rot:
- self._y_rot = angle
- self.y_rotation_changed.emit(angle)
- self.update()
-
- @Slot(int)
- def set_zrotation(self, angle):
- angle = self.normalize_angle(angle)
- if angle != self._z_rot:
- self._z_rot = angle
- self.z_rotation_changed.emit(angle)
- self.update()
-
- @Slot()
- def cleanup(self):
- self.makeCurrent()
- self._logo_vbo.destroy()
- del self.program
- self.program = None
- self.doneCurrent()
-
- def vertex_shader_source_core(self):
- return """#version 150
- in vec4 vertex;
- in vec3 normal;
- out vec3 vert;
- out vec3 vertNormal;
- uniform mat4 projMatrix;
- uniform mat4 mvMatrix;
- uniform mat3 normalMatrix;
- void main() {
- vert = vertex.xyz;
- vertNormal = normalMatrix * normal;
- gl_Position = projMatrix * mvMatrix * vertex;
- }"""
-
- def fragment_shader_source_core(self):
- return """#version 150
- in highp vec3 vert;
- in highp vec3 vertNormal;
- out highp vec4 fragColor;
- uniform highp vec3 lightPos;
- void main() {
- highp vec3 L = normalize(lightPos - vert);
- highp float NL = max(dot(normalize(vertNormal), L), 0.0);
- highp vec3 color = vec3(0.39, 1.0, 0.0);
- highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
- fragColor = vec4(col, 1.0);
- }"""
-
- def vertex_shader_source(self):
- return """attribute vec4 vertex;
- attribute vec3 normal;
- varying vec3 vert;
- varying vec3 vertNormal;
- uniform mat4 projMatrix;
- uniform mat4 mvMatrix;
- uniform mat3 normalMatrix;
- void main() {
- vert = vertex.xyz;
- vertNormal = normalMatrix * normal;
- gl_Position = projMatrix * mvMatrix * vertex;
- }"""
-
- def fragment_shader_source(self):
- return """varying highp vec3 vert;
- varying highp vec3 vertNormal;
- uniform highp vec3 lightPos;
- void main() {
- highp vec3 L = normalize(lightPos - vert);
- highp float NL = max(dot(normalize(vertNormal), L), 0.0);
- highp vec3 color = vec3(0.39, 1.0, 0.0);
- highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
- gl_FragColor = vec4(col, 1.0);
- }"""
-
- def initializeGL(self):
- self.context().aboutToBeDestroyed.connect(self.cleanup)
- self.initializeOpenGLFunctions()
- self.glClearColor(0, 0, 0, 0 if self._transparent else 1)
-
- self.program = QOpenGLShaderProgram()
-
- if self._core:
- self._vertex_shader = self.vertex_shader_source_core()
- self._fragment_shader = self.fragment_shader_source_core()
- else:
- self._vertex_shader = self.vertex_shader_source()
- self._fragment_shader = self.fragment_shader_source()
-
- self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, self._vertex_shader)
- self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, self._fragment_shader)
- self.program.bindAttributeLocation("vertex", 0)
- self.program.bindAttributeLocation("normal", 1)
- self.program.link()
-
- self.program.bind()
- self._proj_matrix_loc = self.program.uniformLocation("projMatrix")
- self._mv_matrix_loc = self.program.uniformLocation("mvMatrix")
- self._normal_matrix_loc = self.program.uniformLocation("normalMatrix")
- self._light_pos_loc = self.program.uniformLocation("lightPos")
-
- self.vao.create()
- with QOpenGLVertexArrayObject.Binder(self.vao):
- self._logo_vbo.create()
- self._logo_vbo.bind()
- float_size = ctypes.sizeof(ctypes.c_float)
- self._logo_vbo.allocate(self.logo.const_data(), self.logo.count() * float_size)
-
- self.setup_vertex_attribs()
-
- self.camera.setToIdentity()
- self.camera.translate(0, 0, -1)
-
- self.program.setUniformValue(self._light_pos_loc, QVector3D(0, 0, 70))
- self.program.release()
-
- def setup_vertex_attribs(self):
- self._logo_vbo.bind()
- f = QOpenGLContext.currentContext().functions()
- f.glEnableVertexAttribArray(0)
- f.glEnableVertexAttribArray(1)
- float_size = ctypes.sizeof(ctypes.c_float)
-
- null = VoidPtr(0)
- pointer = VoidPtr(3 * float_size)
- f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, null)
- f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, pointer)
- self._logo_vbo.release()
-
- def paintGL(self):
- self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
- self.glEnable(GL.GL_DEPTH_TEST)
- self.glEnable(GL.GL_CULL_FACE)
-
- self.world.setToIdentity()
- self.world.rotate(180 - (self._x_rot / 16), 1, 0, 0)
- self.world.rotate(self._y_rot / 16, 0, 1, 0)
- self.world.rotate(self._z_rot / 16, 0, 0, 1)
-
- with QOpenGLVertexArrayObject.Binder(self.vao):
- self.program.bind()
- self.program.setUniformValue(self._proj_matrix_loc, self.proj)
- self.program.setUniformValue(self._mv_matrix_loc, self.camera * self.world)
- normal_matrix = self.world.normalMatrix()
- self.program.setUniformValue(self._normal_matrix_loc, normal_matrix)
-
- self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertex_count())
- self.program.release()
-
- def resizeGL(self, width, height):
- self.proj.setToIdentity()
- self.proj.perspective(45, width / height, 0.01, 100)
-
- def mousePressEvent(self, event):
- self._last_pos = event.position()
-
- def mouseMoveEvent(self, event):
- pos = event.position()
- dx = pos.x() - self._last_pos.x()
- dy = pos.y() - self._last_pos.y()
-
- if event.buttons() & Qt.LeftButton:
- self.set_xrotation(self._x_rot + 8 * dy)
- self.set_yrotation(self._y_rot + 8 * dx)
- elif event.buttons() & Qt.RightButton:
- self.set_xrotation(self._x_rot + 8 * dy)
- self.set_zrotation(self._z_rot + 8 * dx)
-
- self._last_pos = pos
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
- parser = ArgumentParser(description="hellogl2", formatter_class=RawTextHelpFormatter)
- parser.add_argument('--multisample', '-m', action='store_true',
- help='Use Multisampling')
- parser.add_argument('--coreprofile', '-c', action='store_true',
- help='Use Core Profile')
- parser.add_argument('--transparent', '-t', action='store_true',
- help='Transparent Windows')
- options = parser.parse_args()
-
- fmt = QSurfaceFormat()
- fmt.setDepthBufferSize(24)
- if options.multisample:
- fmt.setSamples(4)
- if options.coreprofile:
- fmt.setVersion(3, 2)
- fmt.setProfile(QSurfaceFormat.CoreProfile)
- QSurfaceFormat.setDefaultFormat(fmt)
-
- main_window = Window(options.transparent)
- main_window.resize(main_window.sizeHint())
- main_window.show()
-
- res = app.exec()
- sys.exit(res)
diff --git a/examples/opengl/hellogl2/hellogl2.pyproject b/examples/opengl/hellogl2/hellogl2.pyproject
index 331d835af..d85a139e4 100644
--- a/examples/opengl/hellogl2/hellogl2.pyproject
+++ b/examples/opengl/hellogl2/hellogl2.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["hellogl2.py"]
+ "files": ["main.py", "glwidget.py", "logo.py", "mainwindow.py", "window.py"]
}
diff --git a/examples/opengl/hellogl2/logo.py b/examples/opengl/hellogl2/logo.py
new file mode 100644
index 000000000..c236a1ec9
--- /dev/null
+++ b/examples/opengl/hellogl2/logo.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# Copyright (C) 2013 Riverbank Computing Limited.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import math
+
+from PySide6.QtGui import (QVector3D, QVector3DList)
+
+
+class Logo():
+ def __init__(self):
+ self.m_data = QVector3DList()
+ self.m_data.reserve(5000)
+
+ x1 = +0.06
+ y1 = -0.14
+ x2 = +0.14
+ y2 = -0.06
+ x3 = +0.08
+ y3 = +0.00
+ x4 = +0.30
+ y4 = +0.22
+
+ self.quad(x1, y1, x2, y2, y2, x2, y1, x1)
+ self.quad(x3, y3, x4, y4, y4, x4, y3, x3)
+
+ self.extrude(x1, y1, x2, y2)
+ self.extrude(x2, y2, y2, x2)
+ self.extrude(y2, x2, y1, x1)
+ self.extrude(y1, x1, x1, y1)
+ self.extrude(x3, y3, x4, y4)
+ self.extrude(x4, y4, y4, x4)
+ self.extrude(y4, x4, y3, x3)
+
+ NUM_SECTORS = 100
+
+ for i in range(NUM_SECTORS):
+ angle = (i * 2 * math.pi) / NUM_SECTORS
+ x5 = 0.30 * math.sin(angle)
+ y5 = 0.30 * math.cos(angle)
+ x6 = 0.20 * math.sin(angle)
+ y6 = 0.20 * math.cos(angle)
+
+ angle = ((i + 1) * 2 * math.pi) / NUM_SECTORS
+ x7 = 0.20 * math.sin(angle)
+ y7 = 0.20 * math.cos(angle)
+ x8 = 0.30 * math.sin(angle)
+ y8 = 0.30 * math.cos(angle)
+
+ self.quad(x5, y5, x6, y6, x7, y7, x8, y8)
+
+ self.extrude(x6, y6, x7, y7)
+ self.extrude(x8, y8, x5, y5)
+
+ def const_data(self):
+ return self.m_data.constData()
+
+ def count(self):
+ return len(self.m_data) * 3
+
+ def vertex_count(self):
+ return self.count() / 6
+
+ def quad(self, x1, y1, x2, y2, x3, y3, x4, y4):
+ n = QVector3D.normal(QVector3D(x4 - x1, y4 - y1, 0),
+ QVector3D(x2 - x1, y2 - y1, 0))
+
+ self.add(QVector3D(x1, y1, -0.05), n)
+ self.add(QVector3D(x4, y4, -0.05), n)
+ self.add(QVector3D(x2, y2, -0.05), n)
+
+ self.add(QVector3D(x3, y3, -0.05), n)
+ self.add(QVector3D(x2, y2, -0.05), n)
+ self.add(QVector3D(x4, y4, -0.05), n)
+
+ n = QVector3D.normal(QVector3D(x1 - x4, y1 - y4, 0),
+ QVector3D(x2 - x4, y2 - y4, 0))
+
+ self.add(QVector3D(x4, y4, 0.05), n)
+ self.add(QVector3D(x1, y1, 0.05), n)
+ self.add(QVector3D(x2, y2, 0.05), n)
+
+ self.add(QVector3D(x2, y2, 0.05), n)
+ self.add(QVector3D(x3, y3, 0.05), n)
+ self.add(QVector3D(x4, y4, 0.05), n)
+
+ def extrude(self, x1, y1, x2, y2):
+ n = QVector3D.normal(QVector3D(0, 0, -0.1),
+ QVector3D(x2 - x1, y2 - y1, 0))
+
+ self.add(QVector3D(x1, y1, 0.05), n)
+ self.add(QVector3D(x1, y1, -0.05), n)
+ self.add(QVector3D(x2, y2, 0.05), n)
+
+ self.add(QVector3D(x2, y2, -0.05), n)
+ self.add(QVector3D(x2, y2, 0.05), n)
+ self.add(QVector3D(x1, y1, -0.05), n)
+
+ def add(self, v, n):
+ self.m_data.append(v)
+ self.m_data.append(n)
diff --git a/examples/opengl/hellogl2/main.py b/examples/opengl/hellogl2/main.py
new file mode 100644
index 000000000..c7eb78a82
--- /dev/null
+++ b/examples/opengl/hellogl2/main.py
@@ -0,0 +1,58 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# Copyright (C) 2013 Riverbank Computing Limited.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the opengl/hellogl2 example from Qt v6.x"""
+
+from argparse import ArgumentParser, RawTextHelpFormatter
+import sys
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QSurfaceFormat
+from PySide6.QtWidgets import (QApplication, QMessageBox)
+
+
+try:
+ from mainwindow import MainWindow
+ from glwidget import GLWidget
+except ImportError:
+ app = QApplication(sys.argv)
+ message_box = QMessageBox(QMessageBox.Critical, "OpenGL hellogl",
+ "PyOpenGL must be installed to run this example.",
+ QMessageBox.Close)
+ message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate")
+ message_box.exec()
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ parser = ArgumentParser(description="hellogl2",
+ formatter_class=RawTextHelpFormatter)
+ parser.add_argument('--multisample', '-m', action='store_true',
+ help='Use Multisampling')
+ parser.add_argument('--coreprofile', '-c', action='store_true',
+ help='Use Core Profile')
+ parser.add_argument('--transparent', '-t', action='store_true',
+ help='Transparent Windows')
+ options = parser.parse_args()
+
+ fmt = QSurfaceFormat()
+ fmt.setDepthBufferSize(24)
+ if options.multisample:
+ fmt.setSamples(4)
+ if options.coreprofile:
+ fmt.setVersion(3, 2)
+ fmt.setProfile(QSurfaceFormat.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.show()
+
+ res = app.exec()
+ sys.exit(res)
diff --git a/examples/opengl/hellogl2/mainwindow.py b/examples/opengl/hellogl2/mainwindow.py
new file mode 100644
index 000000000..69b9b66fe
--- /dev/null
+++ b/examples/opengl/hellogl2/mainwindow.py
@@ -0,0 +1,29 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot, Qt
+from PySide6.QtGui import QKeySequence
+from PySide6.QtWidgets import QMainWindow, QMessageBox
+
+from window import Window
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+ menuWindow = self.menuBar().addMenu("Window")
+ menuWindow.addAction("Add new", QKeySequence(Qt.CTRL | Qt.Key_N),
+ self.onAddNew)
+ menuWindow.addAction("Quit", QKeySequence(Qt.CTRL | Qt.Key_Q),
+ qApp.closeAllWindows) # noqa: F821
+
+ self.onAddNew()
+
+ @Slot()
+ def onAddNew(self):
+ if not self.centralWidget():
+ self.setCentralWidget(Window(self))
+ else:
+ QMessageBox.information(self, "Cannot Add Window()",
+ "Already occupied. Undock first.")
diff --git a/examples/opengl/hellogl2/window.py b/examples/opengl/hellogl2/window.py
new file mode 100644
index 000000000..ad61d2f97
--- /dev/null
+++ b/examples/opengl/hellogl2/window.py
@@ -0,0 +1,110 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# Copyright (C) 2013 Riverbank Computing Limited.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Slot, Qt
+from PySide6.QtWidgets import (QHBoxLayout, QMainWindow,
+ QMessageBox, QPushButton, QSlider,
+ QVBoxLayout, QWidget)
+
+from glwidget import GLWidget
+
+
+def _main_window():
+ for t in qApp.topLevelWidgets(): # noqa: F821
+ if isinstance(t, QMainWindow):
+ return t
+ return None
+
+
+class Window(QWidget):
+ instances = [] # Keep references when undocked
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.instances.append(self)
+
+ self._gl_widget = GLWidget()
+
+ self._x_slider = self.create_slider()
+ self._x_slider.valueChanged.connect(self._gl_widget.set_xrotation)
+ self._gl_widget.x_rotation_changed.connect(self._x_slider.setValue)
+
+ self._y_slider = self.create_slider()
+ self._y_slider.valueChanged.connect(self._gl_widget.set_yrotation)
+ self._gl_widget.y_rotation_changed.connect(self._y_slider.setValue)
+
+ self._z_slider = self.create_slider()
+ self._z_slider.valueChanged.connect(self._gl_widget.set_zrotation)
+ self._gl_widget.z_rotation_changed.connect(self._z_slider.setValue)
+
+ mainLayout = QVBoxLayout(self)
+ w = QWidget()
+ container = QHBoxLayout(w)
+ container.addWidget(self._gl_widget)
+ container.addWidget(self._x_slider)
+ container.addWidget(self._y_slider)
+ container.addWidget(self._z_slider)
+
+ mainLayout.addWidget(w)
+ self._dock_btn = QPushButton("Undock")
+ self._dock_btn.clicked.connect(self.dock_undock)
+ mainLayout.addWidget(self._dock_btn)
+
+ self._x_slider.setValue(15 * 16)
+ self._y_slider.setValue(345 * 16)
+ self._z_slider.setValue(0 * 16)
+
+ self.setWindowTitle(self.tr("Hello GL"))
+
+ def create_slider(self):
+ slider = QSlider(Qt.Vertical)
+
+ slider.setRange(0, 360 * 16)
+ slider.setSingleStep(16)
+ slider.setPageStep(15 * 16)
+ slider.setTickInterval(15 * 16)
+ slider.setTickPosition(QSlider.TicksRight)
+ return slider
+
+ def closeEvent(self, event):
+ self.instances.remove(self)
+ event.accept()
+
+ def keyPressEvent(self, event):
+ if self.isWindow() and event.key() == Qt.Key_Escape:
+ self.close()
+ else:
+ super().keyPressEvent(event)
+
+ @Slot()
+ def dock_undock(self):
+ if self.parent():
+ self.undock()
+ else:
+ self.dock()
+
+ def dock(self):
+ mainWindow = _main_window()
+ if not mainWindow or not mainWindow.isVisible():
+ QMessageBox.information(self, "Cannot Dock",
+ "Main window already closed")
+ return
+ if mainWindow.centralWidget():
+ QMessageBox.information(self, "Cannot Dock",
+ "Main window already occupied")
+ return
+
+ self.setAttribute(Qt.WA_DeleteOnClose, False)
+ self._dock_btn.setText("Undock")
+ mainWindow.setCentralWidget(self)
+
+ def undock(self):
+ self.setParent(None)
+ self.setAttribute(Qt.WA_DeleteOnClose)
+ geometry = self.screen().availableGeometry()
+ x = geometry.x() + (geometry.width() - self.width()) / 2
+ y = geometry.y() + (geometry.height() - self.height()) / 2
+ self.move(x, y)
+ self._dock_btn.setText("Dock")
+ self.show()
diff --git a/examples/opengl/textures/textures.py b/examples/opengl/textures/textures.py
index 79f7761a2..87c1164b7 100644
--- a/examples/opengl/textures/textures.py
+++ b/examples/opengl/textures/textures.py
@@ -24,7 +24,7 @@ except ImportError:
messageBox.exec()
sys.exit(1)
-import textures_rc
+import textures_rc # noqa: F401
class GLWidget(QOpenGLWidget):
@@ -32,12 +32,12 @@ class GLWidget(QOpenGLWidget):
refCount = 0
coords = (
- ( ( +1, -1, -1 ), ( -1, -1, -1 ), ( -1, +1, -1 ), ( +1, +1, -1 ) ),
- ( ( +1, +1, -1 ), ( -1, +1, -1 ), ( -1, +1, +1 ), ( +1, +1, +1 ) ),
- ( ( +1, -1, +1 ), ( +1, -1, -1 ), ( +1, +1, -1 ), ( +1, +1, +1 ) ),
- ( ( -1, -1, -1 ), ( -1, -1, +1 ), ( -1, +1, +1 ), ( -1, +1, -1 ) ),
- ( ( +1, -1, +1 ), ( -1, -1, +1 ), ( -1, -1, -1 ), ( +1, -1, -1 ) ),
- ( ( -1, -1, +1 ), ( +1, -1, +1 ), ( +1, +1, +1 ), ( -1, +1, +1 ) )
+ ((+1, -1, -1), (-1, -1, -1), (-1, +1, -1), (+1, +1, -1)),
+ ((+1, +1, -1), (-1, +1, -1), (-1, +1, +1), (+1, +1, +1)),
+ ((+1, -1, +1), (+1, -1, -1), (+1, +1, -1), (+1, +1, +1)),
+ ((-1, -1, -1), (-1, -1, +1), (-1, +1, +1), (-1, +1, -1)),
+ ((+1, -1, +1), (-1, -1, +1), (-1, -1, -1), (+1, -1, -1)),
+ ((-1, -1, +1), (+1, -1, +1), (+1, +1, +1), (-1, +1, +1))
)
clicked = Signal()
@@ -186,7 +186,7 @@ class Window(QWidget):
mainLayout.addWidget(glw, i, j)
glw.clicked.connect(self.setCurrentGlWidget)
- qApp.lastWindowClosed.connect(glw.freeGLResources)
+ qApp.lastWindowClosed.connect(glw.freeGLResources) # noqa: F821
self.currentGlWidget = self.glWidgets[0][0]
diff --git a/examples/opengl/threadedqopenglwidget/renderer.py b/examples/opengl/threadedqopenglwidget/renderer.py
index fbed79423..81ec63cbb 100644
--- a/examples/opengl/threadedqopenglwidget/renderer.py
+++ b/examples/opengl/threadedqopenglwidget/renderer.py
@@ -120,7 +120,7 @@ class Renderer(QObject, QOpenGLFunctions):
if self._exiting:
return
- assert(ctx.thread() == QThread.currentThread())
+ assert ctx.thread() == QThread.currentThread()
# Make the context (and an offscreen surface) current for self thread.
# The QOpenGLWidget's fbo is bound in the context.
diff --git a/examples/pdf/quickpdfviewer/main.py b/examples/pdf/quickpdfviewer/main.py
index 9d6ea68c8..ee7b882c2 100644
--- a/examples/pdf/quickpdfviewer/main.py
+++ b/examples/pdf/quickpdfviewer/main.py
@@ -11,7 +11,7 @@ from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QCoreApplication, QUrl
-import rc_viewer
+import rc_viewer # noqa: F401
"""PySide6 port of the pdf/pdfviewer example from Qt v6.x"""
diff --git a/examples/pdfwidgets/pdfviewer/resources_rc.py b/examples/pdfwidgets/pdfviewer/rc_resources.py
index 7e386e99b..7e386e99b 100644
--- a/examples/pdfwidgets/pdfviewer/resources_rc.py
+++ b/examples/pdfwidgets/pdfviewer/rc_resources.py
diff --git a/examples/pdfwidgets/pdfviewer/ui_mainwindow.py b/examples/pdfwidgets/pdfviewer/ui_mainwindow.py
index e4215769b..9ed525dbf 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.4.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -21,7 +21,7 @@ from PySide6.QtWidgets import (QApplication, QHeaderView, QMainWindow, QMenu,
QMenuBar, QSizePolicy, QSplitter, QStatusBar,
QTabWidget, QToolBar, QTreeView, QVBoxLayout,
QWidget)
-import resources_rc
+import rc_resources
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
@@ -123,7 +123,7 @@ class Ui_MainWindow(object):
self.splitter.setOrientation(Qt.Horizontal)
self.tabWidget = QTabWidget(self.splitter)
self.tabWidget.setObjectName(u"tabWidget")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tabWidget.sizePolicy().hasHeightForWidth())
@@ -152,7 +152,7 @@ class Ui_MainWindow(object):
self.splitter.addWidget(self.tabWidget)
self.pdfView = QPdfView(self.splitter)
self.pdfView.setObjectName(u"pdfView")
- sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy1.setHorizontalStretch(10)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.pdfView.sizePolicy().hasHeightForWidth())
@@ -179,7 +179,7 @@ class Ui_MainWindow(object):
self.mainToolBar.setObjectName(u"mainToolBar")
self.mainToolBar.setMovable(False)
self.mainToolBar.setFloatable(False)
- MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar)
+ MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar)
self.statusBar = QStatusBar(MainWindow)
self.statusBar.setObjectName(u"statusBar")
MainWindow.setStatusBar(self.statusBar)
diff --git a/examples/qml/editingmodel/doc/editingmodel.rst b/examples/qml/editingmodel/doc/editingmodel.rst
index d76bebc22..2b45b23f0 100644
--- a/examples/qml/editingmodel/doc/editingmodel.rst
+++ b/examples/qml/editingmodel/doc/editingmodel.rst
@@ -1,8 +1,10 @@
QAbstractListModel in QML
=========================
+.. tags:: Android
+
This example shows how to add, remove and move items inside a QML
-ListView, but showing and editing the data via roles using a
+ListView, but showing and editing the data via roles using a
QAbstractListModel from Python.
You can add new elements and reset the view using the two top buttons,
diff --git a/examples/qml/editingmodel/main.py b/examples/qml/editingmodel/main.py
index 00b3ae2b1..5240a9de0 100644
--- a/examples/qml/editingmodel/main.py
+++ b/examples/qml/editingmodel/main.py
@@ -8,7 +8,7 @@ from PySide6.QtCore import QUrl
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
-from model import BaseModel
+from model import BaseModel # noqa: F401
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
diff --git a/examples/qml/editingmodel/model.py b/examples/qml/editingmodel/model.py
index 591497872..02a1e5717 100644
--- a/examples/qml/editingmodel/model.py
+++ b/examples/qml/editingmodel/model.py
@@ -2,8 +2,7 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from PySide6.QtCore import (QAbstractListModel, QByteArray, QModelIndex, Qt,
- Slot)
+from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, Qt, Slot
from PySide6.QtGui import QColor
from PySide6.QtQml import QmlElement
@@ -104,18 +103,18 @@ class BaseModel(QAbstractListModel):
self.beginMoveRows(QModelIndex(), sourceRow, sourceRow + count, QModelIndex(), end)
# start database work
- pops = self.db[sourceRow : sourceRow + count + 1]
+ pops = self.db[sourceRow: sourceRow + count + 1]
if sourceRow > dstChild:
self.db = (
self.db[:dstChild]
+ pops
+ self.db[dstChild:sourceRow]
- + self.db[sourceRow + count + 1 :]
+ + self.db[sourceRow + count + 1:]
)
else:
start = self.db[:sourceRow]
- middle = self.db[dstChild : dstChild + 1]
- endlist = self.db[dstChild + count + 1 :]
+ middle = self.db[dstChild: dstChild + 1]
+ endlist = self.db[dstChild + count + 1:]
self.db = start + middle + pops + endlist
# end database work
@@ -136,7 +135,7 @@ class BaseModel(QAbstractListModel):
self.beginRemoveRows(QModelIndex(), row, row + count)
# start database work
- self.db = self.db[:row] + self.db[row + count + 1 :]
+ self.db = self.db[:row] + self.db[row + count + 1:]
# end database work
self.endRemoveRows()
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 d06ff0a5a..4c1b3bdae 100644
--- a/examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst
+++ b/examples/qml/tutorials/extending-qml-advanced/adding/doc/adding.rst
@@ -18,12 +18,12 @@ The ``Person`` type can be used from QML like this:
Declare the Person Class
------------------------
-All QML types map to C++ types. Here we declare a basic C++ Person class
+All QML types map to C++ types. Here we declare a basic C++ Person class
with the two properties we want accessible on the QML type - name and shoeSize.
Although in this example we use the same name for the C++ class as the QML
type, the C++ class can be named differently, or appear in a namespace.
-The Person class implementation is quite basic. The property accessors simply
+The Person class implementation is quite basic. The property accessors simply
return members of the object instance.
.. code-block:: python
diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/main.py b/examples/qml/tutorials/extending-qml-advanced/adding/main.py
index faf13f42f..ec703dbf3 100644
--- a/examples/qml/tutorials/extending-qml-advanced/adding/main.py
+++ b/examples/qml/tutorials/extending-qml-advanced/adding/main.py
@@ -9,7 +9,7 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Person
+from person import Person # noqa: F401
if __name__ == '__main__':
diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/person.py b/examples/qml/tutorials/extending-qml-advanced/adding/person.py
index fafb9d581..526eae714 100644
--- a/examples/qml/tutorials/extending-qml-advanced/adding/person.py
+++ b/examples/qml/tutorials/extending-qml-advanced/adding/person.py
@@ -32,4 +32,3 @@ class Person(QObject):
@shoe_size.setter
def shoe_size(self, s):
self._shoe_size = s
-
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 dc07a2a5f..560db6602 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,7 +1,8 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project example from Qt v6.x"""
+"""PySide6 port of the
+ qml/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project example from Qt v6.x"""
from pathlib import Path
import sys
@@ -9,8 +10,8 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Person
-from birthdayparty import BirthdayParty
+from person import Person # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
app = QCoreApplication(sys.argv)
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 ef4b5ac5a..cc77e2b40 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,17 +1,18 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion example from Qt v6.x"""
+"""PySide6 port of the
+ qml/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion example
+ from Qt v6.x"""
from pathlib import Path
-import os
import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Boy, Girl
-from birthdayparty import BirthdayParty
+from person import Boy, Girl # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
app = QCoreApplication(sys.argv)
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 f469538b6..020974c9b 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,7 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/default advanced3-Default-properties example from Qt v6.x"""
+"""PySide6 port of the
+ qml/examples/qml/tutorials/extending-qml-advanced/default advanced3-Default-properties example
+ from Qt v6.x"""
from pathlib import Path
import sys
@@ -9,8 +11,8 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Boy, Girl
-from birthdayparty import BirthdayParty
+from person import Boy, Girl # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
app = QCoreApplication(sys.argv)
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 3cdddbd06..9757b6daa 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,7 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties example from Qt v6.x"""
+"""PySide6 port of the
+ qml/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties example
+ from Qt v6.x"""
from pathlib import Path
import sys
@@ -9,8 +11,8 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Boy, Girl
-from birthdayparty import BirthdayParty
+from person import Boy, Girl # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
if __name__ == '__main__':
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 bb607ac76..9a92afeb5 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,7 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties example from Qt v6.x"""
+"""PySide6 port of the
+ qml/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties example
+ from Qt v6.x"""
from pathlib import Path
import sys
@@ -9,8 +11,8 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject
-from person import Boy, Girl
-from birthdayparty import BirthdayParty
+from person import Boy, Girl # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
app = QCoreApplication(sys.argv)
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 b763a456a..ea412a547 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,7 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source example from Qt v6.x"""
+"""PySide6 port of the
+ qml/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source example
+ from Qt v6.x"""
from pathlib import Path
import sys
@@ -9,9 +11,9 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject
-from person import Boy, Girl
+from person import Boy, Girl # noqa: F401
from birthdayparty import BirthdayParty
-from happybirthdaysong import HappyBirthdaySong
+from happybirthdaysong import HappyBirthdaySong # noqa: F401
app = QCoreApplication(sys.argv)
diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/main.py b/examples/qml/tutorials/extending-qml-advanced/binding/main.py
index 738bab086..64929a807 100644
--- a/examples/qml/tutorials/extending-qml-advanced/binding/main.py
+++ b/examples/qml/tutorials/extending-qml-advanced/binding/main.py
@@ -9,9 +9,9 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine, qmlAttachedPropertiesObject
-from person import Boy, Girl
-from birthdayparty import BirthdayParty
-from happybirthdaysong import HappyBirthdaySong
+from person import Boy, Girl # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
+from happybirthdaysong import HappyBirthdaySong # noqa: F401
if __name__ == "__main__":
diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/main.py b/examples/qml/tutorials/extending-qml-advanced/methods/main.py
index 5f51ebe1f..fb656f266 100644
--- a/examples/qml/tutorials/extending-qml-advanced/methods/main.py
+++ b/examples/qml/tutorials/extending-qml-advanced/methods/main.py
@@ -9,8 +9,8 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Person
-from birthdayparty import BirthdayParty
+from person import Person # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
app = QCoreApplication(sys.argv)
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 fd0c6d474..16924cdcd 100644
--- a/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst
+++ b/examples/qml/tutorials/extending-qml-advanced/properties/doc/properties.rst
@@ -75,11 +75,11 @@ The class contains a member to store the celebrant object, and also a
list member storing the Person instances.
In QML, the type of a list properties - and the guests property is a list of
-people - are all of type ListProperty. ListProperty is simple value
-type that contains a set of functions. QML calls these functions
-whenever it needs to read from, write to or otherwise interact with
-the list. In addition to concrete lists like the people list used in this
-example, the use of QQmlListProperty allows for "virtual lists" and other advanced
+people - are all of type :class:`~PySide6.QtQml.ListProperty`.
+``ListProperty`` is a simple value type that contains a set of functions.
+QML calls these functions whenever it needs to read from, write to or otherwise
+interact with the list. In addition to concrete lists like the people list used in this
+example, the use of ``ListProperty`` allows for "virtual lists" and other advanced
scenarios.
Running the Example
diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/main.py b/examples/qml/tutorials/extending-qml-advanced/properties/main.py
index 4797e4c7c..11757d5f3 100644
--- a/examples/qml/tutorials/extending-qml-advanced/properties/main.py
+++ b/examples/qml/tutorials/extending-qml-advanced/properties/main.py
@@ -9,8 +9,8 @@ import sys
from PySide6.QtCore import QCoreApplication
from PySide6.QtQml import QQmlComponent, QQmlEngine
-from person import Person
-from birthdayparty import BirthdayParty
+from person import Person # noqa: F401
+from birthdayparty import BirthdayParty # noqa: F401
if __name__ == '__main__':
diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/person.py b/examples/qml/tutorials/extending-qml-advanced/properties/person.py
index fafb9d581..526eae714 100644
--- a/examples/qml/tutorials/extending-qml-advanced/properties/person.py
+++ b/examples/qml/tutorials/extending-qml-advanced/properties/person.py
@@ -32,4 +32,3 @@ class Person(QObject):
@shoe_size.setter
def shoe_size(self, s):
self._shoe_size = s
-
diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py
index c70d7fc42..659850f38 100644
--- a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py
+++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py
@@ -1,7 +1,8 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example from Qt v5.x"""
+"""PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example
+ from Qt v5.x"""
import os
from pathlib import Path
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 90cb41107..a98f18c81 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
@@ -15,8 +15,9 @@ have a ``slices`` property that accepts a list of ``PieSlice`` items:
:lines: 4-32
To do this, we replace the ``pieSlice`` property in ``PieChart`` with a
-``slices`` property, declared as a class variable of the ``QQmlListProperty``
-type. The ``QQmlListProperty`` class enables the creation of list properties in
+``slices`` property, declared as a class variable of the
+:class:`~PySide6.QtQml.ListProperty` type.
+The ``ListProperty`` class enables the creation of list properties in
QML extensions. We replace the ``pieSlice()`` function with a ``slices()``
function that returns a list of slices, and add an internal ``appendSlice()``
function (discussed below). We also use a list to store the internal list of
@@ -31,13 +32,13 @@ slices as ``_slices``:
:lines: 75-79
Although the ``slices`` property does not have an associated setter, it is
-still modifiable because of the way ``QQmlListProperty`` works. We indicate
+still modifiable because of the way ``ListProperty`` works. We indicate
that the internal ``PieChart.appendSlice()`` function is to be called whenever
a request is made from QML to add items to the list.
The ``appendSlice()`` function simply sets the parent item as before, and adds
the new item to the ``_slices`` list. As you can see, the append function for
-a ``QQmlListProperty`` is called with two arguments: the list property, and the
+a ``ListProperty`` is called with two arguments: the list property, and the
item that is to be appended.
The ``PieSlice`` class has also been modified to include ``fromAngle`` and
diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py
index 236c487f9..98952cef1 100644
--- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py
+++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py
@@ -54,7 +54,8 @@ class PieSlice (QQuickPaintedItem):
pen = QPen(self._color, 2)
painter.setPen(pen)
painter.setRenderHints(QPainter.Antialiasing, True)
- painter.drawPie(self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16)
+ painter.drawPie(
+ self.boundingRect().adjusted(1, 1, -1, -1), self._fromAngle * 16, self._angleSpan * 16)
@QmlElement
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 6a7e80ccb..3ab8bcc08 100644
--- a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py
+++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py
@@ -12,6 +12,7 @@ from pieslice import PieSlice
QML_IMPORT_NAME = "Charts"
QML_IMPORT_MAJOR_VERSION = 1
+
@QmlElement
class PieChart(QQuickItem):
def __init__(self, parent=None):
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 67242a967..6f82f1f10 100644
--- a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py
+++ b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py
@@ -11,6 +11,7 @@ from PySide6.QtQml import QmlElement
QML_IMPORT_NAME = "Charts"
QML_IMPORT_MAJOR_VERSION = 1
+
@QmlElement
class PieSlice(QQuickPaintedItem):
def __init__(self, parent=None):
diff --git a/examples/qml/usingmodel/doc/usingmodel.rst b/examples/qml/usingmodel/doc/usingmodel.rst
index 11b476d09..06a1b27b0 100644
--- a/examples/qml/usingmodel/doc/usingmodel.rst
+++ b/examples/qml/usingmodel/doc/usingmodel.rst
@@ -1,6 +1,8 @@
Using Model Example
===================
+.. tags:: Android
+
A Python application that demonstrates how to use a :ref:`QAbstractListModel`
with QML.
diff --git a/examples/qml/usingmodel/usingmodel.py b/examples/qml/usingmodel/usingmodel.py
index 6f8ea5a21..008a1b94b 100644
--- a/examples/qml/usingmodel/usingmodel.py
+++ b/examples/qml/usingmodel/usingmodel.py
@@ -2,24 +2,37 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
+from dataclasses import dataclass
from pathlib import Path
import sys
from PySide6.QtCore import QAbstractListModel, Qt, QUrl, QByteArray
from PySide6.QtGui import QGuiApplication
from PySide6.QtQuick import QQuickView
-from PySide6.QtQml import qmlRegisterSingletonType
+from PySide6.QtQml import QmlElement, QmlSingleton
+QML_IMPORT_NAME = "PersonModel"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@dataclass
+class Person:
+ name: str
+ myrole: str
+
+
+@QmlElement
+@QmlSingleton
class PersonModel (QAbstractListModel):
MyRole = Qt.UserRole + 1
- def __init__(self, parent=None):
- QAbstractListModel.__init__(self, parent)
- self._data = []
+ def __init__(self, data, parent=None):
+ super().__init__(parent)
+ self._data = data
def roleNames(self):
roles = {
- PersonModel.MyRole: QByteArray(b'modelData'),
+ PersonModel.MyRole: QByteArray(b'myrole'),
Qt.DisplayRole: QByteArray(b'display')
}
return roles
@@ -29,26 +42,18 @@ class PersonModel (QAbstractListModel):
def data(self, index, role):
d = self._data[index.row()]
-
if role == Qt.DisplayRole:
- return d['name']
- elif role == Qt.DecorationRole:
+ return d.name
+ if role == Qt.DecorationRole:
return Qt.black
- elif role == PersonModel.MyRole:
- return d['myrole']
+ if role == PersonModel.MyRole:
+ return d.myrole
return None
- def populate(self, data=None):
- for item in data:
- self._data.append(item)
-
-
-def model_callback(engine):
- my_model = PersonModel()
- data = [{'name': 'Qt', 'myrole': 'role1'},
- {'name': 'PySide', 'myrole': 'role2'}]
- my_model.populate(data)
- return my_model
+ @staticmethod
+ def create(engine):
+ data = [Person('Qt', 'myrole'), Person('PySide', 'role2')]
+ return PersonModel(data)
if __name__ == '__main__':
@@ -56,7 +61,6 @@ if __name__ == '__main__':
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
- qmlRegisterSingletonType(PersonModel, "PersonModel", 1, 0, "MyModel", model_callback)
qml_file = os.fspath(Path(__file__).resolve().parent / 'view.qml')
view.setSource(QUrl.fromLocalFile(qml_file))
if view.status() == QQuickView.Error:
diff --git a/examples/qml/usingmodel/view.qml b/examples/qml/usingmodel/view.qml
index c5aa7e0fc..e8b1fb2fb 100644
--- a/examples/qml/usingmodel/view.qml
+++ b/examples/qml/usingmodel/view.qml
@@ -8,21 +8,13 @@ ListView {
width: 100
height: 100
anchors.fill: parent
- model: MyModel
+ model: PersonModel
delegate: Component {
Rectangle {
height: 25
width: 100
Text {
- function displayText() {
- var result = ""
- if (typeof display !== "undefined")
- result = display + ": "
- result += modelData
- return result
- }
-
- text: displayText()
+ text: display + ": " + myrole
}
}
}
diff --git a/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst b/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst
index d71ee61df..a4af62706 100644
--- a/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst
+++ b/examples/quick/models/objectlistmodel/doc/objectlistmodel.rst
@@ -1,6 +1,8 @@
Object List Model Example
=========================
+.. tags:: Android
+
A list of QObject values can also be used as a model.
A list[QObject,] provides the properties of the objects in the list as roles.
diff --git a/examples/quick/models/objectlistmodel/objectlistmodel.py b/examples/quick/models/objectlistmodel/objectlistmodel.py
index 0843ae480..968761e5c 100644
--- a/examples/quick/models/objectlistmodel/objectlistmodel.py
+++ b/examples/quick/models/objectlistmodel/objectlistmodel.py
@@ -9,6 +9,7 @@ from PySide6.QtQuick import QQuickView
# This example illustrates exposing a list of QObjects as a model in QML
+
class DataObject(QObject):
nameChanged = Signal()
@@ -25,7 +26,7 @@ class DataObject(QObject):
def setName(self, name):
if name != self._name:
self._name = name
- nameChanged.emit()
+ self.nameChanged.emit()
def color(self):
return self._color
@@ -33,8 +34,7 @@ class DataObject(QObject):
def setColor(self, color):
if color != self._color:
self._color = color
- colorChanged.emit()
-
+ self.colorChanged.emit()
name = Property(str, name, setName, notify=nameChanged)
color = Property(str, color, setColor, notify=colorChanged)
diff --git a/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst b/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst
index 4c00ed130..ce11674b4 100644
--- a/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst
+++ b/examples/quick/models/stringlistmodel/doc/stringlistmodel.rst
@@ -1,6 +1,8 @@
String List Model Example
=========================
+.. tags:: Android
+
A model may be a simple 'list',
which provides the contents of the list via the modelData role.
diff --git a/examples/quick/models/stringlistmodel/stringlistmodel.py b/examples/quick/models/stringlistmodel/stringlistmodel.py
index a7a1807bb..3982b1ffc 100644
--- a/examples/quick/models/stringlistmodel/stringlistmodel.py
+++ b/examples/quick/models/stringlistmodel/stringlistmodel.py
@@ -15,7 +15,7 @@ if __name__ == '__main__':
dataList = ["Item 1", "Item 2", "Item 3", "Item 4"]
view = QQuickView()
- view.setInitialProperties({"model": dataList })
+ view.setInitialProperties({"model": dataList})
qml_file = Path(__file__).parent / "view.qml"
view.setSource(QUrl.fromLocalFile(qml_file))
diff --git a/examples/quick/painteditem/doc/painteditem.rst b/examples/quick/painteditem/doc/painteditem.rst
index 6f12355ca..5c3e8935b 100644
--- a/examples/quick/painteditem/doc/painteditem.rst
+++ b/examples/quick/painteditem/doc/painteditem.rst
@@ -1,6 +1,8 @@
Scene Graph Painted Item Example
================================
+.. tags:: Android
+
Shows how to implement QPainter-based custom scenegraph items.
The Painted Item example shows how to use the QML Scene Graph framework to
diff --git a/examples/quick/painteditem/painteditem.py b/examples/quick/painteditem/painteditem.py
index 180e22943..cf5a05ada 100644
--- a/examples/quick/painteditem/painteditem.py
+++ b/examples/quick/painteditem/painteditem.py
@@ -1,12 +1,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from argparse import ArgumentParser, RawTextHelpFormatter
from pathlib import Path
import sys
from PySide6.QtGui import QPainter, QBrush, QColor
from PySide6.QtWidgets import QApplication
-from PySide6.QtQml import QmlElement
+from PySide6.QtQml import QmlElement, QQmlDebuggingEnabler
from PySide6.QtCore import QUrl, Property, Signal, Qt, QPointF
from PySide6.QtQuick import QQuickPaintedItem, QQuickView
@@ -62,6 +63,13 @@ class TextBalloon(QQuickPaintedItem):
if __name__ == "__main__":
+ argument_parser = ArgumentParser(description="Scene Graph Painted Item Example",
+ formatter_class=RawTextHelpFormatter)
+ argument_parser.add_argument("-qmljsdebugger", action="store",
+ help="Enable QML debugging")
+ options = argument_parser.parse_args()
+ if options.qmljsdebugger:
+ QQmlDebuggingEnabler.enableDebugging(True)
app = QApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py b/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py
index ae56988aa..69e7321f9 100644
--- a/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py
+++ b/examples/quick/rendercontrol/rendercontrol_opengl/cuberenderer.py
@@ -45,27 +45,27 @@ VERTEXES = numpy.array([-0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5,
-0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5,
-0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5,
- 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
- -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
- -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5,
- 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5],
+ 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
+ -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
+ -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5,
+ 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5],
dtype=numpy.float32)
-TEX_COORDS = numpy.array([0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
- 1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
- 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
- 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
+TEX_COORDS = numpy.array([0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
+ 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
+ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
- 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
- 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
- 0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
- 1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
+ 1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
+ 0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0, 0.0, 0.0, 1.0,
- 0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
- 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
- 1.0, 0.0, 1.0, 1.0, 0.0, 0.0,
- 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], dtype=numpy.float32)
+ 0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
+ 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
+ 1.0, 0.0, 1.0, 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], dtype=numpy.float32)
class CubeRenderer():
@@ -113,7 +113,6 @@ class CubeRenderer():
self.m_vao = QOpenGLVertexArrayObject()
self.m_vao.create()
- vaoBinder = QOpenGLVertexArrayObject.Binder(self.m_vao)
self.m_vbo = QOpenGLBuffer()
self.m_vbo.create()
@@ -166,7 +165,6 @@ class CubeRenderer():
f.glEnable(GL_DEPTH_TEST)
self.m_program.bind()
- vaoBinder = QOpenGLVertexArrayObject.Binder(self.m_vao)
# If VAOs are not supported, set the vertex attributes every time.
if not self.m_vao.isCreated():
self.setupVertexAttribs()
diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py b/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py
index 051647696..6f1e61f94 100644
--- a/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py
+++ b/examples/quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py
@@ -247,7 +247,7 @@ class WindowSingleThreaded(QWindow):
# If self is a resize after the scene is up and running, recreate the
# texture and the Quick item and scene.
if (self.texture_id()
- and self.m_textureSize != self.size() * self.devicePixelRatio()):
+ and self.m_textureSize != self.size() * self.devicePixelRatio()):
self.resizeTexture()
@Slot()
diff --git a/examples/quick/scenegraph/openglunderqml/main.py b/examples/quick/scenegraph/openglunderqml/main.py
index a79d1bed8..0e24877bd 100644
--- a/examples/quick/scenegraph/openglunderqml/main.py
+++ b/examples/quick/scenegraph/openglunderqml/main.py
@@ -8,7 +8,7 @@ from PySide6.QtCore import QUrl
from PySide6.QtGui import QGuiApplication
from PySide6.QtQuick import QQuickView, QQuickWindow, QSGRendererInterface
-from squircle import Squircle
+from squircle import Squircle # noqa: F401
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
diff --git a/examples/quick/window/main.py b/examples/quick/window/main.py
index 549e4251e..62ba6a5e9 100644
--- a/examples/quick/window/main.py
+++ b/examples/quick/window/main.py
@@ -11,12 +11,12 @@ from PySide6.QtQml import QQmlComponent, QQmlEngine
from PySide6.QtQuick import QQuickWindow
from PySide6.QtQuickControls2 import QQuickStyle
-import rc_window
+import rc_window # noqa: F401
# Append the parent directory of this file so that Python can find and
# import from the "shared" sibling directory.
sys.path.append(os.fspath(Path(__file__).parent.parent))
-from shared import shared_rc
+from shared import shared_rc # noqa: F401, E402
if __name__ == "__main__":
diff --git a/examples/quick3d/customgeometry/main.py b/examples/quick3d/customgeometry/main.py
index ff1ce384e..148330d91 100644
--- a/examples/quick3d/customgeometry/main.py
+++ b/examples/quick3d/customgeometry/main.py
@@ -11,9 +11,9 @@ from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuick3D import QQuick3D
# Imports to trigger the resources and registration of QML elements
-import resources_rc
-from examplepoint import ExamplePointGeometry
-from exampletriangle import ExampleTriangleGeometry
+import resources_rc # noqa: F401
+from examplepoint import ExamplePointGeometry # noqa: F401
+from exampletriangle import ExampleTriangleGeometry # noqa: F401
if __name__ == "__main__":
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Basic"
diff --git a/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml b/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml
new file mode 100644
index 000000000..610c08a27
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/ProceduralTextureModule/Main.qml
@@ -0,0 +1,188 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick3D
+import QtQuick3D.Helpers
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ProceduralTextureModule
+
+ApplicationWindow {
+ id: window
+ width: 480
+ height: 320
+ visible: true
+ title: "Procedural Texture Example"
+
+ QtObject {
+ id: applicationState
+ property int size: size256.checked ? 256 : 16
+ property color startColor: "#00dbde"
+ property color endColor: "#fc00ff"
+ property int filterMode: size === 256 ? Texture.Linear : Texture.Nearest
+ property Texture texture: pythonModeRadio.checked ? textureFromPython : textureFromQML
+
+ function randomColor() : color {
+ return Qt.rgba(Math.random(),
+ Math.random(),
+ Math.random(),
+ 1.0);
+ }
+ }
+
+ View3D {
+ anchors.fill: parent
+
+ DirectionalLight {
+ }
+
+ PerspectiveCamera {
+ z: 300
+ }
+
+ Texture {
+ id: textureFromPython
+
+ minFilter: applicationState.filterMode
+ magFilter: applicationState.filterMode
+ textureData: gradientTexture
+
+ GradientTexture {
+ id: gradientTexture
+ startColor: applicationState.startColor
+ endColor: applicationState.endColor
+ width: applicationState.size
+ height: width
+ }
+ }
+
+ Texture {
+ id: textureFromQML
+ minFilter: applicationState.filterMode
+ magFilter: applicationState.filterMode
+ textureData: gradientTextureDataQML
+
+ ProceduralTextureData {
+ id: gradientTextureDataQML
+
+ property color startColor: applicationState.startColor
+ property color endColor: applicationState.endColor
+ width: applicationState.size
+ height: width
+ textureData: generateTextureData()
+
+ function linearInterpolate(startColor : color, endColor : color, fraction : real) : color{
+ return Qt.rgba(
+ startColor.r + (endColor.r - startColor.r) * fraction,
+ startColor.g + (endColor.g - startColor.g) * fraction,
+ startColor.b + (endColor.b - startColor.b) * fraction,
+ startColor.a + (endColor.a - startColor.a) * fraction
+ );
+ }
+
+ function generateTextureData() {
+ let dataBuffer = new ArrayBuffer(width * height * 4)
+ let data = new Uint8Array(dataBuffer)
+
+ let gradientScanline = new Uint8Array(width * 4);
+
+ for (let x = 0; x < width; ++x) {
+ let color = linearInterpolate(startColor, endColor, x / width);
+ let offset = x * 4;
+ gradientScanline[offset + 0] = color.r * 255;
+ gradientScanline[offset + 1] = color.g * 255;
+ gradientScanline[offset + 2] = color.b * 255;
+ gradientScanline[offset + 3] = color.a * 255;
+ }
+
+ for (let y = 0; y < height; ++y) {
+ data.set(gradientScanline, y * width * 4);
+ }
+
+ return dataBuffer;
+ }
+ }
+ }
+
+ Model {
+ source: "#Cube"
+
+ materials: [
+ PrincipledMaterial {
+ baseColorMap: applicationState.texture
+ }
+ ]
+
+ PropertyAnimation on eulerRotation.y {
+ from: 0
+ to: 360
+ duration: 5000
+ loops: Animation.Infinite
+ running: true
+ }
+ }
+ }
+
+ Pane {
+ ColumnLayout {
+
+ GroupBox {
+ title: "Size:"
+
+ ButtonGroup {
+ id: sizeGroup
+ }
+
+ ColumnLayout {
+ RadioButton {
+ id: size256
+ text: "256x256"
+ checked: true
+ ButtonGroup.group: sizeGroup
+ }
+ RadioButton {
+ id: size512
+ text: "16x16"
+ checked: false
+ ButtonGroup.group: sizeGroup
+ }
+ }
+ }
+
+ GroupBox {
+ title: "Backend:"
+
+ ButtonGroup {
+ id: backendGroup
+ }
+
+ ColumnLayout {
+ RadioButton {
+ id: pythonModeRadio
+ text: "Python"
+ checked: true
+ ButtonGroup.group: backendGroup
+ }
+ RadioButton {
+ id: qmlModeRadio
+ text: "QML"
+ checked: false
+ ButtonGroup.group: backendGroup
+ }
+ }
+
+ }
+
+ Button {
+ text: "Random Start Color"
+ onClicked: applicationState.startColor = applicationState.randomColor();
+ }
+ Button {
+ text: "Random End Color"
+ onClicked: applicationState.endColor = applicationState.randomColor();
+ }
+ }
+ }
+}
diff --git a/examples/quick3d/proceduraltexture/ProceduralTextureModule/app.qrc b/examples/quick3d/proceduraltexture/ProceduralTextureModule/app.qrc
new file mode 100644
index 000000000..f0719ad5b
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/ProceduralTextureModule/app.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/qt/qml/ProceduralTextureModule">
+ <file>qmldir</file>
+ <file>Main.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir b/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir
new file mode 100644
index 000000000..7a5644075
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir
@@ -0,0 +1,2 @@
+module ProceduralTextureModule
+Main 1.0 Main.qml
diff --git a/examples/quick3d/proceduraltexture/doc/proceduraltexture-example.webp b/examples/quick3d/proceduraltexture/doc/proceduraltexture-example.webp
new file mode 100644
index 000000000..60bc9a3df
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/doc/proceduraltexture-example.webp
Binary files differ
diff --git a/examples/quick3d/proceduraltexture/doc/proceduraltexture.rst b/examples/quick3d/proceduraltexture/doc/proceduraltexture.rst
new file mode 100644
index 000000000..f2af3ee52
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/doc/proceduraltexture.rst
@@ -0,0 +1,12 @@
+Procedural Texture Example
+==========================
+
+Demonstrates how to provide custom texture data from Python.
+
+.. image:: proceduraltexture-example.webp
+ :width: 400
+ :alt: QtQuick3D Procedural Texture Example
+
+In this example, we leverage `QQuick3DTextureData` and the `textureData`
+property of `Texture` to produce texture data dynamically from Python, rather
+than sourcing it from a static asset.
diff --git a/examples/quick3d/proceduraltexture/gradienttexture.py b/examples/quick3d/proceduraltexture/gradienttexture.py
new file mode 100644
index 000000000..a577f7ebd
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/gradienttexture.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Signal, Property, QSize
+from PySide6.QtGui import QColor
+from PySide6.QtQuick3D import QQuick3DTextureData
+from PySide6.QtQml import QmlElement
+
+QML_IMPORT_NAME = "ProceduralTextureModule"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class GradientTexture(QQuick3DTextureData):
+
+ heightChanged = Signal(int)
+ widthChanged = Signal(int)
+ startColorChanged = Signal(QColor)
+ endColorChanged = Signal(QColor)
+
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ self._height = 256
+ self._width = 256
+ self._startcolor = QColor("#d4fc79")
+ self._endcolor = QColor("#96e6a1")
+ self.updateTexture()
+
+ @Property(int, notify=heightChanged)
+ def height(self):
+ return self._height
+
+ @height.setter
+ def height(self, val):
+ if self._height == val:
+ return
+ self._height = val
+ self.updateTexture()
+ self.heightChanged.emit(self._height)
+
+ @Property(int, notify=widthChanged)
+ def width(self):
+ return self._width
+
+ @width.setter
+ def width(self, val):
+ if self._width == val:
+ return
+ self._width = val
+ self.updateTexture()
+ self.widthChanged.emit(self._width)
+
+ @Property(QColor, notify=startColorChanged)
+ def startColor(self):
+ return self._startcolor
+
+ @startColor.setter
+ def startColor(self, val):
+ if self._startcolor == val:
+ return
+ self._startcolor = val
+ self.updateTexture()
+ self.startColorChanged.emit(self._startcolor)
+
+ @Property(QColor, notify=endColorChanged)
+ def endColor(self):
+ return self._endcolor
+
+ @endColor.setter
+ def endColor(self, val):
+ if self._endcolor == val:
+ return
+ self._endcolor = val
+ self.updateTexture()
+ self.endColorChanged.emit(self._endcolor)
+
+ def updateTexture(self):
+ self.setSize(QSize(self._width, self._height))
+ self.setFormat(QQuick3DTextureData.RGBA8)
+ self.setHasTransparency(False)
+ self.setTextureData(self.generate_texture())
+
+ def generate_texture(self):
+ # Generate a horizontal gradient by interpolating between start and end colors.
+ gradientScanline = [
+ self.linear_interpolate(self._startcolor, self._endcolor, x / self._width)
+ for x in range(self._width)
+ ]
+ # Convert the gradient colors to a flattened list of RGBA values.
+ flattenedGradient = [
+ component
+ for color in gradientScanline
+ for component in (color.red(), color.green(), color.blue(), 255)
+ ]
+ # Repeat the gradient vertically to form the texture.
+ return bytearray(flattenedGradient * self._height)
+
+ def linear_interpolate(self, color1, color2, value):
+ output = QColor()
+
+ output.setRedF(color1.redF() + (value * (color2.redF() - color1.redF())))
+ output.setGreenF(color1.greenF() + (value * (color2.greenF() - color1.greenF())))
+ output.setBlueF(color1.blueF() + (value * (color2.blueF() - color1.blueF())))
+
+ return output
diff --git a/examples/quick3d/proceduraltexture/main.py b/examples/quick3d/proceduraltexture/main.py
new file mode 100644
index 000000000..a732cc015
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/main.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+
+from gradienttexture import GradientTexture # noqa: F401
+
+from pathlib import Path
+
+import os
+import sys
+
+if __name__ == "__main__":
+ app = QGuiApplication(sys.argv)
+ app.setOrganizationName("QtProject")
+ app.setApplicationName("ProceduralTexture")
+
+ engine = QQmlApplicationEngine()
+ app_dir = Path(__file__).parent
+ engine.addImportPath(os.fspath(app_dir))
+ engine.loadFromModule("ProceduralTextureModule", "Main")
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ ex = app.exec()
+ del engine
+
+ sys.exit(ex)
diff --git a/examples/quick3d/proceduraltexture/proceduraltexture.pyproject b/examples/quick3d/proceduraltexture/proceduraltexture.pyproject
new file mode 100644
index 000000000..0815cd8b8
--- /dev/null
+++ b/examples/quick3d/proceduraltexture/proceduraltexture.pyproject
@@ -0,0 +1,9 @@
+{
+ "files": [
+ "main.py",
+ "gradienttexture.py",
+ "ProceduralTextureModule/qmldir",
+ "ProceduralTextureModule/Main.qml",
+ "ProceduralTextureModule/app.qrc"
+ ]
+}
diff --git a/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml
new file mode 100644
index 000000000..affcccc3e
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml
@@ -0,0 +1,82 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+ItemDelegate {
+ id: delegate
+
+ checkable: true
+
+ contentItem: ColumnLayout {
+ spacing: 10
+
+ Label {
+ text: fullName
+ font.bold: true
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+
+ GridLayout {
+ id: grid
+ visible: false
+
+ columns: 2
+ rowSpacing: 10
+ columnSpacing: 10
+
+ Label {
+ text: qsTr("Address:")
+ Layout.leftMargin: 60
+ }
+
+ Label {
+ text: address
+ font.bold: true
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+
+ Label {
+ text: qsTr("City:")
+ Layout.leftMargin: 60
+ }
+
+ Label {
+ text: city
+ font.bold: true
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+
+ Label {
+ text: qsTr("Number:")
+ Layout.leftMargin: 60
+ }
+
+ Label {
+ text: number
+ font.bold: true
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: "expanded"
+ when: delegate.checked
+
+ PropertyChanges {
+ // TODO: When Qt Design Studio supports generalized grouped properties, change to:
+ // grid.visible: true
+ target: grid
+ visible: true
+ }
+ }
+ ]
+}
diff --git a/examples/quickcontrols/contactslist/Contact/ContactDialog.qml b/examples/quickcontrols/contactslist/Contact/ContactDialog.qml
new file mode 100644
index 000000000..d906f00e6
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/ContactDialog.qml
@@ -0,0 +1,45 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+Dialog {
+ id: dialog
+
+ signal finished(string fullName, string address, string city, string number)
+
+ function createContact() {
+ form.fullName.clear();
+ form.address.clear();
+ form.city.clear();
+ form.number.clear();
+
+ dialog.title = qsTr("Add Contact");
+ dialog.open();
+ }
+
+ function editContact(contact) {
+ form.fullName.text = contact.fullName;
+ form.address.text = contact.address;
+ form.city.text = contact.city;
+ form.number.text = contact.number;
+
+ dialog.title = qsTr("Edit Contact");
+ dialog.open();
+ }
+
+ x: parent.width / 2 - width / 2
+ y: parent.height / 2 - height / 2
+
+ focus: true
+ modal: true
+ title: qsTr("Add Contact")
+ standardButtons: Dialog.Ok | Dialog.Cancel
+
+ contentItem: ContactForm {
+ id: form
+ }
+
+ onAccepted: finished(form.fullName.text, form.address.text, form.city.text, form.number.text)
+}
diff --git a/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml
new file mode 100644
index 000000000..56c918619
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/ContactForm.ui.qml
@@ -0,0 +1,72 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+GridLayout {
+ id: grid
+ property alias fullName: fullName
+ property alias address: address
+ property alias city: city
+ property alias number: number
+ property int minimumInputSize: 120
+ property string placeholderText: qsTr("<enter>")
+
+ rows: 4
+ columns: 2
+
+ Label {
+ text: qsTr("Full Name")
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ }
+
+ TextField {
+ id: fullName
+ focus: true
+ Layout.fillWidth: true
+ Layout.minimumWidth: grid.minimumInputSize
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ placeholderText: grid.placeholderText
+ }
+
+ Label {
+ text: qsTr("Address")
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ }
+
+ TextField {
+ id: address
+ Layout.fillWidth: true
+ Layout.minimumWidth: grid.minimumInputSize
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ placeholderText: grid.placeholderText
+ }
+
+ Label {
+ text: qsTr("City")
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ }
+
+ TextField {
+ id: city
+ Layout.fillWidth: true
+ Layout.minimumWidth: grid.minimumInputSize
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ placeholderText: grid.placeholderText
+ }
+
+ Label {
+ text: qsTr("Number")
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ }
+
+ TextField {
+ id: number
+ Layout.fillWidth: true
+ Layout.minimumWidth: grid.minimumInputSize
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
+ placeholderText: grid.placeholderText
+ }
+}
diff --git a/examples/quickcontrols/contactslist/Contact/ContactList.qml b/examples/quickcontrols/contactslist/Contact/ContactList.qml
new file mode 100644
index 000000000..0b7af32b5
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/ContactList.qml
@@ -0,0 +1,70 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ApplicationWindow {
+ id: window
+
+ property int currentContact: -1
+
+ width: 320
+ height: 480
+ visible: true
+ title: qsTr("Contact List")
+
+ ContactDialog {
+ id: contactDialog
+ onFinished: function(fullName, address, city, number) {
+ if (currentContact == -1)
+ contactView.model.append(fullName, address, city, number)
+ else
+ contactView.model.set(currentContact, fullName, address, city, number)
+ }
+ }
+
+ Menu {
+ id: contactMenu
+ x: parent.width / 2 - width / 2
+ y: parent.height / 2 - height / 2
+ modal: true
+
+ Label {
+ padding: 10
+ font.bold: true
+ width: parent.width
+ horizontalAlignment: Qt.AlignHCenter
+ text: currentContact >= 0 ? contactView.model.get(currentContact).fullName : ""
+ }
+ MenuItem {
+ text: qsTr("Edit...")
+ onTriggered: contactDialog.editContact(contactView.model.get(currentContact))
+ }
+ MenuItem {
+ text: qsTr("Remove")
+ onTriggered: contactView.model.remove(currentContact)
+ }
+ }
+
+ ContactView {
+ id: contactView
+ anchors.fill: parent
+ onPressAndHold: {
+ currentContact = index
+ contactMenu.open()
+ }
+ }
+
+ RoundButton {
+ text: qsTr("+")
+ highlighted: true
+ anchors.margins: 10
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ onClicked: {
+ currentContact = -1
+ contactDialog.createContact()
+ }
+ }
+}
diff --git a/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml b/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml
new file mode 100644
index 000000000..3b82b681e
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/ContactView.ui.qml
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import Backend
+
+ListView {
+ id: listView
+
+ signal pressAndHold(int index)
+
+ width: 320
+ height: 480
+
+ focus: true
+ boundsBehavior: Flickable.StopAtBounds
+
+ section.property: "fullName"
+ section.criteria: ViewSection.FirstCharacter
+ section.delegate: SectionDelegate {
+ width: listView.width
+ }
+
+ delegate: ContactDelegate {
+ id: delegate
+ width: listView.width
+ onPressAndHold: listView.pressAndHold(index)
+ }
+
+ model: ContactModel {
+ id: contactModel
+ }
+
+ ScrollBar.vertical: ScrollBar { }
+}
diff --git a/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml b/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml
new file mode 100644
index 000000000..3a62409a8
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ToolBar {
+ id: background
+
+ Label {
+ id: label
+ text: section
+ anchors.fill: parent
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ }
+}
diff --git a/examples/quickcontrols/contactslist/Contact/qmldir b/examples/quickcontrols/contactslist/Contact/qmldir
new file mode 100644
index 000000000..339d45a1d
--- /dev/null
+++ b/examples/quickcontrols/contactslist/Contact/qmldir
@@ -0,0 +1,7 @@
+module Contact
+ContactList 1.0 ContactList.qml
+ContactDialog 1.0 ContactDialog.qml
+ContactDelegate 1.0 ContactDelegate.ui.qml
+ContactForm 1.0 ContactForm.ui.qml
+ContactView 1.0 ContactView.ui.qml
+SectionDelegate 1.0 SectionDelegate.ui.qml
diff --git a/examples/quickcontrols/contactslist/contactlist.pyproject b/examples/quickcontrols/contactslist/contactlist.pyproject
new file mode 100644
index 000000000..75b0bd693
--- /dev/null
+++ b/examples/quickcontrols/contactslist/contactlist.pyproject
@@ -0,0 +1,10 @@
+{
+ "files": ["main.py",
+ "contactmodel.py",
+ "Contact/ContactDialog.qml",
+ "Contact/ContactDelegate.ui.qml",
+ "Contact/ContactForm.ui.qml",
+ "Contact/ContactList.qml",
+ "Contact/ContactView.ui.qml",
+ "Contact/SectionDelegate.ui.qml"]
+}
diff --git a/examples/quickcontrols/contactslist/contactmodel.py b/examples/quickcontrols/contactslist/contactmodel.py
new file mode 100644
index 000000000..5d2746c2e
--- /dev/null
+++ b/examples/quickcontrols/contactslist/contactmodel.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import bisect
+from dataclasses import dataclass
+from enum import IntEnum
+
+from PySide6.QtCore import (QAbstractListModel, QEnum, Qt, QModelIndex, Slot,
+ QByteArray)
+from PySide6.QtQml import QmlElement
+
+QML_IMPORT_NAME = "Backend"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class ContactModel(QAbstractListModel):
+
+ @QEnum
+ class ContactRole(IntEnum):
+ FullNameRole = Qt.DisplayRole
+ AddressRole = Qt.UserRole
+ CityRole = Qt.UserRole + 1
+ NumberRole = Qt.UserRole + 2
+
+ @dataclass
+ class Contact:
+ fullName: str
+ address: str
+ city: str
+ number: str
+
+ def __init__(self, parent=None) -> None:
+ super().__init__(parent)
+ self.m_contacts = []
+ self.m_contacts.append(self.Contact("Angel Hogan", "Chapel St. 368 ", "Clearwater",
+ "0311 1823993"))
+ self.m_contacts.append(self.Contact("Felicia Patton", "Annadale Lane 2", "Knoxville",
+ "0368 1244494"))
+ self.m_contacts.append(self.Contact("Grant Crawford", "Windsor Drive 34", "Riverdale",
+ "0351 7826892"))
+ self.m_contacts.append(self.Contact("Gretchen Little", "Sunset Drive 348", "Virginia Beach",
+ "0343 1234991"))
+ self.m_contacts.append(self.Contact("Geoffrey Richards", "University Lane 54", "Trussville",
+ "0423 2144944"))
+ self.m_contacts.append(self.Contact("Henrietta Chavez", "Via Volto San Luca 3",
+ "Piobesi Torinese", "0399 2826994"))
+ self.m_contacts.append(self.Contact("Harvey Chandler", "North Squaw Creek 11",
+ "Madisonville", "0343 1244492"))
+ self.m_contacts.append(self.Contact("Miguel Gomez", "Wild Rose Street 13", "Trussville",
+ "0343 9826996"))
+ self.m_contacts.append(self.Contact("Norma Rodriguez", " Glen Eagles Street 53",
+ "Buffalo", "0241 5826596"))
+ self.m_contacts.append(self.Contact("Shelia Ramirez", "East Miller Ave 68", "Pickerington",
+ "0346 4844556"))
+ self.m_contacts.append(self.Contact("Stephanie Moss", "Piazza Trieste e Trento 77",
+ "Roata Chiusani", "0363 0510490"))
+
+ def rowCount(self, parent=QModelIndex()):
+ return len(self.m_contacts)
+
+ def data(self, index: QModelIndex, role: int):
+ row = index.row()
+ if row < self.rowCount():
+ if role == ContactModel.ContactRole.FullNameRole:
+ return self.m_contacts[row].fullName
+ elif role == ContactModel.ContactRole.AddressRole:
+ return self.m_contacts[row].address
+ elif role == ContactModel.ContactRole.CityRole:
+ return self.m_contacts[row].city
+ elif role == ContactModel.ContactRole.NumberRole:
+ return self.m_contacts[row].number
+
+ def roleNames(self):
+ default = super().roleNames()
+ default[ContactModel.ContactRole.FullNameRole] = QByteArray(b"fullName")
+ default[ContactModel.ContactRole.AddressRole] = QByteArray(b"address")
+ default[ContactModel.ContactRole.CityRole] = QByteArray(b"city")
+ default[ContactModel.ContactRole.NumberRole] = QByteArray(b"number")
+ return default
+
+ @Slot(int)
+ def get(self, row: int):
+ contact = self.m_contacts[row]
+ return {"fullName": contact.fullName, "address": contact.address,
+ "city": contact.city, "number": contact.number}
+
+ @Slot(str, str, str, str)
+ def append(self, full_name: str, address: str, city: str, number: str):
+ contact = self.Contact(full_name, address, city, number)
+ contact_names = [contact.fullName for contact in self.m_contacts]
+ index = bisect.bisect(contact_names, contact.fullName)
+ self.beginInsertRows(QModelIndex(), index, index)
+ self.m_contacts.insert(index, contact)
+ self.endInsertRows()
+
+ @Slot(int, str, str, str, str)
+ def set(self, row: int, full_name: str, address: str, city: str, number: str):
+ if row < 0 or row >= len(self.m_contacts):
+ return
+
+ self.m_contacts[row] = self.Contact(full_name, address, city, number)
+ self.dataChanged(self.index(row, 0), self.index(row, 0),
+ [ContactModel.ContactRole.FullNameRole,
+ ContactModel.ContactRole.AddressRole,
+ ContactModel.ContactRole.CityRole,
+ ContactModel.ContactRole.NumberRole])
+
+ @Slot(int)
+ def remove(self, row):
+ if row < 0 or row >= len(self.m_contacts):
+ return
+
+ self.beginRemoveRows(QModelIndex(), row, row)
+ del self.m_contacts[row]
+ self.endRemoveRows()
diff --git a/examples/quickcontrols/contactslist/doc/contactslist.rst b/examples/quickcontrols/contactslist/doc/contactslist.rst
new file mode 100644
index 000000000..b5540e39b
--- /dev/null
+++ b/examples/quickcontrols/contactslist/doc/contactslist.rst
@@ -0,0 +1,15 @@
+Qt Quick Controls - Contact List
+================================
+
+.. tags:: Android
+
+A QML app using Qt Quick Controls and a Python class that implements a simple
+contact list. This example can also be deployed to Android using
+**pyside6-android-deploy**
+
+A PySide6 application that demonstrates the analogous example in Qt
+`ContactsList <https://doc.qt.io/qt-6.6/qtquickcontrols-contactlist-example.html>`_
+
+.. image:: qtquickcontrols-contactlist.png
+ :width: 400
+ :alt: ContactList Screenshot
diff --git a/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png b/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png
new file mode 100644
index 000000000..9f1c30654
--- /dev/null
+++ b/examples/quickcontrols/contactslist/doc/qtquickcontrols-contactlist.png
Binary files differ
diff --git a/examples/quickcontrols/contactslist/main.py b/examples/quickcontrols/contactslist/main.py
new file mode 100644
index 000000000..d501bbdb4
--- /dev/null
+++ b/examples/quickcontrols/contactslist/main.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""
+PySide6 port of Qt Quick Controls Contact List example from Qt v6.x
+"""
+import sys
+from pathlib import Path
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+
+from contactmodel import ContactModel # noqa: F401
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ app.setOrganizationName("QtProject")
+ app.setApplicationName("ContactsList")
+ engine = QQmlApplicationEngine()
+
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("Contact", "ContactList")
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ ex = app.exec()
+ del engine
+ sys.exit(ex)
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml
index 4dfc1590d..7f7798ed8 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml
@@ -6,46 +6,68 @@ import QtQuick.Controls.Basic
import QtQuick.Layouts
import FileSystemModule
+pragma ComponentBehavior: Bound
+
ApplicationWindow {
id: root
+
+ property bool expandPath: false
+ property bool showLineNumbers: true
+ property string currentFilePath: ""
+
width: 1100
height: 600
+ minimumWidth: 200
+ minimumHeight: 100
visible: true
+ color: Colors.background
flags: Qt.Window | Qt.FramelessWindowHint
- title: qsTr("Qt Quick Controls - File System Explorer")
+ title: qsTr("File System Explorer Example")
- property string currentFilePath: ""
- property bool expandPath: false
+ function getInfoText() : string {
+ let out = root.currentFilePath
+ if (!out)
+ return qsTr("File System Explorer")
+ return root.expandPath ? out : out.substring(out.lastIndexOf("/") + 1, out.length)
+ }
menuBar: MyMenuBar {
- rootWindow: root
-
- infoText: currentFilePath
- ? (expandPath ? currentFilePath
- : currentFilePath.substring(currentFilePath.lastIndexOf("/") + 1, currentFilePath.length))
- : "File System Explorer"
-
+ dragWindow: root
+ infoText: root.getInfoText()
MyMenu {
title: qsTr("File")
Action {
text: qsTr("Increase Font")
- shortcut: "Ctrl++"
- onTriggered: textArea.font.pixelSize += 1
+ shortcut: StandardKey.ZoomIn
+ onTriggered: editor.text.font.pixelSize += 1
}
Action {
text: qsTr("Decrease Font")
- shortcut: "Ctrl+-"
- onTriggered: textArea.font.pixelSize -= 1
+ shortcut: StandardKey.ZoomOut
+ onTriggered: editor.text.font.pixelSize -= 1
}
Action {
- text: expandPath ? qsTr("Toggle Short Path") : qsTr("Toggle Expand Path")
- enabled: currentFilePath
- onTriggered: expandPath = !expandPath
+ text: root.showLineNumbers ? qsTr("Toggle Line Numbers OFF")
+ : qsTr("Toggle Line Numbers ON")
+ shortcut: "Ctrl+L"
+ onTriggered: root.showLineNumbers = !root.showLineNumbers
+ }
+ Action {
+ text: root.expandPath ? qsTr("Toggle Short Path")
+ : qsTr("Toggle Expand Path")
+ enabled: root.currentFilePath
+ onTriggered: root.expandPath = !root.expandPath
+ }
+ Action {
+ text: qsTr("Reset Filesystem")
+ enabled: sidebar.currentTabIndex === 1
+ onTriggered: fileSystemView.rootIndex = undefined
}
Action {
text: qsTr("Exit")
onTriggered: Qt.exit(0)
+ shortcut: StandardKey.Quit
}
}
@@ -55,134 +77,109 @@ ApplicationWindow {
Action {
text: qsTr("Cut")
shortcut: StandardKey.Cut
- enabled: textArea.selectedText.length > 0
- onTriggered: textArea.cut()
+ enabled: editor.text.selectedText.length > 0
+ onTriggered: editor.text.cut()
}
Action {
text: qsTr("Copy")
shortcut: StandardKey.Copy
- enabled: textArea.selectedText.length > 0
- onTriggered: textArea.copy()
+ enabled: editor.text.selectedText.length > 0
+ onTriggered: editor.text.copy()
}
Action {
text: qsTr("Paste")
shortcut: StandardKey.Paste
- enabled: textArea.canPaste
- onTriggered: textArea.paste()
+ enabled: editor.text.canPaste
+ onTriggered: editor.text.paste()
}
Action {
text: qsTr("Select All")
shortcut: StandardKey.SelectAll
- enabled: textArea.length > 0
- onTriggered: textArea.selectAll()
+ enabled: editor.text.length > 0
+ onTriggered: editor.text.selectAll()
}
Action {
text: qsTr("Undo")
shortcut: StandardKey.Undo
- enabled: textArea.canUndo
- onTriggered: textArea.undo()
+ enabled: editor.text.canUndo
+ onTriggered: editor.text.undo()
}
}
}
-
- Rectangle {
+ // Set up the layout of the main components in a row:
+ // [ Sidebar, Navigation, Editor ]
+ RowLayout {
anchors.fill: parent
- color: Colors.background
-
- RowLayout {
- anchors.fill: parent
- spacing: 0
-
- // Stores the buttons that navigate the application.
- Sidebar {
- id: sidebar
- rootWindow: root
-
- Layout.preferredWidth: 60
- Layout.fillHeight: true
- }
+ spacing: 0
+
+ // Stores the buttons that navigate the application.
+ Sidebar {
+ id: sidebar
+ dragWindow: root
+ Layout.preferredWidth: 50
+ Layout.fillHeight: true
+ }
- // Allows resizing parts of the UI.
- SplitView {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- handle: Rectangle {
- implicitWidth: 10
- color: SplitHandle.pressed ? Colors.color2 : Colors.background
- border.color: Colors.color2
- opacity: SplitHandle.hovered || SplitHandle.pressed ? 1.0 : 0.0
-
- Behavior on opacity {
- OpacityAnimator {
- duration: 900
- }
+ // Allows resizing parts of the UI.
+ SplitView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ // Customized handle to drag between the Navigation and the Editor.
+ handle: Rectangle {
+ implicitWidth: 10
+ color: SplitHandle.pressed ? Colors.color2 : Colors.background
+ border.color: SplitHandle.hovered ? Colors.color2 : Colors.background
+ opacity: SplitHandle.hovered || navigationView.width < 15 ? 1.0 : 0.0
+
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 1400
}
}
+ }
- // We use an inline component to make a reusable TextArea component.
- // This is convenient when the component is only used in one file.
- component MyTextArea: TextArea {
- antialiasing: true
- color: Colors.textFile
- selectedTextColor: Colors.textFile
- selectionColor: Colors.selection
- renderType: Text.QtRendering
- textFormat: TextEdit.PlainText
-
- background: null
- }
-
- Rectangle {
- color: Colors.surface1
-
- SplitView.preferredWidth: 250
- SplitView.fillHeight: true
-
- StackLayout {
- currentIndex: sidebar.currentTabIndex
-
- anchors.fill: parent
-
- // Shows the help text.
- MyTextArea {
- readOnly: true
- text: qsTr("This example shows how to use and visualize the file system.\n\n"
- + "Customized Qt Quick Components have been used to achieve this look.\n\n"
- + "You can edit the files but they won't be changed on the file system.\n\n"
- + "Click on the folder icon to the left to get started.")
- wrapMode: TextArea.Wrap
- }
-
- // Shows the files on the file system.
- FileSystemView {
- id: fileSystemView
- color: Colors.surface1
-
- onFileClicked: (path) => root.currentFilePath = path
- }
+ Rectangle {
+ id: navigationView
+ color: Colors.surface1
+ SplitView.preferredWidth: 250
+ SplitView.fillHeight: true
+ // The stack-layout provides different views, based on the
+ // selected buttons inside the sidebar.
+ StackLayout {
+ anchors.fill: parent
+ currentIndex: sidebar.currentTabIndex
+
+ // Shows the help text.
+ Text {
+ text: qsTr("This example shows how to use and visualize the file system.\n\n"
+ + "Customized Qt Quick Components have been used to achieve this look.\n\n"
+ + "You can edit the files but they won't be changed on the file system.\n\n"
+ + "Click on the folder icon to the left to get started.")
+ wrapMode: TextArea.Wrap
+ color: Colors.text
}
- }
-
- // The ScrollView that contains the TextArea which shows the file's content.
- ScrollView {
- leftPadding: 20
- topPadding: 20
- bottomPadding: 20
- clip: true
-
- SplitView.fillWidth: true
- SplitView.fillHeight: true
-
- property alias textArea: textArea
- MyTextArea {
- id: textArea
- text: FileSystemModel.readFile(root.currentFilePath)
+ // Shows the files on the file system.
+ FileSystemView {
+ id: fileSystemView
+ color: Colors.surface1
+ onFileClicked: path => root.currentFilePath = path
}
}
}
+
+ // The main view that contains the editor.
+ Editor {
+ id: editor
+ showLineNumbers: root.showLineNumbers
+ currentFilePath: root.currentFilePath
+ SplitView.fillWidth: true
+ SplitView.fillHeight: true
+ }
}
- ResizeButton {}
+ }
+
+ ResizeButton {
+ resizeWindow: root
}
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc b/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc
index 05fc728e7..fec76fe67 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/app.qrc
@@ -3,9 +3,9 @@
<file>qmldir</file>
<file>Main.qml</file>
<file>qml/About.qml</file>
+ <file>qml/Editor.qml</file>
<file>qml/Colors.qml</file>
<file>qml/FileSystemView.qml</file>
- <file>qml/Icon.qml</file>
<file>qml/MyMenu.qml</file>
<file>qml/MyMenuBar.qml</file>
<file>qml/ResizeButton.qml</file>
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc
index 5793a62cf..97d8a3d79 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons.qrc
@@ -1,5 +1,6 @@
<RCC>
<qresource>
+ <file>icons/app_icon.svg</file>
<file>icons/folder_closed.svg</file>
<file>icons/folder_open.svg</file>
<file>icons/generic_file.svg</file>
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg
new file mode 100644
index 000000000..5aae4221f
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/icons/app_icon.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#EBDBB2" d="M13.25 8.5a.75.75 0 1 1-.75-.75.75.75 0 0 1 .75.75zM9.911 21.35l.816.578C10.819 21.798 13 18.666 13 13h-1a15.503 15.503 0 0 1-2.089 8.35zM4 6.703V10a2.002 2.002 0 0 1-2 2v1a2.002 2.002 0 0 1 2 2v3.297A3.707 3.707 0 0 0 7.703 22H9v-1H7.703A2.706 2.706 0 0 1 5 18.297V15a2.999 2.999 0 0 0-1.344-2.5A2.999 2.999 0 0 0 5 10V6.703A2.706 2.706 0 0 1 7.703 4H9V3H7.703A3.707 3.707 0 0 0 4 6.703zM20 10V6.703A3.707 3.707 0 0 0 16.297 3H15v1h1.297A2.706 2.706 0 0 1 19 6.703V10a2.999 2.999 0 0 0 1.344 2.5A2.999 2.999 0 0 0 19 15v3.297A2.706 2.706 0 0 1 16.297 21H15v1h1.297A3.707 3.707 0 0 0 20 18.297V15a2.002 2.002 0 0 1 2-2v-1a2.002 2.002 0 0 1-2-2z"/><path fill="none" d="M0 0h24v24H0z"/></svg>
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml
index b7bc0ac6f..178bf03e4 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml
@@ -7,53 +7,87 @@ import FileSystemModule
ApplicationWindow {
id: root
- width: 500
- height: 360
+ width: 650
+ height: 550
flags: Qt.Window | Qt.FramelessWindowHint
color: Colors.surface1
menuBar: MyMenuBar {
id: menuBar
- implicitHeight: 20
- rootWindow: root
+
+ dragWindow: root
+ implicitHeight: 27
infoText: "About Qt"
}
Image {
id: logo
+
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 20
+
source: "../icons/qt_logo.svg"
- sourceSize: Qt.size(80, 80)
+ sourceSize.width: 80
+ sourceSize.height: 80
fillMode: Image.PreserveAspectFit
+
smooth: true
antialiasing: true
asynchronous: true
}
- TextArea {
- anchors.top: logo.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.margins: 20
- antialiasing: true
- wrapMode: Text.WrapAnywhere
- color: Colors.textFile
- horizontalAlignment: Text.AlignHCenter
- readOnly: true
- selectionColor: Colors.selection
- text: qsTr("Qt Group (Nasdaq Helsinki: QTCOM) is a global software company with a strong \
-presence in more than 70 industries and is the leading independent technology behind 1+ billion \
-devices and applications. Qt is used by major global companies and developers worldwide, and the \
-technology enables its customers to deliver exceptional user experiences and advance their digital \
-transformation initiatives. Qt achieves this through its cross-platform software framework for the \
-development of apps and devices, under both commercial and open-source licenses.")
- background: Rectangle {
- color: "transparent"
- }
+ ScrollView {
+ anchors.top: logo.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 20
+
+ TextArea {
+ selectedTextColor: Colors.textFile
+ selectionColor: Colors.selection
+ horizontalAlignment: Text.AlignHCenter
+ textFormat: Text.RichText
+
+ text: qsTr("<h3>About Qt</h3>"
+ + "<p>This program uses Qt version %1.</p>"
+ + "<p>Qt is a C++ toolkit for cross-platform application "
+ + "development.</p>"
+ + "<p>Qt provides single-source portability across all major desktop "
+ + "operating systems. It is also available for embedded Linux and other "
+ + "embedded and mobile operating systems.</p>"
+ + "<p>Qt is available under multiple licensing options designed "
+ + "to accommodate the needs of our various users.</p>"
+ + "<p>Qt licensed under our commercial license agreement is appropriate "
+ + "for development of proprietary/commercial software where you do not "
+ + "want to share any source code with third parties or otherwise cannot "
+ + "comply with the terms of GNU (L)GPL.</p>"
+ + "<p>Qt licensed under GNU (L)GPL is appropriate for the "
+ + "development of Qt&nbsp;applications provided you can comply with the terms "
+ + "and conditions of the respective licenses.</p>"
+ + "<p>Please see <a href=\"http://%2/\">%2</a> "
+ + "for an overview of Qt licensing.</p>"
+ + "<p>Copyright (C) %3 The Qt Company Ltd and other "
+ + "contributors.</p>"
+ + "<p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p>"
+ + "<p>Qt is The Qt Company Ltd product developed as an open source "
+ + "project. See <a href=\"http://%4/\">%4</a> for more information.</p>")
+ .arg(Application.version).arg("qt.io/licensing").arg("2023").arg("qt.io")
+ color: Colors.textFile
+ wrapMode: Text.WordWrap
+ readOnly: true
+ antialiasing: true
+ background: null
+
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link)
+ }
+ }
+ }
+
+ ResizeButton {
+ resizeWindow: root
}
- ResizeButton {}
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml
index 280f89286..285667773 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Colors.qml
@@ -1,22 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-pragma Singleton
import QtQuick
+pragma Singleton
+
QtObject {
- readonly property color background: "#23272E"
- readonly property color surface1: "#1E2227"
+ readonly property color background: "#292828"
+ readonly property color surface1: "#171819"
readonly property color surface2: "#090A0C"
- readonly property color text: "#ABB2BF"
- readonly property color textFile: "#C5CAD3"
- readonly property color disabledText: "#454D5F"
- readonly property color selection: "#2C313A"
- readonly property color active: "#23272E"
- readonly property color inactive: "#3E4452"
- readonly property color folder: "#3D4451"
- readonly property color icon: "#3D4451"
- readonly property color iconIndicator: "#E5C07B"
- readonly property color color1: "#E06B74"
- readonly property color color2: "#62AEEF"
+ readonly property color text: "#D4BE98"
+ readonly property color textFile: "#E1D2B7"
+ readonly property color disabledText: "#2C313A"
+ readonly property color selection: "#4B4A4A"
+ readonly property color active: "#292828"
+ readonly property color inactive: "#383737"
+ readonly property color folder: "#383737"
+ readonly property color icon: "#383737"
+ readonly property color iconIndicator: "#D5B35D"
+ readonly property color color1: "#A7B464"
+ readonly property color color2: "#D3869B"
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml
new file mode 100644
index 000000000..80f7c04c5
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml
@@ -0,0 +1,160 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import FileSystemModule
+
+pragma ComponentBehavior: Bound
+
+// This is the text editor that displays the currently open file, including
+// their corresponding line numbers.
+Rectangle {
+ id: root
+
+ required property string currentFilePath
+ required property bool showLineNumbers
+ property alias text: textArea
+ property int currentLineNumber: -1
+ property int rowHeight: Math.ceil(fontMetrics.lineSpacing)
+
+ color: Colors.background
+
+ onWidthChanged: textArea.update()
+ onHeightChanged: textArea.update()
+
+ RowLayout {
+ anchors.fill: parent
+ // We use a flickable to synchronize the position of the editor and
+ // the line numbers. This is necessary because the line numbers can
+ // extend the available height.
+ Flickable {
+ id: lineNumbers
+
+ // Calculate the width based on the logarithmic scale.
+ Layout.preferredWidth: fontMetrics.averageCharacterWidth
+ * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
+ Layout.fillHeight: true
+
+ interactive: false
+ contentY: editorFlickable.contentY
+ visible: textArea.text !== "" && root.showLineNumbers
+
+ Column {
+ anchors.fill: parent
+ Repeater {
+ id: repeatedLineNumbers
+
+ model: LineNumberModel {
+ lineCount: textArea.text !== "" ? textArea.lineCount : 0
+ }
+
+ delegate: Item {
+ required property int index
+
+ width: parent.width
+ height: root.rowHeight
+ Label {
+ id: numbers
+
+ text: parent.index + 1
+
+ width: parent.width
+ height: parent.height
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+
+ color: (root.currentLineNumber === parent.index)
+ ? Colors.iconIndicator : Qt.darker(Colors.text, 2)
+ font: textArea.font
+ }
+ Rectangle {
+ id: indicator
+
+ anchors.left: numbers.right
+ width: 1
+ height: parent.height
+ color: Qt.darker(Colors.text, 3)
+ }
+ }
+ }
+ }
+ }
+
+ Flickable {
+ id: editorFlickable
+
+ property alias textArea: textArea
+
+ // We use an inline component to customize the horizontal and vertical
+ // scroll-bars. This is convenient when the component is only used in one file.
+ component MyScrollBar: ScrollBar {
+ id: scrollBar
+ background: Rectangle {
+ implicitWidth: scrollBar.interactive ? 8 : 4
+ implicitHeight: scrollBar.interactive ? 8 : 4
+
+ opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
+ color: Colors.background
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 500
+ }
+ }
+ }
+ contentItem: Rectangle {
+ implicitWidth: scrollBar.interactive ? 8 : 4
+ implicitHeight: scrollBar.interactive ? 8 : 4
+ opacity: scrollBar.active && scrollBar.size < 1.0 ? 1.0 : 0.0
+ color: Colors.color1
+ Behavior on opacity {
+ OpacityAnimator {
+ duration: 1000
+ }
+ }
+ }
+ }
+
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ ScrollBar.horizontal: MyScrollBar {}
+ ScrollBar.vertical: MyScrollBar {}
+
+ boundsBehavior: Flickable.StopAtBounds
+
+ TextArea.flickable: TextArea {
+ id: textArea
+ anchors.fill: parent
+
+ focus: false
+ topPadding: 0
+ leftPadding: 10
+
+ text: FileSystemModel.readFile(root.currentFilePath)
+ tabStopDistance: fontMetrics.averageCharacterWidth * 4
+
+ // Grab the current line number from the C++ interface.
+ onCursorPositionChanged: {
+ root.currentLineNumber = FileSystemModel.currentLineNumber(
+ textArea.textDocument, textArea.cursorPosition)
+ }
+
+ color: Colors.textFile
+ selectedTextColor: Colors.textFile
+ selectionColor: Colors.selection
+
+ textFormat: TextEdit.PlainText
+ renderType: Text.QtRendering
+ selectByMouse: true
+ antialiasing: true
+ background: null
+ }
+
+ FontMetrics {
+ id: fontMetrics
+ font: textArea.font
+ }
+ }
+ }
+}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml
index ade2e48c1..db955168c 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/FileSystemView.qml
@@ -2,26 +2,31 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
-import QtQuick.Layouts
+import QtQuick.Effects
import QtQuick.Controls.Basic
import FileSystemModule
+pragma ComponentBehavior: Bound
+
// This is the file system view which gets populated by the C++ model.
Rectangle {
id: root
signal fileClicked(string filePath)
+ property alias rootIndex: fileSystemTreeView.rootIndex
TreeView {
id: fileSystemTreeView
+
+ property int lastIndex: -1
+
anchors.fill: parent
model: FileSystemModel
+ rootIndex: FileSystemModel.rootIndex
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds
clip: true
- property int lastIndex: -1
-
Component.onCompleted: fileSystemTreeView.toggleExpanded(0)
// The delegate represents a single entry in the filesystem.
@@ -31,50 +36,101 @@ Rectangle {
implicitWidth: fileSystemTreeView.width > 0 ? fileSystemTreeView.width : 250
implicitHeight: 25
+ // Since we have the 'ComponentBehavior Bound' pragma, we need to
+ // require these properties from our model. This is a convenient way
+ // to bind the properties provided by the model's role names.
required property int index
required property url filePath
+ required property string fileName
- indicator: null
-
- contentItem: Item {
- anchors.fill: parent
+ indicator: Image {
+ id: directoryIcon
- Icon {
- id: directoryIcon
- x: leftMargin + (depth * indentation)
- anchors.verticalCenter: parent.verticalCenter
- path: treeDelegate.hasChildren
- ? (treeDelegate.expanded ? "../icons/folder_open.svg" : "../icons/folder_closed.svg")
+ x: treeDelegate.leftMargin + (treeDelegate.depth * treeDelegate.indentation)
+ anchors.verticalCenter: parent.verticalCenter
+ source: treeDelegate.hasChildren ? (treeDelegate.expanded
+ ? "../icons/folder_open.svg" : "../icons/folder_closed.svg")
: "../icons/generic_file.svg"
- iconColor: (treeDelegate.expanded && treeDelegate.hasChildren) ? Colors.color2 : Colors.folder
- }
- Text {
- anchors.left: directoryIcon.right
- anchors.verticalCenter: parent.verticalCenter
- width: parent.width
- text: model.fileName
- color: Colors.text
- }
+ sourceSize.width: 20
+ sourceSize.height: 20
+ fillMode: Image.PreserveAspectFit
+
+ smooth: true
+ antialiasing: true
+ asynchronous: true
+ }
+
+ contentItem: Text {
+ text: treeDelegate.fileName
+ color: Colors.text
}
background: Rectangle {
- color: treeDelegate.index === fileSystemTreeView.lastIndex
+ color: (treeDelegate.index === fileSystemTreeView.lastIndex)
? Colors.selection
: (hoverHandler.hovered ? Colors.active : "transparent")
}
- TapHandler {
- onSingleTapped: {
- fileSystemTreeView.toggleExpanded(row)
- fileSystemTreeView.lastIndex = index
- // If this model item doesn't have children, it means it's representing a file.
- if (!treeDelegate.hasChildren)
- root.fileClicked(filePath)
+ // We color the directory icons with this MultiEffect, where we overlay
+ // the colorization color ontop of the SVG icons.
+ MultiEffect {
+ id: iconOverlay
+
+ anchors.fill: directoryIcon
+ source: directoryIcon
+ colorization: 1.0
+ brightness: 1.0
+ colorizationColor: {
+ const isFile = treeDelegate.index === fileSystemTreeView.lastIndex
+ && !treeDelegate.hasChildren;
+ if (isFile)
+ return Qt.lighter(Colors.folder, 3)
+
+ const isExpandedFolder = treeDelegate.expanded && treeDelegate.hasChildren;
+ if (isExpandedFolder)
+ return Colors.color2
+ else
+ return Colors.folder
}
}
+
HoverHandler {
id: hoverHandler
}
+
+ TapHandler {
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onSingleTapped: (eventPoint, button) => {
+ switch (button) {
+ case Qt.LeftButton:
+ fileSystemTreeView.toggleExpanded(treeDelegate.row)
+ fileSystemTreeView.lastIndex = treeDelegate.index
+ // If this model item doesn't have children, it means it's
+ // representing a file.
+ if (!treeDelegate.hasChildren)
+ root.fileClicked(treeDelegate.filePath)
+ break;
+ case Qt.RightButton:
+ if (treeDelegate.hasChildren)
+ contextMenu.popup();
+ break;
+ }
+ }
+ }
+
+ MyMenu {
+ id: contextMenu
+ Action {
+ text: qsTr("Set as root index")
+ onTriggered: {
+ fileSystemTreeView.rootIndex = fileSystemTreeView.index(treeDelegate.row, 0)
+ }
+ }
+ Action {
+ text: qsTr("Reset root index")
+ onTriggered: fileSystemTreeView.rootIndex = undefined
+ }
+ }
}
// Provide our own custom ScrollIndicator for the TreeView.
@@ -85,6 +141,7 @@ Rectangle {
contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 6
+
color: Colors.color1
opacity: fileSystemTreeView.movingVertically ? 0.5 : 0.0
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml
deleted file mode 100644
index 25162d9d3..000000000
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Icon.qml
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Effects
-
-// Custom Component for displaying Icons
-Item {
- id: root
-
- required property url path
- property real padding: 5
- property real size: 30
- property alias iconColor: overlay.colorizationColor
- property alias hovered: mouse.hovered
-
- width: size
- height: size
-
- Image {
- id: icon
- anchors.fill: root
- anchors.margins: padding
- source: path
- sourceSize: Qt.size(size, size)
- fillMode: Image.PreserveAspectFit
- smooth: true
- antialiasing: true
- asynchronous: true
- }
-
- MultiEffect {
- id: overlay
- anchors.fill: icon
- source: icon
- colorization: 1.0
- brightness: 1.0
- }
-
- HoverHandler {
- id: mouse
- acceptedDevices: PointerDevice.Mouse
- }
-}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml
index 99795b5e5..1f1d30c56 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenu.qml
@@ -8,35 +8,38 @@ import FileSystemModule
Menu {
id: root
- background: Rectangle {
- implicitWidth: 200
- implicitHeight: 40
- color: Colors.surface2
- }
-
delegate: MenuItem {
id: menuItem
- implicitWidth: 200
- implicitHeight: 40
contentItem: Item {
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 5
+
text: menuItem.text
color: enabled ? Colors.text : Colors.disabledText
}
Rectangle {
+ id: indicator
+
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: 6
height: parent.height
+
visible: menuItem.highlighted
color: Colors.color2
}
}
background: Rectangle {
+ implicitWidth: 210
+ implicitHeight: 35
color: menuItem.highlighted ? Colors.active : "transparent"
}
}
+ background: Rectangle {
+ implicitWidth: 210
+ implicitHeight: 35
+ color: Colors.surface2
+ }
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml
index a2a3fea88..4874a2c03 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/MyMenuBar.qml
@@ -6,130 +6,172 @@ import QtQuick.Layouts
import QtQuick.Controls.Basic
import FileSystemModule
-// The MenuBar also serves as a controller for our Window as we don't use any decorations.
+// The MenuBar also serves as a controller for our window as we don't use any decorations.
MenuBar {
id: root
- required property ApplicationWindow rootWindow
+ required property ApplicationWindow dragWindow
property alias infoText: windowInfo.text
- implicitHeight: 25
-
- // The top level menus on the left side
+ // Customization of the top level menus inside the MenuBar
delegate: MenuBarItem {
id: menuBarItem
- implicitHeight: 25
contentItem: Text {
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
- color: menuBarItem.highlighted ? Colors.textFile : Colors.text
- opacity: enabled ? 1.0 : 0.3
+
text: menuBarItem.text
- elide: Text.ElideRight
font: menuBarItem.font
+ elide: Text.ElideRight
+ color: menuBarItem.highlighted ? Colors.textFile : Colors.text
+ opacity: enabled ? 1.0 : 0.3
}
background: Rectangle {
+ id: background
+
color: menuBarItem.highlighted ? Colors.selection : "transparent"
Rectangle {
id: indicator
+
width: 0; height: 3
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
- color: Colors.color1
+ color: Colors.color1
states: State {
- name: "active"; when: menuBarItem.highlighted
- PropertyChanges { target: indicator; width: parent.width }
+ name: "active"
+ when: menuBarItem.highlighted
+ PropertyChanges {
+ indicator.width: background.width - 2
+ }
}
-
transitions: Transition {
NumberAnimation {
properties: "width"
- duration: 300
+ duration: 175
}
}
-
}
}
}
+ // We use the contentItem property as a place to attach our window decorations. Beneath
+ // the usual menu entries within a MenuBar, it includes a centered information text, along
+ // with the minimize, maximize, and close buttons.
+ contentItem: RowLayout {
+ id: windowBar
- // The background property contains an information text in the middle as well as the
- // Minimize, Maximize and Close Buttons.
- background: Rectangle {
- color: Colors.surface2
- // Make the empty space drag the specified root window.
- WindowDragHandler { dragWindow: rootWindow }
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ spacing: root.spacing
+ Repeater {
+ id: menuBarItems
- Text {
- id: windowInfo
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.verticalCenter: parent.verticalCenter
- color: Colors.text
+ Layout.alignment: Qt.AlignLeft
+ model: root.contentModel
}
- component InteractionButton: Rectangle {
- signal action;
- property alias hovered: hoverHandler.hovered
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Text {
+ id: windowInfo
+
+ width: parent.width; height: parent.height
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: windowActions.width
+ color: Colors.text
+ clip: true
+ }
+ }
- width: root.height
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- color: hovered ? Colors.background : "transparent"
+ RowLayout {
+ id: windowActions
- HoverHandler { id: hoverHandler }
- TapHandler { onTapped: action() }
- }
+ Layout.alignment: Qt.AlignRight
+ Layout.fillHeight: true
- InteractionButton {
- id: minimize
+ spacing: 0
- anchors.right: maximize.left
- onAction: rootWindow.showMinimized()
- Rectangle {
- width: parent.height - 10; height: 2
- anchors.centerIn: parent
- color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ component InteractionButton: Rectangle {
+ id: interactionButton
+
+ signal action()
+ property alias hovered: hoverHandler.hovered
+
+ Layout.fillHeight: true
+ Layout.preferredWidth: height
+
+ color: hovered ? Colors.background : "transparent"
+ HoverHandler {
+ id: hoverHandler
+ }
+ TapHandler {
+ id: tapHandler
+ onTapped: interactionButton.action()
+ }
}
- }
- InteractionButton {
- id: maximize
+ InteractionButton {
+ id: minimize
- anchors.right: close.left
- onAction: rootWindow.showMaximized()
- Rectangle {
- anchors.fill: parent
- anchors.margins: 5
- border.width: 2
- color: "transparent"
- border.color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ onAction: root.dragWindow.showMinimized()
+ Rectangle {
+ anchors.centerIn: parent
+ color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ height: 2
+ width: parent.height - 14
+ }
}
- }
- InteractionButton {
- id: close
+ InteractionButton {
+ id: maximize
- color: hovered ? "#ec4143" : "transparent"
- anchors.right: parent.right
- onAction: rootWindow.close()
- Rectangle {
- width: parent.height - 8; height: 2
- anchors.centerIn: parent
- color: parent.hovered ? Colors.iconIndicator : Colors.icon
- rotation: 45
- transformOrigin: Item.Center
- antialiasing: true
+ onAction: root.dragWindow.showMaximized()
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 7
+ border.color: parent.hovered ? Colors.iconIndicator : Colors.icon
+ border.width: 2
+ color: "transparent"
+ }
+ }
+
+ InteractionButton {
+ id: close
+
+ color: hovered ? "#ec4143" : "transparent"
+ onAction: root.dragWindow.close()
Rectangle {
- width: parent.height
- height: parent.width
anchors.centerIn: parent
- color: parent.color
+ width: parent.height - 8; height: 2
+
+ rotation: 45
antialiasing: true
+ transformOrigin: Item.Center
+ color: parent.hovered ? Colors.iconIndicator : Colors.icon
+
+ Rectangle {
+ anchors.centerIn: parent
+ width: parent.height
+ height: parent.width
+
+ antialiasing: true
+ color: parent.color
+ }
}
}
}
}
+ background: Rectangle {
+ color: Colors.surface2
+ // Make the empty space drag the specified root window.
+ WindowDragHandler {
+ dragWindow: root.dragWindow
+ }
+ }
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml
index eb2e5bc02..0df65bf82 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml
@@ -5,6 +5,8 @@ import QtQuick.Controls
import FileSystemModule
Button {
+ required property ApplicationWindow resizeWindow
+
icon.width: 20; icon.height: 20
anchors.right: parent.right
anchors.bottom: parent.bottom
@@ -12,12 +14,10 @@ Button {
bottomPadding: 3
icon.source: "../icons/resize.svg"
- icon.color: down || checked ? Colors.iconIndicator : Colors.icon
+ icon.color: hovered ? Colors.iconIndicator : Colors.icon
+ background: null
checkable: false
display: AbstractButton.IconOnly
- background: null
- onPressed: {
- root.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
- }
+ onPressed: resizeWindow.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml
index 9d08562d9..aac530394 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml
@@ -8,77 +8,92 @@ import FileSystemModule
Rectangle {
id: root
+
+ property alias currentTabIndex: topBar.currentIndex
+ required property ApplicationWindow dragWindow
+ readonly property int tabBarSpacing: 10
+
color: Colors.surface2
- required property ApplicationWindow rootWindow
- property alias currentTabIndex: tabBar.currentIndex
+ component SidebarEntry: Button {
+ id: sidebarButton
- ColumnLayout {
- anchors.fill: root
- anchors.topMargin: 10
- anchors.bottomMargin: 10
- spacing: 10
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
- // TabBar is designed to be horizontal, whereas we need a vertical bar.
- // We can easily achieve that by using a Container.
- Container {
- id: tabBar
+ icon.color: down || checked ? Colors.iconIndicator : Colors.icon
+ icon.width: 27
+ icon.height: 27
- Layout.fillWidth: true
+ topPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ background: null
- // ButtonGroup ensures that only one button can be checked at a time.
- ButtonGroup {
- buttons: tabBar.contentItem.children
- // We have to manage the currentIndex ourselves, which we do by setting it to the
- // index of the currently checked button.
- // We use setCurrentIndex instead of setting the currentIndex property to avoid breaking bindings.
- // See "Managing the Current Index" in Container's documentation for more information.
- onCheckedButtonChanged: tabBar.setCurrentIndex(Math.max(0, buttons.indexOf(checkedButton)))
- }
+ Rectangle {
+ id: indicator
- contentItem: ColumnLayout {
- spacing: tabBar.spacing
+ anchors.verticalCenter: parent.verticalCenter
+ x: 2
+ width: 4
+ height: sidebarButton.icon.height * 1.2
- Repeater {
- model: tabBar.contentModel
- }
- }
+ visible: sidebarButton.checked
+ color: Colors.color1
+ }
+ }
+
+ // TabBar is designed to be horizontal, whereas we need a vertical bar.
+ // We can easily achieve that by using a Container.
+ component TabBar: Container {
+ id: tabBarComponent
+
+ Layout.fillWidth: true
+ // ButtonGroup ensures that only one button can be checked at a time.
+ ButtonGroup {
+ buttons: tabBarComponent.contentChildren
+
+ // We have to manage the currentIndex ourselves, which we do by setting it to the index
+ // of the currently checked button. We use setCurrentIndex instead of setting the
+ // currentIndex property to avoid breaking bindings. See "Managing the Current Index"
+ // in Container's documentation for more information.
+ onCheckedButtonChanged: tabBarComponent.setCurrentIndex(
+ Math.max(0, buttons.indexOf(checkedButton)))
+ }
- component SidebarEntry: Button {
- id: sidebarButton
- icon.color: down || checked ? Colors.iconIndicator : Colors.icon
- icon.width: 35
- icon.height: 35
- leftPadding: 8 + indicator.width
-
- background: null
-
- Rectangle {
- id: indicator
- x: 4
- anchors.verticalCenter: parent.verticalCenter
- width: 4
- height: sidebarButton.icon.width
- color: Colors.color1
- visible: sidebarButton.checked
- }
+ contentItem: ColumnLayout {
+ spacing: tabBarComponent.spacing
+ Repeater {
+ model: tabBarComponent.contentModel
}
+ }
+ }
+ ColumnLayout {
+ anchors.fill: root
+ anchors.topMargin: root.tabBarSpacing
+ anchors.bottomMargin: root.tabBarSpacing
+
+ spacing: root.tabBarSpacing
+ TabBar {
+ id: topBar
+
+ spacing: root.tabBarSpacing
// Shows help text when clicked.
SidebarEntry {
+ id: infoTab
icon.source: "../icons/light_bulb.svg"
checkable: true
checked: true
-
- Layout.alignment: Qt.AlignHCenter
}
// Shows the file system when clicked.
SidebarEntry {
+ id: filesystemTab
+
icon.source: "../icons/read.svg"
checkable: true
-
- Layout.alignment: Qt.AlignHCenter
}
}
@@ -88,25 +103,31 @@ Rectangle {
Layout.fillWidth: true
// Make the empty space drag our main window.
- WindowDragHandler { dragWindow: rootWindow }
+ WindowDragHandler {
+ dragWindow: root.dragWindow
+ }
}
- // Opens the Qt website in the system's web browser.
- SidebarEntry {
- id: qtWebsiteButton
- icon.source: "../icons/globe.svg"
- checkable: false
+ TabBar {
+ id: bottomBar
- onClicked: Qt.openUrlExternally("https://www.qt.io/")
- }
+ spacing: root.tabBarSpacing
+ // Opens the Qt website in the system's web browser.
+ SidebarEntry {
+ id: qtWebsiteButton
+ icon.source: "../icons/globe.svg"
+ checkable: false
+ onClicked: Qt.openUrlExternally("https://www.qt.io/")
+ }
- // Opens the About Qt Window.
- SidebarEntry {
- id: aboutQtButton
- icon.source: "../icons/info_sign.svg"
- checkable: false
+ // Opens the About Qt Window.
+ SidebarEntry {
+ id: aboutQtButton
- onClicked: aboutQtWindow.visible = !aboutQtWindow.visible
+ icon.source: "../icons/info_sign.svg"
+ checkable: false
+ onClicked: aboutQtWindow.visible = !aboutQtWindow.visible
+ }
}
}
diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir
index ff7ecb757..b1f684600 100644
--- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir
+++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir
@@ -1,7 +1,7 @@
module FileSystemModule
Main 1.0 Main.qml
-Icon 1.0 qml/Icon.qml
About 1.0 qml/About.qml
+Editor 1.0 qml/Editor.qml
MyMenu 1.0 qml/MyMenu.qml
Sidebar 1.0 qml/Sidebar.qml
MyMenuBar 1.0 qml/MyMenuBar.qml
diff --git a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst
index 0260928b6..b46cbec79 100644
--- a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst
+++ b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.rst
@@ -37,3 +37,16 @@ pleasing UIs.
.. image:: filesystemexplorer.webp
:target: filesystemexplorer.webp
:alt: QtQuickControls Filesystem Explorer Screenshot
+
+References
+----------
+
+If you're interested in the C++ version of this example, you can find it
+`here <https://doc-snapshots.qt.io/qt6-dev/qtquickcontrols-filesystemexplorer-example.html>`_.
+
+Additionally, there is a detailed
+`tutorial <https://doc.qt.io/qtforpython-6/tutorials/extendedexplorer/extendedexplorer.html>`_
+available that provides step-by-step instructions on how to extend this example
+with additional features. This tutorial can be helpful if you want to explore
+and learn more about building upon the existing functionality of the filesystem
+explorer.
diff --git a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp
index cce7e1daf..10ad0d26e 100644
--- a/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp
+++ b/examples/quickcontrols/filesystemexplorer/doc/filesystemexplorer.webp
Binary files differ
diff --git a/examples/quickcontrols/filesystemexplorer/editormodels.py b/examples/quickcontrols/filesystemexplorer/editormodels.py
new file mode 100644
index 000000000..688147726
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/editormodels.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtWidgets import QFileSystemModel
+from PySide6.QtQuick import QQuickTextDocument
+from PySide6.QtQml import QmlElement, QmlSingleton
+from PySide6.QtCore import (Qt, QDir, QAbstractListModel, Slot, QFile, QTextStream,
+ QMimeDatabase, QFileInfo, QStandardPaths, QModelIndex,
+ Signal, Property)
+
+QML_IMPORT_NAME = "FileSystemModule"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+@QmlSingleton
+class FileSystemModel(QFileSystemModel):
+
+ rootIndexChanged = Signal()
+
+ def getDefaultRootDir():
+ return QStandardPaths.writableLocation(QStandardPaths.StandardLocation.HomeLocation)
+
+ def __init__(self, parent=None):
+ super().__init__(parent=parent)
+ self.mRootIndex = QModelIndex()
+ self.mDb = QMimeDatabase()
+ self.setFilter(QDir.Filter.AllEntries | QDir.Filter.Hidden | QDir.Filter.NoDotAndDotDot)
+ self.setInitialDirectory()
+
+ # check for the correct mime type and then read the file.
+ # returns the text file's content or an error message on failure
+ @Slot(str, result=str)
+ def readFile(self, path):
+ if path == "":
+ return ""
+
+ file = QFile(path)
+
+ mime = self.mDb.mimeTypeForFile(QFileInfo(file))
+ if ('text' in mime.comment().lower()
+ or any('text' in s.lower() for s in mime.parentMimeTypes())):
+ if file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text):
+ stream = QTextStream(file).readAll()
+ file.close()
+ return stream
+ else:
+ return self.tr("Error opening the file!")
+ return self.tr("File type not supported!")
+
+ @Slot(QQuickTextDocument, int, result=int)
+ def currentLineNumber(self, textDocument, cursorPosition):
+ td = textDocument.textDocument()
+ tb = td.findBlock(cursorPosition)
+ return tb.blockNumber()
+
+ def setInitialDirectory(self, path=getDefaultRootDir()):
+ dir = QDir(path)
+ if dir.makeAbsolute():
+ self.setRootPath(dir.path())
+ else:
+ self.setRootPath(self.getDefaultRootDir())
+ self.setRootIndex(self.index(dir.path()))
+
+ # we only need one column in this example
+ def columnCount(self, parent):
+ return 1
+
+ @Property(QModelIndex, notify=rootIndexChanged)
+ def rootIndex(self):
+ return self.mRootIndex
+
+ def setRootIndex(self, index):
+ if (index == self.mRootIndex):
+ return
+ self.mRootIndex = index
+ self.rootIndexChanged.emit()
+
+
+@QmlElement
+class LineNumberModel(QAbstractListModel):
+
+ lineCountChanged = Signal()
+
+ def __init__(self, parent=None):
+ self.mLineCount = 0
+ super().__init__(parent=parent)
+
+ @Property(int, notify=lineCountChanged)
+ def lineCount(self):
+ return self.mLineCount
+
+ @lineCount.setter
+ def lineCount(self, n):
+ if n < 0:
+ print("lineCount must be greater then zero")
+ return
+ if self.mLineCount == n:
+ return
+
+ if self.mLineCount < n:
+ self.beginInsertRows(QModelIndex(), self.mLineCount, n - 1)
+ self.mLineCount = n
+ self.endInsertRows()
+ else:
+ self.beginRemoveRows(QModelIndex(), n, self.mLineCount - 1)
+ self.mLineCount = n
+ self.endRemoveRows()
+
+ def rowCount(self, parent):
+ return self.mLineCount
+
+ def data(self, index, role):
+ if not self.checkIndex(index) or role != Qt.ItemDataRole.DisplayRole:
+ return
+ return index.row()
diff --git a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py
deleted file mode 100644
index 90579b360..000000000
--- a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-"""
-This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer.
-"""
-
-# Compile both resource files app.qrc and icons.qrc and include them here if you wish
-# to load them from the resource system. Currently, all resources are loaded locally
-# import FileSystemModule.rc_icons
-# import FileSystemModule.rc_app
-
-from PySide6.QtWidgets import QFileSystemModel
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import (QQmlApplicationEngine, QmlElement, QmlSingleton)
-from PySide6.QtCore import (Slot, QFile, QTextStream, QMimeDatabase, QFileInfo, QStandardPaths)
-
-import sys
-
-
-QML_IMPORT_NAME = "FileSystemModule"
-QML_IMPORT_MAJOR_VERSION = 1
-
-
-@QmlElement
-@QmlSingleton
-class FileSystemModel(QFileSystemModel):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setRootPath(QStandardPaths.writableLocation(QStandardPaths.HomeLocation))
- self.db = QMimeDatabase()
-
- # we only need one column in this example
- def columnCount(self, parent):
- return 1
-
- # check for the correct mime type and then read the file.
- # returns the text file's content or an error message on failure
- @Slot(str, result=str)
- def readFile(self, path):
- if path == "":
- return ""
-
- file = QFile(path)
-
- mime = self.db.mimeTypeForFile(QFileInfo(file))
- if 'text' in mime.comment().lower() or any('text' in s.lower() for s in mime.parentMimeTypes()):
- if file.open(QFile.ReadOnly | QFile.Text):
- stream = QTextStream(file).readAll()
- return stream
- else:
- return self.tr("Error opening the file!")
- return self.tr("File type not supported!")
-
-
-if __name__ == '__main__':
- app = QGuiApplication(sys.argv)
- app.setOrganizationName("QtProject")
- app.setApplicationName("File System Explorer")
- engine = QQmlApplicationEngine()
- # Include the path of this file to search for the 'qmldir' module
- engine.addImportPath(sys.path[0])
-
- engine.loadFromModule("FileSystemModule", "Main")
-
- if not engine.rootObjects():
- sys.exit(-1)
-
- sys.exit(app.exec())
diff --git a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject
index 1e1aa2ad8..8053cfab0 100644
--- a/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject
+++ b/examples/quickcontrols/filesystemexplorer/filesystemexplorer.pyproject
@@ -1,19 +1,21 @@
{
"files": [
- "filesystemexplorer.py",
+ "main.py",
+ "editormodels.py",
"FileSystemModule/qmldir",
"FileSystemModule/app.qrc",
"FileSystemModule/qmldir",
"FileSystemModule/Main.qml",
"FileSystemModule/qml/About.qml",
"FileSystemModule/qml/Colors.qml",
+ "FileSystemModule/qml/Editor.qml",
"FileSystemModule/qml/FileSystemView.qml",
- "FileSystemModule/qml/Icon.qml",
"FileSystemModule/qml/MyMenu.qml",
"FileSystemModule/qml/MyMenuBar.qml",
"FileSystemModule/qml/ResizeButton.qml",
"FileSystemModule/qml/Sidebar.qml",
"FileSystemModule/qml/WindowDragHandler.qml",
+ "FileSystemModule/icons/app_icon.svg",
"FileSystemModule/icons/folder_closed.svg",
"FileSystemModule/icons/folder_open.svg",
"FileSystemModule/icons/generic_file.svg",
diff --git a/examples/quickcontrols/filesystemexplorer/main.py b/examples/quickcontrols/filesystemexplorer/main.py
new file mode 100644
index 000000000..8fad951cb
--- /dev/null
+++ b/examples/quickcontrols/filesystemexplorer/main.py
@@ -0,0 +1,48 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""
+This example shows how to customize Qt Quick Controls by implementing a simple filesystem explorer.
+"""
+
+# Compile both resource files app.qrc and icons.qrc and include them here if you wish
+# to load them from the resource system. Currently, all resources are loaded locally
+# import FileSystemModule.rc_icons
+# import FileSystemModule.rc_app
+
+from editormodels import FileSystemModel # noqa: F401
+from PySide6.QtGui import QGuiApplication, QIcon
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtCore import QCommandLineParser, qVersion
+
+import sys
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ app.setOrganizationName("QtProject")
+ app.setApplicationName("File System Explorer")
+ app.setApplicationVersion(qVersion())
+ app.setWindowIcon(QIcon(sys.path[0] + "/FileSystemModule/icons/app_icon.svg"))
+
+ parser = QCommandLineParser()
+ parser.setApplicationDescription("Qt Filesystemexplorer Example")
+ parser.addHelpOption()
+ parser.addVersionOption()
+ parser.addPositionalArgument("", "Initial directory", "[path]")
+ parser.process(app)
+ args = parser.positionalArguments()
+
+ engine = QQmlApplicationEngine()
+ # Include the path of this file to search for the 'qmldir' module
+ engine.addImportPath(sys.path[0])
+
+ engine.loadFromModule("FileSystemModule", "Main")
+
+ if not engine.rootObjects():
+ sys.exit(-1)
+
+ if (len(args) == 1):
+ fsm = engine.singletonInstance("FileSystemModule", "FileSystemModel")
+ fsm.setInitialDirectory(args[0])
+
+ sys.exit(app.exec())
diff --git a/examples/quickcontrols/gallery/doc/gallery.rst b/examples/quickcontrols/gallery/doc/gallery.rst
index 29cd49f14..acd5096f6 100644
--- a/examples/quickcontrols/gallery/doc/gallery.rst
+++ b/examples/quickcontrols/gallery/doc/gallery.rst
@@ -1,6 +1,8 @@
Qt Quick Controls 2 - Gallery
=============================
+.. tags:: Android
+
The gallery example is a simple application with a drawer menu that contains
all the Qt Quick Controls 2. Each menu item opens a page that shows the
graphical appearance of a control, allows you to interact with the control, and
diff --git a/examples/quickcontrols/gallery/gallery.py b/examples/quickcontrols/gallery/gallery.py
index 1cdc30bab..6c2a3612e 100644
--- a/examples/quickcontrols/gallery/gallery.py
+++ b/examples/quickcontrols/gallery/gallery.py
@@ -17,7 +17,7 @@ from PySide6.QtCore import QSettings, QUrl
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle
-import rc_gallery
+import rc_gallery # noqa: F401
if __name__ == "__main__":
QGuiApplication.setApplicationName("Gallery")
diff --git a/examples/samplebinding/CMakeLists.txt b/examples/samplebinding/CMakeLists.txt
index e7d23db7d..4807904c1 100644
--- a/examples/samplebinding/CMakeLists.txt
+++ b/examples/samplebinding/CMakeLists.txt
@@ -135,7 +135,7 @@ target_compile_definitions(${sample_library} PRIVATE BINDINGS_BUILD)
# Set up the options to pass to shiboken.
set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic
- --enable-return-value-heuristic --use-isnull-as-nb_nonzero
+ --enable-return-value-heuristic --use-isnull-as-nb-bool
--avoid-protected-hack
-I${CMAKE_SOURCE_DIR}
-T${CMAKE_SOURCE_DIR}
diff --git a/examples/samplebinding/doc/samplebinding.rst b/examples/samplebinding/doc/samplebinding.rst
index e96e99df4..defb55d6b 100644
--- a/examples/samplebinding/doc/samplebinding.rst
+++ b/examples/samplebinding/doc/samplebinding.rst
@@ -181,7 +181,7 @@ On Windows:
mkdir build
cd build
- cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
+ cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe
ninja
ninja install
cd ..
@@ -220,13 +220,13 @@ passing the compiler on the command line:
.. code-block:: bash
- cmake -H.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
+ cmake -S.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
or by using the -G option:
.. code-block:: bash
- cmake -H.. -B. -G "Visual Studio 14 Win64"
+ cmake -S.. -B. -G "Visual Studio 14 Win64"
If the ``-G "Visual Studio 14 Win64"`` option is used, a ``sln`` file
will be generated, and can be used with ``MSBuild``
diff --git a/examples/scriptableapplication/CMakeLists.txt b/examples/scriptableapplication/CMakeLists.txt
index 31174628c..fbfa00b98 100644
--- a/examples/scriptableapplication/CMakeLists.txt
+++ b/examples/scriptableapplication/CMakeLists.txt
@@ -101,7 +101,7 @@ set(WRAPPED_HEADER ${CMAKE_SOURCE_DIR}/wrappedclasses.h)
set(TYPESYSTEM_FILE ${CMAKE_SOURCE_DIR}/scriptableapplication.xml)
set(SHIBOKEN_OPTIONS --generator-set=shiboken --enable-parent-ctor-heuristic
- --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero
+ --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb-bool
--avoid-protected-hack
${INCLUDES}
-I${CMAKE_SOURCE_DIR}
diff --git a/examples/scriptableapplication/doc/scriptableapplication.rst b/examples/scriptableapplication/doc/scriptableapplication.rst
index b00d65415..146911f13 100644
--- a/examples/scriptableapplication/doc/scriptableapplication.rst
+++ b/examples/scriptableapplication/doc/scriptableapplication.rst
@@ -84,7 +84,7 @@ On Windows:
mkdir build
cd build
- cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
+ cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
./scriptableapplication
@@ -127,13 +127,13 @@ passing the compiler on the command line:
.. code-block:: bash
- cmake -H.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
+ cmake -S.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
or using the -G option:
.. code-block:: bash
- cmake -H.. -B. -G "Visual Studio 14 Win64" -DCMAKE_BUILD_TYPE=Release
+ cmake -S.. -B. -G "Visual Studio 14 Win64" -DCMAKE_BUILD_TYPE=Release
If the ``-G "Visual Studio 14 Win64"`` option is used, a ``sln`` file
diff --git a/examples/scriptableapplication/mainwindow.cpp b/examples/scriptableapplication/mainwindow.cpp
index 27e6f1247..ece7989e7 100644
--- a/examples/scriptableapplication/mainwindow.cpp
+++ b/examples/scriptableapplication/mainwindow.cpp
@@ -19,37 +19,44 @@
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
-static const char defaultScript[] = R"(
-import AppLib
+using namespace Qt::StringLiterals;
+
+static const auto defaultScript = R"(import AppLib
print("Hello, world")
mainWindow.testFunction1()
-)";
+)"_L1;
MainWindow::MainWindow()
- : m_scriptEdit(new QPlainTextEdit(QString::fromLatin1(defaultScript).trimmed(), this))
+ : m_scriptEdit(new QPlainTextEdit(defaultScript, this))
{
setWindowTitle(tr("Scriptable Application"));
- QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
- const QIcon runIcon = QIcon::fromTheme(QStringLiteral("system-run"));
- QAction *runAction = fileMenu->addAction(runIcon, tr("&Run..."), this, &MainWindow::slotRunScript);
+ auto *fileMenu = menuBar()->addMenu(tr("&File"));
+ const QIcon runIcon = QIcon::fromTheme("system-run"_L1);
+ auto *runAction = fileMenu->addAction(runIcon, tr("&Run..."),
+ this, &MainWindow::slotRunScript);
runAction->setShortcut(Qt::CTRL | Qt::Key_R);
- QAction *diagnosticAction = fileMenu->addAction(tr("&Print Diagnostics"), this, &MainWindow::slotPrintDiagnostics);
+ auto *diagnosticAction = fileMenu->addAction(tr("&Print Diagnostics"),
+ this, &MainWindow::slotPrintDiagnostics);
diagnosticAction->setShortcut(Qt::CTRL | Qt::Key_D);
- fileMenu->addAction(tr("&Invoke testFunction1()"), this, &MainWindow::testFunction1);
- const QIcon quitIcon = QIcon::fromTheme(QStringLiteral("application-exit"));
- QAction *quitAction = fileMenu->addAction(quitIcon, tr("&Quit"), qApp, &QCoreApplication::quit);
+ fileMenu->addAction(tr("&Invoke testFunction1()"),
+ this, &MainWindow::testFunction1);
+ const QIcon quitIcon = QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit);
+ auto *quitAction = fileMenu->addAction(quitIcon, tr("&Quit"),
+ qApp, &QCoreApplication::quit);
quitAction->setShortcut(Qt::CTRL | Qt::Key_Q);
- QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
- const QIcon clearIcon = QIcon::fromTheme(QStringLiteral("edit-clear"));
- QAction *clearAction = editMenu->addAction(clearIcon, tr("&Clear"), m_scriptEdit, &QPlainTextEdit::clear);
+ auto *editMenu = menuBar()->addMenu(tr("&Edit"));
+ const QIcon clearIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditClear);
+ auto *clearAction = editMenu->addAction(clearIcon, tr("&Clear"),
+ m_scriptEdit, &QPlainTextEdit::clear);
- QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
- const QIcon aboutIcon = QIcon::fromTheme(QStringLiteral("help-about"));
- QAction *aboutAction = helpMenu->addAction(aboutIcon, tr("&About Qt"), qApp, &QApplication::aboutQt);
+ auto *helpMenu = menuBar()->addMenu(tr("&Help"));
+ const QIcon aboutIcon = QIcon::fromTheme(QIcon::ThemeIcon::HelpAbout);
+ auto *aboutAction = helpMenu->addAction(aboutIcon, tr("&About Qt"),
+ qApp, &QApplication::aboutQt);
- QToolBar *toolBar = new QToolBar;
+ auto *toolBar = new QToolBar;
addToolBar(toolBar);
toolBar->addAction(quitAction);
toolBar->addSeparator();
@@ -62,26 +69,29 @@ MainWindow::MainWindow()
m_scriptEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
setCentralWidget(m_scriptEdit);
- if (!PythonUtils::bindAppObject("__main__", "mainWindow", PythonUtils::MainWindowType, this))
+ if (!PythonUtils::bindAppObject("__main__"_L1, "mainWindow"_L1,
+ PythonUtils::MainWindowType, this)) {
statusBar()->showMessage(tr("Error loading the application module"));
+ }
}
void MainWindow::slotRunScript()
{
const QString text = m_scriptEdit->toPlainText().trimmed();
- const QStringList script = text.split(u'\n', Qt::SkipEmptyParts);
- if (!script.isEmpty())
- runScript(script);
+ if (!text.isEmpty())
+ runScript(text);
}
void MainWindow::slotPrintDiagnostics()
{
- const QStringList script = QStringList()
- << "import sys" << "print('Path=', sys.path)" << "print('Executable=', sys.executable)";
+ const QString script = R"P(import sys
+print('Path=', sys.path)
+print('Executable=', sys.executable)
+)P"_L1;
runScript(script);
}
-void MainWindow::runScript(const QStringList &script)
+void MainWindow::runScript(const QString &script)
{
if (!::PythonUtils::runScript(script))
statusBar()->showMessage(tr("Error running script"));
diff --git a/examples/scriptableapplication/mainwindow.h b/examples/scriptableapplication/mainwindow.h
index b7cafc3e6..e72f5ca72 100644
--- a/examples/scriptableapplication/mainwindow.h
+++ b/examples/scriptableapplication/mainwindow.h
@@ -6,7 +6,7 @@
#include <QtWidgets/QMainWindow>
-class QPlainTextEdit;
+QT_FORWARD_DECLARE_CLASS(QPlainTextEdit)
class MainWindow : public QMainWindow
{
@@ -16,12 +16,14 @@ public:
void testFunction1();
+ static constexpr auto TEST = QLatin1StringView("test");
+
private Q_SLOTS:
void slotRunScript();
void slotPrintDiagnostics();
private:
- void runScript(const QStringList &);
+ void runScript(const QString &);
QPlainTextEdit *m_scriptEdit;
};
diff --git a/examples/scriptableapplication/pythonutils.cpp b/examples/scriptableapplication/pythonutils.cpp
index b167bb240..8104bb167 100644
--- a/examples/scriptableapplication/pythonutils.cpp
+++ b/examples/scriptableapplication/pythonutils.cpp
@@ -114,20 +114,14 @@ bool bindAppObject(const QString &moduleName, const QString &name,
return true;
}
-bool runScript(const QStringList &script)
+bool runScript(const QString &script)
{
if (init() == PythonUninitialized)
return false;
- // Concatenating all the lines
- QString content;
- QTextStream ss(&content);
- for (const QString &line: script)
- ss << line << "\n";
-
// Executing the whole script as one line
bool result = true;
- const QByteArray line = content.toUtf8();
+ const QByteArray line = script.toUtf8();
if (PyRun_SimpleString(line.constData()) == -1) {
if (PyErr_Occurred())
PyErr_Print();
diff --git a/examples/scriptableapplication/pythonutils.h b/examples/scriptableapplication/pythonutils.h
index 80698d234..09e959159 100644
--- a/examples/scriptableapplication/pythonutils.h
+++ b/examples/scriptableapplication/pythonutils.h
@@ -6,7 +6,7 @@
#include <QtCore/QStringList>
-class QObject;
+QT_FORWARD_DECLARE_CLASS(QObject)
namespace PythonUtils {
@@ -27,7 +27,7 @@ State init();
bool bindAppObject(const QString &moduleName, const QString &name,
int index, QObject *o);
-bool runScript(const QStringList &script);
+bool runScript(const QString &script);
} // namespace PythonUtils
diff --git a/examples/serialbus/can/main.py b/examples/serialbus/can/main.py
index 77d2e6012..97cdcc908 100644
--- a/examples/serialbus/can/main.py
+++ b/examples/serialbus/can/main.py
@@ -16,4 +16,3 @@ if __name__ == "__main__":
w = MainWindow()
w.show()
sys.exit(QCoreApplication.exec())
-
diff --git a/examples/serialbus/can/mainwindow.py b/examples/serialbus/can/mainwindow.py
index e5d068e07..ae2a08f1f 100644
--- a/examples/serialbus/can/mainwindow.py
+++ b/examples/serialbus/can/mainwindow.py
@@ -74,7 +74,7 @@ class MainWindow(QMainWindow):
self.m_ui.actionDisconnect.triggered.connect(self.disconnect_device)
self.m_ui.actionResetController.triggered.connect(self._reset_controller)
self.m_ui.actionQuit.triggered.connect(self.close)
- self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt)
+ self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) # noqa: F821
self.m_ui.actionClearLog.triggered.connect(self.m_model.clear)
self.m_ui.actionPluginDocumentation.triggered.connect(show_help)
self.m_ui.actionDeviceInformation.triggered.connect(self._action_device_information)
@@ -109,9 +109,11 @@ class MainWindow(QMainWindow):
else:
self.m_model.set_queue_limit(0)
- device, error_string = QCanBus.instance().createDevice(p.plugin_name, p.device_interface_name)
+ device, error_string = QCanBus.instance().createDevice(
+ p.plugin_name, p.device_interface_name)
if not device:
- self.m_status.setText(f"Error creating device '{p.plugin_name}', reason: '{error_string}'")
+ self.m_status.setText(
+ f"Error creating device '{p.plugin_name}', reason: '{error_string}'")
return
self.m_number_frames_written = 0
@@ -136,18 +138,22 @@ class MainWindow(QMainWindow):
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_data_bit_rate = self.m_can_device.configurationParameter(QCanBusDevice.DataBitRateKey)
+ config_data_bit_rate = self.m_can_device.configurationParameter(
+ QCanBusDevice.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
- m = f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} at {bit_rate} / {data_bit_rate} kBit/s"
+ m = (f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} "
+ f"at {bit_rate} / {data_bit_rate} kBit/s")
self.m_status.setText(m)
else:
- m = f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} at {bit_rate} kBit/s"
+ m = (f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} "
+ f"at {bit_rate} kBit/s")
self.m_status.setText(m)
else:
- self.m_status.setText(f"Plugin: {p.plugin_name}, connected to {p.device_interface_name}")
+ self.m_status.setText(
+ f"Plugin: {p.plugin_name}, connected to {p.device_interface_name}")
if self.m_can_device.hasBusStatus():
self.m_busStatusTimer.start(2000)
diff --git a/examples/serialbus/can/sendframebox.py b/examples/serialbus/can/sendframebox.py
index 99e4ab7c2..6472fc473 100644
--- a/examples/serialbus/can/sendframebox.py
+++ b/examples/serialbus/can/sendframebox.py
@@ -157,8 +157,8 @@ class SendFrameBox(QGroupBox):
@Slot(bool)
def _flexible_datarate(self, value):
- l = MAX_PAYLOAD_FD if value else MAX_PAYLOAD
- self.m_hexStringValidator.set_max_length(l)
+ len = MAX_PAYLOAD_FD if value else MAX_PAYLOAD
+ self.m_hexStringValidator.set_max_length(len)
self.m_ui.bitrateSwitchBox.setEnabled(value)
if not value:
self.m_ui.bitrateSwitchBox.setChecked(False)
diff --git a/examples/serialbus/can/ui_canbusdeviceinfobox.py b/examples/serialbus/can/ui_canbusdeviceinfobox.py
index 250e653be..d2f0e74a5 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
diff --git a/examples/serialbus/can/ui_canbusdeviceinfodialog.py b/examples/serialbus/can/ui_canbusdeviceinfodialog.py
index fd391a2a8..c32e9ca2d 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -35,7 +35,7 @@ class Ui_CanBusDeviceInfoDialog(object):
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
- self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
diff --git a/examples/serialbus/can/ui_connectdialog.py b/examples/serialbus/can/ui_connectdialog.py
index 815fcf594..f01adc4eb 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -169,7 +169,7 @@ class Ui_ConnectDialog(object):
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
- self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
diff --git a/examples/serialbus/can/ui_mainwindow.py b/examples/serialbus/can/ui_mainwindow.py
index cce2375cf..4ba4dd309 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -95,7 +95,7 @@ class Ui_MainWindow(object):
self.horizontalLayout.addWidget(self.busStatus)
- self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
@@ -119,7 +119,7 @@ class Ui_MainWindow(object):
MainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QToolBar(MainWindow)
self.mainToolBar.setObjectName(u"mainToolBar")
- MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar)
+ MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar)
self.statusBar = QStatusBar(MainWindow)
self.statusBar.setObjectName(u"statusBar")
MainWindow.setStatusBar(self.statusBar)
diff --git a/examples/serialbus/can/ui_sendframebox.py b/examples/serialbus/can/ui_sendframebox.py
index b769645e0..79f013c89 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -88,7 +88,7 @@ class Ui_SendFrameBox(object):
self.frameIdEdit = QLineEdit(SendFrameBox)
self.frameIdEdit.setObjectName(u"frameIdEdit")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.frameIdEdit.sizePolicy().hasHeightForWidth())
@@ -109,7 +109,7 @@ class Ui_SendFrameBox(object):
self.payloadEdit = QLineEdit(SendFrameBox)
self.payloadEdit.setObjectName(u"payloadEdit")
- sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
sizePolicy1.setHorizontalStretch(2)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.payloadEdit.sizePolicy().hasHeightForWidth())
diff --git a/examples/serialbus/modbus/modbusclient/mainwindow.py b/examples/serialbus/modbus/modbusclient/mainwindow.py
index 80a336c59..02f9d478b 100644
--- a/examples/serialbus/modbus/modbusclient/mainwindow.py
+++ b/examples/serialbus/modbus/modbusclient/mainwindow.py
@@ -316,7 +316,7 @@ class MainWindow(QMainWindow):
# do not go beyond 10 entries
number_of_entries = min(int(self.ui.readSize.currentText()),
- 10 - start_address)
+ 10 - start_address)
return QModbusDataUnit(table, start_address, number_of_entries)
def write_request(self):
@@ -327,5 +327,5 @@ class MainWindow(QMainWindow):
# do not go beyond 10 entries
number_of_entries = min(int(self.ui.writeSize.currentText()),
- 10 - start_address)
+ 10 - start_address)
return QModbusDataUnit(table, start_address, number_of_entries)
diff --git a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py
index 10fb0874f..1535fc8d8 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -61,7 +61,7 @@ class Ui_MainWindow(object):
self.gridLayout.setObjectName(u"gridLayout")
self.label_27 = QLabel(self.centralWidget)
self.label_27.setObjectName(u"label_27")
- sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_27.sizePolicy().hasHeightForWidth())
@@ -71,7 +71,7 @@ class Ui_MainWindow(object):
self.connectButton = QPushButton(self.centralWidget)
self.connectButton.setObjectName(u"connectButton")
- sizePolicy1 = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.connectButton.sizePolicy().hasHeightForWidth())
@@ -81,7 +81,7 @@ class Ui_MainWindow(object):
self.gridLayout.addWidget(self.connectButton, 0, 7, 1, 1)
- self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.gridLayout.addItem(self.horizontalSpacer, 0, 4, 1, 1)
@@ -115,7 +115,7 @@ class Ui_MainWindow(object):
self.portEdit = QLineEdit(self.centralWidget)
self.portEdit.setObjectName(u"portEdit")
- sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.portEdit.sizePolicy().hasHeightForWidth())
@@ -255,7 +255,7 @@ class Ui_MainWindow(object):
self.horizontalLayout.addWidget(self.writeTable)
- self.horizontalSpacer_2 = QSpacerItem(13, 17, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer_2 = QSpacerItem(13, 17, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer_2)
diff --git a/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py b/examples/serialbus/modbus/modbusclient/ui_settingsdialog.py
index f1c299594..af3a0665c 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.4.1
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -26,7 +26,7 @@ class Ui_SettingsDialog(object):
SettingsDialog.resize(239, 256)
self.gridLayout = QGridLayout(SettingsDialog)
self.gridLayout.setObjectName(u"gridLayout")
- self.verticalSpacer = QSpacerItem(20, 43, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 43, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.gridLayout.addItem(self.verticalSpacer, 3, 1, 1, 1)
diff --git a/examples/serialport/terminal/mainwindow.py b/examples/serialport/terminal/mainwindow.py
index c35ed2854..bdfb0fb0e 100644
--- a/examples/serialport/terminal/mainwindow.py
+++ b/examples/serialport/terminal/mainwindow.py
@@ -47,7 +47,7 @@ class MainWindow(QMainWindow):
self.m_ui.actionConfigure.triggered.connect(self.m_settings.show)
self.m_ui.actionClear.triggered.connect(self.m_console.clear)
self.m_ui.actionAbout.triggered.connect(self.about)
- self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt)
+ self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) # noqa: F821
self.m_serial.errorOccurred.connect(self.handle_error)
self.m_serial.readyRead.connect(self.read_data)
diff --git a/examples/serialport/terminal/settingsdialog.py b/examples/serialport/terminal/settingsdialog.py
index 07034e4a6..c9373d5b0 100644
--- a/examples/serialport/terminal/settingsdialog.py
+++ b/examples/serialport/terminal/settingsdialog.py
@@ -29,7 +29,7 @@ class Settings():
self.string_data_bits = ""
self.parity = QSerialPort.NoParity
self.string_parity = ""
- self.stop_bits = QSerialPort.OneStop
+ self.stop_bits = QSerialPort.OneStop
self.string_stop_bits = ""
self.flow_control = QSerialPort.SoftwareControl
self.string_flow_control = ""
@@ -52,7 +52,8 @@ class SettingsDialog(QDialog):
self.m_ui.applyButton.clicked.connect(self.apply)
self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(self.show_port_info)
self.m_ui.baudRateBox.currentIndexChanged.connect(self.check_custom_baud_rate_policy)
- self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(self.check_custom_device_path_policy)
+ self.m_ui.serialPortInfoListBox.currentIndexChanged.connect(
+ self.check_custom_device_path_policy)
self.fill_ports_parameters()
self.fill_ports_info()
diff --git a/examples/serialport/terminal/ui_mainwindow.py b/examples/serialport/terminal/ui_mainwindow.py
index 6aa0fcc53..21adbd5c1 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.4.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -74,7 +74,7 @@ class Ui_MainWindow(object):
MainWindow.setMenuBar(self.menuBar)
self.mainToolBar = QToolBar(MainWindow)
self.mainToolBar.setObjectName(u"mainToolBar")
- MainWindow.addToolBar(Qt.TopToolBarArea, self.mainToolBar)
+ MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.mainToolBar)
self.statusBar = QStatusBar(MainWindow)
self.statusBar.setObjectName(u"statusBar")
MainWindow.setStatusBar(self.statusBar)
diff --git a/examples/serialport/terminal/ui_settingsdialog.py b/examples/serialport/terminal/ui_settingsdialog.py
index 212b60b25..6c5a417cc 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.4.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -128,7 +128,7 @@ class Ui_SettingsDialog(object):
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
- self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+ self.horizontalSpacer = QSpacerItem(96, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
diff --git a/examples/spatialaudio/audiopanning/main.py b/examples/spatialaudio/audiopanning/main.py
index 55770e3b6..19b6c3bbd 100644
--- a/examples/spatialaudio/audiopanning/main.py
+++ b/examples/spatialaudio/audiopanning/main.py
@@ -149,7 +149,7 @@ class AudioWidget(QWidget):
self._file_dialog = QFileDialog(self, "Open Audio File", directory)
self._file_dialog.setAcceptMode(QFileDialog.AcceptOpen)
mime_types = ["audio/mpeg", "audio/aac", "audio/x-ms-wma",
- "audio/x-flac+ogg", "audio/x-wav"]
+ "audio/x-flac+ogg", "audio/x-wav"]
self._file_dialog.setMimeTypeFilters(mime_types)
self._file_dialog.selectMimeTypeFilter(mime_types[0])
diff --git a/examples/speech/hello_speak/ui_mainwindow.py b/examples/speech/hello_speak/ui_mainwindow.py
index b42d35f03..ab132e7fb 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -31,7 +31,7 @@ class Ui_MainWindow(object):
self.verticalLayout.setObjectName(u"verticalLayout")
self.plainTextEdit = QPlainTextEdit(self.centralwidget)
self.plainTextEdit.setObjectName(u"plainTextEdit")
- sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth())
@@ -43,7 +43,7 @@ class Ui_MainWindow(object):
self.gridLayout.setObjectName(u"gridLayout")
self.label_5 = QLabel(self.centralwidget)
self.label_5.setObjectName(u"label_5")
- sizePolicy1 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth())
@@ -95,7 +95,7 @@ class Ui_MainWindow(object):
self.language = QComboBox(self.centralwidget)
self.language.setObjectName(u"language")
- sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.language.sizePolicy().hasHeightForWidth())
@@ -169,7 +169,7 @@ class Ui_MainWindow(object):
self.verticalLayout.addLayout(self.horizontalLayout)
- self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
diff --git a/examples/sql/books/bookdelegate.py b/examples/sql/books/bookdelegate.py
index 1d1dba86e..78295adf1 100644
--- a/examples/sql/books/bookdelegate.py
+++ b/examples/sql/books/bookdelegate.py
@@ -13,7 +13,8 @@ class BookDelegate(QSqlRelationalDelegate):
def __init__(self, parent=None):
QSqlRelationalDelegate.__init__(self, parent)
- self.star = QPixmap(":/images/star.png")
+ self.star = QPixmap(":/images/star.svg")
+ self.star_filled = QPixmap(":/images/star-filled.svg")
def paint(self, painter, option, index):
""" Paint the items in the table.
@@ -43,17 +44,19 @@ class BookDelegate(QSqlRelationalDelegate):
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect,
- option.palette.color(color_group, QPalette.Highlight))
+ option.palette.color(color_group, QPalette.Highlight))
rating = model.data(index, Qt.DisplayRole)
width = self.star.width()
height = self.star.height()
x = option.rect.x()
y = option.rect.y() + (option.rect.height() / 2) - (height / 2)
- for i in range(rating):
- painter.drawPixmap(x, y, self.star)
+ for i in range(5):
+ if i < rating:
+ painter.drawPixmap(x, y, self.star_filled)
+ else:
+ painter.drawPixmap(x, y, self.star)
x += width
-
pen = painter.pen()
painter.setPen(option.palette.color(QPalette.Mid))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
diff --git a/examples/sql/books/books.qrc b/examples/sql/books/books.qrc
index d6ad21337..a52ee381b 100644
--- a/examples/sql/books/books.qrc
+++ b/examples/sql/books/books.qrc
@@ -1,5 +1,6 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
- <file>images/star.png</file>
+ <file>images/star.svg</file>
+ <file>images/star-filled.svg</file>
</qresource>
</RCC>
diff --git a/examples/sql/books/bookwindow.py b/examples/sql/books/bookwindow.py
index 4acb63d44..7f9e0f94b 100644
--- a/examples/sql/books/bookwindow.py
+++ b/examples/sql/books/bookwindow.py
@@ -2,7 +2,7 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper,
- QHeaderView, QMainWindow, QMessageBox)
+ QHeaderView, QMainWindow, QMessageBox)
from PySide6.QtGui import QKeySequence
from PySide6.QtSql import QSqlRelation, QSqlRelationalTableModel, QSqlTableModel
from PySide6.QtCore import Qt, Slot
@@ -58,7 +58,7 @@ class BookWindow(QMainWindow, Ui_BookWindow):
# Lock and prohibit resizing of the width of the rating column:
self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"),
- QHeaderView.ResizeToContents)
+ QHeaderView.ResizeToContents)
mapper = QDataWidgetMapper(self)
mapper.setModel(model)
@@ -77,22 +77,22 @@ class BookWindow(QMainWindow, Ui_BookWindow):
def showError(self, err):
QMessageBox.critical(self, "Unable to initialize Database",
- f"Error initializing database: {err.text()}")
+ f"Error initializing database: {err.text()}")
def create_menubar(self):
file_menu = self.menuBar().addMenu(self.tr("&File"))
quit_action = file_menu.addAction(self.tr("&Quit"))
- quit_action.triggered.connect(qApp.quit)
+ quit_action.triggered.connect(qApp.quit) # noqa: F821
help_menu = self.menuBar().addMenu(self.tr("&Help"))
about_action = help_menu.addAction(self.tr("&About"))
about_action.setShortcut(QKeySequence.HelpContents)
about_action.triggered.connect(self.about)
aboutQt_action = help_menu.addAction("&About Qt")
- aboutQt_action.triggered.connect(qApp.aboutQt)
+ aboutQt_action.triggered.connect(qApp.aboutQt) # noqa: F821
@Slot()
def about(self):
QMessageBox.about(self, self.tr("About Books"),
- self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
- "with a model/view framework."))
+ self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
+ "with a model/view framework."))
diff --git a/examples/sql/books/images/star-filled.svg b/examples/sql/books/images/star-filled.svg
new file mode 100644
index 000000000..8a2aee27f
--- /dev/null
+++ b/examples/sql/books/images/star-filled.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#0d0d0d"><path d="M8.41 18.138L12 15.845l3.59 2.323-.94-4.345 3.162-2.897-4.159-.392L12 6.43l-1.652 4.073-4.159.392 3.162 2.927-.94 4.315zm-1.346 3.696a1.04 1.04 0 0 1-1.567-1.104l1.318-6.033-4.476-4.11c-.665-.611-.293-1.726.604-1.808l5.866-.539 2.229-5.587c.348-.872 1.575-.872 1.923 0l2.229 5.587 5.866.539c.897.082 1.269 1.197.604 1.808l-4.476 4.11 1.318 6.033a1.04 1.04 0 0 1-1.567 1.104L12 18.681l-4.935 3.153z"/><path d="M12 5l-1.796 5.528H4.392l4.702 3.416-1.796 5.528L12 16.056l4.702 3.416-1.796-5.528 4.702-3.416h-5.812L12 5z"/></svg>
diff --git a/examples/sql/books/images/star.png b/examples/sql/books/images/star.png
deleted file mode 100644
index 87f4464bd..000000000
--- a/examples/sql/books/images/star.png
+++ /dev/null
Binary files differ
diff --git a/examples/sql/books/images/star.svg b/examples/sql/books/images/star.svg
new file mode 100644
index 000000000..d959abc18
--- /dev/null
+++ b/examples/sql/books/images/star.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path d="M8.41 18.138L12 15.845l3.59 2.323-.94-4.345 3.162-2.897-4.159-.392L12 6.43l-1.652 4.073-4.159.392 3.162 2.927-.94 4.315zm-1.346 3.696a1.04 1.04 0 0 1-1.567-1.104l1.318-6.033-4.476-4.11c-.665-.611-.293-1.726.604-1.808l5.866-.539 2.229-5.587c.348-.872 1.575-.872 1.923 0l2.229 5.587 5.866.539c.897.082 1.269 1.197.604 1.808l-4.476 4.11 1.318 6.033a1.04 1.04 0 0 1-1.567 1.104L12 18.681l-4.935 3.153z" fill="#0d0d0d"/></svg>
diff --git a/examples/sql/books/main.py b/examples/sql/books/main.py
index 9a6575dc2..025b55884 100644
--- a/examples/sql/books/main.py
+++ b/examples/sql/books/main.py
@@ -4,7 +4,7 @@
import sys
from PySide6.QtWidgets import QApplication
from bookwindow import BookWindow
-import rc_books
+import rc_books # noqa: F401
if __name__ == "__main__":
app = QApplication([])
diff --git a/examples/sql/books/rc_books.py b/examples/sql/books/rc_books.py
index b589380e4..123d50de8 100644
--- a/examples/sql/books/rc_books.py
+++ b/examples/sql/books/rc_books.py
@@ -1,62 +1,86 @@
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.2.2
+# Created by: The Resource Compiler for Qt version 6.6.1
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
qt_resource_data = b"\
-\x00\x00\x03\x0e\
-\x89\
-PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
-\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\
-\x00\x00\x00\x09pHYs\x00\x00\x0b\x11\x00\x00\x0b\x11\
-\x01\x7fd_\x91\x00\x00\x00\x07tIME\x07\xd4\x09\
-\x03\x12\x11\x08\x18~\xe5:\x00\x00\x00\x06bKGD\
-\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02\x9bID\
-AT8\xcbc\x98:c\x1e#:\xe6\xe5d\xcf\x17\
-\x12\x12\x16\xc4&\x87\x8e\x19\xb0\x09v\xc6\x18\xb7x\xea\
-\x8b\xcd\x9c=o\x09i\x06,X4\x8f\xf1\xd2\xa5\x99\
-L\xb9\xa1\x16\xc5\xc7\xbb\xed\xff\x0a\xf2\xb2;M\x9f\xb5\
-\x908\x03\x16,\x9a\xcb\xf8\xe0\xde\x04\x96\xc7\x0f\xdby\
-\xe7MO\xc8\xfbv\xbf\xe5\xff\xb4\x0a\x9b\x9by\x851\
-\xdc\xd3g-\x82k\x983\x7f)\xe3l F1`\
-\xca\xf4y\x8c\xd7\xaeMg\x02i~\xf2\xa8Y\xe1\xd2\
-\xa5\xfa\xdc_\x9f7\xfd\xffx\xbf\xea\x7fE\x96m\x97\
-\x81\x81>'33\x8b\xa5\x9e8gi\xb8\x9e\xc0f\
-&&\xa6D\x14\x03&N\x9d\xc7x\xef\xdeD\x96'\
-\x0f[E\x9f>j\xd6\xbdu\xb3\x22\xef\xd7\xb7=\xff\
-\xbe\x7f\xe8\xfb\x7f~S\xcc\xef\x05\xc5\xea\x9fNOQ\
-\xfb\x7f\xbaM\xed\xbf\x87\x1a\xefn5-\x1dV\x14\x03\
-f\xcf[\xce\xa8\xa4\xa9![W\xed\x9b}\xefJ\xcb\
-\xcew\xaf&\x7f\xfa\xfee\xc9\xff\xef\x1f\xfa\xff\xbf\xbf\
-\x95\xf2\xff\xc9^\x83\xffW\x17\xaa\xfdot\x12{\xc4\
-\xc7\xc7/\x8e\x12\x06Y\xb9\x85\xcc\xb2\x82\x1c\xf3\xa7D\
-\xab\xfe\xfa\xbe%\xe2\xff\x8fgm\xff\x7f|\x9a\x08\xd6\
-\xfc\xf5Y\xcd\xff\xd7\xe7\xfc\xfe\xdf\xde\xa0\xf5\x7fE\x94\
-\xecO\x16\x16V\xebi3\xe7\xa3\x06\xe2\xe4is\x18\
-\xe7/Z\xc1\xc8\xce\xc1i\x10\xe5\xa8\xd2\xbe\xa6\xcd\xe7\
-\xf6\xc3m\x99\xff~^(\xf8\xff\xe1j\xe0\xff\x17G\
-L\xff\xdf\xdf\xae\xf6\xbf\xc2]\xf4\xba\x9a\x9a\x06\x1bF\
-,\x00\x01#2\xe6\xe6\xe6`Q\xd6\x941_\xde\xe4\
-q\xfb\xc3y\xd3\xff\x1b\x8aT\xff\xbf?`\xff\xff\xdc\
-l\xe5\xff\xea\xc2\x1c9\xd3g-\xc0i\x00\x13\x10\x8b\
-\x03\xb1?\x10\xe7\xf5\x16\xd8\xde\xf8p\xc6\xe4\xbf\x9d<\
-\xf7t\x7fC\xe9\x95\xb7\x96\xd9\xff\x9b\x9c,\xfdN@\
-@H\x14\x9b\x01LP,\x06\xc4\x19@|\x22;\xca\
-\xf0\xe7\xe9\xf9\x06\xff\x81\xec\x03@\xbc^\x82\x9f\xf3\xf6\
-\x9e\x1a\xf3_az\x823P\xd2\x01T#3\x10\x0b\
-\x00\xb1\x1e\x10\x17\x03\xf1\xd1\xa8@\xdd\x9f\xad\x09J \
-\x03\xfe\x00\xf17 >\x0f\xb4kf\xb9\xa7\xea\x0d}\
-i>#d\x03\xb4\x808\x08\x88k\x81x\x09\xd4\xc6\
-\x1b\x11a\x06\xdf\xec\x94\xb8\xdf\x03\xd9;\x81x\x1a\x10\
-\xf7\x82\xd4\xb0\xb2\xb1G\xf9\xda\x99:L\x9d9\x9f\x09\
-f\x80\x0e\x10;\x02\xb1\x13\x10[\x00\xb1\x01\x10\x07\x06\
-{h\x9c\x02\xd2k\xa0\x86\x8b\x001\x17\x10\xf3\x80\xb0\
-\x88\x88(\xcb,hFC\xf6\x02\x08\xb3\x001+\x10\
-K122\xe4\x01i7 \x96\x01b6\xa88\x0b\
-T=cW\xef$\xb0\x01\x00\xceo{\xf5UL\xf0\
-\xac\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x02e\
+<\
+svg xmlns=\x22http:\
+//www.w3.org/200\
+0/svg\x22 width=\x2224\
+\x22 height=\x2224\x22 fi\
+ll=\x22#0d0d0d\x22><pa\
+th d=\x22M8.41 18.1\
+38L12 15.845l3.5\
+9 2.323-.94-4.34\
+5 3.162-2.897-4.\
+159-.392L12 6.43\
+l-1.652 4.073-4.\
+159.392 3.162 2.\
+927-.94 4.315zm-\
+1.346 3.696a1.04\
+ 1.04 0 0 1-1.56\
+7-1.104l1.318-6.\
+033-4.476-4.11c-\
+.665-.611-.293-1\
+.726.604-1.808l5\
+.866-.539 2.229-\
+5.587c.348-.872 \
+1.575-.872 1.923\
+ 0l2.229 5.587 5\
+.866.539c.897.08\
+2 1.269 1.197.60\
+4 1.808l-4.476 4\
+.11 1.318 6.033a\
+1.04 1.04 0 0 1-\
+1.567 1.104L12 1\
+8.681l-4.935 3.1\
+53z\x22/><path d=\x22M\
+12 5l-1.796 5.52\
+8H4.392l4.702 3.\
+416-1.796 5.528L\
+12 16.056l4.702 \
+3.416-1.796-5.52\
+8 4.702-3.416h-5\
+.812L12 5z\x22/></s\
+vg>\x0a\
+\x00\x00\x01\xfa\
+<\
+svg xmlns=\x22http:\
+//www.w3.org/200\
+0/svg\x22 width=\x2224\
+\x22 height=\x2224\x22 fi\
+ll=\x22none\x22><path \
+d=\x22M8.41 18.138L\
+12 15.845l3.59 2\
+.323-.94-4.345 3\
+.162-2.897-4.159\
+-.392L12 6.43l-1\
+.652 4.073-4.159\
+.392 3.162 2.927\
+-.94 4.315zm-1.3\
+46 3.696a1.04 1.\
+04 0 0 1-1.567-1\
+.104l1.318-6.033\
+-4.476-4.11c-.66\
+5-.611-.293-1.72\
+6.604-1.808l5.86\
+6-.539 2.229-5.5\
+87c.348-.872 1.5\
+75-.872 1.923 0l\
+2.229 5.587 5.86\
+6.539c.897.082 1\
+.269 1.197.604 1\
+.808l-4.476 4.11\
+ 1.318 6.033a1.0\
+4 1.04 0 0 1-1.5\
+67 1.104L12 18.6\
+81l-4.935 3.153z\
+\x22 fill=\x22#0d0d0d\x22\
+/></svg>\x0a\
"
qt_resource_name = b"\
@@ -64,19 +88,25 @@ qt_resource_name = b"\
\x07\x03}\xc3\
\x00i\
\x00m\x00a\x00g\x00e\x00s\
+\x00\x0f\
+\x02\x11 \x07\
+\x00s\
+\x00t\x00a\x00r\x00-\x00f\x00i\x00l\x00l\x00e\x00d\x00.\x00s\x00v\x00g\
\x00\x08\
-\x0a\x85X\x07\
+\x0a\x85U\x87\
\x00s\
-\x00t\x00a\x00r\x00.\x00p\x00n\x00g\
+\x00t\x00a\x00r\x00.\x00s\x00v\x00g\
"
qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01z\xe7\xee&\xfd\
+\x00\x00\x01\x8c\xd4\xc79\xcf\
+\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x02i\
+\x00\x00\x01\x8c\xd4\xc79\xcf\
"
def qInitResources():
diff --git a/examples/sql/books/ui_bookwindow.py b/examples/sql/books/ui_bookwindow.py
index 175891577..52795217e 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
diff --git a/examples/statemachine/rogue/rogue.py b/examples/statemachine/rogue/rogue.py
index a43d4d1bc..f0ce9c28c 100644
--- a/examples/statemachine/rogue/rogue.py
+++ b/examples/statemachine/rogue/rogue.py
@@ -18,11 +18,11 @@ class MovementTransition(QEventTransition):
self.window = window
def eventTest(self, event):
- if (event.type() == QEvent.StateMachineWrapped and
- event.event().type() == QEvent.KeyPress):
+ if (event.type() == QEvent.StateMachineWrapped
+ and event.event().type() == QEvent.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_2 or key == Qt.Key_8
+ or key == Qt.Key_6 or key == Qt.Key_4)
return False
def onTransition(self, event):
@@ -74,8 +74,8 @@ class MainWindow(QMainWindow):
for x in range(self.width):
column = []
for y in range(self.height):
- if (x == 0 or x == self.width - 1 or y == 0 or
- y == self.height - 1 or generator.bounded(0, 40) == 0):
+ if (x == 0 or x == self.width - 1 or y == 0
+ or y == self.height - 1 or generator.bounded(0, 40) == 0):
column.append('#')
else:
column.append('.')
@@ -113,7 +113,7 @@ class MainWindow(QMainWindow):
input_state.addTransition(quit_transition)
machine.setInitialState(input_state)
- machine.finished.connect(qApp.quit)
+ machine.finished.connect(qApp.quit) # noqa: F821
machine.start()
def sizeHint(self):
diff --git a/examples/utils/pyside_config.py b/examples/utils/pyside_config.py
index feb56ed1c..d3067eec1 100644
--- a/examples/utils/pyside_config.py
+++ b/examples/utils/pyside_config.py
@@ -78,10 +78,12 @@ options.append(("--shiboken-module-shared-libraries-cmake",
options.append(("--pyside-shared-libraries-qmake",
lambda: get_shared_libraries_qmake(Package.PYSIDE_MODULE), pyside_libs_error,
- "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) for qmake"))
+ "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) "
+ "for qmake"))
options.append(("--pyside-shared-libraries-cmake",
lambda: get_shared_libraries_cmake(Package.PYSIDE_MODULE), pyside_libs_error,
- f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) for cmake"))
+ f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) "
+ "for cmake"))
options_usage = ''
for i, (flag, _, _, description) in enumerate(options):
@@ -153,11 +155,11 @@ def link_option(lib):
# libraries when compiling the project
baseName = os.path.basename(lib)
link = ' -l'
- if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so'
+ if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so'
link = lib
- elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo'
+ elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo'
link += os.path.splitext(baseName[3:])[0]
- else: # Windows: 'libfoo.dll' -> 'libfoo.dll'
+ else: # Windows: 'libfoo.dll' -> 'libfoo.dll'
link += os.path.splitext(baseName)[0]
return link
@@ -194,7 +196,7 @@ def find_package_path(dir_name):
return None
-# Return version as "3.7"
+# Return version as "x.y" (e.g. 3.9, 3.12, etc)
def python_version():
return str(sys.version_info[0]) + '.' + str(sys.version_info[1])
@@ -212,7 +214,7 @@ def python_link_flags_qmake():
# e.g.: "Program Files" to "Progra~1"
for d in libdir.split("\\"):
if " " in d:
- libdir = libdir.replace(d, d.split(" ")[0][:-1]+"~1")
+ libdir = libdir.replace(d, d.split(" ")[0][:-1] + "~1")
lib_flags = flags['lib']
return f'-L{libdir} -l{lib_flags}'
elif sys.platform == 'darwin':
diff --git a/examples/webchannel/standalone/ui_dialog.py b/examples/webchannel/standalone/ui_dialog.py
index 866d2a2e4..36c2fe400 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
diff --git a/examples/webenginequick/nanobrowser/quicknanobrowser.py b/examples/webenginequick/nanobrowser/quicknanobrowser.py
index 9cd3799ac..aee79c2aa 100644
--- a/examples/webenginequick/nanobrowser/quicknanobrowser.py
+++ b/examples/webenginequick/nanobrowser/quicknanobrowser.py
@@ -14,7 +14,7 @@ from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton
from PySide6.QtGui import QGuiApplication
from PySide6.QtWebEngineQuick import QtWebEngineQuick
-import rc_resources
+import rc_resources # noqa: F401
# To be used on the @QmlElement decorator
@@ -40,20 +40,25 @@ class Utils(QObject):
if __name__ == '__main__':
- QCoreApplication.setApplicationName("Quick Nano Browser");
- QCoreApplication.setOrganizationName("QtProject");
+ QCoreApplication.setApplicationName("Quick Nano Browser")
+ QCoreApplication.setOrganizationName("QtProject")
QtWebEngineQuick.initialize()
argument_parser = ArgumentParser(description="Quick Nano Browser",
formatter_class=RawTextHelpFormatter)
+ argument_parser.add_argument("--single-process", "-s", action="store_true",
+ help="Run in single process mode (trouble shooting)")
argument_parser.add_argument("url", help="The URL to open",
nargs='?', type=str)
options = argument_parser.parse_args()
url = url_from_user_input(options.url) if options.url else QUrl("https://www.qt.io")
- app = QGuiApplication([])
+ app_args = sys.argv
+ if options.single_process:
+ app_args.extend(["--webEngineArgs", "--single-process"])
+ app = QGuiApplication(app_args)
engine = QQmlApplicationEngine()
qml_file = os.fspath(Path(__file__).resolve().parent / 'ApplicationRoot.qml')
engine.load(QUrl.fromLocalFile(qml_file))
diff --git a/examples/webenginewidgets/markdowneditor/main.py b/examples/webenginewidgets/markdowneditor/main.py
index f89aa97d7..4d787f0f0 100644
--- a/examples/webenginewidgets/markdowneditor/main.py
+++ b/examples/webenginewidgets/markdowneditor/main.py
@@ -9,7 +9,7 @@ from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
-import rc_markdowneditor
+import rc_markdowneditor # noqa: F401
if __name__ == '__main__':
diff --git a/examples/webenginewidgets/markdowneditor/ui_mainwindow.py b/examples/webenginewidgets/markdowneditor/ui_mainwindow.py
index 0705ebfda..0be769119 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.2.3
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
diff --git a/examples/webenginewidgets/notifications/notificationpopup.py b/examples/webenginewidgets/notifications/notificationpopup.py
index bbea86102..e68ce3d6f 100644
--- a/examples/webenginewidgets/notifications/notificationpopup.py
+++ b/examples/webenginewidgets/notifications/notificationpopup.py
@@ -51,8 +51,8 @@ class NotificationPopup(QWidget):
self.notification.closed.connect(self.onClosed)
QTimer.singleShot(10000, lambda: self.onClosed())
- self.move(self.parentWidget().mapToGlobal(self.parentWidget().rect().bottomRight() -
- QPoint(self.width() + 10, self.height() + 10)))
+ self.move(self.parentWidget().mapToGlobal(self.parentWidget().rect().bottomRight()
+ - QPoint(self.width() + 10, self.height() + 10)))
@Slot()
def onClosed(self):
diff --git a/examples/webenginewidgets/simplebrowser/browser.py b/examples/webenginewidgets/simplebrowser/browser.py
index 4dc65bfa3..a124ea084 100644
--- a/examples/webenginewidgets/simplebrowser/browser.py
+++ b/examples/webenginewidgets/simplebrowser/browser.py
@@ -33,7 +33,8 @@ class Browser(QObject):
s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
s.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
s.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, False)
- self._profile.downloadRequested.connect(self._download_manager_widget.download_requested)
+ self._profile.downloadRequested.connect(
+ self._download_manager_widget.download_requested)
profile = QWebEngineProfile.defaultProfile() if offTheRecord else self._profile
main_window = BrowserWindow(self, profile, False)
diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py
index 576865742..43b811200 100644
--- a/examples/webenginewidgets/simplebrowser/browserwindow.py
+++ b/examples/webenginewidgets/simplebrowser/browserwindow.py
@@ -45,8 +45,10 @@ class BrowserWindow(QMainWindow):
self._profile = profile
self._tab_widget = TabWidget(profile, self)
- self._stop_icon = QIcon(":process-stop.png")
- self._reload_icon = QIcon(":view-refresh.png")
+ self._stop_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop,
+ QIcon(":process-stop.png"))
+ self._reload_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
+ QIcon(":view-refresh.png"))
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setFocusPolicy(Qt.ClickFocus)
@@ -84,7 +86,8 @@ class BrowserWindow(QMainWindow):
if not forDevTools:
self._tab_widget.link_hovered.connect(self._show_status_message)
self._tab_widget.load_progress.connect(self.handle_web_view_load_progress)
- self._tab_widget.web_action_enabled_changed.connect(self.handle_web_action_enabled_changed)
+ self._tab_widget.web_action_enabled_changed.connect(
+ self.handle_web_action_enabled_changed)
self._tab_widget.url_changed.connect(self._url_changed)
self._tab_widget.fav_icon_changed.connect(self._fav_action.setIcon)
self._tab_widget.dev_tools_requested.connect(self.handle_dev_tools_requested)
@@ -328,7 +331,7 @@ class BrowserWindow(QMainWindow):
def create_help_menu(self):
help_menu = QMenu("Help")
- help_menu.addAction("About Qt", qApp.aboutQt)
+ help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821
return help_menu
@Slot()
@@ -356,7 +359,9 @@ class BrowserWindow(QMainWindow):
back_shortcuts.append(QKeySequence(Qt.Key_Back))
self._history_back_action.setShortcuts(back_shortcuts)
self._history_back_action.setIconVisibleInMenu(False)
- self._history_back_action.setIcon(QIcon(":go-previous.png"))
+ back_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoPrevious,
+ QIcon(":go-previous.png"))
+ self._history_back_action.setIcon(back_icon)
self._history_back_action.setToolTip("Go back in history")
self._history_back_action.triggered.connect(self._back)
navigation_bar.addAction(self._history_back_action)
@@ -366,7 +371,9 @@ class BrowserWindow(QMainWindow):
fwd_shortcuts.append(QKeySequence(Qt.Key_Forward))
self._history_forward_action.setShortcuts(fwd_shortcuts)
self._history_forward_action.setIconVisibleInMenu(False)
- self._history_forward_action.setIcon(QIcon(":go-next.png"))
+ next_icon = QIcon.fromTheme(QIcon.ThemeIcon.GoNext,
+ QIcon(":go-next.png"))
+ self._history_forward_action.setIcon(next_icon)
self._history_forward_action.setToolTip("Go forward in history")
self._history_forward_action.triggered.connect(self._forward)
navigation_bar.addAction(self._history_forward_action)
diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.py b/examples/webenginewidgets/simplebrowser/downloadwidget.py
index 89dc2889a..3b4973cb8 100644
--- a/examples/webenginewidgets/simplebrowser/downloadwidget.py
+++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py
@@ -25,7 +25,6 @@ def with_unit(bytes):
class DownloadWidget(QFrame):
"""Displays one ongoing or finished download (QWebEngineDownloadRequest)."""
-
# This signal is emitted when the user indicates that they want to remove
# this download from the downloads list.
remove_clicked = Signal(QWidget)
@@ -35,8 +34,10 @@ class DownloadWidget(QFrame):
self._download = download
self._time_added = QElapsedTimer()
self._time_added.start()
- self._cancel_icon = QIcon(":process-stop.png")
- self._remove_icon = QIcon(":edit-clear.png")
+ self._cancel_icon = QIcon.fromTheme(QIcon.ThemeIcon.ProcessStop,
+ QIcon(":process-stop.png"))
+ self._remove_icon = QIcon.fromTheme(QIcon.ThemeIcon.EditClear,
+ QIcon(":edit-clear.png"))
self._ui = Ui_DownloadWidget()
self._ui.setupUi(self)
diff --git a/examples/webenginewidgets/simplebrowser/main.py b/examples/webenginewidgets/simplebrowser/main.py
index 054b8fa0f..781ec29eb 100644
--- a/examples/webenginewidgets/simplebrowser/main.py
+++ b/examples/webenginewidgets/simplebrowser/main.py
@@ -13,17 +13,22 @@ from PySide6.QtCore import QCoreApplication, QLoggingCategory, QUrl
from browser import Browser
-import data.rc_simplebrowser
+import data.rc_simplebrowser # noqa: F401
if __name__ == "__main__":
parser = ArgumentParser(description="Qt Widgets Web Browser",
formatter_class=RawTextHelpFormatter)
+ parser.add_argument("--single-process", "-s", action="store_true",
+ help="Run in single process mode (trouble shooting)")
parser.add_argument("url", type=str, nargs="?", help="URL")
args = parser.parse_args()
QCoreApplication.setOrganizationName("QtExamples")
- app = QApplication(sys.argv)
+ app_args = sys.argv
+ if args.single_process:
+ app_args.extend(["--webEngineArgs", "--single-process"])
+ app = QApplication(app_args)
app.setWindowIcon(QIcon(":AppLogoColor.png"))
QLoggingCategory.setFilterRules("qt.webenginecontext.debug=true")
diff --git a/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py b/examples/webenginewidgets/simplebrowser/ui_certificateerrordialog.py
index bf2fef36a..a963f0ac0 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -35,7 +35,7 @@ class Ui_CertificateErrorDialog(object):
self.m_errorLabel = QLabel(CertificateErrorDialog)
self.m_errorLabel.setObjectName(u"m_errorLabel")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.m_errorLabel.sizePolicy().hasHeightForWidth())
@@ -47,7 +47,7 @@ class Ui_CertificateErrorDialog(object):
self.m_infoLabel = QLabel(CertificateErrorDialog)
self.m_infoLabel.setObjectName(u"m_infoLabel")
- sizePolicy1 = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth())
@@ -57,7 +57,7 @@ class Ui_CertificateErrorDialog(object):
self.verticalLayout.addWidget(self.m_infoLabel)
- self.verticalSpacer = QSpacerItem(20, 16, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 16, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadmanagerwidget.py
index 0f98831d4..f0f61aa75 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -49,7 +49,7 @@ class Ui_DownloadManagerWidget(object):
self.m_itemsLayout.setContentsMargins(3, 3, 3, 3)
self.m_zeroItemsLabel = QLabel(self.m_items)
self.m_zeroItemsLabel.setObjectName(u"m_zeroItemsLabel")
- sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.m_zeroItemsLabel.sizePolicy().hasHeightForWidth())
diff --git a/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py b/examples/webenginewidgets/simplebrowser/ui_downloadwidget.py
index 3522f0758..58c32fdf8 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -40,7 +40,7 @@ class Ui_DownloadWidget(object):
self.m_cancelButton = QPushButton(DownloadWidget)
self.m_cancelButton.setObjectName(u"m_cancelButton")
- sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.m_cancelButton.sizePolicy().hasHeightForWidth())
diff --git a/examples/webenginewidgets/simplebrowser/ui_passworddialog.py b/examples/webenginewidgets/simplebrowser/ui_passworddialog.py
index 6a40f30e6..11e0c4a2e 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.5.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -34,7 +34,7 @@ class Ui_PasswordDialog(object):
self.m_infoLabel = QLabel(PasswordDialog)
self.m_infoLabel.setObjectName(u"m_infoLabel")
- sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.m_infoLabel.sizePolicy().hasHeightForWidth())
diff --git a/examples/webenginewidgets/simplebrowser/webview.py b/examples/webenginewidgets/simplebrowser/webview.py
index a7f042dcd..e1282c1dd 100644
--- a/examples/webenginewidgets/simplebrowser/webview.py
+++ b/examples/webenginewidgets/simplebrowser/webview.py
@@ -55,7 +55,8 @@ class WebView(QWebEngineView):
self.renderProcessTerminated.connect(self._render_process_terminated)
self._error_icon = QIcon(":dialog-error.png")
- self._loading_icon = QIcon(":view-refresh.png")
+ self._loading_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
+ QIcon(":view-refresh.png"))
self._default_icon = QIcon(":text-html.png")
@Slot()
@@ -88,7 +89,7 @@ class WebView(QWebEngineView):
elif termStatus == QWebEnginePage.KilledTerminationStatus:
status = "Render process killed"
- m = f"Render process exited with code: {statusCode}\nDo you want to reload the page?"
+ m = f"Render process exited with code: {statusCode:#x}\nDo you want to reload the page?"
btn = QMessageBox.question(self.window(), status, m)
if btn == QMessageBox.Yes:
QTimer.singleShot(0, self.reload)
@@ -99,8 +100,10 @@ class WebView(QWebEngineView):
old_page.createCertificateErrorDialog.disconnect(self.handle_certificate_error)
old_page.authenticationRequired.disconnect(self.handle_authentication_required)
old_page.featurePermissionRequested.disconnect(self.handle_feature_permission_requested)
- old_page.proxyAuthenticationRequired.disconnect(self.handle_proxy_authentication_required)
- old_page.registerProtocolHandlerRequested.disconnect(self.handle_register_protocol_handler_requested)
+ old_page.proxyAuthenticationRequired.disconnect(
+ self.handle_proxy_authentication_required)
+ old_page.registerProtocolHandlerRequested.disconnect(
+ self.handle_register_protocol_handler_requested)
old_page.fileSystemAccessRequested.disconnect(self.handle_file_system_access_requested)
self.create_web_action_trigger(page, QWebEnginePage.Forward)
@@ -112,7 +115,8 @@ class WebView(QWebEngineView):
page.authenticationRequired.connect(self.handle_authentication_required)
page.featurePermissionRequested.connect(self.handle_feature_permission_requested)
page.proxyAuthenticationRequired.connect(self.handle_proxy_authentication_required)
- page.registerProtocolHandlerRequested.connect(self.handle_register_protocol_handler_requested)
+ page.registerProtocolHandlerRequested.connect(
+ self.handle_register_protocol_handler_requested)
page.fileSystemAccessRequested.connect(self.handle_file_system_access_requested)
def load_progress(self):
@@ -229,8 +233,7 @@ class WebView(QWebEngineView):
question = question_for_feature(feature).replace("%1", host)
w = self.window()
page = self.page()
- if (question
- and QMessageBox.question(w, title, question) == QMessageBox.Yes):
+ if question and QMessageBox.question(w, title, question) == QMessageBox.Yes:
page.setFeaturePermission(securityOrigin, feature,
QWebEnginePage.PermissionGrantedByUser)
else:
diff --git a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py
index 48f60c6a8..2db865996 100644
--- a/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py
+++ b/examples/webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py
@@ -7,7 +7,7 @@ import sys
from PySide6.QtCore import QUrl, Slot
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (QApplication, QLineEdit,
- QMainWindow, QPushButton, QToolBar)
+ QMainWindow, QPushButton, QToolBar)
from PySide6.QtWebEngineCore import QWebEnginePage
from PySide6.QtWebEngineWidgets import QWebEngineView
diff --git a/examples/widgetbinding/doc/widgetbinding.md b/examples/widgetbinding/doc/widgetbinding.md
index 5fee93c8c..6355ea311 100644
--- a/examples/widgetbinding/doc/widgetbinding.md
+++ b/examples/widgetbinding/doc/widgetbinding.md
@@ -1,7 +1,5 @@
-# WigglyWidget
-
-The original Qt/C++ example can be found here:
-https://doc.qt.io/qt-6/qtwidgets-widgets-wiggly-example.html
+(widgetbinding-example)=
+# WigglyWidget Example
This example shows how to interact with a custom widget from two
different ways:
@@ -55,7 +53,7 @@ cd C:\pyside-setup\examples\widgetbinding
```bash
mkdir build
cd build
-cmake -H.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
+cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
ninja install
cd ..
diff --git a/examples/widgets/animation/animatedtiles/animatedtiles.py b/examples/widgets/animation/animatedtiles/animatedtiles.py
index b621ec117..02fc75bf5 100644
--- a/examples/widgets/animation/animatedtiles/animatedtiles.py
+++ b/examples/widgets/animation/animatedtiles/animatedtiles.py
@@ -15,7 +15,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsPixmapItem,
QGraphicsWidget, QStyle)
from PySide6.QtStateMachine import QState, QStateMachine
-import animatedtiles_rc
+import animatedtiles_rc # noqa: F401
# Deriving from more than one wrapped class is not supported, so we use
@@ -95,7 +95,7 @@ class Button(QGraphicsWidget):
painter.drawEllipse(r.adjusted(5, 5, -5, -5))
painter.drawPixmap(-self._pix.width() / 2, -self._pix.height() / 2,
- self._pix)
+ self._pix)
def mousePressEvent(self, ev):
self.pressed.emit()
@@ -123,7 +123,7 @@ if __name__ == '__main__':
for i in range(64):
item = Pixmap(kinetic_pix)
item.pixmap_item.setOffset(-kinetic_pix.width() / 2,
- -kinetic_pix.height() / 2)
+ -kinetic_pix.height() / 2)
item.pixmap_item.setZValue(i)
items.append(item)
scene.addItem(item.pixmap_item)
@@ -161,23 +161,25 @@ if __name__ == '__main__':
for i, item in enumerate(items):
# Ellipse.
ellipse_state.assignProperty(item, 'pos',
- QPointF(math.cos((i / 63.0) * 6.28) * 250,
- math.sin((i / 63.0) * 6.28) * 250))
+ QPointF(math.cos((i / 63.0) * 6.28) * 250,
+ math.sin((i / 63.0) * 6.28) * 250))
# Figure 8.
figure_8state.assignProperty(item, 'pos',
- QPointF(math.sin((i / 63.0) * 6.28) * 250,
- math.sin(((i * 2) / 63.0) * 6.28) * 250))
+ QPointF(math.sin((i / 63.0) * 6.28) * 250,
+ math.sin(((i * 2) / 63.0) * 6.28) * 250))
# Random.
random_state.assignProperty(item, 'pos',
- QPointF(-250 + generator.bounded(0, 500),
- -250 + generator.bounded(0, 500)))
+ QPointF(-250 + generator.bounded(0, 500),
+ -250 + generator.bounded(0, 500)))
# Tiled.
+ width = kinetic_pix.width()
+ height = kinetic_pix.height()
tiled_state.assignProperty(item, 'pos',
- QPointF(((i % 8) - 4) * kinetic_pix.width() + kinetic_pix.width() / 2,
- ((i // 8) - 4) * kinetic_pix.height() + kinetic_pix.height() / 2))
+ QPointF(((i % 8) - 4) * width + width / 2,
+ ((i // 8) - 4) * height + height / 2))
# Centered.
centered_state.assignProperty(item, 'pos', QPointF())
@@ -188,8 +190,7 @@ if __name__ == '__main__':
view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
view.setBackgroundBrush(QBrush(bg_pix))
view.setCacheMode(QGraphicsView.CacheBackground)
- view.setRenderHints(
- QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
+ view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
view.show()
states = QStateMachine()
diff --git a/examples/widgets/animation/appchooser/appchooser.py b/examples/widgets/animation/appchooser/appchooser.py
index e49f37dec..610050210 100644
--- a/examples/widgets/animation/appchooser/appchooser.py
+++ b/examples/widgets/animation/appchooser/appchooser.py
@@ -11,7 +11,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsScene, QGraphicsView,
QGraphicsWidget)
from PySide6.QtStateMachine import QState, QStateMachine
-import appchooser_rc
+import appchooser_rc # noqa: F401
class Pixmap(QGraphicsWidget):
diff --git a/examples/widgets/animation/easing/easing.py b/examples/widgets/animation/easing/easing.py
index f8cba0060..ba5032458 100644
--- a/examples/widgets/animation/easing/easing.py
+++ b/examples/widgets/animation/easing/easing.py
@@ -125,7 +125,6 @@ class Window(QWidget):
curve_types = [(f"QEasingCurve.{e.name}", e) for e in QEasingCurve.Type if e.value <= 40]
-
with QPainter(pix) as painter:
for curve_name, curve_type in curve_types:
@@ -144,14 +143,13 @@ class Window(QWidget):
# Start point.
painter.setBrush(Qt.red)
- start = QPoint(y_axis,
- x_axis - curve_scale * curve.valueForProgress(0))
+ 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)
end = QPoint(y_axis + curve_scale,
- x_axis - curve_scale * curve.valueForProgress(1))
+ x_axis - curve_scale * curve.valueForProgress(1))
painter.drawRect(end.x() - 1, end.y() - 1, 3, 3)
curve_path = QPainterPath()
@@ -159,7 +157,7 @@ class Window(QWidget):
t = 0.0
while t <= 1.0:
to = QPointF(y_axis + curve_scale * t,
- x_axis - curve_scale * curve.valueForProgress(t))
+ x_axis - curve_scale * curve.valueForProgress(t))
curve_path.lineTo(to)
t += 1.0 / curve_scale
@@ -185,14 +183,14 @@ class Window(QWidget):
self._anim.setCurrentTime(0)
is_elastic = (curve_type.value >= QEasingCurve.InElastic.value
- and curve_type.value <= QEasingCurve.OutInElastic.value)
+ and curve_type.value <= QEasingCurve.OutInElastic.value)
is_bounce = (curve_type.value >= QEasingCurve.InBounce.value
- and curve_type.value <= QEasingCurve.OutInBounce.value)
+ and curve_type.value <= QEasingCurve.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)
+ and curve_type.value <= QEasingCurve.OutInBack.value)
def path_changed(self, index):
self._anim.set_path_type(index)
diff --git a/examples/widgets/animation/easing/ui_form.py b/examples/widgets/animation/easing/ui_form.py
index 0ff7a1547..2925cbae7 100644
--- a/examples/widgets/animation/easing/ui_form.py
+++ b/examples/widgets/animation/easing/ui_form.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
################################################################################
-## Form generated from reading UI file 'form2.ui'
+## Form generated from reading UI file 'form.ui'
##
-## Created by: Qt User Interface Compiler version 6.4.0
+## Created by: Qt User Interface Compiler version 6.7.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -29,7 +29,7 @@ class Ui_Form(object):
self.gridLayout.setObjectName(u"gridLayout")
self.easingCurvePicker = QListWidget(Form)
self.easingCurvePicker.setObjectName(u"easingCurvePicker")
- sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.easingCurvePicker.sizePolicy().hasHeightForWidth())
@@ -73,7 +73,7 @@ class Ui_Form(object):
self.groupBox = QGroupBox(Form)
self.groupBox.setObjectName(u"groupBox")
- sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
@@ -83,7 +83,7 @@ class Ui_Form(object):
self.formLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
self.label = QLabel(self.groupBox)
self.label.setObjectName(u"label")
- sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
@@ -95,7 +95,7 @@ class Ui_Form(object):
self.periodSpinBox = QDoubleSpinBox(self.groupBox)
self.periodSpinBox.setObjectName(u"periodSpinBox")
self.periodSpinBox.setEnabled(False)
- sizePolicy3 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
+ sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.periodSpinBox.sizePolicy().hasHeightForWidth())
@@ -142,7 +142,7 @@ class Ui_Form(object):
self.verticalLayout.addWidget(self.groupBox)
- self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
self.verticalLayout.addItem(self.verticalSpacer)
@@ -151,7 +151,7 @@ class Ui_Form(object):
self.graphicsView = QGraphicsView(Form)
self.graphicsView.setObjectName(u"graphicsView")
- sizePolicy4 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy4.setHorizontalStretch(0)
sizePolicy4.setVerticalStretch(0)
sizePolicy4.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth())
diff --git a/examples/widgets/animation/states/states.py b/examples/widgets/animation/states/states.py
index 9b85e8373..509c3a7aa 100644
--- a/examples/widgets/animation/states/states.py
+++ b/examples/widgets/animation/states/states.py
@@ -16,7 +16,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsLinearLayout,
from PySide6.QtStateMachine import QState, QStateMachine
-import states_rc
+import states_rc # noqa: F401
class Pixmap(QGraphicsObject):
diff --git a/examples/widgets/desktop/systray/window.py b/examples/widgets/desktop/systray/window.py
index c04a33e3a..0b5bc39dc 100644
--- a/examples/widgets/desktop/systray/window.py
+++ b/examples/widgets/desktop/systray/window.py
@@ -9,7 +9,7 @@ from PySide6.QtWidgets import (QCheckBox, QComboBox, QDialog,
QSpinBox, QStyle, QSystemTrayIcon, QTextEdit,
QVBoxLayout)
-import rc_systray
+import rc_systray # noqa: F401
class Window(QDialog):
@@ -190,8 +190,8 @@ class Window(QDialog):
self._body_label = QLabel("Body:")
self._body_edit = QTextEdit()
- self._body_edit.setPlainText("Don't believe me. Honestly, I don't have a clue."
- "\nClick this balloon for details.")
+ self._body_edit.setPlainText("Don't believe me. Honestly, I don't have a clue.\n"
+ "Click this balloon for details.")
self._show_message_button = QPushButton("Show Message")
self._show_message_button.setDefault(True)
@@ -222,7 +222,7 @@ class Window(QDialog):
self._restore_action.triggered.connect(self.showNormal)
self._quit_action = QAction("Quit", self)
- self._quit_action.triggered.connect(qApp.quit)
+ self._quit_action.triggered.connect(qApp.quit) # noqa: F821
def create_tray_icon(self):
self._tray_icon_menu = QMenu(self)
diff --git a/examples/widgets/dialogs/classwizard/classwizard.py b/examples/widgets/dialogs/classwizard/classwizard.py
index 296cff06e..0efbde69f 100644
--- a/examples/widgets/dialogs/classwizard/classwizard.py
+++ b/examples/widgets/dialogs/classwizard/classwizard.py
@@ -15,7 +15,7 @@ from PySide6.QtWidgets import (QApplication, QComboBox, QCheckBox, QFormLayout,
from listchooser import PropertyChooser, SignalChooser
-import classwizard_rc
+import classwizard_rc # noqa: F401
BASE_CLASSES = ['<None>', 'PySide6.QtCore.QObject',
@@ -333,7 +333,7 @@ class OutputFilesPage(QWizardPage):
def _choose_output_dir(self):
directory = QFileDialog.getExistingDirectory(self, "Output Directory",
- self.output_dir())
+ self.output_dir())
if directory:
self.set_output_dir(directory)
diff --git a/examples/widgets/dialogs/classwizard/listchooser.py b/examples/widgets/dialogs/classwizard/listchooser.py
index bf15be88c..6bf47c6d1 100644
--- a/examples/widgets/dialogs/classwizard/listchooser.py
+++ b/examples/widgets/dialogs/classwizard/listchooser.py
@@ -27,7 +27,7 @@ class ValidatingInputDialog(QDialog):
self._lineedit = QLineEdit()
self._lineedit.setClearButtonEnabled(True)
re = QRegularExpression(pattern)
- assert(re.isValid())
+ assert re.isValid()
self._validator = QRegularExpressionValidator(re, self)
self._lineedit.setValidator(self._validator)
self._form_layout.addRow(label, self._lineedit)
diff --git a/examples/widgets/dialogs/standarddialogs/standarddialogs.py b/examples/widgets/dialogs/standarddialogs/standarddialogs.py
index 8a2a4426e..ef677d5a8 100644
--- a/examples/widgets/dialogs/standarddialogs/standarddialogs.py
+++ b/examples/widgets/dialogs/standarddialogs/standarddialogs.py
@@ -235,14 +235,14 @@ class Dialog(QDialog):
@Slot()
def set_integer(self):
i, ok = QInputDialog.getInt(self,
- "QInputDialog.getInteger()", "Percentage:", 25, 0, 100, 1)
+ "QInputDialog.getInteger()", "Percentage:", 25, 0, 100, 1)
if ok:
self._integer_label.setText(f"{i}%")
@Slot()
def set_double(self):
d, ok = QInputDialog.getDouble(self, "QInputDialog.getDouble()",
- "Amount:", 37.56, -10000, 10000, 2)
+ "Amount:", 37.56, -10000, 10000, 2)
if ok:
self._double_label.setText(f"${d:g}")
@@ -250,23 +250,21 @@ class Dialog(QDialog):
def set_item(self):
items = ("Spring", "Summer", "Fall", "Winter")
- item, ok = QInputDialog.getItem(self, "QInputDialog.getItem()",
- "Season:", items, 0, False)
+ item, ok = QInputDialog.getItem(self, "QInputDialog.getItem()", "Season:", items, 0, False)
if ok and item:
self._item_label.setText(item)
@Slot()
def set_text(self):
text, ok = QInputDialog.getText(self, "QInputDialog.getText()",
- "User name:", QLineEdit.Normal,
- QDir.home().dirName())
+ "User name:", QLineEdit.Normal, QDir.home().dirName())
if ok and text != '':
self._text_label.setText(text)
@Slot()
def set_multiline_text(self):
text, ok = QInputDialog.getMultiLineText(self, "QInputDialog::getMultiLineText()",
- "Address:", "John Doe\nFreedom Street")
+ "Address:", "John Doe\nFreedom Street")
if ok and text != '':
self._multiline_text_label.setText(text)
@@ -301,9 +299,8 @@ class Dialog(QDialog):
options_value = self._file_options.value()
options = QFileDialog.Options(options_value) | QFileDialog.ShowDirsOnly
- directory = QFileDialog.getExistingDirectory(self,
- "QFileDialog.getExistingDirectory()",
- self._directory_label.text(), options)
+ directory = QFileDialog.getExistingDirectory(self, "QFileDialog.getExistingDirectory()",
+ self._directory_label.text(), options)
if directory:
self._directory_label.setText(directory)
@@ -312,10 +309,9 @@ class Dialog(QDialog):
options_value = self._file_options.value()
options = QFileDialog.Options(options_value)
- fileName, filtr = QFileDialog.getOpenFileName(self,
- "QFileDialog.getOpenFileName()",
- self._open_file_name_label.text(),
- "All Files (*);;Text Files (*.txt)", "", options)
+ fileName, _ = QFileDialog.getOpenFileName(self, "QFileDialog.getOpenFileName()",
+ self._open_file_name_label.text(),
+ "All Files (*);;Text Files (*.txt)", "", options)
if fileName:
self._open_file_name_label.setText(fileName)
@@ -324,9 +320,9 @@ class Dialog(QDialog):
options_value = self._file_options.value()
options = QFileDialog.Options(options_value)
- files, filtr = QFileDialog.getOpenFileNames(self,
- "QFileDialog.getOpenFileNames()", self._open_files_path,
- "All Files (*);;Text Files (*.txt)", "", options)
+ files, _ = QFileDialog.getOpenFileNames(self, "QFileDialog.getOpenFileNames()",
+ self._open_files_path,
+ "All Files (*);;Text Files (*.txt)", "", options)
if files:
self._open_files_path = files[0]
file_list = ', '.join(files)
@@ -337,10 +333,9 @@ class Dialog(QDialog):
options_value = self._file_options.value()
options = QFileDialog.Options(options_value)
- fileName, filtr = QFileDialog.getSaveFileName(self,
- "QFileDialog.getSaveFileName()",
- self._save_file_name_label.text(),
- "All Files (*);;Text Files (*.txt)", "", options)
+ fileName, _ = QFileDialog.getSaveFileName(self, "QFileDialog.getSaveFileName()",
+ self._save_file_name_label.text(),
+ "All Files (*);;Text Files (*.txt)", "", options)
if fileName:
self._save_file_name_label.setText(fileName)
diff --git a/examples/widgets/dialogs/trivialwizard/trivialwizard.py b/examples/widgets/dialogs/trivialwizard/trivialwizard.py
index 0eb9fb567..2e551ae19 100644
--- a/examples/widgets/dialogs/trivialwizard/trivialwizard.py
+++ b/examples/widgets/dialogs/trivialwizard/trivialwizard.py
@@ -15,7 +15,7 @@ def create_intro_page():
page.setTitle("Introduction")
label = QLabel("This wizard will help you register your copy of "
- "Super Product Two.")
+ "Super Product Two.")
label.setWordWrap(True)
layout = QVBoxLayout(page)
diff --git a/examples/widgets/draganddrop/draggabletext/draggabletext.py b/examples/widgets/draganddrop/draggabletext/draggabletext.py
index 4b470cc9d..6ffdbd70e 100644
--- a/examples/widgets/draganddrop/draggabletext/draggabletext.py
+++ b/examples/widgets/draganddrop/draggabletext/draggabletext.py
@@ -2,13 +2,14 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the widgets/draganddrop/draggabletext example from Qt v5.x, originating from PyQt"""
+"""PySide6 port of the widgets/draganddrop/draggabletext example from Qt v5.x,
+ originating from PyQt"""
from PySide6.QtCore import QFile, QIODevice, QMimeData, QPoint, Qt, QTextStream
from PySide6.QtGui import QDrag, QPalette, QPixmap
from PySide6.QtWidgets import QApplication, QFrame, QLabel, QWidget
-import draggabletext_rc
+import draggabletext_rc # noqa: F401
class DragLabel(QLabel):
diff --git a/examples/widgets/effects/lighting/lighting.py b/examples/widgets/effects/lighting/lighting.py
index fe200357d..f074813c3 100644
--- a/examples/widgets/effects/lighting/lighting.py
+++ b/examples/widgets/effects/lighting/lighting.py
@@ -37,8 +37,7 @@ class Lighting(QGraphicsView):
def setup_scene(self):
self.m_scene.setSceneRect(-300, -200, 600, 460)
- linear_grad = QLinearGradient(QPointF(-100, -100),
- QPointF(100, 100))
+ linear_grad = QLinearGradient(QPointF(-100, -100), QPointF(100, 100))
linear_grad.setColorAt(0, QColor(255, 255, 255))
linear_grad.setColorAt(1, QColor(192, 192, 255))
self.setBackgroundBrush(linear_grad)
diff --git a/examples/widgets/gettext/doc/gettext.rst b/examples/widgets/gettext/doc/gettext.rst
new file mode 100644
index 000000000..ea1127fe7
--- /dev/null
+++ b/examples/widgets/gettext/doc/gettext.rst
@@ -0,0 +1,7 @@
+.. _gettext-example:
+
+GNU gettext Example
+===================
+
+This example demonstrates the use of GNU gettext for translating
+applications as described in :ref:`translations`.
diff --git a/examples/widgets/graphicsview/anchorlayout/anchorlayout.py b/examples/widgets/graphicsview/anchorlayout/anchorlayout.py
index d6e8e57b2..9d9be041f 100644
--- a/examples/widgets/graphicsview/anchorlayout/anchorlayout.py
+++ b/examples/widgets/graphicsview/anchorlayout/anchorlayout.py
@@ -41,7 +41,7 @@ if __name__ == '__main__':
f = create_item(QSizeF(30, 50), QSizeF(150, 50), max_size, "F")
g = create_item(QSizeF(30, 50), QSizeF(30, 100), max_size, "G")
- l = QGraphicsAnchorLayout()
+ l = QGraphicsAnchorLayout() # noqa: E741
l.setSpacing(0)
w = QGraphicsWidget(None, Qt.Window)
diff --git a/examples/widgets/graphicsview/collidingmice/collidingmice.py b/examples/widgets/graphicsview/collidingmice/collidingmice.py
index 53c5c1aab..fb24db3ab 100644
--- a/examples/widgets/graphicsview/collidingmice/collidingmice.py
+++ b/examples/widgets/graphicsview/collidingmice/collidingmice.py
@@ -5,14 +5,11 @@
import math
import sys
-from PySide6.QtCore import (QLineF, QPointF, QRandomGenerator, QRectF, QTimer,
- Qt)
-from PySide6.QtGui import (QBrush, QColor, QPainter, QPainterPath, QPixmap,
- QPolygonF, QTransform)
-from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene,
- QGraphicsView)
+from PySide6.QtCore import (QLineF, QPointF, QRandomGenerator, QRectF, QTimer, Qt)
+from PySide6.QtGui import (QBrush, QColor, QPainter, QPainterPath, QPixmap, QPolygonF, QTransform)
+from PySide6.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView)
-import mice_rc
+import mice_rc # noqa: F401
def random(boundary):
@@ -26,7 +23,7 @@ class Mouse(QGraphicsItem):
# Create the bounding rectangle once.
adjust = 0.5
BOUNDING_RECT = QRectF(-20 - adjust, -22 - adjust, 40 + adjust,
- 83 + adjust)
+ 83 + adjust)
def __init__(self):
super().__init__()
@@ -103,7 +100,8 @@ class Mouse(QGraphicsItem):
if angle_to_center < Mouse.PI and angle_to_center > Mouse.PI / 4:
# Rotate left.
self.angle += [-0.25, 0.25][self.angle < -Mouse.PI / 2]
- elif angle_to_center >= Mouse.PI and angle_to_center < (Mouse.PI + Mouse.PI / 2 + Mouse.PI / 4):
+ elif (angle_to_center >= Mouse.PI
+ and angle_to_center < (Mouse.PI + Mouse.PI / 2 + Mouse.PI / 4)):
# Rotate right.
self.angle += [-0.25, 0.25][self.angle < Mouse.PI / 2]
elif math.sin(self.angle) < 0:
diff --git a/examples/widgets/graphicsview/diagramscene/diagramscene.py b/examples/widgets/graphicsview/diagramscene/diagramscene.py
index 3348577a4..60e05613c 100644
--- a/examples/widgets/graphicsview/diagramscene/diagramscene.py
+++ b/examples/widgets/graphicsview/diagramscene/diagramscene.py
@@ -17,7 +17,7 @@ from PySide6.QtWidgets import (QAbstractButton, QApplication, QButtonGroup,
QMessageBox, QSizePolicy, QToolBox, QToolButton,
QWidget)
-import diagramscene_rc
+import diagramscene_rc # noqa: F401
class Arrow(QGraphicsLineItem):
@@ -30,8 +30,7 @@ class Arrow(QGraphicsLineItem):
self._my_end_item = endItem
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self._my_color = Qt.black
- self.setPen(QPen(self._my_color, 2, Qt.SolidLine,
- Qt.RoundCap, Qt.RoundJoin))
+ self.setPen(QPen(self._my_color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
def set_color(self, color):
self._my_color = color
@@ -163,19 +162,19 @@ class DiagramItem(QGraphicsPolygonItem):
self._my_polygon = path.toFillPolygon()
elif self.diagram_type == self.Conditional:
self._my_polygon = QPolygonF([
- QPointF(-100, 0), QPointF(0, 100),
- QPointF(100, 0), QPointF(0, -100),
- QPointF(-100, 0)])
+ QPointF(-100, 0), QPointF(0, 100),
+ QPointF(100, 0), QPointF(0, -100),
+ QPointF(-100, 0)])
elif self.diagram_type == self.Step:
self._my_polygon = QPolygonF([
- QPointF(-100, -100), QPointF(100, -100),
- QPointF(100, 100), QPointF(-100, 100),
- QPointF(-100, -100)])
+ QPointF(-100, -100), QPointF(100, -100),
+ QPointF(100, 100), QPointF(-100, 100),
+ QPointF(-100, -100)])
else:
self._my_polygon = QPolygonF([
- QPointF(-120, -80), QPointF(-70, 80),
- QPointF(120, 80), QPointF(70, -80),
- QPointF(-120, -80)])
+ QPointF(-120, -80), QPointF(-70, 80),
+ QPointF(120, 80), QPointF(70, -80),
+ QPointF(-120, -80)])
self.setPolygon(self._my_polygon)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
@@ -291,8 +290,7 @@ class DiagramScene(QGraphicsScene):
item.setPos(mouseEvent.scenePos())
self.item_inserted.emit(item)
elif self._my_mode == self.InsertLine:
- self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(),
- mouseEvent.scenePos()))
+ self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos()))
self.line.setPen(QPen(self._my_line_color, 2))
self.addItem(self.line)
elif self._my_mode == self.InsertText:
@@ -328,10 +326,10 @@ class DiagramScene(QGraphicsScene):
self.removeItem(self.line)
self.line = None
- if (len(start_items) and len(end_items) and
- isinstance(start_items[0], DiagramItem) and
- isinstance(end_items[0], DiagramItem) and
- start_items[0] != end_items[0]):
+ if (len(start_items) and len(end_items)
+ and isinstance(start_items[0], DiagramItem)
+ and isinstance(end_items[0], DiagramItem)
+ and start_items[0] != end_items[0]):
start_item = start_items[0]
end_item = end_items[0]
arrow = Arrow(start_item, end_item)
@@ -484,24 +482,21 @@ class MainWindow(QMainWindow):
def text_color_changed(self):
self._text_action = self.sender()
self._font_color_tool_button.setIcon(self.create_color_tool_button_icon(
- ':/images/textpointer.png',
- QColor(self._text_action.data())))
+ ':/images/textpointer.png', QColor(self._text_action.data())))
self.text_button_triggered()
@Slot()
def item_color_changed(self):
self._fill_action = self.sender()
self._fill_color_tool_button.setIcon(self.create_color_tool_button_icon(
- ':/images/floodfill.png',
- QColor(self._fill_action.data())))
+ ':/images/floodfill.png', QColor(self._fill_action.data())))
self.fill_button_triggered()
@Slot()
def line_color_changed(self):
self._line_action = self.sender()
self._line_color_tool_button.setIcon(self.create_color_tool_button_icon(
- ':/images/linecolor.png',
- QColor(self._line_action.data())))
+ ':/images/linecolor.png', QColor(self._line_action.data())))
self.line_button_triggered()
@Slot()
@@ -541,7 +536,7 @@ class MainWindow(QMainWindow):
@Slot()
def about(self):
QMessageBox.about(self, "About Diagram Scene",
- "The <b>Diagram Scene</b> example shows use of the graphics framework.")
+ "The <b>Diagram Scene</b> example shows use of the graphics framework.")
def create_tool_box(self):
self._button_group = QButtonGroup()
@@ -549,12 +544,9 @@ class MainWindow(QMainWindow):
self._button_group.idClicked.connect(self.button_group_clicked)
layout = QGridLayout()
- layout.addWidget(self.create_cell_widget("Conditional", DiagramItem.Conditional),
- 0, 0)
- layout.addWidget(self.create_cell_widget("Process", DiagramItem.Step), 0,
- 1)
- layout.addWidget(self.create_cell_widget("Input/Output", DiagramItem.Io),
- 1, 0)
+ layout.addWidget(self.create_cell_widget("Conditional", DiagramItem.Conditional), 0, 0)
+ layout.addWidget(self.create_cell_widget("Process", DiagramItem.Step), 0, 1)
+ layout.addWidget(self.create_cell_widget("Input/Output", DiagramItem.Io), 1, 0)
text_button = QToolButton()
text_button.setCheckable(True)
@@ -580,14 +572,14 @@ class MainWindow(QMainWindow):
self._background_button_group.buttonClicked.connect(self.background_button_group_clicked)
background_layout = QGridLayout()
- background_layout.addWidget(self.create_background_cell_widget("Blue Grid",
- ':/images/background1.png'), 0, 0)
- background_layout.addWidget(self.create_background_cell_widget("White Grid",
- ':/images/background2.png'), 0, 1)
- background_layout.addWidget(self.create_background_cell_widget("Gray Grid",
- ':/images/background3.png'), 1, 0)
- background_layout.addWidget(self.create_background_cell_widget("No Grid",
- ':/images/background4.png'), 1, 1)
+ background_layout.addWidget(
+ self.create_background_cell_widget("Blue Grid", ':/images/background1.png'), 0, 0)
+ background_layout.addWidget(
+ self.create_background_cell_widget("White Grid", ':/images/background2.png'), 0, 1)
+ background_layout.addWidget(
+ self.create_background_cell_widget("Gray Grid", ':/images/background3.png'), 1, 0)
+ background_layout.addWidget(
+ self.create_background_cell_widget("No Grid", ':/images/background4.png'), 1, 1)
background_layout.setRowStretch(2, 10)
background_layout.setColumnStretch(2, 10)
@@ -603,38 +595,37 @@ class MainWindow(QMainWindow):
def create_actions(self):
self._to_front_action = QAction(
- QIcon(':/images/bringtofront.png'), "Bring to &Front",
- self, shortcut="Ctrl+F", statusTip="Bring item to front",
- triggered=self.bring_to_front)
+ QIcon(':/images/bringtofront.png'), "Bring to &Front",
+ self, shortcut="Ctrl+F", statusTip="Bring item to front",
+ triggered=self.bring_to_front)
self._send_back_action = QAction(
- QIcon(':/images/sendtoback.png'), "Send to &Back", self,
- shortcut="Ctrl+B", statusTip="Send item to back",
- triggered=self.send_to_back)
+ QIcon(':/images/sendtoback.png'), "Send to &Back", self,
+ shortcut="Ctrl+B", statusTip="Send item to back",
+ triggered=self.send_to_back)
self._delete_action = QAction(QIcon(':/images/delete.png'),
- "&Delete", self, shortcut="Delete",
- statusTip="Delete item from diagram",
- triggered=self.delete_item)
+ "&Delete", self, shortcut="Delete",
+ statusTip="Delete item from diagram",
+ triggered=self.delete_item)
self._exit_action = QAction("E&xit", self, shortcut="Ctrl+X",
- statusTip="Quit Scenediagram example", triggered=self.close)
+ statusTip="Quit Scenediagram example", triggered=self.close)
self._bold_action = QAction(QIcon(':/images/bold.png'),
- "Bold", self, checkable=True, shortcut="Ctrl+B",
- triggered=self.handle_font_change)
+ "Bold", self, checkable=True, shortcut="Ctrl+B",
+ triggered=self.handle_font_change)
self._italic_action = QAction(QIcon(':/images/italic.png'),
- "Italic", self, checkable=True, shortcut="Ctrl+I",
- triggered=self.handle_font_change)
+ "Italic", self, checkable=True, shortcut="Ctrl+I",
+ triggered=self.handle_font_change)
self._underline_action = QAction(
- QIcon(':/images/underline.png'), "Underline", self,
- checkable=True, shortcut="Ctrl+U",
- triggered=self.handle_font_change)
+ QIcon(':/images/underline.png'), "Underline", self,
+ checkable=True, shortcut="Ctrl+U",
+ triggered=self.handle_font_change)
- self._about_action = QAction("A&bout", self, shortcut="Ctrl+B",
- triggered=self.about)
+ self._about_action = QAction("A&bout", self, shortcut="Ctrl+B", triggered=self.about)
def create_menus(self):
self._file_menu = self.menuBar().addMenu("&File")
@@ -669,32 +660,29 @@ class MainWindow(QMainWindow):
self._font_color_tool_button = QToolButton()
self._font_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup)
self._font_color_tool_button.setMenu(
- self.create_color_menu(self.text_color_changed, Qt.black))
+ self.create_color_menu(self.text_color_changed, Qt.black))
self._text_action = self._font_color_tool_button.menu().defaultAction()
self._font_color_tool_button.setIcon(
- self.create_color_tool_button_icon(':/images/textpointer.png',
- Qt.black))
+ self.create_color_tool_button_icon(':/images/textpointer.png', Qt.black))
self._font_color_tool_button.setAutoFillBackground(True)
self._font_color_tool_button.clicked.connect(self.text_button_triggered)
self._fill_color_tool_button = QToolButton()
self._fill_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup)
self._fill_color_tool_button.setMenu(
- self.create_color_menu(self.item_color_changed, Qt.white))
+ self.create_color_menu(self.item_color_changed, Qt.white))
self._fill_action = self._fill_color_tool_button.menu().defaultAction()
self._fill_color_tool_button.setIcon(
- self.create_color_tool_button_icon(':/images/floodfill.png',
- Qt.white))
+ self.create_color_tool_button_icon(':/images/floodfill.png', Qt.white))
self._fill_color_tool_button.clicked.connect(self.fill_button_triggered)
self._line_color_tool_button = QToolButton()
self._line_color_tool_button.setPopupMode(QToolButton.MenuButtonPopup)
self._line_color_tool_button.setMenu(
- self.create_color_menu(self.line_color_changed, Qt.black))
+ self.create_color_menu(self.line_color_changed, Qt.black))
self._line_action = self._line_color_tool_button.menu().defaultAction()
self._line_color_tool_button.setIcon(
- self.create_color_tool_button_icon(':/images/linecolor.png',
- Qt.black))
+ self.create_color_tool_button_icon(':/images/linecolor.png', Qt.black))
self._line_color_tool_button.clicked.connect(self.line_button_triggered)
self._text_tool_bar = self.addToolBar("Font")
@@ -719,8 +707,7 @@ class MainWindow(QMainWindow):
self._pointer_type_group = QButtonGroup()
self._pointer_type_group.addButton(pointer_button, DiagramScene.MoveItem)
- self._pointer_type_group.addButton(line_pointer_button,
- DiagramScene.InsertLine)
+ self._pointer_type_group.addButton(line_pointer_button, DiagramScene.InsertLine)
self._pointer_type_group.idClicked.connect(self.pointer_group_clicked)
self._scene_scale_combo = QComboBox()
@@ -775,8 +762,7 @@ class MainWindow(QMainWindow):
color_menu = QMenu(self)
for color, name in zip(colors, names):
- action = QAction(self.create_color_icon(color), name, self,
- triggered=slot)
+ action = QAction(self.create_color_icon(color), name, self, triggered=slot)
action.setData(QColor(color))
color_menu.addAction(action)
if color == defaultColor:
diff --git a/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py b/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py
index 0bfd3e0db..9ed92b26e 100644
--- a/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py
+++ b/examples/widgets/graphicsview/dragdroprobot/dragdroprobot.py
@@ -13,7 +13,7 @@ from PySide6.QtWidgets import (QApplication, QGraphicsItem,
QGraphicsItemAnimation, QGraphicsScene,
QGraphicsView)
-import dragdroprobot_rc
+import dragdroprobot_rc # noqa: F401
def random(boundary):
@@ -103,8 +103,8 @@ class RobotPart(QGraphicsItem):
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
- if (event.mimeData().hasColor() or
- (isinstance(self, RobotHead) and event.mimeData().hasImage())):
+ if (event.mimeData().hasColor()
+ or (isinstance(self, RobotHead) and event.mimeData().hasImage())):
event.setAccepted(True)
self._drag_over = True
self.update()
@@ -131,8 +131,7 @@ 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.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.drawEllipse(-7, -3 - 20, 7, 7)
@@ -154,7 +153,7 @@ class RobotTorso(RobotPart):
def paint(self, painter, option, widget=None):
painter.setBrush(self._drag_over and self.color.lighter(130)
- or self.color)
+ or self.color)
painter.drawRoundedRect(-20, -20, 40, 60, 25, 25, Qt.RelativeSize)
painter.drawEllipse(-25, -20, 20, 20)
painter.drawEllipse(5, -20, 20, 20)
@@ -169,7 +168,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.RelativeSize)
painter.drawEllipse(-5, -5, 10, 10)
@@ -177,35 +176,35 @@ class Robot(RobotPart):
def __init__(self):
super().__init__()
- self.torsoItem = RobotTorso(self)
- self.headItem = RobotHead(self.torsoItem)
- self.upperLeftArmItem = RobotLimb(self.torsoItem)
- self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem)
+ self.torsoItem = RobotTorso(self)
+ self.headItem = RobotHead(self.torsoItem)
+ self.upperLeftArmItem = RobotLimb(self.torsoItem)
+ self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem)
self._upper_right_arm_item = RobotLimb(self.torsoItem)
self._lower_right_arm_item = RobotLimb(self._upper_right_arm_item)
self._upper_right_leg_item = RobotLimb(self.torsoItem)
self._lower_right_leg_item = RobotLimb(self._upper_right_leg_item)
- self.upperLeftLegItem = RobotLimb(self.torsoItem)
- self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem)
+ self.upperLeftLegItem = RobotLimb(self.torsoItem)
+ self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem)
self.timeline = QTimeLine()
settings = [
- # item position rotation at
- # x y time 0 / 1
- ( self.headItem, 0, -18, 20, -20 ),
- ( self.upperLeftArmItem, -15, -10, 190, 180 ),
- ( self.lowerLeftArmItem, 30, 0, 50, 10 ),
- ( self._upper_right_arm_item, 15, -10, 300, 310 ),
- ( self._lower_right_arm_item, 30, 0, 0, -70 ),
- ( self._upper_right_leg_item, 10, 32, 40, 120 ),
- ( self._lower_right_leg_item, 30, 0, 10, 50 ),
- ( self.upperLeftLegItem, -10, 32, 150, 80 ),
- ( self.lowerLeftLegItem, 30, 0, 70, 10 ),
- ( self.torsoItem, 0, 0, 5, -20 )
+ # item position rotation at
+ # x y time 0 / 1
+ (self.headItem, 0, -18, 20, -20), # noqa: E241
+ (self.upperLeftArmItem, -15, -10, 190, 180), # noqa: E241
+ (self.lowerLeftArmItem, 30, 0, 50, 10), # noqa: E241
+ (self._upper_right_arm_item, 15, -10, 300, 310), # noqa: E241
+ (self._lower_right_arm_item, 30, 0, 0, -70), # noqa: E241
+ (self._upper_right_leg_item, 10, 32, 40, 120), # noqa: E241
+ (self._lower_right_leg_item, 30, 0, 10, 50), # noqa: E241
+ (self.upperLeftLegItem, -10, 32, 150, 80), # noqa: E241
+ (self.lowerLeftLegItem, 30, 0, 70, 10), # noqa: E241
+ (self.torsoItem, 0, 0, 5, -20) # noqa: E241
]
self.animations = []
for item, pos_x, pos_y, rotation1, rotation2 in settings:
- item.setPos(pos_x,pos_y)
+ item.setPos(pos_x, pos_y)
animation = QGraphicsItemAnimation()
animation.setItem(item)
animation.setTimeLine(self.timeline)
@@ -228,7 +227,7 @@ class Robot(RobotPart):
pass
-if __name__== '__main__':
+if __name__ == '__main__':
app = QApplication(sys.argv)
scene = QGraphicsScene(-200, -200, 400, 400)
diff --git a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py
index 58d9d4a90..90cb49626 100644
--- a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py
+++ b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py
@@ -20,8 +20,6 @@ def random(boundary):
class Edge(QGraphicsItem):
- item_type = QGraphicsItem.UserType + 2
-
def __init__(self, sourceNode, destNode):
super().__init__()
@@ -36,7 +34,7 @@ class Edge(QGraphicsItem):
self.adjust()
def item_type(self):
- return Edge.item_type
+ return QGraphicsItem.UserType + 2
def source_node(self):
return self.source()
@@ -119,7 +117,6 @@ class Edge(QGraphicsItem):
class Node(QGraphicsItem):
- item_type = QGraphicsItem.UserType + 1
def __init__(self, graphWidget):
super().__init__()
@@ -133,7 +130,7 @@ class Node(QGraphicsItem):
self.setZValue(-1)
def item_type(self):
- return Node.item_type
+ return QGraphicsItem.UserType + 1
def add_edge(self, edge):
self._edge_list.append(weakref.ref(edge))
@@ -157,7 +154,7 @@ class Node(QGraphicsItem):
line = QLineF(self.mapFromItem(item, 0, 0), QPointF(0, 0))
dx = line.dx()
dy = line.dy()
- l = 2.0 * (dx * dx + dy * dy)
+ l = 2.0 * (dx * dx + dy * dy) # noqa: E741
if l > 0:
xvel += (dx * 150.0) / l
yvel += (dy * 150.0) / l
@@ -192,7 +189,7 @@ class Node(QGraphicsItem):
def boundingRect(self):
adjust = 2.0
return QRectF(-10 - adjust, -10 - adjust,
- 23 + adjust, 23 + adjust)
+ 23 + adjust, 23 + adjust)
def shape(self):
path = QPainterPath()
@@ -348,9 +345,9 @@ class GraphWidget(QGraphicsView):
bottom_shadow = QRectF(scene_rect.left() + 5, scene_rect.bottom(),
scene_rect.width(), 5)
if right_shadow.intersects(rect) or right_shadow.contains(rect):
- painter.fillRect(right_shadow, Qt.darkGray)
+ painter.fillRect(right_shadow, Qt.darkGray)
if bottom_shadow.intersects(rect) or bottom_shadow.contains(rect):
- painter.fillRect(bottom_shadow, Qt.darkGray)
+ painter.fillRect(bottom_shadow, Qt.darkGray)
# Fill.
gradient = QLinearGradient(scene_rect.topLeft(), scene_rect.bottomRight())
@@ -362,7 +359,7 @@ class GraphWidget(QGraphicsView):
# Text.
text_rect = QRectF(scene_rect.left() + 4, scene_rect.top() + 4,
- scene_rect.width() - 4, scene_rect.height() - 4)
+ scene_rect.width() - 4, scene_rect.height() - 4)
message = self.tr("Click and drag the nodes around, and zoom with the "
"mouse wheel or the '+' and '-' keys")
@@ -376,7 +373,8 @@ class GraphWidget(QGraphicsView):
painter.drawText(text_rect, message)
def scale_view(self, scaleFactor):
- factor = self.transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width()
+ factor = self.transform().scale(scaleFactor, scaleFactor).mapRect(
+ QRectF(0, 0, 1, 1)).width()
if factor < 0.07 or factor > 100:
return
diff --git a/examples/widgets/imageviewer/imageviewer.py b/examples/widgets/imageviewer/imageviewer.py
index ded7f246a..93b1e87ba 100644
--- a/examples/widgets/imageviewer/imageviewer.py
+++ b/examples/widgets/imageviewer/imageviewer.py
@@ -34,7 +34,7 @@ class ImageViewer(QMainWindow):
self._image_label = QLabel()
self._image_label.setBackgroundRole(QPalette.Base)
self._image_label.setSizePolicy(QSizePolicy.Ignored,
- QSizePolicy.Ignored)
+ QSizePolicy.Ignored)
self._image_label.setScaledContents(True)
self._scroll_area = QScrollArea()
diff --git a/examples/widgets/itemviews/address_book/adddialogwidget.py b/examples/widgets/itemviews/address_book/adddialogwidget.py
index 276a8d2b4..ecb853e80 100644
--- a/examples/widgets/itemviews/address_book/adddialogwidget.py
+++ b/examples/widgets/itemviews/address_book/adddialogwidget.py
@@ -15,8 +15,8 @@ class AddDialogWidget(QDialog):
name_label = QLabel("Name")
address_label = QLabel("Address")
- button_box = QDialogButtonBox(QDialogButtonBox.Ok |
- QDialogButtonBox.Cancel)
+ button_box = QDialogButtonBox(QDialogButtonBox.Ok
+ | QDialogButtonBox.Cancel)
self._name_text = QLineEdit()
self._address_text = QTextEdit()
diff --git a/examples/widgets/itemviews/address_book/address_book.py b/examples/widgets/itemviews/address_book/address_book.py
index 2e1f6b9b0..af0cf3dee 100644
--- a/examples/widgets/itemviews/address_book/address_book.py
+++ b/examples/widgets/itemviews/address_book/address_book.py
@@ -25,16 +25,19 @@ class MainWindow(QMainWindow):
tool_menu = self.menuBar().addMenu("&Tools")
# Populate the File menu
- open_action = self.create_action("&Open...", file_menu, self.open_file)
- save_action = self.create_action("&Save As...", file_menu, self.save_file)
+ self.open_action = self.create_action("&Open...", file_menu, self.open_file)
+ self.save_action = self.create_action("&Save As...", file_menu, self.save_file)
file_menu.addSeparator()
- exit_action = self.create_action("E&xit", file_menu, self.close)
+ self.exit_action = self.create_action("E&xit", file_menu, self.close)
# Populate the Tools menu
- add_action = self.create_action("&Add Entry...", tool_menu, self._address_widget.add_entry)
- self._edit_action = self.create_action("&Edit Entry...", tool_menu, self._address_widget.edit_entry)
+ self.add_action = self.create_action(
+ "&Add Entry...", tool_menu, self._address_widget.add_entry)
+ self._edit_action = self.create_action(
+ "&Edit Entry...", tool_menu, self._address_widget.edit_entry)
tool_menu.addSeparator()
- self._remove_action = self.create_action("&Remove Entry", tool_menu, self._address_widget.remove_entry)
+ self._remove_action = self.create_action(
+ "&Remove Entry", tool_menu, self._address_widget.remove_entry)
# Disable the edit and remove menu items initially, as there are
# no items yet.
diff --git a/examples/widgets/itemviews/address_book/addresswidget.py b/examples/widgets/itemviews/address_book/addresswidget.py
index ab1330e48..cb2f46ea1 100644
--- a/examples/widgets/itemviews/address_book/addresswidget.py
+++ b/examples/widgets/itemviews/address_book/addresswidget.py
@@ -166,8 +166,9 @@ class AddressWidget(QTabWidget):
proxy_model.setFilterKeyColumn(0) # Filter on the "name" column
proxy_model.sort(0, Qt.AscendingOrder)
- # This prevents an application crash (see: https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash)
- viewselectionmodel = table_view.selectionModel()
+ # This prevents an application crash (see:
+ # https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) # noqa: E501
+ self.viewselectionmodel = table_view.selectionModel()
table_view.selectionModel().selectionChanged.connect(self.selection_changed)
self.addTab(table_view, group)
diff --git a/examples/widgets/itemviews/address_book/tablemodel.py b/examples/widgets/itemviews/address_book/tablemodel.py
index a0d63bbe2..3c1dbd4cc 100644
--- a/examples/widgets/itemviews/address_book/tablemodel.py
+++ b/examples/widgets/itemviews/address_book/tablemodel.py
@@ -105,5 +105,5 @@ class TableModel(QAbstractTableModel):
"""
if not index.isValid():
return Qt.ItemIsEnabled
- return Qt.ItemFlags(QAbstractTableModel.flags(self, index) |
- Qt.ItemIsEditable)
+ return Qt.ItemFlags(QAbstractTableModel.flags(self, index)
+ | Qt.ItemIsEditable)
diff --git a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py
index 834237404..a30b0abdf 100644
--- a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py
+++ b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py
@@ -46,11 +46,11 @@ class Window(QWidget):
self._filter_syntax_combo_box = QComboBox()
self._filter_syntax_combo_box.addItem("Regular expression",
- REGULAR_EXPRESSION)
+ REGULAR_EXPRESSION)
self._filter_syntax_combo_box.addItem("Wildcard",
- WILDCARD)
+ WILDCARD)
self._filter_syntax_combo_box.addItem("Fixed string",
- FIXED_STRING)
+ FIXED_STRING)
self._filter_syntax_label = QLabel("Filter &syntax:")
self._filter_syntax_label.setBuddy(self._filter_syntax_combo_box)
@@ -147,25 +147,25 @@ def create_mail_model(parent):
model.setHeaderData(2, Qt.Horizontal, "Date")
add_mail(model, "Happy New Year!", "Grace K. <grace@software-inc.com>",
- QDateTime(QDate(2006, 12, 31), QTime(17, 3)))
+ QDateTime(QDate(2006, 12, 31), QTime(17, 3)))
add_mail(model, "Radically new concept", "Grace K. <grace@software-inc.com>",
- QDateTime(QDate(2006, 12, 22), QTime(9, 44)))
+ QDateTime(QDate(2006, 12, 22), QTime(9, 44)))
add_mail(model, "Accounts", "pascale@nospam.com",
- QDateTime(QDate(2006, 12, 31), QTime(12, 50)))
+ QDateTime(QDate(2006, 12, 31), QTime(12, 50)))
add_mail(model, "Expenses", "Joe Bloggs <joe@bloggs.com>",
- QDateTime(QDate(2006, 12, 25), QTime(11, 39)))
+ QDateTime(QDate(2006, 12, 25), QTime(11, 39)))
add_mail(model, "Re: Expenses", "Andy <andy@nospam.com>",
- QDateTime(QDate(2007, 1, 2), QTime(16, 5)))
+ QDateTime(QDate(2007, 1, 2), QTime(16, 5)))
add_mail(model, "Re: Accounts", "Joe Bloggs <joe@bloggs.com>",
- QDateTime(QDate(2007, 1, 3), QTime(14, 18)))
+ QDateTime(QDate(2007, 1, 3), QTime(14, 18)))
add_mail(model, "Re: Accounts", "Andy <andy@nospam.com>",
- QDateTime(QDate(2007, 1, 3), QTime(14, 26)))
+ QDateTime(QDate(2007, 1, 3), QTime(14, 26)))
add_mail(model, "Sports", "Linda Smith <linda.smith@nospam.com>",
- QDateTime(QDate(2007, 1, 5), QTime(11, 33)))
+ QDateTime(QDate(2007, 1, 5), QTime(11, 33)))
add_mail(model, "AW: Sports", "Rolf Newschweinstein <rolfn@nospam.com>",
- QDateTime(QDate(2007, 1, 5), QTime(12, 0)))
+ QDateTime(QDate(2007, 1, 5), QTime(12, 0)))
add_mail(model, "RE: Sports", "Petra Schmidt <petras@nospam.com>",
- QDateTime(QDate(2007, 1, 5), QTime(12, 1)))
+ QDateTime(QDate(2007, 1, 5), QTime(12, 1)))
return model
diff --git a/examples/widgets/itemviews/dirview/dirview.py b/examples/widgets/itemviews/dirview/dirview.py
index aa1e62185..d1be6958e 100644
--- a/examples/widgets/itemviews/dirview/dirview.py
+++ b/examples/widgets/itemviews/dirview/dirview.py
@@ -57,4 +57,3 @@ if __name__ == "__main__":
tree.show()
sys.exit(app.exec())
-
diff --git a/examples/widgets/itemviews/editabletreemodel/mainwindow.py b/examples/widgets/itemviews/editabletreemodel/mainwindow.py
index c6abfa568..1489bf28b 100644
--- a/examples/widgets/itemviews/editabletreemodel/mainwindow.py
+++ b/examples/widgets/itemviews/editabletreemodel/mainwindow.py
@@ -52,7 +52,7 @@ class MainWindow(QMainWindow):
self.insert_child_action.setShortcut("Ctrl+N")
self.insert_child_action.triggered.connect(self.insert_child)
help_menu = menubar.addMenu("&Help")
- about_qt_action = help_menu.addAction("About Qt", qApp.aboutQt)
+ about_qt_action = help_menu.addAction("About Qt", qApp.aboutQt) # noqa: F821
about_qt_action.setShortcut("F1")
self.setWindowTitle("Editable Tree Model")
diff --git a/examples/widgets/itemviews/fetchmore/fetchmore.py b/examples/widgets/itemviews/fetchmore/fetchmore.py
index ecee86e38..5150250e0 100644
--- a/examples/widgets/itemviews/fetchmore/fetchmore.py
+++ b/examples/widgets/itemviews/fetchmore/fetchmore.py
@@ -49,7 +49,7 @@ class FileListModel(QAbstractListModel):
if role == Qt.BackgroundRole:
batch = row // BATCH_SIZE
- palette = qApp.palette()
+ palette = qApp.palette() # noqa: F821
return palette.base() if batch % 2 == 0 else palette.alternateBase()
if role == Qt.DecorationRole:
@@ -112,7 +112,7 @@ class Window(QWidget):
self.setWindowTitle("Fetch More Example")
- @Slot(str,int,int,int)
+ @Slot(str, int, int, int)
def update_log(self, path, start, number, total):
native_path = QDir.toNativeSeparators(path)
last = start + number - 1
diff --git a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py
index 266b8c1e1..577f0faa5 100644
--- a/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py
+++ b/examples/widgets/itemviews/spinboxdelegate/spinboxdelegate.py
@@ -10,6 +10,7 @@ from PySide6.QtCore import QModelIndex
"""PySide6 port of the widgets/itemviews/spinboxdelegate from Qt v6.x"""
+
#! [0]
class SpinBoxDelegate(QStyledItemDelegate):
"""A delegate that allows the user to change integer values from the model
@@ -52,7 +53,7 @@ class SpinBoxDelegate(QStyledItemDelegate):
if __name__ == '__main__':
app = QApplication(sys.argv)
- model= QStandardItemModel(4, 2)
+ model = QStandardItemModel(4, 2)
tableView = QTableView()
tableView.setModel(model)
diff --git a/examples/widgets/itemviews/stardelegate/stardelegate.py b/examples/widgets/itemviews/stardelegate/stardelegate.py
index 87bf1ff48..973eb14f6 100644
--- a/examples/widgets/itemviews/stardelegate/stardelegate.py
+++ b/examples/widgets/itemviews/stardelegate/stardelegate.py
@@ -111,15 +111,15 @@ 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.setEditTriggers(QAbstractItemView.DoubleClicked
+ | QAbstractItemView.SelectedClicked)
table_widget.setSelectionBehavior(QAbstractItemView.SelectRows)
table_widget.setHorizontalHeaderLabels(["Title", "Genre", "Artist", "Rating"])
- data = [ ["Mass in B-Minor", "Baroque", "J.S. Bach", 5],
- ["Three More Foxes", "Jazz", "Maynard Ferguson", 4],
- ["Sex Bomb", "Pop", "Tom Jones", 3],
- ["Barbie Girl", "Pop", "Aqua", 5] ]
+ data = [["Mass in B-Minor", "Baroque", "J.S. Bach", 5],
+ ["Three More Foxes", "Jazz", "Maynard Ferguson", 4],
+ ["Sex Bomb", "Pop", "Tom Jones", 3],
+ ["Barbie Girl", "Pop", "Aqua", 5]]
for r in range(len(data)):
table_widget.setItem(r, 0, QTableWidgetItem(data[r][0]))
diff --git a/examples/widgets/itemviews/stardelegate/stareditor.py b/examples/widgets/itemviews/stardelegate/stareditor.py
index 1b44164a8..296afa950 100644
--- a/examples/widgets/itemviews/stardelegate/stareditor.py
+++ b/examples/widgets/itemviews/stardelegate/stareditor.py
@@ -55,8 +55,7 @@ class StarEditor(QWidget):
""" Calculate which star the user's mouse cursor is currently
hovering over.
"""
- star = (x / (self.star_rating.sizeHint().width() /
- self.star_rating.MAX_STAR_COUNT)) + 1
+ star = (x / (self.star_rating.sizeHint().width() / self.star_rating.MAX_STAR_COUNT)) + 1
if (star <= 0) or (star > self.star_rating.MAX_STAR_COUNT):
return -1
diff --git a/examples/widgets/itemviews/stardelegate/starrating.py b/examples/widgets/itemviews/stardelegate/starrating.py
index 4d49f7405..28dbacd6f 100644
--- a/examples/widgets/itemviews/stardelegate/starrating.py
+++ b/examples/widgets/itemviews/stardelegate/starrating.py
@@ -23,13 +23,13 @@ class StarRating(object):
self._star_polygon.append(QPointF(1.0, 0.5))
for i in range(1, 5):
self._star_polygon.append(QPointF(0.5 + 0.5 * cos(0.8 * i * pi),
- 0.5 + 0.5 * sin(0.8 * i * pi)))
+ 0.5 + 0.5 * sin(0.8 * i * pi)))
# Create the diamond shape we'll show in the editor
self._diamond_polygon = QPolygonF()
diamond_points = [QPointF(0.4, 0.5), QPointF(0.5, 0.4),
- QPointF(0.6, 0.5), QPointF(0.5, 0.6),
- QPointF(0.4, 0.5)]
+ QPointF(0.6, 0.5), QPointF(0.5, 0.6),
+ QPointF(0.4, 0.5)]
self._diamond_polygon.append(diamond_points)
def sizeHint(self):
diff --git a/examples/widgets/layouts/basiclayouts/basiclayouts.py b/examples/widgets/layouts/basiclayouts/basiclayouts.py
index a2d29e71f..827cb7850 100644
--- a/examples/widgets/layouts/basiclayouts/basiclayouts.py
+++ b/examples/widgets/layouts/basiclayouts/basiclayouts.py
@@ -27,7 +27,7 @@ class Dialog(QDialog):
big_editor = QTextEdit()
big_editor.setPlainText("This widget takes up all the remaining space "
- "in the top-level layout.")
+ "in the top-level layout.")
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
@@ -75,8 +75,7 @@ class Dialog(QDialog):
layout.addWidget(line_edit, i + 1, 1)
self._small_editor = QTextEdit()
- self._small_editor.setPlainText("This widget takes up about two thirds "
- "of the grid layout.")
+ self._small_editor.setPlainText("This widget takes up about two thirds of the grid layout.")
layout.addWidget(self._small_editor, 0, 2, 4, 1)
diff --git a/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py b/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py
index 4e3791519..c9dfcc730 100644
--- a/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py
+++ b/examples/widgets/layouts/dynamiclayouts/dynamiclayouts.py
@@ -74,8 +74,8 @@ class Dialog(QDialog):
def show_help(self):
QMessageBox.information(self, "Dynamic Layouts Help",
- "This example shows how to change layouts "
- "dynamically.")
+ "This example shows how to change layouts "
+ "dynamically.")
def create_rotable_group_box(self):
self._rotable_group_box = QGroupBox("Rotable Widgets")
@@ -102,7 +102,8 @@ class Dialog(QDialog):
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.currentIndexChanged[int].connect(self.buttons_orientation_changed)
+ buttons_orientation_combo_box.currentIndexChanged[int].connect(
+ self.buttons_orientation_changed)
self._buttons_orientation_combo_box = buttons_orientation_combo_box
@@ -117,7 +118,8 @@ class Dialog(QDialog):
close_button = self._button_box.addButton(QDialogButtonBox.Close)
help_button = self._button_box.addButton(QDialogButtonBox.Help)
- rotate_widgets_button = self._button_box.addButton("Rotate &Widgets", QDialogButtonBox.ActionRole)
+ rotate_widgets_button = self._button_box.addButton(
+ "Rotate &Widgets", QDialogButtonBox.ActionRole)
rotate_widgets_button.clicked.connect(self.rotate_widgets)
close_button.clicked.connect(self.close)
diff --git a/examples/widgets/linguist/doc/linguist.rst b/examples/widgets/linguist/doc/linguist.rst
new file mode 100644
index 000000000..24a49a617
--- /dev/null
+++ b/examples/widgets/linguist/doc/linguist.rst
@@ -0,0 +1,7 @@
+.. _qt-linguist-example:
+
+Qt Linguist Example
+===================
+
+This example demonstrates the use of Qt Linguist and related tools for translating
+applications as described in :ref:`translations`.
diff --git a/examples/widgets/linguist/linguist.qrc b/examples/widgets/linguist/linguist.qrc
index 2b08ae577..6ca287c20 100644
--- a/examples/widgets/linguist/linguist.qrc
+++ b/examples/widgets/linguist/linguist.qrc
@@ -1,5 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
-<qresource>
- <file>translations/example_de.qm</file>
+<qresource prefix="translations">
+ <file>example_de.qm</file>
</qresource>
</RCC>
diff --git a/examples/widgets/linguist/main.py b/examples/widgets/linguist/main.py
index 16655432c..e4212e801 100644
--- a/examples/widgets/linguist/main.py
+++ b/examples/widgets/linguist/main.py
@@ -9,7 +9,7 @@ from PySide6.QtWidgets import (QAbstractItemView, QApplication, QListWidget,
QMainWindow)
-import linguist_rc
+import linguist_rc # noqa: F401
class Window(QMainWindow):
@@ -21,7 +21,7 @@ class Window(QMainWindow):
quit_action.triggered.connect(self.close)
help_menu = self.menuBar().addMenu(self.tr("&Help"))
about_qt_action = help_menu.addAction(self.tr("About Qt"))
- about_qt_action.triggered.connect(qApp.aboutQt)
+ about_qt_action.triggered.connect(qApp.aboutQt) # noqa: F821
self._list_widget = QListWidget()
self._list_widget.setSelectionMode(QAbstractItemView.MultiSelection)
diff --git a/examples/widgets/mainwindows/application/application.py b/examples/widgets/mainwindows/application/application.py
index 320c421a6..f69eade2e 100644
--- a/examples/widgets/mainwindows/application/application.py
+++ b/examples/widgets/mainwindows/application/application.py
@@ -11,7 +11,7 @@ from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow,
QMessageBox, QTextEdit)
-import application_rc
+import application_rc # noqa: F401
class MainWindow(QMainWindow):
@@ -73,61 +73,64 @@ class MainWindow(QMainWindow):
@Slot()
def about(self):
QMessageBox.about(self, "About Application",
- "The <b>Application</b> example demonstrates how to write "
- "modern GUI applications using Qt, with a menu bar, "
- "toolbars, and a status bar.")
+ "The <b>Application</b> example demonstrates how to write "
+ "modern GUI applications using Qt, with a menu bar, "
+ "toolbars, and a status bar.")
@Slot()
def document_was_modified(self):
self.setWindowModified(self._text_edit.document().isModified())
def create_actions(self):
- icon = QIcon.fromTheme("document-new", QIcon(':/images/new.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew, QIcon(':/images/new.png'))
self._new_act = QAction(icon, "&New", self, shortcut=QKeySequence.New,
- statusTip="Create a new file", triggered=self.new_file)
+ statusTip="Create a new file", triggered=self.new_file)
- icon = QIcon.fromTheme("document-open", QIcon(':/images/open.png'))
+ 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",
- triggered=self.open)
+ shortcut=QKeySequence.Open, statusTip="Open an existing file",
+ triggered=self.open)
- icon = QIcon.fromTheme("document-save", QIcon(':/images/save.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave, QIcon(':/images/save.png'))
self._save_act = QAction(icon, "&Save", self,
- shortcut=QKeySequence.Save,
- statusTip="Save the document to disk", triggered=self.save)
+ shortcut=QKeySequence.Save,
+ statusTip="Save the document to disk", triggered=self.save)
self._save_as_act = QAction("Save &As...", self,
- shortcut=QKeySequence.SaveAs,
- statusTip="Save the document under a new name",
- triggered=self.save_as)
+ shortcut=QKeySequence.SaveAs,
+ statusTip="Save the document under a new name",
+ triggered=self.save_as)
- self._exit_act = QAction("E&xit", self, shortcut="Ctrl+Q",
- statusTip="Exit the application", triggered=self.close)
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit)
+ self._exit_act = QAction(icon, "E&xit", self, shortcut="Ctrl+Q",
+ statusTip="Exit the application", triggered=self.close)
- icon = QIcon.fromTheme("edit-cut", QIcon(':/images/cut.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(':/images/cut.png'))
self._cut_act = QAction(icon, "Cu&t", self, shortcut=QKeySequence.Cut,
- statusTip="Cut the current selection's contents to the clipboard",
- triggered=self._text_edit.cut)
+ statusTip="Cut the current selection's contents to the clipboard",
+ triggered=self._text_edit.cut)
- icon = QIcon.fromTheme("edit-copy", QIcon(':/images/copy.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(':/images/copy.png'))
self._copy_act = QAction(icon, "&Copy",
- self, shortcut=QKeySequence.Copy,
- statusTip="Copy the current selection's contents to the clipboard",
- triggered=self._text_edit.copy)
+ self, shortcut=QKeySequence.Copy,
+ statusTip="Copy the current selection's contents to the clipboard",
+ triggered=self._text_edit.copy)
- icon = QIcon.fromTheme("edit-paste", QIcon(':/images/paste.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(':/images/paste.png'))
self._paste_act = QAction(icon, "&Paste",
- self, shortcut=QKeySequence.Paste,
- statusTip="Paste the clipboard's contents into the current selection",
- triggered=self._text_edit.paste)
+ self, shortcut=QKeySequence.Paste,
+ statusTip="Paste the clipboard's contents into the current "
+ "selection",
+ triggered=self._text_edit.paste)
- self._about_act = QAction("&About", self,
- statusTip="Show the application's About box",
- triggered=self.about)
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)
+ self._about_act = QAction(icon, "&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
self._about_qt_act = QAction("About &Qt", self,
- statusTip="Show the Qt library's About box",
- triggered=qApp.aboutQt)
+ statusTip="Show the Qt library's About box",
+ triggered=qApp.aboutQt) # noqa: F821
self._cut_act.setEnabled(False)
self._copy_act.setEnabled(False)
@@ -181,10 +184,9 @@ class MainWindow(QMainWindow):
def maybe_save(self):
if self._text_edit.document().isModified():
ret = QMessageBox.warning(self, "Application",
- "The document has been modified.\nDo you want to save "
- "your changes?",
- QMessageBox.Save | QMessageBox.Discard |
- QMessageBox.Cancel)
+ "The document has been modified.\nDo you want to save "
+ "your changes?",
+ QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
if ret == QMessageBox.Save:
return self.save()
elif ret == QMessageBox.Cancel:
@@ -195,8 +197,7 @@ class MainWindow(QMainWindow):
file = QFile(fileName)
if not file.open(QFile.ReadOnly | QFile.Text):
reason = file.errorString()
- QMessageBox.warning(self, "Application",
- f"Cannot read file {fileName}:\n{reason}.")
+ QMessageBox.warning(self, "Application", f"Cannot read file {fileName}:\n{reason}.")
return
inf = QTextStream(file)
diff --git a/examples/widgets/mainwindows/dockwidgets/dockwidgets.py b/examples/widgets/mainwindows/dockwidgets/dockwidgets.py
index 6d0e95a81..83487ee58 100644
--- a/examples/widgets/mainwindows/dockwidgets/dockwidgets.py
+++ b/examples/widgets/mainwindows/dockwidgets/dockwidgets.py
@@ -2,18 +2,20 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the widgets/mainwindows/dockwidgets example from Qt v5.x, originating from PyQt"""
+"""PySide6 port of the widgets/mainwindows/dockwidgets example from Qt v5.x,
+ originating from PyQt"""
import sys
from PySide6.QtCore import QDate, QFile, Qt, QTextStream
from PySide6.QtGui import (QAction, QFont, QIcon, QKeySequence,
- QTextCharFormat, QTextCursor, QTextTableFormat)
+ QTextCharFormat, QTextCursor, QTextTableFormat)
from PySide6.QtPrintSupport import QPrintDialog, QPrinter
from PySide6.QtWidgets import (QApplication, QDialog, QDockWidget,
- QFileDialog, QListWidget, QMainWindow, QMessageBox, QTextEdit)
+ QFileDialog, QListWidget, QMainWindow,
+ QMessageBox, QTextEdit)
-import dockwidgets_rc
+import dockwidgets_rc # noqa: F401
class MainWindow(QMainWindow):
@@ -62,8 +64,7 @@ class MainWindow(QMainWindow):
cursor.insertBlock()
cursor.insertText("Some Country")
cursor.setPosition(top_frame.lastPosition())
- cursor.insertText(QDate.currentDate().toString("d MMMM yyyy"),
- text_format)
+ cursor.insertText(QDate.currentDate().toString("d MMMM yyyy"), text_format)
cursor.insertBlock()
cursor.insertBlock()
cursor.insertText("Dear ", text_format)
@@ -103,7 +104,7 @@ class MainWindow(QMainWindow):
if not file.open(QFile.WriteOnly | QFile.Text):
reason = file.errorString()
QMessageBox.warning(self, "Dock Widgets",
- f"Cannot write file {filename}:\n{reason}.")
+ f"Cannot write file {filename}:\n{reason}.")
return
out = QTextStream(file)
@@ -143,8 +144,8 @@ class MainWindow(QMainWindow):
if cursor.isNull():
return
cursor.beginEditBlock()
- cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.MoveAnchor,
- 2)
+ cursor.movePosition(QTextCursor.PreviousBlock,
+ QTextCursor.MoveAnchor, 2)
cursor.insertBlock()
cursor.insertText(paragraph)
cursor.insertBlock()
@@ -152,43 +153,44 @@ class MainWindow(QMainWindow):
def about(self):
QMessageBox.about(self, "About Dock Widgets",
- "The <b>Dock Widgets</b> example demonstrates how to use "
- "Qt's dock widgets. You can enter your own text, click a "
- "customer to add a customer name and address, and click "
- "standard paragraphs to add them.")
+ "The <b>Dock Widgets</b> example demonstrates how to use "
+ "Qt's dock widgets. You can enter your own text, click a "
+ "customer to add a customer name and address, and click "
+ "standard paragraphs to add them.")
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,
- statusTip="Create a new form letter", triggered=self.new_letter)
+ self, shortcut=QKeySequence.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,
- statusTip="Save the current form letter", triggered=self.save)
+ shortcut=QKeySequence.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,
- statusTip="Print the current form letter",
- triggered=self.print_)
+ shortcut=QKeySequence.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,
- statusTip="Undo the last editing action", triggered=self.undo)
+ shortcut=QKeySequence.Undo,
+ statusTip="Undo the last editing action", triggered=self.undo)
self._quit_act = QAction("&Quit", self, shortcut="Ctrl+Q",
- statusTip="Quit the application", triggered=self.close)
+ statusTip="Quit the application", triggered=self.close)
self._about_act = QAction("&About", self,
- statusTip="Show the application's About box",
- triggered=self.about)
+ statusTip="Show the application's About box",
+ triggered=self.about)
self._about_qt_act = QAction("About &Qt", self,
- statusTip="Show the Qt library's About box",
- triggered=QApplication.instance().aboutQt)
+ statusTip="Show the Qt library's About box",
+ triggered=QApplication.instance().aboutQt)
def create_menus(self):
self._file_menu = self.menuBar().addMenu("&File")
@@ -241,21 +243,21 @@ class MainWindow(QMainWindow):
self._paragraphs_list.addItems((
"Thank you for your payment which we have received today.",
"Your order has been dispatched and should be with you within "
- "28 days.",
+ "28 days.",
"We have dispatched those items that were in stock. The rest of "
- "your order will be dispatched once all the remaining items "
- "have arrived at our warehouse. No additional shipping "
- "charges will be made.",
+ "your order will be dispatched once all the remaining items "
+ "have arrived at our warehouse. No additional shipping "
+ "charges will be made.",
"You made a small overpayment (less than $5) which we will keep "
- "on account for you, or return at your request.",
+ "on account for you, or return at your request.",
"You made a small underpayment (less than $1), but we have sent "
- "your order anyway. We'll add this underpayment to your next "
- "bill.",
+ "your order anyway. We'll add this underpayment to your next "
+ "bill.",
"Unfortunately you did not send enough money. Please remit an "
- "additional $. Your order will be dispatched as soon as the "
- "complete amount has been received.",
+ "additional $. Your order will be dispatched as soon as the "
+ "complete amount has been received.",
"You made an overpayment (more than $5). Do you wish to buy more "
- "items, or should we return the excess to you?"))
+ "items, or should we return the excess to you?"))
dock.setWidget(self._paragraphs_list)
self.addDockWidget(Qt.RightDockWidgetArea, dock)
self._view_menu.addAction(dock.toggleViewAction())
diff --git a/examples/widgets/mainwindows/mdi/images/new.png b/examples/widgets/mainwindows/mdi/images/new.png
deleted file mode 100644
index 12131b010..000000000
--- a/examples/widgets/mainwindows/mdi/images/new.png
+++ /dev/null
Binary files differ
diff --git a/examples/widgets/mainwindows/mdi/images/save.png b/examples/widgets/mainwindows/mdi/images/save.png
deleted file mode 100644
index daba865fa..000000000
--- a/examples/widgets/mainwindows/mdi/images/save.png
+++ /dev/null
Binary files differ
diff --git a/examples/widgets/mainwindows/mdi/mdi.py b/examples/widgets/mainwindows/mdi/mdi.py
index 597479981..c1fa01b8c 100644
--- a/examples/widgets/mainwindows/mdi/mdi.py
+++ b/examples/widgets/mainwindows/mdi/mdi.py
@@ -9,12 +9,12 @@ from functools import partial
import sys
from PySide6.QtCore import (QByteArray, QFile, QFileInfo, QSettings,
- QSaveFile, QTextStream, Qt, Slot)
+ QSaveFile, QTextStream, Qt, Slot)
from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow,
- QMdiArea, QMessageBox, QTextEdit)
+ QMdiArea, QMessageBox, QTextEdit)
-import mdi_rc
+import PySide6.QtExampleIcons # noqa: F401
class MdiChild(QTextEdit):
@@ -106,7 +106,7 @@ class MdiChild(QTextEdit):
f = self.user_friendly_current_file()
message = f"'{f}' has been modified.\nDo you want to save your changes?"
ret = QMessageBox.warning(self, "MDI", message,
- QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
+ QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
if ret == QMessageBox.Save:
return self.save()
@@ -208,8 +208,8 @@ class MainWindow(QMainWindow):
@Slot()
def about(self):
QMessageBox.about(self, "About MDI",
- "The <b>MDI</b> example demonstrates how to write multiple "
- "document interface applications using Qt.")
+ "The <b>MDI</b> example demonstrates how to write multiple "
+ "document interface applications using Qt.")
@Slot()
def update_menus(self):
@@ -225,8 +225,8 @@ class MainWindow(QMainWindow):
self._previous_act.setEnabled(has_mdi_child)
self._separator_act.setVisible(has_mdi_child)
- has_selection = (self.active_mdi_child() is not None and
- self.active_mdi_child().textCursor().hasSelection())
+ has_selection = (self.active_mdi_child() is not None
+ and self.active_mdi_child().textCursor().hasSelection())
self._cut_act.setEnabled(has_selection)
self._copy_act.setEnabled(has_selection)
@@ -271,82 +271,85 @@ class MainWindow(QMainWindow):
def create_actions(self):
- icon = QIcon.fromTheme("document-new", QIcon(':/images/new.png'))
+ 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)
+ shortcut=QKeySequence.New, statusTip="Create a new file",
+ triggered=self.new_file)
- icon = QIcon.fromTheme("document-open", QIcon(':/images/open.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen)
self._open_act = QAction(icon, "&Open...", self,
- shortcut=QKeySequence.Open, statusTip="Open an existing file",
- triggered=self.open)
+ shortcut=QKeySequence.Open, statusTip="Open an existing file",
+ triggered=self.open)
- icon = QIcon.fromTheme("document-save", QIcon(':/images/save.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave)
self._save_act = QAction(icon, "&Save", self,
- shortcut=QKeySequence.Save,
- statusTip="Save the document to disk", triggered=self.save)
+ shortcut=QKeySequence.Save,
+ statusTip="Save the document to disk", triggered=self.save)
self._save_as_act = QAction("Save &As...", self,
- shortcut=QKeySequence.SaveAs,
- statusTip="Save the document under a new name",
- triggered=self.save_as)
+ shortcut=QKeySequence.SaveAs,
+ statusTip="Save the document under a new name",
+ triggered=self.save_as)
- self._exit_act = QAction("E&xit", self, shortcut=QKeySequence.Quit,
- statusTip="Exit the application",
- triggered=QApplication.instance().closeAllWindows)
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit)
+ self._exit_act = QAction(icon, "E&xit", self, shortcut=QKeySequence.Quit,
+ statusTip="Exit the application",
+ triggered=QApplication.instance().closeAllWindows)
- icon = QIcon.fromTheme("edit-cut", QIcon(':/images/cut.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut)
self._cut_act = QAction(icon, "Cu&t", self,
- shortcut=QKeySequence.Cut,
- statusTip="Cut the current selection's contents to the clipboard",
- triggered=self.cut)
+ shortcut=QKeySequence.Cut,
+ statusTip="Cut the current selection's contents to the clipboard",
+ triggered=self.cut)
- icon = QIcon.fromTheme("edit-copy", QIcon(':/images/copy.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy)
self._copy_act = QAction(icon, "&Copy", self,
- shortcut=QKeySequence.Copy,
- statusTip="Copy the current selection's contents to the clipboard",
- triggered=self.copy)
+ shortcut=QKeySequence.Copy,
+ statusTip="Copy the current selection's contents to the clipboard",
+ triggered=self.copy)
- icon = QIcon.fromTheme("edit-paste", QIcon(':/images/paste.png'))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste)
self._paste_act = QAction(icon, "&Paste", self,
- shortcut=QKeySequence.Paste,
- statusTip="Paste the clipboard's contents into the current selection",
- triggered=self.paste)
+ shortcut=QKeySequence.Paste,
+ statusTip="Paste the clipboard's contents into the current "
+ "selection",
+ triggered=self.paste)
self._close_act = QAction("Cl&ose", self,
- statusTip="Close the active window",
- triggered=self._mdi_area.closeActiveSubWindow)
+ statusTip="Close the active window",
+ triggered=self._mdi_area.closeActiveSubWindow)
self._close_all_act = QAction("Close &All", self,
- statusTip="Close all the windows",
- triggered=self._mdi_area.closeAllSubWindows)
+ statusTip="Close all the windows",
+ triggered=self._mdi_area.closeAllSubWindows)
self._tile_act = QAction("&Tile", self, statusTip="Tile the windows",
- triggered=self._mdi_area.tileSubWindows)
+ triggered=self._mdi_area.tileSubWindows)
self._cascade_act = QAction("&Cascade", self,
- statusTip="Cascade the windows",
- triggered=self._mdi_area.cascadeSubWindows)
+ statusTip="Cascade the windows",
+ triggered=self._mdi_area.cascadeSubWindows)
self._next_act = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild,
- statusTip="Move the focus to the next window",
- triggered=self._mdi_area.activateNextSubWindow)
+ statusTip="Move the focus to the next window",
+ triggered=self._mdi_area.activateNextSubWindow)
self._previous_act = QAction("Pre&vious", self,
- shortcut=QKeySequence.PreviousChild,
- statusTip="Move the focus to the previous window",
- triggered=self._mdi_area.activatePreviousSubWindow)
+ shortcut=QKeySequence.PreviousChild,
+ statusTip="Move the focus to the previous window",
+ triggered=self._mdi_area.activatePreviousSubWindow)
self._separator_act = QAction(self)
self._separator_act.setSeparator(True)
- self._about_act = QAction("&About", self,
- statusTip="Show the application's About box",
- triggered=self.about)
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)
+ self._about_act = QAction(icon, "&About", self,
+ statusTip="Show the application's About box",
+ triggered=self.about)
self._about_qt_act = QAction("About &Qt", self,
- statusTip="Show the Qt library's About box",
- triggered=QApplication.instance().aboutQt)
+ statusTip="Show the Qt library's About box",
+ triggered=QApplication.instance().aboutQt)
def create_menus(self):
self._file_menu = self.menuBar().addMenu("&File")
@@ -432,6 +435,11 @@ if __name__ == '__main__':
options = argument_parser.parse_args()
app = QApplication(sys.argv)
+
+ icon_paths = QIcon.themeSearchPaths()
+ QIcon.setThemeSearchPaths(icon_paths + [":/qt-project.org/icons"])
+ QIcon.setFallbackThemeName("example_icons")
+
main_win = MainWindow()
for f in options.files:
main_win.load(f)
diff --git a/examples/widgets/mainwindows/mdi/mdi.pyproject b/examples/widgets/mainwindows/mdi/mdi.pyproject
index f653090c0..0272873a7 100644
--- a/examples/widgets/mainwindows/mdi/mdi.pyproject
+++ b/examples/widgets/mainwindows/mdi/mdi.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["mdi.py", "mdi.qrc"]
+ "files": ["mdi.py"]
}
diff --git a/examples/widgets/mainwindows/mdi/mdi.qrc b/examples/widgets/mainwindows/mdi/mdi.qrc
deleted file mode 100644
index 0a776fab4..000000000
--- a/examples/widgets/mainwindows/mdi/mdi.qrc
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE RCC><RCC version="1.0">
-<qresource>
- <file>images/copy.png</file>
- <file>images/cut.png</file>
- <file>images/new.png</file>
- <file>images/open.png</file>
- <file>images/paste.png</file>
- <file>images/save.png</file>
-</qresource>
-</RCC>
diff --git a/examples/widgets/mainwindows/mdi/mdi_rc.py b/examples/widgets/mainwindows/mdi/mdi_rc.py
deleted file mode 100644
index bc8336765..000000000
--- a/examples/widgets/mainwindows/mdi/mdi_rc.py
+++ /dev/null
@@ -1,608 +0,0 @@
-# Resource object code (Python 3)
-# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.2.2
-# WARNING! All changes made in this file will be lost!
-
-from PySide6 import QtCore
-
-qt_resource_data = b"\
-\x00\x00\x08\x19\
-\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
-\x00\x00\x00\x19tEXtSoftware\
-\x00Adobe ImageRead\
-yq\xc9e<\x00\x00\x07\xabIDATX\xc3\xad\
-W[P\x93g\x1a\xf6\xca\xce\xec\xcc\xf6b/\xbc\xd9\
-\xe9\xce\xecn\xbd\xda\xd9\x9b\xb5\xce\xba;{\xb0\xad\xcc\
-z\xb1\xce\xce:\xb3vTpu\xdb\xe2\x81\xd6\xb6T\
-\x04\xbb\xa5 m\xc1\x82\x06\x08\x07QB\x80\x80\x80\x02\
-!\x81\x10\x92@H\x10s$!gr\x80\x04B \
-\x9c\x09G\xb5Tx\xf6\xfb~\x13\x160X\x8b}g\
-\x9e\xf9/\x92\xfc\xcf\xfb>\xcf\xfb\xbe\xdf\x97]\x00v\
-\xfd\x98 \xf1\x0b\x82\x14\x02\x03\xc1u\x82\x03\xcf\xfd\xfe\
-\x8fH\xbc\x9b \xe1W\xaf\xef\xb5*\x8c\xd6e\xdb\x02\
-`\x19\x1e[\x09'\xf13\xfa\x19\x81\x22\xfc\xdc>v\
-H~\x8a\xa0\xb9\xb6Y\x1c2\xcf\xadB9\xfe\x1dD\
-\xf6Q\xd8\xc7\xe6\xe8\x87\x86={\xf6XSR\xae,\
-\xca::\x10N\xe2\xe5I\xc3\xc41\x04\xb7>I\xf9\
-,`\x9b]YSM\x03M\xb6\x114\xeb\xfb 1\
-y`\x19\x9d\xc5\xbb\xef\xbe?\xc5\xab\xbe\x83\xf1\x89)\
-LO\xcf\xae\x92\xef\xd7\xbct\x02\x11\x9f\x0f\xbe\x1d\xe3\
-\xb2\x04CO\xb43@\x8b{\x06\xcd=.4\xeb\xec\
-\xa8W\xf6 \x87S\x852^5C\xbc\xb0\xf4\x90\x81\
-\xc1`\x5c&\xbfK|\xe1\x04H\x1c$8A\xfd\xdd\
-\xeas'\xf1\xb9'\x04H\x87\x97\xc1\xd7\xbb \x22U\
-7\xdc7\xa2\xb8N\x88,V>\xccV\xdb:q\x04\
-,\x16k,\xfc\xce\xe7'\x10\x916\x93\x95?F}\
-\xa5\xfe\x12\xc4o\xf4Y1\xb6\x02~\xef Z{\x9c\
-\xe0?0\xa1L(CF\x0e\x1b\xb2\x0e\xf9&\xd2\xf9\
-\xc5e\xcc-,!4\xbf\x88\xbd{\xf7Z\xc9;~\
-\xbam\x02$~C\x90F=5\x13iu\xb3\x80\xd2\
-?\x0f\xcb\xc4\xe2\x9aP\xa1Z\xb4l\xf1Y\xa0\xb6\xa0\
-\xa6]\x8d/\xb2sq\xb7\x9e\xff\x0c1%\x9d\x09\xcd\
-cbj\x06\x83C\x81'\xe4\xdd\xbc-\xd3\xb0;\x92\
-\x033&\xd4S\xb5\xd3\xfbXO\x88\xc5\x03!\x88,\
-CP\xbaF\xd0\xed\x09B\xe5\x9bB\x9bs\xfc\xa9\xcf\
-Z\x1b\xee*t\xc8\xbc\xc9E\x09\xa7l\x93\xcf\x9b\x88\
-'\xa7\x11\x18\x1d\xc3\x80o\x08\xa2\xd6\xd6%\xc2Q\xdb\
-(\x12\x87\xc6\x1f\xaf\x82/b\x94M\x89$\x90\x22\xea\
-R-\x9aB\xab\xe8\x18y\x04\xa1\xc5\xcf\x10St\xf6\
-\x0d\xa3\xd3\xe1\x87\xd4<\x80\x16\xbd\x03\x0d]\x06\x14\xd5\
-\x0a\x90\x91\x95\x0d/y\xf1\xc6\xaa\xa9\xd4\xb3s\x0bL\
-\xc5\x94\xd8\xdd\xef\x85\xc9b\x05\xb7\xbc\x12\xa5\xe5\x95K\
-\x13\xf3\xcb\xab#\x0f\x017\xd9\x11\xe6\xd9\x15\x84\x97\x15\
-\x13\x06\xcb<\xd0h\xf2\xa3\xdd\xee_'\x96;\x86 \
-\xb3x\xd7}\xe6\x08\xa4\xf8<3\x1b*\x8d6\xaa\xdc\
-S3!\x8c\x8e\x8d3\x15\xd3&\xe47\x09\xf1\xc1\xc5\
-\x8fQs\xaf\x01\xbee`\xfc\x11\xa0#\x13#\xf2\xce\
-\xa1\xbe]\xb9\xb8Q\x01\x83\x81ttM\xa7\x1e\x0ag\
-\x80\xa9\xb8\xdd\xea\x83\xd8\xe8B\x93\xca\xcc\xf8|\xe5\xcb\
-,\x88\xda$Q\x89\xa7g\xe7\x18\x1b\x86\x86G`w\
-8I\x82:$|\xf8!\xae\xb3\x0b\xe1\x99\x5c\x80o\
-\x09\xd0\x90\xde\xe1\x0f,\x81\xab\x1f\xc4}\xef\x04\xdd\x07\
-\x1da\xeb\xff\x9f\xc0\x1d\xb9\x16\x1d\xf6!H\xcc\xfdO\
-}\xee\xd4\x22\x9dU\x84\xaa\x9a\xbaM>G\xe4\x8e\xf8\
-<<\x12\x84\xd3\xdd\x0f\xbd\xc1\x88\xc2\xe2b\x9c~/\
-\x1e=\x03\x01\xf4/\x02\x83\x84\xbc\xc5\xff-\xee:C\
-(Q\x91\xf7\xf6\x05\xf1N\xdc\xbf}\x843i\xe3 \
-\x18\xf43\xab\xe0\xc9Th58\xd1\xd8\xdd\x0b\x9eX\
-\x89\xac\x5c\xf63>G\xaa\x9e\x9c\x9ee\xe4\xee\xf7\x0e\
-\xa2\xd7lAC\x03\x1f'b\xe3 \xe9\xd6\xc0E\xcf\
-\x01R\x90$\xb8\x86\xb2\x9e\x00n\xb4\xdbP\xd1\x1bD\
-\x85\xce\x8bJ~\x0bm\xbe\x9b['\xd1\xa0\x99\xf8\x16\
-e\x22\x05\xee)\xf4(\x13\xc8\x90x5\x0b\x1a\xad>\
-\xaa\xdcc\x13\x93\xf0\x0d\x0d\xc3f\xef\x83\xb4]\x8e\xc4\
-K\x97\x90\xc3\xca\xc3\xd4c\xc0NzI1N\xfa\x89\
-\x94\x7f[;\x84|\x85\x13%j\x1fJ\xd5\x03\xe8\xf2\
-0\xa3(\x22\xf8\xf93\x09t\x8f.\xa1\xa8\xbe\x15\xa5\
-|\x09\xb2J*\xf0\xcf\xe3qQ\xe5\xf6\x07F\xd1\xe7\
-\xf2@\xab7 \xfdj\x06\x92\xbfH\x83\xcd7\x02'\
-\xa9\xda@\x1aL\xe0{\x88R\x9d\x1fE\xdd\xfd\x0cq\
-A\x97\x1b\xc5\xdd\x1e\x88\x9cA\xfc\xf9\xcd\xb7]\x84\xeb\
-l\xb4C\xd0(\xf7N#\xa7\xfc\x1e\xb2K\xab\xf1Q\
-\xeaWH\xfeo\xea\xfaXQ\xb9G\x82\xe3\xf0\x0c\xf8\
-`4\x99Q\xc9\xab\xc2\xfbg\xcfA\xfe@\x03?\xe9\
-n\xb2\x8d\x19\xb9oi\x06\x19\xd2\x9b*/r\xe5\x0e\
-\xe4u\xf6\xa1\xf0\xbe\x1b\x1c\x95\x1b\xf9\x9c\xca)\xc2S\
-\xb8\xdd)\xdc+v\x04\x90Q\xc8\xc5\x95ky8\x11\
-\x9f\x80\x9b\xb7n3c\x15\x91\xdbjs@\x22m\xc7\
-\x85\x84\x0fPt\xbb\x0c\xf3+\x80\x9f4X\xf7$ \
-\x1c|\x84J\xd3\x188\xfaa\x86\x9cV\xfdU\xb3\x1e\
-\xac\x0e;\xb8:\x1f\xd9!\x1ez/\xe0\x13\xbc\xba]\
-\x02&\xbe\xc1\x83\x94o\xd88\x9f\x9c\x8a\x03\x7f=\x04\
-c\xaf\x99\xe9n*\xb7F\xd7\x83\xa4\xcb\xc9H\xff:\
-\x8b\x8c\xd5<S\xb5q\xf6\xa9\xdc5\xf6i\x5c\x97Y\
-\x19\xd9\xbfn!\xa7\xa0\xd4\x82t\xbe\x1aW\x9b4`\
-\xc9\xcc\x10\xbb\x82\xf8\xe5\xaf_\xa7g\xc0;\xe1u\x1f\
-5\xcc5\xddf|\x94\x96\x85\xb8s\x17\xf1\x97C1\
-L\xd5t\x99\xf0\xaa\xaaq\xfa\xf4\x19h\xcc\x0e\x8c\x92\
--6\x14\x1e\xabZ\xc7\x0cx\xe6qp\x0d#L\xa3\
-e\x8a\x0c\x8c\xec\xb4\xfa\x9c\xb6^\x94t9\xd0f\xf7\
-\xaf\x1e=\x11KG.o\xc3y\x135,\x5c\x99\x1a\
-\xf1\x97>\xc7\xd1\xd83\xf881\x09\x86^\x13\x1a\x9b\
-\x04\xf8\xdd\x1b\xfbQO\xd4\xf1\x90\x99\xee\x9a\x00\xaa\xad\
-\x93`+]\x0c9\xf5\xbc\xf0\xbeg\xbd\xea\xcc\x16=\
-JU\x1e\x08m\x01\x94\xd4\xf1C\xe1eS@\xf0\xca\
-\xf7%`+nj\xc7\xa9\x84D\xc4\x1c9\x8a\xdc|\
-6ZZ\xc58\x14\x13\x83/95\xc8\x14j\x98\xe6\
-\xa2\xd5\xd2'\xf5\x9azL\x13\xa1Id\xb7\x99\x90\xdb\
-nF\xb9\xda\x8d\x06\xa5v9,9=\xf9N\x13\xec\
-\xd9r\xd4G\x0d;\xabF\x88c\xff9\x8f\xdf\xee\xfb\
-=\x1a\xf9\x02\x9c\xbf\x90\x80\x93\xf1\x17p\xa3\xad\x07\x19\
-\xc4OJ\x14\xe9n\xbaX\xa8\xef,\xfa\x94\x98P(\
-\xb7@\xe9\x0e<\xf9W\xec)*w-\xc1g\x04\xfb\
-\xb6\xb9\xe4D\x8d\xbe\xcc\xb2Z\xfc\xe3\xe4\x19\x1c<\xf4\
-7\xb0r\xf3\xb0\xef\xc0\x1fP \xd1!\x89'e*\
-\xa6K\x85>\xbf!\xd5F\xe4.\x90[!\xb0\x0c\xae\
-\xe5\xdc\xe2\xd2\x11\x13\x13\xe4\x87o<\xaf<\xe7\x96\x15\
-5\x9ciE\xe5\xf8\xfb\xb1X\x1c?\x19\x877\xf6\xef\
-\xc7\x8d:\x11\x92\xab\xa4\x0c!\xedp\xea5U!\x8b\
-4[\xc9\x037*4n\xd4I:\x17\xc3rs\x08\
-\x8em\x95\xfb\x87$\xe0Jesp\xe4\xf8)\x1c>\
-|\x98\x8cc.2\x05*\x5c\x22\xd5\xd3]~M\xdc\
-\x0b6\xe9tv\xa7\x1dw\x8c\xe4\x88\xb6\xf9\x9e\x84\xb7\
-\x1a\x95\xfb\x22\xbdI\xfd\x80\x0bm\xf4\x042JxL\
-\x0f\x9cKI\xc3\xb5\xa6.|\xc2me6Y\xf1\x83\
-\x01\x5c\x97\x9a\xc1Q{ \xf3\x04\xd7\xce%&\x056\
-\xc8\xfd\xc7\x9d\xc8\x1d\xd5\x82\xdc\x1a\x01\xce^NE\x81\
-X\x85x\xf6]\x5c\xa9U\x90\xaa\xfb\xc0\x96\xdbP\xad\
-u\xe3\xaeTA/\x10\xca\x0dr\xbf\xba\xd3j\xa3\x05\
-\xb7\xa2Q\xf8\x1d\xafC\x8dO\xb9-\x88\xcb\xe6\xe1\x9a\
-H\x8f\xaa\x1e/\x9a5\xe6\xc7\x7fz\xf3-Wx\xac\
-\xa8\xdc\xaf\xbd\xac\xdc\xd1\xe2\x08\xdd\x05\x5cu\x1f\xde\xcb\
-\xafE\xb9v\x002g`\xf5\xc2\xa7\x97\xa9\xdc\xf7\x08\
-\xd2\xa9\xdc;\xf8\x03\xf3\xc2\xf1\x13\x82\xca\x1c\xee\x9dP\
-\x0b9\x94\xb8\x0d\xc2\xc8\x16\xa3\x17\x87\xc3/\x22\xf7\x0e\
-\xff\xdam\x8a\xdda\x99\xd5\x1b\xb6\xd8k\xbb^2\xbe\
-/\x89\xff\x01f\xb9_\xfc\x11\x80=\xcf\x00\x00\x00\x00\
-IEND\xaeB`\x82\
-\x00\x00\x03T\
-\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
-\x00\x00\x00\x19tEXtSoftware\
-\x00Adobe ImageRead\
-yq\xc9e<\x00\x00\x02\xe6IDATX\xc3\xd5\
-\x97\xcdN\x13a\x14\x86\xeb5\x94\x95{q\xe1\xd2\xc4\
-\xe0\x05\xb8\xe2\x0e\x5c\xb8\xf4\x02\x5c\xb10\xea\x05\x18\x96\
-&bX\xb8\xb0\x91X \xd1\x9d\xbf\x89\xa4\x14\xb1R\
-\xa4HE\x94\xfe\xd0\x02C\xff\xa6\x9d\x19\xa6e\x80\xe3\
-y{\xfa\x85QJ\x82\xc9!\x86I\xde\x9c3\xa7\xf3\
-\xcd\xfb\x9c\xf3M\x9bN\x84\x88\x22\xffS\x91s\x01\xc0\
-\xc7\xd5\x90n\xff\xa5\xfb\xac\xc7==d\x0d\xa9\x02\xf0\
-12<<\xbcj4::\xba\x19V<\x1e\xaf&\
-\x93\xc9V:\x9dv\x13\x89Dk`` \xcdkn\
-h\x02\xa48\xd2\xe1\xe1q\x99\xba\xef\xb7\xc9\xb2,\xda\
-\xdf\xdf'\x86\xf1x\xcd\x18\xeb\x8a\x1a@?\xf3\xb0\x1c\
-\xc7\xa5Lf\xb9\x0b\x14\x04\x01\xc5b\xb1:\xaf{p\
-\x1a\x88S\x01\x1c\x1c\x10ww\xb2l\xdb\xa1\xf9\xf9\xcf\
-d\x0e\xd7u\xe9\xf9\xc4D\x17B\x05\x00&{\xc1\xc9\
-\xaa7\x1cJ\xce\xcdS\xf8p]\x0f\x8b\x17T\x00\x82\
-\x10@gO\x14\xce\xed\xa6G\x1fgf\xe9\xf5\x9b\xb7\
-\x14\x9f\x9c\xa4\xa9\xa9iz\xf7\xfe\x03E\xa3\xd1e^\
-\x7fA\x05\xc0\xef\x10\xed\xb6%\x86\x85\x9a\xe3\x05\x94]\
-\xcd\xd1\xe4\xf4+z2\xfe\x94\x9e\xc5^\xd0Lb\x0e\
-\x8b\x17U\x00\xda\x81\x18\xf5\x13 <\xff\x90j\xcd6\
-\x157\xab\x94/nS\x89c\x8d\xb7\x85\xd7~Q\x01\
-\xf0y\xcc\xcd]\x1e\xb5\xc7{\xdb\xee\x9f;\xbe\xe4\x88\
-]\xb8\xbd\xee\xe2\x94\xca3\xe0u\xe4\xc6uWb\xd8\
-\x109\xea\xe63D\xd4\x01\xa7\x06\xe0\xf4:\xad9\x22\
-\x98\x98hr\x80\x98kPS\x9d\x00\x00*-\xb91\
-\xe2NS\x8c\x10\x0d\x04\xf2m\xfb(\xb6|E\x00\x9b\
-;\xdbj\xfci\x8e<l\x88\x1a\xae9\x13\x80:\x8f\
-\xb7T#*\xd7\xc5\x04\x06\x06\x005(\x9c\x17\xab\xbc\
-%\xbb\xca\x13\xc0Ma\x0e\x15*rn\xcc~Z\x02\
-hj\xdd\xad\xf1\x94'\x00S\xdc\x1cqm[@`\
-\x9a\xab\x1cu\x9e\xeb\x81A\x15G\x11\xc0j\x891\x0c\
-\xd6w\x04 \x0cd&b\xb6iu\x8b\xa8\xaa\x09P\
-\xb6\xc5\xbc\xd0\x03\xf8\xbe)c\x87)`\x0c\x18\x84\x1c\
-\x00[ME\x00t\x03S\x98\xad\x94\xc5\x1c\xe7F\xe6\
-\x1c\x00\xc8q]\xa9\xa1\x08\x80\xfd\xfcV\x12s3\x01\
-\x085\x18B\xe8\xda|\x8e)\xa8N\x00[\x00\x03\xc8\
-\x98g6\x04\x002\xe6\x85\xde\xf8\x17\x0b\xfc,\xd8\x8a\
-\x00\x18g:O\xb4T\x14#\x98\x02\x00\x02\x0c>\xfb\
-\xc5S(\xf0C\xb8fI\xf7k\xf9R\x87\xd7\xbeT\
-\x01\xc8U\x8f\xbaN\xadK\x0e\x90\xaf\x85\xde\xb7\xc2\x92\
-=O\xa6\xb3\xde\xa3\xb1q\xeb\xda\xd0\xf5\x15\x98\xb3n\
-\xa9\x00l4\xa4k\x18\xff\xe0\x11\x7fZ\x17S\xd4\x13\
-\x0bYo\xe4\xee\xbd\xe2\xa5\xc1\xcbK|m\x8cu\x87\
-5\xa8\xfa\xb7\x1c\xdde\xd9<\x8f\x1f\x19\xfe\x9e\xcf\x1e\
-7\xbd\xc9\xbax&oF\x00h\xf2\xff\x81\x99\x94\x9e\
-\xe9?\xbf\x19\x01B\xd3\xf4\xfc\xbd\x9c\x9e\xa5~\x03Q\
-l%\xa1\x92\x95\x0aw\x00\x00\x00\x00IEND\xae\
-B`\x82\
-\x00\x00\x05:\
-\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
-\x00\x00\x00\x19tEXtSoftware\
-\x00Adobe ImageRead\
-yq\xc9e<\x00\x00\x04\xccIDATX\xc3\xb5\
-\x97]L[e\x1c\xc6wo\xbc\xd9\xe5\x12I q\
-\xd7&\xe3N\x13\xb8p\xd1\x85D\xbdP\xe3\x10\x18\xe5\
-+.&J\x04'\x86\xaa\x8b\x99\xe0\xd0\xa2l\x19\x86\
-9\x17\xdc\x1a\x16\x98\x80@l\xa6C\xca +\x83\x1e\
-(\xcc\xda\xd1\x96\xd2\xd2J{\xfa\x01\xa5\xd0\xef\x16\x1e\
-\xdf\xff\xdb\x1d\xc7\xcc\x04*\x87\x93<9o!\x9c\xe7\
-\xf7<\xefG\x0f\x87\x00\x1c\xcaF\xcf\xbd\xfa\xe9\xbbL\
-Z&a\x0fj`\xca\xd9\xe9y\xd9\x9a?]P\xf2\
-\xa5\xc1\xe9\x8f\xa7W\xc3@0\x02\x84\xa2\x19\xad\xc72\
-\x8a'\x81X\x22s\xbfyk\xdaK\x10r\x02\x1c{\
-\xe7\xac\xda\x1c\xd8\xc8\x98\x12@\x84\x99\x85\xe3\x19\x911\
-)\x1aKa%\x94D8\x9aBs\x87\xc6\xbe\x13\xc4\
-\xff\x02\x90\x12\x93y$\xf1\xc8X\x92\xcf\x1f\x84]\x8c\
-\xc2\xe5\x09\x22\x12K\xa3\xf4\xc3\xefM4uY\x01\xb0\
-\xeb\xd86\xd5\x90\x9e:\xfc\xcc\xb9\xe7_.\x11?V\
-\x9eEEU\x0d*\x99\xde\xaf\xad\xc3\x9d\xb1\x89\xc7\x00\
-\xac\xb6%\xfc\xb9\xe8\x87k\x15X\xf6\x04\x10\x08\xc6\xd2\
-\xaf\x9c\xbep\x9fA\x1c\xd9\x15\x80]\x87\x99\x1a\x8a\x8a\
-\x8a\xcc\x92Z[[\xdd\xa4\xafU\xad\xfe\xafT\xdf\xa6\
-\x06\x06\x06195\x85\xd9\xb99\xe8&&PPP\
-\x80!\xcdo|\xdeI\xa6\xf9\x05\xcc\x98\x5c\x1c\xc0\xe1\
-OA\xf4\x85\xf0C\xaf\xce\xcd\x00j\xf6\x02PCf\
-\xd8\xe5\x8a\xc7\xe3\xf0z\xbdH\xa7\xd3\x98\x9c\x9cDe\
-e5fg\x8d\xbc\x81\x07f\x1bt\xd3\x16\x0e@2\
--x\xf0\xdd\x8dQ\x8f\xac\x00\xe1p\x18F\xa3\x91\x8f\
-S\xa9\x14~\xea\xedE\xe3'\x9fa\x86A8\x96\xdc\
-Pwu\xe3LC#\xce5\x9d\xc7\xed\x91q\x5c\xbc\
->,/\xc0\xc6\xc6\x06\xf4z\xfdc@}}\xfdP\
-2\x88\xd0F\x1cf\x9b\x0b\x82\xc1\x88\xa9\x19\x13\xac\x0e\
-\x11\x97\xbadn\x80\x00\xa6\xd8:\xd8~E\x22\x11\x94\
-+*0\xae\x13@\xe7\x04mW\xda\xaa4\xbe|S\
-\xe65@f:\x9d\x0e\xc3\xc3\xc3\xe8e\xf5\xf7\xf7\xf7\
-C\xab\xd5\xa2\xaa\xba\x06cw\xf5\x90\x0e*w\x90\xed\
-\x04\xb6\x0e\xda\xbbe\x06\xa0y\xb7\xdb\xed\x18\x1a\x1aB\
-gg'zzz8PIi\x19ni\xf5\x10\xd7\
-\x00o\x08\xb0\xf9\x00g\x00\xb8\xd0%3\xc0\xd6\xd6\x16\
-\xdf\x09\x81@\x00\xa2(\xc2\xef\xf7cmm\x0d\xa7\x14\
-\x95\xd0\xfc\xae\xe7\xa9\xc9|\xc1\x0b\x98=@\x9b\xdc\x00\
-\xdbA677\xf9v\xa4V\x14\x15\xd5\xe8\xfbU\xe0\
-\xa9\x1d\x81G\x00\xe7;\x0f\x00\x80\xcc%\x80$3O\
-$\x12(+\xaf\xe2\x00\x7f\xb8\x00\x8b\x98\x01\xa06Z\
-\xd5\x070\x05\xff\x98'\x93<=MI\xc9\xa9J\x0e\
-\xa0\xb7\xb3\x03\x89=\xc5\xf8\x170\xb1\x00|q\xf5\x00\
-\x00\xa4\xea\xc9\x98\x14\x8b\xc5P\xa6\xa8\x82zH\xc0\x98\
-\x19\xb8k\x05\xe6\x9c\x99\xfb\xe7Wd\x04\x90\xd2Sj\
-\x02\x88F\xa3\xdc<\x14\x0a\xa1\xb8\xb4\x02\xd7\x06\x05\xdc\
-f\x87\xe4\xa0\x01\x1cd\xc4\x04(;d\x06H=\x9c\
-s\x12\x99\xd3\xb9@ \xc5eU\xb8\xd8-\xa0\x7f:\
-c\xae}\x90i\xe0\xa3v\x99\x00\xfe]=\xa5&\xad\
-\xae\xaer\x88\xb7J*p\xb9W\xc0=\x1b\xb8~\x9e\
-\x01\xee\xcc\x03g.\xed\x13@\xaa\x9dD\x8b\x8e\x92\xd3\
-qL\xdf\x01+++X__\xe7\x10'Y\x03\xdf\
-t\x09PO\x00\xbf\xcce\x1a\xb82\x064\xec\xa7\x01\
-\xc9X\xda\xebdNi)9\x1dD\x04@\xf5\xd3\xcf\
-\xde|[\x81\x96\xeb\x02O~u\x1c\xb8q\x0f\xf8q\
-,\x9e~\xbdNm\xa67\xaa\xac\x00\x9ed,m7\
-2%\x00\xd1#\xf2\xe4\x12\xcc\x1b'\x15h\xef\x11\xa0\
-\xbcf[\x7fO5\xe2<q\x9a\xbf\x8ei\xf7\xfcJ\
-&\x01\x90\xa9$i\xb5SB2\x0f\x06\x83p\xb9\x5c\
-\xdc\x90^J\xe8\xb3\xc7\xe3\x81\xdb\xed\xc6\xf1\x13\xaf%\
-\x9f}\xa1\x9cL;\x98\x8a\x99\x8e>\xc9xG\x00\x95\
-J\xc5\x01\xa4\x15.\xcd7\x19RR:\xf7)\xb5\xc3\
-\xe1\xe0\x22\xe3\xc5\xc5E\x0e\xf5\xe2\xf1\x97\x5c\xf4\x1e\xb9\
-\x93\xe9\xae\x00---n\xe9`\xa1\xd4\xd2\x97\x0d\x8d\
-\x97\x97\x97\xe1\xf3\xf9`\xb3\xd9\xf8}ii\x89C\x10\
-\x00\x8d\x0b\x0b\x0b\xcd\xb2\x00\xd0\xa2\x92R\x93\x11\x8d\xe9\
-N\xdfxT;5`\xb5Zy\xf5\xd4\x0a\xfd\xce`\
-0$\xf2\xf2\xf2\xee\xb3g\x1c\xd9\x17@SS\x93[\
-\x9agJO\x22\x13\xaa\x9a\xc6\x16\x8b\x997@\x9fG\
-GG#mmm\xde\xfc\xfc|\x13\xfb\xdbA\xa6\xb2\
-\xbd\x9a\xff'@ss3\x9f\x02JG\x10T?U\
-???\xcf\xeb\xd6h4\x91\xba\xba:\xe7\xc3\xb4]\
-L\x1f0\x1d\xcd\xc6xG\x00\xa5R\xe9v:\x9d\xbc\
-bJJo>\x94\xb4\xbe\xbe\xde\x99\x93\x93#\x99\x16\
-gSuV\x00\x8d\x8d\x8dn\x8b\xc5\x82\x81\x81\x81H\
-mm\xad377WV\xd3\xdd\x00\xf8\x7fFL\xc2\
-A\x99n\xd7\xdfC9V\x18\x85p\xc8\x04\x00\x00\x00\
-\x00IEND\xaeB`\x82\
-\x00\x00\x05+\
-\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
-\x00\x00\x00\x19tEXtSoftware\
-\x00Adobe ImageRead\
-yq\xc9e<\x00\x00\x04\xbdIDATX\xc3\xed\
-WkL\x93W\x18>#q\xc92\xe9\x16\x97\xa8T\
-e8\x9d\x02\x15\xf6\x03\x872\x93\x01f,[p\xc4\
-0\xff`\xa2.\x1a:\x1dN\x03\xba1\x89[\xb3\x80\
-\xd9\x0c\x84\x02\x19X\x1c\x14\x8b\x85\xb2\x82\x95^\xe4f\
-\x0b\x8e1\xf8\xc3F\xcb-\x81\x15\xdc\xa8\xc2\x1c\x1b\xb7\
-ji\x91\xf2\xee\xbc\x87\xaf\x0c\xdc\xb8\x0da\xd9\xb2\x93\
-<\xed\x97\xf3}\xfd\xde\xe7\xbc\xef\xf3^J\x00\x80\xfc\
-\x93 \xff\x0a\x02t\x09(D\x14\xd9\x14q\x14\x01+\
-F\x80\xae\xddd\xdd\xc6f\x22L\xf8\x95\xc4\x8bG\xc8\
-\xa1\xd3\xf7\xc8\x8e\x97;82a+A \x85\x9c\xbe\
-0H.\xdd\x80\x19@2\xabyM\xf4\xbe\xfbr\x13\
-hd\x06\x91\x04^\xa3Q\xf4\x06\xee\x85G\xf5\xd0\xbd\
-\x83\xcbM \x9b\x9d\xf6@t/\xbd\x162= \x89\
-?H\xa5,\x1b\x01\x8c1y\xc1\xbb\x9d\x88K\xc6\xd7\
-\xc6&\x0e\xa0\x10\xb9\xfdB\xfe\xc5+6F\x8c\x12\x5c\
-N\x02\x93\xa7\xa7\xa7\x0d\xcc\xd39\xb9\x98c6\x14\x0a\
-\xd2\xe4\xa3+A \x8c)\x9e*\xdf7G\xeb\xdc{\
-\xb5\xcc\x89\x9e@D\x96T\x83+,\x0b6FH\x08\
-\x13\xf5d*{.T\x03\x01\xf8\x037\xbf\xc0\x0e4\
-*T\xdfb\x88R\xd5,X\x03t\x1d\x16\x08\x04z\
-EU\xf5\xc8\xa0mt\xc2\xd4s\xf7!\xbesQ\x95\
-\x90\xae\x8f\xd0\x13\xcf\xe5\x94\x83\x87\xb4\x02\x9e\xcc.\x03\
-\xd4\x06\xdd\xaf\x99\xcb\xb0\xaf\xaf\xaf>\xbf\xd2`\xb5\xdb\
-\xed\x80\xf8y\xe4>\xc4^\xab\xb4\xb9\x88/\x86\x80'\
-\xd3\xc0g\xf9\x8e\x19\xf5`\xd7^3\xbav\xdas\xee\
-h\xd8\xc7\xc7G\x9f\xab\xab\xb0\x0e\x0f\x0d\xc1\x10\x87\xb2\
-\xf6.\xe7\x967\xf7wsa\xd8\xbd\xe8^\x80/f\
-\x9a\xa0\x86\xdf\xa96B\xf7\xf0\x03\xd8\x19\x9f\xd4\xcf\xa5\
-\xe7\x1a\x8a\x98-~\xfem\x97T\x1ak__\x1f\xb8\
-\xd0\xd1s\x07br\x15VN\xc4\x87\x97\xd4\x8c0\x14\
-\xe9\x15\xb7\x1e8\x1c\x0e@\xa4\xd6\x191\x9e\x85\x9b\x05\
-~m\xa9%\x1a[\x97\xd9\x0c\xe6.\x0a\xf3$\x14\xdf\
-6\x8e{\xbd\x1e\xd1\xcdB\xc8\x09o\xa9\x04<\xd1\xbd\
-V\xab\x15\x10w\x7f\x1b\x84\xf3\x92\x5c\xbbR\xa9\x84\xfa\
-\xfaz0\x99L\x0cu\xdf5\xc1Q\xb1d\x18\xc9Q\
-D>\xb6v\xcc\xb4@O\x93_~\xd3\xd6\xdf\xdf\x0f\
-2\x99\x0cD\x22\x11\xa8T*\x90J\xa5\xa0\xd1h \
-K[9\xbe\xe9\x95\xe0\x1f\xb8S\xafy,\xf3\x00\x97\
-\x8e\x22\x9e\xc7\x86\xe6S)\x19\xf6\x82\x82\x02\xe6\xe2\xa0\
-\xa0 \xe0\xf1x`\xb1X@[^\x01\xfb\xcf&\x0c\
--\xa6S\xceg\x94\xcf\x09L\x83\xe2[{\xe6\xc2`\
-\x9a\xb2\x14\x14\x0a\x05\x88\xc5b\xc8\xcc\xcc\x84\xa2\xa2\x22\
-P\xab\xd5\xd0\xd9\xd9\xc9`\xec\xfe\xc9\xb9\xc9\xdb\xa7u\
-.\xb7\xcfK\x80\xae\xb7\xd8)p\x0e\xc0j\x97\xacx\
-\x88\xca\x7f\x82\xe2)\x89\x0e>\x97+![\x96\x0f\x07\
-c\xe3G\x84\x1f&\xd8\x92rd\x8eo\x1a\xbf\x07\xa3\
-\xd1\x08-\xad-\xf0\xcb\xc0 \x1c8\xf1\xbe\x05\xb3b\
-\xc1\x04\x5ci\x84\x85\x85\x84F\xdc&\xe72\xac,\xcf\
-3\xb5\x13\xec;\xe3\xba\xd33\xaf\x82\xe5\xfez\x89\x06\
-\x9e\xde\xfcb\x1b\xf7<\x92\x8d{f\xabO[\xca5\
-\xedXCC=444\x80\xa5\xb7\x172\x14\xc5\xc3\
-\xf3\xe9\xc0e<\x92\xe5(\x9e6]\xe5\x9c*2x\
-}\xf4\x83.Zl\x121\x0c\x1b%\xeaq\xf7/\xcb\
-'\xef\x05\x87_\xfe\xd3\xe4D\x0bLh\xf4\xc9>u\
-\x95\x1e\x0c\x06\x03\xb4\xb7\xb7\xc3\xd7\xc6\x961\xae\x81\x09\
-f\xf16m8h<I::e\xf8b\x81\x83D\
-\xbdWC\xb6\x0a^\x9b*\xc3\x94\x5c\xb0B\x0f\xab$\
-\xb4\x04\x9fJ\xaa\x9bC71(\xd4O\xf2\x0a\xc7t\
-:\x1d\xd4\xd6\xd6\x82\xc9|\xdb\xb9a\x9b\xf7_\xeab\
-\xb2\xe5~\x9cu\x1f\x0d\xf3\xb2\xd4N\xf2\xf6\xb1\xeb.\
-\xb6\xae\x94\xc3\x90l\x97U\xc1KW\xab\x80\x9cMn\
-Z\xd0\x1cI\xbd\xb1\xe7\x88\xb0\xef\xcaW\xc5PZZ\
-\x0a\x1d?\xf6L\x04\x06\x87t<\xaa\x0b\xc2\x84F\x8d\
-\x07\xc8o\x02\xd9\xf9\xaa~\x9a\xf10F\x8e6 \xaf\
-\xbcJxCi\x00\x92(\x1d\x98\xcd\x95\xb3y\xc3}\
-=\xbf\xf9Dj\xa6].\x97CSK+D\x1c{\
-\xf7\xce\xf4\x14%\xae\xf1\x8a\xf5w\x9c\xf5p\x02\xc2\xd9\
-\x0f\x89\xd1\x81\x03O\x8e\xf7\xdc\xd2i\xe7\xf3\xdfu\xfc\
-o\x14.6\xd2\xef\xd8\x17iI\xbe,\x9d\xc8\xd3\x96\
-;\xa7\x0f1\x8c%\xc6\xdf\x9f\xbaw_q5\xa0A\
-l\xb5\x08\x8c\xf9\x94\xf1\xe0\xf03K\x9a|h\x13Z\
-\xbd\xce\xa3\xd9kOH\xf7\x0c\x0f\xb0\x0f\xfe\xf3\x87\xc8\
-\xf9/\xee\xb9In\x00\xf6{>\xed\xf7\x08\x1e*>\
-]\xe5X\xaa\xf1GZ\xf5\xb6Y\x0b\x11\x1d\xb3C\xc9\
-\x918\x099\xf9\xa9\x96!\xfa\x5c\x1a\x0d\xcf\xb3\xff\xff\
-7\xfcO\x13\xf8\x1d\xe7\x87\x19\xb9D\xc3\x01\xcf\x00\x00\
-\x00\x00IEND\xaeB`\x82\
-\x00\x00\x06m\
-\x89\
-PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
-\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
-\x00\x00\x064IDATx^\xad\x97[lT\xc7\
-\x1d\xc6\x7fs\xce\xd9\x8b\xbd\xf6\xfa\x16\xa0\xbe\x00\x0e\xb2\
-ic$BJ!\x22\xa1-\x95b\xa5/\xeeKh\
-+\x95\xa6U\xa5\xc6`U\xaa\xda\xb4\xaa\xfaV\x09U\
-\xca\x03\x94'\xda\x07\x84\x14)\xad\xc4\x8b\xa5R\x83y\
-\x08\xc5\x189\x0ei\xd3\x84\x9a\x9bcj\xec\xb2\x04\x1b\
-;\xbb\xf6z\x8f\xbd\xbb\xde\xb3g\xa6\xc3h\x85\xe5r\
-l\x88\xc9'}\xfa\x9f\x9d\x87\xfd~\xf3\x9f\x99s\x11\
-J)\x82$\x84x\x05x\x9e\xc7kH)\xf5w\xd6\
-(' \xb8C\xbb\x01h\x97R\xbe\xc6cdY\xd6\
-\x07\x1a\xf6\xbb@\xb7\x069\xff\x14\x00&\xfc\xb7\xed\xf5\
-\xe2`]DDn\xce\x89\x8a+W\xaeP]S\x8d\
-@\x00\xa0P\x08e(A)f\xd3i^\xa9\x17/\
-\xbc\xb4Nl;\xf1\x1f\xb9G\x83|[CL<M\
-\x07\xf6\xff`\x8b\xdd,%\xf8J2<<Lee\
-%+\xc9u]\x1e\xc0n\xa9\xb0\x22\x1b\xa2*r?\
-\xa7\xea\x81\xb5\x03\x08-\x05H\xa1\x0d\xf4]\xbcH.\
-\x97\xc3/\x16QJ\x91\xcf\xe7Y\x5c\x5c\xa4P(P\
-\xd4c\xb5\xb5\xb5\x94\x01X\x80\xf8\x82\xf6\x80\x01\x006\
-D\x05\x1f\x0f\xbcK>;\x8f\x85D\x952\xe2\xb6\xc4\
-\xb6\x04!!p>Sl\x8c;\x80D*\x04\xf0\x9c\
-\x10\x02\xe0\xcb@\x05P\x0f4`\xc4Hi\x9f$\x02\
-\x01N\x9c8!\x00\x81\x05\xd2\x87\x96\x96g\x09em\
-\x14\xe5(\xa5\xb4A\x08XW\x19%\xe2\xd8DB\x16\
-\xc3\x13s\x5c\xbc=A\xf7X\x8e\x5c$\xbe\xa9\xbd}\
-\xf7\xef-\xcbZ\xdc\xb1cGYUU\x95\xd3\xd8\xd8\
-\x18~\xe0\x86\x86\x86\xd0\xa5K\x97\xdc\xae\xae\xae\x08\xf0\
-\xd6\xaa\x1d\x00\x13DU,\xc2s\xd51\xf2\x9eO\xa1\
-(\x91Ja\x09A\xd8\xb1\x88\x86l\xe6r\x05\x12\xa2\
-\x8e?\x9f\xff+\x0dM\x1b\x01\x22\xc0f\x96\x84\xef\xfb\
-x\x9eGuu\xb5\x9ePK\xf4\xea\xd5\xab\x87\x84\x10\
-(\xa5\xdeZ\x11\xc0\xb2A\x00\xb6-\x90\xda\xb6\x148\
-\x08\xa4\x12X\xc2\x8c\x1b\x8fL\xb9\xec{\xf5;\xd47\
-6\x11|/\xc1\x84g2\x19\xca\xcb\xcb\xcdf>v\
-\xec\xd8&\xbd\x7f\x0e.A,\x01\xd0\xd9\xd9\xa9\x0e\x1d\
-:\xa4l!\x08Y\x10\xb6-\x1c\xc7\xc6BP\xb4\xcd\
-\x1a\x1b\x00\xc7\xb2\x888\x96\xae\x02`Yx\x10\xc0\xdc\
-\xdc\x1c555\x06 \x1a\x8dr\xe4\xc8\x91\xcd\xc0\x03\
-\x88\x1b\x1a\xa2\xc7b\xb9\xb0mt0f\x8d\xcb#6\
-\xb1\xa8\xa3\xc7,2\x8b\x1e\x93\x99\x1cc\xa9y\xee\xcc\
-.\xe8\xdfEr\xf9<\xab\xc8,A6\x9b5\xa7f\
-\xe9\xffm\x0e\x1c8\xb0\x1e\xe8\x00X\x06\xa0\xb4t\x16\
-\x8e\x0d\xe1\x90\xc0S\x8a\xb1\xa4\xcb\x8d\x8c\x83\xd3\xb2\x97\
-\xa6}\xaf\xb3\xb5\xe3\x17\xac\xdb\xfb:\x0d/\xb4s\xfb\
-\xce$\xfd\xfd\xfd$\x93I\x94R\xe6\xfa\xf8\xf1\xe3\xe8\
-\xba\xac3\xe7\xce\x9d\xe3\xe8\xd1\xa3\x1c>|\x98\xde\xde\
-^\x12\x89\x84\x04,\xa1\x15\xdc\x01\xed\xff\xce\xe6\xf8\xe7\
-\x94Ok\xc7\xcf\xf8\xe6/\xdf&\xf6\xf57\x99|\xa6\
-\x83k\xfe.\xae\xf1-dk\x17\xad{\x7fN^V\
-s\xfaog\xd1wM\xee\xdc\x9d\xe2\x1b\xafvr\xfd\
-\xfau\x03\xa0gk\xd6?\x16\x8b\x99\xebx<\x8e\xe3\
-8%8\x04\xc0#\x00\x96%\x98\xcaA:\xde\xca\xfe\
-\xdf\xbdM\xd5\xae\xd7(\x84b\x08\xdbBY\x82lA\
-r\x7ff\x91O\xeef\x18\xb8\xear\xfa\x1fad\xd5\
-^\xae\x8f\xdcg2\xd7\xc6\x85\x0f\xee\x9b\x00\xed\x87\xa1\
-\xcd\xcd\xcd\xb4\xb5\xb5\x19755\xa1\xa1\x14 \x83\x1f\
-F\x16\xdcq\x15\xdf\xff\xe9o\xa8l\xd8H\xe2\xec;\
-L\x8f^\xc3\x89\x94\xb1\xb5y\x07\x9b[\xb6\xf3Iy\
-%c\x09\x97\xcff\xf2\xdc\x9d\xce2\xa1\xed\x88\x0dL\
-'\xe7\xd8\xb7+\xca\xfa%\x003{=k\xea\xea\xea\
-\x00\xccu*\x952\x00J+\x10\xa0\xb9Zp\xe1\x9d\
-c(,\xca\xe6\xc6\xd9\x10\x8fR\x94\x92{\xc3}$\
-e\x05\xdb\xda\x7fLM\xdb\xcb|<\x9cf\xd2_\xc0\
-\xcdx,\xcck/x \x00\xb5t:B\xa1\x90\x09\
--\xdd\xea\x1f\x8e\x01*\xf8>`\xc1\xc6\xb8\xa0P\x1c\
-#\x1c\x8bS\xb7\xa5\x96\x92xv}\x05\xe9\xac\xc7h\
-\xff\x9f\x98\xae\xbcL\xcb\xf6\x83\xb8\x0ba\xbc\x82\xa4X\
-\x94x\xda!\xc7B-\xaa\x80\xe3i\xa0\x96\xd5\x15\x01\
-\x00\xd6\xc7C\x84\xca#\xfc\xbfjc!\x9e\xa9\x0cs\
-\xe1\xdf\x83\xec\xd9\xf9\x13\xca\xa3\x0e\xb92G\x03(\x03\
-ak\x00\x16K!\xa5\x1c%0*\x15\xa4\x5c\x05@\
-X\xa5*\xcc\xf5#\xfapl\x86\xf1Y\x8f\xef\xfd\xfa\
-\x8f\xdc\xca\xd4\xe0D\x5c\xa2\x11\x1b\xcf\x93\x14=\x07\xd3\
-\x01\xa5\x90R\xf2PjY\x01V\x05\x10\x08L\x0d\x04\
-\x18\x9dv\xf9\xd5_\x86\x18\xbd\xb7\x80=\x93g\xd3\xba\
-2\xf2y_\xbbh\xea\xce\xaf\xd4p\xf9\xdd\xe0%\x00\
-\x9ex\x09L\xb8\x10<\xa2\xd6/U\xf2\x87\x1f>\xcf\
-\xf5O3D\x1b\xb7\xb1\xf3\xc5\x97Y\x12\x5cN`\x8e\
-\xdbS\x01(\xc0\x12%\x00m\xd4R}\xb1\xb5\x96\xdd\
-[\xe2t\xbf\x97\xa5j\xf7W\xf9\xd1\x1bo\x10\xa0\xb5\
-\x03\x98\xb57\xd5\xd8\x08\x01\xd2\xcbSpSx\xf33\
-\x14\xb3i\x0a\x19\x1f%\xfd\xd5\x82\xd6\x08\xf0\xf0)\xe7\
-\xe3\xe73\x14\xe6u\xa8\x0e\xd6\x00\xcb\xf7\x89\x10\xc13\
-}\xfa\xd7r\x8c\xb2\x137\x03\xc7\x01\xb2\x1e\xfe\xad\x94\
-\xcco\xf7DT\x03\xd8_p\x07\x08\x92\x09\xfd\xd7=\
-?\xfd~B\xa6\xcf\xdf\xf6\xef\x02\xeev;\xfc\x92\x06\
-\xa8\xe3s\xcau]\x1fpW\xed\x00@2\xab\x0a\x1f\
-~*\xd3\xbd\xb7\xfc\xd4\xcdi9\x05\xf4\x03\x97th\
-\xbf\x10\xa2\xd3\xb6\xed\xaf}\x9e%XXX\xf0\x07\x06\
-\x06\xd2'O\x9e\x9c\x06\xba\x83\x00>\x1aI\xca\xad\xe3\
-\xb3*\xd7;\xe2\xa7nL\xcb\xd1R\xe8Y\x1dt\x8b\
-\x00=\x09\xc0\xd0\xd0\x90\xdb\xd3\xd3\x93\xd2N\xcf\xce\xce\
-\x9e.\xbd\x1d\xdf\x08\x02\xe8\xee\xea)\x00\x8c\x04\x84\x06\
-\x85\xaf\x08055U\xd0/\x22\xa9S\xa7N%\xc7\
-\xc7\xc7/\x03g\x81~\x1d\xec\xae\xb8\x09K\xdfv\xda\
-O&\x85\x01@\x08@aZ\xfc\xde\xe0`\xba\xbb\xbb\
-;\xa5\xdf\x8a\xcc$\xd0^\xeds\xcda\xed\x9aw3\
-n\x11`p\xf0\xfdt___\xfa\xcc\x993\xa6\xc5\
-\xa5\xd0\x8fx\x02\x89\xb5\x9ec!D\x18x\x13\xd8O\
-is\x06\xb4\xf8\xb1\xfa\x1f\xbd\xfa*_\xf2\xd8\x15\x9d\
-\x00\x00\x00\x00IEND\xaeB`\x82\
-\x00\x00\x04\xa3\
-\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\x04gAMA\x00\x00\xd6\xd8\xd4OX2\
-\x00\x00\x00\x19tEXtSoftware\
-\x00Adobe ImageRead\
-yq\xc9e<\x00\x00\x045IDATX\xc3\xe5\
-\x97\xcd\x8fTE\x14\xc5\x7f\xb7\xea\xd6{\xaf\xdbn\xc7\
-\xf9@\x9d\x89FM4\x99D\x8d\x1aH\x98\xc4\x8c\x1f\
-\x1b\xfe\x02L\x5c\xf1\x07\x18\x16.M\x5ckX\xc3\x8e\
-\xc4\x8d\x1b\x17\xce\x82htA\x5c\x18\x0d\xe2\xc4\xc6\x00\
-=`PQ\x19`\x02\xa2\x0e\x0c\x83\xd3\xfd^\xf7\x94\
-\x8b\xaa\xee\xf9`\xe6\x0d\x84Q\x16VR\xa9\xce{\xb7\
-\xeb\x9e:\xf7\xd4\xa9z\xea\xbd\xe7~6\xe5>\xb7>\
-\x80]\xbbv\xbd\x03\xec\xfd\x8f\xf2N5\x1a\x8d\x03\xeb\
-\x19\xd8\xbb\xef\xbd\xa3;\x1f\x1fv\x00\x9c<:\xcf\xcc\
-\x977X\x9c\xef\xdcS\xa6\xda\xa0\xf2\xdck\x03\xbc\xb8\
-g\x10\x80\x8b\x7f\x16|\xf8\xee\x1e\x80\xdb\x00p\xfc\xec\
-\x1c\xdf?0\x04x.\xfd\xb8\xc0\xfe\xb7\xceo\xcbr\
-\x0f\x1dy\x9a\x0b#\x96\xd3\x9f\x1fd\xfc\xd5}\x9bk\
-@E\xb0\x16@xp,#\xcb\xb2m\x0100\x96\
-a\x8dP\x1b|\x14#%\x22\x14+\xd8\x18\x91\xd5\x95\
-s\xe7\xce\x83*\xb8\x04\xd2\x14\xb2\x0c\xd2,\x8cI\x0a\
-I\x12\xdew:\x90\xe7\x90\xb7\xa1\xd5\x82v+\x8em\
-(r\xb2\xfa8\xd6\x0a\xe3\xaf\xbcIk\xf1\xfa\xe6\x00\
-\xac\x15\xac\x15\x04\xb0F\xd8\xbd{\xe7\x16k\xeb\x86\xae\
-\x80Z\xa8V\x81\xeamQ\x8d\xaf\x04\xb5\x82\xf7\xa0\xa6\
-\x84\x01g\x055\x82\x08\xa8\x0a\x95,\xc3# \x1e\x08\
-\xc0\xf0\x1e/\x02\xde#\x12&\x15|\x88#\xc4!\x1e\
-<!^@MX\x18@\xd7J\x89\x06\xac\xa0\xdac\
-\x00\x9a3\xbf\x05\x8aS\x07i\x02\x95\x04\xb24\xf6\x04\
-\x12\x07N\xa1\xe8@^@+\x8f\xbd\x05K9\xb4s\
-\xc8\x0bT\x87q=\x00*\xe5%p1@\xd509\
-\xf9\xd2\xd6\x0a\xf3>\xd0\xaf\x16\xaa\x1b\x8b\xf6\xd8'a\
-a\xbd\x1c%% \x00\xf0\x81\x8d4M\xa3:\xc3\xb3\
-\x98\x11\x89l\x07\xdac\x09V\x98_)F\xfca\xcd\
-r\x7fa\x1d-\xd1\x80:\x09TI\x18O4/\xe0\
-\x9d\x85\xc4!\x89\xc3g\x09\x92i\xd8\x11\x89\xe2\x13\x87\
-X\x8b\xefv\x91\xbc\x80\xbc\x03\xed\x02\xdfj#\xed\x02\
-\xf2\x02\x9fwP\x1dE\xd5 x:\xebTx\x9b\x06\
-\x9c3x\x0f\x03\x8f$\xbc\xfe\xf2\xf3wh\xe86h\
-\xa4\xbe\xf1\xeb\xc6\xfc\xdf\xb1\x04R^\x82DM_\x84\
-\x8f\x0d\xa58\xe7\xb6\xc5\x88\x9e\x18K\xb9v\xb3\x03\x08\
-\x9dR\x11\xaa\x90\xb8P\xefZ\xc50}\xb1\xcb@\xc5\
-\xb0\x0e\xf4&\xadW\xf9U.\xe1\xe1\xc6\xd22\xf5\xcc\
-p}\xc9\x84-\xe9J\x19\x10\x9c\x1a\xc0s\xe5f\x97\
-+7\xbb\xacQW?\xd7\xaad~\xc5'\xa2)\xac\
-\x05\x15\xc3\x9c\x0b\xb5w\xa6l\x17\xa8\xc1\xa9 \xc8\x1a\
-5\xaf\x9b5\x1a\x8fY1\x9e\xfe{\xe9\xef\x14\x00\xf1\
-\x82\xef\x9bX0+WV\x02U!\xd1\x90\xfc\xe7S\
-\xdf\xf2\xeb\x99\x13,-\xde\xb8\xa7\xfaWj\x03<\xf5\
-\xecN\x9eya\x02\x0f\xa83[1\x10\x03|\x87\xf7\
-\xf7\xbf\xc1\xc2\xc2\x02\xb7n\xdd\xa2(\x0aD\x04k-\
-\xd6ZT\x15U\xc59\x87\xaab\xad\xc5\x98\xf0\xdf\xe5\
-\xe5e\xf2<\xef\xf7#\xcd\xf9\xb8\xf2-\x18pVP\
-\x17\x18\xdc1:\xb6rO8~\x9c\xe9\xe9i\x8c1\
-x\xef\x99\x98\x98`rr\xf2\x8eY\xd81:\xd6\xdf\
-\x86\xae\xd4\x09Up6\xac\xa2V\xaf\xf7k933\
-\xc3\xd0\xd0\x10\xd6Z\xbc\xf74\x9b\xcd\xbb\x02P\xab\xd7\
-p\xd1\x88\xb4\xd4\x88\x14\x9c\x0b'\x5c\xa0*\x00\xa8V\
-\xabdY\xd6\xa7\xb87\xdeis\x1a\xa9\x17AK\xad\
-8\x1e\xc7\xbd#\xb4\xd7\x8c1\x88D\xdf\x8f:\xb8\xab\
-\x9b\xaf5\xa8\x0d\xf3\xf6\x18.=\x8e\x83)m\xe3\xd5\
-\xdb\x12\xa9\xf7\xe5Vl\xad\xf4\x91\x0e\x8e\x0c\xc3\xf2\xef\
-\xdb\x02\xe0\xa1\x91a\xd4\xc2\xb5+\x97Y\x9c\xbf\xbe\x05\
-\x036\xf8\xc0`\xad\x02\x0b\xdb\xc3\xc0P\xad\xc2\xec\xc5\
-K\x9c\xfd\xee\x1b\xce\x9f\x9c\x9e\x03\xa66\x04`$^\
-J\x05\x12\x0b\xed\x91'\xa9=\x0co\x1f8\xc8f\xc7\
-\x81':\xf1*\xe75\x1e2\x81\x14(\xbap\xf9\xea\
-U\xce4\x8e\xd1\xfc\xfa\x8b\xb9\xd9\x1fN\x1d\x02\x0eo\
-\x08\xe0\xb3\x8f>\xe0\xa7\xd3'W\x99\xe9\xda\xa3\x86U\
-\xe6\xbb\x1e\x04\x1b<_\x1do|w\xee\x8f\xd9_\x0e\
-\x01\x87\x1b\x8d\xc6_\x1b\x01\x98\x9a\xfe\xf4\xe3\x7f\xf5s\
-l}\xf25\x00\xe2\xb7\xda\x81\xff\xdd\xd7\xf1?M\xf0\
-K\xb9\xe8F\x89\xaf\x00\x00\x00\x00IEND\xaeB\
-`\x82\
-"
-
-qt_resource_name = b"\
-\x00\x06\
-\x07\x03}\xc3\
-\x00i\
-\x00m\x00a\x00g\x00e\x00s\
-\x00\x08\
-\x06\xc1Y\x87\
-\x00o\
-\x00p\x00e\x00n\x00.\x00p\x00n\x00g\
-\x00\x07\
-\x04\xcaW\xa7\
-\x00n\
-\x00e\x00w\x00.\x00p\x00n\x00g\
-\x00\x08\
-\x06|Z\x07\
-\x00c\
-\x00o\x00p\x00y\x00.\x00p\x00n\x00g\
-\x00\x07\
-\x0a\xc7W\x87\
-\x00c\
-\x00u\x00t\x00.\x00p\x00n\x00g\
-\x00\x09\
-\x0a\xa8\xbaG\
-\x00p\
-\x00a\x00s\x00t\x00e\x00.\x00p\x00n\x00g\
-\x00\x08\
-\x08\xc8Xg\
-\x00s\
-\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
-"
-
-qt_resource_struct = b"\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
-\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\
-\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x08\x1d\
-\x00\x00\x01z\xe7\xee'\x09\
-\x00\x00\x00<\x00\x00\x00\x00\x00\x01\x00\x00\x0bu\
-\x00\x00\x01z\xe7\xee'\x09\
-\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01z\xe7\xee'\x09\
-\x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00\x1cS\
-\x00\x00\x01z\xe7\xee'\x09\
-\x00\x00\x00f\x00\x00\x00\x00\x00\x01\x00\x00\x15\xe2\
-\x00\x00\x01z\xe7\xee'\x09\
-\x00\x00\x00R\x00\x00\x00\x00\x00\x01\x00\x00\x10\xb3\
-\x00\x00\x01z\xe7\xee'\x09\
-"
-
-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/widgets/painting/basicdrawing/basicdrawing.py b/examples/widgets/painting/basicdrawing/basicdrawing.py
index c1f2a5b0c..858a8cd9f 100644
--- a/examples/widgets/painting/basicdrawing/basicdrawing.py
+++ b/examples/widgets/painting/basicdrawing/basicdrawing.py
@@ -6,11 +6,12 @@
from PySide6.QtCore import QPoint, QRect, QSize, Qt, qVersion
from PySide6.QtGui import (QBrush, QConicalGradient, QLinearGradient, QPainter,
- QPainterPath, QPalette, QPen, QPixmap, QPolygon, QRadialGradient)
+ QPainterPath, QPalette, QPen, QPixmap, QPolygon,
+ QRadialGradient)
from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout,
- QLabel, QSpinBox, QWidget)
+ QLabel, QSpinBox, QWidget)
-import basicdrawing_rc
+import basicdrawing_rc # noqa: F401
class RenderArea(QWidget):
@@ -190,12 +191,9 @@ class Window(QWidget):
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("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)
@@ -260,24 +258,23 @@ class Window(QWidget):
self.setWindowTitle("Basic Drawing")
def shape_changed(self):
- shape = self._shape_combo_box.itemData(self._shape_combo_box.currentIndex(),
- id_role)
+ shape = self._shape_combo_box.itemData(self._shape_combo_box.currentIndex(), id_role)
self._render_area.set_shape(shape)
def pen_changed(self):
width = self._pen_width_spin_box.value()
style = Qt.PenStyle(self._pen_style_combo_box.itemData(
- self._pen_style_combo_box.currentIndex(), id_role))
+ self._pen_style_combo_box.currentIndex(), id_role))
cap = Qt.PenCapStyle(self._pen_cap_combo_box.itemData(
- self._pen_cap_combo_box.currentIndex(), id_role))
+ self._pen_cap_combo_box.currentIndex(), id_role))
join = Qt.PenJoinStyle(self._pen_join_combo_box.itemData(
- self._pen_join_combo_box.currentIndex(), id_role))
+ self._pen_join_combo_box.currentIndex(), id_role))
self._render_area.set_pen(QPen(Qt.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))
+ self._brush_style_combo_box.currentIndex(), id_role))
if style == Qt.LinearGradientPattern:
linear_gradient = QLinearGradient(0, 0, 100, 100)
diff --git a/examples/widgets/painting/concentriccircles/concentriccircles.py b/examples/widgets/painting/concentriccircles/concentriccircles.py
index 0edc9cb9f..d2c60178f 100644
--- a/examples/widgets/painting/concentriccircles/concentriccircles.py
+++ b/examples/widgets/painting/concentriccircles/concentriccircles.py
@@ -2,12 +2,13 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-"""PySide6 port of the widgets/painting/concentriccircles example from Qt v5.x, originating from PyQt"""
+"""PySide6 port of the widgets/painting/concentriccircles example from Qt v5.x, originating
+ from PyQt"""
from PySide6.QtCore import QRect, QRectF, QSize, Qt, QTimer
from PySide6.QtGui import QColor, QPainter, QPalette, QPen
from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel,
- QSizePolicy, QWidget)
+ QSizePolicy, QWidget)
class CircleWidget(QWidget):
@@ -52,10 +53,10 @@ class CircleWidget(QWidget):
if self._float_based:
painter.drawEllipse(QRectF(-diameter / 2.0,
- -diameter / 2.0, diameter, diameter))
+ -diameter / 2.0, diameter, diameter))
else:
painter.drawEllipse(QRect(-diameter / 2,
- -diameter / 2, diameter, diameter))
+ -diameter / 2, diameter, diameter))
class Window(QWidget):
diff --git a/examples/widgets/painting/painter/painter.py b/examples/widgets/painting/painter/painter.py
index 58584baff..2ca078ad9 100644
--- a/examples/widgets/painting/painter/painter.py
+++ b/examples/widgets/painting/painter/painter.py
@@ -116,15 +116,17 @@ class MainWindow(QMainWindow):
self.bar = self.addToolBar("Menu")
self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self._save_action = self.bar.addAction(
- qApp.style().standardIcon(QStyle.SP_DialogSaveButton), "Save", self.on_save
+ qApp.style().standardIcon(QStyle.SP_DialogSaveButton), # noqa: F821
+ "Save", self.on_save
)
self._save_action.setShortcut(QKeySequence.Save)
self._open_action = self.bar.addAction(
- qApp.style().standardIcon(QStyle.SP_DialogOpenButton), "Open", self.on_open
+ qApp.style().standardIcon(QStyle.SP_DialogOpenButton), # noqa: F821
+ "Open", self.on_open
)
self._open_action.setShortcut(QKeySequence.Open)
self.bar.addAction(
- qApp.style().standardIcon(QStyle.SP_DialogResetButton),
+ qApp.style().standardIcon(QStyle.SP_DialogResetButton), # noqa: F821
"Clear",
self.painter_widget.clear,
)
diff --git a/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst
new file mode 100644
index 000000000..c33c6660a
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.rst
@@ -0,0 +1,34 @@
+.. _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`
+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
+does not need to worry about lower level details, such as setting up the window
+and the ``QRhi``, or dealing with swapchain and window events, as that is taken
+care of by the ``QWidget`` framework here. The instance of the ``QRhiWidget``
+subclass is added to a ``QVBoxLayout``. To keep the example minimal and
+compact, there are no further widgets or 3D content introduced.
+
+Once an instance of ``ExampleRhiWidget``, a ``QRhiWidget`` subclass, is added
+to a top-level widget's child hierarchy, the corresponding window automatically
+becomes a Direct 3D, Vulkan, Metal, or OpenGL-rendered window. The
+``QPainter``-rendered widget content, i.e. everything that is not a
+``QRhiWidget``, ``QOpenGLWidget``, or ``QQuickWidget``, is then uploaded to a
+texture, whereas the mentioned special widgets each render to a texture. The
+resulting set textures is composited together by the top-level widget's
+backingstore.
+
+As opposed to the C++ example, the cleanup is done by reimplementing
+``QRhiWidget.releaseResources()``, which is called from QWidget.closeEvent() of
+the top level widget to ensure a deterministic cleanup sequence.
+
+.. image:: simplerhiwidget.webp
+ :width: 400
+ :alt: Screenshot of the Simple RHI Widget example
diff --git a/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webp b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webp
new file mode 100644
index 000000000..fdb8d20ec
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/doc/simplerhiwidget.webp
Binary files differ
diff --git a/examples/widgets/rhi/simplerhiwidget/examplewidget.py b/examples/widgets/rhi/simplerhiwidget/examplewidget.py
new file mode 100644
index 000000000..5b3e40f50
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/examplewidget.py
@@ -0,0 +1,135 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import numpy
+
+from PySide6.QtCore import (QFile, QIODevice)
+from PySide6.QtGui import (QColor, QMatrix4x4)
+from PySide6.QtGui import (QRhiBuffer,
+ QRhiDepthStencilClearValue,
+ QRhiShaderResourceBinding,
+ QRhiShaderStage,
+ QRhiVertexInputAttribute, QRhiVertexInputBinding,
+ QRhiVertexInputLayout, QRhiViewport,
+ QShader)
+from PySide6.QtWidgets import QRhiWidget
+from PySide6.support import VoidPtr
+
+VERTEX_DATA = numpy.array([ 0.0, 0.5, 1.0, 0.0, 0.0, # noqa E:201
+ -0.5, -0.5, 0.0, 1.0, 0.0, # noqa E:241
+ 0.5, -0.5, 0.0, 0.0, 1.0],
+ dtype=numpy.float32)
+
+
+def getShader(name):
+ f = QFile(name)
+ if f.open(QIODevice.ReadOnly):
+ return QShader.fromSerialized(f.readAll())
+ return QShader()
+
+
+class ExampleRhiWidget(QRhiWidget):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.m_rhi = None
+ self.m_vbuf = None
+ self.m_ubuf = None
+ self.m_srb = None
+ self.m_pipeline = None
+ self.m_viewProjection = QMatrix4x4()
+ self.m_rotation = 0.0
+
+ def releaseResources(self):
+ self.m_pipeline.destroy()
+ del self.m_pipeline
+ self.m_pipeline = None
+ self.m_srb.destroy()
+ del self.m_srb
+ self.m_srb = None
+ self.m_ubuf.destroy()
+ del self.m_ubuf
+ self.m_ubuf = None
+ self.m_vbuf.destroy()
+ del self.m_vbuf
+ self.m_buf = None
+
+ def initialize(self, cb):
+ if self.m_rhi != self.rhi():
+ self.m_pipeline = None
+ self.m_rhi = self.rhi()
+
+ 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.create()
+
+ self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Dynamic,
+ QRhiBuffer.UniformBuffer, 64)
+ self.m_ubuf.create()
+
+ self.m_srb = self.m_rhi.newShaderResourceBindings()
+ bindings = [
+ QRhiShaderResourceBinding.uniformBuffer(0, QRhiShaderResourceBinding.VertexStage,
+ self.m_ubuf)
+ ]
+ self.m_srb.setBindings(bindings)
+ self.m_srb.create()
+
+ self.m_pipeline = self.m_rhi.newGraphicsPipeline()
+ stages = [
+ QRhiShaderStage(QRhiShaderStage.Vertex,
+ getShader(":/shader_assets/color.vert.qsb")),
+ QRhiShaderStage(QRhiShaderStage.Fragment,
+ getShader(":/shader_assets/color.frag.qsb"))
+ ]
+ self.m_pipeline.setShaderStages(stages)
+ inputLayout = QRhiVertexInputLayout()
+ 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)
+ ]
+ inputLayout.setAttributes(attributes)
+ self.m_pipeline.setVertexInputLayout(inputLayout)
+ self.m_pipeline.setShaderResourceBindings(self.m_srb)
+ self.m_pipeline.setRenderPassDescriptor(self.renderTarget().renderPassDescriptor())
+ self.m_pipeline.create()
+
+ resourceUpdates = self.m_rhi.nextResourceUpdateBatch()
+ resourceUpdates.uploadStaticBuffer(self.m_vbuf, VoidPtr(VERTEX_DATA.tobytes(),
+ vertex_size))
+ cb.resourceUpdate(resourceUpdates)
+
+ outputSize = self.renderTarget().pixelSize()
+ self.m_viewProjection = self.m_rhi.clipSpaceCorrMatrix()
+ r = float(outputSize.width()) / float(outputSize.height())
+ self.m_viewProjection.perspective(45.0, r, 0.01, 1000.0)
+ self.m_viewProjection.translate(0, 0, -4)
+
+ def render(self, cb):
+ resourceUpdates = self.m_rhi.nextResourceUpdateBatch()
+ self.m_rotation += 1.0
+ modelViewProjection = self.m_viewProjection
+ modelViewProjection.rotate(self.m_rotation, 0, 1, 0)
+ projection = numpy.array(modelViewProjection.data(),
+ dtype=numpy.float32)
+ resourceUpdates.updateDynamicBuffer(self.m_ubuf, 0, 64,
+ projection.tobytes())
+ clearColor = QColor.fromRgbF(0.4, 0.7, 0.0, 1.0)
+ cv = QRhiDepthStencilClearValue(1.0, 0)
+ cb.beginPass(self.renderTarget(), clearColor, cv, resourceUpdates)
+
+ cb.setGraphicsPipeline(self.m_pipeline)
+ outputSize = self.renderTarget().pixelSize()
+ cb.setViewport(QRhiViewport(0, 0, outputSize.width(),
+ outputSize.height()))
+ cb.setShaderResources()
+ vbufBinding = (self.m_vbuf, 0)
+ cb.setVertexInput(0, [vbufBinding])
+ cb.draw(3)
+ cb.endPass()
+
+ self.update()
diff --git a/examples/widgets/rhi/simplerhiwidget/main.py b/examples/widgets/rhi/simplerhiwidget/main.py
new file mode 100644
index 000000000..59be81ddc
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/main.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 port of the Qt Simple RHI Widget Example example from Qt v6.x"""
+
+import sys
+
+from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget
+
+from examplewidget import ExampleRhiWidget
+import rc_simplerhiwidget # noqa F:401
+
+
+class Widget(QWidget):
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ layout = QVBoxLayout(self)
+ self._rhi_widget = ExampleRhiWidget(self)
+ layout.addWidget(self._rhi_widget)
+
+ def closeEvent(self, e):
+ self._rhi_widget.releaseResources()
+ e.accept()
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+
+ w = Widget()
+ w.resize(1280, 720)
+ w.show()
+ exit_code = app.exec()
+ del w
+ sys.exit(exit_code)
diff --git a/examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py b/examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py
new file mode 100644
index 000000000..ee8b41242
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/rc_simplerhiwidget.py
@@ -0,0 +1,163 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.7.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x02\xe2\
+\x00\
+\x00\x07\xc4x\x9c\xb5T]k\xd4@\x14\x9d\xed\xa6]\
+\x9b\xd6\xda\x0f\xd07\x19\xe9\xcb\x16%\xa6\xb5\x15q\xdd\
+\xfa\xb0U)\x14Z\xdbR\x84e\x091\x9bM\x07\xb2\
+\xc9\x92\x8fE)\x05\xdf}\xf7w\xf8'\xfcO\xbe\x88\
+\xde;s\xd3\xcc\xa6[*\x88\x03\xb3\x999s\xef=\
+g\xceM\x9616\xcf\x183`\xd6`.\xb11s\
+\x98\xc7b\x16\xc2L\x00\xa9\xc3d\xbfi\xb0)\x03\xd3\
+V\xd9\x00\x82]\x16\xb0\x8e\x96j\xdc\x96\xfa\xafc\x8e\
+\x9e}M\xcd\xf7\xfa\xccz\x03U\x99\xec\x0e[#t\
+\x85\xcdH\x9d\x0b\x90\x82\xcfw\x07'\x07V\x9a\xf5\xad\
+\xed\x1d\x1b\xcf\x97\xd45\xe5\xd9=\xd6\x90\xcaq\x0e]\
+\x11!\x8e\x16-\xc2\x5c\x868\xc4\x1bX\x03V\x88?\
+$\x0e\xdc/j\xfb5\xe0\x9c\x81\xe7#\xc8\xc1\xea\xb8\
+\xbe\x0f+\x14\xcda>\x80\xf8\x06]\xc2\x90\x98\x01\x8a\
+\x95\xe1\x88\xb7h?O\x18\xc6\x9b\x14_\xa7\xf8\x05\xd2\
+lR\xfc\x02\xe9D\xec1\xec\xe7\xe4\xddp|y\xfd\
+\x9c\xcdJ\x0d\x06\xe9\xc3\x1a?\x01\x99\x85g\x9bj\xdf\
+\xa5\xfc\xf7\x80\xceI/\x14\xc64l\x99\xb0\x9a\x86\xad\
+\x10\x86\xf5\x8f@=\xea_\xa5\xfce:G\x1d\xbb\xc0\
+:Og\xbf\xa0\xc2\x0bY\x85\x19\xe4s\x8dzY#\
+\xca\xaf\xebc?IE\x1c\xf1M\xdb6G\x89\xef\x09\
+\xb9;\x17\xc1\xf9\x88\x0f\xc2\xd8\xcdZ\xd7`\x11\x01h\
+\x8e\xdd\xe4\xb3\x88\x02>\xf6\xbdg|\xecxq\x18'\
+\x08\xc7\xa2\xcf\x91\xad\xb9a^\x98\x1cF\x10:o\x13\
+7\xd8s3\xb7k\xf7x\x1b3\xb6\x9b\x94\xf1\x84o\
+Z\xf6F\xcb\xbc4\xcd\x8a\xccO\xe5+\xc7\x9cR\xe6\
+\x96\xfd\xff\xa9\xbfi\xd4\xa2\xa4\xde\x01\xea8\xcfd\x11\
+>\x80\xba\x1dE,\xa2\xdb\x95\x5c\x85\xff\x95\x08\xec\xf2\
+V\xa9\xa1\xf6#\xcd\xdcLx\xaa!\x13\xdc\xfa\x81\xae\
+ \xcd\x92\xdc\xcb\xf8\xc9\xd1\xfe\xf1\x99\xd3I\xe24u\
+\xf6\xa3Q\x9e\x15r&\xe2\xf9K~\xfa\xe6C\xe7\xf0\
+\xf0x\xcf\x06)\xd3\xd3\x0f\xf3\xac\x92\xaf\x09\x81\x0a'\
+g\xce\xa9\x9b\x04~F%\xa4\x07\x18\xe0\xdch\x84\xaa\
+2a\xc5@yq\x9dX\xd9y\xed>\x1c\x0c\x08|\
+G\xe0\xba\xa0(n\xd5\xd6\x0f\xad+o\x0a\x15\xa4K\
+\x01S\x08Ur,7*HG\xac\x89\x8b\x94\x0d\xc1\
+\xb8\xc4\xcf\xf2$\xaa\x14\xb8\xd4\x1bl\xd0\xdf@\xd1\xe0\
+\xfdu\x11ya\xde\xf7\xf9\xab\xa1\x9f\xb9\xa1\x03\x7f\x9d\
+\xa1\xf8\xb8k\x96x*\x86\xfd\xa7\xf8c\x9d\xef\x9af\
+\x9e\xe2\x07\x10\xb9C?\x1d\xb9\x9e\xcfeV\xd98\xa4\
+\xb1\x91\xfa\xa6~u\xbb\xd2\x8d\xa6\xbd\xd1\xebM\xb4\x5c\
+e\x8ah\xfa\x8b\xd2\xed\xe6\xa9\x9f4\xc3\xd8\x8b\xcaL\
+\xac:\xf4#\x8dU\xad\x9aE-\xf8\xc7\x80\xcc\xa2\x19\
+\xbd^\xd1\xa82\x1eg\x9b_\x5c*\xff\xe2\xaa\xbd\xea\
+=\x11\x91U\xf9j4\xb3c\xe51\x9a<+\xeb\xd2\
+gT\xf9\x94\xe4\xa8\xb8_\xb6\xe1\xa6\xb3?Y\xbd\xa1\
+\xc8\
+\x00\x00\x04C\
+\x00\
+\x00\x0c\xf4x\x9c\xddW[o\x1bE\x14\x1e_\x92\x92\
+-\xb9\xb4i\x93\x16\xda2\xa9%dCe\x9c4A\
+\xa8&\x11\xa8 Z\xa9R\xaa\x06UHV\xb4Z\xaf\
+\xd7\xe9\xc2^\xac\xddY7\xa8\xf2\x8f\xe0\x85\xbf\xc3o\
+\xe0o\xf0\xc8\x13/U9g\xe6\xcc\xee\xec\xda\xa1*\
+\x0f<0\xd2d\xe7\x5c\xe6|\xe7|sf\x920\xc6\
+V\x98\x1au\x98\x16sY\xcc\x02\x98\x09H\x0d\x98\xb5\
+74\xd8\x82\xb1\xc1&\xe0\x9a2\x9f\x09\x981\x8b@\
+\xd7D\xc3?m\xaa\xc1\x5ccSf\xcfa\xbdu\xdb\
+2\x1b\xb2\x8c\x8d\xe5\xcaf{\xec\x00V_\xcd\xf9\x84\
+\x10|\x02\xab\xcb\xa45=6\x16\x85~\x97Q\xa3o\
+\x93\xbe\xcb\xf4\x1d\x15.\x8d\x17\x8dz\xeb\x12\xbaZ\xec\
+=\xd6\x22\xed\x15 \xb8&\xb3Z\x96\xdf\xef\x9e\x9c<\
+\xe9\xa6b\xd4\xdd?\xe8\xa1}MQ m\xebt(\
+\x08\x12:>\xd2*5X\xd1&\xcc;\xb8\x1f\xac+\
+\xb4f$_&\x19c<bK\x80\xa9\x86fB\xeb\
+j\xa43\xfd\xea\xa4k\xc8X\x8d\x5c\xf7\x08\xe2n\xd3\
+\xfe%\xf2\xd7r\xcb\x88\xabuP\xb8d\x19clS\
+\x0c\xcc\xed&|\xef\x1a\xb9\xa2\xbcc\xc8w\x8cZ6\
+aW]\xda\x1b2\x1f\x5co\xc1\x0a\xc9\xe60\xb7\xc1\
+\xff\x12\x91\xdf\x90\xba&0\xad\xd6\xa8\xef\x93\xbcb\xd8\
+-\xaaW\xdb-\x83\x03\x8c\xb7J\xf1\x90\xf3\xeb\xf0s\
+\x8d\xb0p|J\xf2:\xf9\xdf\x02y\x83\xfc\xd7e\xde\
+\x8aC\x1dc\x830\xaf\x12\xfe\x15\xc2\xbcJ\xe7\xd7 \
+\x8ck\x84Q#\x8ckR\xaf\xc6\x0d\x90\xb7(fS\
+b(>\xb7(\xf6\x0d\xe2e\x9bb\xa3|\x93th\
+\xff \xe7M\xc9\xb7\x09g\x95\xfco\xb3\xa2W\xd0\xbe\
+Cy\xa1\xfds\xa8\xa7\xce\x8a&G\xfd_\xa0\xc1\xf3\
+?$\xee\xdf'\xfe\x8e\xc0\xbaB\xf2\xd7\xe0\x81\xb8\x1f\
+R.\xd7\xc9\x7fKr\xa6\xf4\x87\xc4\xf5G\x84\xff\x0b\
+\xecY%\x1en\x91\x1e\xe3\xecP\xbflR\x1c\xc4\xb9\
+K~\xaf!\xeb/\xd4Ul\xd2\xfd@a\xc4\x8a\xfb\
+\xf9{k\xea%\xa9\x1fG|\xb7\xd7\xb3\xacT$\x99\
++\xf80\x1b[\xaf,\x0e#t\xc4>\x0f\xa7\x93\xbe\
+5\xeb[V\x16\xf9\xe38\x09\xd1\xce\xed\xbd\x03\xd0L\
+\x9d\xe4g?:\xe3S\xcf\xbd\xcf\xa7\xb6\x1b\x07q\xd2\
+\xb7\x1c!\x12\x7f\x98\x09O\xe9\x17i\xf7\xf9$N}\
+\x01\xc8\x18%\xf6G\x1c3lw\x08\x97B\xf1C\xbd\
+\x19\x95g\x81\xfd\x946\x81\x01\x12\xe8Bf\xfc\x13#\
+\xd2\xcc\xb2*\xc5\x9e\xb3b\x18\xc5\xee\xfd/\x8b\xfd\xd5\
+(\xf6\xb7\xa2\xd8\x83\x7fSl\x9c\x89J\xa1~T\xaa\
+P\x89\xffEix\xc7\xf6\x8a\xca\xea\x7f\xb8\x90\xe6\xd8\
+Kd\xb6\x0fx\xe2\x9d\xf9\xa9\xf0\x92\xf6\xb0\xa7a\x93\
+\xf8\xa5\x1d:?\x02\xf08\x88\xa1\xd0\xf3}\x84\xb1\x11\
+\xe6\x01\x9f8\xeeO\xf1x\x9cz\xa2\xed\xf6:\xaa~\
+\xe0\xc7\x11\xbeK\xeef\x86\xfd\x92\xc9\xa0\xa3\xac^\xa0\
+,QC\xfc\x9f<}\xfc\xec\xb9\xfd0\x89\xd3\xd4~\
+\x1cM2A\x09W6@\x96\xdf\x7f\xfb\xc3\xc3\xe3\xe3\
+g\xdf\xf4\xfa\x85\x03\xc1\x18\xd6]\x95\xfe\x82\xe8\xc7\x99\
+\xa8\x84\xcfs\xbf z\xa9l\xf09yn\x900\xd3\
+\xc7\x0b]%\xecw=\xe30\x0b\xda\xba\xb6{\xfa(\
+:\xf2\x9c\xe7sV\x1d4\xc7\x14\x07n\xcf<\xdb\xc7\
+\xb5\x86\xd6\xc0\x86\xa9k$1)20=\x8ac\x91\
+\xe9\x17\x05)\xc5\x82\x8c\xd4\xeeX\x0a\xca\xc9\xd4t\xcb\
+\xc5\x96\x9ag\xce\xb7`+\xef$\xd9\xb1\x9e\xc8\x92\xa8\
+\x0243oA\x93~{\xd0\xa8\xfd\xd9\xf2#7\xc8\
+F\x1e\xff2\xf4\x84\x13\xd8\xf0WR\xe0\x0f\x8f\xacB\
+\x9f\xfa\xe1\xe83\xfc\xd1}q\x04w<\xc5\xc7+r\
+B/\x85+\xe0q\xb9\xab\xbf\xe0i\xc8\xefL\xfe<\
+\x90\x0bf\xd2\xc3\xec\x16\xb7\xd5`\x90\xa5p\x0d\x83\xd8\
+\x8dz\x9d\xd3\xd3\x0b[k0\xd0'\x80Ns\xf1\xfd\
+\xe8\x82K1\x18\xe4\xafj\xbb\x02\xa0o\x86\xe9\xb2\xdb\
+\xd1\xe1\xf1\x88\xbd\xf3\x22}\xb5jk8\xeecl\xdd\
+!\xa7\xa7\xf7 X\x04b$I\xf9\x18\xfb\x15\xec\xea\
+\xcd\x91\xc0\x9d\xfc\x0d\xd5\x01q\x1e\xf2W3\x95R\x5c\
+:g?2{2\x9e\xeb\x97\xe2\x01\x04\xcfroR\
+W\xc4\xaa\x19\xb0\x1b\x96$&=\x8a\x95\x871\xff\xad\
+\xce\xe8\xbf\x83J\xcb\xcc\xd9\xb5\xae\xea'\xc7\xdf%\xe1\
+\xb4\xa2\
+"
+
+qt_resource_name = b"\
+\x00\x0d\
+\x06E\xc5\xd3\
+\x00s\
+\x00h\x00a\x00d\x00e\x00r\x00_\x00a\x00s\x00s\x00e\x00t\x00s\
+\x00\x0e\
+\x04\x16\xeb\xb2\
+\x00c\
+\x00o\x00l\x00o\x00r\x00.\x00f\x00r\x00a\x00g\x00.\x00q\x00s\x00b\
+\x00\x0e\
+\x00\xfb\xe9\x92\
+\x00c\
+\x00o\x00l\x00o\x00r\x00.\x00v\x00e\x00r\x00t\x00.\x00q\x00s\x00b\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00B\x00\x00\x00\x00\x00\x01\x00\x00\x02\xe6\
+\x00\x00\x01\x8a!\x0c\xa5\xeb\
+\x00\x00\x00 \x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x8a!\x0c\xa5\xeb\
+"
+
+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/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb
new file mode 100644
index 000000000..32bd2d595
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.frag.qsb
Binary files differ
diff --git a/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb
new file mode 100644
index 000000000..bf97035d7
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/shader_assets/color.vert.qsb
Binary files differ
diff --git a/examples/widgets/rhi/simplerhiwidget/shaders/color.frag b/examples/widgets/rhi/simplerhiwidget/shaders/color.frag
new file mode 100644
index 000000000..375587662
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/shaders/color.frag
@@ -0,0 +1,10 @@
+#version 440
+
+layout(location = 0) in vec3 v_color;
+
+layout(location = 0) out vec4 fragColor;
+
+void main()
+{
+ fragColor = vec4(v_color, 1.0);
+}
diff --git a/examples/widgets/rhi/simplerhiwidget/shaders/color.vert b/examples/widgets/rhi/simplerhiwidget/shaders/color.vert
new file mode 100644
index 000000000..e876f290e
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/shaders/color.vert
@@ -0,0 +1,16 @@
+#version 440
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec3 color;
+
+layout(location = 0) out vec3 v_color;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+};
+
+void main()
+{
+ v_color = color;
+ gl_Position = mvp * position;
+}
diff --git a/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject
new file mode 100644
index 000000000..ff0d62755
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.pyproject
@@ -0,0 +1,4 @@
+{
+ "files": ["main.py","examplewidget.py", "simplerhiwidget.qrc",
+ "shaders/color.frag", "shaders/color.vert"]
+}
diff --git a/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc
new file mode 100644
index 000000000..ddc6dfbe5
--- /dev/null
+++ b/examples/widgets/rhi/simplerhiwidget/simplerhiwidget.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/">
+ <file>shader_assets/color.vert.qsb</file>
+ <file>shader_assets/color.frag.qsb</file>
+</qresource>
+</RCC>
diff --git a/examples/widgets/richtext/orderform/orderform.py b/examples/widgets/richtext/orderform/orderform.py
index fd0be01fe..9725624c3 100644
--- a/examples/widgets/richtext/orderform/orderform.py
+++ b/examples/widgets/richtext/orderform/orderform.py
@@ -89,8 +89,7 @@ class MainWindow(QMainWindow):
body_frame_format.setWidth(QTextLength(QTextLength.PercentageLength, 100))
cursor.insertFrame(body_frame_format)
- cursor.insertText("I would like to place an order for the following "
- "items:", text_format)
+ cursor.insertText("I would like to place an order for the following items:", text_format)
cursor.insertBlock()
cursor.insertBlock()
@@ -121,17 +120,17 @@ class MainWindow(QMainWindow):
cursor.insertBlock()
cursor.insertText("Please update my records to take account of the "
- "following privacy information:")
+ "following privacy information:")
cursor.insertBlock()
offers_table = cursor.insertTable(2, 2)
cursor = offers_table.cellAt(0, 1).firstCursorPosition()
cursor.insertText("I want to receive more information about your "
- "company's products and special offers.", text_format)
+ "company's products and special offers.", text_format)
cursor = offers_table.cellAt(1, 1).firstCursorPosition()
cursor.insertText("I do not want to receive any promotional "
- "information from your company.", text_format)
+ "information from your company.", text_format)
if sendOffers:
cursor = offers_table.cellAt(0, 0).firstCursorPosition()
@@ -153,8 +152,8 @@ class MainWindow(QMainWindow):
def create_sample(self):
dialog = DetailsDialog('Dialog with default values', self)
self.create_letter('Mr Smith',
- '12 High Street\nSmall Town\nThis country',
- dialog.order_items(), True)
+ '12 High Street\nSmall Town\nThis country',
+ dialog.order_items(), True)
@Slot()
def open_dialog(self):
@@ -162,7 +161,7 @@ class MainWindow(QMainWindow):
if dialog.exec() == QDialog.Accepted:
self.create_letter(dialog.sender_name(), dialog.sender_address(),
- dialog.order_items(), dialog.send_offers())
+ dialog.order_items(), dialog.send_offers())
@Slot()
def print_file(self):
@@ -193,8 +192,7 @@ class DetailsDialog(QDialog):
self._name_edit = QLineEdit()
self._address_edit = QTextEdit()
- self._offers_check_box = QCheckBox("Send information about "
- "products and special offers:")
+ self._offers_check_box = QCheckBox("Send information about products and special offers:")
self.setup_items_table()
@@ -250,9 +248,9 @@ class DetailsDialog(QDialog):
return
answer = QMessageBox.warning(self, "Incomplete Form",
- "The form does not contain all the necessary information.\n"
- "Do you want to discard it?",
- QMessageBox.Yes, QMessageBox.No)
+ "The form does not contain all the necessary information.\n"
+ "Do you want to discard it?",
+ QMessageBox.Yes, QMessageBox.No)
if answer == QMessageBox.Yes:
self.reject()
diff --git a/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py b/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py
index c329eefd6..021328977 100644
--- a/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py
+++ b/examples/widgets/richtext/syntaxhighlighter/syntaxhighlighter.py
@@ -12,7 +12,7 @@ from PySide6.QtCore import (QFile, Qt, QTextStream)
from PySide6.QtGui import (QColor, QFont, QFontDatabase, QKeySequence,
QSyntaxHighlighter, QTextCharFormat)
from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow,
- QPlainTextEdit)
+ QPlainTextEdit)
class MainWindow(QMainWindow):
@@ -81,7 +81,7 @@ class MainWindow(QMainWindow):
quit_act.triggered.connect(self.close)
help_menu = self.menuBar().addMenu("&Help")
- help_menu.addAction("About &Qt", qApp.aboutQt)
+ help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821
class Highlighter(QSyntaxHighlighter):
diff --git a/examples/widgets/richtext/textedit/main.py b/examples/widgets/richtext/textedit/main.py
index 88b679edc..330ea5fc5 100644
--- a/examples/widgets/richtext/textedit/main.py
+++ b/examples/widgets/richtext/textedit/main.py
@@ -11,7 +11,7 @@ from PySide6.QtWidgets import QApplication
from textedit import TextEdit
-import textedit_rc
+import textedit_rc # noqa: F401
if __name__ == '__main__':
diff --git a/examples/widgets/richtext/textedit/textedit.py b/examples/widgets/richtext/textedit/textedit.py
index 4f4146d34..8019446f0 100644
--- a/examples/widgets/richtext/textedit/textedit.py
+++ b/examples/widgets/richtext/textedit/textedit.py
@@ -55,7 +55,7 @@ class TextEdit(QMainWindow):
help_menu = self.menuBar().addMenu("Help")
help_menu.addAction("About", self.about)
- help_menu.addAction("About &Qt", qApp.aboutQt)
+ help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821
text_font = QFont("Helvetica")
text_font.setStyleHint(QFont.SansSerif)
@@ -101,13 +101,14 @@ class TextEdit(QMainWindow):
tb = self.addToolBar("File self.actions")
menu = self.menuBar().addMenu("&File")
- icon = QIcon.fromTheme("document-new", QIcon(RSRC_PATH + "/filenew.png"))
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew,
+ QIcon(RSRC_PATH + "/filenew.png"))
a = menu.addAction(icon, "&New", self.file_new)
tb.addAction(a)
a.setPriority(QAction.LowPriority)
a.setShortcut(QKeySequence.New)
- icon = QIcon.fromTheme("document-open",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen,
QIcon(RSRC_PATH + "/fileopen.png"))
a = menu.addAction(icon, "&Open...", self.file_open)
a.setShortcut(QKeySequence.Open)
@@ -115,7 +116,7 @@ class TextEdit(QMainWindow):
menu.addSeparator()
- icon = QIcon.fromTheme("document-save",
+ 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)
@@ -126,7 +127,7 @@ class TextEdit(QMainWindow):
a.setPriority(QAction.LowPriority)
menu.addSeparator()
- icon = QIcon.fromTheme("document-print",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentPrint,
QIcon(RSRC_PATH + "/fileprint.png"))
a = menu.addAction(icon, "&Print...", self.file_print)
a.setPriority(QAction.LowPriority)
@@ -151,32 +152,36 @@ class TextEdit(QMainWindow):
tb = self.addToolBar("Edit self.actions")
menu = self.menuBar().addMenu("&Edit")
- icon = QIcon.fromTheme("edit-undo",
+ 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)
tb.addAction(self._action_undo)
- icon = QIcon.fromTheme("edit-redo", QIcon(RSRC_PATH + "/editredo.png"))
+ 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)
tb.addAction(self._action_redo)
menu.addSeparator()
- icon = QIcon.fromTheme("edit-cut", QIcon(RSRC_PATH + "/editcut.png"))
+ 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)
tb.addAction(self._action_cut)
- icon = QIcon.fromTheme("edit-copy", QIcon(RSRC_PATH + "/editcopy.png"))
+ 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)
tb.addAction(self._action_copy)
- icon = QIcon.fromTheme("edit-paste", QIcon(RSRC_PATH + "/editpaste.png"))
+ 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)
@@ -190,7 +195,7 @@ class TextEdit(QMainWindow):
tb = self.addToolBar("Format self.actions")
menu = self.menuBar().addMenu("F&ormat")
- icon = QIcon.fromTheme("format-text-bold",
+ 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)
@@ -201,7 +206,7 @@ class TextEdit(QMainWindow):
tb.addAction(self._action_text_bold)
self._action_text_bold.setCheckable(True)
- icon = QIcon.fromTheme("format-text-italic",
+ 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)
@@ -212,7 +217,7 @@ class TextEdit(QMainWindow):
tb.addAction(self._action_text_italic)
self._action_text_italic.setCheckable(True)
- icon = QIcon.fromTheme("format-text-underline",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatTextUnderline,
QIcon(RSRC_PATH + "/textunder.png"))
self._action_text_underline = menu.addAction(icon, "&Underline",
self.text_underline)
@@ -226,36 +231,36 @@ class TextEdit(QMainWindow):
menu.addSeparator()
- icon = QIcon.fromTheme("format-justify-left",
+ 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.setCheckable(True)
self._action_align_left.setPriority(QAction.LowPriority)
- icon = QIcon.fromTheme("format-justify-center",
+ 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.setCheckable(True)
self._action_align_center.setPriority(QAction.LowPriority)
- icon = QIcon.fromTheme("format-justify-right",
+ 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.setCheckable(True)
self._action_align_right.setPriority(QAction.LowPriority)
- icon = QIcon.fromTheme("format-justify-fill",
+ 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.setCheckable(True)
self._action_align_justify.setPriority(QAction.LowPriority)
- icon = QIcon.fromTheme("format-indent-more",
+ 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)
- icon = QIcon.fromTheme("format-indent-less",
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.FormatIndentLess,
QIcon(RSRC_PATH + "/format-indent-less.png"))
self._action_indent_less = menu.addAction(icon, "&Unindent",
self.unindent)
@@ -621,7 +626,7 @@ class TextEdit(QMainWindow):
above = QTextCursor(cursor)
above.movePosition(QTextCursor.Up)
if (above.currentList()
- and list_fmt.indent() + amount == above.currentList().format().indent()):
+ and list_fmt.indent() + amount == above.currentList().format().indent()):
above.currentList().add(cursor.block())
else:
list_fmt.setIndent(list_fmt.indent() + amount)
diff --git a/examples/widgets/tools/regularexpression/regularexpressiondialog.py b/examples/widgets/tools/regularexpression/regularexpressiondialog.py
index 4fc9c62a2..2d2bb2bb7 100644
--- a/examples/widgets/tools/regularexpression/regularexpressiondialog.py
+++ b/examples/widgets/tools/regularexpression/regularexpressiondialog.py
@@ -5,12 +5,14 @@ import re
import logging
from PySide6.QtCore import (QMargins, QRegularExpression, QRegularExpressionMatch,
- QRegularExpressionMatchIterator, Qt, Slot,)
+ QRegularExpressionMatchIterator, Qt, Slot,)
from PySide6.QtGui import (QAction, QColor, QContextMenuEvent, QFontDatabase,
- QGuiApplication, QIcon, QPalette,)
+ QGuiApplication, QIcon, QPalette,)
from PySide6.QtWidgets import (QCheckBox, QComboBox, QDialog, QFormLayout,
- QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QPlainTextEdit,
- QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget,)
+ QFrame, QGridLayout, QHBoxLayout, QLabel,
+ QLineEdit, QPlainTextEdit, QSpinBox,
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout,
+ QWidget)
def rawStringLiteral(pattern: str) -> str:
@@ -42,7 +44,7 @@ def codeToPattern(code: str) -> str:
if code_characters[index] == '\\':
del code_characters[index]
code_characters_size -= 1
- index +=1
+ index += 1
code = "".join(code_characters)
if code.startswith('"') and code.endswith('"'):
@@ -85,7 +87,7 @@ class PatternLineEdit(QLineEdit):
t = (
t[: selection_start]
+ escapedSelection
- + t[selection_start + len(selection) :]
+ + t[selection_start + len(selection):]
)
self.setText(t)
@@ -327,23 +329,20 @@ class RegularExpressionDialog(QDialog):
self.patternOptionsCheckBoxLayout = QGridLayout()
gridRow = 0
- self.patternOptionsCheckBoxLayout.addWidget(self.caseInsensitiveOptionCheckBox, gridRow, \
- 1)
- self.patternOptionsCheckBoxLayout.addWidget(self.dotMatchesEverythingOptionCheckBox, gridRow\
- ,2)
+ self.patternOptionsCheckBoxLayout.addWidget(self.caseInsensitiveOptionCheckBox, gridRow, 1)
+ self.patternOptionsCheckBoxLayout.addWidget(
+ self.dotMatchesEverythingOptionCheckBox, gridRow, 2)
gridRow = gridRow + 1
- self.patternOptionsCheckBoxLayout.addWidget(self.multilineOptionCheckBox, gridRow, \
- 1)
- self.patternOptionsCheckBoxLayout.addWidget(self.extendedPatternSyntaxOptionCheckBox, gridRow \
- , 2)
+ self.patternOptionsCheckBoxLayout.addWidget(self.multilineOptionCheckBox, gridRow, 1)
+ self.patternOptionsCheckBoxLayout.addWidget(
+ self.extendedPatternSyntaxOptionCheckBox, gridRow, 2)
gridRow = gridRow + 1
- self.patternOptionsCheckBoxLayout.addWidget(self.invertedGreedinessOptionCheckBox, gridRow,\
- 1)
- self.patternOptionsCheckBoxLayout.addWidget(self.dontCaptureOptionCheckBox, gridRow,\
- 2)
+ self.patternOptionsCheckBoxLayout.addWidget(
+ self.invertedGreedinessOptionCheckBox, gridRow, 1)
+ self.patternOptionsCheckBoxLayout.addWidget(self.dontCaptureOptionCheckBox, gridRow, 2)
gridRow = gridRow + 1
- self.patternOptionsCheckBoxLayout.addWidget(self.useUnicodePropertiesOptionCheckBox, gridRow,\
- 1)
+ self.patternOptionsCheckBoxLayout.addWidget(
+ self.useUnicodePropertiesOptionCheckBox, gridRow, 1)
form_layout.addRow("Pattern options:", self.patternOptionsCheckBoxLayout)
diff --git a/examples/widgets/tutorials/addressbook/part2.py b/examples/widgets/tutorials/addressbook/part2.py
index 89c813006..3c0eb451d 100644
--- a/examples/widgets/tutorials/addressbook/part2.py
+++ b/examples/widgets/tutorials/addressbook/part2.py
@@ -102,17 +102,16 @@ class AddressBook(QWidget):
address = self._address_text.toPlainText()
if name == "" or address == "":
- QMessageBox.information(self, "Empty Field",
- "Please enter a name and address.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
return
if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
+ f'"{name}" has been added to your address book.')
else:
QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
if not self.contacts:
diff --git a/examples/widgets/tutorials/addressbook/part3.py b/examples/widgets/tutorials/addressbook/part3.py
index 571a96a48..611796f5e 100644
--- a/examples/widgets/tutorials/addressbook/part3.py
+++ b/examples/widgets/tutorials/addressbook/part3.py
@@ -116,17 +116,16 @@ class AddressBook(QWidget):
address = self._address_text.toPlainText()
if name == "" or address == "":
- QMessageBox.information(self, "Empty Field",
- "Please enter a name and address.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
return
if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
+ f'"{name}" has been added to your address book.')
else:
QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
if not self.contacts:
diff --git a/examples/widgets/tutorials/addressbook/part4.py b/examples/widgets/tutorials/addressbook/part4.py
index 505fe4db9..95f31d46c 100644
--- a/examples/widgets/tutorials/addressbook/part4.py
+++ b/examples/widgets/tutorials/addressbook/part4.py
@@ -128,34 +128,34 @@ class AddressBook(QWidget):
address = self._address_text.toPlainText()
if name == "" or address == "":
- QMessageBox.information(self, "Empty Field",
- "Please enter a name and address.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
return
if self._current_mode == self.AddingMode:
if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
+ f'"{name}" has been added to your address book.')
else:
QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._current_mode == self.EditingMode:
if self._old_name != name:
if name not in self.contacts:
QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your address book.')
+ f'"{self.oldName}" has been edited in your '
+ 'address book.')
del self.contacts[self._old_name]
self.contacts[name] = address
else:
QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._old_address != address:
QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
+ f'"{name}" has been edited in your address book.')
self.contacts[name] = address
self.update_interface(self.NavigationMode)
@@ -169,19 +169,18 @@ class AddressBook(QWidget):
@Slot()
def remove_contact(self):
name = self._name_line.text()
- address = self._address_text.toPlainText()
if name in self.contacts:
button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
+ f'Are you sure you want to remove "{name}"?',
+ QMessageBox.Yes | QMessageBox.No)
if button == QMessageBox.Yes:
self.previous()
del self.contacts[name]
QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
+ f'"{name}" has been removed from your address book.')
self.update_interface(self.NavigationMode)
diff --git a/examples/widgets/tutorials/addressbook/part5.py b/examples/widgets/tutorials/addressbook/part5.py
index 72245703f..1e9c05862 100644
--- a/examples/widgets/tutorials/addressbook/part5.py
+++ b/examples/widgets/tutorials/addressbook/part5.py
@@ -134,34 +134,34 @@ class AddressBook(QWidget):
address = self._address_text.toPlainText()
if name == "" or address == "":
- QMessageBox.information(self, "Empty Field",
- "Please enter a name and address.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
return
if self._current_mode == self.AddingMode:
if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
+ f'"{name}" has been added to your address book.')
else:
QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._current_mode == self.EditingMode:
if self._old_name != name:
if name not in self.contacts:
QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your address book.')
+ f'"{self.oldName}" has been edited in your '
+ 'address book.')
del self.contacts[self._old_name]
self.contacts[name] = address
else:
QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._old_address != address:
QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
+ f'"{name}" has been edited in your address book.')
self.contacts[name] = address
self.update_interface(self.NavigationMode)
@@ -175,19 +175,18 @@ class AddressBook(QWidget):
@Slot()
def remove_contact(self):
name = self._name_line.text()
- address = self._address_text.toPlainText()
if name in self.contacts:
button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
+ f'Are you sure you want to remove "{name}"?',
+ QMessageBox.Yes | QMessageBox.No)
if button == QMessageBox.Yes:
self.previous()
del self.contacts[name]
QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
+ f'"{name}" has been removed from your address book.')
self.update_interface(self.NavigationMode)
@@ -243,7 +242,7 @@ class AddressBook(QWidget):
self._address_text.setText(self.contacts[contact_name])
else:
QMessageBox.information(self, "Contact Not Found",
- f'Sorry, "{contact_name}" is not in your address book.')
+ f'Sorry, "{contact_name}" is not in your address book.')
return
self.update_interface(self.NavigationMode)
@@ -311,8 +310,7 @@ class FindDialog(QDialog):
text = self._line_edit.text()
if not text:
- QMessageBox.information(self, "Empty Field",
- "Please enter a name.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name.")
return
else:
self._find_text = text
diff --git a/examples/widgets/tutorials/addressbook/part6.py b/examples/widgets/tutorials/addressbook/part6.py
index f75fbf44f..d11298fb9 100644
--- a/examples/widgets/tutorials/addressbook/part6.py
+++ b/examples/widgets/tutorials/addressbook/part6.py
@@ -145,34 +145,34 @@ class AddressBook(QWidget):
address = self._address_text.toPlainText()
if name == "" or address == "":
- QMessageBox.information(self, "Empty Field",
- "Please enter a name and address.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
return
if self._current_mode == self.AddingMode:
if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
+ f'"{name}" has been added to your address book.')
else:
QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._current_mode == self.EditingMode:
if self._old_name != name:
if name not in self.contacts:
QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your address book.')
+ f'"{self.oldName}" has been edited in your '
+ 'address book.')
del self.contacts[self._old_name]
self.contacts[name] = address
else:
QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._old_address != address:
QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
+ f'"{name}" has been edited in your address book.')
self.contacts[name] = address
self.update_interface(self.NavigationMode)
@@ -186,19 +186,18 @@ class AddressBook(QWidget):
@Slot()
def remove_contact(self):
name = self._name_line.text()
- address = self._address_text.toPlainText()
if name in self.contacts:
button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
+ f'Are you sure you want to remove "{name}"?',
+ QMessageBox.Yes | QMessageBox.No)
if button == QMessageBox.Yes:
self.previous()
del self.contacts[name]
QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
+ f'"{name}" has been removed from your address book.')
self.update_interface(self.NavigationMode)
@@ -254,7 +253,7 @@ class AddressBook(QWidget):
self._address_text.setText(self.contacts[contact_name])
else:
QMessageBox.information(self, "Contact Not Found",
- f'Sorry, "{contact_name}" is not in your address book.')
+ f'Sorry, "{contact_name}" is not in your address book.')
return
self.update_interface(self.NavigationMode)
@@ -304,8 +303,8 @@ class AddressBook(QWidget):
def save_to_file(self):
fileName, _ = QFileDialog.getSaveFileName(self,
- "Save Address Book", '',
- "Address Book (*.abk);;All Files (*)")
+ "Save Address Book", '',
+ "Address Book (*.abk);;All Files (*)")
if not fileName:
return
@@ -314,7 +313,7 @@ class AddressBook(QWidget):
out_file = open(str(fileName), 'wb')
except IOError:
QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
+ f'There was an error opening "{fileName}"')
return
pickle.dump(self.contacts, out_file)
@@ -322,8 +321,8 @@ class AddressBook(QWidget):
def load_from_file(self):
fileName, _ = QFileDialog.getOpenFileName(self,
- "Open Address Book", '',
- "Address Book (*.abk);;All Files (*)")
+ "Open Address Book", '',
+ "Address Book (*.abk);;All Files (*)")
if not fileName:
return
@@ -332,7 +331,7 @@ class AddressBook(QWidget):
in_file = open(str(fileName), 'rb')
except IOError:
QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
+ f'There was an error opening "{fileName}"')
return
self.contacts = pickle.load(in_file)
@@ -340,8 +339,7 @@ class AddressBook(QWidget):
if len(self.contacts) == 0:
QMessageBox.information(self, "No contacts in file",
- "The file you are attempting to open contains no "
- "contacts.")
+ "The file you are attempting to open contains no contacts.")
else:
for name, address in self.contacts:
self._name_line.setText(name)
@@ -375,8 +373,7 @@ class FindDialog(QDialog):
text = self._line_edit.text()
if not text:
- QMessageBox.information(self, "Empty Field",
- "Please enter a name.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name.")
return
self._find_text = text
diff --git a/examples/widgets/tutorials/addressbook/part7.py b/examples/widgets/tutorials/addressbook/part7.py
index 2f874f9bd..3829c003d 100644
--- a/examples/widgets/tutorials/addressbook/part7.py
+++ b/examples/widgets/tutorials/addressbook/part7.py
@@ -151,34 +151,34 @@ class AddressBook(QWidget):
address = self._address_text.toPlainText()
if name == "" or address == "":
- QMessageBox.information(self, "Empty Field",
- "Please enter a name and address.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
return
if self._current_mode == self.AddingMode:
if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
+ f'"{name}" has been added to your address book.')
else:
QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._current_mode == self.EditingMode:
if self._old_name != name:
if name not in self.contacts:
QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your address book.')
+ f'"{self.oldName}" has been edited in your '
+ 'address book.')
del self.contacts[self._old_name]
self.contacts[name] = address
else:
QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
+ f'Sorry, "{name}" is already in your address book.')
return
elif self._old_address != address:
QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
+ f'"{name}" has been edited in your address book.')
self.contacts[name] = address
self.update_interface(self.NavigationMode)
@@ -192,19 +192,18 @@ class AddressBook(QWidget):
@Slot()
def remove_contact(self):
name = self._name_line.text()
- address = self._address_text.toPlainText()
if name in self.contacts:
button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
+ f'Are you sure you want to remove "{name}"?',
+ QMessageBox.Yes | QMessageBox.No)
if button == QMessageBox.Yes:
self.previous()
del self.contacts[name]
QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
+ f'"{name}" has been removed from your address book.')
self.update_interface(self.NavigationMode)
@@ -260,7 +259,7 @@ class AddressBook(QWidget):
self._address_text.setText(self.contacts[contact_name])
else:
QMessageBox.information(self, "Contact Not Found",
- f'Sorry, "{contact_name}" is not in your address book.')
+ f'Sorry, "{contact_name}" is not in your address book.')
return
self.update_interface(self.NavigationMode)
@@ -313,8 +312,8 @@ class AddressBook(QWidget):
def save_to_file(self):
fileName, _ = QFileDialog.getSaveFileName(self,
- "Save Address Book", '',
- "Address Book (*.abk);;All Files (*)")
+ "Save Address Book", '',
+ "Address Book (*.abk);;All Files (*)")
if not fileName:
return
@@ -323,7 +322,7 @@ class AddressBook(QWidget):
out_file = open(str(fileName), 'wb')
except IOError:
QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
+ f'There was an error opening "{fileName}"')
return
pickle.dump(self.contacts, out_file)
@@ -331,8 +330,8 @@ class AddressBook(QWidget):
def load_from_file(self):
fileName, _ = QFileDialog.getOpenFileName(self,
- "Open Address Book", '',
- "Address Book (*.abk);;All Files (*)")
+ "Open Address Book", '',
+ "Address Book (*.abk);;All Files (*)")
if not fileName:
return
@@ -341,7 +340,7 @@ class AddressBook(QWidget):
in_file = open(str(fileName), 'rb')
except IOError:
QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
+ f'There was an error opening "{fileName}"')
return
self.contacts = pickle.load(in_file)
@@ -349,8 +348,7 @@ class AddressBook(QWidget):
if len(self.contacts) == 0:
QMessageBox.information(self, "No contacts in file",
- "The file you are attempting to open contains no "
- "contacts.")
+ "The file you are attempting to open contains no contacts.")
else:
for name, address in self.contacts:
self._name_line.setText(name)
@@ -372,7 +370,7 @@ class AddressBook(QWidget):
last_name = ''
file_name = QFileDialog.getSaveFileName(self, "Export Contact",
- '', "vCard Files (*.vcf);;All Files (*)")[0]
+ '', "vCard Files (*.vcf);;All Files (*)")[0]
if not file_name:
return
@@ -380,8 +378,7 @@ class AddressBook(QWidget):
out_file = QFile(file_name)
if not out_file.open(QIODevice.WriteOnly):
- QMessageBox.information(self, "Unable to open file",
- out_file.errorString())
+ QMessageBox.information(self, "Unable to open file", out_file.errorString())
return
out_s = QTextStream(out_file)
@@ -399,7 +396,7 @@ class AddressBook(QWidget):
out_s << 'END:VCARD' << '\n'
QMessageBox.information(self, "Export Successful",
- f'"{name}" has been exported as a vCard.')
+ f'"{name}" has been exported as a vCard.')
class FindDialog(QDialog):
@@ -427,8 +424,7 @@ class FindDialog(QDialog):
text = self._line_edit.text()
if not text:
- QMessageBox.information(self, "Empty Field",
- "Please enter a name.")
+ QMessageBox.information(self, "Empty Field", "Please enter a name.")
return
self._find_text = text
diff --git a/examples/widgets/tutorials/cannon/t10.py b/examples/widgets/tutorials/cannon/t10.py
index c9c3fa7f6..8649bb562 100644
--- a/examples/widgets/tutorials/cannon/t10.py
+++ b/examples/widgets/tutorials/cannon/t10.py
@@ -43,8 +43,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -113,7 +113,7 @@ class MyWidget(QWidget):
quit = QPushButton("&Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange()
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/cannon/t11.py b/examples/widgets/tutorials/cannon/t11.py
index 997eecbd1..fbfd2481d 100644
--- a/examples/widgets/tutorials/cannon/t11.py
+++ b/examples/widgets/tutorials/cannon/t11.py
@@ -45,8 +45,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -175,7 +175,7 @@ class MyWidget(QWidget):
quit = QPushButton("&Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange()
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/cannon/t12.py b/examples/widgets/tutorials/cannon/t12.py
index 8847b2208..749c24684 100644
--- a/examples/widgets/tutorials/cannon/t12.py
+++ b/examples/widgets/tutorials/cannon/t12.py
@@ -62,8 +62,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -226,7 +226,7 @@ class MyWidget(QWidget):
quit = QPushButton("&Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange("ANGLE")
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/cannon/t13.py b/examples/widgets/tutorials/cannon/t13.py
index 4eb45a374..f9a771d15 100644
--- a/examples/widgets/tutorials/cannon/t13.py
+++ b/examples/widgets/tutorials/cannon/t13.py
@@ -64,8 +64,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -259,7 +259,7 @@ class GameBoard(QWidget):
quit = QPushButton("&Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange("ANGLE")
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/cannon/t14.py b/examples/widgets/tutorials/cannon/t14.py
index e4f1c350d..3c94408f3 100644
--- a/examples/widgets/tutorials/cannon/t14.py
+++ b/examples/widgets/tutorials/cannon/t14.py
@@ -65,8 +65,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -174,7 +174,8 @@ class CannonField(QWidget):
self._auto_shoot_timer.stop()
self.hit.emit()
self.can_shoot.emit(True)
- elif shot_r.x() > self.width() or shot_r.y() > self.height() or shot_r.intersects(self.barrier_rect()):
+ elif (shot_r.x() > self.width() or shot_r.y() > self.height()
+ or shot_r.intersects(self.barrier_rect())):
self._auto_shoot_timer.stop()
self.missed.emit()
self.can_shoot.emit(True)
@@ -301,7 +302,7 @@ class GameBoard(QWidget):
quit = QPushButton("&Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange("ANGLE")
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/cannon/t4.py b/examples/widgets/tutorials/cannon/t4.py
index ba0ebc41b..37a2dc9dd 100644
--- a/examples/widgets/tutorials/cannon/t4.py
+++ b/examples/widgets/tutorials/cannon/t4.py
@@ -20,7 +20,7 @@ class MyWidget(QWidget):
self.quit.setGeometry(62, 40, 75, 30)
self.quit.setFont(QFont("Times", 18, QFont.Bold))
- self.quit.clicked.connect(qApp.quit)
+ self.quit.clicked.connect(qApp.quit) # noqa: F821
if __name__ == '__main__':
diff --git a/examples/widgets/tutorials/cannon/t5.py b/examples/widgets/tutorials/cannon/t5.py
index 42faeed01..ed5d085f8 100644
--- a/examples/widgets/tutorials/cannon/t5.py
+++ b/examples/widgets/tutorials/cannon/t5.py
@@ -25,7 +25,7 @@ class MyWidget(QWidget):
slider.setRange(0, 99)
slider.setValue(0)
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
slider.valueChanged.connect(lcd.display)
layout = QVBoxLayout(self)
diff --git a/examples/widgets/tutorials/cannon/t6.py b/examples/widgets/tutorials/cannon/t6.py
index 1cc2906f2..ea2e044e6 100644
--- a/examples/widgets/tutorials/cannon/t6.py
+++ b/examples/widgets/tutorials/cannon/t6.py
@@ -33,7 +33,7 @@ class MyWidget(QWidget):
quit = QPushButton("Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
layout = QVBoxLayout(self)
layout.addWidget(quit)
diff --git a/examples/widgets/tutorials/cannon/t7.py b/examples/widgets/tutorials/cannon/t7.py
index 51128e6c7..1175107b8 100644
--- a/examples/widgets/tutorials/cannon/t7.py
+++ b/examples/widgets/tutorials/cannon/t7.py
@@ -46,7 +46,7 @@ class MyWidget(QWidget):
quit = QPushButton("Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ 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 b82e24a01..9bb5516b8 100644
--- a/examples/widgets/tutorials/cannon/t8.py
+++ b/examples/widgets/tutorials/cannon/t8.py
@@ -43,8 +43,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning("LCDRange.setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -88,7 +88,7 @@ class MyWidget(QWidget):
quit = QPushButton("Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange()
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/cannon/t9.py b/examples/widgets/tutorials/cannon/t9.py
index 297e98e50..7cdda4e7b 100644
--- a/examples/widgets/tutorials/cannon/t9.py
+++ b/examples/widgets/tutorials/cannon/t9.py
@@ -43,8 +43,8 @@ class LCDRange(QWidget):
def set_range(self, minValue, maxValue):
if minValue < 0 or maxValue > 99 or minValue > maxValue:
qWarning(f"LCDRange::setRange({minValue}, {maxValue})\n"
- "\tRange must be 0..99\n"
- "\tand minValue must not be greater than maxValue")
+ "\tRange must be 0..99\n"
+ "\tand minValue must not be greater than maxValue")
return
self.slider.setRange(minValue, maxValue)
@@ -94,7 +94,7 @@ class MyWidget(QWidget):
quit = QPushButton("Quit")
quit.setFont(QFont("Times", 18, QFont.Bold))
- quit.clicked.connect(qApp.quit)
+ quit.clicked.connect(qApp.quit) # noqa: F821
angle = LCDRange()
angle.set_range(5, 70)
diff --git a/examples/widgets/tutorials/modelview/2_formatting.py b/examples/widgets/tutorials/modelview/2_formatting.py
index 73c993e5f..f39ec462c 100644
--- a/examples/widgets/tutorials/modelview/2_formatting.py
+++ b/examples/widgets/tutorials/modelview/2_formatting.py
@@ -32,7 +32,7 @@ class MyModel(QAbstractTableModel):
return "<--left"
if row == 1 and col == 1:
return "right-->"
- return f"Row{row}, Column{col+1}"
+ return f"Row{row}, Column{col + 1}"
elif role == Qt.FontRole:
if row == 0 and col == 0: # change font only for cell(0,0)
diff --git a/examples/widgets/tutorials/modelview/3_changingmodel.py b/examples/widgets/tutorials/modelview/3_changingmodel.py
index e277dd1e8..2148ec5d3 100644
--- a/examples/widgets/tutorials/modelview/3_changingmodel.py
+++ b/examples/widgets/tutorials/modelview/3_changingmodel.py
@@ -43,6 +43,7 @@ class MyModel(QAbstractTableModel):
self.dataChanged.emit(top_left, top_left, [Qt.DisplayRole])
#! [3]
+
if __name__ == '__main__':
app = QApplication(sys.argv)
table_view = QTableView()
diff --git a/examples/widgets/tutorials/modelview/6_treeview.py b/examples/widgets/tutorials/modelview/6_treeview.py
index 09300560c..cac3c6d53 100644
--- a/examples/widgets/tutorials/modelview/6_treeview.py
+++ b/examples/widgets/tutorials/modelview/6_treeview.py
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QTreeView
"""PySide6 port of the widgets/tutorials/modelview/6_treeview example from Qt v6.x"""
+
#! [1]
class MainWindow(QMainWindow):
def __init__(self, parent=None):
diff --git a/examples/widgets/tutorials/modelview/7_selections.py b/examples/widgets/tutorials/modelview/7_selections.py
index 6c519c865..c879d8f67 100644
--- a/examples/widgets/tutorials/modelview/7_selections.py
+++ b/examples/widgets/tutorials/modelview/7_selections.py
@@ -9,6 +9,7 @@ from PySide6.QtWidgets import QApplication, QMainWindow, QTreeView
"""PySide6 port of the widgets/tutorials/modelview/7_selections example from Qt v6.x"""
+
#! [1]
class MainWindow(QMainWindow):
def __init__(self, parent=None):
diff --git a/examples/widgets/widgets/charactermap/characterwidget.py b/examples/widgets/widgets/charactermap/characterwidget.py
index 1df2a3b74..0f01f9684 100644
--- a/examples/widgets/widgets/charactermap/characterwidget.py
+++ b/examples/widgets/widgets/charactermap/characterwidget.py
@@ -127,7 +127,7 @@ class CharacterWidget(QWidget):
self._square_size, self._square_size, QBrush(Qt.red))
text = chr(key)
- painter.drawText(column * self._square_size + (self._square_size / 2) -
- font_metrics.horizontalAdvance(text) / 2,
+ painter.drawText(column * self._square_size + (self._square_size / 2)
+ - font_metrics.horizontalAdvance(text) / 2,
row * self._square_size + 4 + font_metrics.ascent(),
text)
diff --git a/examples/widgets/widgets/charactermap/mainwindow.py b/examples/widgets/widgets/charactermap/mainwindow.py
index 5f0e2bce4..d79285def 100644
--- a/examples/widgets/widgets/charactermap/mainwindow.py
+++ b/examples/widgets/widgets/charactermap/mainwindow.py
@@ -30,7 +30,7 @@ class MainWindow(QMainWindow):
file_menu.addAction("Quit", self.close)
help_menu = self.menuBar().addMenu("Help")
help_menu.addAction("Show Font Info", self.show_info)
- help_menu.addAction("About &Qt", qApp.aboutQt)
+ help_menu.addAction("About &Qt", qApp.aboutQt) # noqa: F821
central_widget = QWidget()
diff --git a/examples/widgets/widgets/digitalclock/doc/digitalclock.rst b/examples/widgets/widgets/digitalclock/doc/digitalclock.rst
index ef800d9c0..d13275d24 100644
--- a/examples/widgets/widgets/digitalclock/doc/digitalclock.rst
+++ b/examples/widgets/widgets/digitalclock/doc/digitalclock.rst
@@ -1,6 +1,8 @@
Digital Clock Example
=====================
+.. tags:: Android
+
The Digital Clock example shows how to use QLCDNumber to display a number with
LCD-like digits.
diff --git a/examples/widgets/widgets/tetrix/tetrix.py b/examples/widgets/widgets/tetrix/tetrix.py
index 3accd557a..b5df2aa35 100644
--- a/examples/widgets/widgets/tetrix/tetrix.py
+++ b/examples/widgets/widgets/tetrix/tetrix.py
@@ -52,7 +52,7 @@ class TetrixWindow(QWidget):
start_button.clicked.connect(self.board.start)
pause_button.clicked.connect(self.board.pause)
- quit_button.clicked.connect(qApp.quit)
+ quit_button.clicked.connect(qApp.quit) # noqa: F821
self.board.score_changed.connect(score_lcd.display)
self.board.level_changed.connect(level_lcd.display)
self.board.lines_removed_changed.connect(lines_lcd.display)
@@ -134,11 +134,11 @@ class TetrixBoard(QFrame):
def sizeHint(self):
return QSize(TetrixBoard.board_width * 15 + self.frameWidth() * 2,
- TetrixBoard.board_height * 15 + self.frameWidth() * 2)
+ TetrixBoard.board_height * 15 + self.frameWidth() * 2)
def minimum_size_hint(self):
return QSize(TetrixBoard.board_width * 5 + self.frameWidth() * 2,
- TetrixBoard.board_height * 5 + self.frameWidth() * 2)
+ TetrixBoard.board_height * 5 + self.frameWidth() * 2)
@Slot()
def start(self):
@@ -190,16 +190,17 @@ class TetrixBoard(QFrame):
shape = self.shape_at(j, TetrixBoard.board_height - i - 1)
if shape != Piece.NoShape:
self.draw_square(painter,
- rect.left() + j * self.square_width(),
- board_top + i * self.square_height(), shape)
+ rect.left() + j * self.square_width(),
+ board_top + i * self.square_height(), shape)
if self._cur_piece.shape() != Piece.NoShape:
for i in range(4):
x = self._cur_x + self._cur_piece.x(i)
y = self._cur_y - self._cur_piece.y(i)
self.draw_square(painter, rect.left() + x * self.square_width(),
- board_top + (TetrixBoard.board_height - y - 1) * self.square_height(),
- self._cur_piece.shape())
+ board_top
+ + (TetrixBoard.board_height - y - 1) * self.square_height(),
+ self._cur_piece.shape())
def keyPressEvent(self, event):
if not self._is_started or self._is_paused or self._cur_piece.shape() == Piece.NoShape:
@@ -234,7 +235,8 @@ class TetrixBoard(QFrame):
super(TetrixBoard, self).timerEvent(event)
def clear_board(self):
- self.board = [Piece.NoShape for i in range(TetrixBoard.board_height * TetrixBoard.board_width)]
+ self.board = [
+ Piece.NoShape for _ in range(TetrixBoard.board_height * TetrixBoard.board_width)]
def drop_down(self):
drop_height = 0
@@ -283,7 +285,7 @@ class TetrixBoard(QFrame):
if line_is_full:
num_full_lines += 1
- for k in range(TetrixBoard.board_height - 1):
+ for k in range(i, TetrixBoard.board_height - 1):
for j in range(TetrixBoard.board_width):
self.set_shape_at(j, k, self.shape_at(j, k + 1))
@@ -328,7 +330,7 @@ class TetrixBoard(QFrame):
x = self._next_piece.x(i) - self._next_piece.min_x()
y = self._next_piece.y(i) - self._next_piece.min_y()
self.draw_square(painter, x * self.square_width(),
- y * self.square_height(), self._next_piece.shape())
+ y * self.square_height(), self._next_piece.shape())
self.nextPieceLabel.setPixmap(pixmap)
@@ -349,11 +351,10 @@ class TetrixBoard(QFrame):
def draw_square(self, painter, x, y, shape):
color_table = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
- 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
+ 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
color = QColor(color_table[shape])
- painter.fillRect(x + 1, y + 1, self.square_width() - 2,
- self.square_height() - 2, color)
+ painter.fillRect(x + 1, y + 1, self.square_width() - 2, self.square_height() - 2, color)
painter.setPen(color.lighter())
painter.drawLine(x, y + self.square_height() - 1, x, y)
@@ -361,25 +362,25 @@ class TetrixBoard(QFrame):
painter.setPen(color.darker())
painter.drawLine(x + 1, y + self.square_height() - 1,
- x + self.square_width() - 1, y + self.square_height() - 1)
+ x + self.square_width() - 1, y + self.square_height() - 1)
painter.drawLine(x + self.square_width() - 1,
- y + self.square_height() - 1, x + self.square_width() - 1, y + 1)
+ y + self.square_height() - 1, x + self.square_width() - 1, y + 1)
class TetrixPiece(object):
coords_table = (
- ((0, 0), (0, 0), (0, 0), (0, 0)),
- ((0, -1), (0, 0), (-1, 0), (-1, 1)),
- ((0, -1), (0, 0), (1, 0), (1, 1)),
- ((0, -1), (0, 0), (0, 1), (0, 2)),
- ((-1, 0), (0, 0), (1, 0), (0, 1)),
- ((0, 0), (1, 0), (0, 1), (1, 1)),
- ((-1, -1), (0, -1), (0, 0), (0, 1)),
- ((1, -1), (0, -1), (0, 0), (0, 1))
+ ((0, 0), (0, 0), (0, 0), (0, 0)),
+ ((0, -1), (0, 0), (-1, 0), (-1, 1)),
+ ((0, -1), (0, 0), (1, 0), (1, 1)),
+ ((0, -1), (0, 0), (0, 1), (0, 2)),
+ ((-1, 0), (0, 0), (1, 0), (0, 1)),
+ ((0, 0), (1, 0), (0, 1), (1, 1)),
+ ((-1, -1), (0, -1), (0, 0), (0, 1)),
+ ((1, -1), (0, -1), (0, 0), (0, 1))
)
def __init__(self):
- self.coords = [[0,0] for _ in range(4)]
+ self.coords = [[0, 0] for _ in range(4)]
self._piece_shape = Piece.NoShape
self.set_shape(Piece.NoShape)
diff --git a/examples/widgets/widgetsgallery/widgetgallery.py b/examples/widgets/widgetsgallery/widgetgallery.py
index bf1f523e2..d43ab26a5 100644
--- a/examples/widgets/widgetsgallery/widgetgallery.py
+++ b/examples/widgets/widgetsgallery/widgetgallery.py
@@ -164,8 +164,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.Help
+ | QDialogButtonBox.Close)
init_widget(dialog_buttonbox, "dialogButtonBox")
dialog_buttonbox.helpRequested.connect(launch_module_help)
dialog_buttonbox.rejected.connect(self.reject)
diff --git a/examples/xml/dombookmarks/dombookmarks.py b/examples/xml/dombookmarks/dombookmarks.py
index e77adc3d1..a35aeb0f2 100644
--- a/examples/xml/dombookmarks/dombookmarks.py
+++ b/examples/xml/dombookmarks/dombookmarks.py
@@ -7,7 +7,7 @@
import sys
from PySide6.QtCore import QDir, QFile, Qt, QTextStream
-from PySide6.QtGui import QAction, QIcon
+from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QFileDialog, QHeaderView,
QMainWindow, QMessageBox, QStyle, QTreeWidget,
QTreeWidgetItem)
@@ -21,7 +21,6 @@ class MainWindow(QMainWindow):
self._xbel_tree = XbelTree()
self.setCentralWidget(self._xbel_tree)
- self.create_actions()
self.create_menus()
self.statusBar().showMessage("Ready")
@@ -31,8 +30,8 @@ class MainWindow(QMainWindow):
def open(self):
file_name = QFileDialog.getOpenFileName(self,
- "Open Bookmark File", QDir.currentPath(),
- "XBEL Files (*.xbel *.xml)")[0]
+ "Open Bookmark File", QDir.currentPath(),
+ "XBEL Files (*.xbel *.xml)")[0]
if not file_name:
return
@@ -41,7 +40,7 @@ class MainWindow(QMainWindow):
if not in_file.open(QFile.ReadOnly | QFile.Text):
reason = in_file.errorString()
QMessageBox.warning(self, "DOM Bookmarks",
- f"Cannot read file {file_name}:\n{reason}.")
+ f"Cannot read file {file_name}:\n{reason}.")
return
if self._xbel_tree.read(in_file):
@@ -49,8 +48,8 @@ class MainWindow(QMainWindow):
def save_as(self):
file_name = QFileDialog.getSaveFileName(self,
- "Save Bookmark File", QDir.currentPath(),
- "XBEL Files (*.xbel *.xml)")[0]
+ "Save Bookmark File", QDir.currentPath(),
+ "XBEL Files (*.xbel *.xml)")[0]
if not file_name:
return
@@ -59,7 +58,7 @@ class MainWindow(QMainWindow):
if not out_file.open(QFile.WriteOnly | QFile.Text):
reason = out_file.errorString()
QMessageBox.warning(self, "DOM Bookmarks",
- "Cannot write file {fileName}:\n{reason}.")
+ f"Cannot write file {file_name}:\n{reason}.")
return
if self._xbel_tree.write(out_file):
@@ -67,35 +66,26 @@ class MainWindow(QMainWindow):
def about(self):
QMessageBox.about(self, "About DOM Bookmarks",
- "The <b>DOM Bookmarks</b> example demonstrates how to use Qt's "
- "DOM classes to read and write XML documents.")
-
- def create_actions(self):
- self._open_act = QAction("&Open...", self, shortcut="Ctrl+O",
- triggered=self.open)
-
- self._save_as_act = QAction("&Save As...", self, shortcut="Ctrl+S",
- triggered=self.save_as)
-
- self._exit_act = QAction("E&xit", self, shortcut="Ctrl+Q",
- triggered=self.close)
-
- self._about_act = QAction("&About", self, triggered=self.about)
-
- self._about_qt_act = QAction("About &Qt", self,
- triggered=qApp.aboutQt)
+ "The <b>DOM Bookmarks</b> example demonstrates how to use Qt's "
+ "DOM classes to read and write XML documents.")
def create_menus(self):
self._file_menu = self.menuBar().addMenu("&File")
- self._file_menu.addAction(self._open_act)
- self._file_menu.addAction(self._save_as_act)
- self._file_menu.addAction(self._exit_act)
+ self._file_menu.addAction(QAction("&Open...", self,
+ shortcut=QKeySequence(
+ Qt.CTRL | Qt.Key_O), triggered=self.open))
+ self._file_menu.addAction(QAction("&Save As...", self,
+ shortcut=QKeySequence(
+ Qt.CTRL | Qt.Key_S), triggered=self.save_as))
+ self._file_menu.addAction(QAction("E&xit", self,
+ shortcut=QKeySequence(
+ Qt.CTRL | Qt.Key_Q), triggered=self.close))
self.menuBar().addSeparator()
self._help_menu = self.menuBar().addMenu("&Help")
- self._help_menu.addAction(self._about_act)
- self._help_menu.addAction(self._about_qt_act)
+ self._help_menu.addAction(QAction("&About", self, triggered=self.about))
+ self._help_menu.addAction(QAction("About &Qt", self, triggered=qApp.aboutQt)) # noqa: F821
class XbelTree(QTreeWidget):
@@ -113,26 +103,27 @@ class XbelTree(QTreeWidget):
self._bookmark_icon = QIcon()
self._folder_icon.addPixmap(self.style().standardPixmap(QStyle.SP_DirClosedIcon),
- QIcon.Normal, QIcon.Off)
+ QIcon.Normal, QIcon.Off)
self._folder_icon.addPixmap(self.style().standardPixmap(QStyle.SP_DirOpenIcon),
- QIcon.Normal, QIcon.On)
+ QIcon.Normal, QIcon.On)
self._bookmark_icon.addPixmap(self.style().standardPixmap(QStyle.SP_FileIcon))
def read(self, device):
ok, errorStr, errorLine, errorColumn = self._dom_document.setContent(device, True)
if not ok:
QMessageBox.information(self.window(), "DOM Bookmarks",
- f"Parse error at line {errorLine}, column {errorColumn}:\n{errorStr}")
+ f"Parse error at line {errorLine}, "
+ f"column {errorColumn}:\n{errorStr}")
return False
root = self._dom_document.documentElement()
if root.tagName() != 'xbel':
QMessageBox.information(self.window(), "DOM Bookmarks",
- "The file is not an XBEL file.")
+ "The file is not an XBEL file.")
return False
elif root.hasAttribute('version') and root.attribute('version') != '1.0':
QMessageBox.information(self.window(), "DOM Bookmarks",
- "The file is not an XBEL version 1.0 file.")
+ "The file is not an XBEL version 1.0 file.")
return False
self.clear()
diff --git a/examples/xml/dombookmarks/dombookmarks.pyproject b/examples/xml/dombookmarks/dombookmarks.pyproject
index 9a688558d..0a0b203a6 100644
--- a/examples/xml/dombookmarks/dombookmarks.pyproject
+++ b/examples/xml/dombookmarks/dombookmarks.pyproject
@@ -1,3 +1,3 @@
{
- "files": ["jennifer.xbel", "frank.xbel", "dombookmarks.py"]
+ "files": ["jennifer.xbel", "dombookmarks.py"]
}
diff --git a/examples/xml/dombookmarks/frank.xbel b/examples/xml/dombookmarks/frank.xbel
deleted file mode 100644
index e4184deee..000000000
--- a/examples/xml/dombookmarks/frank.xbel
+++ /dev/null
@@ -1,230 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE xbel>
-<xbel version="1.0">
- <folder folded="yes">
- <title>Literate Programming</title>
- <bookmark href="http://www.vivtek.com/litprog.html">
- <title>Synopsis of Literate Programming</title>
- </bookmark>
- <bookmark href="http://vasc.ri.cmu.edu/old_help/Programming/Literate/literate.html">
- <title>Literate Programming: Propaganda and Tools</title>
- </bookmark>
- <bookmark href="http://www.isy.liu.se/%7Eturbell/litprog/">
- <title>Literate Programming by Henrik Turbell</title>
- </bookmark>
- <bookmark href="http://www.desy.de/user/projects/LitProg.html">
- <title>Literate Programming Library</title>
- </bookmark>
- <bookmark href="http://www.loria.fr/services/tex/english/litte.html">
- <title>Literate Programming Basics</title>
- </bookmark>
- <bookmark href="http://ei.cs.vt.edu/%7Ecs5014/courseNotes/4.LiterateProgramming/literate_prog.html">
- <title>Literate Programming Overview</title>
- </bookmark>
- <bookmark href="http://www.perl.com/pub/a/tchrist/litprog.html">
- <title>POD is not Literate Programming</title>
- </bookmark>
- <bookmark href="http://www.cornellcollege.edu/%7Eltabak/publications/articles/swsafety.html">
- <title>Computers That We Can Count On</title>
- </bookmark>
- <bookmark href="http://www.cs.auc.dk/%7Enormark/litpro/issues-and-problems.html">
- <title>Literate Programming - Issues and Problems</title>
- </bookmark>
- <bookmark href="http://c2.com/cgi/wiki?LiterateProgramming">
- <title>Literate Programming - Wiki Pages</title>
- </bookmark>
- <bookmark href="http://developers.slashdot.org/developers/02/05/19/2216233.shtml">
- <title>What is well-commented code?</title>
- </bookmark>
- <bookmark href="http://liinwww.ira.uka.de/bibliography/SE/litprog.html">
- <title>Bibliography on literate programming - A searchable bibliography</title>
- </bookmark>
- <bookmark href="http://www2.umassd.edu/SWPI/ProcessBibliography/bib-codereading.html">
- <title>Program comprehension and code reading bibliography</title>
- </bookmark>
- <bookmark href="http://www.cs.auc.dk/%7Enormark/elucidative-programming/">
- <title>Elucidative Programming</title>
- </bookmark>
- <bookmark href="http://www.msu.edu/%7Epfaffben/avl/index.html">
- <title>AVL Trees (TexiWeb)</title>
- </bookmark>
- <bookmark href="http://literate-programming.wikiverse.org/">
- <title>Literate Programming on Wikiverse</title>
- </bookmark>
- <bookmark href="http://www.pbrt.org/">
- <title>Physically Based Rendering: From Theory to Implementation</title>
- </bookmark>
- </folder>
- <folder folded="no">
- <title>Useful C++ Links</title>
- <folder folded="no">
- <title>STL</title>
- <bookmark href="http://www.sgi.com/tech/stl/table_of_contents.html">
- <title>STL Reference Documentation</title>
- </bookmark>
- <bookmark href="http://www.yrl.co.uk/~phil/stl/stl.htmlx">
- <title>STL Tutorial</title>
- </bookmark>
- <bookmark href="http://www.cppreference.com/cpp_stl.html">
- <title>STL Reference</title>
- </bookmark>
- </folder>
- <folder folded="no">
- <title>Qt</title>
- <bookmark href="http://doc.trolltech.com/2.3/">
- <title>Qt 2.3 Reference</title>
- </bookmark>
- <bookmark href="http://doc.trolltech.com/3.3/">
- <title>Qt 3.3 Reference</title>
- </bookmark>
- <bookmark href="http://doc.trolltech.com/4.0/">
- <title>Qt 4.0 Reference</title>
- </bookmark>
- <bookmark href="http://www.trolltech.com/">
- <title>Trolltech Home Page</title>
- </bookmark>
- </folder>
- <folder folded="yes">
- <title>IOStreams</title>
- <bookmark href="http://www.cplusplus.com/ref/iostream/index.html">
- <title>IO Stream Library</title>
- </bookmark>
- <bookmark href="http://courses.cs.vt.edu/~cs2604/fall01/binio.html">
- <title>Binary I/O</title>
- </bookmark>
- <bookmark href="http://www.parashift.com/c++-faq-lite/input-output.html">
- <title>I/O Stream FAQ</title>
- </bookmark>
- </folder>
- <folder folded="yes">
- <title>gdb</title>
- <bookmark href="http://www.cs.princeton.edu/~benjasik/gdb/gdbtut.html">
- <title>GDB Tutorial</title>
- </bookmark>
- <bookmark href="https://www.gnu.org/manual/gdb-4.17/html_mono/gdb.html">
- <title>Debugging with GDB</title>
- </bookmark>
- <bookmark href="http://www.cs.washington.edu/orgs/acm/tutorials/dev-in-unix/gdb-refcard.pdf">
- <title>GDB Quick Reference Page (PDF) (Handy)</title>
- </bookmark>
- </folder>
- <folder folded="yes">
- <title>Classes and Constructors</title>
- <bookmark href="http://www.parashift.com/c++-faq-lite/ctors.html">
- <title>Constructor FAQ</title>
- </bookmark>
- <bookmark href="http://www.juicystudio.com/tutorial/cpp/index.html">
- <title>Organizing Classes</title>
- </bookmark>
- </folder>
- </folder>
- <folder folded="yes">
- <title>Software Documentation or System Documentation</title>
- <bookmark href="http://www.martinfowler.com/distributedComputing/thud.html">
- <title>The Almighty Thud</title>
- </bookmark>
- <bookmark href="http://msdn.microsoft.com/library/techart/cfr.htm">
- <title>Microsoft Coding Techniques and Programming Practices</title>
- </bookmark>
- <bookmark href="http://www.bearcave.com/software/prog_docs.html">
- <title>Software and Documentation</title>
- </bookmark>
- <bookmark href="http://c2.com/cgi/wiki?TheSourceCodeIsTheDesign">
- <title>The Source Code is the Design</title>
- </bookmark>
- <bookmark href="http://www.bleading-edge.com/Publications/C++Journal/Cpjour2.htm">
- <title>What is Software Design?</title>
- </bookmark>
- <bookmark href="http://www.mindprod.com/unmain.html">
- <title>How To Write Unmaintainable Code</title>
- </bookmark>
- <bookmark href="http://www.idinews.com/selfDoc.html">
- <title>Self Documenting Program Code Remains a Distant Goal</title>
- </bookmark>
- <bookmark href="http://www.sdmagazine.com/documents/s=730/sdm0106m/0106m.htm">
- <title>Place Tab A in Slot B</title>
- </bookmark>
- <bookmark href="http://www.holub.com/class/uml/uml.html">
- <title>UML Reference Card</title>
- </bookmark>
- </folder>
- <folder folded="yes">
- <title>TeX Resources</title>
- <bookmark href="http://www.tug.org/">
- <title>The TeX User's Group</title>
- </bookmark>
- <bookmark href="http://www.miktex.org/">
- <title>MikTeX website</title>
- </bookmark>
- <bookmark href="http://cm.bell-labs.com/who/hobby/MetaPost.html">
- <title>MetaPost website</title>
- </bookmark>
- <bookmark href="http://pauillac.inria.fr/%7Emaranget/hevea/">
- <title>HEVEA is a quite complete and fast LATEX to HTML translator</title>
- </bookmark>
- </folder>
- <folder folded="no">
- <title>Portable Document Format (PDF)</title>
- <bookmark href="http://www.adobe.com/">
- <title>Adobe - The postscript and PDF standards</title>
- </bookmark>
- <bookmark href="http://partners.adobe.com/asn/developer/technotes/acrobatpdf.html">
- <title>Reference Manual Portable Document Format</title>
- </bookmark>
- <bookmark href="http://partners.adobe.com/asn/developer/acrosdk/main.html">
- <title>Adobe Acrobat Software Development Kit</title>
- </bookmark>
- </folder>
- <folder folded="yes">
- <title>Literature Sites</title>
- <bookmark href="http://www.cc.columbia.edu/cu/libraries/subjects/speccol.html">
- <title>Guide to Special Collections (Columbia University)</title>
- </bookmark>
- <bookmark href="http://www.ipl.org/ref/litcrit/">
- <title>Literary Criticism on the Web from the Internet Public Library</title>
- </bookmark>
- <bookmark href="http://www.victorianweb.org/">
- <title>Victorian Web.</title>
- </bookmark>
- <bookmark href="http://vos.ucsb.edu/">
- <title>Voice of the Shuttle.</title>
- </bookmark>
- <bookmark href="http://www.modjourn.brown.edu/">
- <title>Modernist Journals Project</title>
- </bookmark>
- <bookmark href="http://www.poetspath.com">
- <title>Museum of American Poetics</title>
- </bookmark>
- <bookmark href="http://www.english.uiuc.edu/maps/">
- <title>Modern American Poetry</title>
- </bookmark>
- <bookmark href="http://www.findarticles.com/">
- <title>FindArticles.com</title>
- </bookmark>
- <bookmark href="http://www.literaryhistory.com">
- <title>Literary History</title>
- </bookmark>
- <bookmark href="http://www.litencyc.com/LitEncycFrame.htm">
- <title>Literary Encyclopedia</title>
- </bookmark>
- <separator/>
- <bookmark href="http://texts.cdlib.org/ucpress/">
- <title>The University of California Press</title>
- </bookmark>
- <bookmark href="http://www.letrs.indiana.edu/web/w/wright2/">
- <title>Wright American Fiction, 1851-1875</title>
- </bookmark>
- <bookmark href="http://docsouth.unc.edu/">
- <title>Documenting the American South: Beginnings to 1920</title>
- </bookmark>
- <bookmark href="http://etext.lib.virginia.edu/eng-on.html">
- <title>Electronic Text Center at the University of Virginia</title>
- </bookmark>
- <bookmark href="http://digital.nypl.org/schomburg/writers_aa19/">
- <title>The Schomburg Center for Research in Black Culture</title>
- </bookmark>
- <bookmark href="http://www.infomotions.com/alex2/">
- <title>Alex Catalog of Electronic Texts.</title>
- </bookmark>
- </folder>
-</xbel>
diff --git a/examples/xml/dombookmarks/jennifer.xbel b/examples/xml/dombookmarks/jennifer.xbel
index 939e6f583..d50423683 100644
--- a/examples/xml/dombookmarks/jennifer.xbel
+++ b/examples/xml/dombookmarks/jennifer.xbel
@@ -3,91 +3,67 @@
<xbel version="1.0">
<folder folded="no">
<title>Qt Resources</title>
+ <bookmark href="https://www.qt.io/">
+ <title>Qt home page</title>
+ </bookmark>
+ <bookmark href="https://www.qt.io/contact-us/partners">
+ <title>Qt Partners</title>
+ </bookmark>
+ <bookmark href="https://www.qt.io/qt-professional-services">
+ <title>Professional Services</title>
+ </bookmark>
+ <bookmark href="https://doc.qt.io/">
+ <title>Qt Documentation</title>
+ </bookmark>
<folder folded="yes">
- <title>Trolltech Partners</title>
- <bookmark href="http://partners.trolltech.com/partners/training.html">
- <title>Training Partners</title>
- </bookmark>
- <bookmark href="http://partners.trolltech.com/partners/service.html">
- <title>Consultants and System Integrators</title>
- </bookmark>
- <bookmark href="http://partners.trolltech.com/partners/tech.html">
- <title>Technology Partners</title>
+ <title>Community Resources</title>
+ <bookmark href="https://contribute.qt-project.org">
+ <title>The Qt Project</title>
</bookmark>
- <bookmark href="http://partners.trolltech.com/partners/resellers.html">
- <title>Value Added Resellers (VARs)</title>
+ <bookmark href="https://www.qtcentre.org/content/">
+ <title>Qt Centre</title>
</bookmark>
- </folder>
- <folder folded="yes">
- <title>Community Resources</title>
- <bookmark href="https://www.qtforum.org/">
- <title>QtForum.org</title>
+ <bookmark href="https://forum.qt.io/">
+ <title>Forum.Qt.org</title>
</bookmark>
- <bookmark href="http://www.digitalfanatics.org/projects/qt_tutorial/">
+ <bookmark href="https://digitalfanatics.org/projects/qt_tutorial/">
<title>The Independent Qt Tutorial</title>
</bookmark>
- <bookmark href="http://prog.qt.free.fr/">
- <title>French PROG.Qt</title>
- </bookmark>
<bookmark href="https://www.qtforum.de/">
<title>German Qt Forum</title>
</bookmark>
- <bookmark href="http://www.korone.net/">
+ <bookmark href="https://www.qt-dev.com/">
<title>Korean Qt Community Site</title>
</bookmark>
- <bookmark href="http://prog.org.ru/forum/forum_14.html">
+ <bookmark href="http://www.prog.org.ru/">
<title>Russian Qt Forum</title>
</bookmark>
- <bookmark href="http://qt4.digitalfanatics.org/">
- <title>Digitalfanatics: The QT 4 Resource Center</title>
- </bookmark>
- <bookmark href="https://www.qtquestions.org/">
- <title>QtQuestions</title>
- </bookmark>
</folder>
- <bookmark href="http://doc.trolltech.com/qq/">
- <title>Qt Quarterly</title>
- </bookmark>
- <bookmark href="http://www.trolltech.com/">
- <title>Trolltech's home page</title>
- </bookmark>
- <bookmark href="http://doc.trolltech.com/4.0/">
- <title>Qt 4.0 documentation</title>
- </bookmark>
- <bookmark href="http://www.trolltech.com/developer/faqs/">
- <title>Frequently Asked Questions</title>
- </bookmark>
</folder>
<folder folded="no">
<title>Online Dictionaries</title>
- <bookmark href="http://www.dictionary.com/">
+ <bookmark href="https://www.dictionary.com/">
<title>Dictionary.com</title>
</bookmark>
- <bookmark href="http://www.m-w.com/">
+ <bookmark href="https://www.merriam-webster.com/">
<title>Merriam-Webster Online</title>
</bookmark>
- <bookmark href="http://dictionary.cambridge.org/">
+ <bookmark href="https://dictionary.cambridge.org/">
<title>Cambridge Dictionaries Online</title>
</bookmark>
- <bookmark href="http://www.onelook.com/">
+ <bookmark href="https://www.onelook.com/">
<title>OneLook Dictionary Search</title>
</bookmark>
<separator/>
- <bookmark href="www.iee.et.tu-dresden.de/">
- <title>The New English-German Dictionary</title>
- </bookmark>
- <bookmark href="http://dict.tu-chemnitz.de/">
- <title>TU Chemnitz German-English Dictionary</title>
+ <bookmark href="https://dict.tu-chemnitz.de/">
+ <title>BEOLINGUS, a service of TU Chemnitz</title>
</bookmark>
<separator/>
<bookmark href="http://atilf.atilf.fr/tlf.htm">
<title>Trésor de la Langue Française informatisé</title>
</bookmark>
- <bookmark href="http://dictionnaires.atilf.fr/dictionnaires/ACADEMIE/">
+ <bookmark href="https://www.dictionnaire-academie.fr/">
<title>Dictionnaire de l'Académie Française</title>
</bookmark>
- <bookmark href="http://elsap1.unicaen.fr/cgi-bin/cherches.cgi">
- <title>Dictionnaire des synonymes</title>
- </bookmark>
</folder>
</xbel>