New upstream version 6.10.2
authorStuart Prescott <stuart@debian.org>
Tue, 10 Feb 2026 06:10:18 +0000 (17:10 +1100)
committerStuart Prescott <stuart@debian.org>
Tue, 10 Feb 2026 06:10:18 +0000 (17:10 +1100)
653 files changed:
build_scripts/main.py
build_scripts/options.py
build_scripts/platforms/unix.py
build_scripts/platforms/windows_desktop.py
build_scripts/utils.py
build_scripts/wheel_files.py
coin/dependencies.yaml
coin/instructions/common_environment.yaml
coin/instructions/execute_android_instructions.yaml
coin/instructions_utils.py
coin/module_config.yaml
create_wheels.py
doc/changelogs/changes-6.10.0 [new file with mode: 0644]
doc/changelogs/changes-6.10.1 [new file with mode: 0644]
doc/changelogs/changes-6.10.2 [new file with mode: 0644]
doc/changelogs/changes-6.9.3 [new file with mode: 0644]
examples/bluetooth/heartrate_game/HeartRateGame/BluetoothAlarmDialog.qml
examples/bluetooth/heartrate_game/HeartRateGame/BottomLine.qml
examples/bluetooth/heartrate_game/HeartRateGame/Connect.qml
examples/bluetooth/heartrate_game/HeartRateGame/GamePage.qml
examples/bluetooth/heartrate_game/HeartRateGame/GameSettings.qml
examples/bluetooth/heartrate_game/HeartRateGame/Measure.qml
examples/bluetooth/heartrate_game/HeartRateGame/SplashScreen.qml
examples/bluetooth/heartrate_game/HeartRateGame/Stats.qml
examples/bluetooth/heartrate_game/HeartRateGame/TitleBar.qml
examples/bluetooth/heartrate_game/HeartRateGame/images/alert.svg [new file with mode: 0644]
examples/bluetooth/heartrate_game/HeartRateGame/images/bluetooth.svg [new file with mode: 0644]
examples/bluetooth/heartrate_game/HeartRateGame/images/clock.svg [new file with mode: 0644]
examples/bluetooth/heartrate_game/HeartRateGame/images/heart.png
examples/bluetooth/heartrate_game/HeartRateGame/images/progress.svg [new file with mode: 0644]
examples/bluetooth/heartrate_game/HeartRateGame/images/search.svg [new file with mode: 0644]
examples/bluetooth/heartrate_game/bluetoothbaseclass.py
examples/bluetooth/heartrate_game/devicefinder.py
examples/bluetooth/heartrate_game/devicehandler.py
examples/bluetooth/heartrate_game/deviceinfo.py
examples/bluetooth/heartrate_game/doc/heartrate_game.rst
examples/bluetooth/heartrate_game/heartrate_global.py
examples/bluetooth/heartrate_game/main.py
examples/bluetooth/lowenergyscanner/Scanner/Services.qml
examples/bluetooth/lowenergyscanner/device.py
examples/charts/callout/callout.py
examples/corelib/mimetypesbrowser/mimetypesbrowser.py
examples/demos/colorpaletteclient/restservice.py
examples/examples.pyproject
examples/graphs/2d/hellographs/HelloGraphs/Main.qml
examples/graphs/3d/widgetgraphgallery/bargraph.py
examples/graphs/3d/widgetgraphgallery/graphmodifier.py
examples/graphs/3d/widgetgraphgallery/highlightseries.py
examples/graphs/3d/widgetgraphgallery/rainfalldata.py
examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py [deleted file]
examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py [deleted file]
examples/graphs/3d/widgetgraphgallery/variantdataset.py [deleted file]
examples/graphs/3d/widgetgraphgallery/widgetgraphgallery.pyproject
examples/gui/rhiwindow/rhiwindow.py
examples/location/mapviewer/MapViewer/Main.qml
examples/location/mapviewer/MapViewer/forms/MessageForm.ui.qml
examples/location/mapviewer/main.py
examples/multimedia/audiooutput/audiooutput.py
examples/multimedia/audiosource/audiosource.py
examples/multimedia/camera/camera.py
examples/multimedia/camera/camera.ui
examples/multimedia/camera/ui_camera.py
examples/multimedia/camera/ui_camera_mobile.py
examples/multimedia/player/audiolevelmeter.py [new file with mode: 0644]
examples/multimedia/player/doc/player.png [deleted file]
examples/multimedia/player/doc/player.rst
examples/multimedia/player/doc/player.webp [new file with mode: 0644]
examples/multimedia/player/main.py [new file with mode: 0644]
examples/multimedia/player/player.py
examples/multimedia/player/player.pyproject
examples/multimedia/player/playercontrols.py [new file with mode: 0644]
examples/multimedia/player/videowidget.py [new file with mode: 0644]
examples/pdfwidgets/pdfviewer/images/go-down-search.svgz [new file with mode: 0644]
examples/pdfwidgets/pdfviewer/images/go-up-search.svgz [new file with mode: 0644]
examples/pdfwidgets/pdfviewer/mainwindow.py
examples/pdfwidgets/pdfviewer/mainwindow.ui
examples/pdfwidgets/pdfviewer/pdfviewer.pyproject
examples/pdfwidgets/pdfviewer/rc_resources.py
examples/pdfwidgets/pdfviewer/resources.qrc
examples/pdfwidgets/pdfviewer/searchresultdelegate.py [new file with mode: 0644]
examples/pdfwidgets/pdfviewer/ui_mainwindow.py
examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/People/Main.qml
examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/People/Main.qml
examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/person.py
examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/People/Main.qml
examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/person.py
examples/qml/tutorials/extending-qml/chapter1-basics/Charts/App.qml [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter1-basics/app.qml [deleted file]
examples/qml/tutorials/extending-qml/chapter1-basics/basics.py
examples/qml/tutorials/extending-qml/chapter1-basics/chapter1-basics.pyproject
examples/qml/tutorials/extending-qml/chapter1-basics/doc/chapter1-basics.rst
examples/qml/tutorials/extending-qml/chapter2-methods/Charts/App.qml [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter2-methods/app.qml [deleted file]
examples/qml/tutorials/extending-qml/chapter2-methods/chapter2-methods.pyproject
examples/qml/tutorials/extending-qml/chapter2-methods/doc/chapter2-methods.rst
examples/qml/tutorials/extending-qml/chapter2-methods/methods.py
examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/App.qml [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter3-bindings/app.qml [deleted file]
examples/qml/tutorials/extending-qml/chapter3-bindings/bindings.py
examples/qml/tutorials/extending-qml/chapter3-bindings/chapter3-bindings.pyproject
examples/qml/tutorials/extending-qml/chapter3-bindings/doc/chapter3-bindings.rst
examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/App.qml [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/app.qml [deleted file]
examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/chapter4-customPropertyTypes.pyproject
examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py
examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/doc/chapter4-customPropertyTypes.rst
examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/App.qml [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter5-listproperties/app.qml [deleted file]
examples/qml/tutorials/extending-qml/chapter5-listproperties/chapter5-listproperties.pyproject
examples/qml/tutorials/extending-qml/chapter5-listproperties/doc/chapter5-listproperties.rst
examples/qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py
examples/qml/tutorials/extending-qml/chapter6-plugins/App.qml [new file with mode: 0644]
examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png [deleted file]
examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml [deleted file]
examples/qml/tutorials/extending-qml/chapter6-plugins/chapter6-plugins.pyproject
examples/qml/tutorials/extending-qml/chapter6-plugins/doc/chapter6-plugins.rst
examples/quick/customitems/painteditem/TextBalloon/textballoon.py [new file with mode: 0644]
examples/quick/customitems/painteditem/doc/painteditem.png [new file with mode: 0644]
examples/quick/customitems/painteditem/doc/painteditem.rst [new file with mode: 0644]
examples/quick/customitems/painteditem/main.py [new file with mode: 0644]
examples/quick/customitems/painteditem/painteditem.pyproject [new file with mode: 0644]
examples/quick/customitems/painteditem/painteditemexample/Main.qml [new file with mode: 0644]
examples/quick/customitems/painteditem/painteditemexample/qmldir [new file with mode: 0644]
examples/quick/models/objectlistmodel/view.qml
examples/quick/painteditem/doc/painteditem.png [deleted file]
examples/quick/painteditem/doc/painteditem.rst [deleted file]
examples/quick/painteditem/main.qml [deleted file]
examples/quick/painteditem/painteditem.py [deleted file]
examples/quick/painteditem/painteditem.pyproject [deleted file]
examples/quick/rendercontrol/rendercontrol_opengl/demo.qml
examples/quick/scenegraph/openglunderqml/main.qml
examples/quick/scenegraph/scenegraph_customgeometry/main.qml
examples/quick/window/Splash.qml
examples/quick/window/rc_window.py
examples/quick/window/window.qml
examples/quick/window/window.qrc
examples/quick3d/customgeometry/CustomGeometryExample/Main.qml [new file with mode: 0644]
examples/quick3d/customgeometry/CustomGeometryExample/TorusMesh.qml [new file with mode: 0644]
examples/quick3d/customgeometry/CustomGeometryExample/qmldir [new file with mode: 0644]
examples/quick3d/customgeometry/CustomGeometryExample/qt_logo_rect.png [new file with mode: 0644]
examples/quick3d/customgeometry/customgeometry.pyproject
examples/quick3d/customgeometry/examplepoint.py
examples/quick3d/customgeometry/exampletriangle.py
examples/quick3d/customgeometry/main.py
examples/quick3d/customgeometry/main.qml [deleted file]
examples/quick3d/customgeometry/qt_logo_rect.png [deleted file]
examples/quick3d/customgeometry/resources.qrc [deleted file]
examples/quick3d/customgeometry/resources_rc.py [deleted file]
examples/quick3d/intro/main.qml
examples/quickcontrols/contactslist/Contact/ContactDelegate.ui.qml
examples/quickcontrols/contactslist/Contact/ContactDialog.qml
examples/quickcontrols/contactslist/Contact/ContactList.qml
examples/quickcontrols/contactslist/Contact/ContactView.ui.qml
examples/quickcontrols/contactslist/Contact/SectionDelegate.ui.qml
examples/quickcontrols/contactslist/contactmodel.py
examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml
examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml
examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml
examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml
examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml
examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir
examples/quickcontrols/gallery/gallery.py
examples/quickcontrols/gallery/gallery.pyproject
examples/quickcontrols/gallery/gallery.qml
examples/quickcontrols/gallery/gallery.qrc
examples/quickcontrols/gallery/pages/BusyIndicatorPage.qml
examples/quickcontrols/gallery/pages/ButtonPage.qml
examples/quickcontrols/gallery/pages/CheckBoxPage.qml
examples/quickcontrols/gallery/pages/ComboBoxPage.qml
examples/quickcontrols/gallery/pages/DelayButtonPage.qml
examples/quickcontrols/gallery/pages/DelegatePage.qml
examples/quickcontrols/gallery/pages/DialPage.qml
examples/quickcontrols/gallery/pages/DialogPage.qml
examples/quickcontrols/gallery/pages/FramePage.qml
examples/quickcontrols/gallery/pages/GalleryConfig.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/GroupBoxPage.qml
examples/quickcontrols/gallery/pages/MenuBarPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/MonthGridPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/PageIndicatorPage.qml
examples/quickcontrols/gallery/pages/ProgressBarPage.qml
examples/quickcontrols/gallery/pages/RadioButtonPage.qml
examples/quickcontrols/gallery/pages/RangeSliderPage.qml
examples/quickcontrols/gallery/pages/ScrollBarPage.qml
examples/quickcontrols/gallery/pages/ScrollIndicatorPage.qml
examples/quickcontrols/gallery/pages/SearchFieldPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/SliderPage.qml
examples/quickcontrols/gallery/pages/SpinBoxPage.qml
examples/quickcontrols/gallery/pages/SplitViewPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/StackViewPage.qml
examples/quickcontrols/gallery/pages/SwipeViewPage.qml
examples/quickcontrols/gallery/pages/SwitchPage.qml
examples/quickcontrols/gallery/pages/TabBarPage.qml
examples/quickcontrols/gallery/pages/TableViewPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/TextAreaPage.qml
examples/quickcontrols/gallery/pages/TextFieldPage.qml
examples/quickcontrols/gallery/pages/ToolBarPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/ToolTipPage.qml
examples/quickcontrols/gallery/pages/TreeViewPage.qml [new file with mode: 0644]
examples/quickcontrols/gallery/pages/TumblerPage.qml
examples/quickcontrols/gallery/qmldir
examples/quickcontrols/gallery/rc_gallery.py
examples/remoteobjects/modelview/doc/modelview.png [new file with mode: 0644]
examples/samplebinding/CMakeLists.txt
examples/samplebinding/doc/samplebinding.pyproject
examples/samplebinding/doc/samplebinding.rst
examples/scriptableapplication/CMakeLists.txt
examples/scriptableapplication/pythonutils.cpp
examples/tutorials/drumpad/final_project/.gitignore [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad.qmlproject [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad.qrc [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/AvailableSoundsComboBox.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/CenteredFlow.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/Constants.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/PadButton.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/SoundEffectPlayer.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/StyledSpinBox.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/VolumeSlider.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Drumpad/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/final_project/DrumpadContent/App.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/DrumpadContent/MainScreen.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/DrumpadContent/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Mocks/Audio/AudioEngine.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Mocks/Audio/WaveformItem.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Mocks/Audio/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Mocks/Components/AudioFilesModel.qml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Mocks/Components/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/audio/__init__.py [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/audio/audio_engine.py [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/audio/audio_files_model.py [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/audio/waveform_item.py [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/autogen/settings.py [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/main.py [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Python/pyproject.toml [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Sounds/Bongo Loop 125bpm.wav [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Sounds/Clap.wav [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Sounds/Closed Hat.wav [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Sounds/Kick Drum.wav [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Sounds/Open Hat.wav [new file with mode: 0644]
examples/tutorials/drumpad/final_project/Sounds/Sine Bass Ebm.wav [new file with mode: 0644]
examples/tutorials/drumpad/final_project/doc/final_project.md [new file with mode: 0644]
examples/tutorials/drumpad/final_project/qtquickcontrols2.conf [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/.gitignore [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad.qmlproject [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad.qrc [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/AvailableSoundsComboBox.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/CenteredFlow.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/Constants.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/PadButton.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/SoundEffectPlayer.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/StyledSpinBox.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/VolumeSlider.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Drumpad/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/DrumpadContent/App.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/DrumpadContent/MainScreen.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/DrumpadContent/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Mocks/Audio/AudioEngine.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Mocks/Audio/WaveformItem.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Mocks/Audio/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Mocks/Components/AudioFilesModel.qml [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Mocks/Components/qmldir [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/Sounds/Clap.wav [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/doc/drumpad_initial_project.pyproject [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/doc/initial_project.md [new file with mode: 0644]
examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf [new file with mode: 0644]
examples/utils/pyside_config.py
examples/webenginequick/nanobrowser/ApplicationRoot.qml
examples/webenginequick/nanobrowser/BrowserWindow.qml
examples/webenginequick/nanobrowser/DownloadView.qml
examples/webenginequick/nanobrowser/FindBar.qml
examples/webenginequick/nanobrowser/FullScreenNotification.qml
examples/webenginequick/nanobrowser/WebAuthDialog.qml [new file with mode: 0644]
examples/webenginequick/nanobrowser/nanobrowser.pyproject
examples/webenginequick/nanobrowser/qmldir [new file with mode: 0644]
examples/webenginequick/nanobrowser/rc_resources.py
examples/widgetbinding/CMakeLists.txt
examples/widgetbinding/doc/widgetbinding.md
examples/widgetbinding/doc/widgetbinding.pyproject
examples/widgets/animation/easing/easing.py
examples/widgets/dialogs/standarddialogs/standarddialogs.py
examples/widgets/draganddrop/draggabletext/draggabletext.py
examples/widgets/itemviews/rangemodel/doc/rangemodel.rst [new file with mode: 0644]
examples/widgets/itemviews/rangemodel/main.py [new file with mode: 0644]
examples/widgets/itemviews/rangemodel/rangemodel.pyproject [new file with mode: 0644]
examples/widgets/mainwindows/mdi/mdi.py
examples/widgets/widgetsgallery/widgetgallery.py
examples/xml/dombookmarks/dombookmarks.py
requirements-doc.txt
sources/pyside-tools/cmake/PySideAndroid.cmake
sources/pyside-tools/project_lib/pyproject_toml.py
sources/pyside6/.cmake.conf
sources/pyside6/CMakeLists.txt
sources/pyside6/PySide6/CMakeLists.txt
sources/pyside6/PySide6/QtAsyncio/events.py
sources/pyside6/PySide6/QtAsyncio/tasks.py
sources/pyside6/PySide6/QtCore/CMakeLists.txt
sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp
sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h
sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp
sources/pyside6/PySide6/QtCore/typesystem_core_common.xml
sources/pyside6/PySide6/QtDBus/CMakeLists.txt
sources/pyside6/PySide6/QtDataVisualization/CMakeLists.txt
sources/pyside6/PySide6/QtDesigner/CMakeLists.txt
sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp
sources/pyside6/PySide6/QtGraphs/CMakeLists.txt
sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml
sources/pyside6/PySide6/QtGui/CMakeLists.txt
sources/pyside6/PySide6/QtGui/typesystem_gui.xml
sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml
sources/pyside6/PySide6/QtGui/typesystem_gui_nativeinterface.xml [new file with mode: 0644]
sources/pyside6/PySide6/QtGui/typesystem_gui_rhi.xml
sources/pyside6/PySide6/QtGui/typesystem_gui_wayland.xml [new file with mode: 0644]
sources/pyside6/PySide6/QtMultimedia/CMakeLists.txt
sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml
sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp
sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp
sources/pyside6/PySide6/QtSql/typesystem_sql.xml
sources/pyside6/PySide6/QtUiTools/typesystem_uitools.xml
sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt
sources/pyside6/PySide6/QtWebEngineCore/typesystem_webenginecore.xml
sources/pyside6/PySide6/QtWebView/CMakeLists.txt
sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml
sources/pyside6/PySide6/QtXml/typesystem_xml.xml
sources/pyside6/PySide6/__init__.py.in
sources/pyside6/PySide6/doc/qtcore.rst
sources/pyside6/PySide6/global.h.in [deleted file]
sources/pyside6/PySide6/glue/qtcore.cpp
sources/pyside6/PySide6/glue/qtgui.cpp
sources/pyside6/PySide6/glue/qtmultimedia.cpp
sources/pyside6/PySide6/glue/qttest.cpp
sources/pyside6/PySide6/glue/qtuitools.cpp
sources/pyside6/PySide6/pyside6_global.h [new file with mode: 0644]
sources/pyside6/PySide6/templates/core_common.xml
sources/pyside6/cmake/Macros/PySideModules.cmake
sources/pyside6/cmake/PySideHelpers.cmake
sources/pyside6/cmake/PySideSetup.cmake
sources/pyside6/doc/CMakeLists.txt
sources/pyside6/doc/_tags/android.rst
sources/pyside6/doc/additionaldocs.lst
sources/pyside6/doc/building_from_source/index.rst
sources/pyside6/doc/building_from_source/linux.rst
sources/pyside6/doc/building_from_source/macOS.rst
sources/pyside6/doc/building_from_source/windows.rst
sources/pyside6/doc/conf.py.in
sources/pyside6/doc/deployment/deployment-pyside6-android-deploy.rst
sources/pyside6/doc/developer/add_port_example.rst
sources/pyside6/doc/developer/documentation.rst
sources/pyside6/doc/developer/extras.rst
sources/pyside6/doc/developer/index.rst
sources/pyside6/doc/developer/pythonversions.md [new file with mode: 0644]
sources/pyside6/doc/extras/QtCore.Property.rst
sources/pyside6/doc/extras/QtQuick.rst
sources/pyside6/doc/gettingstarted.rst
sources/pyside6/doc/tools/index.rst
sources/pyside6/doc/tools/vscode-ext.rst [new file with mode: 0644]
sources/pyside6/doc/tutorials/basictutorial/qrcfiles.rst
sources/pyside6/doc/tutorials/basictutorial/uifiles.rst
sources/pyside6/doc/tutorials/datavisualize/add_chart.rst
sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst
sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst
sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject [new file with mode: 0644]
sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject [new file with mode: 0644]
sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject [new file with mode: 0644]
sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject [new file with mode: 0644]
sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py
sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py
sources/pyside6/doc/tutorials/datavisualize/filter_data.rst
sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst
sources/pyside6/doc/tutorials/datavisualize/read_data.rst
sources/pyside6/doc/tutorials/debugging/qml_debugging.rst
sources/pyside6/doc/tutorials/drumpad/index.md [new file with mode: 0644]
sources/pyside6/doc/tutorials/drumpad/resources/design_studio_enable_python_generator.png [new file with mode: 0644]
sources/pyside6/doc/tutorials/drumpad/resources/design_studio_main_screen.png [new file with mode: 0644]
sources/pyside6/doc/tutorials/drumpad/resources/drumpad.png [new file with mode: 0644]
sources/pyside6/doc/tutorials/finance_manager/part1/part1.md
sources/pyside6/doc/tutorials/index.rst
sources/pyside6/doc/tutorials/qmlapp/main.py
sources/pyside6/doc/tutorials/qmlapp/newpyproject.png [deleted file]
sources/pyside6/doc/tutorials/qmlapp/newpyproject.webp [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/projectsmode.png [deleted file]
sources/pyside6/doc/tutorials/qmlapp/projectsmode.webp [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/pyproject.toml [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/pyprojname.png [deleted file]
sources/pyside6/doc/tutorials/qmlapp/pyprojname.webp [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png [deleted file]
sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.webp [new file with mode: 0644]
sources/pyside6/doc/tutorials/qmlapp/qmlapplication.rst
sources/pyside6/libpyside/CMakeLists.txt
sources/pyside6/libpyside/PySide6Config-spec.cmake.in
sources/pyside6/libpyside/class_property.cpp
sources/pyside6/libpyside/dynamicqmetaobject.cpp
sources/pyside6/libpyside/dynamicqmetaobject.h
sources/pyside6/libpyside/dynamicslot.cpp
sources/pyside6/libpyside/feature_select.cpp
sources/pyside6/libpyside/pyside.cpp
sources/pyside6/libpyside/pyside6.pc.in
sources/pyside6/libpyside/pyside_p.h
sources/pyside6/libpyside/pysideclassinfo.cpp
sources/pyside6/libpyside/pysidemetafunction.cpp
sources/pyside6/libpyside/pysideproperty.cpp
sources/pyside6/libpyside/pysideqenum.cpp
sources/pyside6/libpyside/pysideqenum.h
sources/pyside6/libpyside/pysideqobject.h
sources/pyside6/libpyside/pysidesignal.cpp
sources/pyside6/libpyside/pysideslot.cpp
sources/pyside6/libpyside/pysideutils.h
sources/pyside6/libpyside/pysidevariantutils.cpp [new file with mode: 0644]
sources/pyside6/libpyside/pysidevariantutils.h [new file with mode: 0644]
sources/pyside6/libpyside/qobjectconnect.cpp
sources/pyside6/libpyside/signalmanager.cpp
sources/pyside6/libpysideqml/CMakeLists.txt
sources/pyside6/libpysideqml/PySide6QmlConfig-spec.cmake.in [deleted file]
sources/pyside6/libpysideqml/PySide6QmlConfig.cmake.in [deleted file]
sources/pyside6/libpysideqml/PySide6QmlConfigVersion.cmake.in [deleted file]
sources/pyside6/libpysideqml/pysideqmlattached.cpp
sources/pyside6/libpysideqml/pysideqmlextended.cpp
sources/pyside6/libpysideqml/pysideqmlforeign.cpp
sources/pyside6/libpysideqml/pysideqmllistproperty.cpp
sources/pyside6/libpysideqml/pysideqmlmetacallerror.cpp
sources/pyside6/libpysideqml/pysideqmlnamedelement.cpp
sources/pyside6/libpysideqml/pysideqmlregistertype.cpp
sources/pyside6/libpysideqml/pysideqmltypeinfo.cpp
sources/pyside6/libpysideqml/pysideqmluncreatable.cpp
sources/pyside6/libpysideremoteobjects/CMakeLists.txt
sources/pyside6/libpysideremoteobjects/pysidecapsulemethod.cpp
sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp
sources/pyside6/libpysideremoteobjects/pysidedynamiccommon.cpp
sources/pyside6/libpysideremoteobjects/pysidedynamicpod.cpp
sources/pyside6/libpysideremoteobjects/pysiderephandler.cpp
sources/pyside6/plugins/designer/CMakeLists.txt
sources/pyside6/plugins/designer/designercustomwidgets.cpp
sources/pyside6/plugins/uitools/CMakeLists.txt
sources/pyside6/plugins/uitools/customwidget.cpp
sources/pyside6/qtexampleicons/CMakeLists.txt [deleted file]
sources/pyside6/qtexampleicons/module.c [deleted file]
sources/pyside6/tests/CMakeLists.txt
sources/pyside6/tests/QtAsyncio/CMakeLists.txt
sources/pyside6/tests/QtAsyncio/bug_2790.py
sources/pyside6/tests/QtAsyncio/bug_2799.py
sources/pyside6/tests/QtAsyncio/qasyncio_test.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_task.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_cancel_taskgroup.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_chain.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_executor.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_queues.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_threadsafe.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_time.py
sources/pyside6/tests/QtAsyncio/qasyncio_test_uncancel.py
sources/pyside6/tests/QtCore/CMakeLists.txt
sources/pyside6/tests/QtCore/deepcopy_test.py
sources/pyside6/tests/QtCore/qenum_designer_test.py [new file with mode: 0644]
sources/pyside6/tests/QtCore/qenum_test.py
sources/pyside6/tests/QtCore/qobject_event_filter_test.py
sources/pyside6/tests/QtCore/qobject_parent_test.py
sources/pyside6/tests/QtCore/qobject_property_test.py
sources/pyside6/tests/QtCore/qrangemodel_test.py [new file with mode: 0644]
sources/pyside6/tests/QtCore/repr_test.py
sources/pyside6/tests/QtCore/tr_noop_test.py
sources/pyside6/tests/QtDBus/CMakeLists.txt
sources/pyside6/tests/QtGui/CMakeLists.txt
sources/pyside6/tests/QtGui/nativeinterface_test.py [new file with mode: 0644]
sources/pyside6/tests/QtGui/qmatrix_test.py [new file with mode: 0644]
sources/pyside6/tests/QtGui/qpolygonf_test.py
sources/pyside6/tests/QtGui/qstandarditemmodel_test.py
sources/pyside6/tests/QtStateMachine/qabstracttransition_test.py
sources/pyside6/tests/QtSvg/qsvggenerator_test.py
sources/pyside6/tests/QtUiTools/bug_909.py
sources/pyside6/tests/QtWidgets/bug_750.py
sources/pyside6/tests/QtWidgets/bug_811.py
sources/pyside6/tests/QtWidgets/keep_reference_test.py
sources/pyside6/tests/QtWidgets/qaccessible_test.py
sources/pyside6/tests/QtWidgets/qpicture_test.py
sources/pyside6/tests/QtWidgets/qwidget_test.py
sources/pyside6/tests/QtWidgets/reference_count_test.py
sources/pyside6/tests/QtXml/qdomdocument_test.py
sources/pyside6/tests/pysidetest/CMakeLists.txt
sources/pyside6/tests/pysidetest/enum_test.py
sources/pyside6/tests/pysidetest/mypy_correctness_test.py
sources/pyside6/tests/pysidetest/properties_test.py
sources/pyside6/tests/pysidetest/qvariant_test.py
sources/pyside6/tests/pysidetest/snake_case_imported.py [new file with mode: 0644]
sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py [new file with mode: 0644]
sources/pyside6/tests/pysidetest/snake_case_sub.py [deleted file]
sources/pyside6/tests/pysidetest/snake_case_test.py
sources/pyside6/tests/pysidetest/testqvariantenum.cpp
sources/pyside6/tests/pysidetest/testqvariantenum.h
sources/pyside6/tests/signals/disconnect_test.py
sources/pyside6/tests/signals/qobject_sender_test.py
sources/pyside6/tests/signals/samenamesender.py [new file with mode: 0644]
sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
sources/pyside6/tests/tools/pyside6-project/example_project/subproject/pyproject.toml
sources/pyside6/tests/tools/pyside6-project/example_project/subproject/subproject.pyproject
sources/pyside6/tests/tools/pyside6-project/existing_pyproject_toml/expected_pyproject.toml
sources/pyside6/tests/tools/pyside6-project/test_pyside6_project.py
sources/shiboken6/.cmake.conf
sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp
sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h
sources/shiboken6/ApiExtractor/abstractmetaenum.cpp
sources/shiboken6/ApiExtractor/abstractmetaenum.h
sources/shiboken6/ApiExtractor/abstractmetalang.cpp
sources/shiboken6/ApiExtractor/addedfunction.cpp
sources/shiboken6/ApiExtractor/apiextractor.cpp
sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp
sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp
sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp
sources/shiboken6/ApiExtractor/clangparser/compilersupport.h
sources/shiboken6/ApiExtractor/classdocumentation.cpp
sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp
sources/shiboken6/ApiExtractor/conditionalstreamreader.h
sources/shiboken6/ApiExtractor/customconversion.cpp
sources/shiboken6/ApiExtractor/customconversion.h
sources/shiboken6/ApiExtractor/documentation.h
sources/shiboken6/ApiExtractor/enumtypeentry.h
sources/shiboken6/ApiExtractor/fileout.cpp
sources/shiboken6/ApiExtractor/messages.cpp
sources/shiboken6/ApiExtractor/parser/codemodel.cpp
sources/shiboken6/ApiExtractor/parser/codemodel.h
sources/shiboken6/ApiExtractor/parser/codemodel_enums.h
sources/shiboken6/ApiExtractor/parser/typeinfo.cpp
sources/shiboken6/ApiExtractor/parser/typeinfo.h
sources/shiboken6/ApiExtractor/predefined_templates.cpp
sources/shiboken6/ApiExtractor/qtdocparser.cpp
sources/shiboken6/ApiExtractor/reporthandler.cpp
sources/shiboken6/ApiExtractor/reporthandler.h
sources/shiboken6/ApiExtractor/smartpointertypeentry.h
sources/shiboken6/ApiExtractor/tests/a.xml
sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp
sources/shiboken6/ApiExtractor/tests/testextrainclude.h
sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp
sources/shiboken6/ApiExtractor/tests/testutil.h
sources/shiboken6/ApiExtractor/typedatabase.cpp
sources/shiboken6/ApiExtractor/typesystem.cpp
sources/shiboken6/ApiExtractor/typesystem_enums.h
sources/shiboken6/ApiExtractor/typesystemparser.cpp
sources/shiboken6/cmake/ShibokenHelpers.cmake
sources/shiboken6/data/CMakeLists.txt
sources/shiboken6/data/Shiboken6Config-spec.cmake.in
sources/shiboken6/data/Shiboken6ToolsConfig.cmake.in
sources/shiboken6/data/Shiboken6ToolsMacros.cmake [new file with mode: 0644]
sources/shiboken6/data/shiboken6.pc.in
sources/shiboken6/doc/shibokengenerator.rst
sources/shiboken6/doc/typesystem_arguments.rst
sources/shiboken6/doc/typesystem_codeinjection.rst
sources/shiboken6/doc/typesystem_conversionrule.rst
sources/shiboken6/doc/typesystem_specifying_types.rst
sources/shiboken6/doc/typesystem_variables.rst
sources/shiboken6/generator/CMakeLists.txt
sources/shiboken6/generator/generator.cpp
sources/shiboken6/generator/main.cpp
sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp
sources/shiboken6/generator/qtdoc/qtdocgenerator.h
sources/shiboken6/generator/qtdoc/qtxmltosphinx.cpp
sources/shiboken6/generator/shiboken/cppgenerator.cpp
sources/shiboken6/generator/shiboken/cppgenerator.h
sources/shiboken6/generator/shiboken/cppgenerator_container.cpp
sources/shiboken6/generator/shiboken/cppgenerator_smartpointer.cpp
sources/shiboken6/generator/shiboken/generatorstrings.h
sources/shiboken6/generator/shiboken/headergenerator.cpp
sources/shiboken6/generator/shiboken/shibokengenerator.cpp
sources/shiboken6/generator/shiboken/shibokengenerator.h
sources/shiboken6/libshiboken/CMakeLists.txt
sources/shiboken6/libshiboken/basewrapper.cpp
sources/shiboken6/libshiboken/basewrapper.h
sources/shiboken6/libshiboken/basewrapper_p.h
sources/shiboken6/libshiboken/bindingmanager.cpp
sources/shiboken6/libshiboken/bindingmanager.h
sources/shiboken6/libshiboken/bufferprocs_py37.cpp
sources/shiboken6/libshiboken/bufferprocs_py37.h
sources/shiboken6/libshiboken/helper.cpp
sources/shiboken6/libshiboken/helper.h
sources/shiboken6/libshiboken/pep384impl.cpp
sources/shiboken6/libshiboken/pep384impl.h
sources/shiboken6/libshiboken/sbkbindingutils.cpp
sources/shiboken6/libshiboken/sbkbindingutils.h
sources/shiboken6/libshiboken/sbkcontainer.cpp
sources/shiboken6/libshiboken/sbkcontainer.h
sources/shiboken6/libshiboken/sbkconverter.cpp
sources/shiboken6/libshiboken/sbkconverter.h
sources/shiboken6/libshiboken/sbkconverter_p.h
sources/shiboken6/libshiboken/sbkcppstring.cpp
sources/shiboken6/libshiboken/sbkenum.cpp
sources/shiboken6/libshiboken/sbkerrors.cpp
sources/shiboken6/libshiboken/sbkerrors.h
sources/shiboken6/libshiboken/sbkfeature_base.cpp
sources/shiboken6/libshiboken/sbkmodule.cpp
sources/shiboken6/libshiboken/sbkmodule.h
sources/shiboken6/libshiboken/sbkmodule_p.h [new file with mode: 0644]
sources/shiboken6/libshiboken/sbkpep.h [new file with mode: 0644]
sources/shiboken6/libshiboken/sbkpepbuffer.h [new file with mode: 0644]
sources/shiboken6/libshiboken/sbkpython.h
sources/shiboken6/libshiboken/sbksmartpointer.cpp
sources/shiboken6/libshiboken/sbkstaticstrings.cpp
sources/shiboken6/libshiboken/sbkstaticstrings_p.h
sources/shiboken6/libshiboken/sbkstring.cpp
sources/shiboken6/libshiboken/sbktypefactory.cpp
sources/shiboken6/libshiboken/sbktypefactory.h
sources/shiboken6/libshiboken/shibokenbuffer.cpp
sources/shiboken6/libshiboken/signature/signature.cpp
sources/shiboken6/libshiboken/signature/signature_extend.cpp
sources/shiboken6/libshiboken/signature/signature_globals.cpp
sources/shiboken6/libshiboken/signature/signature_helper.cpp
sources/shiboken6/libshiboken/voidptr.cpp
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/layout.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/enum_sig.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/lib/pyi_generator.py
sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
sources/shiboken6/shibokenmodule/shibokenmodule.cpp
sources/shiboken6/tests/CMakeLists.txt
sources/shiboken6/tests/libminimal/CMakeLists.txt
sources/shiboken6/tests/libminimal/invisiblenamespace.h [new file with mode: 0644]
sources/shiboken6/tests/libsample/bucket.cpp
sources/shiboken6/tests/libsample/bucket.h
sources/shiboken6/tests/libsample/bytearray.cpp
sources/shiboken6/tests/libsample/functions.h
sources/shiboken6/tests/libsample/listuser.cpp
sources/shiboken6/tests/libsample/listuser.h
sources/shiboken6/tests/libsample/point.h
sources/shiboken6/tests/libsample/pointf.h
sources/shiboken6/tests/libsample/samplenamespace.cpp
sources/shiboken6/tests/libsample/samplenamespace.h
sources/shiboken6/tests/minimalbinding/CMakeLists.txt
sources/shiboken6/tests/minimalbinding/global.h
sources/shiboken6/tests/minimalbinding/typesystem_minimal.xml
sources/shiboken6/tests/samplebinding/class_fields_test.py
sources/shiboken6/tests/samplebinding/keep_reference_test.py
sources/shiboken6/tests/samplebinding/list_test.py
sources/shiboken6/tests/samplebinding/namespace_test.py
sources/shiboken6/tests/samplebinding/ownership_delete_parent_test.py
sources/shiboken6/tests/samplebinding/sample_test.py
sources/shiboken6/tests/samplebinding/typesystem_sample.xml
sources/shiboken6/tests/smartbinding/std_optional_test.py
sources/shiboken6/tests/smartbinding/typesystem_smart.xml
testing/runner.py
testing/wheel_tester.py
tools/create_changelog.py
tools/cross_compile_android/main.py
tools/doc_modules.py
tools/example_gallery/main.py
tools/release_notes/main.py
tools/sync_examples.py [new file with mode: 0644]
wheel_artifacts/pyproject.toml.base

index 57e337ac289821149b49248fcf9e2a23e03c4ad6..1819b7e431ff4ea6b3314a51d22228604efbe4d3 100644 (file)
@@ -720,12 +720,12 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin):
             cmake_cmd.append(f"-DCMAKE_CXX_COMPILER_LAUNCHER={compiler_launcher}")
 
         if OPTION["SANITIZE_ADDRESS"]:
+            cmake_cmd.append("-DSANITIZE_ADDRESS=ON")
+        if OPTION["SANITIZE_THREAD"]:
             # Some simple sanity checking. Only use at your own risk.
-            if (sys.platform.startswith('linux')
-                    or sys.platform.startswith('darwin')):
-                cmake_cmd.append("-DSANITIZE_ADDRESS=ON")
-            else:
-                raise SetupError("Address sanitizer can only be used on Linux and macOS.")
+            if sys.platform == "win32" and not self.is_cross_compile:
+                self.warn("Thread sanitizer may not be supported yet.")
+            cmake_cmd.append("-DSANITIZE_THREAD=ON")
 
         if extension.lower() == PYSIDE:
             pyside_qt_conf_prefix = ''
index 5963a3982161402dce5f9670b6b0dda23dd72229..ba4039e99a4fe25a8692b3ae9c55f758f2c090da 100644 (file)
@@ -208,6 +208,7 @@ class CommandMixin:
         ('verbose-build', None, 'Verbose build'),
         ('quiet', None, 'Quiet build'),
         ('sanitize-address', None, 'Build with address sanitizer'),
+        ('sanitize-thread', None, 'Build with thread sanitizer'),
         ('shorter-paths', None, 'Use shorter paths'),
         ('doc-build-online', None, 'Build online documentation'),
         ('qtpaths=', None, 'Path to qtpaths'),
@@ -290,6 +291,7 @@ class CommandMixin:
         self.log_level = "info"
         self.verbose_build = False
         self.sanitize_address = False
+        self.sanitize_thread = False
         self.snapshot_build = False
         self.shorter_paths = False
         self.doc_build_online = False
@@ -444,6 +446,7 @@ class CommandMixin:
                 log.setLevel(logging.DEBUG)
 
         OPTION['SANITIZE_ADDRESS'] = self.sanitize_address
+        OPTION['SANITIZE_THREAD'] = self.sanitize_thread
         OPTION['SHORTER_PATHS'] = self.shorter_paths
         OPTION['DOC_BUILD_ONLINE'] = self.doc_build_online
         if self.unity:
index 04403ba0fb947d8393dfb1df17978476cfcac641..59c63fda3b84181fa876fd731bd4f3810795d968 100644 (file)
@@ -8,7 +8,7 @@ from pathlib import Path
 from ..log import log
 from ..config import config
 from ..options import OPTION
-from ..utils import copydir, copyfile, copy_qt_metatypes, makefile
+from ..utils import (copydir, copyfile, copy_qt_metatypes, makefile, copy_cmake_config_dirs)
 from .. import PYSIDE, SHIBOKEN
 from .linux import prepare_standalone_package_linux
 from .macos import prepare_standalone_package_macos
@@ -108,11 +108,11 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False):
             "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
             force=False, _vars=_vars)
 
-    if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build():
-        # <install>/include/* -> <setup>/{st_package_name}/include
+    if config.is_internal_shiboken_module_build() or config.is_internal_pyside_build():
+        # <install>/{cmake_package_name}/include/* -> <package_for_wheels>/{st_package_name}/include
         copydir(
-            "{install_dir}/include/{cmake_package_name}",
-            "{st_build_dir}/{st_package_name}/include",
+            "{install_dir}/{cmake_package_name}/include",
+            destination_dir / "include",
             _vars=_vars)
 
     if config.is_internal_pyside_build():
@@ -256,6 +256,26 @@ def prepare_packages_posix(pyside_build, _vars, cross_build=False):
             # Copy over clang before rpath patching.
             pyside_build.prepare_standalone_clang(is_win=False)
 
+    # Copy CMake config files
+    if config.is_internal_shiboken_generator_build():
+        # Copy Shiboken6Tools CMake package for generator
+        copy_cmake_config_dirs(
+            _vars["install_dir"], _vars["st_build_dir"],
+            _vars["st_package_name"], "Shiboken6Tools"
+        )
+    elif config.is_internal_shiboken_module_build():
+        # Copy Shiboken6 CMake package for module
+        copy_cmake_config_dirs(
+            _vars["install_dir"], _vars["st_build_dir"],
+            _vars["st_package_name"], "Shiboken6"
+        )
+    elif config.is_internal_pyside_build():
+        # Copy PySide6 CMake package
+        copy_cmake_config_dirs(
+            _vars["install_dir"], _vars["st_build_dir"],
+            _vars["st_package_name"], "PySide6"
+        )
+
     # Update rpath to $ORIGIN
     if (sys.platform.startswith('linux') or sys.platform.startswith('darwin')) and not is_android:
         pyside_build.update_rpath(executables)
index 073f390a43b0d85b06e57e4eeee55da58dd98702..e023ff003f07fb2b014577e4a74ca552841cd55b 100644 (file)
@@ -12,9 +12,8 @@ from pathlib import Path
 from ..log import log
 from ..config import config
 from ..options import OPTION
-from ..utils import (copydir, copyfile, copy_qt_metatypes,
-                     download_and_extract_7z, filter_match, makefile, in_coin,
-                     coin_job_id)
+from ..utils import (copydir, copyfile, copy_qt_metatypes, download_and_extract_7z,
+                     filter_match, makefile, in_coin, coin_job_id, copy_cmake_config_dirs)
 from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_MULTIMEDIA_LIBS
 
 
@@ -95,10 +94,10 @@ def prepare_packages_win32(pyside_build, _vars):
             _filter=pdbs,
             recursive=False, _vars=_vars)
 
-    if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build():
-        # <install>/include/* -> <setup>/{st_package_name}/include
+    if config.is_internal_shiboken_module_build() or config.is_internal_pyside_build():
+        # <install>/{cmake_package_name}/include/* -> <package_for_wheels>/{st_package_name}/include
         copydir(
-            "{install_dir}/include/{cmake_package_name}",
+            "{install_dir}/{cmake_package_name}/include",
             destination_dir / "include",
             _vars=_vars)
 
@@ -202,6 +201,8 @@ def prepare_packages_win32(pyside_build, _vars):
         copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars)
         download_qt_dependency_dlls(_vars, destination_dir, msvc_redist)
 
+    copy_cmake_packages(_vars)
+
 
 # MSVC redistributable file list.
 msvc_redist = [
@@ -440,3 +441,33 @@ def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars):
     if copy_clang or platform.machine() == "ARM64":
         # Qt CI is using dynamic libclang with arm config.
         pyside_build.prepare_standalone_clang(is_win=True)
+
+
+def copy_cmake_packages(_vars):
+    if config.is_internal_shiboken_generator_build():
+        print("copy_cmake_config_dirs called for Shiboken6Tools with:",
+              "_vars['install_dir'] =", _vars["install_dir"],
+              "_vars['st_build_dir'] =", _vars["st_build_dir"],
+              "_vars['st_package_name'] =", _vars["st_package_name"])
+        copy_cmake_config_dirs(
+            _vars["install_dir"], _vars["st_build_dir"],
+            _vars["st_package_name"], "Shiboken6Tools"
+        )
+    elif config.is_internal_shiboken_module_build():
+        print("copy_cmake_config_dirs called for Shiboken6 with:",
+              "_vars['install_dir'] =", _vars["install_dir"],
+              "_vars['st_build_dir'] =", _vars["st_build_dir"],
+              "_vars['st_package_name'] =", _vars["st_package_name"])
+        copy_cmake_config_dirs(
+            _vars["install_dir"], _vars["st_build_dir"],
+            _vars["st_package_name"], "Shiboken6"
+        )
+    elif config.is_internal_pyside_build():
+        print("copy_cmake_config_dirs called for PySide6 with:",
+              "_vars['install_dir'] =", _vars["install_dir"],
+              "_vars['st_build_dir'] =", _vars["st_build_dir"],
+              "_vars['st_package_name'] =", _vars["st_package_name"])
+        copy_cmake_config_dirs(
+            _vars["install_dir"], _vars["st_build_dir"],
+            _vars["st_package_name"], "PySide6"
+        )
index eb0c8b0b673f170b77aaf603088831e982b456b4..3cb7ade0f37850ca2d265e92b53861d786472c98 100644 (file)
@@ -1140,3 +1140,48 @@ def parse_modules(modules: str) -> str:
             module_sub_set += ';'
         module_sub_set += m
     return module_sub_set
+
+
+def copy_cmake_config_dirs(install_dir, st_build_dir, st_package_name, cmake_package_name):
+    """
+    Copy all CMake config directories from <install_dir>/lib/cmake whose names start with
+    <cmake_package_name> (case-insensitive) into <st_build_dir>/<st_package_name>/lib/cmake.
+    """
+    src_cmake_dir = Path(install_dir) / "lib" / "cmake"
+    wheel_cmake_dir = Path(install_dir) / "lib" / "wheels" / "cmake"
+    dst_cmake_dir = Path(st_build_dir) / st_package_name / "lib" / "cmake"
+    dst_cmake_dir.mkdir(parents=True, exist_ok=True)
+
+    for src_path in src_cmake_dir.iterdir():
+        if src_path.is_dir() and (src_path.name.lower() == cmake_package_name.lower()):
+            dst_path = dst_cmake_dir / src_path.name
+            if dst_path.exists():
+                shutil.rmtree(dst_path)
+            dst_path.mkdir(parents=True)
+
+            # check for wheel target files
+            wheel_path = wheel_cmake_dir / src_path.name
+            wheel_targets_exist = {}
+            if wheel_path.exists():
+                for item in wheel_path.iterdir():
+                    if item.is_file() and re.search(r"Targets(-.*)?\.cmake$", item.name):
+                        base_name = item.name.split('Targets')[0]
+                        if base_name in ("PySide6", "Shiboken6", "Shiboken6Tools"):
+                            wheel_targets_exist[base_name] = True
+                            # Copy wheel target file
+                            shutil.copy2(str(item), str(dst_path / item.name))
+
+            # Copy remaining files
+            for item in src_path.iterdir():
+                if item.is_file():
+                    skip_file = False
+                    if re.search(r"Targets(-.*)?\.cmake$", item.name):
+                        base_name = item.name.split('Targets')[0]
+                        is_pyside_shiboken = base_name in ("PySide6", "Shiboken6", "Shiboken6Tools")
+                        if is_pyside_shiboken and base_name in wheel_targets_exist:
+                            skip_file = True
+
+                    if not skip_file:
+                        shutil.copy2(str(item), str(dst_path / item.name))
+                elif item.is_dir():
+                    shutil.copytree(str(item), str(dst_path / item.name))
index e9b936f100504d816a44813b9fe960f733c6910b..6aaa66567acb550d4d8b5a70c0da24441213dda2 100644 (file)
@@ -91,7 +91,7 @@ class ModuleData:
         self.glue.append(f"qt{_lo}.cpp")
         self.doc_glue.append(f"qt{_lo}.rst")
         if not len(self.metatypes):
-            self.metatypes.append(f"qt6{_lo}_relwithdebinfo_metatypes.json")
+            self.metatypes.append(f"qt6{_lo}_metatypes.json")
 
     # The PySide6 directory that gets packaged by the build_scripts
     # 'prepare_packages()' has a certain structure that depends on
@@ -106,9 +106,7 @@ class ModuleData:
             self.qtlib = [f"{i}.*dll".replace("lib", "") for i in self.qtlib]
             self.qml = [f"qml/{i}" for i in self.qml]
             self.translations = [f"translations/{i}" for i in self.translations]
-            self.metatypes = [
-                f"metatypes/{i}".replace("_relwithdebinfo", "") for i in self.metatypes
-            ]
+            self.metatypes = [f"metatypes/{i}" for i in self.metatypes]
             self.plugins = [f"plugins/{i}" for i in self.plugins]
         else:
             if sys.platform == "darwin":
@@ -178,7 +176,6 @@ def wheel_files_pyside_essentials() -> list[ModuleData]:
         module_QtSvg(),
         module_QtSvgWidgets(),
         module_QtUiTools(),
-        module_QtExampleIcons(),
         # Only for plugins
         module_QtWayland(),
         # there are no bindings for these modules, but their binaries are
@@ -319,6 +316,10 @@ def module_QtCore() -> ModuleData:
     data.qtlib.append("libicuuc*")
     data.qtlib.append("libicuio*")
 
+    # add the include folders for libpyside binaries
+    # this is useful for downstream cmake projects like QtBridges
+    data.include.append("pyside6/*.h")
+
     return data
 
 
@@ -327,17 +328,19 @@ def module_QtGui() -> ModuleData:
     _typesystems = [
         "gui_common.xml",
         "typesystem_gui_common.xml",
+        "typesystem_gui_nativeinterface.xml",
         "typesystem_gui_mac.xml",
         "typesystem_gui_win.xml",
         "typesystem_gui_x11.xml",
-        "typesystem_gui_rhi.xml"
+        "typesystem_gui_rhi.xml",
+        "typesystem_gui_wayland.xml"
     ]
 
     _metatypes = [
-        "qt6eglfsdeviceintegrationprivate_relwithdebinfo_metatypes.json",
-        "qt6eglfskmssupportprivate_relwithdebinfo_metatypes.json",
-        "qt6kmssupportprivate_relwithdebinfo_metatypes.json",
-        "qt6xcbqpaprivate_relwithdebinfo_metatypes.json",
+        "qt6eglfsdeviceintegrationprivate_metatypes.json",
+        "qt6eglfskmssupportprivate_metatypes.json",
+        "qt6kmssupportprivate_metatypes.json",
+        "qt6xcbqpaprivate_metatypes.json",
     ]
 
     _qtlib = [
@@ -408,7 +411,7 @@ def module_QtDBus() -> ModuleData:
 def module_QtDesigner() -> ModuleData:
     data = ModuleData("Designer")
     data.qtlib.append("libQt6DesignerComponents")
-    data.metatypes.append("qt6designercomponentsprivate_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6designercomponentsprivate_metatypes.json")
     json_data = get_module_json_data("Designer")
     data.plugins = get_module_plugins(json_data)
     data.extra_files.append("Qt/plugins/assetimporters/libuip*")
@@ -483,25 +486,25 @@ def module_QtQml() -> ModuleData:
     ]
 
     _metatypes = [
-        "qt6labsanimation_relwithdebinfo_metatypes.json",
-        "qt6labsfolderlistmodel_relwithdebinfo_metatypes.json",
-        "qt6labsqmlmodels_relwithdebinfo_metatypes.json",
-        "qt6labssettings_relwithdebinfo_metatypes.json",
-        "qt6labssharedimage_relwithdebinfo_metatypes.json",
-        "qt6labswavefrontmesh_relwithdebinfo_metatypes.json",
-        "qt6packetprotocolprivate_relwithdebinfo_metatypes.json",
-        "qt6qmlcompilerprivate_relwithdebinfo_metatypes.json",
-        "qt6qmlcompilerplusprivate_relwithdebinfo_metatypes.json",
-        "qt6qmlcore_relwithdebinfo_metatypes.json",
-        "qt6qmldebugprivate_relwithdebinfo_metatypes.json",
-        "qt6qmldomprivate_relwithdebinfo_metatypes.json",
-        "qt6qmllintprivate_relwithdebinfo_metatypes.json",
-        "qt6qmllocalstorage_relwithdebinfo_metatypes.json",
-        "qt6qmlmodels_relwithdebinfo_metatypes.json",
-        "qt6qmlworkerscript_relwithdebinfo_metatypes.json",
-        "qt6qmlxmllistmodel_relwithdebinfo_metatypes.json",
-        "qt6qmlmeta_relwithdebinfo_metatypes.json",
-        "qt6labsplatform_relwithdebinfo_metatypes.json",
+        "qt6labsanimation_metatypes.json",
+        "qt6labsfolderlistmodel_metatypes.json",
+        "qt6labsqmlmodels_metatypes.json",
+        "qt6labssettings_metatypes.json",
+        "qt6labssharedimage_metatypes.json",
+        "qt6labswavefrontmesh_metatypes.json",
+        "qt6packetprotocolprivate_metatypes.json",
+        "qt6qmlcompilerprivate_metatypes.json",
+        "qt6qmlcompilerplusprivate_metatypes.json",
+        "qt6qmlcore_metatypes.json",
+        "qt6qmldebugprivate_metatypes.json",
+        "qt6qmldomprivate_metatypes.json",
+        "qt6qmllintprivate_metatypes.json",
+        "qt6qmllocalstorage_metatypes.json",
+        "qt6qmlmodels_metatypes.json",
+        "qt6qmlworkerscript_metatypes.json",
+        "qt6qmlxmllistmodel_metatypes.json",
+        "qt6qmlmeta_metatypes.json",
+        "qt6labsplatform_metatypes.json",
     ]
 
     _qml = [
@@ -553,22 +556,22 @@ def module_QtQml() -> ModuleData:
 def module_QtQuick() -> ModuleData:
     data = ModuleData("Quick")
     _metatypes = [
-        "qt6quickcontrolstestutilsprivate_relwithdebinfo_metatypes.json",
-        "qt6quickdialogs2_relwithdebinfo_metatypes.json",
-        "qt6quickdialogs2quickimpl_relwithdebinfo_metatypes.json",
-        "qt6quickdialogs2utils_relwithdebinfo_metatypes.json",
-        "qt6quickeffectsprivate_relwithdebinfo_metatypes.json",
-        "qt6quicketest_relwithdebinfo_metatypes.json",
-        "qt6quicketestutilsprivate_relwithdebinfo_metatypes.json",
-        "qt6quicklayouts_relwithdebinfo_metatypes.json",
-        "qt6quickparticlesprivate_relwithdebinfo_metatypes.json",
-        "qt6quickshapesprivate_relwithdebinfo_metatypes.json",
-        "qt6quicktemplates2_relwithdebinfo_metatypes.json",
-        "qt6quicktest_relwithdebinfo_metatypes.json",
-        "qt6quicktestutilsprivate_relwithdebinfo_metatypes.json",
-        "qt6quicktimeline_relwithdebinfo_metatypes.json",
-        "qt6quickvectorimage_relwithdebinfo_metatypes.json",
-        "qt6quickvectorimagegeneratorprivate_relwithdebinfo_metatypes.json",
+        "qt6quickcontrolstestutilsprivate_metatypes.json",
+        "qt6quickdialogs2_metatypes.json",
+        "qt6quickdialogs2quickimpl_metatypes.json",
+        "qt6quickdialogs2utils_metatypes.json",
+        "qt6quickeffectsprivate_metatypes.json",
+        "qt6quicketest_metatypes.json",
+        "qt6quicketestutilsprivate_metatypes.json",
+        "qt6quicklayouts_metatypes.json",
+        "qt6quickparticlesprivate_metatypes.json",
+        "qt6quickshapesprivate_metatypes.json",
+        "qt6quicktemplates2_metatypes.json",
+        "qt6quicktest_metatypes.json",
+        "qt6quicktestutilsprivate_metatypes.json",
+        "qt6quicktimeline_metatypes.json",
+        "qt6quickvectorimage_metatypes.json",
+        "qt6quickvectorimagegeneratorprivate_metatypes.json",
     ]
     _qtlib = [
         "libQt6QuickEffects",
@@ -583,7 +586,8 @@ def module_QtQuick() -> ModuleData:
         "libQt6QuickTimeline",
         "libQt6QuickTimelineBlendTrees",
         "libQt6QuickVectorImage",
-        "libQt6QuickVectorImageGenerator"
+        "libQt6QuickVectorImageGenerator",
+        "libQt6QuickVectorImageHelpers"
     ]
 
     data.qtlib.extend(_qtlib)
@@ -618,7 +622,7 @@ def module_QtQuickControls2() -> ModuleData:
         data.qtlib.append("libQt6QuickControls2IOSStyleImpl")
         data.qtlib.append("libQt6QuickControls2MacOSStyleImpl")
 
-    data.metatypes.append("qt6quickcontrols2impl_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6quickcontrols2impl_metatypes.json")
 
     return data
 
@@ -690,9 +694,9 @@ def module_QtWayland() -> ModuleData:
     ]
 
     _metatypes = [
-        "qt6waylandclient_relwithdebinfo_metatypes.json",
-        "qt6waylandeglclienthwintegrationprivate_relwithdebinfo_metatypes.json",
-        "qt6wlshellintegrationprivate_relwithdebinfo_metatypes.json",
+        "qt6waylandclient_metatypes.json",
+        "qt6waylandeglclienthwintegrationprivate_metatypes.json",
+        "qt6wlshellintegrationprivate_metatypes.json",
     ]
 
     data.qtlib.extend(_qtlib)
@@ -777,24 +781,24 @@ def module_QtQuick3D() -> ModuleData:
     ]
 
     _metatypes = [
-        "qt63dquick_relwithdebinfo_metatypes.json",
-        "qt63dquickanimation_relwithdebinfo_metatypes.json",
-        "qt63dquickextras_relwithdebinfo_metatypes.json",
-        "qt63dquickinput_relwithdebinfo_metatypes.json",
-        "qt63dquickrender_relwithdebinfo_metatypes.json",
-        "qt63dquickscene2d_relwithdebinfo_metatypes.json",
-        "qt6quick3dassetimport_relwithdebinfo_metatypes.json",
-        "qt6quick3dassetutils_relwithdebinfo_metatypes.json",
-        "qt6quick3deffects_relwithdebinfo_metatypes.json",
-        "qt6quick3dglslparserprivate_relwithdebinfo_metatypes.json",
-        "qt6quick3dhelpers_relwithdebinfo_metatypes.json",
-        "qt6quick3diblbaker_relwithdebinfo_metatypes.json",
-        "qt6quick3dparticleeffects_relwithdebinfo_metatypes.json",
-        "qt6quick3dparticles_relwithdebinfo_metatypes.json",
-        "qt6quick3druntimerender_relwithdebinfo_metatypes.json",
-        "qt6quick3dutils_relwithdebinfo_metatypes.json",
-        "qt6shadertools_relwithdebinfo_metatypes.json",
-        "qt6quick3dxr_relwithdebinfo_metatypes.json"
+        "qt63dquick_metatypes.json",
+        "qt63dquickanimation_metatypes.json",
+        "qt63dquickextras_metatypes.json",
+        "qt63dquickinput_metatypes.json",
+        "qt63dquickrender_metatypes.json",
+        "qt63dquickscene2d_metatypes.json",
+        "qt6quick3dassetimport_metatypes.json",
+        "qt6quick3dassetutils_metatypes.json",
+        "qt6quick3deffects_metatypes.json",
+        "qt6quick3dglslparserprivate_metatypes.json",
+        "qt6quick3dhelpers_metatypes.json",
+        "qt6quick3diblbaker_metatypes.json",
+        "qt6quick3dparticleeffects_metatypes.json",
+        "qt6quick3dparticles_metatypes.json",
+        "qt6quick3druntimerender_metatypes.json",
+        "qt6quick3dutils_metatypes.json",
+        "qt6shadertools_metatypes.json",
+        "qt6quick3dxr_metatypes.json"
     ]
 
     json_data = get_module_json_data("Quick3DAssetImport")
@@ -844,7 +848,7 @@ def module_QtWebEngineWidgets() -> ModuleData:
 def module_QtWebEngineQuick() -> ModuleData:
     data = ModuleData("WebEngineQuick")
     data.qtlib.append("libQt6WebEngineQuickDelegatesQml")
-    data.metatypes.append("qt6webenginequickdelegatesqml_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6webenginequickdelegatesqml_metatypes.json")
 
     return data
 
@@ -852,7 +856,7 @@ def module_QtWebEngineQuick() -> ModuleData:
 def module_QtCharts() -> ModuleData:
     data = ModuleData("Charts")
     data.qtlib.append("libQt6ChartsQml")
-    data.metatypes.append("qt6chartsqml_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6chartsqml_metatypes.json")
 
     return data
 
@@ -860,7 +864,7 @@ def module_QtCharts() -> ModuleData:
 def module_QtDataVisualization() -> ModuleData:
     data = ModuleData("DataVisualization")
     data.qtlib.append("libQt6DataVisualizationQml")
-    data.metatypes.append("qt6datavisualizationqml_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6datavisualizationqml_metatypes.json")
     data.typesystems.append("datavisualization_common.xml")
 
     return data
@@ -881,7 +885,7 @@ def module_QtGraphsWidgets() -> ModuleData:
 def module_QtMultimedia() -> ModuleData:
     data = ModuleData("Multimedia")
     data.qtlib.append("libQt6MultimediaQuick")
-    data.metatypes.append("qt6multimediaquickprivate_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6multimediaquickprivate_metatypes.json")
 
     json_data = get_module_json_data("Multimedia")
     data.translations.append("qtmultimedia_*")
@@ -914,7 +918,7 @@ def module_QtNetworkAuth() -> ModuleData:
 def module_QtPositioning() -> ModuleData:
     data = ModuleData("Positioning")
     data.qtlib.append("libQt6PositioningQuick")
-    data.metatypes.append("qt6positioningquick_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6positioningquick_metatypes.json")
     json_data = get_module_json_data("Positioning")
     data.plugins = get_module_plugins(json_data)
 
@@ -924,7 +928,7 @@ def module_QtPositioning() -> ModuleData:
 def module_QtRemoteObjects() -> ModuleData:
     data = ModuleData("RemoteObjects")
     data.qtlib.append("libQt6RemoteObjectsQml")
-    data.metatypes.append("qt6remoteobjectsqml_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6remoteobjectsqml_metatypes.json")
 
     return data
 
@@ -932,7 +936,7 @@ def module_QtRemoteObjects() -> ModuleData:
 def module_QtSensors() -> ModuleData:
     data = ModuleData("Sensors")
     data.qtlib.append("libQt6SensorsQuick")
-    data.metatypes.append("qt6sensorsquick_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6sensorsquick_metatypes.json")
     json_data = get_module_json_data("Sensors")
     data.plugins = get_module_plugins(json_data)
 
@@ -948,7 +952,7 @@ def module_QtSerialPort() -> ModuleData:
 
 def module_QtSpatialAudio() -> ModuleData:
     data = ModuleData("SpatialAudio")
-    data.metatypes.append("qt6spatialaudio_debug_metatypes.json")
+    data.metatypes.append("qt6spatialaudio_metatypes.json")
 
     return data
 
@@ -956,7 +960,7 @@ def module_QtSpatialAudio() -> ModuleData:
 def module_QtStateMachine() -> ModuleData:
     data = ModuleData("StateMachine")
     data.qtlib.append("libQt6StateMachineQml")
-    data.metatypes.append("qt6statemachineqml_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6statemachineqml_metatypes.json")
 
     return data
 
@@ -964,7 +968,7 @@ def module_QtStateMachine() -> ModuleData:
 def module_QtScxml() -> ModuleData:
     data = ModuleData("Scxml")
     data.qtlib.append("libQt6ScxmlQml")
-    data.metatypes.append("qt6scxmlqml_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6scxmlqml_metatypes.json")
     json_data = get_module_json_data("Scxml")
     data.plugins = get_module_plugins(json_data)
 
@@ -1052,13 +1056,13 @@ def module_QtHttpServer() -> ModuleData:
 
 def module_QtLanguageServer() -> ModuleData:
     data = ModuleData("LanguageServer")
-    data.metatypes.append("qt6languageserverprivate_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6languageserverprivate_metatypes.json")
     return data
 
 
 def module_QtJsonRpc() -> ModuleData:
     data = ModuleData("JsonRpc")
-    data.metatypes.append("qt6jsonrpcprivate_relwithdebinfo_metatypes.json")
+    data.metatypes.append("qt6jsonrpcprivate_metatypes.json")
     return data
 
 
@@ -1076,11 +1080,6 @@ def module_QtAsyncio() -> ModuleData:
     return data
 
 
-def module_QtExampleIcons() -> ModuleData:
-    data = ModuleData("ExampleIcons")
-    return data
-
-
 def module_QtWebView() -> ModuleData:
     data = ModuleData("WebView")
     json_data = get_module_json_data("WebView")
index ad4751d25fe5996ed2a63fca6ee49b3668a6cd9f..9d0157f7e9343a4cab21c2688aff63ce1ad1bd97 100644 (file)
@@ -1,6 +1,6 @@
 product_dependency:
   ../../qt/qt5:
-    ref: "af7939f2df81369a4a6501c9b41e6e68278269eb"
+    ref: "abfb3788bcfd08d6192899eb39a8ff34d7546da8"
 dependency_source: supermodule
 dependencies: [
       "../../qt/qt3d",
index 949eecad3b0cc15f868b2d73d68af2bd13641eac..478a0765bdba5a9cf2a155d6e4cc6da5c6c9ea3a 100644 (file)
@@ -3,6 +3,12 @@ instructions:
     - type: EnvironmentVariable
       variableName: QTEST_ENVIRONMENT
       variableValue: "ci"
+    - type: EnvironmentVariable
+      variableName: PYTHON_BUILD_MIRROR_URL
+      variableValue: "https://ci-files01-hki.ci.qt.io/input/python/"
+    - type: EnvironmentVariable
+      variableName: PYTHON_BUILD_MIRROR_URL_SKIP_CHECKSUM
+      variableValue: "1"
     - type: EnvironmentVariable
       variableName: PYSIDE_VIRTUALENV
       variableValue: "{{.AgentWorkingDir}}\\pyside\\pyside-setup\\env"
@@ -328,13 +334,84 @@ instructions:
         condition: property
         property: host.osVersion
         not_in_values: [RHEL_8_6, RHEL_8_8, RHEL_8_10]
-    # ToDo: can be removed after 3.11 is available on qt5#3.8 on macOS
-    # start of ToDo
+
     - type: ExecuteCommand
-      command: "tools/install-p311.sh"
+      command: "pyenv install 3.13.7"
       maxTimeInSeconds: 14400
       maxTimeBetweenOutput: 1200
       ignoreExitCode: true
+      enable_if:
+          condition: and
+          conditions:
+          - condition: property
+            property: host.osVersion
+            equals_value: MacOS_15
+          - condition: property
+            property: host.arch
+            equals_value: ARM64
+      userMessageOnFailure: >
+          Failed to install python 3.13
+    - type: PrependToEnvironmentVariable
+      variableName: PATH
+      variableValue: "/Users/qt/.pyenv/versions/3.13.7/bin:"
+      enable_if:
+          condition: and
+          conditions:
+          - condition: property
+            property: host.osVersion
+            equals_value: MacOS_15
+          - condition: property
+            property: host.arch
+            equals_value: ARM64
+    - type: EnvironmentVariable
+      variableName: interpreter
+      variableValue: "python3.13"
+      enable_if:
+          condition: and
+          conditions:
+          - condition: property
+            property: host.osVersion
+            equals_value: MacOS_15
+          - condition: property
+            property: host.arch
+            equals_value: ARM64
+
+    - type: ExecuteCommand
+      command: "sudo subscription-manager refresh"
+      ignoreExitCode: true
+      maxTimeInSeconds: 6000
+      maxTimeBetweenOutput: 1200
+      enable_if:
+          condition: property
+          property: host.osVersion
+          in_values: [RHEL_9_4, RHEL_9_6]
+      userMessageOnFailure: >
+        Failed to refresh subscription.
+
+    - type: ExecuteCommand
+      command: "sudo yum -y install python3.11-devel python3.11-pip"
+      maxTimeInSeconds: 14400
+      maxTimeBetweenOutput: 1200
+      enable_if:
+          condition: property
+          property: host.osVersion
+          in_values: [RHEL_9_4, RHEL_9_6]
+      userMessageOnFailure: >
+          Failed to install python 3.11
+
+    - type: EnvironmentVariable
+      variableName: interpreter
+      variableValue: "python3.11"
+      enable_if:
+          condition: property
+          property: host.osVersion
+          in_values: [RHEL_9_4, RHEL_9_6]
+
+    # Use pyenv to install Python 3.11 on macOS+Android
+    - type: ExecuteCommand
+      command: "pyenv install 3.11.9"
+      maxTimeInSeconds: 14400
+      maxTimeBetweenOutput: 1200
       enable_if:
           condition: and
           conditions:
@@ -345,10 +422,10 @@ instructions:
             property: target.os
             equals_value: Android
       userMessageOnFailure: >
-          Failed to install python 3.11
+          Failed to install python 3.11 with pyenv
     - type: PrependToEnvironmentVariable
       variableName: PATH
-      variableValue: "/Users/qt/python311/bin:"
+      variableValue: "/Users/qt/.pyenv/versions/3.11.9/bin:"
       enable_if:
           condition: and
           conditions:
@@ -358,7 +435,6 @@ instructions:
           - condition: property
             property: target.os
             equals_value: Android
-    # end of ToDo
     - type: ExecuteCommand
       command: "virtualenv -p {{.Env.interpreter}} {{.AgentWorkingDir}}/env"
       maxTimeInSeconds: 14400
index ab4d02997e9f826fc5eb8a53da4f32cb705c7dd1..0c031bf406daa5147acb8fb198ae25865c4f4895 100644 (file)
@@ -32,7 +32,7 @@ instructions:
     userMessageOnFailure: >
          Failed to install deps
   - type: ExecuteCommand
-    command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /Users/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT_DEFAULT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name aarch64 --coin"
+    command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /Users/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name aarch64 --coin"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     ignoreExitCode: true
@@ -43,7 +43,7 @@ instructions:
     userMessageOnFailure: >
          Failed to execute build instructions on macOS
   - type: ExecuteCommand
-    command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /home/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT_DEFAULT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name x86_64 --coin"
+    command: "{{.Env.interpreter}} tools/cross_compile_android/main.py --qt-install-path /home/qt/work/install --auto-accept-license --skip-update --verbose --ndk-path {{.Env.ANDROID_NDK_ROOT}} --sdk-path {{.Env.ANDROID_SDK_ROOT}} --plat-name x86_64 --coin"
     maxTimeInSeconds: 14400
     maxTimeBetweenOutput: 1200
     ignoreExitCode: true
index 52c8211e08ecdd7e253151af8ce9f6331ece49bb..0337a0a4cab406a9a79cd92a043a97010d107f34 100644 (file)
@@ -148,8 +148,12 @@ def setup_virtualenv(python, exe, env, pip, log, ci):
     # Within Ubuntu 24.04 one can't install anything with pip to outside of
     # virtual env. Trust that we already have proper virtualenv installed.
     if os.environ.get("HOST_OSVERSION_COIN") != "ubuntu_24_04":
+        virtualenv_version = "20.7.2"
+        # 20.7.2 is too old for 3.13
+        if sys.version_info[1] > 12:
+            virtualenv_version = "20.32.0"
         run_instruction(
-            [str(python), "-m", "pip", "install", "--user", "virtualenv==20.7.2"],
+            [str(python), "-m", "pip", "install", "--user", "virtualenv==" + virtualenv_version],
             "Failed to pin virtualenv",
         )
     # installing to user base might not be in PATH by default.
index 471c840356dbc86e4604e221b27726a297f6a038..a9f83666d2dc854b63d3ad34d4cfcf7da06b4c23 100644 (file)
@@ -21,7 +21,7 @@ accept_configuration:
           not_in_values: [Mingw, MSVC2015,Clang]
         - condition: property  # Webassembly
           property: target.osVersion
-          not_equals_value: WebAssembly
+          not_in_values: [WebAssembly, RHEL_8_10]
         - condition: property  # Windows on Arm
           property: target.arch
           not_equals_value: ARM64
@@ -31,6 +31,9 @@ accept_configuration:
         - condition: property
           property: target.osVersion
           not_equals_value: VxWorks
+        - condition: property
+          property: host.osVersion
+          not_equals_value: Windows_11_22H2
     - condition: and
       conditions:
         - condition: property
@@ -73,7 +76,31 @@ accept_configuration:
         - condition: property
           property: features
           contains_value: Packaging
-
+    - condition: and
+      conditions:
+        - condition: property
+          property: target.os
+          equals_value: MacOS
+        - condition: property
+          property: features
+          contains_value: Packaging
+        - condition: property
+          property: target.arch
+          equals_value: X86_64-ARM64
+    - condition: and
+      conditions:
+        - condition: property
+          property: target.os
+          equals_value: Windows
+        - condition: property
+          property: features
+          contains_value: Packaging
+        - condition: property
+          property: target.arch
+          equals_value: X86_64
+        - condition: property
+          property: target.compiler
+          equals_value: Mingw
 machine_type:
   Build:
     cores: 8
index 55d02928bbf65b20f12f6618babffebae9f22a14..6f8d8812e16757504870f248814a0aa14f0b4251 100644 (file)
@@ -60,13 +60,13 @@ def create_module_plugin_json(wheel_name: str, data: list[ModuleData], package_p
             json.dump(all_plugins, fp, indent=4)
 
 
-def get_manifest(wheel_name: str, data: list[ModuleData], package_path: Path) -> str:
+def get_manifest(wheel_name: str, data: list[ModuleData], package_path: Path, verbose: int) -> str:
     lines = []
 
     for module in data:
         # It's crucial to have this adjust method here
         # because it include all the necessary modifications to make
-        # our soltuion work on the three main platforms.
+        # our solution work on the three main platforms.
         module.adjusts_paths_and_extensions()
 
         for field in module.get_fields():
@@ -76,12 +76,15 @@ def get_manifest(wheel_name: str, data: list[ModuleData], package_path: Path) ->
             if field == "ext":
                 continue
             for line in getattr(module, field):
+                file = f"PySide6/{line}"
+                if verbose > 0 and "*" not in file and not Path(package_path / file).exists():
+                    print(f"Warning: {file} does not exist.", file=sys.stderr)
                 if field in ("extra_dirs", "qml", "plugins"):
-                    lines.append(f"graft PySide6/{line}")
+                    lines.append(f"graft {file}")
                 elif field == "qtlib" and sys.platform == "darwin":
-                    lines.append(f"graft PySide6/{line}")
+                    lines.append(f"graft {file}")
                 else:
-                    lines.append(f"include PySide6/{line}")
+                    lines.append(f"include {file}")
         lines.append("recursive-exclude PySide6 *qt.conf*")
         lines.append("")
 
@@ -92,6 +95,10 @@ def get_manifest(wheel_name: str, data: list[ModuleData], package_path: Path) ->
     # adding PySide6_Essentials.json and PySide6_Addons.json
     lines.append(f"include PySide6/{wheel_name}.json")
 
+    # Only include CMake configs for PySide6_Essentials
+    if wheel_name == "PySide6_Essentials":
+        lines.append("graft PySide6/lib/cmake")
+
     return "\n".join(lines)
 
 
@@ -368,7 +375,7 @@ def check_modules_consistency():
 
     if len(missing_modules):
         print("Warning: the following modules don't have a function "
-              f"in 'build_scripts/wheel_files.py':\n  {missing_modules}")
+              f"in 'build_scripts/wheel_files.py':\n  {missing_modules}", file=sys.stderr)
 
     # Check READMEs
     readme_modules = set()
@@ -382,12 +389,13 @@ def check_modules_consistency():
 
     if len(missing_modules_readme):
         print("Warning: the following modules are not in READMEs :"
-              f"\n  {missing_modules_readme}")
+              f"\n  {missing_modules_readme}", file=sys.stderr)
 
 
 if __name__ == "__main__":
 
     parser = ArgumentParser()
+    parser.add_argument('--verbose', '-v', type=int, help='Verbose level')
     # Command line option to find the build/<envname>a/package_for_wheels
     parser.add_argument(
         "--env", type=str, default=None,
@@ -411,7 +419,7 @@ if __name__ == "__main__":
 
     build_directory = get_build_directory(options)
 
-    verbose = False
+    verbose = options.verbose if options.verbose else 0
     # Setup paths
     current_path = Path(__file__).resolve().parent
     artifacts_path = Path("wheel_artifacts/")
@@ -480,7 +488,7 @@ if __name__ == "__main__":
         if data is None:
             manifest_content = get_simple_manifest(name)
         else:
-            manifest_content = get_manifest(name, data, package_path)
+            manifest_content = get_manifest(name, data, package_path, verbose)
         with open(package_path / "MANIFEST.in", "w") as f:
             f.write(manifest_content)
 
@@ -494,7 +502,7 @@ if __name__ == "__main__":
 
         # 6. call the build module to create the wheel
         print("-- Creating wheels")
-        if not verbose:
+        if verbose < 2:
             _runner = pyproject_hooks.quiet_subprocess_runner
         else:
             _runner = pyproject_hooks.default_subprocess_runner
diff --git a/doc/changelogs/changes-6.10.0 b/doc/changelogs/changes-6.10.0
new file mode 100644 (file)
index 0000000..630df3f
--- /dev/null
@@ -0,0 +1,88 @@
+Qt for Python 6.10.0 is a minor release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+* **************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+PySide now uses multi-phase Python extension module initialization for the
+non-deployed case. For scripting appplications, this implies that it is no
+longer sufficient to call the Init() function of a module to load the module
+in order to be able to access its type structs for binding variables.
+Instead, PyImport_ImportModule() must be used (see scriptable application
+example).
+
+ - [PYSIDE-1735] @QEnum, @QFlag and QMetaEnum now support unsigned 64 bit
+                 values for QML usage (with the exception of Qt Widgets
+                 Designer), following the support added to Qt.
+ - [PYSIDE-2840] It is now possible to use @QEnum/@QFlag-decorated
+                 enumerations as properties of custom widgets in Qt Widgets
+                 Designer.
+ - [PYSIDE-2916] The invocation of functions overridden in Python
+                 has been optimized for speed.
+ - [PYSIDE-3012] type hints: The annotations of QPropertyAnimation
+                 have been fixed.
+ - [PYSIDE-3084] Enumerations are now stored as such instead of an opaque
+                 PyObjectWrapper in functions returning QVariant.
+ - [PYSIDE-3137] A warning occurring when doing the first signal connection
+                 from a thread has been fixed.
+ - [PYSIDE-3143] A bug causing events to be wrongly converted to
+                 QStandardItem has been fixed. Also, a leak of QStandardItem
+                 instances returned from QStandardItemModel functions
+                 has been fixed.
+ - [PYSIDE-3146] Deployment: Values generated into pysidedeploy.spec are
+                 now sorted.
+ - [PYSIDE-3147] Initial adaptations for the upcoming Python version 3.14
+                 have been done.
+ - [PYSIDE-3164] type hints: Enum values have been added to the stubs for
+                 improved type checking.
+ - [PYSIDE-3178] type hints: The return types of
+                 QPoint(F)/QSize(F).toTuple have been fixed.
+ - [QTBUG-110428] The QtExampleIcons module has been removed due to
+                  the removal of the underlying library in Qt.
+
+ ***************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - Template specializations like "std::optional<int>" can now be specified
+   as primitive types with converter code.
+ - [PYSIDE-2221] Multi-phase Python extension module initialization is now
+                 used for the non-deployed case.
+ - [PYSIDE-3011] It is now possible to inject code into the the wrapper
+                 class declaration, which can be used to import base class
+                 members via the "using" keyword.
+ - [PYSIDE-3105] The support for cross compiling (using the correct target
+                 for clang-based parsing) has been improved. Various options
+                 have been added to shiboken to be able to specify target
+                 platform and compiler.
+ - [PYSIDE-2854] libshiboken: The internal map instances has been changed
+                 to be a multimap to improve support for co-located objects.
+ - [PYSIDE-3107] An automatic conversion from T to std::optional<T>
+                 has been added (in case std::optional is specified in
+                 the type system).
+ - [PYSIDE-3138] A CMake package "Shiboken6Tools" has been introduced to make
+                 integrating Shiboken with CMake easier. This significantly
+                 reduces the amount of CMake code required by users,
+                 replacing the previous method of calling the executable
+                 directly with a Python script (`pyside_config.py`).
+ - [PYSIDE-3171] libshiboken's replacement functions providing functions
+                 missing from the limited API or appearing in future Python
+                 versions have been moved from sbkpython.h (providing a
+                 sanitized Python.h) to separate headers sbkpep.h and
+                 sbkpepbuffer.h (providing buffer API). This should not
+                 affect binding code as the generator includes the new
+                 headers, but may require adaption in client code using
+                 libshiboken/libpyside directly.
diff --git a/doc/changelogs/changes-6.10.1 b/doc/changelogs/changes-6.10.1
new file mode 100644 (file)
index 0000000..7d565d8
--- /dev/null
@@ -0,0 +1,41 @@
+Qt for Python 6.10.1 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - [PYSIDE-2206] Many examples and tutorials have been updated.
+ - [PYSIDE-3147] Python 3.14 is now supported.
+ - [PYSIDE-3174] type hints: The type annotations of QtCore.Slot have been
+                 fixed.
+ - [PYSIDE-3179] A regression breaking QtRemoteObjects has been fixed.
+ - [PYSIDE-3190] Disconnecting multiple index-based connections has been
+                 fixed.
+ - [PYSIDE-3201] An issue handling types with equal names in signal/slot
+                 connections has been fixed.
+ - [PYSIDE-3206] A conversion from tuple to QVariantList has been added.
+ - [PYSIDE-3213] QQuickTextDocument can now be used as a property in QML.
+ - [PYSIDE-3217] A regression breaking enumeration properties in Qt Widgets
+                 Designer has been fixed.
+ - [PYSIDE-3227] A bug affecting QMetaProperty attributes when using
+                 @Property has been fixed.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - [QTBUG-141204] An exclusion for a clang warning occurring when parsing
+                  Qt code has been added (libclang v21).
diff --git a/doc/changelogs/changes-6.10.2 b/doc/changelogs/changes-6.10.2
new file mode 100644 (file)
index 0000000..d9bb532
--- /dev/null
@@ -0,0 +1,48 @@
+Qt for Python 6.10.2 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - [PYSIDE-2206] The multimedia player example has been updated.
+ - [PYSIDE-3189] Type hints: Some missing optional return types have been
+                 added.
+ - [PYSIDE-3231] Type hints: Element access for all QMatrix types
+                 has been fixed to consistently provide a call
+                 operator (operator()) as well as mgetitem (operator[][]).
+ - [PYSIDE-3233] The Shiboken6Tools CMake module has been fixed to find
+                 Python on manylinux_2_34.
+ - [PYSIDE-3241] The metatype JSON files that had been missing from the
+                 wheels have been added.
+ - [PYSIDE-3248] QtWebView has been added to the Android wheels.
+ - [PYSIDE-3034] Type hints: Public variables have been added.
+ - [PYSIDE-3250] The snake_case feature has been fixed to also work
+                 for imported modules.
+ - [PYSIDE-3254] The include-dir specification in the pkgconfig file
+                 of libpyside has been fixed.
+ - [PYSIDE-3244] A regression causing tuples to be converted to
+ - [PYSIDE-3256] QVariantList when passed in a QVariant has been fixed.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - [PYSIDE-3235] Building for Yocto using a Clang toolchain has been fixed.
+ - [PYSIDE-3246] A crash occurring when multiple threads attempt to
+                 retrieve the same method override has been fixed.
+ - [PYSIDE-3259] The conversion generated for std::vector<bool> has been
+                 fixed for compilers that actually implement the
+                 std::vector<bool> optimization (Clang).
diff --git a/doc/changelogs/changes-6.9.3 b/doc/changelogs/changes-6.9.3
new file mode 100644 (file)
index 0000000..b0f55fc
--- /dev/null
@@ -0,0 +1,40 @@
+Qt for Python 6.9.3 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+*                                  PySide6                                 *
+****************************************************************************
+
+ - [PYSIDE-2308] type hints: The type annotation of the notify parameter of
+                 QtCore.Property has been corrected.
+ - [PYSIDE-3047] type hints: The type annotations of QPolygon(F)'s
+                 operator<< have been corrected.
+ - [PYSIDE-3048] type hints: The type annotations now contain enum values.
+ - [PYSIDE-3162] type hints: The 'from __future__ import annotations' have
+                 been removed from the stub files.
+ - [PYSIDE-3163] type hints: The mypy comment has been removed from the
+                 docstring.
+ - [PYSIDE-2206] Tye QtBluetooth/heartrate_game example has been updated.
+
+****************************************************************************
+*                                  Shiboken6                               *
+****************************************************************************
+
+ - [PYSIDE-1106] DocGenerator: Extracting documentation from C++ structs
+                 has been fixed.
+ - [PYSIDE-3173] A crash when encountering UTF-8 encoding errors has been
+                 changed to a fatal error.
+ - [PYSIDE-3175] A crash occurring when the typesystem's package attribute
+                 is missing has been fixed.
index 3687b133125aa3a903979d4affe597c1b2945885..16b4d32b3791a78fb1fcfd542b0a93bea51246f1 100644 (file)
@@ -52,7 +52,7 @@ Item {
                 horizontalAlignment: Text.AlignHCenter
                 verticalAlignment: Text.AlignVCenter
                 wrapMode: Text.WordWrap
-                font.pixelSize: GameSettings.mediumFontSize
+                font.pixelSize: GameSettings.smallFontSize
                 color: GameSettings.textColor
                 text: root.permissionError
                       ? qsTr("Bluetooth permissions are not granted. Please grant the permissions in the system settings.")
@@ -70,8 +70,8 @@ Item {
                 Text {
                     anchors.centerIn: parent
                     color: GameSettings.textColor
-                    font.pixelSize: GameSettings.bigFontSize
-                    text: qsTr("Quit")
+                    font.pixelSize: GameSettings.microFontSize
+                    text: qsTr("QUIT")
                 }
             }
         }
index caebc307ec29a0dbea8216b18c617a5285f4080b..80fdaa8cdec79f990dda6b303983a33487b89d5f 100644 (file)
@@ -6,7 +6,6 @@ import QtQuick
 Rectangle {
     anchors.horizontalCenter: parent.horizontalCenter
     anchors.bottom: parent.bottom
-    width: parent.width * 0.85
+    width: parent.width
     height: parent.height * 0.05
-    radius: height*0.5
 }
index ca8ef2923d94954789d1f5ec4a01a2b5b7526895..ed5fb63d7fd6e62b7bdbed5655bb9c5b4102df56 100644 (file)
@@ -16,43 +16,39 @@ GamePage {
 
     errorMessage: deviceFinder.error
     infoMessage: deviceFinder.info
+    iconType: deviceFinder.icon
+
+    Text {
+        id: viewCaption
+        anchors {
+            top: parent.top
+            topMargin: GameSettings.fieldMargin + connectPage.messageHeight
+            horizontalCenter: parent.horizontalCenter
+        }
+        width: parent.width - GameSettings.fieldMargin * 2
+        height: GameSettings.fieldHeight
+        horizontalAlignment: Text.AlignLeft
+        verticalAlignment: Text.AlignVCenter
+        color: GameSettings.textColor
+        font.pixelSize: GameSettings.smallFontSize
+        text: qsTr("Found Devices")
+    }
 
     Rectangle {
         id: viewContainer
-        anchors.top: parent.top
+        anchors.top: viewCaption.bottom
         // only BlueZ platform has address type selection
         anchors.bottom: connectPage.connectionHandler.requiresAddressType ? addressTypeButton.top
                                                                           : searchButton.top
-        anchors.topMargin: GameSettings.fieldMargin + connectPage.messageHeight
         anchors.bottomMargin: GameSettings.fieldMargin
         anchors.horizontalCenter: parent.horizontalCenter
         width: parent.width - GameSettings.fieldMargin * 2
         color: GameSettings.viewColor
         radius: GameSettings.buttonRadius
 
-        Text {
-            id: title
-            width: parent.width
-            height: GameSettings.fieldHeight
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            color: GameSettings.textColor
-            font.pixelSize: GameSettings.mediumFontSize
-            text: qsTr("FOUND DEVICES")
-
-            BottomLine {
-                height: 1
-                width: parent.width
-                color: "#898989"
-            }
-        }
-
         ListView {
             id: devices
-            anchors.left: parent.left
-            anchors.right: parent.right
-            anchors.bottom: parent.bottom
-            anchors.top: title.bottom
+            anchors.fill: parent
             model: connectPage.deviceFinder.devices
             clip: true
 
@@ -76,22 +72,22 @@ GamePage {
 
                 Text {
                     id: device
-                    font.pixelSize: GameSettings.smallFontSize
+                    font.pixelSize: GameSettings.microFontSize
                     text: box.modelData.deviceName
                     anchors.top: parent.top
-                    anchors.topMargin: parent.height * 0.1
-                    anchors.leftMargin: parent.height * 0.1
+                    anchors.topMargin: parent.height * 0.15
+                    anchors.leftMargin: parent.height * 0.15
                     anchors.left: parent.left
                     color: GameSettings.textColor
                 }
 
                 Text {
                     id: deviceAddress
-                    font.pixelSize: GameSettings.smallFontSize
+                    font.pixelSize: GameSettings.microFontSize
                     text: box.modelData.deviceAddress
                     anchors.bottom: parent.bottom
-                    anchors.bottomMargin: parent.height * 0.1
-                    anchors.rightMargin: parent.height * 0.1
+                    anchors.bottomMargin: parent.height * 0.15
+                    anchors.rightMargin: parent.height * 0.15
                     anchors.right: parent.right
                     color: Qt.darker(GameSettings.textColor)
                 }
@@ -114,19 +110,19 @@ GamePage {
             State {
                 name: "public"
                 PropertyChanges {
-                    addressTypeText.text: qsTr("Public Address")
+                    addressTypeText.text: qsTr("PUBLIC ADDRESS")
                 }
                 PropertyChanges {
-                    connectPage.deviceHandler.addressType: DeviceHandler.PUBLIC_ADDRESS
+                    connectPage.deviceHandler.addressType: DeviceHandler.PublicAddress
                 }
             },
             State {
                 name: "random"
                 PropertyChanges {
-                    addressTypeText.text: qsTr("Random Address")
+                    addressTypeText.text: qsTr("RANDOM ADDRESS")
                 }
                 PropertyChanges {
-                    connectPage.deviceHandler.addressType: DeviceHandler.RANDOM_ADDRESS
+                    connectPage.deviceHandler.addressType: DeviceHandler.RandomAddress
                 }
             }
         ]
@@ -134,8 +130,8 @@ GamePage {
         Text {
             id: addressTypeText
             anchors.centerIn: parent
-            font.pixelSize: GameSettings.tinyFontSize
-            color: GameSettings.textColor
+            font.pixelSize: GameSettings.microFontSize
+            color: GameSettings.textDarkColor
         }
     }
 
@@ -151,9 +147,9 @@ GamePage {
 
         Text {
             anchors.centerIn: parent
-            font.pixelSize: GameSettings.tinyFontSize
+            font.pixelSize: GameSettings.microFontSize
             text: qsTr("START SEARCH")
-            color: searchButton.enabled ? GameSettings.textColor : GameSettings.disabledTextColor
+            color: GameSettings.textDarkColor
         }
     }
 }
index 249f94186db43e46a2078e559bf645e353347264..2d592cfdbb9d1db80f0d92f8777918669673c982 100644 (file)
@@ -11,25 +11,65 @@ Item {
     property real messageHeight: msg.height
     property bool hasError: errorMessage != ""
     property bool hasInfo: infoMessage != ""
+    property int iconType: BluetoothBaseClass.IconNone
+
+    function iconTypeToName(icon: int) : string {
+        switch (icon) {
+        case BluetoothBaseClass.IconNone: return ""
+        case BluetoothBaseClass.IconBluetooth: return "images/bluetooth.svg"
+        case BluetoothBaseClass.IconError: return "images/alert.svg"
+        case BluetoothBaseClass.IconProgress: return "images/progress.svg"
+        case BluetoothBaseClass.IconSearch: return "images/search.svg"
+        }
+    }
 
     Rectangle {
         id: msg
-        anchors.top: parent.top
-        anchors.left: parent.left
-        anchors.right: parent.right
+        anchors {
+            top: parent.top
+            left: parent.left
+            right: parent.right
+            topMargin: GameSettings.fieldMargin * 0.5
+            leftMargin: GameSettings.fieldMargin
+            rightMargin: GameSettings.fieldMargin
+        }
         height: GameSettings.fieldHeight
-        color: page.hasError ? GameSettings.errorColor : GameSettings.infoColor
+        radius: GameSettings.buttonRadius
+        color: page.hasError ? GameSettings.errorColor : "transparent"
         visible: page.hasError || page.hasInfo
+        border {
+            width: 1
+            color: page.hasError ? GameSettings.errorColor : GameSettings.infoColor
+        }
+
+        Image {
+            id: icon
+            readonly property int imgSize: GameSettings.fieldHeight * 0.5
+            anchors {
+                left: parent.left
+                leftMargin: GameSettings.fieldMargin * 0.5
+                verticalCenter: parent.verticalCenter
+            }
+            visible: source.toString() !== ""
+            source: page.iconTypeToName(page.iconType)
+            sourceSize.width: imgSize
+            sourceSize.height: imgSize
+            fillMode: Image.PreserveAspectFit
+        }
 
         Text {
             id: error
-            anchors.fill: parent
+            anchors {
+                fill: parent
+                leftMargin: GameSettings.fieldMargin + icon.width
+                rightMargin: GameSettings.fieldMargin + icon.width
+            }
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
             minimumPixelSize: 5
-            font.pixelSize: GameSettings.smallFontSize
+            font.pixelSize: GameSettings.microFontSize
             fontSizeMode: Text.Fit
-            color: GameSettings.textColor
+            color: page.hasError ? GameSettings.textColor : GameSettings.infoColor
             text: page.hasError ? page.errorMessage : page.infoMessage
         }
     }
index 0fe85460993c2157f60ca46f4637fa946ba28387..4032787ce9cc6af647ebe3792356f4a897694081 100644 (file)
@@ -4,35 +4,49 @@
 pragma Singleton
 import QtQuick
 
-Item {
+QtObject {
     property int wHeight
     property int wWidth
 
     // Colors
-    readonly property color backgroundColor: "#2d3037"
-    readonly property color buttonColor: "#202227"
-    readonly property color buttonPressedColor: "#6ccaf2"
-    readonly property color disabledButtonColor: "#555555"
-    readonly property color viewColor: "#202227"
-    readonly property color delegate1Color: Qt.darker(viewColor, 1.2)
-    readonly property color delegate2Color: Qt.lighter(viewColor, 1.2)
+    readonly property color lightGreenColor: "#80ebb6"
+    readonly property color backgroundColor: "#2c3038"
+    readonly property color buttonColor: "#2cde85"
+    readonly property color buttonPressedColor: lightGreenColor
+    readonly property color disabledButtonColor: "#808080"
+    readonly property color viewColor: "#262626"
+    readonly property color delegate1Color: "#262626"
+    readonly property color delegate2Color: "#404040"
     readonly property color textColor: "#ffffff"
-    readonly property color textDarkColor: "#232323"
-    readonly property color disabledTextColor: "#777777"
-    readonly property color sliderColor: "#6ccaf2"
+    readonly property color textDarkColor: "#0d0d0d"
+    readonly property color textInfoColor: lightGreenColor
+    readonly property color sliderColor: "#00414a"
+    readonly property color sliderBorderColor: lightGreenColor
+    readonly property color sliderTextColor: lightGreenColor
     readonly property color errorColor: "#ba3f62"
-    readonly property color infoColor: "#3fba62"
+    readonly property color infoColor: lightGreenColor
+    readonly property color titleColor: "#202227"
+    readonly property color selectedTitleColor: "#19545c"
+    readonly property color hoverTitleColor: Qt.rgba(selectedTitleColor.r,
+                                                     selectedTitleColor.g,
+                                                     selectedTitleColor.b,
+                                                     0.25)
+    readonly property color bottomLineColor: "#e6e6e6"
+    readonly property color heartRateColor: "#f80067"
+
+    // All the fonts are given for the window of certain size.
+    // Resizing the window changes all the fonts accordingly
+    readonly property int defaultSize: 500
+    readonly property real fontScaleFactor: Math.min(wWidth, wHeight) / defaultSize
 
     // Font sizes
-    property real microFontSize: hugeFontSize * 0.2
-    property real tinyFontSize: hugeFontSize * 0.4
-    property real smallTinyFontSize: hugeFontSize * 0.5
-    property real smallFontSize: hugeFontSize * 0.6
-    property real mediumFontSize: hugeFontSize * 0.7
-    property real bigFontSize: hugeFontSize * 0.8
-    property real largeFontSize: hugeFontSize * 0.9
-    property real hugeFontSize: (wWidth + wHeight) * 0.03
-    property real giganticFontSize: (wWidth + wHeight) * 0.04
+    readonly property real microFontSize: 16 * fontScaleFactor
+    readonly property real tinyFontSize: 20 * fontScaleFactor
+    readonly property real smallFontSize: 24 * fontScaleFactor
+    readonly property real mediumFontSize: 32 * fontScaleFactor
+    readonly property real bigFontSize: 36 * fontScaleFactor
+    readonly property real largeFontSize: 54 * fontScaleFactor
+    readonly property real hugeFontSize: 128 * fontScaleFactor
 
     // Some other values
     property real fieldHeight: wHeight * 0.08
@@ -41,10 +55,6 @@ Item {
     property real buttonRadius: buttonHeight * 0.1
 
     // Some help functions
-    function widthForHeight(h, ss) {
-        return h / ss.height * ss.width
-    }
-
     function heightForWidth(w, ss) {
         return w / ss.width * ss.height
     }
index 48e84e7624a8294d8c9c54177be35247d3cf8ace..04ebeb09a154c10ee8dc314a56c4e8edc4e64587 100644 (file)
@@ -11,10 +11,15 @@ GamePage {
 
     errorMessage: deviceHandler.error
     infoMessage: deviceHandler.info
+    iconType: deviceHandler.icon
 
     property real __timeCounter: 0
     property real __maxTimeCount: 60
-    property string relaxText: qsTr("Relax!\nWhen you are ready, press Start. You have %1s time to increase heartrate so much as possible.\nGood luck!").arg(__maxTimeCount)
+
+    readonly property string relaxText: qsTr("Relax!")
+    readonly property string startText: qsTr("When you are ready,\npress Start.")
+    readonly property string instructionText: qsTr("You have %1s time to increase heart\nrate as much as possible.").arg(__maxTimeCount)
+    readonly property string goodLuckText: qsTr("Good luck!")
 
     signal showStatsPage
 
@@ -55,6 +60,10 @@ GamePage {
 
         Rectangle {
             id: circle
+
+            readonly property bool hintVisible: !measurePage.deviceHandler.measuring
+            readonly property real innerSpacing: Math.min(width * 0.05, 25)
+
             anchors.horizontalCenter: parent.horizontalCenter
             width: Math.min(measurePage.width, measurePage.height - GameSettings.fieldHeight * 4)
                    - 2 * GameSettings.fieldMargin
@@ -63,30 +72,127 @@ GamePage {
             color: GameSettings.viewColor
 
             Text {
-                id: hintText
-                anchors.centerIn: parent
-                anchors.verticalCenterOffset: -parent.height * 0.1
+                id: relaxTextBox
+                anchors {
+                    bottom: startTextBox.top
+                    bottomMargin: parent.innerSpacing
+                    horizontalCenter: parent.horizontalCenter
+                }
+                width: parent.width * 0.6
+                height: parent.height * 0.1
                 horizontalAlignment: Text.AlignHCenter
                 verticalAlignment: Text.AlignVCenter
-                width: parent.width * 0.8
-                height: parent.height * 0.6
-                wrapMode: Text.WordWrap
                 text: measurePage.relaxText
-                visible: !measurePage.deviceHandler.measuring
+                visible: circle.hintVisible
                 color: GameSettings.textColor
                 fontSizeMode: Text.Fit
-                minimumPixelSize: 10
-                font.pixelSize: GameSettings.mediumFontSize
+                font.pixelSize: GameSettings.smallFontSize
+                font.bold: true
             }
 
             Text {
-                id: text
-                anchors.centerIn: parent
-                anchors.verticalCenterOffset: -parent.height * 0.15
-                font.pixelSize: parent.width * 0.45
+                id: startTextBox
+                anchors {
+                    bottom: heart.top
+                    bottomMargin: parent.innerSpacing
+                    horizontalCenter: parent.horizontalCenter
+                }
+                width: parent.width * 0.8
+                height: parent.height * 0.15
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: measurePage.startText
+                visible: circle.hintVisible
+                color: GameSettings.textColor
+                fontSizeMode: Text.Fit
+                font.pixelSize: GameSettings.tinyFontSize
+            }
+
+            Text {
+                id: measureTextBox
+                anchors {
+                    bottom: heart.top
+                    horizontalCenter: parent.horizontalCenter
+                }
+                width: parent.width * 0.7
+                height: parent.height * 0.35
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
                 text: measurePage.deviceHandler.hr
                 visible: measurePage.deviceHandler.measuring
+                color: GameSettings.heartRateColor
+                fontSizeMode: Text.Fit
+                font.pixelSize: GameSettings.hugeFontSize
+                font.bold: true
+            }
+
+            Image {
+                id: heart
+                anchors.centerIn: circle
+                width: parent.width * 0.2
+                height: width
+                fillMode: Image.PreserveAspectFit
+                source: "images/heart.png"
+                smooth: true
+                antialiasing: true
+
+                SequentialAnimation {
+                    id: heartAnim
+                    running: measurePage.deviceHandler.measuring
+                    loops: Animation.Infinite
+                    alwaysRunToEnd: true
+                    PropertyAnimation {
+                        target: heart
+                        property: "scale"
+                        to: 1.4
+                        duration: 500
+                        easing.type: Easing.InQuad
+                    }
+                    PropertyAnimation {
+                        target: heart
+                        property: "scale"
+                        to: 1.0
+                        duration: 500
+                        easing.type: Easing.OutQuad
+                    }
+                }
+            }
+
+            Text {
+                id: instructionTextBox
+                anchors {
+                    top: heart.bottom
+                    topMargin: parent.innerSpacing
+                    horizontalCenter: parent.horizontalCenter
+                }
+                width: parent.width * 0.8
+                height: parent.height * 0.15
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: measurePage.instructionText
+                visible: circle.hintVisible
                 color: GameSettings.textColor
+                fontSizeMode: Text.Fit
+                font.pixelSize: GameSettings.tinyFontSize
+            }
+
+            Text {
+                id: goodLuckBox
+                anchors {
+                    top: instructionTextBox.bottom
+                    topMargin: parent.innerSpacing
+                    horizontalCenter: parent.horizontalCenter
+                }
+                width: parent.width * 0.6
+                height: parent.height * 0.1
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: measurePage.goodLuckText
+                visible: circle.hintVisible
+                color: GameSettings.textColor
+                fontSizeMode: Text.Fit
+                font.pixelSize: GameSettings.smallFontSize
+                font.bold: true
             }
 
             Item {
@@ -101,14 +207,22 @@ GamePage {
                 Text {
                     anchors.left: parent.left
                     anchors.verticalCenter: parent.verticalCenter
+                    width: parent.width * 0.35
+                    horizontalAlignment: Text.AlignLeft
+                    verticalAlignment: Text.AlignVCenter
                     text: measurePage.deviceHandler.minHR
                     color: GameSettings.textColor
-                    font.pixelSize: GameSettings.hugeFontSize
+                    fontSizeMode: Text.Fit
+                    font.pixelSize: GameSettings.largeFontSize
 
                     Text {
                         anchors.left: parent.left
                         anchors.bottom: parent.top
-                        font.pixelSize: parent.font.pixelSize * 0.8
+                        horizontalAlignment: Text.AlignLeft
+                        verticalAlignment: Text.AlignVCenter
+                        width: parent.width
+                        fontSizeMode: Text.Fit
+                        font.pixelSize: GameSettings.mediumFontSize
                         color: parent.color
                         text: "MIN"
                     }
@@ -117,51 +231,27 @@ GamePage {
                 Text {
                     anchors.right: parent.right
                     anchors.verticalCenter: parent.verticalCenter
+                    horizontalAlignment: Text.AlignRight
+                    verticalAlignment: Text.AlignVCenter
+                    width: parent.width * 0.35
                     text: measurePage.deviceHandler.maxHR
                     color: GameSettings.textColor
-                    font.pixelSize: GameSettings.hugeFontSize
+                    fontSizeMode: Text.Fit
+                    font.pixelSize: GameSettings.largeFontSize
 
                     Text {
                         anchors.right: parent.right
                         anchors.bottom: parent.top
-                        font.pixelSize: parent.font.pixelSize * 0.8
+                        horizontalAlignment: Text.AlignRight
+                        verticalAlignment: Text.AlignVCenter
+                        width: parent.width
+                        fontSizeMode: Text.Fit
+                        font.pixelSize: GameSettings.mediumFontSize
                         color: parent.color
                         text: "MAX"
                     }
                 }
             }
-
-            Image {
-                id: heart
-                anchors.horizontalCenter: minMaxContainer.horizontalCenter
-                anchors.verticalCenter: minMaxContainer.bottom
-                width: parent.width * 0.2
-                height: width
-                source: "images/heart.png"
-                smooth: true
-                antialiasing: true
-
-                SequentialAnimation {
-                    id: heartAnim
-                    running: measurePage.deviceHandler.alive
-                    loops: Animation.Infinite
-                    alwaysRunToEnd: true
-                    PropertyAnimation {
-                        target: heart
-                        property: "scale"
-                        to: 1.2
-                        duration: 500
-                        easing.type: Easing.InQuad
-                    }
-                    PropertyAnimation {
-                        target: heart
-                        property: "scale"
-                        to: 1.0
-                        duration: 500
-                        easing.type: Easing.OutQuad
-                    }
-                }
-            }
         }
 
         Rectangle {
@@ -171,21 +261,43 @@ GamePage {
             width: circle.width
             height: GameSettings.fieldHeight
             radius: GameSettings.buttonRadius
+            border {
+                width: 1
+                color: GameSettings.sliderBorderColor
+            }
 
             Rectangle {
-                height: parent.height
+                anchors {
+                    top: parent.top
+                    topMargin: parent.border.width
+                    left: parent.left
+                    leftMargin: parent.border.width
+                }
+                height: parent.height - 2 * parent.border.width
+                width: Math.min(1.0, measurePage.__timeCounter / measurePage.__maxTimeCount)
+                       * (parent.width - 2 * parent.border.width)
                 radius: parent.radius
                 color: GameSettings.sliderColor
-                width: Math.min(
-                           1.0,
-                           measurePage.__timeCounter / measurePage.__maxTimeCount) * parent.width
+            }
+
+            Image {
+                readonly property int imgSize: GameSettings.fieldHeight * 0.5
+                anchors {
+                    verticalCenter: parent.verticalCenter
+                    left: parent.left
+                    leftMargin: GameSettings.fieldMargin * 0.5
+                }
+                source: "images/clock.svg"
+                sourceSize.width: imgSize
+                sourceSize.height: imgSize
+                fillMode: Image.PreserveAspectFit
             }
 
             Text {
                 anchors.centerIn: parent
-                color: "gray"
+                color: GameSettings.sliderTextColor
                 text: (measurePage.__maxTimeCount - measurePage.__timeCounter).toFixed(0) + " s"
-                font.pixelSize: GameSettings.bigFontSize
+                font.pixelSize: GameSettings.smallFontSize
             }
         }
     }
@@ -197,16 +309,17 @@ GamePage {
         anchors.bottomMargin: GameSettings.fieldMargin
         width: circle.width
         height: GameSettings.fieldHeight
-        enabled: !measurePage.deviceHandler.measuring
+        enabled: measurePage.deviceHandler.alive && !measurePage.deviceHandler.measuring
+                 && measurePage.errorMessage === ""
         radius: GameSettings.buttonRadius
 
         onClicked: measurePage.start()
 
         Text {
             anchors.centerIn: parent
-            font.pixelSize: GameSettings.tinyFontSize
+            font.pixelSize: GameSettings.microFontSize
             text: qsTr("START")
-            color: startButton.enabled ? GameSettings.textColor : GameSettings.disabledTextColor
+            color: GameSettings.textDarkColor
         }
     }
 }
index 2f9ac1b3fed13a69ac000c66b0170067e4bd10ce..918319d78bf49187676af031d38c31ce747637ba 100644 (file)
@@ -23,7 +23,7 @@ Item {
     Timer {
         id: splashTimer
         interval: 1000
-        onTriggered: splashIsReady = true
+        onTriggered: root.splashIsReady = true
     }
 
     Component.onCompleted: splashTimer.start()
index 22cdd5365cecc87464f63be1c00ba262ea105963..87487c94426fb1cfeb79bb251b42307e0b53f434 100644 (file)
@@ -13,20 +13,45 @@ GamePage {
         anchors.centerIn: parent
         width: parent.width
 
-        Text {
+        Rectangle {
+            id: resultRect
             anchors.horizontalCenter: parent.horizontalCenter
-            font.pixelSize: GameSettings.hugeFontSize
-            color: GameSettings.textColor
-            text: qsTr("RESULT")
-        }
+            width: height
+            height: statsPage.height / 2 - GameSettings.fieldHeight
+            radius: height / 2
+            color: GameSettings.viewColor
 
-        Text {
-            anchors.horizontalCenter: parent.horizontalCenter
-            font.pixelSize: GameSettings.giganticFontSize * 3
-            color: GameSettings.textColor
-            text: (statsPage.deviceHandler.maxHR - statsPage.deviceHandler.minHR).toFixed(0)
+            Column {
+                anchors.centerIn: parent
+
+                Text {
+                    id: resultCaption
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    width: resultRect.width * 0.8
+                    height: resultRect.height * 0.15
+                    horizontalAlignment: Text.AlignHCenter
+                    fontSizeMode: Text.Fit
+                    font.pixelSize: GameSettings.bigFontSize
+                    color: GameSettings.textColor
+                    text: qsTr("RESULT")
+                }
+
+                Text {
+                    id: resultValue
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    width: resultRect.width * 0.8
+                    height: resultRect.height * 0.4
+                    horizontalAlignment: Text.AlignHCenter
+                    fontSizeMode: Text.Fit
+                    font.pixelSize: GameSettings.hugeFontSize
+                    font.bold: true
+                    color: GameSettings.heartRateColor
+                    text: (statsPage.deviceHandler.maxHR - statsPage.deviceHandler.minHR).toFixed(0)
+                }
+            }
         }
 
+
         Item {
             height: GameSettings.fieldHeight
             width: 1
index 016a44358f7604eca51e7cb190a0622fdd3067c4..ccec7608961f002c206385bc85cca6d122e54e09 100644 (file)
@@ -13,42 +13,51 @@ Rectangle {
     signal titleClicked(int index)
 
     height: GameSettings.fieldHeight
-    color: GameSettings.viewColor
+    color: GameSettings.titleColor
+
+    Rectangle {
+        anchors.bottom: parent.bottom
+        width: parent.width / 3
+        height: parent.height
+        x: titleBar.currentIndex * width
+        color: GameSettings.selectedTitleColor
+
+        BottomLine {
+            color: GameSettings.bottomLineColor
+        }
+
+        Behavior on x {
+            NumberAnimation {
+                duration: 200
+            }
+        }
+    }
 
     Repeater {
         model: 3
-        Text {
+        Rectangle {
             id: caption
             required property int index
+            property bool hoveredOrPressed: mouseArea.pressed || mouseArea.containsMouse
             width: titleBar.width / 3
             height: titleBar.height
             x: index * width
-            horizontalAlignment: Text.AlignHCenter
-            verticalAlignment: Text.AlignVCenter
-            text: titleBar.__titles[index]
-            font.pixelSize: GameSettings.tinyFontSize
-            color: titleBar.currentIndex === index ? GameSettings.textColor
-                                                   : GameSettings.disabledTextColor
-
+            color: (titleBar.currentIndex !== index) && hoveredOrPressed
+                   ? GameSettings.hoverTitleColor : "transparent"
+            Text {
+                anchors.fill: parent
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: titleBar.__titles[caption.index]
+                font.pixelSize: GameSettings.microFontSize
+                color: GameSettings.textColor
+            }
             MouseArea {
+                id: mouseArea
                 anchors.fill: parent
+                hoverEnabled: true
                 onClicked: titleBar.titleClicked(caption.index)
             }
         }
     }
-
-    Item {
-        anchors.bottom: parent.bottom
-        width: parent.width / 3
-        height: parent.height
-        x: titleBar.currentIndex * width
-
-        BottomLine {}
-
-        Behavior on x {
-            NumberAnimation {
-                duration: 200
-            }
-        }
-    }
 }
diff --git a/examples/bluetooth/heartrate_game/HeartRateGame/images/alert.svg b/examples/bluetooth/heartrate_game/HeartRateGame/images/alert.svg
new file mode 100644 (file)
index 0000000..c48c10e
--- /dev/null
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4ZM12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 13C11.4477 13 11 12.5523 11 12V8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8V12C13 12.5523 12.5523 13 12 13ZM12 15C12.5523 15 13 15.4477 13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15Z" fill="white"/>
+</svg>
diff --git a/examples/bluetooth/heartrate_game/HeartRateGame/images/bluetooth.svg b/examples/bluetooth/heartrate_game/HeartRateGame/images/bluetooth.svg
new file mode 100644 (file)
index 0000000..6d01b28
--- /dev/null
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.1869 2.24413C11.4926 2.11749 11.8445 2.18749 12.0785 2.42149L16.5785 6.92149C16.8981 7.24101 16.8981 7.75905 16.5785 8.07857L12.6571 12L16.5785 15.9215C16.8981 16.241 16.8981 16.7591 16.5785 17.0786L12.0785 21.5786C11.8445 21.8126 11.4926 21.8826 11.1869 21.7559C10.8812 21.6293 10.6818 21.331 10.6818 21V13.9753L7.57855 17.0786C7.25903 17.3981 6.74098 17.3981 6.42146 17.0786C6.10194 16.7591 6.10194 16.241 6.42146 15.9215L10.3429 12L6.42146 8.07857C6.10194 7.75905 6.10194 7.24101 6.42146 6.92149C6.74098 6.60197 7.25903 6.60197 7.57855 6.92149L10.6818 10.0248V3.00003C10.6818 2.66911 10.8812 2.37077 11.1869 2.24413ZM12.3182 13.9753L14.8429 16.5L12.3182 19.0248V13.9753ZM12.3182 10.0248V4.9753L14.8429 7.50003L12.3182 10.0248Z" fill="#80EBB6"/>
+</svg>
diff --git a/examples/bluetooth/heartrate_game/HeartRateGame/images/clock.svg b/examples/bluetooth/heartrate_game/HeartRateGame/images/clock.svg
new file mode 100644 (file)
index 0000000..655996b
--- /dev/null
@@ -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="M8 20H16V17C16 15.9 15.6083 14.9583 14.825 14.175C14.0417 13.3917 13.1 13 12 13C10.9 13 9.95833 13.3917 9.175 14.175C8.39167 14.9583 8 15.9 8 17V20ZM12 11C13.1 11 14.0417 10.6083 14.825 9.825C15.6083 9.04167 16 8.1 16 7V4H8V7C8 8.1 8.39167 9.04167 9.175 9.825C9.95833 10.6083 10.9 11 12 11ZM5 22C4.44772 22 4 21.5523 4 21C4 20.4477 4.44772 20 5 20H6V17C6 15.9833 6.2375 15.0292 6.7125 14.1375C7.1875 13.2458 7.85 12.5333 8.7 12C7.85 11.4667 7.1875 10.7542 6.7125 9.8625C6.2375 8.97083 6 8.01667 6 7V4H5C4.44772 4 4 3.55228 4 3C4 2.44772 4.44772 2 5 2H19C19.5523 2 20 2.44772 20 3C20 3.55228 19.5523 4 19 4H18V7C18 8.01667 17.7625 8.97083 17.2875 9.8625C16.8125 10.7542 16.15 11.4667 15.3 12C16.15 12.5333 16.8125 13.2458 17.2875 14.1375C17.7625 15.0292 18 15.9833 18 17V20H19C19.5523 20 20 20.4477 20 21C20 21.5523 19.5523 22 19 22H5Z" fill="#80EBB6"/>
+<path d="M17 22H7L7 18C7 15.2386 9.23858 13 12 13C14.7614 13 17 15.2386 17 18V22Z" fill="#80EBB6"/>
+</svg>
index f2b3c0a3e97e12ddff880aeb01f23d1c59bba144..4ba0f822da214292e9a0e788caa37a9f398c4792 100644 (file)
Binary files a/examples/bluetooth/heartrate_game/HeartRateGame/images/heart.png and b/examples/bluetooth/heartrate_game/HeartRateGame/images/heart.png differ
diff --git a/examples/bluetooth/heartrate_game/HeartRateGame/images/progress.svg b/examples/bluetooth/heartrate_game/HeartRateGame/images/progress.svg
new file mode 100644 (file)
index 0000000..449fe5e
--- /dev/null
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M19 11C19 11.5523 19.4477 12 20 12C20.5523 12 21 11.5523 21 11V10.8478C21 8.11075 19.7088 5.53404 17.5163 3.89561C17.0739 3.56501 16.4472 3.65565 16.1166 4.09805C15.786 4.54046 15.8767 5.1671 16.3191 5.4977C18.0064 6.75857 19 8.74149 19 10.8478V11ZM4 12C4.55228 12 5 11.5523 5 11V10.8478C5 8.74149 5.99363 6.75857 7.68091 5.4977C8.12331 5.1671 8.21395 4.54046 7.88335 4.09805C7.55275 3.65565 6.92611 3.56501 6.4837 3.89561C4.29117 5.53404 3 8.11075 3 10.8478V11C3 11.5523 3.44772 12 4 12ZM7.10555 19.5528C7.35253 19.0588 7.95321 18.8586 8.44719 19.1055C10.6837 20.2238 13.3162 20.2238 15.5528 19.1056C16.0467 18.8586 16.6474 19.0588 16.8944 19.5528C17.1414 20.0468 16.9412 20.6474 16.4472 20.8944C13.6476 22.2942 10.3523 22.2942 7.55276 20.8944C7.05878 20.6474 6.85856 20.0467 7.10555 19.5528Z" fill="#80EBB6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13 5C13 5.55228 12.5523 6 12 6C11.4477 6 11 5.55228 11 5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5ZM15 5C15 6.65685 13.6569 8 12 8C10.3431 8 9 6.65685 9 5C9 3.34315 10.3431 2 12 2C13.6569 2 15 3.34315 15 5ZM5 17C5.55228 17 6 16.5523 6 16C6 15.4477 5.55228 15 5 15C4.44772 15 4 15.4477 4 16C4 16.5523 4.44772 17 5 17ZM5 19C6.65685 19 8 17.6569 8 16C8 14.3431 6.65685 13 5 13C3.34315 13 2 14.3431 2 16C2 17.6569 3.34315 19 5 19ZM19 17C19.5523 17 20 16.5523 20 16C20 15.4477 19.5523 15 19 15C18.4477 15 18 15.4477 18 16C18 16.5523 18.4477 17 19 17ZM19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16C16 17.6569 17.3431 19 19 19Z" fill="#80EBB6"/>
+</svg>
diff --git a/examples/bluetooth/heartrate_game/HeartRateGame/images/search.svg b/examples/bluetooth/heartrate_game/HeartRateGame/images/search.svg
new file mode 100644 (file)
index 0000000..9af5fe4
--- /dev/null
@@ -0,0 +1,4 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2929 14.2929C14.6834 13.9024 15.3166 13.9024 15.7071 14.2929L21.7071 20.2929C22.0976 20.6834 22.0976 21.3166 21.7071 21.7071C21.3166 22.0976 20.6834 22.0976 20.2929 21.7071L14.2929 15.7071C13.9024 15.3166 13.9024 14.6834 14.2929 14.2929Z" fill="#80EBB6"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10 4C6.68629 4 4 6.68629 4 10C4 13.3137 6.68629 16 10 16C13.3137 16 16 13.3137 16 10C16 6.68629 13.3137 4 10 4ZM2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10Z" fill="#80EBB6"/>
+</svg>
index 6278b04181e90526f4299204020635dee864301f..7f4c5a5b4c1b206b6c2d8410c412a9d2c8f5c9d6 100644 (file)
@@ -2,18 +2,36 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtCore import QObject, Property, Signal, Slot
+from enum import IntEnum
 
+from PySide6.QtQml import QmlElement, QmlUncreatable
+from PySide6.QtCore import QObject, Property, Signal, Slot, QEnum
 
+QML_IMPORT_NAME = "HeartRateGame"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+@QmlUncreatable("BluetoothBaseClass is not intended to be created directly")
 class BluetoothBaseClass(QObject):
 
+    @QEnum
+    class IconType(IntEnum):
+        IconNone = 0
+        IconBluetooth = 1
+        IconError = 2
+        IconProgress = 3
+        IconSearch = 4
+
     errorChanged = Signal()
     infoChanged = Signal()
+    iconChanged = Signal()
 
     def __init__(self, parent=None):
         super().__init__(parent)
         self.m_error = ""
         self.m_info = ""
+        self.m_icon = BluetoothBaseClass.IconType.IconNone
 
     @Property(str, notify=errorChanged)
     def error(self):
@@ -35,7 +53,18 @@ class BluetoothBaseClass(QObject):
             self.m_info = i
             self.infoChanged.emit()
 
+    @Property(int, notify=iconChanged)
+    def icon(self):
+        return self.m_icon
+
+    @icon.setter
+    def icon(self, i):
+        if self.m_icon != i:
+            self.m_icon = i
+            self.iconChanged.emit()
+
     @Slot()
     def clearMessages(self):
         self.info = ""
         self.error = ""
+        self.icon = BluetoothBaseClass.IconType.IconNone
index 5c00e7c94d74f760401c6e8429cc09ab69dabfd0..932f2bb4c1e78cc4fb0499fd41e7cdc3c7449bd3 100644 (file)
@@ -5,7 +5,7 @@ import sys
 
 from PySide6.QtBluetooth import (QBluetoothDeviceDiscoveryAgent,
                                  QBluetoothDeviceInfo)
-from PySide6.QtQml import QmlElement
+from PySide6.QtQml import QmlElement, QmlUncreatable
 from PySide6.QtCore import QTimer, Property, Signal, Slot, Qt
 
 from bluetoothbaseclass import BluetoothBaseClass
@@ -22,6 +22,7 @@ QML_IMPORT_MAJOR_VERSION = 1
 
 
 @QmlElement
+@QmlUncreatable("This class is not intended to be created directly")
 class DeviceFinder(BluetoothBaseClass):
 
     scanningChanged = Signal()
@@ -57,6 +58,7 @@ class DeviceFinder(BluetoothBaseClass):
                 qApp.requestPermission(permission, self, self.startSearch)  # noqa: F82 1
                 return
             elif permission_status == Qt.PermissionStatus.Denied:
+                self.icon = BluetoothBaseClass.IconType.IconError
                 return
             elif permission_status == Qt.PermissionStatus.Granted:
                 print("[HeartRateGame] Bluetooth Permission Granted")
@@ -75,6 +77,7 @@ class DeviceFinder(BluetoothBaseClass):
 #! [devicediscovery-2]
             self.scanningChanged.emit()
         self.info = "Scanning for devices..."
+        self.icon = BluetoothBaseClass.IconType.IconProgress
 
 #! [devicediscovery-3]
     @Slot(QBluetoothDeviceInfo)
@@ -83,6 +86,7 @@ class DeviceFinder(BluetoothBaseClass):
         if device.coreConfigurations() & QBluetoothDeviceInfo.LowEnergyCoreConfiguration:
             self.m_devices.append(DeviceInfo(device))
             self.info = "Low Energy device found. Scanning more..."
+            self.icon = BluetoothBaseClass.IconType.IconProgress
 #! [devicediscovery-3]
             self.devicesChanged.emit()
 #! [devicediscovery-4]
@@ -97,6 +101,7 @@ class DeviceFinder(BluetoothBaseClass):
             self.error = "Writing or reading from the device resulted in an error."
         else:
             self.error = "An unknown error has occurred."
+        self.icon = BluetoothBaseClass.IconType.IconError
 
     @Slot()
     def scanFinished(self):
@@ -107,12 +112,20 @@ class DeviceFinder(BluetoothBaseClass):
 
         if self.m_devices:
             self.info = "Scanning done."
+            self.icon = BluetoothBaseClass.IconType.IconBluetooth
         else:
             self.error = "No Low Energy devices found."
+            self.icon = BluetoothBaseClass.IconType.IconError
 
         self.scanningChanged.emit()
         self.devicesChanged.emit()
 
+    @Slot()
+    def resetMessages(self):
+        self.error = ""
+        self.info = "Start search to find devices"
+        self.icon = BluetoothBaseClass.IconType.IconSearch
+
     @Slot(str)
     def connectToService(self, address):
         self.m_deviceDiscoveryAgent.stop()
@@ -127,7 +140,7 @@ class DeviceFinder(BluetoothBaseClass):
         if currentDevice:
             self.m_deviceHandler.setDevice(currentDevice)
 
-        self.clearMessages()
+        self.resetMessages()
 
     @Property(bool, notify=scanningChanged)
     def scanning(self):
index 8599c029659e156be480280f90b6aeaf94fb40c7..f10c052bedcbb5d510cfeae81bcd6cbadfa2cf53 100644 (file)
@@ -69,31 +69,37 @@ class DeviceHandler(BluetoothBaseClass):
             self.m_demoTimer.start()
             self.updateDemoHR()
 
-    @Property(int)
     def addressType(self):
         if self.m_addressType == QLowEnergyController.RemoteAddressType.RandomAddress:
             return DeviceHandler.AddressType.RANDOM_ADDRESS
         return DeviceHandler.AddressType.PUBLIC_ADDRESS
 
-    @addressType.setter
-    def addressType(self, type):
+    @Slot(int)
+    def setAddressType(self, type):
         if type == DeviceHandler.AddressType.PUBLIC_ADDRESS:
             self.m_addressType = QLowEnergyController.RemoteAddressType.PublicAddress
         elif type == DeviceHandler.AddressType.RANDOM_ADDRESS:
             self.m_addressType = QLowEnergyController.RemoteAddressType.RandomAddress
 
+    @Slot()
+    def resetAddressType(self):
+        self.m_addressType = QLowEnergyController.RemoteAddressType.PublicAddress
+
     @Slot(QLowEnergyController.Error)
     def controllerErrorOccurred(self, device):
         self.error = "Cannot connect to remote device."
+        self.icon = BluetoothBaseClass.IconType.IconError
 
     @Slot()
     def controllerConnected(self):
         self.info = "Controller connected. Search services..."
+        self.icon = BluetoothBaseClass.IconType.IconProgress
         self.m_control.discoverServices()
 
     @Slot()
     def controllerDisconnected(self):
         self.error = "LowEnergy controller disconnected"
+        self.icon = BluetoothBaseClass.IconType.IconError
 
     def setDevice(self, device):
         self.clearMessages()
@@ -101,6 +107,7 @@ class DeviceHandler(BluetoothBaseClass):
 
         if simulator():
             self.info = "Demo device connected."
+            self.icon = BluetoothBaseClass.IconType.IconBluetooth
             return
 
         # Disconnect and delete old connection
@@ -152,6 +159,7 @@ class DeviceHandler(BluetoothBaseClass):
     def serviceDiscovered(self, gatt):
         if gatt == QBluetoothUuid(QBluetoothUuid.ServiceClassUuid.HeartRate):
             self.info = "Heart Rate service discovered. Waiting for service scan to be done..."
+            self.icon = BluetoothBaseClass.IconType.IconProgress
             self.m_foundHeartRateService = True
 
 #! [Filter HeartRate service 1]
@@ -159,6 +167,7 @@ class DeviceHandler(BluetoothBaseClass):
     @Slot()
     def serviceScanDone(self):
         self.info = "Service scan done."
+        self.icon = BluetoothBaseClass.IconType.IconProgress
 
         # Delete old service if available
         if self.m_service:
@@ -177,6 +186,8 @@ class DeviceHandler(BluetoothBaseClass):
             self.m_service.discoverDetails()
         else:
             self.error = "Heart Rate Service not found."
+            self.icon = BluetoothBaseClass.IconType.IconError
+
 #! [Filter HeartRate service 2]
 
 # Service functions
@@ -185,8 +196,10 @@ class DeviceHandler(BluetoothBaseClass):
     def serviceStateChanged(self, switch):
         if switch == QLowEnergyService.RemoteServiceDiscovering:
             self.info = "Discovering services..."
+            self.icon = BluetoothBaseClass.IconType.IconProgress
         elif switch == QLowEnergyService.RemoteServiceDiscovered:
             self.info = "Service discovered."
+            self.icon = BluetoothBaseClass.IconType.IconBluetooth
             hrChar = self.m_service.characteristic(
                 QBluetoothUuid(QBluetoothUuid.CharacteristicType.HeartRateMeasurement))
             if hrChar.isValid():
@@ -197,6 +210,7 @@ class DeviceHandler(BluetoothBaseClass):
                                                    QByteArray.fromHex(b"0100"))
             else:
                 self.error = "HR Data not found."
+                self.icon = BluetoothBaseClass.IconType.IconError
         self.aliveChanged.emit()
 #! [Find HRM characteristic]
 
@@ -308,3 +322,5 @@ class DeviceHandler(BluetoothBaseClass):
                                 + (0.2017 * 24)) / 4.184) * 60 * self.time / 3600
 
         self.statsChanged.emit()
+
+    addressType = Property(int, addressType, setAddressType, freset=resetAddressType)
index 136bbbacbf165152a753d6d5c9dbe79936cd629c..60cdb5aafa65470957a29ba3dba8bbc6977233d5 100644 (file)
@@ -27,7 +27,7 @@ class DeviceInfo(QObject):
     @Property(str, notify=deviceChanged)
     def deviceName(self):
         if simulator():
-            return "Demo device"
+            return "Demo BT device"
         return self.m_device.name()
 
     @Property(str, notify=deviceChanged)
index 9d190d991848e7afcf8d917ea5cb7d2c8e706d55..1704e32d87f74ea068419d49d1b1cdda21c52175 100644 (file)
@@ -9,3 +9,6 @@ application covers the scanning for Bluetooth Low Energy devices,
 connecting to a Heart Rate service on the device, writing
 characteristics and descriptors, and receiving updates from the device
 once the heart rate has changed.
+
+The command line option `--simulator` can be used to run the example against a
+demo server in case no Bluetooth hardware is available.
index 384eb93a7d5e234af62443578aef2a220e22ff51..75a0b42b8d52de6d7c94e2e4e31693b6427e4dce 100644 (file)
@@ -8,7 +8,6 @@ _simulator = False
 
 
 def simulator():
-    global _simulator
     return _simulator
 
 
index 00df7de6a8c63ae83cc29fb2d06650431cd58aa6..a70b9f3498deccec5ca8cc9171a502aae71c6eaf 100644 (file)
@@ -15,6 +15,7 @@ from PySide6.QtCore import QCoreApplication, QLoggingCategory
 from connectionhandler import ConnectionHandler
 from devicefinder import DeviceFinder
 from devicehandler import DeviceHandler
+from bluetoothbaseclass import BluetoothBaseClass  # noqa: F401
 from heartrate_global import set_simulator
 
 
index 70326242e30b831fa5c2c446fe6e0d9973d0fa05..ec0d599ba642d8ca505458342871c038e020890a 100644 (file)
@@ -39,14 +39,14 @@ Rectangle {
 
     Connections {
         target: Device
-        function onservices_updated() {
+        function onServices_updated() {
             if (servicesview.count === 0)
                 info.dialogText = "No services found"
             else
                 info.visible = false
         }
 
-        function ondisconnected() {
+        function onDisconnected() {
             servicesPage.showDevices()
         }
     }
index afa30ff80cdd9f641271bd414d9dbce87d206719..5ae701fde553cd9e11d4d21f962ec6a64d04d5f6 100644 (file)
@@ -82,7 +82,8 @@ class Device(QObject):
 
     @Property(bool)
     def controller_error(self):
-        return self.controller and (self.controller.error() != QLowEnergyController.NoError)
+        return (self.controller is not None
+                and (self.controller.error() != QLowEnergyController.NoError))
 
     @Slot()
     def start_device_discovery(self):
index d8ba8e6e8c1e80a76546c292ecc90d60964ed91b..c61e955c33854c4e6fd915ea5e7668461392e336 100644 (file)
@@ -82,6 +82,7 @@ class Callout(QGraphicsItem):
             path.lineTo(point2)
             path = path.simplified()
 
+        painter.setPen(QColor(0, 0, 0))
         painter.setBrush(QColor(255, 255, 255))
         painter.drawPath(path)
         painter.drawText(self._textRect, self._text)
@@ -183,7 +184,7 @@ class View(QGraphicsView):
                 self._chart.size().width() / 2 + 50,
                 self._chart.size().height() - 20)
             for callout in self._callouts:
-                callout.updateGeometry()
+                callout.update_geometry()
         QGraphicsView.resizeEvent(self, event)
 
     def mouseMoveEvent(self, event):
index 4f7b6959a5b905589226a8e97cd63e3f2cbc659b..00d58bde32bf39d38b159c3757608246a8e95b4d 100644 (file)
@@ -8,10 +8,15 @@ import argparse
 import sys
 
 from mainwindow import MainWindow
+from PySide6.QtCore import QLibraryInfo, QLocale, QTranslator
 from PySide6.QtWidgets import QApplication
 
 if __name__ == "__main__":
     app = QApplication(sys.argv)
+    translator = QTranslator(app)
+    if translator.load(QLocale.system(), "qtbase", "_",
+                       QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)):
+        app.installTranslator(translator)
 
     parser = argparse.ArgumentParser(description="MimeTypesBrowser Example")
     parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0")
index 852c50fe31ea9e110b3628e6161e5b4a3bef460e..076c56e399c6c9813b9e82cbb32eba40969cec02 100644 (file)
@@ -12,6 +12,19 @@ QML_IMPORT_NAME = "ColorPalette"
 QML_IMPORT_MAJOR_VERSION = 1
 
 
+class ApiKeyRequestFactory(QNetworkRequestFactory):
+    """Custom request factory that adds the reqres.in API key to all requests"""
+
+    def createRequest(self, path, query=None):
+        """Override to add API key header to every request"""
+        if query is None:
+            request = super().createRequest(path)
+        else:
+            request = super().createRequest(path, query)
+        request.setRawHeader(b"x-api-key", b"reqres-free-v1")
+        return request
+
+
 @QmlElement
 @ClassInfo(DefaultProperty="resources")
 class RestService(QPyQmlParserStatus):
@@ -24,7 +37,7 @@ class RestService(QPyQmlParserStatus):
         self.m_qnam = QNetworkAccessManager()
         self.m_qnam.setAutoDeleteReplies(True)
         self.m_manager = QRestAccessManager(self.m_qnam)
-        self.m_serviceApi = QNetworkRequestFactory()
+        self.m_serviceApi = ApiKeyRequestFactory()
 
     @Property(str, notify=urlChanged)
     def url(self):
index 814aae024446c1775661f86c05948655407c1aef..1a702aba9e28db419950b2ae30b9712170cd4a51 100644 (file)
@@ -1,10 +1,34 @@
 {
     "files": ["3d/simple3d/simple3d.py",
+              "async/eratosthenes/eratosthenes_asyncio.py",
+              "async/eratosthenes/eratosthenes_trio.py",
+              "async/minimal/minimal_asyncio.py",
+              "async/minimal/minimal_trio.py",
               "axcontainer/axviewer/axviewer.py",
+              "bluetooth/btscanner/main.py",
+              "bluetooth/btscanner/device.py",
+              "bluetooth/btscanner/service.py",
+              "bluetooth/heartrate_game/main.py",
+              "bluetooth/heartrate_game/bluetoothbaseclass.py",
+              "bluetooth/heartrate_game/connectionhandler.py",
+              "bluetooth/heartrate_game/devicefinder.py",
+              "bluetooth/heartrate_game/devicehandler.py",
+              "bluetooth/heartrate_game/deviceinfo.py",
+              "bluetooth/heartrate_game/heartrate_global.py",
+              "bluetooth/heartrate_server/heartrate_server.py",
+              "bluetooth/lowenergyscanner/main.py",
+              "bluetooth/lowenergyscanner/characteristicinfo.py",
+              "bluetooth/lowenergyscanner/device.py",
+              "bluetooth/lowenergyscanner/deviceinfo.py",
+              "bluetooth/lowenergyscanner/serviceinfo.py",
+              "charts/areachart/areachart.py",
               "charts/audio/audio.py",
+              "charts/barchart/barchart.py",
               "charts/callout/callout.py",
               "charts/chartthemes/main.py",
               "charts/donutbreakdown/donutbreakdown.py",
+              "charts/dynamicspline/dynamicspline.py",
+              "charts/dynamicspline/main.py",
               "charts/legend/legend.py",
               "charts/lineandbar/lineandbar.py",
               "charts/linechart/linechart.py",
               "charts/nesteddonuts/nesteddonuts.py",
               "charts/percentbarchart/percentbarchart.py",
               "charts/piechart/piechart.py",
+              "charts/pointconfiguration/chartwindow.py",
+              "charts/pointconfiguration/pointconfiguration.py",
+              "charts/pointselectionandmarkers/pointselectionandmarkers.py",
+              "charts/pointselectionandmarkers/utilities.py",
               "charts/qmlpolarchart/qmlpolarchart.py",
               "charts/temperaturerecords/temperaturerecords.py",
+              "charts/zoomlinechart/chart.py",
+              "charts/zoomlinechart/chartview.py",
+              "charts/zoomlinechart/main.py",
+              "corelib/ipc/sharedmemory/dialog.py",
+              "corelib/ipc/sharedmemory/main.py",
+              "corelib/mimetypesbrowser/mainwindow.py",
+              "corelib/mimetypesbrowser/mimetypemodel.py",
+              "corelib/mimetypesbrowser/mimetypesbrowser.py",
               "corelib/settingseditor/settingseditor.py",
               "corelib/threads/mandelbrot.py",
               "datavisualization/bars3d/bars3d.py",
-              "declarative/extending/chapter1-basics/basics.py",
-              "declarative/extending/chapter2-methods/methods.py",
-              "declarative/extending/chapter3-bindings/bindings.py",
-              "declarative/extending/chapter4-customPropertyTypes/customPropertyTypes.py",
-              "declarative/extending/chapter5-listproperties/listproperties.py",
-              "declarative/scrolling/scrolling.py",
-              "declarative/signals/pytoqml1/main.py",
-              "declarative/signals/pytoqml2/main.py",
-              "declarative/signals/qmltopy1/main.py",
-              "declarative/signals/qmltopy2/main.py",
-              "declarative/signals/qmltopy3/main.py",
-              "declarative/signals/qmltopy4/main.py",
-              "declarative/textproperties/main.py",
-              "declarative/usingmodel/usingmodel.py",
+              "datavisualization/graphgallery/axesinputhandler.py",
+              "datavisualization/graphgallery/bargraph.py",
+              "datavisualization/graphgallery/custominputhandler.py",
+              "datavisualization/graphgallery/graphmodifier.py",
+              "datavisualization/graphgallery/highlightseries.py",
+              "datavisualization/graphgallery/main.py",
+              "datavisualization/graphgallery/rainfalldata.py",
+              "datavisualization/graphgallery/scatterdatamodifier.py",
+              "datavisualization/graphgallery/scattergraph.py",
+              "datavisualization/graphgallery/surfacegraph.py",
+              "datavisualization/graphgallery/surfacegraphmodifier.py",
+              "datavisualization/graphgallery/topographicseries.py",
+              "datavisualization/graphgallery/variantbardatamapping.py",
+              "datavisualization/graphgallery/variantbardataproxy.py",
+              "datavisualization/graphgallery/variantdataset.py",
+              "datavisualization/minimalsurface/main.py",
+              "datavisualization/qmlsurfacegallery/datasource.py",
+              "datavisualization/qmlsurfacegallery/main.py",
+              "datavisualization/surface/main.py",
+              "datavisualization/surface/surfacegraph.py",
+              "datavisualization/surface_model_numpy/main.py",
+              "datavisualization/surface_model_numpy/surfacegraph.py",
+              "datavisualization/surface_numpy/main.py",
+              "datavisualization/surface_numpy/surfacegraph.py",
+              "dbus/listnames/listnames.py",
+              "dbus/pingpong/ping.py",
+              "dbus/pingpong/pong.py",
+              "demos/colorpaletteclient/abstractresource.py",
+              "demos/colorpaletteclient/basiclogin.py",
+              "demos/colorpaletteclient/main.py",
+              "demos/colorpaletteclient/paginatedresource.py",
+              "demos/colorpaletteclient/restservice.py",
+              "demos/documentviewer/imageviewer/imageviewer.py",
+              "demos/documentviewer/jsonviewer/jsonviewer.py",
+              "demos/documentviewer/pdfviewer/pdfviewer.py",
+              "demos/documentviewer/pdfviewer/zoomselector.py",
+              "demos/documentviewer/txtviewer/txtviewer.py",
+              "demos/documentviewer/abstractviewer.py",
+              "demos/documentviewer/main.py",
+              "demos/documentviewer/mainwindow.py",
+              "demos/documentviewer/recentfilemenu.py",
+              "demos/documentviewer/recentfiles.py",
+              "demos/documentviewer/viewerfactory.py",
+              "demos/osmbuildings/main.py",
+              "demos/osmbuildings/manager.py",
+              "demos/osmbuildings/request.py",
               "designer/taskmenuextension/main.py",
               "designer/taskmenuextension/registertictactoe.py",
               "designer/taskmenuextension/tictactoeplugin.py",
               "designer/taskmenuextension/tictactoe.py",
               "designer/taskmenuextension/tictactoetaskmenu.py",
-              "external/matplotlib/widget_3dplot.py",
+              "external/matplotlib/widget_gaussian/widget_gaussian.py",
+              "external/matplotlib/widget3d/widget3d.py",
+              "external/networkx/main.py",
               "external/opencv/webcam_pattern_detection.py",
               "external/pandas/dataframe_model.py",
               "external/scikit/staining_colors_separation.py",
+              "graphs/2d/graphsaudio/main.py",
+              "graphs/2d/hellographs/main.py",
+              "graphs/3d/bars/main.py",
+              "graphs/3d/minimalsurfacegraph/main.py",
+              "graphs/3d/widgetgraphgallery/bargraph.py",
+              "graphs/3d/widgetgraphgallery/graphmodifier.py",
+              "graphs/3d/widgetgraphgallery/highlightseries.py",
+              "graphs/3d/widgetgraphgallery/main.py",
+              "graphs/3d/widgetgraphgallery/rainfalldata.py",
+              "graphs/3d/widgetgraphgallery/scatterdatamodifier.py",
+              "graphs/3d/widgetgraphgallery/scattergraph.py",
+              "graphs/3d/widgetgraphgallery/surfacegraph.py",
+              "graphs/3d/widgetgraphgallery/surfacegraphmodifier.py",
+              "graphs/3d/widgetgraphgallery/topographicseries.py",
+              "graphs/3d/widgetgraphgallery/variantbardatamapping.py",
+              "graphs/3d/widgetgraphgallery/variantbardataproxy.py",
+              "graphs/3d/widgetgraphgallery/variantdataset.py",
+              "gui/analogclock/main.py",
+              "gui/rhiwindow/main.py",
+              "gui/rhiwindow/rhiwindow.py",
+              "httpserver/simplehttpserver/main.py",
               "installer_test/hello.py",
-              "macextras/macpasteboardmime/macpasteboardmime.py",
+              "location/mapviewer/main.py",
               "multimedia/audiooutput/audiooutput.py",
+              "multimedia/audiosource/audiosource.py",
               "multimedia/camera/camera.py",
+              "multimedia/camera/imagesettings.py",
+              "multimedia/camera/videosettings.py",
               "multimedia/player/player.py",
+              "multimedia/screencapture/main.py",
+              "multimedia/screencapture/screencapturepreview.py",
+              "multimedia/screencapture/screenlistmodel.py",
+              "multimedia/screencapture/windowlistmodel.py",
               "network/blockingfortuneclient/blockingfortuneclient.py",
+              "network/downloader/downloader.py",
               "network/fortuneclient/fortuneclient.py",
               "network/fortuneserver/fortuneserver.py",
+              "network/googlesuggest/googlesuggest.py",
+              "network/googlesuggest/main.py",
+              "network/googlesuggest/searchbox.py",
+              "network/loopback/dialog.py",
+              "network/loopback/main.py",
               "network/threadedfortuneserver/threadedfortuneserver.py",
+              "networkauth/redditclient/main.py",
+              "networkauth/redditclient/redditmodel.py",
+              "networkauth/redditclient/redditwrapper.py",
               "opengl/contextinfo/contextinfo.py",
-              "opengl/hellogl2/hellogl2.py",
+              "opengl/hellogl2/glwidget.py",
+              "opengl/hellogl2/logo.py",
+              "opengl/hellogl2/main.py",
+              "opengl/hellogl2/mainwindow.py",
+              "opengl/hellogl2/window.py",
               "opengl/textures/textures.py",
+              "opengl/threadedopenglwidget/glwidget.py",
+              "opengl/threadedopenglwidget/main.py",
+              "opengl/threadedopenglwidget/mainwindow.py",
+              "opengl/threadedopenglwidget/renderer.py",
+              "pdf/quickpdfviewer/main.py",
+              "pdfwidgets/pdfviewer/main.py",
+              "pdfwidgets/pdfviewer/mainwindow.py",
+              "pdfwidgets/pdfviewer/zoomselector.py",
+              "qml/editingmodel/main.py",
+              "qml/editingmodel/model.py",
+              "qml/signals/qmltopy1/main.py",
+              "qml/signals/qmltopy2/main.py",
+              "qml/signals/qmltopy3/main.py",
+              "qml/signals/qmltopy4/main.py",
+              "qml/tutorials/extending-qml/chapter1-basics/basics.py",
+              "qml/tutorials/extending-qml/chapter2-methods/methods.py",
+              "qml/tutorials/extending-qml/chapter3-bindings/bindings.py",
+              "qml/tutorials/extending-qml/chapter4-customPropertyTypes/customPropertyTypes.py",
+              "qml/tutorials/extending-qml/chapter5-listproperties/listproperties.py",
+              "qml/tutorials/extending-qml/chapter6-plugins/Charts/piechart.py",
+              "qml/tutorials/extending-qml/chapter6-plugins/Charts/pieslice.py",
+              "qml/textproperties/main.py",
+              "qml/usingmodel/usingmodel.py",
+              "quick/models/objectlistmodel/objectlistmodel.py",
+              "quick/models/stringlistmodel/stringlistmodel.py",
+              "quick/painteditem/painteditem.py",
+              "quick/rendercontrol/rendercontrol_opengl/cuberenderer.py",
+              "quick/rendercontrol/rendercontrol_opengl/main.py",
+              "quick/rendercontrol/rendercontrol_opengl/window_singlethreaded.py",
+              "quick/scenegraph/openglunderqml/main.py",
+              "quick/scenegraph/openglunderqml/squircle.py",
+              "quick/scenegraph/openglunderqml/squirclerenderer.py",
+              "quick/scenegraph/scenegraph_customgeometry/main.py",
+              "quick/scenegraph/window/main.py",
+              "quick3d/customgeometry/examplepoint.py",
+              "quick3d/customgeometry/exampletriangle.py",
+              "quick3d/customgeometry/main.py",
+              "quick3d/intro/main.py",
+              "quick3d/proceduraltexture/gradienttexture.py",
+              "quick3d/proceduraltexture/main.py",
+              "quickcontrols/contactslist/main.py",
               "quickcontrols/gallery/gallery.py",
-              "quickcontrols/filesystemexplorer/filesystemexplorer.py",
+              "quickcontrols/filesystemexplorer/main.py",
               "quick/painteditem/painteditem.py",
               "remoteobjects/modelview/modelviewclient.py",
               "remoteobjects/modelview/modelviewserver.py",
               "samplebinding/main.py",
+              "serialbus/can/bitratebox.py",
+              "serialbus/can/canbusdeviceinfo.py",
+              "serialbus/can/canbusdeviceinfodialog.py",
+              "serialbus/can/connectdialog.py",
+              "serialbus/can/main.py",
+              "serialbus/can/mainwindow.py",
+              "serialbus/can/receivedframesmodel.py",
+              "serialbus/can/receivedframesview.py",
+              "serialbus/can/sendframebox.py",
+              "serialbus/modbus/modbusclient/main.py",
+              "serialbus/modbus/modbusclient/mainwindow.py",
+              "serialbus/modbus/modbusclient/settingsdialog.py",
+              "serialbus/modbus/modbusclient/writeregistermodel.py",
+              "serialport/terminal/console.py",
+              "serialport/terminal/main.py",
+              "serialport/terminal/mainwindow.py",
+              "serialport/terminal/settingsdialog.py",
+              "spatialaudio/audiospanning/main.py",
+              "speech/hello_speak/main.py",
+              "speech/hello_speak/mainwindow.py",
               "sql/books/bookdelegate.py",
               "sql/books/bookwindow.py",
               "sql/books/createdb.py",
               "sql/books/main.py",
-              "texttospeech/hello_speak/hello_speak.py",
+              "sql/relationaltablemodel/connection.py",
+              "sql/relationaltablemodel/relationaltablemodel.py",
+              "statemachine/moveblocks/moveblocks.py",
+              "statemachine/ping_pong/ping_pong.py",
+              "statemachine/rogue/rogue.py",
+              "statemachine/trafficlight/trafficlight.py",
+              "tutorials/finance_manager/part1/financemodel.py",
+              "tutorials/finance_manager/part1/main.py",
+              "tutorials/finance_manager/part2/database.py",
+              "tutorials/finance_manager/part2/financemodel.py",
+              "tutorials/finance_manager/part2/main.py",
+              "tutorials/finance_manager/part3/Backend/database.py",
+              "tutorials/finance_manager/part3/Backend/main.py",
+              "tutorials/finance_manager/part3/Backend/rest_api.py",
+              "tutorials/finance_manager/part3/Frontend/financemodel.py",
+              "tutorials/finance_manager/part3/Frontend/main.py",
               "uitools/uiloader/uiloader.py",
+              "utils/pyside_config.py",
               "webchannel/standalone/core.py",
               "webchannel/standalone/dialog.py",
               "webchannel/standalone/main.py",
               "webchannel/standalone/websocketclientwrapper.py",
               "webchannel/standalone/websockettransport.py",
               "webenginequick/nanobrowser/quicknanobrowser.py",
-              "webenginewidgets/simplebrowser/simplebrowser.py",
-              "webenginewidgets/tabbedbrowser/bookmarkwidget.py",
-              "webenginewidgets/tabbedbrowser/browsertabwidget.py",
-              "webenginewidgets/tabbedbrowser/downloadwidget.py",
-              "webenginewidgets/tabbedbrowser/findtoolbar.py",
-              "webenginewidgets/tabbedbrowser/historywindow.py",
-              "webenginewidgets/tabbedbrowser/main.py",
-              "webenginewidgets/tabbedbrowser/webengineview.py",
+              "webenginewidgets/markdowneditor/document.py",
+              "webenginewidgets/markdowneditor/main.py",
+              "webenginewidgets/markdowneditor/mainwindow.py",
+              "webenginewidgets/markdowneditor/previewpage.py",
+              "webenginewidgets/notifications/main.py",
+              "webenginewidgets/notifications/notificationpopup.py",
+              "webenginewidgets/simplebrowser/browser.py",
+              "webenginewidgets/simplebrowser/browserwindow.py",
+              "webenginewidgets/simplebrowser/downloadmanagerwidget.py",
+              "webenginewidgets/simplebrowser/downloadwidget.py",
+              "webenginewidgets/simplebrowser/main.py",
+              "webenginewidgets/simplebrowser/tabwidget.py",
+              "webenginewidgets/simplebrowser/webauthdialog.py",
+              "webenginewidgets/simplebrowser/webpage.py",
+              "webenginewidgets/simplebrowser/webpopupwindow.py",
+              "webenginewidgets/simplebrowser/webview.py",
+              "webenginewidgets/widgetsnanobrowser/widgetsnanobrowser.py",
+              "webview/minibrowser/main.py",
               "widgetbinding/dialog.py",
               "widgetbinding/main.py",
               "widgetbinding/registerwigglywidget.py",
               "widgets/animation/appchooser/appchooser.py",
               "widgets/animation/easing/easing.py",
               "widgets/animation/states/states.py",
-              "widgets/codeeditor/codeeditor.py",
-              "widgets/codeeditor/main.py",
+              "widgets/desktop/screenshot/screenshot.py",
+              "widgets/desktop/systray/main.py",
+              "widgets/desktop/systray/window.py",
               "widgets/dialogs/classwizard/classwizard.py",
+              "widgets/dialogs/classwizard/listchooser.py",
               "widgets/dialogs/extension/extension.py",
-              "widgets/dialogs/findfiles/findfiles.py",
+              "widgets/dialogs/licensewizard/licensewizard.py",
+              "widgets/dialogs/licensewizard/main.py",
               "widgets/dialogs/standarddialogs/standarddialogs.py",
+              "widgets/dialogs/tabdialog/tabdialog.py",
               "widgets/dialogs/trivialwizard/trivialwizard.py",
+              "widgets/draganddrop/draggableicons/draggableicons.py",
               "widgets/draganddrop/draggabletext/draggabletext.py",
-              "widgets/effects/lighting.py",
-              "widgets/gallery/main.py",
-              "widgets/gallery/widgetgallery.py",
+              "widgets/draganddrop/dropsite/droparea.py",
+              "widgets/draganddrop/dropsite/dropsitewindow.py",
+              "widgets/draganddrop/dropsite/main.py",
+              "widgets/effects/blurpicker/blureffect.py",
+              "widgets/effects/blurpicker/blurpicker.py",
+              "widgets/effects/blurpicker/main.py",
+              "widgets/effects/lighting/lighting.py",
+              "widgets/gettext/main.py",
               "widgets/graphicsview/anchorlayout/anchorlayout.py",
               "widgets/graphicsview/collidingmice/collidingmice.py",
               "widgets/graphicsview/diagramscene/diagramscene.py",
               "widgets/graphicsview/elasticnodes/elasticnodes.py",
               "widgets/imageviewer/imageviewer.py",
               "widgets/imageviewer/main.py",
-              "widgets/itemviews/addressbook/adddialogwidget.py",
-              "widgets/itemviews/addressbook/addressbook.py",
-              "widgets/itemviews/addressbook/addresswidget.py",
-              "widgets/itemviews/addressbook/newaddresstab.py",
-              "widgets/itemviews/addressbook/tablemodel.py",
+              "widgets/itemviews/address_book/adddialogwidget.py",
+              "widgets/itemviews/address_book/address_book.py",
+              "widgets/itemviews/address_book/addresswidget.py",
+              "widgets/itemviews/address_book/newaddresstab.py",
+              "widgets/itemviews/address_book/tablemodel.py",
               "widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py",
+              "widgets/itemviews/dirview/dirview.py",
+              "widgets/itemviews/editabletreemodel/main.py",
+              "widgets/itemviews/editabletreemodel/mainwindow.py",
+              "widgets/itemviews/editabletreemodel/treeitem.py",
+              "widgets/itemviews/editabletreemodel/treemodel.py",
               "widgets/itemviews/fetchmore/fetchmore.py",
+              "widgets/itemviews/jsonmodel/jsonmodel.py",
+              "widgets/itemviews/spinboxdelegate/spinboxdelegate.py",
+              "widgets/itemviews/spreadsheet/main.py",
+              "widgets/itemviews/spreadsheet/spreadsheet.py",
+              "widgets/itemviews/spreadsheet/spreadsheetdelegate.py",
+              "widgets/itemviews/spreadsheet/spreadsheetitem.py",
               "widgets/itemviews/stardelegate/stardelegate.py",
               "widgets/itemviews/stardelegate/stareditor.py",
               "widgets/itemviews/stardelegate/starrating.py",
               "widgets/layouts/basiclayouts/basiclayouts.py",
+              "widgets/layouts/borderlayout/borderlayout.py",
               "widgets/layouts/dynamiclayouts/dynamiclayouts.py",
               "widgets/layouts/flowlayout/flowlayout.py",
+              "widgets/linguist/main.py",
               "widgets/mainwindows/application/application.py",
               "widgets/mainwindows/dockwidgets/dockwidgets.py",
               "widgets/mainwindows/mdi/mdi.py",
               "widgets/painting/basicdrawing/basicdrawing.py",
               "widgets/painting/concentriccircles/concentriccircles.py",
+              "widgets/painting/painter/painter.py",
+              "widgets/painting/plot/plot.py",
+              "widgets/rhi/simplerhiwidget/examplewidget.py",
+              "widgets/rhi/simplerhiwidget/main.py",
               "widgets/richtext/orderform/orderform.py",
               "widgets/richtext/syntaxhighlighter/syntaxhighlighter.py",
+              "widgets/richtext/textedit/main.py",
+              "widgets/richtext/textedit/textedit.py",
               "widgets/richtext/textobject/textobject.py",
-              "widgets/state-machine/eventtrans/eventtrans.py",
-              "widgets/state-machine/factstates/factstates.py",
-              "widgets/state-machine/pingpong/pingpong.py",
-              "widgets/state-machine/rogue/rogue.py",
-              "widgets/state-machine/trafficlight/trafficlight.py",
-              "widgets/state-machine/twowaybutton/twowaybutton.py",
-              "widgets/systray/main.py",
-              "widgets/systray/window.py",
-              "widgets/tetrix/tetrix.py",
               "widgets/threads/thread_signals.py",
-              "widgets/tutorials/addressbook/part1.py",
-              "widgets/tutorials/addressbook/part2.py",
-              "widgets/tutorials/addressbook/part3.py",
-              "widgets/tutorials/addressbook/part4.py",
-              "widgets/tutorials/addressbook/part5.py",
-              "widgets/tutorials/addressbook/part6.py",
-              "widgets/tutorials/addressbook/part7.py",
+              "widgets/tools/regularexpression/regularexpression.py",
+              "widgets/tools/regularexpression/regularexpressiondialog.py",
               "widgets/tutorials/cannon/t10.py",
               "widgets/tutorials/cannon/t11.py",
               "widgets/tutorials/cannon/t12.py",
               "widgets/tutorials/cannon/t7.py",
               "widgets/tutorials/cannon/t8.py",
               "widgets/tutorials/cannon/t9.py",
+              "widgets/tutorials/modelview/1_readonly.py",
+              "widgets/tutorials/modelview/2_formatting.py",
+              "widgets/tutorials/modelview/3_changingmodel.py",
+              "widgets/tutorials/modelview/4_headers.py",
+              "widgets/tutorials/modelview/5_edit.py",
+              "widgets/tutorials/modelview/6_treeview.py",
+              "widgets/tutorials/modelview/7_selections.py",
+              "widgets/widgets/charactermap/characterwidget.py",
+              "widgets/widgets/charactermap/fontinfodialog.py",
+              "widgets/widgets/charactermap/main.py",
+              "widgets/widgets/charactermap/mainwindow.py",
+              "widgets/widgets/digitalclock/digitalclock.py",
+              "widgets/widgets/tetrix/tetrix.py",
+              "widgets/widgetsgallery/main.py",
+              "widgets/widgetsgallery/widgetgallery.py",
               "xml/dombookmarks/dombookmarks.py"]
 }
index 815e365ee4bf24d07c8bde4c9dfc2ea078fc5c41..268bf99e9f094fe05f4f582ab90b1df992ae7a95 100644 (file)
@@ -42,7 +42,7 @@ Item {
                     subTickCount: 9
                 }
                 theme: GraphsTheme {
-                    colorScheme: Qt.Dark
+                    colorScheme: GraphsTheme.ColorScheme.Dark
                     theme: GraphsTheme.Theme.QtGreen
                 }
                 //! [bargraph]
@@ -77,7 +77,7 @@ Item {
                     readonly property color c1: "#DBEB00"
                     readonly property color c2: "#373F26"
                     readonly property color c3: Qt.lighter(c2, 1.5)
-                    colorScheme: Qt.Dark
+                    colorScheme: GraphsTheme.ColorScheme.Dark
                     seriesColors: ["#2CDE85", "#DBEB00"]
                     grid.mainColor: c3
                     grid.subColor: c2
index 3259e8a33382bad002c749ca5b12c80fe1da9883..3e3cf1092601480c64539a61977817c4bfa50c8b 100644 (file)
@@ -180,9 +180,9 @@ class BarGraph(QObject):
         modeGroup = QButtonGroup(self._barsWidget)
         modeWeather = QRadioButton("Temperature Data", self._barsWidget)
         modeWeather.setChecked(True)
-        modeCustomProxy = QRadioButton("Custom Proxy Data", self._barsWidget)
+        modelProxy = QRadioButton("Model Proxy Data", self._barsWidget)
         modeGroup.addButton(modeWeather)
-        modeGroup.addButton(modeCustomProxy)
+        modeGroup.addButton(modelProxy)
 
         vLayout.addWidget(QLabel("Rotate horizontally"))
         vLayout.addWidget(rotationSliderX, 0, Qt.AlignmentFlag.AlignTop)
@@ -215,7 +215,7 @@ class BarGraph(QObject):
         vLayout.addWidget(QLabel("Axis label rotation"))
         vLayout.addWidget(axisLabelRotationSlider, 0, Qt.AlignmentFlag.AlignTop)
         vLayout.addWidget(modeWeather, 0, Qt.AlignmentFlag.AlignTop)
-        vLayout.addWidget(modeCustomProxy, 1, Qt.AlignmentFlag.AlignTop)
+        vLayout.addWidget(modelProxy, 1, Qt.AlignmentFlag.AlignTop)
 
         modifier = GraphModifier(barsGraph, self)
         modifier.changeTheme(themeList.currentIndex())
@@ -260,7 +260,7 @@ class BarGraph(QObject):
         axisLabelRotationSlider.valueChanged.connect(modifier.changeLabelRotation)
 
         modeWeather.toggled.connect(modifier.setDataModeToWeather)
-        modeCustomProxy.toggled.connect(modifier.setDataModeToCustom)
+        modelProxy.toggled.connect(modifier.setDataModeToModel)
         modeWeather.toggled.connect(seriesCheckBox.setEnabled)
         modeWeather.toggled.connect(rangeList.setEnabled)
         modeWeather.toggled.connect(axisTitlesVisibleCB.setEnabled)
index 5c4d23c92b60ece93df41e62aec02c00046e5d18..b77d0deb14666fc21d6af5f7a7b72502f86bf737 100644 (file)
@@ -336,7 +336,7 @@ class GraphModifier(QObject):
             self.changeDataMode(False)
 
     @Slot(bool)
-    def setDataModeToCustom(self, enabled):
+    def setDataModeToModel(self, enabled):
         if enabled:
             self.changeDataMode(True)
 
index 58a0d531b9ca82e54634f1d063d1b9fb31ba6f1a..be78521298f7afe421880bcc1e11142efe291782 100644 (file)
@@ -23,7 +23,7 @@ class HighlightSeries(QSurface3DSeries):
         self._height = 100
         self._srcWidth = 0
         self._srcHeight = 0
-        self._position = {}
+        self._position = QPoint()
         self._topographicSeries = None
         self._minHeight = 0.0
         self._height_adjustment = 5.0
@@ -52,16 +52,16 @@ class HighlightSeries(QSurface3DSeries):
         halfWidth = self._width / 2
         halfHeight = self._height / 2
 
-        startX = position.y() - halfWidth
+        startX = position.x() - halfWidth
         if startX < 0:
             startX = 0
-        endX = position.y() + halfWidth
+        endX = position.x() + halfWidth
         if endX > (self._srcWidth - 1):
             endX = self._srcWidth - 1
-        startZ = position.x() - halfHeight
+        startZ = position.y() - halfHeight
         if startZ < 0:
             startZ = 0
-        endZ = position.x() + halfHeight
+        endZ = position.y() + halfHeight
         if endZ > (self._srcHeight - 1):
             endZ = self._srcHeight - 1
 
@@ -71,10 +71,10 @@ class HighlightSeries(QSurface3DSeries):
         for i in range(int(startZ), int(endZ)):
             newRow = []
             srcRow = srcArray[i]
-            for j in range(startX, endX):
-                pos = srcRow.at(j).position()
+            for j in range(int(startX), int(endX)):
+                pos = QVector3D(srcRow[j].position())
                 pos.setY(pos.y() + self._height_adjustment)
-                item = QSurfaceDataItem(QVector3D(pos))
+                item = QSurfaceDataItem(pos)
                 newRow.append(item)
             dataArray.append(newRow)
         self.dataProxy().resetArray(dataArray)
index a5339672ec36bedfd4d2bab6421e366f351d5790..9fe95aff2a231fec3969dde1cade28abc6240d6f 100644 (file)
@@ -6,12 +6,8 @@ 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
+from PySide6.QtCore import QFile, QIODevice, QObject, QRangeModel
+from PySide6.QtGraphs import (QBar3DSeries, QCategory3DAxis, QValue3DAxis, QItemModelBarDataProxy)
 
 
 MONTHS = ["January", "February", "March", "April",
@@ -19,6 +15,40 @@ MONTHS = ["January", "February", "March", "April",
           "November", "December"]
 
 
+def read_data(file_path):
+    """Return a tuple of data matrix/first year."""
+    dataFile = QFile(file_path)
+    if not dataFile.open(QIODevice.OpenModeFlag.ReadOnly | QIODevice.OpenModeFlag.Text):
+        print("Unable to open data file:", dataFile.fileName(), file=sys.stderr)
+        return None, None
+
+    last_year = -1
+    first_year = -1
+    result = []
+    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.
+                year = int(tokens[0].strip())
+                month = int(tokens[1].strip())
+                value = float(tokens[2].strip())
+                if year != last_year:
+                    if first_year == -1:
+                        first_year = last_year
+                    result.append([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
+                    last_year = year
+                yearly_values = result[len(result) - 1]
+                yearly_values[month - 1] = value
+
+    return result, first_year
+
+
 class RainfallData(QObject):
 
     def __init__(self):
@@ -27,9 +57,7 @@ class RainfallData(QObject):
         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()
@@ -41,10 +69,13 @@ class RainfallData(QObject):
 
         self._columnCount = len(self._numericMonths)
 
-        self.updateYearsList(2010, 2022)
-
-        # Create proxy and series
-        self._proxy = VariantBarDataProxy()
+        file_path = Path(__file__).resolve().parent / "data" / "raindata.txt"
+        values, first_year = read_data(file_path)
+        assert (values)
+        self.updateYearsList(first_year, first_year + len(values))
+        self._model = QRangeModel(values, self)
+        self._proxy = QItemModelBarDataProxy(self._model)
+        self._proxy.setUseModelCategories(True)
         self._series = QBar3DSeries(self._proxy)
 
         self._series.setItemLabelFormat("%.1f mm")
@@ -68,8 +99,6 @@ class RainfallData(QObject):
         self._colAxis.setTitleVisible(True)
         self._valueAxis.setTitleVisible(True)
 
-        self.addDataSet()
-
     def customSeries(self):
         return self._series
 
@@ -87,40 +116,3 @@ class RainfallData(QObject):
         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.OpenModeFlag.ReadOnly | QIODevice.OpenModeFlag.Text):
-            data = dataFile.readAll().data().decode("utf8")
-            for line in data.split("\n"):
-                if line and not line.startswith("#"):  # Ignore comments
-                    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/widgetgraphgallery/variantbardatamapping.py b/examples/graphs/3d/widgetgraphgallery/variantbardatamapping.py
deleted file mode 100644 (file)
index 5b1986b..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-from PySide6.QtCore import QObject, Signal
-
-
-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/widgetgraphgallery/variantbardataproxy.py b/examples/graphs/3d/widgetgraphgallery/variantbardataproxy.py
deleted file mode 100644 (file)
index 9cd71b1..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-from PySide6.QtCore import Slot
-from PySide6.QtGraphs import QBarDataProxy, QBarDataItem
-
-
-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/widgetgraphgallery/variantdataset.py b/examples/graphs/3d/widgetgraphgallery/variantdataset.py
deleted file mode 100644 (file)
index c9b8ab1..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-from PySide6.QtCore import QObject, Signal
-
-
-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
index ebc680f66889bb9b3f195642ccf82a37a058b490..4e4a691bb79f007e2cfe8063b31bde5bc38e7756 100644 (file)
@@ -9,9 +9,6 @@
               "surfacegraph.py",
               "surfacegraphmodifier.py",
               "topographicseries.py",
-              "variantbardatamapping.py",
-              "variantbardataproxy.py",
-              "variantdataset.py",
               "data/layer_1.png",
               "data/layer_2.png",
               "data/layer_3.png",
index d4522e5126a141e4e386c813962fd1ece581d34b..e9b6c1d15f1b6a9b1a128dbd9e876ea3bf83475c 100644 (file)
@@ -176,7 +176,7 @@ class RhiWindow(QWindow):
             self.m_rhi = QRhi.create(QRhi.Implementation.D3D12, params)
         elif self.m_graphicsApi == QRhi.Implementation.Metal:
             params = QRhiMetalInitParams()
-            self.m_rhi.reset(QRhi.create(QRhi.Implementation.Metal, params))
+            self.m_rhi = QRhi.create(QRhi.Implementation.Metal, params)
 
         if not self.m_rhi:
             qFatal("Failed to create RHI backend")
index f4ae7ea04e094cd2c7ac37dd0610aebf5284d650..6fcf37dbcedc03c07d829c5b665c7a9dfe8073bf 100644 (file)
@@ -1,6 +1,7 @@
 // Copyright (C) 2023 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
+import QtCore
 import QtQuick
 import QtQuick.Controls
 import QtLocation
@@ -221,7 +222,10 @@ ApplicationWindow {
             stackView.pop(page)
             switch (state) {
             case "FollowMe":
-                mapview.followme = !mapview.followme
+                if (!mapview.followme && (permission.status !== Qt.Granted))
+                    permissionDialog.open();
+                else
+                    mapview.followme = !mapview.followme
                 break
             case "MiniMap":
                 toggleMiniMapState()
@@ -457,4 +461,42 @@ support"
             }
         }
     }
+
+    LocationPermission {
+        id: permission
+        accuracy: LocationPermission.Precise
+        availability: LocationPermission.WhenInUse
+    }
+
+    Dialog {
+        id: permissionDialog
+        anchors.centerIn: parent
+        padding: 20
+        standardButtons: (permission.status === Qt.Denied) ? Dialog.Close
+                                                           : Dialog.Close | Dialog.Ok
+        closePolicy: Dialog.NoAutoClose
+        title: qsTr("Permission")
+
+        Label {
+            id: permissionRequestText
+            text: (permission.status === Qt.Denied)
+                  ? qsTr("Grant the location permission then open the app again.")
+                  : qsTr("Location permission is needed.")
+        }
+
+        onAccepted: {
+            if (permission.status !== Qt.Denied)
+                permission.request();
+        }
+
+        onStandardButtonsChanged: {
+            if (standardButtons & Dialog.Ok)
+                standardButton(Dialog.Ok).text = qsTr("Request Permission");
+        }
+
+        Component.onCompleted: {
+            if (permission.status !== Qt.Granted)
+                open();
+        }
+    }
 }
index f2206111974cdc51d03480a24359479e9f9470b2..426c72757d474456f877fd5645543e1c09c3a1f2 100644 (file)
@@ -66,3 +66,4 @@ Item {
         }
     }
 }
+
index 1eefb497cf87fafbc97a5cd043582f2982bf1470..c7d9da4ce8c0066ffebb98ee5d7f13c323c99c60 100644 (file)
@@ -47,6 +47,7 @@ if __name__ == "__main__":
     application = QGuiApplication(sys.argv)
     name = "QtLocation Mapviewer example"
     QCoreApplication.setApplicationName(name)
+    QGuiApplication.setDesktopFileName(QCoreApplication.applicationName())
 
     args = sys.argv[1:]
     if "--help" in args:
index b0ab567d863a60bfb7641480d1811987b098eccd..cadfe2d09378b9ea35a6446d2f5b11930d0c6e86 100644 (file)
@@ -11,8 +11,7 @@ from struct import pack
 
 from PySide6.QtCore import (QByteArray, QIODevice, Qt, QSysInfo, QTimer,
                             qWarning, Slot)
-from PySide6.QtMultimedia import (QAudio, QAudioFormat,
-                                  QAudioSink, QMediaDevices)
+from PySide6.QtMultimedia import (QAudioFormat, QAudioSink, QMediaDevices, QtAudio)
 from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QLabel,
                                QMainWindow, QPushButton, QSlider,
                                QVBoxLayout, QWidget)
@@ -211,7 +210,7 @@ class AudioTest(QMainWindow):
 
     @Slot()
     def pull_timer_expired(self):
-        if self.m_audioSink is not None and self.m_audioSink.state() != QAudio.State.StoppedState:
+        if self.m_audioSink is not None and self.m_audioSink.state() != QtAudio.State.StoppedState:
             bytes_free = self.m_audioSink.bytesFree()
             data = self.m_generator.read(bytes_free)
             if data:
@@ -236,28 +235,28 @@ class AudioTest(QMainWindow):
 
     @Slot()
     def toggle_suspend_resume(self):
-        if self.m_audioSink.state() == QAudio.State.SuspendedState:
+        if self.m_audioSink.state() == QtAudio.State.SuspendedState:
             qWarning("status: Suspended, resume()")
             self.m_audioSink.resume()
             self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
-        elif self.m_audioSink.state() == QAudio.State.ActiveState:
+        elif self.m_audioSink.state() == QtAudio.State.ActiveState:
             qWarning("status: Active, suspend()")
             self.m_audioSink.suspend()
             self.m_suspendResumeButton.setText(self.RESUME_LABEL)
-        elif self.m_audioSink.state() == QAudio.State.StoppedState:
+        elif self.m_audioSink.state() == QtAudio.State.StoppedState:
             qWarning("status: Stopped, resume()")
             self.m_audioSink.resume()
             self.m_suspendResumeButton.setText(self.SUSPEND_LABEL)
-        elif self.m_audioSink.state() == QAudio.State.IdleState:
+        elif self.m_audioSink.state() == QtAudio.State.IdleState:
             qWarning("status: IdleState")
 
     state_map = {
-        QAudio.State.ActiveState: "ActiveState",
-        QAudio.State.SuspendedState: "SuspendedState",
-        QAudio.State.StoppedState: "StoppedState",
-        QAudio.State.IdleState: "IdleState"}
+        QtAudio.State.ActiveState: "ActiveState",
+        QtAudio.State.SuspendedState: "SuspendedState",
+        QtAudio.State.StoppedState: "StoppedState",
+        QtAudio.State.IdleState: "IdleState"}
 
-    @Slot("QAudio::State")
+    @Slot("QtAudio::State")
     def handle_state_changed(self, state):
         state = self.state_map.get(state, 'Unknown')
         qWarning(f"state = {state}")
index 1c0d9841255b3a813b320c7f1d953e4efb237045..807a705260e53b8a688f10c0504c0f70132e6a48 100644 (file)
@@ -19,7 +19,7 @@ import sys
 import PySide6
 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.QtMultimedia import QAudioDevice, QAudioFormat, QAudioSource, QMediaDevices, QtAudio
 from PySide6.QtWidgets import (QApplication, QComboBox, QPushButton, QSlider, QVBoxLayout,
                                QWidget, QLabel)
 
@@ -163,10 +163,10 @@ class InputTest(QWidget):
         self.m_audio_info = AudioInfo(format)
 
         self.m_audio_input = QAudioSource(device_info, format)
-        initial_volume = QAudio.convertVolume(
+        initial_volume = QtAudio.convertVolume(
             self.m_audio_input.volume(),
-            QAudio.VolumeScale.LinearVolumeScale,
-            QAudio.VolumeScale.LogarithmicVolumeScale,
+            QtAudio.VolumeScale.LinearVolumeScale,
+            QtAudio.VolumeScale.LogarithmicVolumeScale,
         )
         self.m_volume_slider.setValue(int(round(initial_volume * 100)))
         self.toggle_mode()
@@ -195,10 +195,10 @@ class InputTest(QWidget):
     def toggle_suspend(self):
         # toggle suspend/resume
         state = self.m_audio_input.state()
-        if (state == QAudio.State.SuspendedState) or (state == QAudio.State.StoppedState):
+        if (state == QtAudio.State.SuspendedState) or (state == QtAudio.State.StoppedState):
             self.m_audio_input.resume()
             self.m_suspend_resume_button.setText("Suspend recording")
-        elif state == QAudio.State.ActiveState:
+        elif state == QtAudio.State.ActiveState:
             self.m_audio_input.suspend()
             self.m_suspend_resume_button.setText("Resume recording")
         # else no-op
@@ -211,9 +211,9 @@ class InputTest(QWidget):
 
     @Slot(int)
     def slider_changed(self, value):
-        linearVolume = QAudio.convertVolume(value / float(100),
-                                            QAudio.VolumeScale.LogarithmicVolumeScale,
-                                            QAudio.VolumeScale.LinearVolumeScale)
+        linearVolume = QtAudio.convertVolume(value / float(100),
+                                             QtAudio.VolumeScale.LogarithmicVolumeScale,
+                                             QtAudio.VolumeScale.LinearVolumeScale)
         self.m_audio_input.setVolume(linearVolume)
 
 
index 90a8fed46ca3ea846b884af78f87e04f280187f9..12971b4d9178abb34c11312b966d4761a15b6c48 100644 (file)
@@ -111,7 +111,6 @@ class Camera(QMainWindow):
         self._ui.captureWidget.currentChanged.connect(self.updateCaptureMode)
 
         self._ui.metaDataButton.clicked.connect(self.showMetaDataDialog)
-        self._ui.exposureCompensation.valueChanged.connect(self.setExposureCompensation)
 
         self.setCamera(QMediaDevices.defaultVideoInput())
 
@@ -278,10 +277,6 @@ class Camera(QMainWindow):
             self._ui.stopButton.setEnabled(True)
             self._ui.metaDataButton.setEnabled(False)
 
-    @Slot(int)
-    def setExposureCompensation(self, index):
-        self.m_camera.setExposureCompensation(index * 0.5)
-
     @Slot()
     def displayRecorderError(self):
         if self.m_mediaRecorder.error() != QMediaRecorder.NoError:
index a338fb513360093dd49477e0ba1724326d075fb5..1852c3e9fe228fdbe0a893321187f5ddeebbcbe8 100644 (file)
           </property>
          </widget>
         </item>
-        <item row="5" 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::Orientation::Horizontal</enum>
-          </property>
-          <property name="tickPosition">
-           <enum>QSlider::TickPosition::TicksAbove</enum>
-          </property>
-         </widget>
-        </item>
-        <item row="4" column="0">
-         <widget class="QLabel" name="label">
-          <property name="text">
-           <string>Exposure Compensation:</string>
-          </property>
-         </widget>
-        </item>
        </layout>
       </widget>
       <widget class="QWidget" name="tab">
   <widget class="QStatusBar" name="statusbar"/>
   <action name="actionExit">
    <property name="text">
-    <string>Quit</string>
+    <string>Close</string>
    </property>
    <property name="shortcut">
     <string>Ctrl+Q</string>
     </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>
index db78ecf5a9dc193b23f1a60637cb1a7d988422c9..ec7dfcec5b42d0471771032eaf3e58458e86713c 100644 (file)
@@ -19,8 +19,8 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
 from PySide6.QtMultimediaWidgets import QVideoWidget
 from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel,
     QMainWindow, QMenu, QMenuBar, QPushButton,
-    QSizePolicy, QSlider, QSpacerItem, QStackedWidget,
-    QStatusBar, QTabWidget, QWidget)
+    QSizePolicy, QSpacerItem, QStackedWidget, QStatusBar,
+    QTabWidget, QWidget)
 
 class Ui_Camera(object):
     def setupUi(self, Camera):
@@ -57,21 +57,6 @@ class Ui_Camera(object):
 
         self.gridLayout.addWidget(self.takeImageButton, 0, 0, 1, 1)
 
-        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.Orientation.Horizontal)
-        self.exposureCompensation.setTickPosition(QSlider.TickPosition.TicksAbove)
-
-        self.gridLayout.addWidget(self.exposureCompensation, 5, 0, 1, 1)
-
-        self.label = QLabel(self.tab_2)
-        self.label.setObjectName(u"label")
-
-        self.gridLayout.addWidget(self.label, 4, 0, 1, 1)
-
         self.captureWidget.addTab(self.tab_2, "")
         self.tab = QWidget()
         self.tab.setObjectName(u"tab")
@@ -121,15 +106,15 @@ class Ui_Camera(object):
         self.stackedWidget.setSizePolicy(sizePolicy)
         palette = QPalette()
         brush = QBrush(QColor(255, 255, 255, 255))
-        brush.setStyle(Qt.SolidPattern)
-        palette.setBrush(QPalette.Active, QPalette.Base, brush)
+        brush.setStyle(Qt.BrushStyle.SolidPattern)
+        palette.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.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)
+        brush1.setStyle(Qt.BrushStyle.SolidPattern)
+        palette.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.Window, brush1)
+        palette.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Base, brush)
+        palette.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Window, brush1)
+        palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, brush1)
+        palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Window, brush1)
         self.stackedWidget.setPalette(palette)
         self.viewfinderPage = QWidget()
         self.viewfinderPage.setObjectName(u"viewfinderPage")
@@ -193,7 +178,6 @@ class Ui_Camera(object):
         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)
@@ -207,7 +191,7 @@ class Ui_Camera(object):
 
     def retranslateUi(self, Camera):
         Camera.setWindowTitle(QCoreApplication.translate("Camera", u"Camera", None))
-        self.actionExit.setText(QCoreApplication.translate("Camera", u"Quit", None))
+        self.actionExit.setText(QCoreApplication.translate("Camera", u"Close", None))
 #if QT_CONFIG(shortcut)
         self.actionExit.setShortcut(QCoreApplication.translate("Camera", u"Ctrl+Q", None))
 #endif // QT_CONFIG(shortcut)
@@ -216,7 +200,6 @@ class Ui_Camera(object):
         self.actionSettings.setText(QCoreApplication.translate("Camera", u"Change Settings", None))
         self.actionAbout_Qt.setText(QCoreApplication.translate("Camera", u"About Qt", None))
         self.takeImageButton.setText(QCoreApplication.translate("Camera", u"Capture Photo", None))
-        self.label.setText(QCoreApplication.translate("Camera", u"Exposure Compensation:", 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))
index a9bd4d0978b6bafef3af6b8b9f24abf68684e672..eff3310afa16c1b62227f6bd65e486798e0389c0 100644 (file)
@@ -144,15 +144,15 @@ class Ui_Camera(object):
         self.stackedWidget.setSizePolicy(sizePolicy2)
         palette = QPalette()
         brush = QBrush(QColor(255, 255, 255, 255))
-        brush.setStyle(Qt.SolidPattern)
-        palette.setBrush(QPalette.Active, QPalette.Base, brush)
+        brush.setStyle(Qt.BrushStyle.SolidPattern)
+        palette.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.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)
+        brush1.setStyle(Qt.BrushStyle.SolidPattern)
+        palette.setBrush(QPalette.ColorGroup.Active, QPalette.ColorRole.Window, brush1)
+        palette.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Base, brush)
+        palette.setBrush(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Window, brush1)
+        palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, brush1)
+        palette.setBrush(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Window, brush1)
         self.stackedWidget.setPalette(palette)
         self.viewfinderPage = QWidget()
         self.viewfinderPage.setObjectName(u"viewfinderPage")
diff --git a/examples/multimedia/player/audiolevelmeter.py b/examples/multimedia/player/audiolevelmeter.py
new file mode 100644 (file)
index 0000000..d961a39
--- /dev/null
@@ -0,0 +1,387 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from math import log10, sqrt
+from PySide6.QtMultimedia import QAudioBuffer
+from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QSizePolicy, QToolButton,
+                               QVBoxLayout, QWidget)
+from PySide6.QtGui import QBrush, QPainter, QPalette
+from PySide6.QtCore import QObject, QRectF, QThread, QTimer, qFuzzyCompare, Qt, Signal, Slot
+
+
+# Constants used by AudioLevelMeter and MeterChannel
+WIDGET_WIDTH = 34
+MAX_CHANNELS = 8
+PEAK_COLOR = "#1F9B5D"
+RMS_COLOR = "#28C878"
+RMS_WINDOW = 400  # ms
+PEAK_LABEL_HOLD_TIME = 2000  # ms
+DECAY_EASE_IN_TIME = 160  # ms
+UPDATE_INTERVAL = 16  # ms, Assuming 60 Hz refresh rate.
+DB_DECAY_PER_SECOND = 20.0
+DB_DECAY_PER_UPDATE = DB_DECAY_PER_SECOND / (1000 / UPDATE_INTERVAL)
+DB_MAX = 0.0
+DB_MIN = -60.0
+
+
+def amplitudeToDb(f):
+    """Converts a float sample value to dB and clamps it between DB_MIN and DB_MAX."""
+    if f <= 0:
+        return DB_MIN
+    v = 20.0 * log10(f)
+    if v < DB_MIN:
+        return DB_MIN
+    if v > DB_MAX:
+        return DB_MAX
+    return v
+
+
+# A struct used by BufferAnalyzer to emit its results back to AudioLevelMeter
+class BufferValues:
+    """A struct used by BufferAnalyzer to emit its results back to AudioLevelMeter."""
+    def __init__(self, nChannels):
+        self.peaks = [0.0] * nChannels
+        self.squares = [0.0] * nChannels
+
+
+class BufferAnalyzer(QObject):
+    """A worker class analyzing incoming buffers on a separate worker thread."""
+    valuesReady = Signal(BufferValues)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.m_stopRequested = False
+
+    def requestStop(self):
+        self.m_stopRequested = True
+
+    @Slot(QAudioBuffer, int)
+    def analyzeBuffer(self, buffer, maxChannelsToAnalyze):
+        """Analyzes an audio buffer and emits its peak and sumOfSquares values.
+           Skips remaining frames if self.m_stopRequested is set to true."""
+
+        if QThread.currentThread().isInterruptionRequested():
+            return  # Interrupted by ~AudioLevelMeter, skipping remaining buffers in signal queue
+
+        self.m_stopRequested = False
+
+        channelCount = buffer.format().channelCount()
+        channelsToAnalyze = min(channelCount, maxChannelsToAnalyze)
+
+        values = BufferValues(channelsToAnalyze)
+
+        bufferData = buffer.constData()
+        bufferSize = len(bufferData)
+        bytesPerSample = buffer.format().bytesPerSample()
+
+        for i in range(0, bufferSize, bytesPerSample * channelCount):
+            if self.m_stopRequested:
+                framesSkipped = (bufferSize - i) / channelCount
+                print("BufferAnalyzer::analyzeBuffer skipped", framesSkipped, "out of",
+                      buffer.frameCount(), "frames")
+                # Emit incomplete values also when stop is requested to get some audio level readout
+                # even if frames are being skipped for every buffer. Displayed levels will be
+                # inaccurate.
+                break
+
+            for channelIndex in range(0, channelsToAnalyze):
+                offset = i + bytesPerSample * channelIndex
+                sample = buffer.format().normalizedSampleValue(bufferData[offset:])
+                values.peaks[channelIndex] = max(values.peaks[channelIndex], abs(sample))
+                values.squares[channelIndex] += sample * sample
+
+        self.valuesReady.emit(values)
+
+
+class MeterChannel(QWidget):
+    """A custom QWidget representing an audio channel in the audio level meter. It serves
+       both as a model for the channels's peak and RMS values and as a view using the overridden
+       paintEvent()."""
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.m_peakDecayRate = 0.0
+        self.m_rmsDecayRate = 0.0
+        self.m_peak = DB_MIN
+        self.m_rms = DB_MIN
+        self.m_sumOfSquares = 0.0
+        self.m_sumOfSquaresQueue = []
+        self.m_peakBrush = QBrush(PEAK_COLOR)
+        self.m_rmsBrush = QBrush(RMS_COLOR)
+
+    def normalize(self, dB):
+        """# Normalizes a dB value for visualization."""
+        return (dB - DB_MIN) / (DB_MAX - DB_MIN)
+
+    def clearRmsData(self):
+        """Clears the data used to calculate RMS values."""
+        self.m_sumOfSquares = 0.0
+        self.m_sumOfSquaresQueue = []
+
+    def decayPeak(self):
+        """Decays self.m_peak value by DB_DECAY_PER_UPDATE with ease-in animation based
+           on DECAY_EASE_IN_TIME."""
+        peak = self.m_peak
+        if qFuzzyCompare(peak, DB_MIN):
+            return
+
+        cubicEaseInFactor = self.m_peakDecayRate * self.m_peakDecayRate * self.m_peakDecayRate
+        self.m_peak = max(DB_MIN, peak - DB_DECAY_PER_UPDATE * cubicEaseInFactor)
+
+        if self.m_peakDecayRate < 1:
+            self.m_peakDecayRate += float(UPDATE_INTERVAL) / float(DECAY_EASE_IN_TIME)
+            if self.m_peakDecayRate > 1.0:
+                self.m_peakDecayRate = 1.0
+
+    def decayRms(self):
+        """Decays self.m_rms value by DB_DECAY_PER_UPDATE with ease-in animation based on
+           DECAY_EASE_IN_TIME."""
+        rms = self.m_rms
+        if qFuzzyCompare(rms, DB_MIN):
+            return
+
+        cubicEaseInFactor = self.m_rmsDecayRate * self.m_rmsDecayRate * self.m_rmsDecayRate
+        self.m_rms = max(DB_MIN, rms - DB_DECAY_PER_UPDATE * cubicEaseInFactor)
+
+        if self.m_rmsDecayRate < 1:
+            self.m_rmsDecayRate += float(UPDATE_INTERVAL) / float(DECAY_EASE_IN_TIME)
+            if self.m_rmsDecayRate > 1.0:
+                self.m_rmsDecayRate = 1.0
+
+    def updatePeak(self, sampleValue):
+        """Updates self.m_peak and resets self.m_peakDecayRate if sampleValue > self.m_peak."""
+        dB = amplitudeToDb(sampleValue)
+        if dB > self.m_peak:
+            self.m_peakDecayRate = 0
+            self.m_peak = dB
+
+    def updateRms(self, sumOfSquaresForOneBuffer, duration, frameCount):
+        """Calculates current RMS. Resets self.m_rmsDecayRate and updates self.m_rms
+           if current RMS > self.m_rms."""
+
+        # Add the new sumOfSquares to the queue and update the total
+        self.m_sumOfSquaresQueue.append(sumOfSquaresForOneBuffer)
+        self.m_sumOfSquares += sumOfSquaresForOneBuffer
+
+        # Remove the oldest sumOfSquares to stay within the RMS window
+        if len(self.m_sumOfSquaresQueue) * duration > RMS_WINDOW:
+            self.m_sumOfSquares -= self.m_sumOfSquaresQueue[0]
+            del self.m_sumOfSquaresQueue[0]
+
+        # Fix negative values caused by floating point precision errors
+        if self.m_sumOfSquares < 0:
+            self.m_sumOfSquares = 0
+
+        # Calculate the new RMS value
+        if self.m_sumOfSquares > 0 and self.m_sumOfSquaresQueue:
+            newRms = sqrt(self.m_sumOfSquares / (frameCount * len(self.m_sumOfSquaresQueue)))
+            dB = amplitudeToDb(newRms)
+            if dB > self.m_rms:
+                self.m_rmsDecayRate = 0
+                self.m_rms = dB
+
+    def paintEvent(self, event):
+        """Paints the level bar of the meter channel based on the decayed peak and rms values."""
+        if qFuzzyCompare(self.m_peak, DB_MIN) and qFuzzyCompare(self.m_rms, DB_MIN):
+            return  # Nothing to paint
+
+        peakLevel = self.normalize(self.m_peak)
+        rmsLevel = self.normalize(self.m_rms)
+
+        with QPainter(self) as painter:
+            rect = QRectF(0, self.height(), self.width(), -peakLevel * self.height())
+            painter.fillRect(rect, self.m_peakBrush)  # Paint the peak level
+            rect.setHeight(-rmsLevel * self.height())
+            painter.fillRect(rect, self.m_rmsBrush)  # Paint the RMS level
+
+
+class AudioLevelMeter(QWidget):
+    """The audio level meter´s parent widget class. It acts as a controller
+       for the MeterChannel widgets and the BufferAnalyzer worker."""
+
+    newBuffer = Signal(QAudioBuffer, int)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.m_isOn = True
+        self.m_isActive = False
+        self.m_channels = []
+        self.m_channelCount = 0
+        self.m_bufferDurationMs = 0
+        self.m_frameCount = 0
+        self.m_highestPeak = 0.0
+        self.m_updateTimer = QTimer()
+        self.m_deactivationTimer = QTimer()
+        self.m_peakLabelHoldTimer = QTimer()
+        self.m_peakLabel = None
+        self.m_onOffButton = None
+        self.m_bufferAnalyzer = None
+        self.m_analyzerThread = QThread()
+
+        # Layout and background color
+        self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
+        self.setMinimumWidth(WIDGET_WIDTH)
+        currentPalette = self.palette()
+        currentPalette.setColor(QPalette.ColorRole.Window,
+                                currentPalette.color(QPalette.ColorRole.Base))
+        self.setPalette(currentPalette)
+        self.setAutoFillBackground(True)
+        mainLayout = QVBoxLayout(self)
+        mainLayout.setSpacing(2)
+        mainLayout.setContentsMargins(0, 0, 0, 0)
+
+        # Meter channels
+        meterChannelLayout = QHBoxLayout()
+        meterChannelLayout.setContentsMargins(2, 2, 2, 2)
+        meterChannelLayout.setSpacing(2)
+        for i in range(0, MAX_CHANNELS):
+            channel = MeterChannel(self)
+            meterChannelLayout.addWidget(channel)
+            self.m_channels.append(channel)
+        mainLayout.addLayout(meterChannelLayout)
+
+        # Peak label
+        self.m_peakLabel = QLabel("-", self)
+        self.m_peakLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+        font = QApplication.font()
+        font.setPointSize(10)
+        self.m_peakLabel.setFont(font)
+        mainLayout.addWidget(self.m_peakLabel)
+        mainLayout.setStretch(0, 1)
+
+        # On/off button
+        self.m_onOffButton = QToolButton(self)
+        mainLayout.addWidget(self.m_onOffButton)
+        self.m_onOffButton.setMaximumWidth(WIDGET_WIDTH)
+        self.m_onOffButton.setText("On")
+        self.m_onOffButton.setCheckable(True)
+        self.m_onOffButton.setChecked(True)
+        self.m_onOffButton.clicked.connect(self.toggleOnOff)
+
+        # Timer triggering update of the audio level bars
+        self.m_updateTimer.timeout.connect(self.updateBars)
+
+        # Timer postponing deactivation of update timer to allow meters to fade to 0
+        self.m_deactivationTimer.timeout.connect(self.m_updateTimer.stop)
+        self.m_deactivationTimer.setSingleShot(True)
+
+        # Timer resetting the peak label
+        self.m_peakLabelHoldTimer.timeout.connect(self.resetPeakLabel)
+        self.m_peakLabelHoldTimer.setSingleShot(True)
+
+        # Buffer analyzer and worker thread that analyzes incoming buffers
+        self.m_bufferAnalyzer = BufferAnalyzer()
+        self.m_bufferAnalyzer.moveToThread(self.m_analyzerThread)
+        self.m_analyzerThread.finished.connect(self.m_bufferAnalyzer.deleteLater)
+        self.newBuffer.connect(self.m_bufferAnalyzer.analyzeBuffer)
+        self.m_bufferAnalyzer.valuesReady.connect(self.updateValues)
+        self.m_analyzerThread.start()
+
+    def closeRequest(self):
+        self.m_analyzerThread.requestInterruption()
+        self.m_bufferAnalyzer.requestStop()
+        self.m_analyzerThread.quit()
+        self.m_analyzerThread.wait()
+
+    @Slot(QAudioBuffer)
+    def onAudioBufferReceived(self, buffer):
+        """Receives a buffer from QAudioBufferOutput and triggers BufferAnalyzer to analyze it."""
+        if not self.m_isOn or not buffer.isValid() or not buffer.format().isValid():
+            return
+
+        if not self.m_isActive:
+            self.activate()
+
+        # Update internal values to match the current audio stream
+        self.updateChannelCount(buffer.format().channelCount())
+        self.m_frameCount = buffer.frameCount()
+        self.m_bufferDurationMs = buffer.duration() / 1000
+
+        # Stop any ongoing analysis, skipping remaining frames
+        self.m_bufferAnalyzer.requestStop()
+
+        self.newBuffer.emit(buffer, self.m_channelCount)
+
+    @Slot(BufferValues)
+    def updateValues(self, values):
+        """Updates peak/RMS values and peak label."""
+        if not self.m_isActive:
+            return  # Discard incoming values from BufferAnalyzer
+
+        bufferPeak = 0.0
+        for i in range(0, len(values.peaks)):
+            bufferPeak = max(bufferPeak, values.peaks[i])
+            self.m_channels[i].updatePeak(values.peaks[i])
+            self.m_channels[i].updateRms(values.squares[i], self.m_bufferDurationMs,
+                                         self.m_frameCount)
+        self.updatePeakLabel(bufferPeak)
+
+    def updatePeakLabel(self, peak):
+        """Updates peak label and restarts self.m_peakLabelHoldTimer
+           if peak >= self.m_highestPeak."""
+        if peak < self.m_highestPeak:
+            return
+
+        self.m_peakLabelHoldTimer.start(PEAK_LABEL_HOLD_TIME)
+
+        if qFuzzyCompare(peak, self.m_highestPeak):
+            return
+
+        self.m_highestPeak = peak
+        dB = amplitudeToDb(self.m_highestPeak)
+        self.m_peakLabel.setText(f"{int(dB)}")
+
+    @Slot()
+    def resetPeakLabel(self):
+        """Resets peak label. Called when self.m_labelHoldTimer timeouts."""
+        self.m_highestPeak = 0.0
+        self.m_peakLabel.setText(f"{DB_MIN}" if self.m_isOn else "")
+
+    def clearAllRmsData(self):
+        """Clears internal data used to calculate RMS values."""
+        for channel in self.m_channels.copy():
+            channel.clearRmsData()
+
+    @Slot()
+    def activate(self):
+        """Starts the update timer that updates the meter bar."""
+        self.m_isActive = True
+        self.m_deactivationTimer.stop()
+        self.m_updateTimer.start(UPDATE_INTERVAL)
+
+    @Slot()
+    def deactivate(self):
+        """Start the deactiviation timer that eventually stops the update timer."""
+        self.m_isActive = False
+        self.clearAllRmsData()
+        # Calculate the time it takes to decay fram max to min dB
+        interval = (DB_MAX - DB_MIN) / (DB_DECAY_PER_SECOND / 1000) + DECAY_EASE_IN_TIME
+        self.m_deactivationTimer.start(interval)
+
+    @Slot()
+    def updateBars(self):
+        """Decays internal peak and RMS values and triggers repainting of meter bars."""
+        for i in range(0, self.m_channelCount):
+            channel = self.m_channels[i]
+            channel.decayPeak()
+            channel.decayRms()
+            channel.update()  # Trigger paint event
+
+    @Slot()
+    def toggleOnOff(self):
+        """Toggles between on (activated) and off (deactivated) state."""
+        self.m_isOn = not self.m_isOn
+        if not self.m_isOn:
+            self.deactivate()
+        else:
+            self.activate()
+        self.m_onOffButton.setText("On" if self.m_isOn else "Off")
+
+    def updateChannelCount(self, channelCount):
+        """Updates the number of visible MeterChannel widgets."""
+        if (channelCount == self.m_channelCount
+                or (channelCount > MAX_CHANNELS and MAX_CHANNELS == self.m_channelCount)):
+            return
+
+        self.m_channelCount = min(channelCount, MAX_CHANNELS)
+        for i in range(0, MAX_CHANNELS):
+            self.m_channels[i].setVisible(i < self.m_channelCount)
diff --git a/examples/multimedia/player/doc/player.png b/examples/multimedia/player/doc/player.png
deleted file mode 100644 (file)
index f751d4a..0000000
Binary files a/examples/multimedia/player/doc/player.png and /dev/null differ
index fdf5fa920eef1ea785777151f4e72c677eca4c72..77d051227544811ab1bae62241be20ded4f79f79 100644 (file)
@@ -4,6 +4,6 @@ Player Example
 Media Player demonstrates a simple multimedia player that can play audio and or
 video files using various codecs.
 
-.. image:: player.png
-   :width: 400
+.. image:: player.webp
+   :width: 800
    :alt: Player Screenshot
diff --git a/examples/multimedia/player/doc/player.webp b/examples/multimedia/player/doc/player.webp
new file mode 100644 (file)
index 0000000..5a52c66
Binary files /dev/null and b/examples/multimedia/player/doc/player.webp differ
diff --git a/examples/multimedia/player/main.py b/examples/multimedia/player/main.py
new file mode 100644 (file)
index 0000000..2afe762
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""PySide6 Multimedia player example"""
+
+import sys
+from argparse import ArgumentParser, RawTextHelpFormatter
+
+from PySide6.QtWidgets import QApplication
+from PySide6.QtCore import qVersion, QCoreApplication, QDir, QUrl
+
+from player import Player
+
+
+if __name__ == "__main__":
+    app = QApplication(sys.argv)
+
+    QCoreApplication.setApplicationName("Player Example")
+    QCoreApplication.setOrganizationName("QtProject")
+    QCoreApplication.setApplicationVersion(qVersion())
+    argument_parser = ArgumentParser(description=QCoreApplication.applicationName(),
+                                     formatter_class=RawTextHelpFormatter)
+    argument_parser.add_argument("file", help="File", nargs='?', type=str)
+    options = argument_parser.parse_args()
+
+    player = Player()
+    if options.file:
+        player.openUrl(QUrl.fromUserInput(options.file, QDir.currentPath(),
+                       QUrl.UserInputResolutionOption.AssumeLocalFile))
+    player.show()
+    sys.exit(QCoreApplication.exec())
index 23fdbb4cc5518da205e41281eb4f2f69b80f48d1..57e0112c7b44281f6b7f36e45daa1e2a8bae96e0 100644 (file)
-# Copyright (C) 2022 The Qt Company Ltd.
+# Copyright (C) 2025 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
 
-"""PySide6 Multimedia player example"""
+from functools import cache
 
-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)
-from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat,
-                                  QMediaPlayer, QAudio)
-from PySide6.QtMultimediaWidgets import QVideoWidget
+from PySide6.QtMultimedia import (QAudioBufferOutput, QAudioDevice, QAudioOutput, QMediaDevices,
+                                  QMediaFormat, QMediaMetaData, QMediaPlayer)
+from PySide6.QtWidgets import (QApplication, QComboBox, QDialog, QFileDialog, QGridLayout,
+                               QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton,
+                               QSizePolicy, QSlider, QVBoxLayout, QWidget)
+from PySide6.QtGui import QCursor, QPixmap
+from PySide6.QtCore import QDir, QLocale, QStandardPaths, QTime, Qt, Signal, Slot
 
-
-AVI = "video/x-msvideo"  # AVI
+from audiolevelmeter import AudioLevelMeter
+from playercontrols import PlayerControls
+from videowidget import VideoWidget
 
 
 MP4 = 'video/mp4'
 
 
-def get_supported_mime_types():
+@cache
+def getSupportedMimeTypes():
     result = []
-    for f in QMediaFormat().supportedFileFormats(QMediaFormat.Decode):
+    for f in QMediaFormat().supportedFileFormats(QMediaFormat.ConversionMode.Decode):
         mime_type = QMediaFormat(f).mimeType()
         result.append(mime_type.name())
+    if MP4 not in result:
+        result.append(MP4)  # Should always be there when using FFMPEG
     return result
 
 
-class MainWindow(QMainWindow):
-
-    def __init__(self):
-        super().__init__()
-
-        self._playlist = []  # FIXME 6.3: Replace by QMediaPlaylist?
-        self._playlist_index = -1
-        self._audio_output = QAudioOutput()
-        self._player = QMediaPlayer()
-        self._player.setAudioOutput(self._audio_output)
-
-        self._player.errorOccurred.connect(self._player_error)
-
-        tool_bar = QToolBar()
-        self.addToolBar(tool_bar)
-
-        file_menu = self.menuBar().addMenu("&File")
-        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(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(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(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(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(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(QIcon.ThemeIcon.MediaPlaybackStop,
-                               style.standardIcon(QStyle.SP_MediaStop))
-        self._stop_action = tool_bar.addAction(icon, "Stop")
-        self._stop_action.triggered.connect(self._ensure_stopped)
-        play_menu.addAction(self._stop_action)
-
-        self._volume_slider = QSlider()
-        self._volume_slider.setOrientation(Qt.Orientation.Horizontal)
-        self._volume_slider.setMinimum(0)
-        self._volume_slider.setMaximum(100)
-        available_width = self.screen().availableGeometry().width()
-        self._volume_slider.setFixedWidth(available_width / 10)
-        self._volume_slider.setValue(self._audio_output.volume() * 100)
-        self._volume_slider.setTickInterval(10)
-        self._volume_slider.setTickPosition(QSlider.TicksBelow)
-        self._volume_slider.setToolTip("Volume")
-        self._volume_slider.valueChanged.connect(self.setVolume)
-        tool_bar.addWidget(self._volume_slider)
-
-        icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)
-        about_menu = self.menuBar().addMenu("&About")
-        about_qt_act = QAction(icon, "About &Qt", self, triggered=qApp.aboutQt)  # noqa: F821
-        about_menu.addAction(about_qt_act)
-
-        self._video_widget = QVideoWidget()
-        self.setCentralWidget(self._video_widget)
-        self._player.playbackStateChanged.connect(self.update_buttons)
-        self._player.setVideoOutput(self._video_widget)
-
-        self.update_buttons(self._player.playbackState())
-        self._mime_types = []
+class Player(QWidget):
+
+    fullScreenChanged = Signal(bool)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.m_statusInfo = ""
+        self.m_mediaDevices = QMediaDevices()
+        self.m_player = QMediaPlayer(self)
+        self.m_audioOutput = QAudioOutput(self)
+        self.m_player.setAudioOutput(self.m_audioOutput)
+        self.m_player.durationChanged.connect(self.durationChanged)
+        self.m_player.positionChanged.connect(self.positionChanged)
+        self.m_player.metaDataChanged.connect(self.metaDataChanged)
+        self.m_player.mediaStatusChanged.connect(self.statusChanged)
+        self.m_player.bufferProgressChanged.connect(self.bufferingProgress)
+        self.m_player.hasVideoChanged.connect(self.videoAvailableChanged)
+        self.m_player.errorChanged.connect(self.displayErrorMessage)
+        self.m_player.tracksChanged.connect(self.tracksChanged)
+
+        self.m_videoWidget = VideoWidget(self)
+        available_geometry = self.screen().availableGeometry()
+        self.m_videoWidget.setMinimumSize(available_geometry.width() / 2,
+                                          available_geometry.height() / 3)
+        self.m_player.setVideoOutput(self.m_videoWidget)
+
+        # audio level meter
+        self.m_audioBufferOutput = QAudioBufferOutput(self)
+        self.m_player.setAudioBufferOutput(self.m_audioBufferOutput)
+        self.m_audioLevelMeter = AudioLevelMeter(self)
+        self.m_audioBufferOutput.audioBufferReceived.connect(self.m_audioLevelMeter.onAudioBufferReceived)  # noqa: E501
+        self.m_player.playingChanged.connect(self.m_audioLevelMeter.deactivate)
+
+        # player layout
+        layout = QVBoxLayout(self)
+
+        # display
+        displayLayout = QHBoxLayout()
+        displayLayout.addWidget(self.m_videoWidget, 2)
+        displayLayout.addWidget(self.m_audioLevelMeter, 3)
+        layout.addLayout(displayLayout)
+
+        # duration slider and label
+        hLayout = QHBoxLayout()
+
+        self.m_slider = QSlider(Qt.Orientation.Horizontal, self)
+        self.m_slider.setRange(0, self.m_player.duration())
+        self.m_slider.sliderMoved.connect(self.seek)
+        hLayout.addWidget(self.m_slider)
+
+        self.m_labelDuration = QLabel()
+        self.m_labelDuration.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
+        hLayout.addWidget(self.m_labelDuration)
+        layout.addLayout(hLayout)
+
+        # controls
+        controlLayout = QHBoxLayout()
+        controlLayout.setContentsMargins(0, 0, 0, 0)
+
+        openButton = QPushButton("Open", self)
+        openButton.clicked.connect(self.open)
+        controlLayout.addWidget(openButton)
+        controlLayout.addStretch(1)
+
+        controls = PlayerControls()
+        controls.setState(self.m_player.playbackState())
+        controls.setVolume(self.m_audioOutput.volume())
+        controls.setMuted(controls.isMuted())
+
+        controls.play.connect(self.m_player.play)
+        controls.pause.connect(self.m_player.pause)
+        controls.stop.connect(self.m_player.stop)
+        controls.previous.connect(self.previousClicked)
+        controls.changeVolume.connect(self.m_audioOutput.setVolume)
+        controls.changeMuting.connect(self.m_audioOutput.setMuted)
+        controls.changeRate.connect(self.m_player.setPlaybackRate)
+        controls.stop.connect(self.m_videoWidget.update)
+
+        self.m_player.playbackStateChanged.connect(controls.setState)
+        self.m_audioOutput.volumeChanged.connect(controls.setVolume)
+        self.m_audioOutput.mutedChanged.connect(controls.setMuted)
+
+        controlLayout.addWidget(controls)
+        controlLayout.addStretch(1)
+
+        self.m_fullScreenButton = QPushButton("FullScreen", self)
+        self.m_fullScreenButton.setCheckable(True)
+        controlLayout.addWidget(self.m_fullScreenButton)
+
+        self.m_pitchCompensationButton = QPushButton("Pitch compensation", self)
+        self.m_pitchCompensationButton.setCheckable(True)
+        av = self.m_player.pitchCompensationAvailability()
+        toolTip = ""
+        if av == QMediaPlayer.PitchCompensationAvailability.AlwaysOn:
+            self.m_pitchCompensationButton.setEnabled(False)
+            self.m_pitchCompensationButton.setChecked(True)
+            toolTip = "Pitch compensation always enabled on self backend"
+        elif av == QMediaPlayer.PitchCompensationAvailability.Unavailable:
+            self.m_pitchCompensationButton.setEnabled(False)
+            self.m_pitchCompensationButton.setChecked(False)
+            toolTip = "Pitch compensation unavailable on self backend"
+        elif av == QMediaPlayer.PitchCompensationAvailability.Available:
+            self.m_pitchCompensationButton.setEnabled(True)
+            self.m_pitchCompensationButton.setChecked(self.m_player.pitchCompensation())
+        self.m_pitchCompensationButton.setToolTip(toolTip)
+
+        controlLayout.addWidget(self.m_pitchCompensationButton)
+        self.m_player.pitchCompensationChanged.connect(self._updatePitchCompensation)
+        self.m_pitchCompensationButton.setChecked(self.m_player.pitchCompensation())
+        self.m_pitchCompensationButton.toggled.connect(self.m_player.setPitchCompensation)
+
+        self.m_audioOutputCombo = QComboBox(self)
+        controlLayout.addWidget(self.m_audioOutputCombo)
+
+        self.updateAudioDevices()
+
+        self.m_audioOutputCombo.activated.connect(self.audioOutputChanged)
+
+        self.m_mediaDevices.audioOutputsChanged.connect(self.updateAudioDevices)
+
+        layout.addLayout(controlLayout)
+
+        # tracks
+        tracksLayout = QGridLayout()
+
+        self.m_audioTracks = QComboBox(self)
+        self.m_audioTracks.activated.connect(self.selectAudioStream)
+        tracksLayout.addWidget(QLabel("Audio Tracks:"), 0, 0)
+        tracksLayout.addWidget(self.m_audioTracks, 0, 1)
+
+        self.m_videoTracks = QComboBox(self)
+        self.m_videoTracks.activated.connect(self.selectVideoStream)
+        tracksLayout.addWidget(QLabel("Video Tracks:"), 1, 0)
+        tracksLayout.addWidget(self.m_videoTracks, 1, 1)
+
+        self.m_subtitleTracks = QComboBox(self)
+        self.m_subtitleTracks.activated.connect(self.selectSubtitleStream)
+        tracksLayout.addWidget(QLabel("Subtitle Tracks:"), 2, 0)
+        tracksLayout.addWidget(self.m_subtitleTracks, 2, 1)
+
+        layout.addLayout(tracksLayout)
+
+        # metadata
+        metaDataLabel = QLabel("Metadata for file:")
+        layout.addWidget(metaDataLabel)
+
+        metaDataLayout = QGridLayout()
+        metaDataCount = QMediaMetaData.NumMetaData
+        self.m_metaDataLabels = [None] * metaDataCount
+        self.m_metaDataFields = [None] * metaDataCount
+        key = QMediaMetaData.Key.Title.value
+        for i in range(0, round((metaDataCount + 2) / 3)):
+            for j in range(0, 6, 2):
+                labelText = QMediaMetaData.metaDataKeyToString(QMediaMetaData.Key(key))
+                self.m_metaDataLabels[key] = QLabel(labelText)
+                if (key == QMediaMetaData.Key.ThumbnailImage
+                        or key == QMediaMetaData.Key.CoverArtImage):
+                    self.m_metaDataFields[key] = QLabel()
+                else:
+                    lineEdit = QLineEdit()
+                    lineEdit.setReadOnly(True)
+                    self.m_metaDataFields[key] = lineEdit
+
+                self.m_metaDataLabels[key].setDisabled(True)
+                self.m_metaDataFields[key].setDisabled(True)
+                metaDataLayout.addWidget(self.m_metaDataLabels[key], i, j)
+                metaDataLayout.addWidget(self.m_metaDataFields[key], i, j + 1)
+                key += 1
+                if key == QMediaMetaData.NumMetaData:
+                    break
+
+        layout.addLayout(metaDataLayout)
+
+        if not self.isPlayerAvailable():
+            QMessageBox.warning(self, "Service not available",
+                                "The QMediaPlayer object does not have a valid service.\n"
+                                "Please check the media service plugins are installed.")
+
+            controls.setEnabled(False)
+            openButton.setEnabled(False)
+            self.m_fullScreenButton.setEnabled(False)
+        self.metaDataChanged()
 
     def closeEvent(self, event):
-        self._ensure_stopped()
+        self.m_audioLevelMeter.closeRequest()
         event.accept()
 
+    @Slot()
+    def _updatePitchCompensation(self):
+        self.m_pitchCompensationButton.setChecked(self.m_player.pitchCompensation())
+
+    def isPlayerAvailable(self):
+        return self.m_player.isAvailable()
+
     @Slot()
     def open(self):
-        self._ensure_stopped()
-        file_dialog = QFileDialog(self)
-
-        is_windows = sys.platform == 'win32'
-        if not self._mime_types:
-            self._mime_types = get_supported_mime_types()
-            if (is_windows and AVI not in self._mime_types):
-                self._mime_types.append(AVI)
-            elif MP4 not in self._mime_types:
-                self._mime_types.append(MP4)
-
-        file_dialog.setMimeTypeFilters(self._mime_types)
-
-        default_mimetype = AVI if is_windows else MP4
-        if default_mimetype in self._mime_types:
-            file_dialog.selectMimeTypeFilter(default_mimetype)
-
-        movies_location = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
-        file_dialog.setDirectory(movies_location)
-        if file_dialog.exec() == QDialog.Accepted:
-            url = file_dialog.selectedUrls()[0]
-            self._playlist.append(url)
-            self._playlist_index = len(self._playlist) - 1
-            self._player.setSource(url)
-            self._player.play()
+        fileDialog = QFileDialog(self)
+        fileDialog.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
+        fileDialog.setWindowTitle("Open Files")
+        fileDialog.setMimeTypeFilters(getSupportedMimeTypes())
+        fileDialog.selectMimeTypeFilter(MP4)
+        movieDirs = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.MoviesLocation)
+        fileDialog.setDirectory(movieDirs[0] if movieDirs else QDir.homePath())
+        if fileDialog.exec() == QDialog.DialogCode.Accepted:
+            self.openUrl(fileDialog.selectedUrls()[0])
+
+    def openUrl(self, url):
+        self.m_player.setSource(url)
+
+    @Slot("qlonglong")
+    def durationChanged(self, duration):
+        self.m_duration = duration / 1000
+        self.m_slider.setMaximum(duration)
+
+    @Slot("qlonglong")
+    def positionChanged(self, progress):
+        if not self.m_slider.isSliderDown():
+            self.m_slider.setValue(progress)
+        self.updateDurationInfo(progress / 1000)
 
     @Slot()
-    def _ensure_stopped(self):
-        if self._player.playbackState() != QMediaPlayer.StoppedState:
-            self._player.stop()
+    def metaDataChanged(self):
+        metaData = self.m_player.metaData()
+        artist = metaData.value(QMediaMetaData.Key.AlbumArtist)
+        title = metaData.value(QMediaMetaData.Key.Title)
+        trackInfo = QApplication.applicationName()
+        if artist and title:
+            trackInfo = f"{artist} - {title}"
+        elif artist:
+            trackInfo = artist
+        elif title:
+            trackInfo = title
+        self.setTrackInfo(trackInfo)
+
+        for i in range(0, QMediaMetaData.NumMetaData):
+            field = self.m_metaDataFields[i]
+            if isinstance(field, QLineEdit):
+                field.clear()
+            elif isinstance(field, QLabel):
+                field.clear()
+            self.m_metaDataFields[i].setDisabled(True)
+            self.m_metaDataLabels[i].setDisabled(True)
+
+        for key in metaData.keys():
+            i = key.value
+            field = self.m_metaDataFields[i]
+            if key == QMediaMetaData.Key.CoverArtImage or key == QMediaMetaData.Key.ThumbnailImage:
+                if isinstance(field, QLabel):
+                    field.setPixmap(QPixmap.fromImage(metaData.value(key)))
+            elif isinstance(field, QLineEdit):
+                field.setText(metaData.stringValue(key))
+
+            self.m_metaDataFields[i].setDisabled(False)
+            self.m_metaDataLabels[i].setDisabled(False)
+
+        tracks = self.m_player.videoTracks()
+        currentVideoTrack = self.m_player.activeVideoTrack()
+        if currentVideoTrack >= 0 and currentVideoTrack < len(tracks):
+            track = tracks[currentVideoTrack]
+            trackKeys = track.keys()
+            for key in trackKeys:
+                i = key.value
+                field = self.m_metaDataFields[i]
+                if isinstance(field, QLineEdit):
+                    stringValue = track.stringValue(key)
+                    field.setText(stringValue)
+                self.m_metaDataFields[i].setDisabled(True)
+                self.m_metaDataLabels[i].setDisabled(True)
+
+    def trackName(self, metaData, index):
+        name = ""
+        title = metaData.stringValue(QMediaMetaData.Key.Title)
+        lang = metaData.value(QMediaMetaData.Key.Language)
+        if not title:
+            if lang == QLocale.Language.AnyLanguage:
+                name = f"Track {index + 1}"
+            else:
+                name = QLocale.languageToString(lang)
+        else:
+            if lang == QLocale.Language.AnyLanguage:
+                name = title
+            else:
+                langName = QLocale.languageToString(lang)
+                name = f"{title} - [{langName}]"
+        return name
 
     @Slot()
-    def previous_clicked(self):
-        # Go to previous track if we are within the first 5 seconds of playback
-        # Otherwise, seek to the beginning.
-        if self._player.position() <= 5000 and self._playlist_index > 0:
-            self._playlist_index -= 1
-            self._playlist.previous()
-            self._player.setSource(self._playlist[self._playlist_index])
+    def tracksChanged(self):
+        self.m_audioTracks.clear()
+        self.m_videoTracks.clear()
+        self.m_subtitleTracks.clear()
+
+        audioTracks = self.m_player.audioTracks()
+        self.m_audioTracks.addItem("No audio", -1)
+        for i in range(0, len(audioTracks)):
+            self.m_audioTracks.addItem(self.trackName(audioTracks[i], i), i)
+        self.m_audioTracks.setCurrentIndex(self.m_player.activeAudioTrack() + 1)
+
+        videoTracks = self.m_player.videoTracks()
+        self.m_videoTracks.addItem("No video", -1)
+        for i in range(0, len(videoTracks)):
+            self.m_videoTracks.addItem(self.trackName(videoTracks[i], i), i)
+        self.m_videoTracks.setCurrentIndex(self.m_player.activeVideoTrack() + 1)
+
+        self.m_subtitleTracks.addItem("No subtitles", -1)
+        subtitleTracks = self.m_player.subtitleTracks()
+        for i in range(0, len(subtitleTracks)):
+            self.m_subtitleTracks.addItem(self.trackName(subtitleTracks[i], i), i)
+        self.m_subtitleTracks.setCurrentIndex(self.m_player.activeSubtitleTrack() + 1)
+
+    @Slot()
+    def previousClicked(self):
+        self.m_player.setPosition(0)
+
+    @Slot(int)
+    def seek(self, mseconds):
+        self.m_player.setPosition(mseconds)
+
+    @Slot(QMediaPlayer.MediaStatus)
+    def statusChanged(self, status):
+        self.handleCursor(status)
+        # handle status message
+        if (status == QMediaPlayer.MediaStatus.NoMedia
+                or status == QMediaPlayer.MediaStatus.LoadedMedia):
+            self.setStatusInfo("")
+        elif status == QMediaPlayer.MediaStatus.LoadingMedia:
+            self.setStatusInfo("Loading...")
+        elif (status == QMediaPlayer.MediaStatus.BufferingMedia
+              or status == QMediaPlayer.MediaStatus.BufferedMedia):
+            progress = round(self.m_player.bufferProgress() * 100.0)
+            self.setStatusInfo(f"Buffering {progress}%")
+        elif status == QMediaPlayer.MediaStatus.StalledMedia:
+            progress = round(self.m_player.bufferProgress() * 100.0)
+            self.setStatusInfo(f"Stalled {progress}%")
+        elif status == QMediaPlayer.MediaStatus.EndOfMedia:
+            QApplication.alert(self)
+        elif status == QMediaPlayer.MediaStatus.InvalidMedia:
+            self.displayErrorMessage()
+
+    def handleCursor(self, status):
+        if (status == QMediaPlayer.MediaStatus.LoadingMedia
+                or status == QMediaPlayer.MediaStatus.BufferingMedia
+                or status == QMediaPlayer.MediaStatus.StalledMedia):
+            self.setCursor(QCursor(Qt.CursorShape.BusyCursor))
+        else:
+            self.unsetCursor()
+
+    @Slot("float")
+    def bufferingProgress(self, progressV):
+        progress = round(progressV * 100.0)
+        if self.m_player.mediaStatus() == QMediaPlayer.MediaStatus.StalledMedia:
+            self.setStatusInfo(f"Stalled {progress}%")
         else:
-            self._player.setPosition(0)
+            self.setStatusInfo(f"Buffering {progress}%")
+
+    @Slot(bool)
+    def videoAvailableChanged(self, available):
+        if not available:
+            self.m_fullScreenButton.clicked.disconnect(self.m_videoWidget.switchToFullScreen)
+            self.m_videoWidget.fullScreenChanged.disconnect(self.m_fullScreenButton.setChecked)
+            self.m_videoWidget.setFullScreen(False)
+        else:
+            self.m_fullScreenButton.clicked.connect(self.m_videoWidget.switchToFullScreen)
+            self.m_videoWidget.fullScreenChanged.connect(self.m_fullScreenButton.setChecked)
+            if self.m_fullScreenButton.isChecked():
+                self.m_videoWidget.setFullScreen(True)
+
+    @Slot()
+    def selectAudioStream(self):
+        stream = self.m_audioTracks.currentData()
+        self.m_player.setActiveAudioTrack(stream)
+
+    @Slot()
+    def selectVideoStream(self):
+        stream = self.m_videoTracks.currentData()
+        self.m_player.setActiveVideoTrack(stream)
 
     @Slot()
-    def next_clicked(self):
-        if self._playlist_index < len(self._playlist) - 1:
-            self._playlist_index += 1
-            self._player.setSource(self._playlist[self._playlist_index])
-
-    @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._pause_action.setEnabled(state == QMediaPlayer.PlayingState)
-        self._stop_action.setEnabled(state != QMediaPlayer.StoppedState)
-        self._previous_action.setEnabled(self._player.position() > 0)
-        self._next_action.setEnabled(media_count > 1)
-
-    def show_status_message(self, message):
-        self.statusBar().showMessage(message, 5000)
-
-    @Slot("QMediaPlayer::Error", str)
-    def _player_error(self, error, error_string):
-        print(error_string, file=sys.stderr)
-        self.show_status_message(error_string)
+    def selectSubtitleStream(self):
+        stream = self.m_subtitleTracks.currentData()
+        self.m_player.setActiveSubtitleTrack(stream)
+
+    def setTrackInfo(self, info):
+        self.m_trackInfo = info
+        title = self.m_trackInfo
+        if self.m_statusInfo:
+            title += f" | {self.m_statusInfo}"
+        self.setWindowTitle(title)
+
+    def setStatusInfo(self, info):
+        self.m_statusInfo = info
+        title = self.m_trackInfo
+        if self.m_statusInfo:
+            title += f" | {self.m_statusInfo}"
+        self.setWindowTitle(title)
 
     @Slot()
-    def setVolume(self):
-        self.volumeValue = QAudio.convertVolume(self._volume_slider.value() / 100.0,
-                                                QAudio.VolumeScale.LogarithmicVolumeScale,
-                                                QAudio.VolumeScale.LinearVolumeScale)
-        self._audio_output.setVolume(self.volumeValue)
-
-
-if __name__ == '__main__':
-    app = QApplication(sys.argv)
-    main_win = MainWindow()
-    available_geometry = main_win.screen().availableGeometry()
-    main_win.resize(available_geometry.width() / 3,
-                    available_geometry.height() / 2)
-    main_win.show()
-    sys.exit(app.exec())
+    def displayErrorMessage(self):
+        if self.m_player.error() != QMediaPlayer.Error.NoError:
+            self.setStatusInfo(self.m_player.errorString())
+
+    def updateDurationInfo(self, currentInfo):
+        tStr = ""
+        if currentInfo or self.m_duration:
+            currentTime = QTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60,
+                                currentInfo % 60, (currentInfo * 1000) % 1000)
+            totalTime = QTime((self.m_duration / 3600) % 60, (self.m_duration / 60) % 60,
+                              self.m_duration % 60, (self.m_duration * 1000) % 1000)
+            format = "hh:mm:ss" if self.m_duration > 3600 else "mm:ss"
+            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
+        self.m_labelDuration.setText(tStr)
+
+    @Slot()
+    def updateAudioDevices(self):
+        self.m_audioOutputCombo.clear()
+
+        self.m_audioOutputCombo.addItem("Default", QAudioDevice())
+        for deviceInfo in QMediaDevices.audioOutputs():
+            self.m_audioOutputCombo.addItem(deviceInfo.description(), deviceInfo)
+
+    @Slot(int)
+    def audioOutputChanged(self, index):
+        device = self.m_audioOutputCombo.itemData(index)
+        self.m_player.audioOutput().setDevice(device)
index 2e16f450570e56c62ae668c31e26aeca833181fd..cb278248f117d921d688e9c32edad097f08d70ce 100644 (file)
@@ -1,3 +1,7 @@
 {
-    "files": ["player.py"]
+    "files": ["main.py",
+              "audiolevelmeter.py",
+              "player.py",
+              "playercontrols.py",
+              "videowidget.py"]
 }
diff --git a/examples/multimedia/player/playercontrols.py b/examples/multimedia/player/playercontrols.py
new file mode 100644 (file)
index 0000000..2093e99
--- /dev/null
@@ -0,0 +1,162 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtMultimedia import QMediaPlayer, QtAudio
+from PySide6.QtWidgets import (QComboBox, QHBoxLayout, QSizePolicy, QSlider, QStyle,
+                               QToolButton, QWidget)
+from PySide6.QtGui import QPalette
+from PySide6.QtCore import qFuzzyCompare, Qt, Signal, Slot
+
+
+class PlayerControls(QWidget):
+
+    play = Signal()
+    pause = Signal()
+    stop = Signal()
+    previous = Signal()
+    changeVolume = Signal(float)
+    changeMuting = Signal(bool)
+    changeRate = Signal(float)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+
+        style = self.style()
+        self.m_playerState = QMediaPlayer.PlaybackState.StoppedState
+        self.m_playerMuted = False
+
+        self.m_playButton = QToolButton(self)
+        self.m_playButton.setIcon(style.standardIcon(QStyle.StandardPixmap.SP_MediaPlay))
+        self.m_playButton.setToolTip("Play")
+        self.m_playButton.clicked.connect(self.playClicked)
+
+        self.m_pauseButton = QToolButton(self)
+        self.m_pauseButton.setIcon(style.standardIcon(QStyle.StandardPixmap.SP_MediaPause))
+        self.m_pauseButton.setToolTip("Pause")
+        self.m_pauseButton.clicked.connect(self.pauseClicked)
+
+        self.m_stopButton = QToolButton(self)
+        self.m_stopButton.setIcon(style.standardIcon(QStyle.StandardPixmap.SP_MediaStop))
+        self.m_stopButton.setToolTip("Stop")
+        self.m_stopButton.clicked.connect(self.stop)
+
+        self.m_previousButton = QToolButton(self)
+        self.m_previousButton.setIcon(style.standardIcon(QStyle.StandardPixmap.SP_MediaSkipBackward))  # noqa: E501
+        self.m_previousButton.setToolTip("Rewind")
+        self.m_previousButton.clicked.connect(self.previous)
+
+        self.m_muteButton = QToolButton(self)
+        self.m_muteButton.setIcon(style.standardIcon(QStyle.StandardPixmap.SP_MediaVolume))
+        self.m_muteButton.setToolTip("Mute")
+        self.m_muteButton.clicked.connect(self.muteClicked)
+
+        self.m_volumeSlider = QSlider(Qt.Orientation.Horizontal, self)
+        self.m_volumeSlider.setRange(0, 100)
+        sp = self.m_volumeSlider.sizePolicy()
+        sp.setHorizontalPolicy(QSizePolicy.Policy.MinimumExpanding)
+        self.m_volumeSlider.setSizePolicy(sp)
+        self.m_volumeSlider.valueChanged.connect(self.onVolumeSliderValueChanged)
+
+        self.m_rateBox = QComboBox(self)
+        self.m_rateBox.setToolTip("Rate")
+        self.m_rateBox.addItem("0.5x", 0.5)
+        self.m_rateBox.addItem("1.0x", 1.0)
+        self.m_rateBox.addItem("2.0x", 2.0)
+        self.m_rateBox.setCurrentIndex(1)
+
+        self.m_rateBox.activated.connect(self.updateRate)
+
+        self._doSetState(QMediaPlayer.PlaybackState.StoppedState, True)
+
+        layout = QHBoxLayout(self)
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(self.m_stopButton)
+        layout.addWidget(self.m_previousButton)
+        layout.addWidget(self.m_pauseButton)
+        layout.addWidget(self.m_playButton)
+        layout.addWidget(self.m_muteButton)
+        layout.addWidget(self.m_volumeSlider)
+        layout.addWidget(self.m_rateBox)
+
+    def state(self):
+        return self.m_playerState
+
+    @Slot(QMediaPlayer.PlaybackState)
+    def setState(self, state):
+        self._doSetState(state, False)
+
+    def _doSetState(self, state, force):
+        if state != self.m_playerState or force:
+            self.m_playerState = state
+
+            baseColor = self.palette().color(QPalette.ColorRole.Base)
+            inactiveStyleSheet = f"background-color: {baseColor.name()}"
+            defaultStyleSheet = ""
+
+            if state == QMediaPlayer.PlaybackState.StoppedState:
+                self.m_stopButton.setStyleSheet(inactiveStyleSheet)
+                self.m_playButton.setStyleSheet(defaultStyleSheet)
+                self.m_pauseButton.setStyleSheet(defaultStyleSheet)
+            elif state == QMediaPlayer.PlaybackState.PlayingState:
+                self.m_stopButton.setStyleSheet(defaultStyleSheet)
+                self.m_playButton.setStyleSheet(inactiveStyleSheet)
+                self.m_pauseButton.setStyleSheet(defaultStyleSheet)
+            elif state == QMediaPlayer.PlaybackState.PausedState:
+                self.m_stopButton.setStyleSheet(defaultStyleSheet)
+                self.m_playButton.setStyleSheet(defaultStyleSheet)
+                self.m_pauseButton.setStyleSheet(inactiveStyleSheet)
+
+    def volume(self):
+        linearVolume = QtAudio.convertVolume(self.m_volumeSlider.value() / 100.0,
+                                             QtAudio.VolumeScale.LogarithmicVolumeScale,
+                                             QtAudio.VolumeScale.LinearVolumeScale)
+        return linearVolume
+
+    @Slot("float")
+    def setVolume(self, volume):
+        logarithmicVolume = QtAudio.convertVolume(volume, QtAudio.VolumeScale.LinearVolumeScale,
+                                                  QtAudio.VolumeScale.LogarithmicVolumeScale)
+        self.m_volumeSlider.setValue(round(logarithmicVolume * 100.0))
+
+    def isMuted(self):
+        return self.m_playerMuted
+
+    @Slot(bool)
+    def setMuted(self, muted):
+        if muted != self.m_playerMuted:
+            self.m_playerMuted = muted
+            sp = (QStyle.StandardPixmap.SP_MediaVolumeMuted
+                  if muted else QStyle.StandardPixmap.SP_MediaVolume)
+            self.m_muteButton.setIcon(self.style().standardIcon(sp))
+
+    @Slot()
+    def playClicked(self):
+        self.play.emit()
+
+    @Slot()
+    def pauseClicked(self):
+        self.pause.emit()
+
+    @Slot()
+    def muteClicked(self):
+        self.changeMuting.emit(not self.m_playerMuted)
+
+    def playbackRate(self):
+        return self.m_rateBox.itemData(self.m_rateBox.currentIndex())
+
+    def setPlaybackRate(self, rate):
+        for i in range(0, self.m_rateBox.count()):
+            if qFuzzyCompare(rate, self.m_rateBox.itemData(i)):
+                self.m_rateBox.setCurrentIndex(i)
+                return
+
+        self.m_rateBox.addItem(f"{rate}x", rate)
+        self.m_rateBox.setCurrentIndex(self.m_rateBox.count() - 1)
+
+    @Slot()
+    def updateRate(self):
+        self.changeRate.emit(self.playbackRate())
+
+    @Slot()
+    def onVolumeSliderValueChanged(self):
+        self.changeVolume.emit(self.volume())
diff --git a/examples/multimedia/player/videowidget.py b/examples/multimedia/player/videowidget.py
new file mode 100644 (file)
index 0000000..d2ec9c7
--- /dev/null
@@ -0,0 +1,41 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtMultimediaWidgets import QVideoWidget
+from PySide6.QtWidgets import QSizePolicy
+from PySide6.QtGui import QPalette
+from PySide6.QtCore import Qt, QOperatingSystemVersion, Slot
+
+
+class VideoWidget(QVideoWidget):
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
+        p = self.palette()
+        p.setColor(QPalette.ColorRole.Window, Qt.GlobalColor.black)
+        self.setPalette(p)
+        if QOperatingSystemVersion.currentType() != QOperatingSystemVersion.OSType.Android:
+            self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent)
+
+    def keyPressEvent(self, event):
+        key = event.key()
+        if (key == Qt.Key.Key_Escape or key == Qt.Key.Key_Back) and self.isFullScreen():
+            self.setFullScreen(False)
+            event.accept()
+        elif key == Qt.Key.Key_Enter and event.modifiers() & Qt.Key.Key_Alt:
+            self.setFullScreen(not self.isFullScreen())
+            event.accept()
+        else:
+            super().keyPressEvent(event)
+
+    @Slot()
+    def switchToFullScreen(self):
+        self.setFullScreen(True)
+
+    def mouseDoubleClickEvent(self, event):
+        self.setFullScreen(not self.isFullScreen())
+        event.accept()
+
+    def mousePressEvent(self, event):
+        super().mousePressEvent(event)
diff --git a/examples/pdfwidgets/pdfviewer/images/go-down-search.svgz b/examples/pdfwidgets/pdfviewer/images/go-down-search.svgz
new file mode 100644 (file)
index 0000000..f845473
Binary files /dev/null and b/examples/pdfwidgets/pdfviewer/images/go-down-search.svgz differ
diff --git a/examples/pdfwidgets/pdfviewer/images/go-up-search.svgz b/examples/pdfwidgets/pdfviewer/images/go-up-search.svgz
new file mode 100644 (file)
index 0000000..6378721
Binary files /dev/null and b/examples/pdfwidgets/pdfviewer/images/go-up-search.svgz differ
index 154c877065bc8e01b8dc0851b8e981056a2e235d..f6344195914679cd4967fbcece0666ec5ab5019a 100644 (file)
@@ -5,13 +5,15 @@ from __future__ import annotations
 import math
 import sys
 
-from PySide6.QtPdf import QPdfBookmarkModel, QPdfDocument
+from PySide6.QtPdf import QPdfBookmarkModel, QPdfDocument, QPdfSearchModel
 from PySide6.QtPdfWidgets import QPdfView
-from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, QMessageBox,
+from PySide6.QtWidgets import (QDialog, QFileDialog, QLineEdit, QMainWindow, QMessageBox,
                                QSpinBox)
-from PySide6.QtCore import QModelIndex, QPoint, QStandardPaths, QUrl, Slot
+from PySide6.QtGui import QKeySequence, QShortcut
+from PySide6.QtCore import QModelIndex, QPoint, QPointF, QStandardPaths, QUrl, Qt, Slot
 
 from zoomselector import ZoomSelector
+from searchresultdelegate import SearchResultDelegate
 from ui_mainwindow import Ui_MainWindow
 
 
@@ -50,12 +52,47 @@ class MainWindow(QMainWindow):
         self.ui.bookmarkView.setModel(bookmark_model)
         self.ui.bookmarkView.activated.connect(self.bookmark_selected)
 
-        self.ui.tabWidget.setTabEnabled(1, False)  # disable 'Pages' tab for now
+        self.ui.thumbnailsView.setModel(self.m_document.pageModel())
 
         self.ui.pdfView.setDocument(self.m_document)
 
         self.ui.pdfView.zoomFactorChanged.connect(self.m_zoomSelector.set_zoom_factor)
 
+        self.m_searchModel = QPdfSearchModel(self)
+        self.m_searchModel.setDocument(self.m_document)
+        self.m_searchField = QLineEdit(self)
+
+        self.ui.pdfView.setSearchModel(self.m_searchModel)
+        self.ui.searchToolBar.insertWidget(self.ui.actionFindPrevious, self.m_searchField)
+        self.m_findShortcut = QShortcut(QKeySequence.StandardKey.Find, self)
+        self.m_findShortcut.activated.connect(self.setSearchFocus)
+        self.m_searchField.setPlaceholderText("Find in document")
+        self.m_searchField.setMaximumWidth(400)
+        self.m_searchField.textEdited.connect(self.searchTextChanged)
+        self.ui.searchResultsView.setModel(self.m_searchModel)
+        self.m_delegate = SearchResultDelegate(self)
+        self.ui.searchResultsView.setItemDelegate(self.m_delegate)
+        sel_model = self.ui.searchResultsView.selectionModel()
+        sel_model.currentChanged.connect(self.searchResultSelected)
+
+    @Slot()
+    def setSearchFocus(self):
+        self.m_searchField.setFocus(Qt.FocusReason.ShortcutFocusReason)
+
+    @Slot()
+    def searchTextChanged(self, text):
+        self.m_searchModel.setSearchString(text)
+        self.ui.tabWidget.setCurrentWidget(self.ui.searchResultsTab)
+
+    @Slot(QModelIndex, QModelIndex)
+    def searchResultSelected(self, current, previous):
+        if not current.isValid():
+            return
+        page = current.data(QPdfSearchModel.Role.Page.value)
+        location = current.data(QPdfSearchModel.Role.Location.value)
+        self.ui.pdfView.pageNavigator().jump(page, location)
+        self.ui.pdfView.setCurrentSearchResultIndex(current.row())
+
     @Slot(QUrl)
     def open(self, doc_location):
         if doc_location.isLocalFile():
@@ -94,6 +131,20 @@ class MainWindow(QMainWindow):
             if to_open.isValid():
                 self.open(to_open)
 
+    @Slot()
+    def on_actionFindNext_triggered(self):
+        next = self.ui.searchResultsView.currentIndex().row() + 1
+        if next >= self.m_searchModel.rowCount(QModelIndex()):
+            next = 0
+        self.ui.searchResultsView.setCurrentIndex(self.m_searchModel.index(next))
+
+    @Slot()
+    def on_actionFindPrevious_triggered(self):
+        prev = self.ui.searchResultsView.currentIndex().row() - 1
+        if prev < 0:
+            prev = self.m_searchModel.rowCount(QModelIndex()) - 1
+        self.ui.searchResultsView.setCurrentIndex(self.m_searchModel.index(prev))
+
     @Slot()
     def on_actionQuit_triggered(self):
         self.close()
@@ -127,6 +178,11 @@ class MainWindow(QMainWindow):
         nav = self.ui.pdfView.pageNavigator()
         nav.jump(nav.currentPage() + 1, QPoint(), nav.currentZoom())
 
+    @Slot(QModelIndex)
+    def on_thumbnailsView_activated(self, index):
+        nav = self.ui.pdfView.pageNavigator()
+        nav.jump(index.row(), QPointF(), nav.currentZoom())
+
     @Slot()
     def on_actionContinuous_triggered(self):
         cont_checked = self.ui.actionContinuous.isChecked()
index 3bf46887cda233ddd4d8e13d197e0b5169b76db3..a9a153bf62cc44d81e2ba0497da3ef8419cdf91c 100644 (file)
            <attribute name="title">
             <string>Pages</string>
            </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_4">
+            <property name="leftMargin">
+             <number>2</number>
+            </property>
+            <property name="topMargin">
+             <number>2</number>
+            </property>
+            <property name="rightMargin">
+             <number>2</number>
+            </property>
+            <property name="bottomMargin">
+             <number>2</number>
+            </property>
+            <item>
+             <widget class="QListView" name="thumbnailsView">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="iconSize">
+               <size>
+                <width>128</width>
+                <height>128</height>
+               </size>
+              </property>
+              <property name="movement">
+               <enum>QListView::Movement::Static</enum>
+              </property>
+              <property name="resizeMode">
+               <enum>QListView::ResizeMode::Adjust</enum>
+              </property>
+              <property name="viewMode">
+               <enum>QListView::ViewMode::IconMode</enum>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QWidget" name="searchResultsTab">
+           <attribute name="title">
+            <string>Search Results</string>
+           </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_5">
+            <property name="spacing">
+             <number>0</number>
+            </property>
+            <property name="leftMargin">
+             <number>2</number>
+            </property>
+            <property name="topMargin">
+             <number>2</number>
+            </property>
+            <property name="rightMargin">
+             <number>2</number>
+            </property>
+            <property name="bottomMargin">
+             <number>2</number>
+            </property>
+            <item>
+             <widget class="QListView" name="searchResultsView">
+              <property name="horizontalScrollBarPolicy">
+               <enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
+              </property>
+             </widget>
+            </item>
+           </layout>
           </widget>
          </widget>
          <widget class="QPdfView" name="pdfView" native="true">
    <addaction name="actionForward"/>
   </widget>
   <widget class="QStatusBar" name="statusBar"/>
+  <widget class="QToolBar" name="searchToolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="actionFindPrevious"/>
+   <addaction name="actionFindNext"/>
+  </widget>
   <action name="actionOpen">
    <property name="icon">
     <iconset theme="document-open" resource="resources.qrc">
     <string>Zoom In</string>
    </property>
    <property name="shortcut">
-    <string>Ctrl++</string>
+    <string>Ctrl+=</string>
    </property>
   </action>
   <action name="actionZoom_Out">
     <string>forward to next view</string>
    </property>
   </action>
+  <action name="actionFindNext">
+   <property name="icon">
+    <iconset theme="go-down" resource="resources.qrc">
+     <normaloff>:/icons/images/go-down-search.svgz</normaloff>:/icons/images/go-down-search.svgz</iconset>
+   </property>
+   <property name="text">
+    <string>Find Next</string>
+   </property>
+   <property name="toolTip">
+    <string>Find the next occurrence of the phrase</string>
+   </property>
+   <property name="shortcut">
+    <string>F3</string>
+   </property>
+  </action>
+  <action name="actionFindPrevious">
+   <property name="icon">
+    <iconset theme="go-up" resource="resources.qrc">
+     <normaloff>:/icons/images/go-up-search.svgz</normaloff>:/icons/images/go-up-search.svgz</iconset>
+   </property>
+   <property name="text">
+    <string>Find Previous</string>
+   </property>
+   <property name="toolTip">
+    <string>Find the previous occurrence of the phrase</string>
+   </property>
+   <property name="shortcut">
+    <string>Shift+F3</string>
+   </property>
+  </action>
  </widget>
  <layoutdefault spacing="6" margin="11"/>
  <customwidgets>
index cbd5f156f00a470c6d56d8b719ba21e44121ee77..ecaae7c35b468917457f68c7fbf2769154c1d41e 100644 (file)
@@ -1,4 +1,4 @@
 {
-    "files": ["main.py", "mainwindow.py", "zoomselector.py",
+    "files": ["main.py", "mainwindow.py", "zoomselector.py", "searchresultdelegate.py",
               "mainwindow.ui","resources.qrc"]
 }
index 7e386e99b157eb4f21ccb17f3e2425d8815bc083..d0daa31646930331676f6654e9150616bde3a93f 100644 (file)
@@ -1,11 +1,29 @@
 # Resource object code (Python 3)
 # Created by: object code
-# Created by: The Resource Compiler for Qt version 6.4.0
+# Created by: The Resource Compiler for Qt version 6.10.0
 # WARNING! All changes made in this file will be lost!
 
 from PySide6 import QtCore
 
 qt_resource_data = b"\
+\x00\x00\x00\xf1\
+\x1f\
+\x8b\x08\x08A0\x10d\x00\x03go-up-s\
+earch.svg\x00]OA\x8e\xc20\
+\x0c\xbc\xf7\x15\x969'i\xd3\x02\x85m{\xe1\x0a\xa7\
+\xdd\x0f\xa0\x90m*\x05Z5\x86\x80V\xfbw\x92\x82\
+z`,K#\x8fF3\xae\xdc\xad\x05\xa3\xbb\xd6P\
+\x8d\xb2@\xf0\xdd\x89\xcc\x8b\xde\xcf\xf6\xe2j4D\xc3\
+V\x08\xef=\xf79\xef\xc7V\xc84ME0b\x93\
+@@\xe5\xe8a5\xd0c\xd05\x92\xbe\x93P\xce!\
+t\xa7\x1a\xd5u\x1c\xf5\x85\x98\xeam?2\xa7\x8c>\
+\xeb\xb7+\x82\xef\xe2\xfd{:\xb3\x9f\xe0\x84\xbfY\x8b\
+\x98l\xdb\x85\xcc\xe5Jn\xbef\xe9\xff\x15+\xa6\xdc\
+w\x87\xe1H\x06B\xe2\xa1\xe0\xebt\x0d\xd9j_@\
+\xb6\xe4r\x93\xdb\x92\x95\x10\x86E!\xee>\x93PF\
+\x82\xa0\xec\xd1\x85\x17?k \xfcv\xd6\xce\xf5'\x19\
+E\x93T\xf1\xeb&y\x02\x19\x0e\x0c\xf45\x01\x00\x00\
+\
 \x00\x001G\
 \x1f\
 \x8b\x08\x00\x00\x00\x00\x00\x00\x00\xec}\xebs\x1b\xc7\xb1\
@@ -1609,6 +1627,29 @@ U\xda\x18\xd8\xccs\x13\x15\x87\xa4`-\x83\x1eT\xcd\
 \xe0i\x8a\xa6\xd0\x09\x9f4\xdd\xda'm\xd6'\xbf\xdb\
 \xbf\xfa\xf1\xdd\x9f~\xf7_\xea_?\x7f\x00\x9a\x00\x00\
 \
+\x00\x00\x01J\
+\x1f\
+\x8b\x08\x08I0\x10d\x00\x03go-down\
+-search.svg\x00]Q\xcbn\
+\xc20\x10\xbc\xf3\x15+\xf7\x1c?\xf3&\xe1\xd0^\xe9\
+\xa9\xfd\x01\x14\x0c\x89\x1a\xe2(v\x09i\xd5\x7f\xafm\
+\x5cT\x90,ywvfvdW\xfa|\x84\xcb\xa9\
+\x1ft\x8dZc\xc6\x92\x90y\x9e\xf1,\xb0\x9a\x8e\x84\
+SJ\x89e 8wr~V\x97\x1aQ\xa0\xc0c\
+{\xd0f\x05P\xed\xe5AC\xb7\xaf\x91+\x04M\x98\
+\x87\xed@\x9b\xa5\x97`\x96Q\xd6\xc8\xc8\x8b!\x8d\xd6\
+\xc83\x9b\xcfi\x92\x83\x89\x1a\xd5\xab)\xd2M+O\
+2\xa8\x00\xf0\x8bC\xdf<\x18\xbd[\x1d|\x87\x09\x80\
+\x17\x94O\x5c\xf0\x94\x17\xeb\x00\xff\x84\xbb\x22~\xa3\x0f\
+E\x5c\x18[U\xe3\xce\xb4\xe0\xf1\x1a\x1d\xba\xbe/\xc3\
+n\xbfd\xed\x90H\x8d\xbb\xa63K\xc9\xd6\xdaL\xea\
+C\x96\x83\x1a$\xba\x9a\xda\xb0\xaf\x10\xe3\x8cfT0\
+\x0e9l!\x86\xfc\xd6o\x81Q\xccx\x02,\xc6\xb9\
+\xe0\x16s\x10\x07\x96\x06\x8ak\x05\xce\xb3{\x06\xa7\xf7\
+\x1e\x05\xe6\x05/\xd2\xc2\xfb[>K3\xd7Y\x89\xf3\
+\xbe:&\x7f\x1c\xbf3X\xdd\x18\xff#~A\x08\xdf\
+\xf4;m?\xf5\xf1=\xc3\x94lV\x95\xfb\xd9\xcd\xea\
+\x17i\x1a\x96c\x02\x02\x00\x00\
 \x00\x00\x15,\
 \x1f\
 \x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=ko\xe3F\x92\
@@ -3401,6 +3442,11 @@ qt_resource_name = b"\
 \x07\x03}\xc3\
 \x00i\
 \x00m\x00a\x00g\x00e\x00s\
+\x00\x11\
+\x0e\x9eN\xea\
+\x00g\
+\x00o\x00-\x00u\x00p\x00-\x00s\x00e\x00a\x00r\x00c\x00h\x00.\x00s\x00v\x00g\x00z\
+\
 \x00\x16\
 \x02\x1b\xe1\x0a\
 \x00g\
@@ -3416,6 +3462,11 @@ qt_resource_name = b"\
 \x00g\
 \x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00.\
 \x00s\x00v\x00g\x00z\
+\x00\x13\
+\x03\xa8\x05\x0a\
+\x00g\
+\x00o\x00-\x00d\x00o\x00w\x00n\x00-\x00s\x00e\x00a\x00r\x00c\x00h\x00.\x00s\x00v\
+\x00g\x00z\
 \x00\x0d\
 \x0e\xb9\xa6*\
 \x00z\
@@ -3441,22 +3492,26 @@ qt_resource_struct = b"\
 \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\x07\x00\x00\x00\x03\
+\x00\x00\x00\x10\x00\x02\x00\x00\x00\x09\x00\x00\x00\x03\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x010\x00\x00\x00\x00\x00\x01\x00\x00\xba\xe2\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
-\x00\x00\x00|\x00\x00\x00\x00\x00\x01\x00\x00J'\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
-\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00\x89\xa4\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
+\x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x00\xbd%\
+\x00\x00\x01\x975l\xc7\xe5\
+\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x01\x00\x00K\x1c\
+\x00\x00\x01\x975l\xc7\xe5\
+\x00\x00\x01J\x00\x00\x00\x00\x00\x01\x00\x00\x8b\xe7\
+\x00\x00\x01\x975l\xc7\xe5\
+\x00\x00\x00J\x00\x00\x00\x00\x00\x01\x00\x00\x00\xf5\
+\x00\x00\x01\x975l\xc7\xe5\
+\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00d\xb1\
+\x00\x00\x01\x99v\xc8\x0b%\
+\x00\x00\x00|\x00\x00\x00\x00\x00\x01\x00\x002@\
+\x00\x00\x01\x975l\xc7\xe5\
+\x00\x00\x01 \x00\x00\x00\x00\x00\x01\x00\x00{/\
+\x00\x00\x01\x975l\xc7\xe5\
 \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
-\x00\x00\x00T\x00\x00\x00\x00\x00\x01\x00\x001K\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
-\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00x\xec\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
-\x00\x00\x00\xac\x00\x00\x00\x00\x00\x01\x00\x00c\xbc\
-\x00\x00\x01\x81\x8a\xd9\xf0\x94\
+\x00\x00\x01\x99v\xc7\xf9e\
+\x00\x00\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00e\xff\
+\x00\x00\x01\x975l\xc7\xe6\
 "
 
 def qInitResources():
index db77763d2e776e052783ec34ac815f8ec517b9e8..ea408b825c4cc50f6e896d2f417c78a19e0a2c10 100644 (file)
@@ -1,10 +1,12 @@
 <RCC>
     <qresource prefix="/icons">
         <file>images/document-open.svgz</file>
+        <file>images/go-down-search.svgz</file>
         <file>images/go-next-view.svgz</file>
         <file>images/go-previous-view.svgz</file>
         <file>images/go-next-view-page.svgz</file>
         <file>images/go-previous-view-page.svgz</file>
+        <file>images/go-up-search.svgz</file>
         <file>images/zoom-in.svgz</file>
         <file>images/zoom-out.svgz</file>
     </qresource>
diff --git a/examples/pdfwidgets/pdfviewer/searchresultdelegate.py b/examples/pdfwidgets/pdfviewer/searchresultdelegate.py
new file mode 100644 (file)
index 0000000..72c39da
--- /dev/null
@@ -0,0 +1,47 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QFont, QFontMetrics
+from PySide6.QtWidgets import QStyle, QStyledItemDelegate
+from PySide6.QtPdf import QPdfSearchModel
+
+
+class SearchResultDelegate(QStyledItemDelegate):
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+
+    def paint(self, painter, option, index):
+        displayText = index.data()
+        boldBegin = displayText.find("<b>") + 3
+        boldEnd = displayText.find("</b>", boldBegin)
+        if boldBegin >= 3 and boldEnd > boldBegin:
+            page = index.data(QPdfSearchModel.Role.Page.value)
+            pageLabel = f"Page {page}: "
+            boldText = displayText[boldBegin:boldEnd]
+            if option.state & QStyle.State_Selected:
+                painter.fillRect(option.rect, option.palette.highlight())
+            defaultFont = painter.font()
+            fm = painter.fontMetrics()
+            pageLabelWidth = fm.horizontalAdvance(pageLabel)
+            yOffset = (option.rect.height() - fm.height()) / 2 + fm.ascent()
+            painter.drawText(0, option.rect.y() + yOffset, pageLabel)
+            boldFont = QFont(defaultFont)
+            boldFont.setBold(True)
+            boldWidth = QFontMetrics(boldFont).horizontalAdvance(boldText)
+            prefixSuffixWidth = (option.rect.width() - pageLabelWidth - boldWidth) / 2
+            painter.setFont(boldFont)
+            painter.drawText(pageLabelWidth + prefixSuffixWidth, option.rect.y() + yOffset,
+                             boldText)
+            painter.setFont(defaultFont)
+            suffix = fm.elidedText(displayText[boldEnd + 4:],
+                                   Qt.TextElideMode.ElideRight, prefixSuffixWidth)
+            painter.drawText(pageLabelWidth + prefixSuffixWidth + boldWidth,
+                             option.rect.y() + yOffset, suffix)
+            prefix = fm.elidedText(displayText[0:boldBegin - 3],
+                                   Qt.TextElideMode.ElideLeft, prefixSuffixWidth)
+            painter.drawText(pageLabelWidth + prefixSuffixWidth - fm.horizontalAdvance(prefix),
+                             option.rect.y() + yOffset, prefix)
+        else:
+            super().paint(painter, option, index)
index c31da6ff0adf42455ec272f05a228d6573d717b8..46c319a77ce9401dc0891b670c1e12206d8d2cae 100644 (file)
@@ -3,7 +3,7 @@
 ################################################################################
 ## Form generated from reading UI file 'mainwindow.ui'
 ##
-## Created by: Qt User Interface Compiler version 6.8.0
+## Created by: Qt User Interface Compiler version 6.10.0
 ##
 ## WARNING! All changes made in this file will be lost when recompiling UI file!
 ################################################################################
@@ -17,10 +17,10 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
     QPainter, QPalette, QPixmap, QRadialGradient,
     QTransform)
 from PySide6.QtPdfWidgets import QPdfView
-from PySide6.QtWidgets import (QApplication, QHeaderView, QMainWindow, QMenu,
-    QMenuBar, QSizePolicy, QSplitter, QStatusBar,
-    QTabWidget, QToolBar, QTreeView, QVBoxLayout,
-    QWidget)
+from PySide6.QtWidgets import (QApplication, QHeaderView, QListView, QMainWindow,
+    QMenu, QMenuBar, QSizePolicy, QSplitter,
+    QStatusBar, QTabWidget, QToolBar, QTreeView,
+    QVBoxLayout, QWidget)
 import rc_resources
 
 class Ui_MainWindow(object):
@@ -104,6 +104,26 @@ class Ui_MainWindow(object):
         icon8 = QIcon()
         icon8.addFile(u":/icons/images/go-next-view.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
         self.actionForward.setIcon(icon8)
+        self.actionFindNext = QAction(MainWindow)
+        self.actionFindNext.setObjectName(u"actionFindNext")
+        icon9 = QIcon()
+        iconThemeName = u"go-down"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon9 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon9.addFile(u":/icons/images/go-down-search.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+
+        self.actionFindNext.setIcon(icon9)
+        self.actionFindPrevious = QAction(MainWindow)
+        self.actionFindPrevious.setObjectName(u"actionFindPrevious")
+        icon10 = QIcon()
+        iconThemeName = u"go-up"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon10 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon10.addFile(u":/icons/images/go-up-search.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+
+        self.actionFindPrevious.setIcon(icon10)
         self.centralWidget = QWidget(MainWindow)
         self.centralWidget.setObjectName(u"centralWidget")
         self.verticalLayout = QVBoxLayout(self.centralWidget)
@@ -148,7 +168,37 @@ class Ui_MainWindow(object):
         self.tabWidget.addTab(self.bookmarkTab, "")
         self.pagesTab = QWidget()
         self.pagesTab.setObjectName(u"pagesTab")
+        self.verticalLayout_4 = QVBoxLayout(self.pagesTab)
+        self.verticalLayout_4.setSpacing(6)
+        self.verticalLayout_4.setContentsMargins(11, 11, 11, 11)
+        self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+        self.verticalLayout_4.setContentsMargins(2, 2, 2, 2)
+        self.thumbnailsView = QListView(self.pagesTab)
+        self.thumbnailsView.setObjectName(u"thumbnailsView")
+        sizePolicy.setHeightForWidth(self.thumbnailsView.sizePolicy().hasHeightForWidth())
+        self.thumbnailsView.setSizePolicy(sizePolicy)
+        self.thumbnailsView.setIconSize(QSize(128, 128))
+        self.thumbnailsView.setMovement(QListView.Movement.Static)
+        self.thumbnailsView.setResizeMode(QListView.ResizeMode.Adjust)
+        self.thumbnailsView.setViewMode(QListView.ViewMode.IconMode)
+
+        self.verticalLayout_4.addWidget(self.thumbnailsView)
+
         self.tabWidget.addTab(self.pagesTab, "")
+        self.searchResultsTab = QWidget()
+        self.searchResultsTab.setObjectName(u"searchResultsTab")
+        self.verticalLayout_5 = QVBoxLayout(self.searchResultsTab)
+        self.verticalLayout_5.setSpacing(0)
+        self.verticalLayout_5.setContentsMargins(11, 11, 11, 11)
+        self.verticalLayout_5.setObjectName(u"verticalLayout_5")
+        self.verticalLayout_5.setContentsMargins(2, 2, 2, 2)
+        self.searchResultsView = QListView(self.searchResultsTab)
+        self.searchResultsView.setObjectName(u"searchResultsView")
+        self.searchResultsView.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
+
+        self.verticalLayout_5.addWidget(self.searchResultsView)
+
+        self.tabWidget.addTab(self.searchResultsTab, "")
         self.splitter.addWidget(self.tabWidget)
         self.pdfView = QPdfView(self.splitter)
         self.pdfView.setObjectName(u"pdfView")
@@ -183,6 +233,9 @@ class Ui_MainWindow(object):
         self.statusBar = QStatusBar(MainWindow)
         self.statusBar.setObjectName(u"statusBar")
         MainWindow.setStatusBar(self.statusBar)
+        self.searchToolBar = QToolBar(MainWindow)
+        self.searchToolBar.setObjectName(u"searchToolBar")
+        MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.searchToolBar)
 
         self.menuBar.addAction(self.menuFile.menuAction())
         self.menuBar.addAction(self.menuView.menuAction())
@@ -204,6 +257,8 @@ class Ui_MainWindow(object):
         self.mainToolBar.addSeparator()
         self.mainToolBar.addAction(self.actionBack)
         self.mainToolBar.addAction(self.actionForward)
+        self.searchToolBar.addAction(self.actionFindPrevious)
+        self.searchToolBar.addAction(self.actionFindNext)
 
         self.retranslateUi(MainWindow)
 
@@ -227,7 +282,7 @@ class Ui_MainWindow(object):
         self.actionAbout_Qt.setText(QCoreApplication.translate("MainWindow", u"About Qt", None))
         self.actionZoom_In.setText(QCoreApplication.translate("MainWindow", u"Zoom In", None))
 #if QT_CONFIG(shortcut)
-        self.actionZoom_In.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl++", None))
+        self.actionZoom_In.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+=", None))
 #endif // QT_CONFIG(shortcut)
         self.actionZoom_Out.setText(QCoreApplication.translate("MainWindow", u"Zoom Out", None))
 #if QT_CONFIG(shortcut)
@@ -250,10 +305,26 @@ class Ui_MainWindow(object):
 #if QT_CONFIG(tooltip)
         self.actionForward.setToolTip(QCoreApplication.translate("MainWindow", u"forward to next view", None))
 #endif // QT_CONFIG(tooltip)
+        self.actionFindNext.setText(QCoreApplication.translate("MainWindow", u"Find Next", None))
+#if QT_CONFIG(tooltip)
+        self.actionFindNext.setToolTip(QCoreApplication.translate("MainWindow", u"Find the next occurrence of the phrase", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+        self.actionFindNext.setShortcut(QCoreApplication.translate("MainWindow", u"F3", None))
+#endif // QT_CONFIG(shortcut)
+        self.actionFindPrevious.setText(QCoreApplication.translate("MainWindow", u"Find Previous", None))
+#if QT_CONFIG(tooltip)
+        self.actionFindPrevious.setToolTip(QCoreApplication.translate("MainWindow", u"Find the previous occurrence of the phrase", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(shortcut)
+        self.actionFindPrevious.setShortcut(QCoreApplication.translate("MainWindow", u"Shift+F3", None))
+#endif // QT_CONFIG(shortcut)
         self.tabWidget.setTabText(self.tabWidget.indexOf(self.bookmarkTab), QCoreApplication.translate("MainWindow", u"Bookmarks", None))
         self.tabWidget.setTabText(self.tabWidget.indexOf(self.pagesTab), QCoreApplication.translate("MainWindow", u"Pages", None))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.searchResultsTab), QCoreApplication.translate("MainWindow", u"Search Results", None))
         self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"File", None))
         self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None))
         self.menuView.setTitle(QCoreApplication.translate("MainWindow", u"View", None))
+        self.searchToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None))
     # retranslateUi
 
index 3c34234fd8362be2ab5921648af1f5af56a365c9..525c377c716214463d5bbb77665df0aae17de60b 100644 (file)
@@ -1,9 +1,8 @@
 // Copyright (C) 2022 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
-import QtQuick
-
 import People
+import QtQuick  // For QColor
 
 BirthdayParty {
     host: Boy {
@@ -15,7 +14,8 @@ BirthdayParty {
         name: "Leo Hodges"
         shoe { size: 10; color: "black"; brand: "Thebok"; price: 59.95 }
     }
-    Boy { name: "Jack Smith"
+    Boy {
+        name: "Jack Smith"
         shoe {
             size: 8
             color: "blue"
@@ -28,6 +28,6 @@ BirthdayParty {
         shoe.size: 7
         shoe.color: "red"
         shoe.brand: "Job Macobs"
-        shoe.price: 699.99
+        shoe.price: 99.99
     }
 }
index 795d638673af02d11c6a0b13a669afea920204ef..4fedadbc55db965b722f52f9dd04b9545aa9978c 100644 (file)
@@ -2,21 +2,22 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
 import People
+import QtQuick  // For QColor
 
 BirthdayParty {
     Boy {
         name: "Robert Campbell"
-        BirthdayParty.rsvp: "2009-07-01"
+        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-01", "yyyy-MM-dd")
     }
 
     Boy {
         name: "Leo Hodges"
-        shoe_size: 10
-        BirthdayParty.rsvp: "2009-07-06"
+        shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }
+        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-03", "yyyy-MM-dd")
     }
 
     host: Boy {
         name: "Jack Smith"
-        shoe_size: 8
+        shoe { size: 8; color: "blue"; brand: "Puma"; price: 19.95 }
     }
 }
index 8deb7d0bfbe1a385ed1e82c8f2ed60341abfb137..db3b8d5bd01765b43c6634517b306843e01ef60a 100644 (file)
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 from PySide6.QtCore import QObject, Property, Signal
+from PySide6.QtGui import QColor
 from PySide6.QtQml import QmlAnonymous, QmlElement
 
 # To be used on the @QmlElement decorator
@@ -11,15 +12,69 @@ QML_IMPORT_NAME = "People"
 QML_IMPORT_MAJOR_VERSION = 1
 
 
+@QmlAnonymous
+class ShoeDescription(QObject):
+    brand_changed = Signal()
+    size_changed = Signal()
+    price_changed = Signal()
+    color_changed = Signal()
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._brand = ''
+        self._size = 0
+        self._price = 0
+        self._color = QColor()
+
+    @Property(str, notify=brand_changed, final=True)
+    def brand(self):
+        return self._brand
+
+    @brand.setter
+    def brand(self, b):
+        if self._brand != b:
+            self._brand = b
+            self.brand_changed.emit()
+
+    @Property(int, notify=size_changed, final=True)
+    def size(self):
+        return self._size
+
+    @size.setter
+    def size(self, s):
+        if self._size != s:
+            self._size = s
+            self.size_changed.emit()
+
+    @Property(float, notify=price_changed, final=True)
+    def price(self):
+        return self._price
+
+    @price.setter
+    def price(self, p):
+        if self._price != p:
+            self._price = p
+            self.price_changed.emit()
+
+    @Property(QColor, notify=color_changed, final=True)
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, c):
+        if self._color != c:
+            self._color = c
+            self.color_changed.emit()
+
+
 @QmlAnonymous
 class Person(QObject):
     name_changed = Signal()
-    shoe_size_changed = Signal()
 
     def __init__(self, parent=None):
         super().__init__(parent)
         self._name = ''
-        self._shoe_size = 0
+        self._shoe = ShoeDescription()
 
     @Property(str, notify=name_changed, final=True)
     def name(self):
@@ -31,13 +86,9 @@ class Person(QObject):
             self._name = n
             self.name_changed.emit()
 
-    @Property(int, notify=shoe_size_changed, final=True)
-    def shoe_size(self):
-        return self._shoe_size
-
-    @shoe_size.setter
-    def shoe_size(self, s):
-        self._shoe_size = s
+    @Property(ShoeDescription, final=True)
+    def shoe(self):
+        return self._shoe
 
 
 @QmlElement
index 254265a8018ac4f090a9c1aaa315593d71f230d4..db0d4613a05b937bb66f6fc86390bc15baa2eac5 100644 (file)
@@ -2,26 +2,37 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
 import People
+import QtQuick  // For QColor
 
 BirthdayParty {
-    HappyBirthdaySong on announcement { name: "Bob Jones" }
+    id: party
+    HappyBirthdaySong on announcement {
+        name: party.host.name
+    }
 
     onPartyStarted: (time) => { console.log("This party started rockin' at " + time); }
 
+
     host: Boy {
         name: "Bob Jones"
-        shoe_size: 12
+        shoe { size: 12; color: "white"; brand: "Nike"; price: 90.0 }
     }
 
     Boy {
         name: "Leo Hodges"
-        BirthdayParty.rsvp: "2009-07-06"
+        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-01", "yyyy-MM-dd")
+        shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }
     }
     Boy {
         name: "Jack Smith"
+        shoe { size: 8; color: "blue"; brand: "Puma"; price: 19.95 }
     }
     Girl {
         name: "Anne Brown"
-        BirthdayParty.rsvp: "2009-07-01"
+        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-03", "yyyy-MM-dd")
+        shoe.size: 7
+        shoe.color: "red"
+        shoe.brand: "Marc Jacobs"
+        shoe.price: 99.99
     }
 }
index 8deb7d0bfbe1a385ed1e82c8f2ed60341abfb137..db3b8d5bd01765b43c6634517b306843e01ef60a 100644 (file)
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 from PySide6.QtCore import QObject, Property, Signal
+from PySide6.QtGui import QColor
 from PySide6.QtQml import QmlAnonymous, QmlElement
 
 # To be used on the @QmlElement decorator
@@ -11,15 +12,69 @@ QML_IMPORT_NAME = "People"
 QML_IMPORT_MAJOR_VERSION = 1
 
 
+@QmlAnonymous
+class ShoeDescription(QObject):
+    brand_changed = Signal()
+    size_changed = Signal()
+    price_changed = Signal()
+    color_changed = Signal()
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._brand = ''
+        self._size = 0
+        self._price = 0
+        self._color = QColor()
+
+    @Property(str, notify=brand_changed, final=True)
+    def brand(self):
+        return self._brand
+
+    @brand.setter
+    def brand(self, b):
+        if self._brand != b:
+            self._brand = b
+            self.brand_changed.emit()
+
+    @Property(int, notify=size_changed, final=True)
+    def size(self):
+        return self._size
+
+    @size.setter
+    def size(self, s):
+        if self._size != s:
+            self._size = s
+            self.size_changed.emit()
+
+    @Property(float, notify=price_changed, final=True)
+    def price(self):
+        return self._price
+
+    @price.setter
+    def price(self, p):
+        if self._price != p:
+            self._price = p
+            self.price_changed.emit()
+
+    @Property(QColor, notify=color_changed, final=True)
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, c):
+        if self._color != c:
+            self._color = c
+            self.color_changed.emit()
+
+
 @QmlAnonymous
 class Person(QObject):
     name_changed = Signal()
-    shoe_size_changed = Signal()
 
     def __init__(self, parent=None):
         super().__init__(parent)
         self._name = ''
-        self._shoe_size = 0
+        self._shoe = ShoeDescription()
 
     @Property(str, notify=name_changed, final=True)
     def name(self):
@@ -31,13 +86,9 @@ class Person(QObject):
             self._name = n
             self.name_changed.emit()
 
-    @Property(int, notify=shoe_size_changed, final=True)
-    def shoe_size(self):
-        return self._shoe_size
-
-    @shoe_size.setter
-    def shoe_size(self, s):
-        self._shoe_size = s
+    @Property(ShoeDescription, final=True)
+    def shoe(self):
+        return self._shoe
 
 
 @QmlElement
diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/App.qml b/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/App.qml
new file mode 100644 (file)
index 0000000..523dc71
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import Charts
+import QtQuick
+
+Item {
+    width: 300; height: 200
+
+    PieChart {
+        id: aPieChart
+        anchors.centerIn: parent
+        width: 100; height: 100
+        name: "A simple pie chart"
+        color: "red"
+    }
+
+    Text {
+        anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
+        text: aPieChart.name
+    }
+}
diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir
new file mode 100644 (file)
index 0000000..78602c6
--- /dev/null
@@ -0,0 +1,4 @@
+module Charts
+typeinfo chapter1-basics.qmltypes
+depends QtQuick
+App 254.0 App.qml
diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/app.qml b/examples/qml/tutorials/extending-qml/chapter1-basics/app.qml
deleted file mode 100644 (file)
index 6feef56..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import Charts
-import QtQuick
-
-Item {
-    width: 300; height: 200
-
-    PieChart {
-        id: aPieChart
-        anchors.centerIn: parent
-        width: 100; height: 100
-        name: "A simple pie chart"
-        color: "red"
-    }
-
-    Text {
-        anchors {
-            bottom: parent.bottom;
-            horizontalCenter: parent.horizontalCenter;
-            bottomMargin: 20
-        }
-        text: aPieChart.name
-    }
-}
index 322bef954c6e25dcd9dcce3c37899c52c93c8536..08fa91c1458ca510addf63b28a8869487283f249 100644 (file)
@@ -4,11 +4,10 @@ from __future__ import annotations
 
 """PySide6 port of the qml/tutorials/extending-qml/chapter1-basics example from Qt v5.x"""
 
-import os
 from pathlib import Path
 import sys
 
-from PySide6.QtCore import Property, Signal, QUrl
+from PySide6.QtCore import Property, Signal
 from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor
 from PySide6.QtQml import QmlElement
 from PySide6.QtQuick import QQuickPaintedItem, QQuickView
@@ -57,8 +56,8 @@ if __name__ == '__main__':
 
     view = QQuickView()
     view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
-    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
-    view.setSource(QUrl.fromLocalFile(qml_file))
+    view.engine().addImportPath(Path(__file__).parent)
+    view.loadFromModule("Charts", "App")
     if view.status() == QQuickView.Status.Error:
         sys.exit(-1)
     view.show()
index 869556bb8bfb7d4c007fb4d26d8f664971df5e33..2207b83447450645976eb8c5ade42ff033b98677 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "files": ["basics.py", "app.qml"]
+    "files": ["basics.py", "Charts/App.qml", "Charts/qmldir"]
 }
index d9bc18cc26f41b8c15a604ce5d6de529117dd2d8..4963b44642d8cb77151fb3a150f857eb1217c082 100644 (file)
@@ -72,19 +72,19 @@ drawing operations with the ``QPainter`` API, we can just subclass
 The ``PieChart`` class defines the two properties, ``name`` and ``color``, with
 the ``Property`` decorator, and overrides ``QQuickPaintedItem.paint()``. The
 ``PieChart`` class is registered using the :deco:`~PySide6.QtQml.QmlElement`
-decorator, to allow it to be used from QML. If you don't register the class, ``app.qml``
+decorator, to allow it to be used from QML. If you don't register the class, ``App.qml``
 won't be able to create a ``PieChart``.
 
 QML Usage
 ---------
 
 Now that we have defined the ``PieChart`` type, we will use it from QML. The
-``app.qml`` file creates a ``PieChart`` item and displays the pie chart's details
+``App.qml`` file creates a ``PieChart`` item and displays the pie chart's details
 using a standard QML ``Text`` item:
 
-.. literalinclude:: app.qml
+.. literalinclude:: Charts/App.qml
     :lineno-start: 7
-    :lines: 7-26
+    :lines: 7-22
 
 Notice that although the color is specified as a string in QML, it is
 automatically converted to a :class:`~PySide6.QtGui.QColor` object for the PieChart
@@ -93,7 +93,7 @@ For example, a string like "640x480" can be automatically converted to a
 ``QSize`` value.
 
 We'll also create a main function that uses a :class:`~PySide6.QtQuick.QQuickView`
-to run and display ``app.qml``. Here is the application ``basics.py``:
+to run and display ``App.qml``. Here is the application ``basics.py``:
 
 .. literalinclude:: basics.py
     :lineno-start: 54
diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/App.qml b/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/App.qml
new file mode 100644 (file)
index 0000000..6190cbc
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import Charts
+import QtQuick
+
+Item {
+    width: 300; height: 200
+
+    PieChart {
+        id: aPieChart
+        anchors.centerIn: parent
+        width: 100; height: 100
+        color: "red"
+
+        onChartCleared: console.log("The chart has been cleared")
+    }
+
+    MouseArea {
+        anchors.fill: parent
+        onClicked: aPieChart.clearChart()
+    }
+
+    Text {
+        anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
+        text: "Click anywhere to clear the chart"
+    }
+}
diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir
new file mode 100644 (file)
index 0000000..dad5378
--- /dev/null
@@ -0,0 +1,4 @@
+module Charts
+typeinfo chapter2-methods.qmltypes
+depends QtQuick
+App 254.0 App.qml
diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/app.qml b/examples/qml/tutorials/extending-qml/chapter2-methods/app.qml
deleted file mode 100644 (file)
index d9477e2..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import Charts
-import QtQuick
-
-Item {
-    width: 300; height: 200
-
-    PieChart {
-        id: aPieChart
-        anchors.centerIn: parent
-        width: 100; height: 100
-        color: "red"
-
-        onChartCleared: console.log("The chart has been cleared")
-    }
-
-    MouseArea {
-        anchors.fill: parent
-        onClicked: aPieChart.clearChart()
-    }
-
-    Text {
-        anchors {
-            bottom: parent.bottom;
-            horizontalCenter: parent.horizontalCenter;
-            bottomMargin: 20
-        }
-        text: "Click anywhere to clear the chart"
-    }
-}
index cdf33be7f82a7f59cd488d302001e15e176d4bc2..b0942a27b75793d1ee7101c8eb549257e39ec1da 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "files": ["methods.py", "app.qml"]
+    "files": ["methods.py", "Charts/App.qml", "Charts/qmldir"]
 }
index c192fb9b64010b1c0d1b0ee582c3f91462d59469..0d8538b8b438116d5e9b585ad06c5c860f2bcb9e 100644 (file)
@@ -5,12 +5,12 @@ This is the second of a series of 6 examples forming a tutorial about extending
 QML with Python.
 
 Suppose we want ``PieChart`` to have a ``clearChart()`` method that erases the
-chart and then emits a ``chartCleared`` signal. Our ``app.qml`` would be able
+chart and then emits a ``chartCleared`` signal. Our ``App.qml`` would be able
 to call ``clearChart()`` and receive ``chartCleared()`` signals like this:
 
-.. literalinclude:: app.qml
+.. literalinclude:: Charts/App.qml
     :lineno-start: 4
-    :lines: 4-32
+    :lines: 4-28
 
 To do this, we add a ``clearChart()``  method and a ``chartCleared()``  signal
 to our C++ class:
index 238225fd3e3579c3827bf227b7ef51596e3c6c68..02f600d1bcd186c729017008be0852a24369d3df 100644 (file)
@@ -4,11 +4,10 @@ from __future__ import annotations
 
 """PySide6 port of the qml/tutorials/extending-qml/chapter2-methods example from Qt v5.x"""
 
-import os
 from pathlib import Path
 import sys
 
-from PySide6.QtCore import Property, Signal, Slot, Qt, QUrl
+from PySide6.QtCore import Property, Signal, Slot, Qt
 from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor
 from PySide6.QtQml import QmlElement
 from PySide6.QtQuick import QQuickPaintedItem, QQuickView
@@ -64,8 +63,8 @@ if __name__ == '__main__':
 
     view = QQuickView()
     view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
-    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
-    view.setSource(QUrl.fromLocalFile(qml_file))
+    view.engine().addImportPath(Path(__file__).parent)
+    view.loadFromModule("Charts", "App")
     if view.status() == QQuickView.Status.Error:
         sys.exit(-1)
     view.show()
diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/App.qml b/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/App.qml
new file mode 100644 (file)
index 0000000..0e30ba6
--- /dev/null
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import Charts
+import QtQuick
+
+Item {
+    width: 300; height: 200
+
+    Row {
+        anchors.centerIn: parent
+        spacing: 20
+
+        PieChart {
+            id: chartA
+            width: 100; height: 100
+            color: "red"
+        }
+
+        PieChart {
+            id: chartB
+            width: 100; height: 100
+            color: chartA.color
+        }
+    }
+
+    MouseArea {
+        anchors.fill: parent
+        onClicked: { chartA.color = "blue" }
+    }
+
+    Text {
+        anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 }
+        text: "Click anywhere to change the chart color"
+    }
+}
diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir
new file mode 100644 (file)
index 0000000..f2d39dd
--- /dev/null
@@ -0,0 +1,4 @@
+module Charts
+typeinfo chapter3-bindings.qmltypes
+depends QtQuick
+App 254.0 App.qml
diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/app.qml b/examples/qml/tutorials/extending-qml/chapter3-bindings/app.qml
deleted file mode 100644 (file)
index f153051..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import Charts
-import QtQuick
-
-Item {
-    width: 300; height: 200
-
-    Row {
-        anchors.centerIn: parent
-        spacing: 20
-
-        PieChart {
-            id: chartA
-            width: 100; height: 100
-            color: "red"
-        }
-
-        PieChart {
-            id: chartB
-            width: 100; height: 100
-            color: chartA.color
-        }
-    }
-
-    MouseArea {
-        anchors.fill: parent
-        onClicked: { chartA.color = "blue" }
-    }
-
-    Text {
-        anchors {
-            bottom: parent.bottom;
-            horizontalCenter: parent.horizontalCenter;
-            bottomMargin: 20
-        }
-        text: "Click anywhere to change the chart color"
-    }
-}
index ed332cba7524eba1ecb65ebb5d91918ca41c4863..a3fa77ed9a4b204d80322978bd5240e060be4221 100644 (file)
@@ -4,11 +4,10 @@ from __future__ import annotations
 
 """PySide6 port of the qml/tutorials/extending-qml/chapter3-bindings example from Qt v5.x"""
 
-import os
 from pathlib import Path
 import sys
 
-from PySide6.QtCore import Property, Signal, Slot, QUrl, Qt
+from PySide6.QtCore import Property, Signal, Slot, Qt
 from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor
 from PySide6.QtQml import QmlElement
 from PySide6.QtQuick import QQuickPaintedItem, QQuickView
@@ -68,8 +67,8 @@ if __name__ == '__main__':
 
     view = QQuickView()
     view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
-    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
-    view.setSource(QUrl.fromLocalFile(qml_file))
+    view.engine().addImportPath(Path(__file__).parent)
+    view.loadFromModule("Charts", "App")
     if view.status() == QQuickView.Status.Error:
         sys.exit(-1)
     view.show()
index 6e21f86f90704acdcd97aaa3ba6d6e7da2712d89..ebd65a02781e8a8d605f2d2a8ebfe5b544c957df 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "files": ["app.qml", "bindings.py"]
+    "files": ["bindings.py", "Charts/App.qml", "Charts/qmldir"]
 }
index 8d29c07ad6a30505351aa3bcb5ef583b48142ffb..cba65e2d052eac333a9dc0bdc41a75f51cb5040a 100644 (file)
@@ -11,9 +11,9 @@ other types' values when property values are changed.
 Let's enable property bindings for the ``color`` property. That means if we
 have code like this:
 
-.. literalinclude:: app.qml
+.. literalinclude:: Charts/App.qml
     :lineno-start: 7
-    :lines: 7-40
+    :lines: 7-36
 
 The ``color: chartA.color`` statement binds the ``color`` value of ``chartB``
 to the ``color`` of ``chartA.`` Whenever ``chartA`` 's ``color`` value changes,
diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/App.qml b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/App.qml
new file mode 100644 (file)
index 0000000..eb0a3cd
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import Charts
+import QtQuick
+
+Item {
+    width: 300; height: 200
+
+    PieChart {
+        id: chart
+        anchors.centerIn: parent
+        width: 100; height: 100
+
+        pieSlice: PieSlice {
+            anchors.fill: parent
+            color: "red"
+        }
+    }
+
+    Component.onCompleted: console.log("The pie is colored " + chart.pieSlice.color)
+}
diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir
new file mode 100644 (file)
index 0000000..7a7a418
--- /dev/null
@@ -0,0 +1,4 @@
+module Charts
+typeinfo chapter4-customPropertyTypes.qmltypes
+depends QtQuick
+App 254.0 App.qml
diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/app.qml b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/app.qml
deleted file mode 100644 (file)
index a5c5ff9..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import Charts
-import QtQuick
-
-Item {
-    width: 300; height: 200
-
-    PieChart {
-        id: chart
-        anchors.centerIn: parent
-        width: 100; height: 100
-
-        pieSlice: PieSlice {
-            anchors.fill: parent
-            color: "red"
-        }
-    }
-
-    Component.onCompleted: console.log("The pie is colored " + chart.pieSlice.color)
-}
index af1cfefb7a58db772843f15566e93a4889ce31b4..076f3a825007daf79ae892a9cfa5c8fd2dafb90a 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "files": ["app.qml", "customPropertyTypes.py"]
+    "files": ["customPropertyTypes.py", "Charts/App.qml", "Charts/qmldir"]
 }
index bf24ec3c3ab0e2de87c1f8684742e9dd61844c0b..2d03b5cb20574d141945e30f5590e6471c50809f 100644 (file)
@@ -5,11 +5,10 @@ from __future__ import annotations
 """PySide6 port of the qml/tutorials/extending-qml/chapter4-customPropertyTypes example
    from Qt v5.x"""
 
-import os
 from pathlib import Path
 import sys
 
-from PySide6.QtCore import Property, QUrl
+from PySide6.QtCore import Property
 from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor
 from PySide6.QtQml import QmlElement
 from PySide6.QtQuick import QQuickPaintedItem, QQuickView, QQuickItem
@@ -72,8 +71,8 @@ if __name__ == '__main__':
 
     view = QQuickView()
     view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
-    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
-    view.setSource(QUrl.fromLocalFile(qml_file))
+    view.engine().addImportPath(Path(__file__).parent)
+    view.loadFromModule("Charts", "App")
     if view.status() == QQuickView.Status.Error:
         sys.exit(-1)
     view.show()
index 394f8261175188b6755875aae5c62c97ff148728..2a3638647fad03409712799df0d2c757868a4223 100644 (file)
@@ -41,7 +41,7 @@ For example, let's replace the use of the ``property`` with a type called
 ``PieSlice`` that has a ``color`` property. Instead of assigning a color,
 we assign an ``PieSlice`` value which itself contains a ``color``:
 
-.. literalinclude:: app.qml
+.. literalinclude:: Charts/App.qml
     :lineno-start: 4
     :lines: 4-22
 
diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/App.qml b/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/App.qml
new file mode 100644 (file)
index 0000000..c0c3e82
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+import Charts
+import QtQuick
+
+Item {
+    width: 300; height: 200
+
+    PieChart {
+        id: chart
+        anchors.centerIn: parent
+        width: 100; height: 100
+
+        component Slice: PieSlice {
+            parent: chart
+            anchors.fill: parent
+        }
+
+        slices: [
+            Slice {
+                color: "red"
+                fromAngle: 0
+                angleSpan: 110
+            },
+            Slice {
+                color: "black"
+                fromAngle: 110
+                angleSpan: 50
+            },
+            Slice {
+                color: "blue"
+                fromAngle: 160
+                angleSpan: 100
+            }
+        ]
+    }
+}
diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir
new file mode 100644 (file)
index 0000000..48ec243
--- /dev/null
@@ -0,0 +1,4 @@
+module Charts
+typeinfo chapter5-listproperties.qmltypes
+depends QtQuick
+App 254.0 App.qml
diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/app.qml b/examples/qml/tutorials/extending-qml/chapter5-listproperties/app.qml
deleted file mode 100644 (file)
index ac99d5a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import Charts
-import QtQuick
-
-Item {
-    width: 300; height: 200
-
-    PieChart {
-        anchors.centerIn: parent
-        width: 100; height: 100
-
-        slices: [
-            PieSlice {
-                anchors.fill: parent
-                color: "red"
-                fromAngle: 0; angleSpan: 110
-            },
-            PieSlice {
-                anchors.fill: parent
-                color: "black"
-                fromAngle: 110; angleSpan: 50
-            },
-            PieSlice {
-                anchors.fill: parent
-                color: "blue"
-                fromAngle: 160; angleSpan: 100
-            }
-        ]
-    }
-}
index a3f89d57532d78d6494fcad538c92a63d6920f21..d726432f0dc8b22ee9f7ed9067958e62d04248ff 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "files": ["app.qml", "listproperties.py"]
+    "files": ["listproperties.py", "Charts/App.qml", "Charts/qmldir"]
 }
index ece79e9d6dd8ec6eb800928393353e58ce5e3c92..980024ee2794021dcb3530980d2bc7d18a2c5859 100644 (file)
@@ -8,7 +8,7 @@ Right now, a ``PieChart`` can only have one ``PieSlice.`` Ideally a chart would
 have multiple slices, with different colors and sizes. To do this, we could
 have a ``slices`` property that accepts a list of ``PieSlice`` items:
 
-.. literalinclude:: app.qml
+.. literalinclude:: Charts/App.qml
    :lineno-start: 4
    :lines: 4-32
 
index 67d7482ec932446e293fc9de610a23d667573072..c2ea85ff40615486b3a15e8c2a7402b3c71ff5b8 100644 (file)
@@ -4,11 +4,10 @@ from __future__ import annotations
 
 """PySide6 port of the qml/tutorials/extending-qml/chapter5-listproperties example from Qt v5.x"""
 
-import os
 from pathlib import Path
 import sys
 
-from PySide6.QtCore import Property, QUrl
+from PySide6.QtCore import Property
 from PySide6.QtGui import QGuiApplication, QPen, QPainter, QColor
 from PySide6.QtQml import QmlElement, ListProperty
 from PySide6.QtQuick import QQuickPaintedItem, QQuickView, QQuickItem
@@ -86,8 +85,8 @@ if __name__ == '__main__':
 
     view = QQuickView()
     view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
-    qml_file = os.fspath(Path(__file__).resolve().parent / 'app.qml')
-    view.setSource(QUrl.fromLocalFile(qml_file))
+    view.engine().addImportPath(Path(__file__).parent)
+    view.loadFromModule("Charts", "App")
     if view.status() == QQuickView.Status.Error:
         sys.exit(-1)
     view.show()
diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/App.qml b/examples/qml/tutorials/extending-qml/chapter6-plugins/App.qml
new file mode 100644 (file)
index 0000000..79a9ce9
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+pragma ComponentBehavior: Bound
+import QtQuick
+import Charts
+
+Item {
+    width: 300; height: 200
+
+    PieChart {
+        id: chart
+        anchors.centerIn: parent
+        width: 100; height: 100
+
+        component Slice: PieSlice {
+            parent: chart
+            anchors.fill: parent
+        }
+
+        slices: [
+            Slice {
+                color: "red"
+                fromAngle: 0
+                angleSpan: 110
+            },
+            Slice {
+                color: "black"
+                fromAngle: 110
+                angleSpan: 50
+            },
+            Slice {
+                color: "blue"
+                fromAngle: 160
+                angleSpan: 100
+            }
+        ]
+    }
+}
+
diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png b/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png
deleted file mode 100644 (file)
index 8992e89..0000000
Binary files a/examples/qml/tutorials/extending-qml/chapter6-plugins/Charts/plugins.png and /dev/null differ
diff --git a/examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml b/examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml
deleted file mode 100644 (file)
index 1a4772e..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import Charts 1.0
-
-Item {
-    width: 300; height: 200
-
-    PieChart {
-        anchors.centerIn: parent
-        width: 100; height: 100
-
-        slices: [
-            PieSlice {
-                anchors.fill: parent
-                color: "red"
-                fromAngle: 0; angleSpan: 110
-            },
-            PieSlice {
-                anchors.fill: parent
-                color: "black"
-                fromAngle: 110; angleSpan: 50
-            },
-            PieSlice {
-                anchors.fill: parent
-                color: "blue"
-                fromAngle: 160; angleSpan: 100
-            }
-        ]
-    }
-}
index cc684401fa9f23166c96fce2430d0d69d3fe59d1..c4d3f4ce66226154df9066f12d1d06d25b0f6db1 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "files": ["app.qml", "Charts/piechart.py", "Charts/pieslice.py"]
+    "files": ["App.qml", "Charts/piechart.py", "Charts/pieslice.py"]
 }
index 2320a8fa1193ead03b1eb4fb0ce409e3e8f1ce95..3677a71f5f5505d33cc4635c551e6bca8ac181b3 100644 (file)
@@ -21,4 +21,4 @@ Running the Example
 
 .. code-block:: shell
 
-    pyside6-qml examples/qml/tutorials/extending-qml/chapter6-plugins/app.qml -I examples/qml/tutorials/extending-qml/chapter6-plugins/Charts
+    pyside6-qml examples/qml/tutorials/extending-qml/chapter6-plugins/App.qml -I examples/qml/tutorials/extending-qml/chapter6-plugins/Charts
diff --git a/examples/quick/customitems/painteditem/TextBalloon/textballoon.py b/examples/quick/customitems/painteditem/TextBalloon/textballoon.py
new file mode 100644 (file)
index 0000000..7ff2a7d
--- /dev/null
@@ -0,0 +1,56 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtGui import QPainter, QBrush, QColor
+from PySide6.QtQml import QmlElement
+from PySide6.QtCore import QPointF, Qt, Property, Signal
+from PySide6.QtQuick import QQuickPaintedItem
+
+QML_IMPORT_NAME = "TextBalloon"
+QML_IMPORT_MAJOR_VERSION = 1
+QML_IMPORT_MINOR_VERSION = 0  # Optional
+
+
+@QmlElement
+class TextBalloon(QQuickPaintedItem):
+
+    rightAlignedChanged = Signal()
+
+    def __init__(self, parent=None):
+        self._rightAligned = False
+        super().__init__(parent)
+
+    @Property(bool, notify=rightAlignedChanged)
+    def rightAligned(self):
+        return self._rightAligned
+
+    @rightAligned.setter
+    def rightAligned(self, value):
+        self._rightAligned = value
+        self.rightAlignedChanged.emit()
+
+    def paint(self, painter: QPainter):
+
+        brush = QBrush(QColor("#007430"))
+
+        painter.setBrush(brush)
+        painter.setPen(Qt.PenStyle.NoPen)
+        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
+
+        itemSize = self.size()
+
+        painter.drawRoundedRect(0, 0, itemSize.width(), itemSize.height() - 10, 10, 10)
+
+        if self.rightAligned:
+            points = [
+                QPointF(itemSize.width() - 10.0, itemSize.height() - 10.0),
+                QPointF(itemSize.width() - 20.0, itemSize.height()),
+                QPointF(itemSize.width() - 30.0, itemSize.height() - 10.0),
+            ]
+        else:
+            points = [
+                QPointF(10.0, itemSize.height() - 10.0),
+                QPointF(20.0, itemSize.height()),
+                QPointF(30.0, itemSize.height() - 10.0),
+            ]
+        painter.drawConvexPolygon(points)
diff --git a/examples/quick/customitems/painteditem/doc/painteditem.png b/examples/quick/customitems/painteditem/doc/painteditem.png
new file mode 100644 (file)
index 0000000..743cd11
Binary files /dev/null and b/examples/quick/customitems/painteditem/doc/painteditem.png differ
diff --git a/examples/quick/customitems/painteditem/doc/painteditem.rst b/examples/quick/customitems/painteditem/doc/painteditem.rst
new file mode 100644 (file)
index 0000000..5c3e893
--- /dev/null
@@ -0,0 +1,13 @@
+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
+implement custom scenegraph items using QPainter.
+
+.. image:: painteditem.png
+   :width: 400
+   :alt: Painted Item Screenshot
diff --git a/examples/quick/customitems/painteditem/main.py b/examples/quick/customitems/painteditem/main.py
new file mode 100644 (file)
index 0000000..fa846b6
--- /dev/null
@@ -0,0 +1,39 @@
+# 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 QGuiApplication
+from PySide6.QtCore import QCoreApplication
+from PySide6.QtQml import QQmlDebuggingEnabler
+from PySide6.QtQuick import QQuickView
+
+from TextBalloon.textballoon import TextBalloon  # noqa: F401
+
+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 = QGuiApplication(sys.argv)
+    QCoreApplication.setOrganizationName("QtProject")
+    QCoreApplication.setOrganizationDomain("qt-project.org")
+
+    view = QQuickView()
+    view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
+    view.engine().addImportPath(Path(__file__).parent)
+    view.loadFromModule("painteditemexample", "Main")
+
+    if view.status() == QQuickView.Status.Error:
+        sys.exit(-1)
+    view.show()
+
+    exit_code = QCoreApplication.exec()
+    del view
+    sys.exit(exit_code)
diff --git a/examples/quick/customitems/painteditem/painteditem.pyproject b/examples/quick/customitems/painteditem/painteditem.pyproject
new file mode 100644 (file)
index 0000000..ffe340e
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "files": ["main.py", "painteditemexample/Main.qml", "painteditemexample/qmldir",
+              "TextBalloon/textballoon.py"]
+}
diff --git a/examples/quick/customitems/painteditem/painteditemexample/Main.qml b/examples/quick/customitems/painteditem/painteditemexample/Main.qml
new file mode 100644 (file)
index 0000000..69be4a0
--- /dev/null
@@ -0,0 +1,72 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import TextBalloon
+
+Item {
+    height: 480
+    width: 320
+
+    //! [0]
+    ListModel {
+        id: balloonModel
+        ListElement {
+            balloonWidth: 200
+        }
+        ListElement {
+            balloonWidth: 120
+        }
+    }
+
+    ListView {
+        id: balloonView
+        anchors.bottom: controls.top
+        anchors.bottomMargin: 2
+        anchors.top: parent.top
+        delegate: TextBalloon {
+            anchors.right: index % 2 !== 0 ? parent?.right : undefined
+            height: 60
+            rightAligned: index % 2 !== 0
+            width: balloonWidth
+        }
+        model: balloonModel
+        spacing: 5
+        width: parent.width
+    }
+    //! [0]
+
+    //! [1]
+    Rectangle {
+        id: controls
+
+        anchors.bottom: parent.bottom
+        anchors.left: parent.left
+        anchors.margins: 1
+        anchors.right: parent.right
+        border.width: 2
+        color: "white"
+        height: parent.height * 0.15
+
+        Text {
+            anchors.centerIn: parent
+            text: qsTr("Add another balloon")
+        }
+
+        MouseArea {
+            anchors.fill: parent
+            hoverEnabled: true
+            onClicked: {
+                balloonModel.append({"balloonWidth": Math.floor(Math.random() * 200 + 100)})
+                balloonView.positionViewAtIndex(balloonView.count -1, ListView.End)
+            }
+            onEntered: {
+                parent.color = "#8ac953"
+            }
+            onExited: {
+                parent.color = "white"
+            }
+        }
+    }
+    //! [1]
+}
diff --git a/examples/quick/customitems/painteditem/painteditemexample/qmldir b/examples/quick/customitems/painteditem/painteditemexample/qmldir
new file mode 100644 (file)
index 0000000..b33630b
--- /dev/null
@@ -0,0 +1,2 @@
+module painteditemexample
+Main 1.0 Main.qml
index b7cf68a9b83f523696628f9183d804efeb5bf84b..3cb072dd376825bf3429715d638b6d5a6af4b1cc 100644 (file)
@@ -2,14 +2,20 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
 import QtQuick
+import QtQuick.Controls
 
 ListView {
-    width: 100; height: 100
+    id: listview
+    width: 200; height: 320
+    required model
+    ScrollBar.vertical: ScrollBar { }
 
     delegate: Rectangle {
-        color: model.modelData.color
-        height: 25
-        width: 100
-        Text { text: model.modelData.name }
+        width: listview.width; height: 25
+
+        required color
+        required property string name
+
+        Text { text: parent.name }
     }
 }
diff --git a/examples/quick/painteditem/doc/painteditem.png b/examples/quick/painteditem/doc/painteditem.png
deleted file mode 100644 (file)
index 743cd11..0000000
Binary files a/examples/quick/painteditem/doc/painteditem.png and /dev/null differ
diff --git a/examples/quick/painteditem/doc/painteditem.rst b/examples/quick/painteditem/doc/painteditem.rst
deleted file mode 100644 (file)
index 5c3e893..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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
-implement custom scenegraph items using QPainter.
-
-.. image:: painteditem.png
-   :width: 400
-   :alt: Painted Item Screenshot
diff --git a/examples/quick/painteditem/main.qml b/examples/quick/painteditem/main.qml
deleted file mode 100644 (file)
index 44be89d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2020 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import TextBalloonPlugin
-
-Item {
-    height: 480
-    width: 320
-
-    //! [0]
-    ListModel {
-        id: balloonModel
-        ListElement {
-            balloonWidth: 200
-        }
-        ListElement {
-            balloonWidth: 120
-        }
-    }
-
-    ListView {
-        anchors.bottom: controls.top
-        anchors.bottomMargin: 2
-        anchors.top: parent.top
-        id: balloonView
-        delegate: TextBalloon {
-            anchors.right: index % 2 == 0 ? undefined : balloonView.contentItem.right
-            height: 60
-            rightAligned: index % 2 == 0 ? false : true
-            width: balloonWidth
-        }
-        model: balloonModel
-        spacing: 5
-        width: parent.width
-    }
-    //! [0]
-
-    //! [1]
-    Rectangle {
-        id: controls
-
-        anchors.bottom: parent.bottom
-        anchors.left: parent.left
-        anchors.margins: 1
-        anchors.right: parent.right
-        border.width: 2
-        color: "white"
-        height: parent.height * 0.15
-
-        Text {
-            anchors.centerIn: parent
-            text: "Add another balloon"
-        }
-
-        MouseArea {
-            anchors.fill: parent
-            hoverEnabled: true
-            onClicked: {
-                balloonModel.append({
-                                        "balloonWidth": Math.floor(
-                                                            Math.random(
-                                                                ) * 200 + 100)
-                                    })
-                balloonView.positionViewAtIndex(balloonView.count - 1,
-                                                ListView.End)
-            }
-            onEntered: {
-                parent.color = "#8ac953"
-            }
-            onExited: {
-                parent.color = "white"
-            }
-        }
-    }
-    //! [1]
-}
diff --git a/examples/quick/painteditem/painteditem.py b/examples/quick/painteditem/painteditem.py
deleted file mode 100644 (file)
index 95390b6..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-from argparse import ArgumentParser, RawTextHelpFormatter
-from pathlib import Path
-import sys
-
-from PySide6.QtGui import QPainter, QBrush, QColor
-from PySide6.QtWidgets import QApplication
-from PySide6.QtQml import QmlElement, QQmlDebuggingEnabler
-from PySide6.QtCore import QUrl, Property, Signal, Qt, QPointF
-from PySide6.QtQuick import QQuickPaintedItem, QQuickView
-
-QML_IMPORT_NAME = "TextBalloonPlugin"
-QML_IMPORT_MAJOR_VERSION = 1
-QML_IMPORT_MINOR_VERSION = 0  # Optional
-
-
-@QmlElement
-class TextBalloon(QQuickPaintedItem):
-
-    rightAlignedChanged = Signal()
-
-    def __init__(self, parent=None):
-        self._rightAligned = False
-        super().__init__(parent)
-
-    @Property(bool, notify=rightAlignedChanged)
-    def rightAligned(self):
-        return self._rightAligned
-
-    @rightAligned.setter
-    def rightAligned(self, value):
-        self._rightAligned = value
-        self.rightAlignedChanged.emit()
-
-    def paint(self, painter: QPainter):
-
-        brush = QBrush(QColor("#007430"))
-
-        painter.setBrush(brush)
-        painter.setPen(Qt.PenStyle.NoPen)
-        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
-
-        itemSize = self.size()
-
-        painter.drawRoundedRect(0, 0, itemSize.width(), itemSize.height() - 10, 10, 10)
-
-        if self.rightAligned:
-            points = [
-                QPointF(itemSize.width() - 10.0, itemSize.height() - 10.0),
-                QPointF(itemSize.width() - 20.0, itemSize.height()),
-                QPointF(itemSize.width() - 30.0, itemSize.height() - 10.0),
-            ]
-        else:
-            points = [
-                QPointF(10.0, itemSize.height() - 10.0),
-                QPointF(20.0, itemSize.height()),
-                QPointF(30.0, itemSize.height() - 10.0),
-            ]
-        painter.drawConvexPolygon(points)
-
-
-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.ResizeMode.SizeRootObjectToView)
-    qml_file = Path(__file__).parent / "main.qml"
-    view.setSource(QUrl.fromLocalFile(qml_file))
-
-    if view.status() == QQuickView.Status.Error:
-        sys.exit(-1)
-    view.show()
-
-    sys.exit(app.exec())
diff --git a/examples/quick/painteditem/painteditem.pyproject b/examples/quick/painteditem/painteditem.pyproject
deleted file mode 100644 (file)
index 0597c2a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-    "files": ["main.qml", "painteditem.py"]
-}
index 00f6a81e903a8967123e2ba76ded49b261b1292f..ca6ba7b2b49003ad63aeb62cabebef77301d0ded 100644 (file)
@@ -2,13 +2,14 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
 import QtQuick
-import QtQuick.Particles 2.0
+import QtQuick.Particles
 
 Rectangle {
     id: root
+    property bool keyDown: false
 
     gradient: Gradient {
-        GradientStop { position: 0; color: mouse.pressed ? "lightsteelblue" : "steelblue" }
+        GradientStop { position: 0; color: mouse.pressed ? "lightsteelblue" : (root.keyDown ? "blue" : "steelblue") }
         GradientStop { position: 1; color: "black" }
     }
 
@@ -158,4 +159,7 @@ Rectangle {
         id: mouse
         anchors.fill: parent
     }
+
+    Keys.onPressed: keyDown = true
+    Keys.onReleased: keyDown = false
 }
index 73bfa326225edc190018ab04d92e718698fe26af..5d4e51f76b3a1808d49831e75b5bd0c01423ebc0 100644 (file)
@@ -30,7 +30,7 @@ Item {
         id: label
         color: "black"
         wrapMode: Text.WordWrap
-        text: "The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML"
+        text: qsTr("The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML")
         anchors.right: parent.right
         anchors.left: parent.left
         anchors.bottom: parent.bottom
index 88431a176ead825641be4d46afdc48d2889ab2c0..044302428edb1a77006cb36e7cd8e39b9c72c368 100644 (file)
@@ -22,13 +22,13 @@ Item {
         p2: Qt.point(t, 1 - t)
         p3: Qt.point(1 - t, t)
     }
-
     Text {
         anchors.bottom: line.bottom
+
         x: 20
         width: parent.width - 40
         wrapMode: Text.WordWrap
 
-        text: "This curve is a custom scene graph item, implemented using GL_LINE_STRIP"
+        text: qsTr("This curve is a custom scene graph item, implemented using line strips")
     }
 }
index 0a7da219dbea07ff1ef776b01a0c6742e34fdbee..6989425548dea2356ba58d57916f3201b690e915 100644 (file)
@@ -24,19 +24,20 @@ Window {
     Image {
         id: splashImage
         source: Images.qtLogo
-        MouseArea {
-            anchors.fill: parent
-            onClicked: Qt.quit()
+        TapHandler {
+            onTapped: splash.exit()
         }
     }
+
+    function exit() {
+        splash.visible = false
+        splash.timeout()
+    }
+
     //! [timer]
     Timer {
-        interval: splash.timeoutInterval; running: true; repeat: false
-        onTriggered: {
-            splash.visible = false
-            splash.timeout()
-        }
+        interval: splash.timeoutInterval; running: splash.visible; repeat: false
+        onTriggered: splash.exit()
     }
     //! [timer]
-    Component.onCompleted: visible = true
 }
index 30b1fbf0ae28e4527ce7153dbea6868860164906..13dd34325ec91062c7022752adfd19a7b5a3cb50 100644 (file)
 # Resource object code (Python 3)
 # Created by: object code
-# Created by: The Resource Compiler for Qt version 6.5.0
+# Created by: The Resource Compiler for Qt version 6.10.0
 # WARNING! All changes made in this file will be lost!
 
 from PySide6 import QtCore
 
 qt_resource_data = b"\
-\x00\x00\x05\x12\
+\x00\x00\x04\xe8\
 /\
 / Copyright (C) \
 2021 The Qt Comp\
-any Ltd.\x0d\x0a// SPD\
-X-License-Identi\
-fier: LicenseRef\
--Qt-Commercial O\
-R BSD-3-Clause\x0d\x0a\
-\x0d\x0aimport QtQuick\
-\x0d\x0aimport QtQuick\
-.Controls\x0d\x0a\x0d\x0aCol\
-umn {\x0d\x0a    id: r\
-oot\x0d\x0a    spacing\
-: 8\x0d\x0a\x0d\x0a    Label\
- {\x0d\x0a        text\
-: \x22Total number \
-of screens: \x22 + \
-screenInfo.count\
-\x0d\x0a        font.b\
-old: true\x0d\x0a    }\
-\x0d\x0a\x0d\x0a    Flow {\x0d\x0a\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ai\
+mport QtQuick\x0aim\
+port QtQuick.Con\
+trols\x0a\x0aColumn {\x0a\
+    id: root\x0a   \
+ spacing: 8\x0a\x0a   \
+ Label {\x0a       \
+ text: \x22Total nu\
+mber of screens:\
+ \x22 + screenInfo.\
+count\x0a        fo\
+nt.bold: true\x0a  \
+  }\x0a\x0a    Flow {\x0a\
         spacing:\
- 12\x0d\x0a        wid\
-th: parent.width\
-\x0d\x0a\x0d\x0a        Repe\
-ater {\x0d\x0a        \
-    id: screenIn\
-fo\x0d\x0a            \
-model: (Qt.appli\
+ 12\x0a        widt\
+h: parent.width\x0a\
+\x0a        Repeate\
+r {\x0a            \
+id: screenInfo\x0a \
+           model\
+: (Qt.applicatio\
+n as Application\
+).screens\x0a      \
+      Label {\x0a  \
+              re\
+quired property \
+string name\x0a    \
+            requ\
+ired property in\
+t virtualX\x0a     \
+           requi\
+red property int\
+ virtualY\x0a      \
+          requir\
+ed property var \
+modelData // avo\
+id shadowing Lab\
+el.width and hei\
+ght\x0a\x0a           \
+     lineHeight:\
+ 1.5\x0a           \
+     text: name \
++ \x22\x5cn\x22 + virtual\
+X + \x22, \x22 + virtu\
+alY + \x22 \x22 + mode\
+lData.width + \x22x\
+\x22 + modelData.he\
+ight\x0a           \
+ }\x0a        }\x0a   \
+ }\x0a\x0a    Componen\
+t.onCompleted: {\
+\x0a        var scr\
+eens = (Qt.appli\
 cation as Applic\
-ation).screens\x0d\x0a\
-            Labe\
-l {\x0d\x0a           \
-     required pr\
-operty string na\
-me\x0d\x0a            \
-    required pro\
-perty int virtua\
-lX\x0d\x0a            \
-    required pro\
-perty int virtua\
-lY\x0d\x0a            \
-    required pro\
-perty var modelD\
-ata // avoid sha\
-dowing Label.wid\
-th and height\x0d\x0a\x0d\
-\x0a               \
- lineHeight: 1.5\
-\x0d\x0a              \
-  text: name + \x22\
-\x5cn\x22 + virtualX +\
- \x22, \x22 + virtualY\
- + \x22 \x22 + modelDa\
-ta.width + \x22x\x22 +\
- modelData.heigh\
-t\x0d\x0a            }\
-\x0d\x0a        }\x0d\x0a   \
- }\x0d\x0a\x0d\x0a    Compon\
-ent.onCompleted:\
- {\x0d\x0a        var \
-screens = (Qt.ap\
-plication as App\
-lication).screen\
-s;\x0d\x0a        for \
-(var i = 0; i < \
-screens.length; \
-++i)\x0d\x0a          \
-  console.log(\x22s\
-creen \x22 + screen\
-s[i].name + \x22 ha\
-s geometry \x22 +\x0d\x0a\
-                \
-        screens[\
-i].virtualX + \x22,\
+ation).screens;\x0a\
+        for (var\
+ i = 0; i < scre\
+ens.length; ++i)\
+\x0a            con\
+sole.log(\x22screen\
  \x22 + screens[i].\
-virtualY + \x22 \x22 +\
-\x0d\x0a              \
-          screen\
-s[i].width + \x22x\x22\
- + screens[i].he\
-ight)\x0d\x0a    }\x0d\x0a}\x0d\
-\x0a\
-\x00\x00\x04\x8a\
-\x00\
-\x00\x16\xa7x\xda\xcdXYo\xdbF\x10~7\xe0\xff\
-0a_\xec\x06\xba\x93\x17\x15Fa+H\x1d\xc0A\
-b\xcb\xa8\x03\x14}\xa0\xc9\x91\xb8\xf5\x8a+\xec.-\
-9\xae\xff{\x87\xc7\x92\x94\xb8$\xe5ZnJ\x08\x10\
-\xb9;;\xc77\x07g\xd8\xeb\xc1D,\x1f$\x9b\x07\
-\x1a\x8e&\xc70\xec\x0f\x07p\x1d \x5cj\xdaY,\
-\xdd\xf0\x01.\xb4\xdf=<\xe8\xf5`\xfa\xf5\xc3\xb7\xce\
-\x05\xf30T\xd8\xf9\xe4c\xa8\xd9\x8c\xa1\x1cC\xb6v\
-\x85\xb3\xce\xa5\xee\xd0\xb9\x05J\x8f\xb9\x1c\xbe\x5c\xc1\xd9\
-\xf4Cg\xd4\x99p7Rxxpx\xc0\x16K!\
-5\xf1\xbf\x8c\x98w\xb7\xfd\xdc\x9d\x88PK\xc1UL\
-z\xa9\xbf\xdc\xfe\x85\x9e\x86\xc7\xc3\x03\xa0\x8b\xf9c\x90\
-B\xe8\xf4i)\xc5\x12\xa5~\x00\x89$\xc9\xc7\x99\x1b\
-q=]\xba\x1e\x0b\xe7c\x18\xf4\xb7\xa8\xa6\x0fJ\xe3\
-\xe2\xab\xcbQk\x84e\xfa?\xdeZ~\x84\xa7X\xf0\
-\xc6\xc1{W\x82\x97ju\xc3B_\xac\xc6\x90\xfe\x1b\
-\xb5\xe2k\xc5|\x1d\x8c\x89\x8ew\xc9 \xce<\xa6o\
-\xe2%x\x9bh\xdc\xddT\x0f~\x86aq6\xc0\x18\
-\xfe\xcd\xc3\xe7\xc9\xda.\xa7\xe9\x94\x90).\xdd\xcc\xaa\
-\xee*\xd1\xaf\xa0\xd1Ls\xb2\xd5\xc9\xc0\xcd\xf4w\x0a\
-\x82\x89\xe0\xd1\x22,\x1bd\xf0&\xf6\x9b\x8bn\xe8\x05\
-B\xaa\xee\x8cq>&\x1c%\x85\x81\x9db\xe1\xca9\
-\x0b\xd5\xd8f\xc3\xe6\x09e\xbc\xd6J\xb9\xe9t\x0f9\
-\xbf)\x90O\x9c\x00=\x18A\xc7\xb0\xdc<}\xe1\xde\
-\x22''k\x5c\xeb\x12\x1c\x9a\xe2]!\xf9\xd8\x87\x14\
-\xb9\xb1\x13\xc7A\xf9\xe4o\x92\xf9\xdb\xf0\x18\x88\xe6\xb4\
-W\xdd\xf1\x12H\xc9\xfaQuow{K\xb1\x95B\
-\x9d\x1aY%:\x8b\xb4\x16\xa1ME\xa3\xa6\x0a\xc4*\
-\xa5\xb2\xd3\x94B8\xc7\xd5N\x99\xc2\x97\xe8\xaeQ\xe9\
-4\x9a\xba\xf7L\xb1[\x8e\xf0+8\xe7\xccG\x07\x08\
-\xe1i\xb0\x11f\xe5K\x84\x13\x8a\xf4;\xf4\xeb9\x9d\
-\xc0\x9b\x9a\xad*\xcb\xa7\xeaR\xaf\xf7\x06\xfeH\x1d\x8a\
-\xfe$@\xef\xeeV\xac\xff\xac\xd2%[gb]\x07\
-^\x16-7\x19\xa7\x1a{L\x16\x170w\xd3\xa5\x97\
-\xa2}Fr\xe3\xac'\xefz\xb1\xa6H\x81HE\x89\
-GX\x83\x1c\xe3\x8c\x12\xe4\xe4\xe4$\xcb\xf3\xae\xd1\xdc\
-\x06\xd2.\xae\xc8\x18n\xb3\xfbO\x9d\xf0j\xf0f\xde\
-\xfd\x18q\x0eSO\x22\x86\xcek\xf9!\x96\x91\x8a\xd8\
-\x93'\x0a\x86;\xf9\xa2\xbdF\xb8\x91\x16\xfb\xac\x11\xce\
-)1\x5c\xb8\x9ay\xce^\x0c\xce\xd9\xfd\x9eo\xeed\
-\xf9\xae\xd1U\xd8\xdf\x18]\x99q\x9fY\xc8\x16\xec{\
-mIxy\xc4\xe4\x12\xf6\x1409\xbf\x1f\x89\x9a\xbb\
-~m\xd4\x8c\x84}\xa1f\xf8\xb5\xa2\xb6\xf58\x8bB\
-O32\xa4\xe0z-\xa6Z\x92yG\xf7\xc76X\
-\xd5\x8ai/\x80\x9a]\xcfU\xb8]\x85\xc7v\x13%\
-\xeaH\x86\xe0\x98\xd2\xeb\xfc\xd2\xcc.\x8f\x8c\x16~\x8b\
-<\xe6\xdb\x18\x1a\xd0\xda\x18\xe6\xe1\xd0\xc2\xb0(v-\
-\x1cgD\xa8\xd2J\xde\xc2\xd2RNZx\xbby=\
-kaM-\x90\xdf\xaai\x90\x10\xd9XY\x02\xd7\x1c\
-\x8a\xc2\xbbP\xac*\xa7\x9e\xac\xad\xae\xbdc-\xc21\
-!\xab\x12e\xa9\xba\xd1\x14\x03S\xe0\xd0Dr\xd4\xd0\
-\xf8e\xb7I\xef\xc7B\xf3t\x0co\xed8\x001t\
-I@\xe0\xaa\x92N\x89\x94\xac\xdb\xb5$N}\xc6\x1e\
-7\x22rE\x93\xa4\x1b\xceI\xd1Gk\xb7^\x99\xa2\
-b\x10\xfee7n*\xe3\xa0Q\xa3I$c&Y\
-K\xf0\xf8\xffW\xf8\xd4d\xa0\x22m-|\xcb\xf4\xd9\
-\xad}\x9e.\xdc\xd70L\x8f\x86\xfd\xea\x90<|\xd7\
-\xaf\xcc\xbe\xceO\xc3\xc1\xfbw\xfd\xbeS\x9dx\xafI\
-\x90\x91@\xa55H\x8f$\x11\x96\xdc\x15'f\xdc\x9d\
-\xd3\xb4v\xa9\xb3\xca\x0a\x7f\x17\xf7\x1f\xf3\x8a\x92\xbe\xe6\
-\xceYy\xe6\xad\xf5\xd3k\x8c\xca\xb5ymXy$\
-\x04\xe5\xa7\xd0.\xb0\x94\xdc\xd34\xb9+\xdf\x02,~\
-\xff,\x22\x85\xa74r7\x09\xae\xb7\xb2\xe5\x95\x9b\xba\
-\xe4\x84\xdc\x88}o4\xc0f]\xea\x9bX\xa3\x88L\
-#%\x0bLioH\x0c\xb1\x16\xcb\x9c\x94\xee\xeb\x09\
-wwQ\xcb\x88\xdc4\x16P\x09\xc55\xd3Pz\x85\
-\xc5\xa5t.\xca+\xb5)>x\xdfo\x04\xbe\xae\xf1\
-\x9f\xc1\xd1s5=\xae+\xe8\xb0\xbf>>\xbe\x90+\
-|\x99\xa8g\xccH\xcf\x8e4\x8e\xb3\x22\xd0\xe2\x87\x1f\
-\x14g\xce\xb7\xfa\xa0\x18\xf5\x9f\x97\x8c\x5c(<\xaa\x7f\
-\x936\x96u\xb5\xe4\xae\x0aLa\x9f&Oe\xfcD\
-x\xcd\x16(\x22\x93\x1a\x1b\x9fUK\x9f}\xb4\x8c0\
-\x97C\xbf\x7f\x00\xe8k\x05\xe6\
-\x00\x00\x04$\
+name + \x22 has geo\
+metry \x22 +\x0a      \
+                \
+  screens[i].vir\
+tualX + \x22, \x22 + s\
+creens[i].virtua\
+lY + \x22 \x22 +\x0a     \
+                \
+   screens[i].wi\
+dth + \x22x\x22 + scre\
+ens[i].height)\x0a \
+   }\x0a}\x0a\
+\x00\x00\x05\x01\
+(\
+\xb5/\xfd`\xff\x16\xbd'\x00\x06p\x912`\x8d:\
+0\x0c\xc3\xc0\x161\x1c\x00\x98G\xc1\xb5\xb6\x93$\xda\
+IN\x11E,I\x22\xd5#\xdck\xe3\x5c\x9d/Y\
+\xc2BlU\xb2\xcdF\x1e\x12Xb\x833\xad\x1c\x83\
+\x00~\x00\x85\x00\x15\xc9\xe1\xec\x8b\xb7\xd4\x8d&Lf\
++\xd1\xec\xe70\xcf\xcb\xcc\x85\x18\xaetL\xb7\xf4\x94\
+1\xa7\xd8\xbb\xd3\xad`\xef\xeeN \xfb\x8f7@\x13\
+\xdf\x99\xa6*\x9d\xfet\x8b\xe2x\x9du\xbe\xb3O\x1d\
+\xbbz\x9a\xb8fNZ\x96\x8d\xa5\xc1\xc3\xb0\x15\x13+\
+]?\xca\x97\xe5;\x96\x10WlE\xb9C\xfd\x93!\
+\x84\x85\xeb\xe4\xd7\x11\x16\x8c\x06\x0a\xcfD\xc1\x8f\xd8\xd2\
+t\xd8\xe5\xd6~g9~6\xa3\xc1h\xb6bQ\xf6\
+\xa3\xa8i\x09sk/\x97\x1c\x8f\xa3\x059c\x01.\
+\x17\x11\x80M\x80\xcc\xf1ZZ\xd3\xa3\xa7m\xb5\x8da\
+s\xda*,KP\xd7\x1eQ_\xf9I\x1e\xab\xffg\
+\x0c\x9b'\x82M\x1fD\xb6\x91\x16N\x7f\x5c\x97\xe3\xbf\
+\x07\xff\x82\xacd\x90\x8b\xa8k\xd9\xcd^\xf6BJ~\
+\xb5\xaa\xb3E\xd4]?\x99J\xd9!\xaaR\x8fo\xba\
+ye\xd2\xad\xd6\xb6b\xf9\xfd$\x8a\xffY:l+\
+\xea\x1c\x8e\x9a\x15Xj`/\xbe_K\xecjJ\xfa\
+\xdb*>l\xd2\x06_\xf9\xd30\x98\x02\x83\x8c&\xa0\
+\x15h0\x9a\x8d\x08\xc8\xd6\x05\x10\x9aY\x19\x96\xa2\xe9\
+\xfeqE\xa5\xba\xb8p\xbam\x03[\x99M\xd9\x81m\
+[{I>9f\x16!l\x8e\xab\xa1\x0b\xa1\x91\xda\
+N\x9a=\xd3\x8a\xab{\x1b\xdf7\xa9VF\x14\xd1l\
+\xfa\x1e\xff\xec\xf4\xfe7\xb3\x90\x87\x83\xfc\xd1W\xcf\x96\
+\xb0h\xaf\xdf]\xfe\xd9B\xf3\xcf\x5c\x9b\xcc\xf4yV\
+\xca\xcca\x13\x12\x12\xb6\xf4-\x9b3\xe9F\x1c\xdfN\
+\x87\xb3\x19\xa8\x0b\x0fg\xd0\xf0pP\x93H\xe6|\xb5\
+'V'\xb1Z;\xfeV(\x5c]\xbe\x91\x92\x10\xcb\
+\xad\xa8\x7fe\xda(\xf4S;VA\xba\x11\xe5\x9f\x10\
+P:\x07\xce\x85Z\x99Rp\xcd\x9ce\xe9k\xc9I\
+\x86\x90)\x8f<b\xfd\x91\xee\x9ffBrR)p\
+F\x1f\xe40\xa74\xb4\xc9\x14\x1cP\x98\x9d\x084{\
+\xd9\x0a\xe5r8\x1d\xa5\xe2;\x89\x1dX\x03\x0d\x85\xa1\
+8\x90\x88&`\xd3)K8\x1d\x07s\x1f\xcf\x84\xa0\
+\xfc\x93\xdf\xf4\xb39\x0e!\xd4w\xb6\xaa\x22'C\xf1\
+\x03\x81 \xa8\x11\xa6\x19\x1a\x11\x19IR\x94\x0c\x07A\
+\x08\x820\x14uV\x9a\x07Rx$IC\x18\xc3 \
+\x14\x8d\x10)`\x06\x11D\x08\x99HH\x82\x0bd\x92\
+4\xd5\x01\xfd\x86\xe3\x00;U\xd9[%\xb0+\xa0H\
+Y\x22\xa8\xc45\x02LtZ\xa2\x1f\xf5yiy\xf2\
+1\xe7\xffg\xce\x8eR\xb6\x88\xc1\xb6\x88m\xaf\xd4)\
+\xd7\xa7\x00\xb5\x15Y,,MT\xd3\x84\xcc\xb2\xd8\xca\
+\xa0\xd5&S\x9b&\x97\x05\xed\x06\x92\xfc]\xe6-i\
+q\x1ckF\x85\xbc(\x08\xa3\x9c\xbd\x00\xb3~1Z\
+\xf8\xd0\xa6d\xb1\x8a\x8f\xd7{\x9b\x97/=\xf0F\xf8\
+\xb4L\xefw\xce\xcd\xd0\xdb\xe9\xc9\xc7\xa8\x08\xdd\x97\xb9\
+\xfc\x035B\xc9\xfa\xcdKA\x83\xd7)\x8e\xdf\xb0\xd4\
+\x04F\xe2\xe7\xca\xcf<v\xa5D\xa0\xbd\xc9x\x87E\
+R\xb3@\x8cTv\x10\x04+\xc8\xd8\xc35\x07r\x18\
+VCyf\xa6\x08q\x03\x10\x07<\xefF\xbd\xff\xf9\
+\xff\x84\x9d\xfb\xa7\x88\xff\x0c\x0d\xed\xed\xb4\xd8\xa2\xe22\
+\x10\xac\xd3PA\xe2\xf8\x17\x13;4\x9e\xfd1\x92\x11\
+\xb5\xf6\x228t\xd6\x8c&\x87\xaf\xd0\xc3\xc3\xba\xa6\xa1\
+\xb8\xb9N\xb9/\xf7\x05~\x22\xa5d,\xc1b\xd1#\
+%U\x05\xfe\xc3\x14T\xbc0\xc9\xe7\x80PZ\xea_\
+\xdd\x9eO@1\xf8\x10.\xf7\x8c\x1fe\xc0\xacHQ\
+<\xb6\x026\xee\xcb\x81] 4\x0e%\xbe,\x0f\x87\
+Jk\x91XN9\xb1\x14$\xa0\xad\xd8I\xc1Uy\
+S\xb4\xae\xd4F\xf7X3\x92\x9e!E\xca$\xe7x\
+\xaazGx\x0eG(\x8e\x0cmij\x0aL\xd3\xfd\
+\xc0\x0a\xe4\xf3BKz\xa02G\xf6\x10\xca\x04D\x1b\
+\xc9\x86\xe0\xa7^\xb5Dz@\xcd\x9c\xfa\xd3-\x97\x10\
+a5\xb1\xba\x97D\x86\xb1\x0f\xea\x0c\x95\xf4tU\x1f\
+\x93\xf7\xacR\x9c\xb6\x00\x83\xf4\xefK\xa5\xf8]\x97\x80\
+'\xa2\x04\x17\xeeu\xf43\x1f\x81\xe0C\xee\xf4\xf7\x85\
+7\xda17\x00\x1a\x14]\xdb\xb6\xad\x1c\x0d8\x12q\
+\x9c:\x0c1\x22\xf3\xd1\x09Vz\x916\x01h\xd9\x88\
+\x8b\xbf\xb5\xa3\xb84\xe2\xcd\xe7b\xf1\xac\xad\x9cR\x96\
+\xe3\x11\x97\x92\x93\xa3\xc1\x87\xf2:\xf2y\x0b\xb0\xca\xbd\
+\xdd \xc7t?D\xaf\x16-\x05a\xd2\xbd9 \xb3\
+\x9fRI\xaf\xbc\x99\x1cC\x0eK?\x9e\xdc\x86\xc3$\
+\xfe)\xab-\x01v\xbb\x1e\x8e\xdd\xfbm\x12\xc8\x1b\xf5\
+\xce\x1fym\xd8\x10\x0e\xe5\xd5w\xd1\x05m\x14`\xd5\
+\x0bAo)\xb0c,\x81'H\x81.xsAP\
+=&\xe5\xa0Z\xe0\x828\xa6\xdc\xde\xa0\xeb\x8e\xb1F\
+\xcb\xc0D6_,\x04\xa8y\xe0\xdf\x95\x9f\xfb\xe1\x97\
+\xa3I\xa6\x1f#k)\xf4\xaf\x03m\xd7\xc8AP\x12\
+]\x154_\xbd\x1cU\x0dcv\xb8\xbf\x1a\xc0\xa8\x02\
+\
+\x00\x00\x03\xd5\
 /\
 / Copyright (C) \
 2021 The Qt Comp\
-any Ltd.\x0d\x0a// SPD\
-X-License-Identi\
-fier: LicenseRef\
--Qt-Commercial O\
-R BSD-3-Clause\x0d\x0a\
-\x0d\x0aimport QtQuick\
-\x0d\x0aimport shared\x0d\
-\x0a\x0d\x0a//! [splash-p\
-roperties]\x0d\x0aWind\
-ow {\x0d\x0a    id: sp\
-lash\x0d\x0a    color:\
- \x22transparent\x22\x0d\x0a\
-    title: \x22Spla\
-sh Window\x22\x0d\x0a    \
-modality: Qt.App\
-licationModal\x0d\x0a \
-   flags: Qt.Spl\
-ashScreen\x0d\x0a    p\
-roperty int time\
-outInterval: 200\
-0\x0d\x0a    signal ti\
-meout\x0d\x0a//! [spla\
-sh-properties]\x0d\x0a\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ai\
+mport QtQuick\x0aim\
+port shared\x0a\x0a//!\
+ [splash-propert\
+ies]\x0aWindow {\x0a  \
+  id: splash\x0a   \
+ color: \x22transpa\
+rent\x22\x0a    title:\
+ \x22Splash Window\x22\
+\x0a    modality: Q\
+t.ApplicationMod\
+al\x0a    flags: Qt\
+.SplashScreen\x0a  \
+  property int t\
+imeoutInterval: \
+2000\x0a    signal \
+timeout\x0a//! [spl\
+ash-properties]\x0a\
 //! [screen-prop\
-erties]\x0d\x0a    x: \
-(Screen.width - \
-splashImage.widt\
-h) / 2\x0d\x0a    y: (\
-Screen.height - \
-splashImage.heig\
-ht) / 2\x0d\x0a//! [sc\
-reen-properties]\
-\x0d\x0a    width: spl\
-ashImage.width\x0d\x0a\
-    height: spla\
-shImage.height\x0d\x0a\
-\x0d\x0a    Image {\x0d\x0a \
-       id: splas\
-hImage\x0d\x0a        \
-source: Images.q\
-tLogo\x0d\x0a        M\
-ouseArea {\x0d\x0a    \
-        anchors.\
-fill: parent\x0d\x0a  \
-          onClic\
-ked: Qt.quit()\x0d\x0a\
-        }\x0d\x0a    }\
-\x0d\x0a    //! [timer\
-]\x0d\x0a    Timer {\x0d\x0a\
-        interval\
-: splash.timeout\
-Interval; runnin\
-g: true; repeat:\
- false\x0d\x0a        \
-onTriggered: {\x0d\x0a\
-            spla\
-sh.visible = fal\
-se\x0d\x0a            \
-splash.timeout()\
-\x0d\x0a        }\x0d\x0a   \
- }\x0d\x0a    //! [tim\
-er]\x0d\x0a    Compone\
-nt.onCompleted: \
-visible = true\x0d\x0a\
-}\x0d\x0a\
-\x00\x00\x03\x08\
-\x00\
-\x00\x0a\xb7x\xda\xadV\xdfo\xda0\x10~G\xe2\x7f\
-\xb8\xe5\x09\xb6\x91\x00\xeb\xa6)}\x986\xaam\x95\xd0\
-\xdaB\xa5uZ\xf7`\x92\x03\xac&v\xe48\x144\
-\xf5\x7f\x9f\x9d\x1f\x8d\x01\x0f\x82\xb6\x08\x89\xdc\xf9\xbb\xfb\
->\xdbg_<\x0fF<\xd9\x08\xbaXJ\xe8\x8c\xba\
-0\xec\x0f\x07p\xbbD\xb8\x91j$N\x08\xdb\xc0X\
-\x86n\xbb\xe5y0\xbd\xbe\xb8\xeb\x8di\x80,\xc5\xde\
-e\x88L\xd29E\xe1C\xe9\x9b\xe0\xbcw#{*\
-.F\x11P\x12\xc1\xd5\x04>M/zoz\xa3\x88\
-d)\xb6[\xed\x16\x8d\x13.\xa4\xca\x7f\x93\xd1\xe0a\
-\xd7vG\x9cI\xc1\xa3TC/%\xc6\xf0\xbb\xdd\x02\
-\xf5\xd0\xd0\x07\xc1\xb9,\xacG\x1a\xca\xa5\x0fg\xfd~\
-a/Q\xcf\xc0\x87D\xf0\x04\x85\xdc|\x114tU\
-\xe6\x88\x06T~\xcd\x07\xe1\x15\x0c\xde\xe9\xac\x1a?\xcf\
-X )g\xc0\x05U\xd3 \xfa\xfd\x96O\xa5\xa0l\
-\xd1\xe1\xdd\x8aT?\xe9#\x95\xc1\x12v\xbc\x01I\xf5\
-\x1a\xb9\xd7\x82\xc6Dl\xae\xea4~\x0d\xd2\x8f@\x99\
-\x09\x06NR\x00\x9dsK\x0e5\x7fA\xa8l\x90\xa4\
-D\xda\xb2\x8c\x09\x0b\xd3\x80$x<MTAmy\
-.\xd9J\xad \x86\xcdU\xd12\x02\x0e\xc9\xab\xd2\x9e\
- \xf39\xafU\xefS\xfdZ\x05d\xec\x81\xf1GV\
-\xa1\x9e\xaa\xdd\xd6\xd5`n\x9e\xae%\xb3R\x0c\xb5<\
-\xcab\x96\xfa04\x0a !\x81*\x0b\x1f\xde\xd7\xbe\
-\xb5_\xb9k\xdf\xc6\xf0\xd5^\xcf{\x01?\xd3@ \
-\xb2_\xb5wLf\x18\x99\x92\xf4#q\xadJ\xd8\x99\
-\xe6`\xb8w\x1cU\xb2\x85\xe12\x12\xa3\xb2\x9c{\xc7\
-w\xb6\x83\xe6\xea\xbc\xb83\x1e\xa9)I\x91\xa1u}\
-\x8asT\x1d\x9a\xc1\xf9\xf3q\x19\xc0\x93\x12\x98\xcbF\
-a\xaa.\xf5U\x92b\xc2\xb29\x09\xd4*\xa3p\xcc\
-\xcc\xdb\xb8R\xac\x89\x86\x0fV\xaf_oV\xbdM6\
-b\x1eb\xd4\x80Q\xc3\x0c\xaa\xdcl\xca\x91\xa2\xd0W\
-\x15\xcb\xe2Y\x93\xd9\x15\xf0o9\xba\xa6\xdc\xf26e\
-\x0ei\xac\xaeMu\x00\xd2\xe3\xb4\xf9\xe6\xe9\x12X\x1b\
-eQ\xec\xe3A\x8e\x84\xae\x95\x19j\x1e\xb99N\x93\
-\xc3/\x0a\xb4+\xf9ge\x86\x9daW\x13C\xc8e\
-\xea\xc51t\xb4\x82\x8e%\x00^\xc2\xf0\xad{\xd6\xb5\
-\x06R\x16,\xbb\x87\xd7#\xe2\x0b\x1a\xa8\xad8Qs\
-\x19v}\xaatK\xdc\xbf\xce \xc4\x95j\x85\xe5\x04\
-\x84\xbe\xdc\x8e\xcb/br\x15\x13\x1dar\x1f\xe2\x22\
-+B#2\x8b\x10VT\xc8\x8c\xe8\x15K\x1f$O\
-\x9aP\xe6\xc0\x8fU\x86\xef\xb6\xe2\xda\x05}mPl\
-\x5c-\xa2n\xab\x94\x9d.\xaa\x0c\xb8\xd3:^\x83!\
-\xa4\x1c\xf8q\x90\xdah\xe4\x07\xa8\xf4\x17\x84k\xeb\xf9\
-%\x951Tl|\xc7\xd0a\x0c\xea\xb1#\xa5P6\
-|\xf8?\xc2\x92\xbd\xef\x8c}}\xfb\x98Z\xa6\xbd\x1d\
-\xfdU\xfc\xa0\xaf\x0e\x8b\xc0@\x12\xb6\x88p+\xc3\xa4\
-\xf2\xee\xb6/\xd5;\xb9\xfa\x16t\x04\x86;=\xaal\
-<\xf6\x1bc\xd0\xdf\x06W\xbd)\x0f\xdakg\xeaO\
-\xfd\xfe\x00\x1e\x91\x1e\xa9\
+erties]\x0a    x: (\
+Screen.width - s\
+plashImage.width\
+) / 2\x0a    y: (Sc\
+reen.height - sp\
+lashImage.height\
+) / 2\x0a//! [scree\
+n-properties]\x0a  \
+  width: splashI\
+mage.width\x0a    h\
+eight: splashIma\
+ge.height\x0a\x0a    I\
+mage {\x0a        i\
+d: splashImage\x0a \
+       source: I\
+mages.qtLogo\x0a   \
+     TapHandler \
+{\x0a            on\
+Tapped: splash.e\
+xit()\x0a        }\x0a\
+    }\x0a\x0a    funct\
+ion exit() {\x0a   \
+     splash.visi\
+ble = false\x0a    \
+    splash.timeo\
+ut()\x0a    }\x0a\x0a    \
+//! [timer]\x0a    \
+Timer {\x0a        \
+interval: splash\
+.timeoutInterval\
+; running: splas\
+h.visible; repea\
+t: false\x0a       \
+ onTriggered: sp\
+lash.exit()\x0a    \
+}\x0a    //! [timer\
+]\x0a}\x0a\
+\x00\x00\x03\x03\
+(\
+\xb5/\xfd`d\x09\xcd\x17\x00fbg\x1f\x00\xdb6\
+\x8e\x19\x94\xa4Zd_\xd9sQ1d\xb4\x22\x92\x81\
+P\xe2a+\xcaH\xcc\xa5\xff\xed?\x94`\x00\x5c\x00\
+^\x00\xddtf\xe6\x0c\xc6^?&\xcb@j\xefW\
+;\xd9V\xd2\xc3\xcew\xf8\xaf\xfd<\xcf\xb2\x06\xc3H\
+'\x06\x85\x81\xf2\x91\xb0g\xc34\xba\xbc\xed\x19\xbec\
+@\xb5\x87\xbb\xdb\x8e\xf2\xaf=1\x86\x06kK\xcf\x18\
+D\x14\xce'\xdb\xe6g\xf8U\xe3\xe7\xd9\x09\x7f\x87[\
+\x05bD\xe2\x90`\xc1$\xc0\xafJ\xf9}}v\xc2\
+\x1c\x17\xb6\xaej\x98\xc5\xd8g\xffmV\xc5\xd0\xfe\xa2\
+\x04g\x8d\x99\xf7x\xff\xda\x87\xf6i\x5co\xf8}9\
+\xeb6\xaf\x1b=\xbf\x9c\x0d\x8c\x92U\xf2\xbd\x05\x0al\
+ U\xfd\xfe\xc7\x82\xe2\xd7\xf6k\xb9Z\x7fYCT\
+\xd6@0\x81/\xef\xd2\xe96\xfdZ^\x7f\xfb\xb3\xf7\
+\xe3\xaeU\x87\xc3\x82;\xd5Oo\xbd\xb5\x97\x03\xb5=\
+\x9f\x9a\x93\x17V\xa6\xd1o\x83\xb4\xa7\x8dq\x07\xe1\x18\
+k\xb7o\xf82\xa2\xc2\x1a\xbd\xec6\xc7q\x8c\x11\x0f\
+D\x87\xf7\xb9\x9b\xd4\x9e\xb6\xaf\x16\xa5uZIc\x02\
+\xb7\x17\xeb\xc6\x04\xd6\xb4\x12\x7f4\xee\xde\xd5O\xfb\x14\
+3\xa6\xda}%Hb\x00\xc7\xbd:\x97\x83\xbbTu\
+\x19\xbf\x04w`\xf5Bj\xa7\xc7j+Y\xec\x80\x98\
+}\xaf\xf54M\xd5\xf8\xb2/{\xde\x01\xb1\xe1\xed\xad\
+\xbd#.\x88\xaa;h/\xa1\xc8g\x7f\x05\xc6\xf0\xaf\
+\xb3\xb8\xe9)R\x8f\xac\xe8\xb1\xfe\xf1\xb6tVO\x0e\
+\xd3(=e\xa9@%\xab\x0b\x16\x1b#\xc1\x03\x0e\x04\
+\xd8\xf38\xf0\xeddA\x220\xdb\xe5\x15\xfb\x1b\xe5\xf5\
+\x93i\xbb\xfcs\x81\xd5:\x18\x80\x93\xa0\xa1\xa532\
+C3\x92$)\xa5\x03 \x84\x10\xc4\xdcq\x07\xe9\x80\
+\xaa\xec\xf5}\xa7\x0a~\xe6v j\x95\xc9\x9a\x9f\x93\
+\xb1\x1fxx\x8dL\x93\x8d$fa.\xaf\x96kF\
+\x7fRr.\x00r\x00\xedF2\x85\xee\x9e\x1a\x02H\
+\xfc\x150\x0f\x05'x\xbb\x5c\x19\xc8p\xef\xa2\xad\xb2\
+'W\xec]a^\x5cb\x0f\x22\xcb\xd4\x0d+\x15\x8c\
+\xe4lK\x86\x11\xea$T\x9a'\xf2\xae\xe7BL\xee\
+\xc5k\xac!\xbf8\x8d5D\xb0\x0e\xed\xd7\x91\x83\xbb\
+\x880\xa8mr\xbb\xc2\xd9\x0cO+\xd34\x80\xc9\xbc\
+o\x14Y\x0b\x883O\x10\xfc\x88]p>\x8cr\x8d\
+T\xf0k\xf8\x0bj\xef?\xd0r\xb57\x1d\x07(\xe7\
+\xee{h\x13\x8b\xc1\xb6\x90\x92\xf9\x8a\xe5\x95)\xd9\x22\
+2\xc6\xc9e\xe7i\xb0R\x1f\x18W\x95lPV#\
+}Mgl\xb3\xb8\xa7\xd8EF\x22\xaa\xe40P8\
+\xf1\xaf\x0f\x82\x0dt\xe6\xa4\x01G@\x07\xa5;^\x0c\
+v\x14\xe4\x90k*\x5c\xfe\x9b\x14o\xd6@9\xd6\xa2\
+\x7f\xb0\x85\x1f\xe7yV2\xe4+\xf4\xcc1S<\x98\
+\x01\x9e<Q]\xa5R\xc8\xc4\x84\x06\x22!\x8f\x0f$\
+\xcc\xec\x19pP\xe8\xed\xc2\x83\xa3\xc1H.\xd7\x80\x85\
+\x91K\xe8\xdc\x9cW\xf2V\x8a\x15\x02\xbeH,\xe0<\
+\x16\x8c\xbbSP\xfb_\xd8)}\x00u\xcc(5\xe0\
+U\x01\
 "
 
 qt_resource_name = b"\
@@ -316,14 +316,14 @@ qt_resource_struct = b"\
 \x00\x00\x00\x00\x00\x00\x00\x00\
 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00h\x00\x01\x00\x00\x00\x01\x00\x00\x0d\xcc\
-\x00\x00\x01\x87V\x12\x92R\
-\x00\x00\x00N\x00\x00\x00\x00\x00\x01\x00\x00\x09\xa4\
-\x00\x00\x01\x87V\x12\x92R\
-\x00\x00\x004\x00\x01\x00\x00\x00\x01\x00\x00\x05\x16\
-\x00\x00\x01\x87V\x12\x92R\
+\x00\x00\x00h\x00\x04\x00\x00\x00\x01\x00\x00\x0d\xca\
+\x00\x00\x01\x975l\xc7\xec\
+\x00\x00\x00N\x00\x00\x00\x00\x00\x01\x00\x00\x09\xf1\
+\x00\x00\x01\x998\xf9\x98B\
+\x00\x00\x004\x00\x04\x00\x00\x00\x01\x00\x00\x04\xec\
+\x00\x00\x01\x998\xf9\x98B\
 \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x87V\x12\x92R\
+\x00\x00\x01\x975l\xc7\xec\
 "
 
 def qInitResources():
index 47cc5257bb52bf227e4f94c5ae63672a751989af..99ff4403e42dbd48beb254d8a40969454bb61b3b 100644 (file)
@@ -7,12 +7,11 @@ import QtQuick.Controls
 QtObject {
     id: root
     property real defaultSpacing: 10
-    property SystemPalette palette: SystemPalette { }
 
     property var controlWindow: Window {
         width: col.implicitWidth + root.defaultSpacing * 2
         height: col.implicitHeight + root.defaultSpacing * 2
-        color: root.palette.window
+        color: palette.window
         title: "Control Window"
         Column {
             id: col
@@ -90,13 +89,13 @@ QtObject {
                       " and has visibility " + parent.visibilityToString(root.testWindow.visibility)
             }
             Rectangle {
-                color: root.palette.text
+                color: palette.text
                 width: parent.width
                 height: 1
             }
             CurrentScreen { }
             Rectangle {
-                color: root.palette.text
+                color: palette.text
                 width: parent.width
                 height: 1
             }
@@ -117,35 +116,44 @@ QtObject {
                 anchors.centerIn: parent
                 text: "Second Window"
             }
-            MouseArea {
-                anchors.fill: parent
-                onClicked: root.testWindow.color = "#e0c31e"
+            TapHandler {
+                onTapped: root.testWindow.color = Qt.rgba(0.25 + Math.random() / 2,
+                                                          0.65 + Math.random() / 4,
+                                                          0.15 + Math.random() / 3)
             }
             Button {
-                anchors.right: parent.right
-                anchors.top: parent.top
-                anchors.margins: root.defaultSpacing
-                text: root.testWindow.visibility === Window.FullScreen ? "exit fullscreen" : "go fullscreen"
-                width: 150
-                onClicked: {
-                    if (root.testWindow.visibility === Window.FullScreen)
-                        root.testWindow.visibility = Window.AutomaticVisibility
-                    else
-                        root.testWindow.visibility = Window.FullScreen
+                id: fullscreenButton
+                anchors {
+                    right: parent.right
+                    top: parent.top
+                    margins: root.defaultSpacing
                 }
+                width: height
+                checkable: true
+                Binding on checked { value: root.testWindow.visibility === Window.FullScreen }
+                onClicked: root.testWindow.visibility = checked ? Window.FullScreen : Window.AutomaticVisibility
+                text: "â›¶"
+                ToolTip.visible: hovered
+                ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
+                ToolTip.text: root.testWindow.visibility === Window.FullScreen ? qsTr("restore") : qsTr("fill screen")
             }
             Button {
                 anchors.left: parent.left
                 anchors.top: parent.top
                 anchors.margins: root.defaultSpacing
-                text: "X"
-                width: 30
+                text: "✘"
+                width: height
+                height: fullscreenButton.height
                 onClicked: root.testWindow.close()
+                ToolTip.visible: hovered
+                ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
+                ToolTip.text: qsTr("close this window")
             }
         }
     }
 
     property var splashWindow: Splash {
+        visible: true
         onTimeout: root.controlWindow.visible = true
     }
 }
index 89d1de1b1feb2c4b9e8fc9c5f5f604560d56033b..b9ae1d931f0d31479b16c700386bd00c34a11cae 100644 (file)
@@ -1,5 +1,5 @@
 <RCC>
-    <qresource prefix="/window">
+    <qresource prefix="window">
         <file>window.qml</file>
         <file>Splash.qml</file>
         <file>CurrentScreen.qml</file>
diff --git a/examples/quick3d/customgeometry/CustomGeometryExample/Main.qml b/examples/quick3d/customgeometry/CustomGeometryExample/Main.qml
new file mode 100644 (file)
index 0000000..ced493e
--- /dev/null
@@ -0,0 +1,398 @@
+// 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 QtQuick3D
+import QtQuick3D.Helpers
+
+import CustomGeometryExample
+
+ApplicationWindow {
+    id: window
+    width: 1280
+    height: 720
+    visible: true
+    title: "Custom Geometry Example"
+
+    property bool isLandscape: width > height
+
+    View3D {
+        id: v3d
+        anchors.left: window.isLandscape ? controlsPane.right : parent.left
+        anchors.top: window.isLandscape ? parent.top : controlsPane.bottom
+        anchors.right: parent.right
+        anchors.bottom: parent.bottom
+
+        camera: camera
+
+        environment: SceneEnvironment {
+            id: env
+            backgroundMode: SceneEnvironment.Color
+            clearColor: "#002b36"
+        }
+
+        Node {
+            id: originNode
+            PerspectiveCamera {
+                id: cameraNode
+                z: 600
+            }
+        }
+
+        DirectionalLight {
+            id: directionalLight
+            color: Qt.rgba(0.4, 0.2, 0.6, 1.0)
+            ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
+        }
+
+        PointLight {
+            id: pointLight
+            position: Qt.vector3d(0, 0, 100)
+            color: Qt.rgba(0.1, 1.0, 0.1, 1.0)
+            ambientColor: Qt.rgba(0.2, 0.2, 0.2, 1.0)
+        }
+
+        Model {
+            id: gridModel
+            visible: false
+            scale: Qt.vector3d(100, 100, 100)
+            geometry: GridGeometry {
+                id: grid
+                horizontalLines: 20
+                verticalLines: 20
+            }
+            materials: [
+                PrincipledMaterial {
+                    lineWidth: sliderLineWidth.value
+                }
+            ]
+        }
+
+        //! [model triangle]
+        Model {
+            id: triangleModel
+            visible: false
+            scale: Qt.vector3d(100, 100, 100)
+            geometry: ExampleTriangleGeometry {
+                normals: cbNorm.checked
+                normalXY: sliderNorm.value
+                uv: cbUV.checked
+                uvAdjust: sliderUV.value
+            }
+            materials: [
+                PrincipledMaterial {
+                    Texture {
+                        id: baseColorMap
+                        source: "qt_logo_rect.png"
+                    }
+                    cullMode: PrincipledMaterial.NoCulling
+                    baseColorMap: cbTexture.checked ? baseColorMap : null
+                    specularAmount: 0.5
+                }
+            ]
+        }
+        //! [model triangle]
+
+        Model {
+            id: pointModel
+            visible: false
+            scale: Qt.vector3d(100, 100, 100)
+            geometry: ExamplePointGeometry { }
+            materials: [
+                PrincipledMaterial {
+                    lighting: PrincipledMaterial.NoLighting
+                    cullMode: PrincipledMaterial.NoCulling
+                    baseColor: "yellow"
+                    pointSize: sliderPointSize.value
+                }
+            ]
+        }
+
+        Model {
+            id: torusModel
+            visible: false
+            geometry: TorusMesh {
+                radius: radiusSlider.value
+                tubeRadius: tubeRadiusSlider.value
+                segments: segmentsSlider.value
+                rings: ringsSlider.value
+            }
+            materials: [
+                PrincipledMaterial {
+                    id: torusMaterial
+                    baseColor: "#dc322f"
+                    metalness: 0.0
+                    roughness: 0.1
+                }
+            ]
+        }
+
+        OrbitCameraController {
+            origin: originNode
+            camera: cameraNode
+        }
+    }
+
+    Pane {
+        id: controlsPane
+        width: window.isLandscape ? implicitWidth : window.width
+        height: window.isLandscape ? window.height : implicitHeight
+        ColumnLayout {
+            GroupBox {
+                title: "Mode"
+                ButtonGroup {
+                    id: modeGroup
+                    buttons: [ radioGridGeom, radioCustGeom, radioPointGeom, radioQMLGeom ]
+                }
+                ColumnLayout {
+                    RadioButton {
+                        id: radioGridGeom
+                        text: "GridGeometry"
+                        checked: true
+                    }
+                    RadioButton {
+                        id: radioCustGeom
+                        text: "Custom geometry from application (triangle)"
+                        checked: false
+                    }
+                    RadioButton {
+                        id: radioPointGeom
+                        text: "Custom geometry from application (points)"
+                        checked: false
+                    }
+                    RadioButton {
+                        id: radioQMLGeom
+                        text: "Custom geometry from QML"
+                        checked: false
+                    }
+                }
+            }
+
+            Pane {
+                id: gridSettings
+                visible: false
+                ColumnLayout {
+                    Button {
+                        text: "+ Y Cells"
+                        onClicked: grid.horizontalLines += 1
+                        Layout.alignment: Qt.AlignHCenter
+
+                    }
+                    RowLayout {
+                        Layout.alignment: Qt.AlignHCenter
+                        Button  {
+                            text: "- X Cells"
+                            onClicked: grid.verticalLines -= 1
+                        }
+                        Button {
+                            text: "+ X Cells"
+                            onClicked: grid.verticalLines += 1
+                        }
+                    }
+                    Button  {
+                        text: "- Y Cells"
+                        onClicked: grid.horizontalLines -= 1
+                        Layout.alignment: Qt.AlignHCenter
+                    }
+
+                    Label {
+                        text: "Line width (if supported)"
+                    }
+                    Slider {
+                        Layout.fillWidth: true
+                        id: sliderLineWidth
+                        from: 1.0
+                        to: 10.0
+                        stepSize: 0.5
+                        value: 1.0
+                    }
+                }
+            }
+            Pane {
+                id: triangleSettings
+                visible: false
+                ColumnLayout {
+                    CheckBox {
+                        id: cbNorm
+                        text: "provide normals in geometry"
+                        checked: false
+                    }
+                    RowLayout {
+                        enabled: cbNorm.checked
+                        Label {
+                            Layout.fillWidth: true
+                            text: "Normal adjust: "
+                        }
+                        Slider {
+                            id: sliderNorm
+
+                            from: 0.0
+                            to: 1.0
+                            stepSize: 0.01
+                            value: 0.0
+                        }
+                    }
+                    CheckBox {
+                        id: cbTexture
+                        text: "enable base color map"
+                        checked: false
+                    }
+                    CheckBox {
+                        id: cbUV
+                        text: "provide UV in geometry"
+                        checked: false
+                    }
+                    RowLayout {
+                        enabled: cbUV.checked
+                        Label {
+                            Layout.fillWidth: true
+                            text: "UV adjust:"
+                        }
+                        Slider {
+                            id: sliderUV
+                            from: 0.0
+                            to: 1.0
+                            stepSize: 0.01
+                            value: 0.0
+                        }
+                    }
+                }
+
+            }
+            Pane {
+                id: pointSettings
+                visible: false
+                RowLayout {
+                    ColumnLayout {
+                        RowLayout {
+                            Label {
+                                text: "Point size (if supported)"
+                            }
+                            Slider {
+                                id: sliderPointSize
+                                from: 1.0
+                                to: 16.0
+                                stepSize: 1.0
+                                value: 1.0
+                            }
+                        }
+                    }
+                }
+            }
+            Pane {
+                id: torusSettings
+                visible: false
+                ColumnLayout {
+                    Label {
+                        text: "Radius: (" + radiusSlider.value + ")"
+                    }
+                    Slider {
+                        id: radiusSlider
+                        from: 1.0
+                        to: 1000.0
+                        stepSize: 1.0
+                        value: 200
+                    }
+                    Label {
+                        text: "Tube Radius: (" + tubeRadiusSlider.value + ")"
+                    }
+                    Slider {
+                        id: tubeRadiusSlider
+                        from: 1.0
+                        to: 500.0
+                        stepSize: 1.0
+                        value: 50
+                    }
+                    Label {
+                        text: "Rings: (" + ringsSlider.value + ")"
+                    }
+                    Slider {
+                        id: ringsSlider
+                        from: 3
+                        to: 35
+                        stepSize: 1.0
+                        value: 20
+                    }
+                    Label {
+                        text: "Segments: (" + segmentsSlider.value + ")"
+                    }
+                    Slider {
+                        id: segmentsSlider
+                        from: 3
+                        to: 35
+                        stepSize: 1.0
+                        value: 20
+                    }
+                    CheckBox {
+                        id: wireFrameCheckbox
+                        text: "Wireframe Mode"
+                        checked: false
+                        onCheckedChanged: {
+                            env.debugSettings.wireframeEnabled = checked
+                            torusMaterial.cullMode = checked ? Material.NoCulling : Material.BackFaceCulling
+
+
+                        }
+                    }
+                }
+
+            }
+        }
+        states: [
+            State {
+                name: "gridMode"
+                when: radioGridGeom.checked
+                PropertyChanges {
+                    gridModel.visible: true
+                    gridSettings.visible: true
+                    env.debugSettings.wireframeEnabled: false
+                    originNode.position: Qt.vector3d(0, 0, 0)
+                    originNode.rotation: Qt.quaternion(1, 0, 0, 0)
+                    cameraNode.z: 600
+
+                }
+            },
+            State {
+                name: "triangleMode"
+                when: radioCustGeom.checked
+                PropertyChanges {
+                    triangleModel.visible: true
+                    triangleSettings.visible: true
+                    env.debugSettings.wireframeEnabled: false
+                    originNode.position: Qt.vector3d(0, 0, 0)
+                    originNode.rotation: Qt.quaternion(1, 0, 0, 0)
+                    cameraNode.z: 600
+                }
+            },
+            State {
+                name: "pointMode"
+                when: radioPointGeom.checked
+                PropertyChanges {
+                    pointModel.visible: true
+                    pointSettings.visible: true
+                    env.debugSettings.wireframeEnabled: false
+                    originNode.position: Qt.vector3d(0, 0, 0)
+                    originNode.rotation: Qt.quaternion(1, 0, 0, 0)
+                    cameraNode.z: 600
+                }
+            },
+            State {
+                name: "qmlMode"
+                when: radioQMLGeom.checked
+                PropertyChanges {
+                    torusModel.visible: true
+                    torusSettings.visible: true
+                    directionalLight.eulerRotation: Qt.vector3d(-40, 0, 0)
+                    directionalLight.color: "white"
+                    pointLight.color: "white"
+                    pointLight.position: Qt.vector3d(0, 0, 0)
+                    originNode.position: Qt.vector3d(0, 0, 0)
+                    originNode.eulerRotation: Qt.vector3d(-40, 0, 0)
+                    cameraNode.z: 600
+                }
+            }
+        ]
+    }
+}
diff --git a/examples/quick3d/customgeometry/CustomGeometryExample/TorusMesh.qml b/examples/quick3d/customgeometry/CustomGeometryExample/TorusMesh.qml
new file mode 100644 (file)
index 0000000..7be6059
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick3D.Helpers
+
+ProceduralMesh {
+    property int rings: 50
+    property int segments: 50
+    property real radius: 100.0
+    property real tubeRadius: 10.0
+    property var meshArrays: generateTorus(rings, segments, radius, tubeRadius)
+    positions: meshArrays.verts
+    normals: meshArrays.normals
+    uv0s: meshArrays.uvs
+    indexes: meshArrays.indices
+
+    function generateTorus(rings: int, segments: int, radius: real, tubeRadius: real) : var {
+        let verts = []
+        let normals = []
+        let uvs = []
+        let indices = []
+
+        for (let i = 0; i <= rings; ++i) {
+            for (let j = 0; j <= segments; ++j) {
+                const u = i / rings * Math.PI * 2;
+                const v = j / segments * Math.PI * 2;
+
+                const centerX = radius * Math.cos(u);
+                const centerZ = radius * Math.sin(u);
+
+                const posX = centerX + tubeRadius * Math.cos(v) * Math.cos(u);
+                const posY = tubeRadius * Math.sin(v);
+                const posZ = centerZ + tubeRadius * Math.cos(v) * Math.sin(u);
+
+                verts.push(Qt.vector3d(posX, posY, posZ));
+
+                const normal = Qt.vector3d(posX - centerX, posY, posZ - centerZ).normalized();
+                normals.push(normal);
+
+                uvs.push(Qt.vector2d(i / rings, j / segments));
+            }
+        }
+
+        for (let i = 0; i < rings; ++i) {
+            for (let j = 0; j < segments; ++j) {
+                const a = (segments + 1) * i + j;
+                const b = (segments + 1) * (i + 1) + j;
+                const c = (segments + 1) * (i + 1) + j + 1;
+                const d = (segments + 1) * i + j + 1;
+
+                // Generate two triangles for each quad in the mesh
+                // Adjust order to be counter-clockwise
+                indices.push(a, d, b);
+                indices.push(b, d, c);
+            }
+        }
+        return { verts: verts, normals: normals, uvs: uvs, indices: indices }
+    }
+}
diff --git a/examples/quick3d/customgeometry/CustomGeometryExample/qmldir b/examples/quick3d/customgeometry/CustomGeometryExample/qmldir
new file mode 100644 (file)
index 0000000..9d54279
--- /dev/null
@@ -0,0 +1,3 @@
+module CustomGeometryExample
+Main 1.0 Main.qml
+TorusMesh 1.0 TorusMesh.qml
diff --git a/examples/quick3d/customgeometry/CustomGeometryExample/qt_logo_rect.png b/examples/quick3d/customgeometry/CustomGeometryExample/qt_logo_rect.png
new file mode 100644 (file)
index 0000000..129b873
Binary files /dev/null and b/examples/quick3d/customgeometry/CustomGeometryExample/qt_logo_rect.png differ
index 3e31ac9315dc46a8b7b14f85a31f4a150818253d..d3aeb7d0c56f7a1f49d350d2a203057f58f837c6 100644 (file)
@@ -1,3 +1,5 @@
 {
-    "files": ["examplepoint.py", "exampletriangle.py", "main.py", "main.qml", "resources.qrc"]
+    "files": ["examplepoint.py", "exampletriangle.py", "main.py",
+              "CustomGeometryExample/Main.qml", "CustomGeometryExample/TorusMesh.qml",
+              "CustomGeometryExample/qmldir", "CustomGeometryExample/qt_logo_rect.png"]
 }
index 3b4984222a8af850ffce179a4cb8cff44939c57f..df5e8f90e6a350538b92577d1153089f06bafe58 100644 (file)
@@ -9,7 +9,7 @@ from PySide6.QtGui import QVector3D
 from PySide6.QtQml import QmlElement
 from PySide6.QtQuick3D import QQuick3DGeometry
 
-QML_IMPORT_NAME = "ExamplePointGeometry"
+QML_IMPORT_NAME = "CustomGeometryExample"
 QML_IMPORT_MAJOR_VERSION = 1
 
 
index 996a9f85c6c5939db64e0c51c6d07756087a0236..8cc7a7278542c613529cef5137d61cbd988fc0d6 100644 (file)
@@ -8,7 +8,7 @@ from PySide6.QtGui import QVector3D
 from PySide6.QtQml import QmlElement
 from PySide6.QtQuick3D import QQuick3DGeometry
 
-QML_IMPORT_NAME = "ExampleTriangleGeometry"
+QML_IMPORT_NAME = "CustomGeometryExample"
 QML_IMPORT_MAJOR_VERSION = 1
 
 
index bff6b4a9573e8579d3783a2a916e862178fb257d..169cf17e82228731d646315e1da835790a810979 100644 (file)
@@ -3,27 +3,24 @@
 from __future__ import annotations
 
 
-import os
 import sys
+from pathlib import Path
 
-from PySide6.QtCore import QUrl
 from PySide6.QtGui import QGuiApplication, QSurfaceFormat
 from PySide6.QtQml import QQmlApplicationEngine
 from PySide6.QtQuick3D import QQuick3D
 
-# Imports to trigger the resources and registration of QML elements
-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"
     app = QGuiApplication(sys.argv)
 
     QSurfaceFormat.setDefaultFormat(QQuick3D.idealSurfaceFormat())
 
     engine = QQmlApplicationEngine()
-    engine.load(QUrl.fromLocalFile(":/main.qml"))
+    engine.addImportPath(Path(__file__).parent)
+    engine.loadFromModule("CustomGeometryExample", "Main")
     if not engine.rootObjects():
         sys.exit(-1)
 
diff --git a/examples/quick3d/customgeometry/main.qml b/examples/quick3d/customgeometry/main.qml
deleted file mode 100644 (file)
index 45bb446..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (C) 2021 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtQuick3D
-import QtQuick3D.Helpers
-import ExamplePointGeometry
-import ExampleTriangleGeometry
-
-
-Window {
-    id: window
-    width: 1280
-    height: 720
-    visible: true
-    color: "#848895"
-
-    View3D {
-        id: v3d
-        anchors.fill: parent
-        camera: camera
-
-        PerspectiveCamera {
-            id: camera
-            position: Qt.vector3d(0, 0, 600)
-        }
-
-        DirectionalLight {
-            position: Qt.vector3d(-500, 500, -100)
-            color: Qt.rgba(0.4, 0.2, 0.6, 1.0)
-            ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0)
-        }
-
-        PointLight {
-            position: Qt.vector3d(0, 0, 100)
-            color: Qt.rgba(0.1, 1.0, 0.1, 1.0)
-            ambientColor: Qt.rgba(0.2, 0.2, 0.2, 1.0)
-        }
-
-        Model {
-            visible: radioGridGeom.checked
-            scale: Qt.vector3d(100, 100, 100)
-            geometry: GridGeometry {
-                id: grid
-                horizontalLines: 20
-                verticalLines: 20
-            }
-            materials: [
-                DefaultMaterial {
-                    lineWidth: sliderLineWidth.value
-                }
-            ]
-        }
-
-        //! [model triangle]
-        Model {
-            visible: radioCustGeom.checked
-            scale: Qt.vector3d(100, 100, 100)
-            geometry: ExampleTriangleGeometry {
-                normals: cbNorm.checked
-                normalXY: sliderNorm.value
-                uv: cbUV.checked
-                uvAdjust: sliderUV.value
-            }
-            materials: [
-                DefaultMaterial {
-                    Texture {
-                        id: baseColorMap
-                        source: "qt_logo_rect.png"
-                    }
-                    cullMode: DefaultMaterial.NoCulling
-                    diffuseMap: cbTexture.checked ? baseColorMap : null
-                    specularAmount: 0.5
-                }
-            ]
-        }
-        //! [model triangle]
-
-        Model {
-            visible: radioPointGeom.checked
-            scale: Qt.vector3d(100, 100, 100)
-            geometry: ExamplePointGeometry { }
-            materials: [
-                DefaultMaterial {
-                    lighting: DefaultMaterial.NoLighting
-                    cullMode: DefaultMaterial.NoCulling
-                    diffuseColor: "yellow"
-                    pointSize: sliderPointSize.value
-                }
-            ]
-        }
-    }
-
-    WasdController {
-        controlledObject: camera
-    }
-
-    ColumnLayout {
-        Label {
-            text: "Use WASD and mouse to navigate"
-            font.bold: true
-        }
-        ButtonGroup {
-            buttons: [ radioGridGeom, radioCustGeom, radioPointGeom ]
-        }
-        RadioButton {
-            id: radioGridGeom
-            text: "GridGeometry"
-            checked: true
-            focusPolicy: Qt.NoFocus
-        }
-        RadioButton {
-            id: radioCustGeom
-            text: "Custom geometry from application (triangle)"
-            checked: false
-            focusPolicy: Qt.NoFocus
-        }
-        RadioButton {
-            id: radioPointGeom
-            text: "Custom geometry from application (points)"
-            checked: false
-            focusPolicy: Qt.NoFocus
-        }
-        RowLayout {
-            visible: radioGridGeom.checked
-            ColumnLayout {
-                Button {
-                    text: "More X cells"
-                    onClicked: grid.verticalLines += 1
-                    focusPolicy: Qt.NoFocus
-                }
-                Button  {
-                    text: "Fewer X cells"
-                    onClicked: grid.verticalLines -= 1
-                    focusPolicy: Qt.NoFocus
-                }
-            }
-            ColumnLayout {
-                Button {
-                    text: "More Y cells"
-                    onClicked: grid.horizontalLines += 1
-                    focusPolicy: Qt.NoFocus
-                }
-                Button  {
-                    text: "Fewer Y cells"
-                    onClicked: grid.horizontalLines -= 1
-                    focusPolicy: Qt.NoFocus
-                }
-            }
-        }
-        RowLayout {
-            visible: radioGridGeom.checked
-            Label {
-                text: "Line width (if supported)"
-            }
-            Slider {
-                id: sliderLineWidth
-                from: 1.0
-                to: 10.0
-                stepSize: 0.5
-                value: 1.0
-                focusPolicy: Qt.NoFocus
-            }
-        }
-        RowLayout {
-            visible: radioCustGeom.checked
-            CheckBox {
-                id: cbNorm
-                text: "provide normals in geometry"
-                checked: false
-                focusPolicy: Qt.NoFocus
-            }
-            RowLayout {
-                Label {
-                    text: "manual adjust"
-                }
-                Slider {
-                    id: sliderNorm
-                    from: 0.0
-                    to: 1.0
-                    stepSize: 0.01
-                    value: 0.0
-                    focusPolicy: Qt.NoFocus
-                }
-            }
-        }
-        RowLayout {
-            visible: radioCustGeom.checked
-            CheckBox {
-                id: cbTexture
-                text: "enable base color map"
-                checked: false
-                focusPolicy: Qt.NoFocus
-            }
-            CheckBox {
-                id: cbUV
-                text: "provide UV in geometry"
-                checked: false
-                focusPolicy: Qt.NoFocus
-            }
-            RowLayout {
-                Label {
-                    text: "UV adjust"
-                }
-                Slider {
-                    id: sliderUV
-                    from: 0.0
-                    to: 1.0
-                    stepSize: 0.01
-                    value: 0.0
-                    focusPolicy: Qt.NoFocus
-                }
-            }
-        }
-        RowLayout {
-            visible: radioPointGeom.checked
-            ColumnLayout {
-                RowLayout {
-                    Label {
-                        text: "Point size (if supported)"
-                    }
-                    Slider {
-                        id: sliderPointSize
-                        from: 1.0
-                        to: 16.0
-                        stepSize: 1.0
-                        value: 1.0
-                        focusPolicy: Qt.NoFocus
-                    }
-                }
-            }
-        }
-        TextArea {
-            id: infoText
-            readOnly: true
-        }
-    }
-}
diff --git a/examples/quick3d/customgeometry/qt_logo_rect.png b/examples/quick3d/customgeometry/qt_logo_rect.png
deleted file mode 100644 (file)
index 129b873..0000000
Binary files a/examples/quick3d/customgeometry/qt_logo_rect.png and /dev/null differ
diff --git a/examples/quick3d/customgeometry/resources.qrc b/examples/quick3d/customgeometry/resources.qrc
deleted file mode 100644 (file)
index dc55e9d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<RCC>
-    <qresource prefix="/">
-        <file>main.qml</file>
-        <file>qt_logo_rect.png</file>
-    </qresource>
-</RCC>
diff --git a/examples/quick3d/customgeometry/resources_rc.py b/examples/quick3d/customgeometry/resources_rc.py
deleted file mode 100644 (file)
index 1422353..0000000
+++ /dev/null
@@ -1,585 +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\x09C\
-\x00\
-\x00#\x9fx\x9c\xe5ZQs\xdaH\x12~\xe7W\xf4\
-\xf9\xf2`\xef\x11\x19;\x9b\x5c\x8e\xab\xab+\x01\xb2\xad\
-*,\x11I\xd8qmm\xa5\x844\xc0l\x84\x86\x95\
-F&l\xca\xff\xfd\xbaG\x12\x08\x108N\xec\xbd\xba\
-\xba)\x17\xa0\x99\x9e\xee\xaf\xbf\xee\xe9\x99Qr\xfa\xd3\
-3\xb6\x86\xfa\x83\xae\x98/\x13>\x99J8\xee\x9e\xc0\
-y\xeb\xfc\x0c\xbc)\x83\x0f\x12Gfs?^B_\
-\x86Z.\x19K?\x90m\x98J9O\xdb\xa7\xa7\x8b\
-\xc5B\xfb]j\x5c\x9cF<`q\xca\xe3\xc9i\xa1\
-\xd5\x9b\xf2\x14\xc6<b\x80\xdfs?\x91 \xc6 s\
-\xbdc\x91\xc0`)\xa7\x22\x06\xf6\xc5\x9f\xcd#\x96V\
-F=!\xa2\xcf\x5cj\x85\xa2W\x1f\xbcO\x1d\xe3\xd2\
-\xb4>\xf5\xcd\xaea\xb9F\xbb\xe3\xf6^\xe5pf3\
-\x96\x04\xdc\x8f\xa0\xaf\xcc3\x18\xa6\xfe\x84\xd1X\xd1\x81\
-\x8a\xa7\x22\x0a\x11\x17\xdc\xfb\x11\x0f!X\xcfAS9\
-j\x14\x9a\xf9K\xc8p\xbe\x5c\xa3\x8eI\x8d\x1f\x04\x22\
-\x09\xfd8`\xb0\xe0r\xaa VT\x14\xf3\xc1\x9f$\
-\x8c\xcdX,a\x9e\x88{\x1e\xb2p%NZ\x5c1\
-\x96\x0b?a \x92&\xf8\x91dI\xecK~\xcf\xa2\
-e\x13\xed\xd4\x1aA\x99Y\x8a\xa6\x90p\x1e\xa3\xba\x02\
-\x0e,\x12.%\x8b+\x16GL.\x18\xf6,E\x06\
-~\x1cn\xc5N\x83\x0b${\x15\x9d\x5c\xafR\x15\x13\
-\x19q\xc8%\x17q\x0aHUMT\x95\xf4\xeb\xb5X\
-\xaem\x9c%\x081!-<\xc6X\xce|\x1a,\xf8\
-c9\xe8@Ey\x06\xbe\xacQ[H\xbc\xce\xd22\
-\xc8\x18\xd2\xdd\x18\xea\x9bL\x91\x83\xbbq\xca\xe2\x90%\
-\x15\xca\x8a<\x22\x85Et\x94\xb7(-\xa2H,\xd2\
-va\xf1\xc8a!Oe\xc2G\x99BO|\x90f\
-\x8cG*\xb2\x04cA=#\x1e\xfb\xc9R\xb9\x926\
-\xf3\xe8 \x01\xf4-2Ijf\x22\xe4c\x1e(\x06\
-0\xb6\x18\xe39\xc2\xa0\x18\x85\xeb\x5c\x90S\xa4\x81P\
-\xe5\x18(\x0e\x15\xeaq\x92\xd2\xc4$a\x03\x80\x9f`\
-\x13\x9br\xaa\x00\x15\x88\x90\xc1,K%$\x8cRC\
-\xa9\xf5G\xe2\x9e\x86\x8au\x9ck\x01\x88\x85D\x06\x9a\
-9Y\x11*$=U\xc3q\xb8\x85\x0a\xad\x06\x91\xcf\
-1\xbd\xb5}P\xd0d\x85\x94\x12\x0a\xba\x1af\x01{\
-)4E\xfaS#\x91P\x04\x19\xe5\xbe_F\xee\x14\
-\x83\x22('1A0\x0dpi\xa6\xf5+\x91Z\xd5\
-\x9f\x95\x9b\x16\xe3j>\xa9\x8f\xfd\x19#p\xbbU\x10\
-\x9dX\x8b\xa8\xb0p\x99\x96z)\xab\x95^\x91\xe4\x05\
-e\xc4(\xa3\xd0+\x01,\x0e\xb1\x97\x0a\x00\xe1\x9a\x09\
-\xc9 \xa7L\xa6\x80\xf9\x8b\x19\x1e\x96j\xc68\x9e\x93\
-\x94\x96e\xa3\xc87H\xe7,\xa0l\xc3\xb9\x9c\xd2\xb0\
-\xa8\x05*\xe3\xd2\xb4p\xa7\xac\xbfW\xa6\x0b\xae}\xe1\
-\xdd\xea\x8e\x01\xf8{\xe0\xd87f\xcf\xe8A\xe7\x0e\x07\
-\x0d\xe8\xda\x83;\xc7\xbc\xbc\xf2\xe0\xca\xee\xf7\x0c\xc7\x05\
-\xdd\xeaa\xaf\xe59fg\xe8\xd9\x8e\xab\x96\x89\xee\xe2\
-\xe4#5\xa6[w`|\x1c8\x86\xeb\x82\xed\x80y\
-=\xe8\x9b\xa8\x0f\x0d8\xba\xe5\x99\x86\xdb\x04\xd3\xea\xf6\
-\x87=\xd3\xbal\x02\xea\x00\xcb\xf6T16\xafM\x0f\
-%=\xbb\xa9L\xef\xce\x04\xfb\x02\xae\x0d\xa7{\x85\x8f\
-z\xc7\xec\x9b\xde\x9d2yaz\x16\x99\xbb\xb0\x1dU\
-\x11`\xa0;\x9e\xd9\x1d\xf6u\x07\x06Cg`\xbb\x06\
-\x90\x7f=\xd3\xed\xf6u\xf3\xda\xe8i\x88\x01\xed\x82q\
-cX\x1e\xb8Wz\xbf\xbf\xe9.\xe9\xb1o-\xc3!\
-\x1f\xaa\xeeB\xc7@\xa4z\xa7o\x909\xe5m\xcft\
-\x8c\xaeGn\xad\x7fu\x91D\x04\xd9o\xaa\xca>0\
-\xba&\xfeF^\x0ctJw\xee\x9a\x85Z\xd7\xf80\
-D9\x1c\x84\x9e~\xad_\xa2\x8f\xc7\x8f\xb3\x83A\xea\
-\x0e\x1d\xe3\x9a\xb0#%\xee\xb0\xe3z\xa67\xf4\x0c\xb8\
-\xb4\xed\x9e\xa2\xdd5\x9c\x1b\xdc\x08\xdd\x7fB\xdfv\x15\
-qC\xd7P`z\xba\xa7+\xf3\xa8\x05\x89C\x09\xfc\
-\xdd\x19\xba\xa6\xa2\xd0\xb4<\xc3q\x86\x03\xcf\xb4\xad\x13\
-\x8c\xf9-2\x84Hu\x9c\xddS\x5c\xdb\x16\xf9\x9c\xe7\
-\x8ea;w\xa4\x9a\xf8P\xd1h\xc2\xed\x95\x81\xfd\x0e\
-\xd1\xabX\xd3\x89\x0e\x17\xd9\xebzU14\x89d*\
-\xc7\xd6\xfe\x82e\x5c\xf6\xcdK\xc3\xea\x1a$`\x93\xa2\
-[\xd35N0x\xa6K\x02\xa62\x8e\x19\x81f\x87\
-\xcaw\x0a\x1abS\xe1\xba\xd8L\xe7\xa6\x8a.\x98\x17\
-\xa0\xf7nL\xc2_\xc8c>\xb8f\x91>\x8a\xbe\xee\
-U\xc1\xbevT9N\x18V\xaf<L\xbc\xca\xbb\x9f\
-\xaf\x9d6\x1a|6\x17x\xde\xf9 ?d<\xf8\xbc\
-\xf5\xa8\xd1!*\x11Q\xba\xdd\xdf\xf7q\x83\x93\xdb\xdd\
-oz;\x1d\xda\x15\x8bp\xc9\xaf$\x8d\xfc\x085\x10\
-<\x96\x97L\xe0>\x92,\xb7\xc6<,\x89\xf1$b\
-\xab\xe1F\xe3\x96cIZ\xc0\xd7\x06\xd5\x1c\x1e\xb6\xb1\
-\xc8P\x87z\x5c\xf0PN\xdbpv\xfe\xbe\xa5\x9e\xa7\
-\x8c\x0ay\x1b\xfe~\x9e?\xdf\xf3\x94\x8f\x22\xd6\x06\x99\
-d\xac\x91\xd7\xbeH$m8\xfa\xeb\xfb\x9f\xdf\xbf\xff\
-\xc7\xdb\xa3\x86\xea\xbd\xe1l\xf1\xa6W\xd8(\xed\xdc\xbf\
-\x09W\xcfx\xe6\x99b]\xd4p'\x8f\xdatJ\xc4\
-\xa2\xbe\x1a\x0c\xb0\xc6&~\xbb\xf8n\xac\xfa\x07\xe8;\
-\x16B:\x16t\xd5P\xc5@i\xa4\x98S\xed\x9e\x8b\
-T\xed4mdR\xbb\xc7\xf9\x22y\x13\x1e\xb7\x9a\x80\
-\x7f\xefZ\xad\x93\x95\xf0\xc3\xdaT\x8f'dH\xc4~\
-\xd4W'\xe4\xaf\xdf\xa0\xf2\xf5\xdb\x16\xaaT\x1f\xaf\xcf\
-\xaa\x8a+D\xa1|2\x19\xf9\xc7-\xedg\x04\xa0\x9d\
-\xd3\xc7\xbb&\x9ci[\xe2\xfel\xc4\x91\x93\xee\xf6\xac\
-3\x9aP~l\xcc\xaa\xa0W\x19\xf1\xed\xb8s*\x1e\
-G\x9c[\xac\xb3}\x08\xf1\xf9\xca\xcf\xf3\xfd\x88\xaf\xf1\
-t\x13m\x81]%[\xe2\x87\x5c\x5c&<\xa4,\xd6\
-\x82)\x0b>\xb3pC4\x0d|\x12\xac:u\xd6\xca\
-}\xaaqlR,\x866\x94J\xe9i\xcb:5J\
-\xa8\x09\x8a\xec\x0c`\xee\xf2?\xe8,\x8b\xd9\x11\xb3\xb4\
-\x0d\xc5\xfa\xd8\x80\xcf\x12<\xf7\xec\x93x\xd8xZ\x1d\
-^\xda\xf0\xcb\x8e\xa2\x1e\x1b\xfbY$\xaf\x0b\x99\x1a\x9c\
-\xd4\x224s\x9b/\xdf\x14\xef;,\xe9\x97\x1d\x1a^\
-\x80\x8a\xd5\xba\x1f\xc1\xafuQ9=\xfd\x0b\xfc2S\
-\xa1\x91E!\xf9\xf5\x09!\xeb\xe2\x11\xf1\xd9C\xb6\xa7\
-\xb0\xd5\xb0\x12\xd3\x05\x85(\x0dF\x16\xfe\xacE\xb1\x96\
-\xfbxW\x12\xa7\x84\xeb9\xcb\xeeI\xdb\xf0f\xaf\xae\
-\xec^\x0f\x7fC\xb7K](\xba\xab\xe9\xd9c\xef\xb1\
-/2\xc3\xe3b\xfd(5\xca\xe4\x91\x9f2\xb5:\xaf\
-\xfd\xf9^\xc1\xfc\xb6\x81\x05\xfdw\xf9)\x12\x13\xf1\x89\
-\xea\xa06\x8f'G\xb5S\x1ej{\x83,\x8a(=\
-\xda\xdb\xf85\x0b\x93\x22\xc2T\x9d\xd4\xce\xc3\x1b\xd5\x18\
-\x0f\xce\x88\x8fh.\xbc*\xb9\x86\x7fox\x00m\x88\
-QU\xad\x1e\xda%\xb2\xc8O\xf4\x99\xc8b\x8cEK\
-{\xfb\x84\xf4/\x7f\xd5f\xff\x13\xd2\x7f\xb5/\xbfD\
-\xfeol\xfa\xf0\xf5%\xea\x09n\x1f\x18\xa8\xba\x18\xf6\
-\x8b\xb1\x97\x08~\xb1\x7f\x1c-\x19\xdd\x06\xeb\xb3nN\
-\xce\xbb\xfc\x0fV.\xb3A\xd9\xf1\xe4Z\x97\x7f\xaa\xaf\
-[?\x0d\x8b3Z\x84\x17\xc25-A\xd9\x19\xda\xa3\
-\xdf\x18\xbd\x06\xab\x9c2\x8a\xc9\x08;\x9b\xc5\xf9Q\xae\
-2\xb5\xef\x8fv\xb2Db^\xa3\x83C\xbc\x15\xde\xea\
-nO\xdd\x7f1Q\xe9\xe5\x86\xc0\xfb\xe5=\x9f a\
-\x9b\x8e\x8f\x11\x816\x12QX9ym\xfa\xd5\xc9\xa4\
-\x14\xf1e\x22\xb2\xf9\x96\xb9\x91\x1a\xa1L\xd8\xdcH\x9b\
-\x9bE\xba\xb9\x95\xb4\xb5K\xc2!\x91\xdcT\xcd\xd9k\
-C}\x9d\xcb\xd5\xedv\xd3\xc1b\x85l\xb9\x97\xbb\x1e\
-d\xe9@D<X\xaa\xd5b\x89\x0b\xea\xf9>p\xa5\
-\xb3u\xe0h\x0c\xdd.WZ~\x1d\xf7\xe7\xf3\xa8x\
-\xc9\x03\xc7e\x158\xd9\x83}\x8c\x0b\xee\x05\xc1\xafB\
-\xf3}\xe8\xd5\xa2I_\x04\xbbX\xec$>\xb5'\x1c\
-\xe0\xf6,\x9f\xb2\xd5\x12\xb3\xc5\xc0\xb5\xc0\x1d\xf0#\x04\
-X7\xd2\xfa\xb2!\xe2.z\xa3\xdc\xa5S\x9d\xb6q\
-B\x83\xbf\xfd\x0b\xcej\xa7=F\xc4.![\xb0\x0f\
-\xe3\xbe`\x0b\xac6?\x00\xfc\xf5\xf3\x02\x7fx\x91\xc0\
-\xdc=\xc5\xbf\xad\xe3\xf5\x7f94?\x04\xfd\xc5\x82\xf3\
-\xac\xcb\xafn\x9b\xaa\xf0@\xae\xe4\xef\x06\xe0\x98\x8f!\
-\xcd\xe6\xf4\x8e\x81\x85[\xc5d\x13\xaa\xabv\xe6=\xb7\
-\xaa\xad+\xca\x8e\x0c\x95\xaf6\xdd\x16w!\x09\xeco\
-\xd5\x0c\xa4\x92\xcd\xf33A\xddqO\x9d\x0b\xea5~\
-K$\xbe\x9f\xf7\x83\x97\xa0.\xf5u\xc4\x97=,\xe5\
-\x17\x97}Q)\xdes\x97\xf7\x1czA?\xa9\xdd\x5c\
-\xa9\x1d(\xf4O\xe7\xe0\x90\xf7\xd4\xf6\xa5S\x05\xfc\xcc\
-\x8f3<{\xfa\xea\xb6\xb4\x0bww\xc1\xeeM'j\
-\xeb\x94\xaa%Ly\xa82\xaa.q\x14*Q\x9f\x1b\
-\xd4\xaa\x89\xd5\xaa_\xccEv\xed\xd3\xfeg\xaf\xf5\x1f\
-\xcb\xb9\xe2\xde\xb5/\xedX\xec\xa3%u\x17\xcb_\x11\
-\xe1mc\xfe'$\xdc\xa3\xb8\x877\x8f\xad\x94\xe1\xcd\
-\xff\xd6\x22A\xbc\xcf\xbe@jXR\xbe\xfd\x1f-\x8f\
-\xc3\x17\xf3GN<\x87BJ\xedPX\xa9\x15\xa1U\
-\x18 E\xde\x0en\xa9\xf5\xae\x97\xed`\xc8\xa9\xad\xc3\
-\xbe\xba!\xef\x95\xdd\xbf\xe5\xae\xa0S\x16\xbc; \xb0\
-N\x85Cj\x0el\xc5+,\xdf\x98\x11\xd4v\xa9y\
-<O\xa8\xc4\xe9\x09\xab\xfb\x07\x04\xfa\x8f\x0d4\xbc1\
-\x80\xa2\xa1\x1dG\xcb\xda\xeb\xf7C\xe3\xa1\xf1\x1f\x04$\
-=\xda\
-\x00\x00\x18\xaf\
-\x89\
-PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
-\x00\x01_\x00\x00\x01)\x08\x02\x00\x00\x00\xbb\xac\xb6\x0a\
-\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
-\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00\
-\x09pHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\
-\xa8d\x00\x00\x18DIDATx^\xed\x9d\xffk\
-\x14\xd7\xfa\xc7\xef\x7fT\xb8\x5c\x8a\x88\xc8m)Wi\
-S\x09%\x10h\xa9\xd4\x22\xc1\x82\x12\xb0\xa2\x1fR\xa4\
-\x85\x16,---T\x0a\xf9A\xa1\x22\x96\xa2PA\
-\xc1R\x11\x94\x16\xb1\xb9\xc1\xa61,!JZcX\
-B\x1aC\x88\x12\x97\xdc\xe0\xe7=\xf3\xec\x9eLf\x9f\
-]\x93\xcd\x99|\xf3\xf5\xf0b\x98\xcc\x9eo\x1b\xe6y\
-\xed9\xb3\xbb\xb3\xff\xf8w\xff\x1b\x00\x00\xf5`\x07\x00\
-\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\
-\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\
-\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\
-\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\
-\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0f\
-v\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8\
-`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\
-\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\
-\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\
-\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\
-\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\
-\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\
-\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07\
-;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\
-\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\
-\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00\
-|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\
-\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\
-\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\
-\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\
-\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\
-\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\
-\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\
-\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00\
->\xd8a}i\xaf;\x02\xab\x81\xffgL\xb0\xc3:\
-\xc2\xa9\x1c\x1d\xfe\xa51\xc1\x0e\xebE\xf5<\xde\xd9\xb7\
-g{_\x1b\xac\x92\x1d}m\xb5\x7f)\x82\x88\x06v\
-X/\xda_\xee\x7fcG\xdf\x9e\xd7n\xbf\xb5o\xa8\
-{\xef\xd0Ah\x19\xfd\x03;\x07\xbb^\xee\xef@\x0d\
-q\xc1\x0e\xeb\x82\xa9\xa1\xad}\xe0\x9d\xdfg\xef<%\
-V\x1dw\x1f\x8fv\xfc\xf1\xael\xab\x7fl\xdd\x7f\x1b\
-Z\x04;\xac=\x8b\xb3\x86[3\xfd:\xb3\xe7\x9f\xce\
-/<]\x80\xd6\xa8,\xcc\xeb\x7f88[zc\x00\
-;D\x06;\xac1\x89\x1av\xf6\xed\xd9u\xbb\xf3\xc6\
-\xf4\xcdD\x0d\x0b\xff\xd3Vg\xb9\xb6D\x0ba\xff\xc0\
-\xa1\xd9a\xec\x10\x1d\xec\xb0fhI\x9c\xaa\xa1\xbf\xfd\
-\x95\xfe\x8e\x9f\xa7\xae\xdb\x99\x1d^\x03\xd3S\x9dXq\
-`\x87\xe2\xc0\x0ekFU\x0d\xda^\x9e\xbcj\xa7\xb5\
-I\x015\xac&\xb0Cq`\x87\xb5!\x91\x82M\x1f\
-.N^\xb1s:\xa8\x01;\xac&\xb0Cq`\x87\
-5\xa0\xfa6\xdb\xce\xbe=\xdf\x97\x7f\xd4\xa9\x1c\x8c\x80\
-\x1aV\x1f\xd8\xa18\xb0C\xd1T\xd5\xb0\xbd\xaf\xedl\
-\xf9\x82\xcec\xbc\x107\xb0Cq`\x87BI\xd4\xa0\
-\xf3u[\xdf\xab\xa7\xc6\xcf\xe9$\x0eR@\x0d\xb1\x02\
-;\x14\x07v(\x8eE5\x9c\x1c;\xad3\x185\x14\
-\x11\xd8\xa18\xb0CA\x045\xec\xfe\xea~\xaf\x9d\xc7\
-A\x0d\xd8!b`\x87\xe2\xc0\x0eE\xf1r\x7f\xc7\x8b\
-}\xbb\xbf\xf8\xeb[;\x89QCA\x81\x1d\x8a\x03;\
-\x14A\xbb\xd4\xf0\xaf[\xff91\xfau\x90\x82mQ\
-C\xf4\xc0\x0e\xc5\x81\x1d\xe2\xa2\x05EU\x0d\x1f\x8f~\
-YY\xa8\xe8\xc4\xcd\x0a\x82\x88\x1e\xd8\xa18\xb0CD\
-\xecZC\xa2\x86\x0f\xee}\xfaxaNgm\xa1j\
-P\xb3\xa1\xfd\x1c\xf35r\xc7\x85\x95O\x1b\xd8\x0a\x81\
-\x1d\x8a\x03;\xc4\xa2z\x19\xf2\xc5\xbe\xdd\xc7F>\x99\
-\x9d\x7f\x94\x9c\xb8\x85\xa5bHr\xa1\xf4\xb0\x0cYN\
-$\xe5\x95P\x19M\xd8\xce\xe6\x0d\xecP\x1c\xd8!\x0a\
-\x89\x1a\xc4\xf6\xbe\xb6\xee\xe1\xe3\xd3\xf33\xe1\xacU\xc4\
-M\xbf\x90\xd5\x89\x14\xe4\x9fZh\x15\xf3wejl\
-n\xfc\xee\xe3\xd1\xe1\xc7#\xca\x96\xc1\xd9\x92\xb6\xda\xd7\
-\x11\x1d\xd7\xa3s\x0bO\xaa\xa5\xd3\xe1\x89\xd0\x9a\x1d\xdc\
-\x8c\x81\x1d\x8a\x03;DcG_[\xe7`\x97\x920\
-9ekY\x171\xf1Bk\xda\x06\xf5LT&\x7f\
-\x9d\xfe\xedl\xf9\xc2\xc7\xa3_\x1e(\x1d\xed\x18\xdc\xaf\
-\xa5\x8d$\xa5\xd5\xcd?o\xbd\xa2\xed\xf6\xbeW\x950\
-\xca\x9c\xae\xd2\x91\x0f\xef}\xfe]\xf9\x87\x1b\xd37%\
-\x0b\xab\x9e\x15\x84\xed,3\xb2\xb5\x9a\x10J\x16\x17\xd8\
-\xa18\xb0C4\x94\x93\x87\x86{\xc2\xf9\x1a7+B\
-kaGS\x83\xaf\xee\xf7*\xe7w\xf6\xedy\xe1\xd6\
-KZ\xd1h\x00\xda\xd7H$\x88Wjh_Gv\
-\xf6\xb7\xebQ\x95\x912\xb4\xb3o\xa8\xfb\xb3?\xbf\xb1\
-{\xcf(B\x1a\x1bv\xf0\x99\xb1\x9c\x92\xcbo\xad\xe5\
-\xc0\x0e\xc5\x81\x1d\xa2\xa1\xac;8\xfc\x7f:SW\x94\
-c\xcb\x09k-4;\xfe\xa4\xac\x99\xc2\xeb\x03ok\
-j\xb0-\x9d\x1a\xec\xba\xdd\x99\x8a \xb9\xf0\x91\x1b\x95\
-a\x0f\x995lr!M\xa8\xd6\xb1\x91O\xb4\xf4\xc8\
-6n\xfb\xb6\xd3$\xac\x8c\xb6\xe5\xca\x84f\x22\x1aR\
-\x16[\xc8\x842i\x8d\xa2\x02;\x14\x07v\x88F\xb0\
-\x83\x9d\xaf\xb1\x22\x97c?M]W\x1aH\x0a\xca\x84\
-05\x08\x17>\x96A\xf5\xea\xa9\xeaj\xa2\xa1v^\
-\xbb\xfd\x96}sT\x11zyfJ[\x01=SM\
-C\xd4\x82T\x15h\x1fxG\x8d\xcb_\xd9wm\x8a\
-\x0b\xecP\x1c\xd8!\x1aE\xd8\xc1R+$\xd8\xd9\xf2\
-\x05\xf5b9PK\x83\xe5{!\x8f5\xa2E\x87\x1c\
-\xa1E\x8a\x0d;\xd7c\xa3\xb0\x02\xaa\x12T\x15\xd0\x90\
-41\xd1\xac\x04;lv\xb0C4\x8a\xb3\x835(\
-5(\x0f\x95\xcc\xb5\x1e\xeb\xbdP=bi\x1f\xc8=\
-\x9aa\xb1\xbc\x16)\xd9\x0f}/\xdf\x0e\x9d\x83]v\
-\xb1#\xa0\x89\xc3\x8b}\xbb{\xee\x9e\xc0\x0e\x9b\x1d\xec\
-\x10\x8d\xe8v\xa8f`\xfa\xb6\xe5\xb5\x87\xbf(\x99k\
-j\xf0\xf3<uA\xf5\x1a\xe4\x8e\xdao\xc0\x84\xeb\x94\
-\xb5\xb4q\xeb&\xb7\xae\x92zrw\xa0\xd0\xb6Q\x04\
-;t\x0c\xeeO\xe7\x0b\x8b\x0db\x87-\x03v\x88F\
-\x5c;\x84\xf4\xd3vln\x5c/\xd1j?M\xfel\
-z'\x89m\xfb\xe1]\x09\x95\xe9\xf8\xe3\xdd\xfd\xa5\xc3\
-\xdd\xc3\xc7\xb5U\xf6Z\xbano6\xef\xb0?\xdbw\
-\xdd\xee\x0c\x17)\xc3\xd6\x0d\xec\xf0<\x80\x1d\xa2Q\xc4\
-\xdc\xc1R\xeb\xab\xfb\xbd\x9a\xf9+\xeb\x96f\xb5\xf6\x93\
-?\xcd\x0b\xaf\x0f\xbc}b\xf4\xeb_\xa7\x7f\x1b\x7fR\
-\x9e\xa8LN\xcf\xcf\xcc\xce?\xd2V\xfb:\xd2?3\
-\xa0\x85C\xfb\xc0^\x9bM\xa4)\x94\x17\x84\x0e\xaa\x1d\
-9%t\xdd$\xb1\xed!\xec\xb0\xb5\xc1\x0e\xd1\x88h\
-\x07\xcbL\xfbH\xd5\xe0l\xc9\xbcP\x97\xd2U5h\
-+}\xd8g\xb7\x9b\x87\xd2\xb5\xf7\xc1\x19M.jY\
-\x94\x13D\xd2\x9a\xfa\xd2*F\x85\x9b?\x0b\xec\xf0<\
-\x80\x1d\xa2\x11\xdd\x0e\xb6\xd3`\xe2\x90\xa0d\xd6\x94\xc1\
-~5\xc7\xc2*\xbaTK<}\xaayD\x9a\xd2\xf5\
-\xeb\x94\xe4\xf2\x84\x12\xfb\xd8\xc8'*\xa6g\x91\xab\x98\
-\x0d;\x8e\x1d\xb66\xd8!\x1aE\xd8\xa1\x5c\x99x\xed\
-\xf6[\xf5^HI.R\x86\xd7y\xabb\xb5lk\
-;\xe1\x88a\xd78%\x88]\xb7;\xebf\x10\xc9\xf4\
-D\x07e\x1c\x15P\xb1&O\xc4\x9a\xc5\x0e[\x1b\xec\
-\x10\x8d\x22\xecpe\xea\x9a2mi\x0e'\xfb:\xb2\
-\xadv\xbbJ\xeb.Ti\x14\xf6\xa8\xb6V\xfe\xbb\xf2\
-\x0f\xb6*\xc9\xa1\xdc\xd6T\xc5\xee\x91\x9b\xfd\x81O\xab\
-\x9b%)\xb0<;4\xc1\x0ah\xdbr`\x87\xe2\xc0\
-\x0e\xd1\x88k\x07\xdby\x7f\xe4\xa3\x5c\xee\x19J\xec\xf6\
-\x81w\xec\x1b_\xd9Lk\x1eV\xc6\xb6\x95\x85\xca\xde\
-\xa1\x83R\xccR\xf5\xc8;In\x87o\x9a\x86\x96\x1b\
-\xb5\xdf\xc4\x0ev\xf3\x9b\xe6\xd1\xa8\xd9\xe5\x07v(\x0e\
-\xec\x10\x8d\xe8v\x98\x9d\x7f\xa4\xf9\x7f\xdd+|r\xe1\
-P/\xef\xbd\x0f\xce\x84\xc2\xcb\xcf1+i\xeb\x0b\xb5\
-\x90~\x1abI\xfb&\x0b-g\x94oI\xc9\xf4\xb9\
-X-m\xe7\x16\x9ehF`h_#\xec\xf8\xe3\xdd\
-\xdc\xa7\xa1\xc2\xc5\x8b\x87\xf3\xd3\x12D(\x9fC\x0f\x85\
-f\xb5m9\xb0Cq`\x87hD\xb7\xc3\xaf\xd3\xbf\
-\xa5\xa9\x9b\x9f\xff+\x01\xa4\x8c\xc1\xd9\x92\x954\xd2z\
-\xcb\x8aP~ln\x5c\x16\xc8\xe5\xb6\x90}\xfey\xeb\
-\x15-jTF\xcf%\x94\x97\x0b\xbaJG:\x07\xbb\
-\xde\xbc\xf3^\xa0\xfe\xd2\xa6\xd0\xf0\xe4\xb5l\xb1,\x9a\
-\xb3(\x93\xbf\xf8\xeb[9\xc2\xc6\xa3m\xcb\x81\x1d\x8a\
-\x03;D#\x96\x1dB6\x9e\x1c;\x9dN\xdas\xaf\
-\xed\x1d\xdb\xfb^=4\xdc\xa3Wf+\x9cVZA\
-X\x15\xdb\x1e(\x1d\xdd\xd1\xd7\x96m_\xdd\xd9\xdc\xe4\
-\xab\xfb\xbd\xa1\xa4\xed\xc8\x0e\xb2\x89}\x9a[N1j\
-\xc3\xcb\x0e\xb2z$\x94\xc9\xa1G\xd5~\xac\xcb\x96\xd8\
-\xa18\xb0C4\x22\xce\x1d\xac\x05-\xfe\xd5\xe6\xd2^\
-\x92\xd4\xd5\x0b\xbb}'\xc2\xf2\xb6\x85\xec\x0a\xb5R\x01\
-\xe5\xbaH\xe6&\xeaW\xbd\xdbg(Ba\xfd\xf9\xfa\
-\xc0\xdb^\x06\xe6\xe7\x0e\xde\x91E\xf4\x14\xa4\x98\xdc\xad\
-7[\x0e\xecP\x1c\xd8!\x1aq\xe7\x0eB\x93\xf0:\
-;T\xa7\xfd\xe7'.\xa9\xa4\xbd\xa7`\xb5V\x14\xd6\
-\xbev\xae=\xfc\xa5\xd6\xc5b>+\xc14;P\xb2\
-\x95+\x13\xd9\xc25;\xe4\x87\xb4R\xec\xc2\x04v\xd8\
-\xf8`\x87hD\xb4\x83\xb6cs\xe3\xed\x03\xef\xe4R\
-1=\xf5\x93%\xbd\xdd\xd6\xa9\xe5\x8eB\xc2\x8f?)\
-\xa7\x8b\x97\xc5.\x0cK\xe0\xbb\x8fG\xb3\x85\xb1\xc3\xf3\
-\x06v\x88F,;\xd8\x07\xa8\xfbg\x06\xb4\xc8\xcf\xa5\
-\xaeN}%\xa7\xe6\x14\x96\xb7vC\xfa\xb4\xd2\x8a#\
-$\xbc\x1cT\xff\xb6\x88\x12\xf8_\xb7\xfec\x0e\xca\xda\
-\xc1\x0a\xeb\xd1\x80\x95_Z\xbdz$[,\x8b\xec\xb6\
-\xbd\xef\xd5\x0f\xef}\x8e\x1d68\xd8!\x1a\xd1\xec\x90\
-V\xbf<yU\x8b\x88\x5c\xde*\xb5\xb4b\xdf_:\
-<Q\x99T\x99\xd5\xe4\x95\xd5U~\xaa\xb5\xfa\xa4\xd2\
-\x9fzy\xbf8y\xc5J\x06;(\xb1e\x0d\x8d!\
-PSCv\x9c\xc9\xfe\xce\xf4\xc6S.\x12\xdc\x0b\xb7\
-^\x0a\xf7\xf5\xc7\x0e\x1b\x16\xec\x10\x8dxs\x87\xe4\xc3\
-\x08\xb5\xcf2\xe6\xed\xa0\xa4\xed\x1e>\xbe\xfaW]\xab\
-[Y\xa8(K5\xf2z;(\x8d\xb3\x1f\xa9\xd0V\
-\x9d\xbe?\xf2QW\xe9\xc8\x81\xd2\xd1\x80\x14\x96\x1b\xa4\
-\x90\x1a\xda\x07\xf6f\x8be\xd1\x7fi\xdfP\xb7\x1a\xb7\
-;\xe8c\x87\x0d\x0bv\x88F\x14;(U\xac\xfa\xc9\
-\xb1\xd3j0\xd7\x85RQ/\xdd\x9a\x93[\xc9\xd5\xe4\
-\x95\xd5\xd5\xf6\x8b\xbf\xbe\x95\x08\xe4\x9dlG\xca1M\
-\xfe\xf5P(f\xdb\xbf+S\x9a\xb6hkh_9\
-ioR\x064H)L\x83,W&r\xe5\x03:\
-8=?\x93m\xbc\xe5\xc0\x0e\xc5\x81\x1d\xa2\x11k\xee\
-PYH\xe6\x0e\x9f\xfd\xf9\xcd\xb6\xea7,\x02\xd5\xb7\
-3O\x8c~m\xc5V\x93W\xa1\xee\xa9\xf1sJf\
-\xcf\x0em\xca\xf0\x90\xc0\x8d\xfa\xea\x1c\xecRNf\xeb\
-\x9a\xc2l\x90\xcf\x8c\xd5<\x05\x0b\xecP\x1c\xd8!\x1a\
-\xb1\xe6\x0e\xf6\xf5\x84\x0f\xee}\xaa\x1c\xcb%\xad\xd9\xc1\
->\xec\xb0\x9co14\x89\x90\x96g\xcb\x17\xea;2\
-;h\x1da\xcf\xc5\x0ak\x9bC\x86r\xbfgav\
-\xb0\xcfJ\xab\x85l\x95\x1c\xa1\xf1\x96\x03;\x14\x07v\
-\x88F<;$s\x87c#\x9f\xd4\x92vqUo\
-v\xf8\xea~\xaf\x0a\x84\xef)\xb4\x16!9\xbf/\xff\
-\xa8\x8e\xd4r\xe8\xc5P\xa6\xe9\xe9d\xed\x90\x0d;\xa2\
-G\x9b\xd8\xa1Q\xdd\xb8\x81\x1d\x8a\x03;D#\x96\x1d\
-\xec\xaa\xa4^\xb7\xbd\x97\xf4$\xf1\xec\x8b\xdb\xb1\xecp\
-q\xf2J#;\x1c(\x1d\xb5\x19J}Gv\x04;\
-lm\xb0C4\xa2\xd9!\xad\xde\xc0\x0e\xc9\x1b\x8d\xf6\
-V\xc2\xeaW\x16\x96\xb7\x97'\xaf6\xb2\xc3\xfe\xd2a\
-\xec\xf0<\x83\x1d\xa2\x11\xd7\x0e\xdd\xc3\xc7];l\xeb\
-{\xd5n\xcdb\x0b\x90\x96C\x1dY\xde\xfe4u]\
-\xab\x15\xd7\x0e\xfb\x86\xba\xb1\xc3\xf3\x0cv\x88\xc6\xda\xcc\
-\x1d\xd6\xd2\x0e\xcc\x1d\x9es\xb0C4\xd6\xcc\x0e\xb5\x95\
-E\x1c;\x5c\x99\xba\xd6\xc8\x0e\xe9u\x87\xa4\x97\xfa\x0c\
-\xb7#\xd8ak\x83\x1d\xa2\x11\xd7\x0e\xee{\x16\xfa3\
-{\xdda5\x89\xa7\xbaV\xbd\xf1u\x87\xb6C\xc3=\
-\x8d2\xdc\x8e\xe8Q\xec\xb0\x85\xc1\x0e\xd1\x88e\x07\x9b\
-\xcc\xf7\xdc=Q?wP\xe2e\xdf\xd1Lk\xb4\x18\
-\xea\xc8\xf2\xf6\xfc\xc4\xa5Fv\xe8\x1e>no\xa0\xd4\
-g\xb8\x1d\xc1\x0e[\x1b\xec\x10\x8d(vPX\xda\x7f\
-<\xfae\xfa\x11\xc6%]\x98\x1d>\xfb\xf3\x1b+\xb6\
-\x9a\xc4\x0bu\xbf+\xff\xd0\xe8\xb3\x92\x9a\xbf4\xcap\
-\xec\xf0<\x80\x1d\xa2\x11we\xa1\x09B\xfa\xf5\x87l\
-\x17\xd5ORK\x1c*\xa0b\xabI\xbcP\xf7\xe4\xd8\
-\xe9FvP\x86[\xb1\xfa\x8e\xec\x88\xc6\x80\x1d\xb60\
-\xd8!\x1a\xb1\xe6\x0e\xe1\x86\xd1\xb9\xac\x13\xcaa%\x9e\
-^\xd2\x95r\x86Ui!Bz+\x8d\xeb4\xb4\xe4\
-\xf2g(\x9c\x8dP\x1d;la\xb0C4\xa2\xd9!\
-\xad~~\xe2R\xda\xac\xf3\x0d\xeeC\xc3=\xe1\xc7&\
-\xd2\x1a\xad\x84\xd5\xd5\xf2\xe4\xfd\x91\x8f4r\xcf\x0e\xbb\
-\xbf/\xff\x98-\x9c\x0d\xec\xf0<\x80\x1d\xa2\x11\xd7\x0e\
-7\xa6o&?f\xb14\xf1t\xea\xab\x97\xbdC\x07\
-\xc7\xe6\xc6\x93\x92\xabH<K\xda\xd9\xf9Gj\xcd\x9d\
-\xa4HCv\xd3z\x95\xc4\x0e\xcf'\xd8!\x1a\xb1\xec\
-`\xe9\xa4\xd3\xddn\x00\x9d\xedB\xa7\xfe\xce\xf4\x97u\
-\x7f\x9f\xbd\xa32\xea\xa8\xe5\xdc\xb3\x8a\x9a\x83\xa4W\x1c\
-r\xb7o\xa9\xde9.\xfb\x93\x19i\xa5\xc5\xb0#\xd8\
-ak\x83\x1d\xa2\x11\xd7\x0e\xca\xdb\x8e?\xdeU\x9bi\
-\xe3\xf9\x8f<\x84\x9f\xa2Ik\xac8B\xc2+\xa9\xea\
-\xbb0\x94\xe46Cq\xd3\xdb\x0eb\x87\xad\x0dv\x88\
-FD;\xd8\x92a\xdfP\xf7\xf6\xe4\xc6\x8d\xd9^\xaa\
-o[\xd8\xf5B\x954\xd2z+\x88P\xeb\xfc\xc4\xa5\
-\xfa{L+\xc1\x94f\xea\xfd\xef\xcc\xeft\xa6\xf5\x16\
-\xc3\x8e`\x87\xad\x0dv\x88F,;(,\xa3>\x1e\
-\xfd\xd2K\xdd\xe4\xc6\xb3\xdd\x99_\xc1Mk\xac8\xac\
-b\xcf\xdd\x13\x8d\xba\xc8\xde3\xba\xbe\x17;\xa2g\xda\
-\xe8\xdeP\x1a\xbc}p\xa3\xe5\x11.3\xb0Cq`\
-\x87hD\x9c;XF]\x9c\xbc\x92N\xf8\xf3s~\
-\xa1\x1cX\xfd\xefh\xa6w\xa0\xdf\x9b\xcb\xed0=\xb1\
-7,\x1a5n\x075\xc7\xa9\xbf\xa8\xa9\xeaZ\xfbH\
-.\xd8a\xb3\x83\x1d\xa2\x11}\xee\xa0e\xbf\x9a\xad\xff\
-\xb1\x09\xcb\xde\x93c\xa7\xad\x98\xb6\xcb\xcf@+i+\
-\x97\xcb\x93W\x95Ku\xedWW\x16\xfd3\x03I\xc9\
-\x06\xcf%tm7\xbc_Z=\xb1\xc3\xb1H7\xa4\
-\x7ff`\x87\xe2\xc0\x0e\xd1\x88n\x07\xb5\xa3W\xe6\xdc\
-\x9b\x9a5\x92\x1f\xd1\x0f\xd7\x05l\xdb<\x0fC\x81\xb0\
-sh\xb8\xa7\xfe\xe3\x98\xfaSOd\xdfP\xf7\xf8\x93\
-r\xb6p.BSz\xca\xf5vP\xb3\xd9\xdf\x01v\
-[\x88\x15\xd8\xa18\xb0C4\xa2\xdbAqj\xfc\x5c\
--\x81\xb3\xaf\xf0\xc9\xbe\xba\xb3\x8fT+B\xf9Fy\
-\x18RT[\x1b\x9e\x16\x0e\xa9w\xac\xd9\xd0xub\
-b_\xf4\xb2ZV1\x17\xa1\xb5\xf7G>\xca]\xb9\
-\xd0h\x95\xa5o\xdey/\xfb\x96\x87\xdbH\x94\xc0\x0e\
-\xc5\x81\x1d\xa2\x11\xd7\x0e\x96Nw\x1f\x8f\xaaY\xbd\x1a\
-\xe7\xfaJiW2|W\xfe\xc1\xaaT\x16\xe6\xc3\x0f\
-\xe7eS1\x1c\x11\x1a\x98\xfd\xf9\xeb\xf4o\x9az\xc8\
-\x0eK\xbd\x93L\x1c\xb4\xd0\xd0C\xcf\xfc\x9d\xce\xd0\xc5\
-\x17\x7f}\x9b\x8e\xd0Z\xa8\xa2\x01\xeb\xa0]\x19\xa9,\
-T\xd4\xce\x22O5\xce\xf9d\xb4\xb5\xc1\x84\xa6Z\x0b\
-\xb5\xa3-v(\x02\xec\x10\x8d\x88vPX\xce(\xb5\
-z\xee\x9eH\x7f\xd8\x22\xf7\x99\xa5d\xdf.\x19\xf4>\
-8c\xef_(\xd4u}6\xa6\xe2\xa8\xde*FG\
-.N^Q\x22\xd5RzI\x9b\xe9\xa2`\xb7\xa6\x03\
-\xea=`\x15s\x11\x8e\x9f-_\xd8\xee,O\x92K\
-\x0f'\xd2\x9b\xd6[\xb1F\xd1\xa8\xfd\xe5\x87\x9e\x91\xb6\
-\xd8\xa1\x08\xb0C4\x22\xda!\x9b\x997\xa6o*\xfd\
-4\xe1\xcfu\x17\x12[yx\xa0t\xf4\xf2\xe4\xd5\xd1\
-\xb9\xfbV\xdd\x8d\x89\xca\xa4\x9a\xfa\xf0\xde\xe7Z\xaa\xd4\
-\xcd\x1a\xaa\xfb:\xae\x8e4\xb3Py{\x16\x8d\xb27\
-;<\x0d\xc0\x13Mr\x87\x08u\xa7\x81i&\xd2?\
-3\xa0fU\xf8\xe7\xa9\xebW\xa6\xae\x9d\x9f\xb8\xa4#\
-vq\xb4Q\x17\xcb\x0c\xecP\x1c\xd8!\x1aq\xe7\x0e\
-\x0aK\x1b\xbd\xfc\xda}\xa2RAd3P$\x7f\xea\
-\xb8\x12^\xbdk\xa9\xff\xc1\xbdO5\x95\xf8\xbe\xfc\xa3\
-rR\xa9hyxj\xfc\x9c^\xc6\xf7\x97\x0e\xeb%\
-]\xedh[\x97\xccU\xd4\x88\xf2Y\x9d6W\x83\x22\
-\xd8AJr\x9b2A\xa8;\x8dMK\x15\xb1\xebv\
-g\xed)\xb4\xbfp\xeb%\xcd\x89\xf8\x1d\xcd\x0d\x0ev\
-\x88F\x5c;X\xceXS\xbf\xcf\xdeQv\xd5N\xfd\
-zA$3y=\xa4\x01X6\xda\x14@\xd9(\xf4\
-h8\xae\xf2i~:\x8d\xa8\xba^\xea\x95`\xe1\xad\
-\x8a\xb0m\x14\xf6\xe8\xec\xfc#=kuQ\x97\x96I\
-\x17\xeaNc\xd3\x0aHCJ\xd7A\xd5\x83*/;\
-\xf0\x1b\xdc\x1b\x1c\xec\x10\x8d\xb8vP(m\x84\xb5\xa6\
-\xe9@\xfa\xd6@\x92\xc6u\xb9]=\xa2\x87j\xf3\x82\
-\xe4\x92DH\xc8\xda\xf1\xe0\x85\x5c\xf5D.i^u\
-\xd8g\x1c,]\x9b'\xad\x8d\xcd\xca|W\xfeA\xf6\
-1\x13\xe5Z\xb6#\xe9\x00\x12\xec\xb8:\xd2Zi\xf5\
-\x1f\xf7\xb4\xc0\x0e\xc5\x81\x1d\xa2Q\x84\x1dlk;'\
-\xc7N\xdb\xeb\x7f\x9a\xe7\xb9<4\xdc\x83\x81\xfaG\x93\
-\xecU:\xd9+\xffOS\xd7Cw\xd6\xe33\xc3\x8a\
-\x8d\xcd\x8d\xb7\x0f\xbc\xa3\xcc\xac-\x1cr\xbd\xe4\xb1\x1e\
-\xbbJG&*\x93\xa1\x91\x96\x03;\x14\x07v\x88F\
-t;(,s\xd2tM\xa2\xf7\xc1\x19\xcd\x08\xd4Q\
-\x83\x05Bk\xb4w\x0ev\xdd\x98\xbeY\xeb\xa8\xdac\
-\xdaa\xb3\xb0\xc2V\xf2\xe2\xe4\x95m}\xbb\xd5ZM\
-\x10\xcd\x06\xa6\x04V\x1a\xef\x1d:X\xaeLX;i\
-{-\x06v(\x0e\xec\x10\x8d\x22\xec\xa0\xb0\xe4\x09y\
-\xf8\xf3\xd4\xf57\xef\xbc\xf7\xcf[\xaf\xa8\xc7\x06\x99P\
-\x9f\x99\xd9#K\x1e\xb5\x97\xf1\xb3\xe5\x0bj\xb9R\xbb\
-\x8d\xed\x8a\xd25TI\xdf\xdal\xd3\xecFm6O\
-Q\xcd}\xb4\xe4\xe9\x18\xdc\xdf\xe4\x1b\xe2\xcb\x0f\xecP\
-\x1c\xd8!\x1a\x05\xd9A\x112\xd0v\xf4\x92{j\xfc\
-\xdc\xeb\x03o+\x15\x95\x0f\xd9\x0b~\x96\x99\xf5d\xc6\
-\x99\xb7\x83\x1ay\x7f\xe4\xa3\xec\x05\xc2\xe5\xa7k\xae|\
-\xff\xcc\xc0\xb1\x91O\xb6'o\xa0$\xef\xa1\xecH\xd8\
-\x93\xa1M\x07\x0du*\xc7e\xaf\x80\xb6\x1c\xd8\xa18\
-\xb0C4\x0a\xb5C\xc8\xc3\xd0\xb8\x16\xed\x9a\xcfw\x0f\
-\x1f\xdfu\xbbS)a+\x0ee\x9d\xb0\xe4\xacaG\
-\x92D\xad\x0d5\x08\xa2*\x94\x17\xfbv\x9f\x9f\xb8\xa4\
-6\xd5\xf8Js5\x0c\xcc\xfe\xd4\x04dt\xee\xfe\xe5\
-\xc9\xab'F\xbf\xd6\xd8\xbaJGd\x01\xad\x5c\xf6\x0d\
-u\xcbA\x9f\xfd\xf9\x8d\xa6\x18\xd7\x1e\xfe\xa22\xd3\xf3\
-3\xf6\x5cV\xdac.\xb0Cq`\x87h(\x03\x0b\
-\xb2\x83E\xc8\xc3\x5c:\xfd]\x99\xba1}\xb3\xf7\xc1\
-\x99\x0f\xef}~\xa0tT\xebyKHC\xfb:\xd2\
-s\xf7\xc4\xa1\xe1\x9et\x9c\xa6\x86EA(\x9d\x94T\
-*\xd9\xf2\xd7\x22\x1a\x0d\xcc\xa2\xd1q\x8b&\x0f-3\
-\xb0Cq`\x87h\x14m\x07E\xc84\xdbi\x94Z\
-z\x01\xd7JA/\xce\xc2>q\xa4\xd0\xbe\x14\x90\x99\
-A\x04\xaa\xdf\xbc\xfa\xe2\xafo\xadd\x93\x96\x1b\x85\x95\
-\xb7\x8a\xcd\xabg\xcbX1\xdb\xb6\x1c\xf6\xdf\x1e\x9c-\
-a\x87\xe8`\x87h\xac\x81\x1d,BR\xe5P\xbf\xc2\
-\xf6\xad\xa4\x85\xfe\xb4\xef;h%\xa2A\xa6\x13\x870\
-w\xa8\x92&U{\xcbw\x94\xb1\xb0*\xa1zsB\
-\xf9\x96\xc3\xda\x09v\xe8\xf8\x03;D\x06;Dc\xcd\
-\xec\xe0F\xc87\x17{H\x03\xd3\x08\xd3OUi\xc0\
-YA$\xfb:n\xe3\x0f\xed\xd8\xfe\xc6\x8c0<\xfb\
-oke\xd1>\xb0w'v\x88\x0av\x88\xc6\xfa\xda\
-\xa1y\x84l\xff}\xf6N\x93\x17Xe\x97\xe6\x17*\
-fOa#\x0b\xc2\xc6\x16\xb6Z\x16mK\xdeL\xb5\
-\x8f\x81@\x1c\xb0C46\x85\x1d\xb4\xfd\xec\xcfo^\
-\xec\xdb\xed~\xaeQO\xa1s\xb0+\xfb\x01g\xdbn\
-\xb4\xc8\x8d\xad\xf7\xc1\x19\xa9\xa1\xf6,\xf2O\x0aZ\x06\
-;Dc\x83\xdbA[\xfb\xc6\xf4\xe8\xdc}\xfb\xe0\xb3\
-]k\xa8\x8d?\xd9\xd1\x11=\x8b\x93c\xa7\xd3\xc2\xf3\
-\xaa\xb5\x01\xed\x10\xd4`;\xa7\xc6\xcfIv\xd9g\x01\
-\xb1\xc0\x0e\xd1\xd8\xf8s\x07ac;[\xbe\xe0\xcd\xc3\
-\x93w7\xb5\xb8\xd0\x02>\xfc\xd6VZ{\x03\x85\x19\
-\xc1\x9e\x8bv\xec\x89\x84\xf1g\x9e\x0bD\x00;Dc\
-#\xdbA\x11\xf2J\xdb\xd9\xf9G]\xa5#\xe9][\
-\x96\xac/\x12;\xa47_\xd0\x5c]\xc5\xc2\xad\xe86\
-B\x04#\x84\x9d\xa5?\xd5\x83\x1a\xe2\x83\x1d\xa2\xb1\xc1\
-\xed\xa0\xb0\xa4\xb2\xe1\xfd4u\xdd\xbe\xef #\x98\x14\
-\xb4\xd6\xd0S\xd8_:\xfc\xf3\xd4\xf5\x87\xf3\xd3Qn\
-\xdc\x141rj\xb88yE\x03\xb6\xf9\x0ej(\x08\
-\xec\x10\x8d\x8do\x07\x85e\x97\x8d0\xfdND\x9b\xbc\
-\xa0\xad\x12L^\xb8\xf6\xf0\x97\xf0\xe9\xa9\x90\x87\x1b!\
-l$aH\x97'\xaf\xdaWKPC\xa1`\x87h\
-l\x16;\x84\xed\xdd\xc7\xa3\xf6\x1d\x8dC\xc3=\xf2\x82\
-\x1d\xb4GC\xb1pp\x1d#\x0c\xc3\xfe\xb1\x9a\xda\xc8\
-\x086\xebA\x0d\x85\x82\x1d\xa2\xb1)\xec\xa0\x08\x99\xaf\
-\xad\xdd\x812=\x9c\x1c\x09\xd8\x9fv|}#\x8c\xc7\
-\xfe\xab7\xa6o\xda\x82(\xfd\x9f\xa3\x86b\xc1\x0e\xd1\
-\xd8,vP\xe4\xf2?d\xa0\xed\xdb\xceF\x8800\
-\xbb\xe3\xfe\xad\x99~MvvV\xbf*\x82\x1a\x0a\x07\
-;Dcs\xd9\xa1\x9au\xb5\xaff\xd8\xc1\xf4\xc1\x8d\
-\x12aT\xf6\xff\xec\x9f\x19\xf0~\xa1\x07\x0a\x04;D\
-c\x13\xd9\xc1B\x89\xb71\xbd\xa0\xc8\xa9\xe1\xf7\xd9;\
-\xf6s\xe1\xb5\xb7`\xb1\xc3Z\x80\x1d\xa2\xb1\xe9\xec\xb0\
-\x91\xc3\xec`\xff\xc9\xda\xbd\x1b\xec\x17\x03Q\xc3\xda\x81\
-\x1d\xa2\x81\x1dbEV\x0d\xc3\x8fG:\x07\xbb\xf4\xbf\
-\xad-(P\xc3\xda\x81\x1d\xa2\x81\x1d\xa2DV\x0d\xa3\
-s\xf7\xf7\x0e\x1d\xcc\xa8a\xc9?\x1c\x8a\x06;D#\
-k\x07\x9d\xe2\xd02\xa6\x86\xf1'\xe5\xfd\xa5\xc3\xdb\xab\
-?\xe1\x8b\x1a\xd6\x01\xec\x10\x0d\xd9\xe1\xd0p\x8fNk\
-b\xf5Q\xaeL\x1c(\x1d\xcd|U\x0c;\xac\x03\xd8\
-!\x1a\xb2\x83N\xe8\xcaB\xe5\xef\xca\x94\xdd\xd3\x11V\
-\xca\xc3\xf9\xe9\xd9\xf9Gcs\xe3\xdd\xc3\xc7k_\x12\
-\xd3\xff\x165\xac\x0f\xd8!\x0av\xfaj\x0b\x11\xd8\x99\
-\xfe\xfe\xc5\xd2\xff-\xac\x03\xd8!\x22\x9c\xc7\xd1\xe1_\
-\xba\x9e`\x87\x88p*G\x84\x7f\xe6\xfa\x83\x1d\x00\xc0\
-\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00\
-|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\
-\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\x01\
-\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\
-\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\xd8\
-\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\
-\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00>\
-\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\xe0\
-\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\x00\
->\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\x00\
-\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\x00\
-\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\x0e\
-\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\xec\
-\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\xc1\
-\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\x1f\
-\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\xf0\
-\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\x00\
-\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\x00\
-\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\x00\
-\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\x07\
-\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0fv\
-\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8`\
-\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\x07;\x00\x80\x0f\
-v\x00\x00\x1f\xec\x00\x00>\xd8\x01\x00|\xb0\x03\x00\xf8\
-`\x07\x00\xf0\xc1\x0e\x00\xe0\x83\x1d\x00\xc0\xe7\x1f\xff\xfe\
-\xef\x1b\x00\x00y\xfe\xfb\xc6\xff\x034\xc3}\xffR)\
-\xdb\xa4\x00\x00\x00\x00IEND\xaeB`\x82\
-"
-
-qt_resource_name = b"\
-\x00\x08\
-\x08\x01Z\x5c\
-\x00m\
-\x00a\x00i\x00n\x00.\x00q\x00m\x00l\
-\x00\x10\
-\x0d\x0d\xd3\xc7\
-\x00q\
-\x00t\x00_\x00l\x00o\x00g\x00o\x00_\x00r\x00e\x00c\x00t\x00.\x00p\x00n\x00g\
-"
-
-qt_resource_struct = b"\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\
-\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01}k\x86\xb3\x9c\
-\x00\x00\x00\x16\x00\x00\x00\x00\x00\x01\x00\x00\x09G\
-\x00\x00\x01}k\x86\xb3\x9c\
-"
-
-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()
index 648cfcf5ccdd4cb0ee1bf32a218d9b74cc385fb7..ab4e6a6de81daa7c36195bc8fde48c8cb76189c2 100644 (file)
@@ -42,8 +42,8 @@ Window {
             position: Qt.vector3d(0, -200, 0)
             source: "#Cylinder"
             scale: Qt.vector3d(2, 0.2, 1)
-            materials: [ DefaultMaterial {
-                    diffuseColor: "red"
+            materials: [ PrincipledMaterial {
+                    baseColor: "red"
                 }
             ]
         }
@@ -52,8 +52,8 @@ Window {
             position: Qt.vector3d(0, 150, 0)
             source: "#Sphere"
 
-            materials: [ DefaultMaterial {
-                    diffuseColor: "blue"
+            materials: [ PrincipledMaterial {
+                    baseColor: "blue"
                 }
             ]
 
index affcccc3ee6d09f0a268809e9db143f35480a59a..e1e6127bb514b37f4195027394441e057426744b 100644 (file)
@@ -7,14 +7,18 @@ import QtQuick.Controls
 
 ItemDelegate {
     id: delegate
-
     checkable: true
 
+    required property string fullName
+    required property string address
+    required property string city
+    required property string number
+
     contentItem: ColumnLayout {
         spacing: 10
 
         Label {
-            text: fullName
+            text: delegate.fullName
             font.bold: true
             elide: Text.ElideRight
             Layout.fillWidth: true
@@ -34,7 +38,7 @@ ItemDelegate {
             }
 
             Label {
-                text: address
+                text: delegate.address
                 font.bold: true
                 elide: Text.ElideRight
                 Layout.fillWidth: true
@@ -46,7 +50,7 @@ ItemDelegate {
             }
 
             Label {
-                text: city
+                text: delegate.city
                 font.bold: true
                 elide: Text.ElideRight
                 Layout.fillWidth: true
@@ -58,7 +62,7 @@ ItemDelegate {
             }
 
             Label {
-                text: number
+                text: delegate.number
                 font.bold: true
                 elide: Text.ElideRight
                 Layout.fillWidth: true
@@ -74,6 +78,7 @@ ItemDelegate {
             PropertyChanges {
                 // TODO: When Qt Design Studio supports generalized grouped properties, change to:
                 //       grid.visible: true
+                // qmllint disable Quick.property-changes-parsed
                 target: grid
                 visible: true
             }
index d906f00e62521ac8685f3c213b596e48573afa2f..3f287447ef852d6cc149b62ca3f5e42429e13926 100644 (file)
@@ -41,5 +41,9 @@ Dialog {
         id: form
     }
 
-    onAccepted: finished(form.fullName.text, form.address.text, form.city.text, form.number.text)
+    onAccepted: {
+        if (form.fullName.text && form.address.text && form.city.text && form.number.text) {
+            finished(form.fullName.text, form.address.text, form.city.text, form.number.text);
+        }
+    }
 }
index 0b7af32b5b5b8fb44be9cb0d3f573ef0e3dca7bd..121b38f352041f731b859970ed8d46ddebb86919 100644 (file)
@@ -17,10 +17,10 @@ ApplicationWindow {
     ContactDialog {
         id: contactDialog
         onFinished: function(fullName, address, city, number) {
-            if (currentContact == -1)
+            if (window.currentContact === -1)
                 contactView.model.append(fullName, address, city, number)
             else
-                contactView.model.set(currentContact, fullName, address, city, number)
+                contactView.model.set(window.currentContact, fullName, address, city, number)
         }
     }
 
@@ -35,23 +35,23 @@ ApplicationWindow {
             font.bold: true
             width: parent.width
             horizontalAlignment: Qt.AlignHCenter
-            text: currentContact >= 0 ? contactView.model.get(currentContact).fullName : ""
+            text: window.currentContact >= 0 ? contactView.model.get(window.currentContact).fullName : ""
         }
         MenuItem {
             text: qsTr("Edit...")
-            onTriggered: contactDialog.editContact(contactView.model.get(currentContact))
+            onTriggered: contactDialog.editContact(contactView.model.get(window.currentContact))
         }
         MenuItem {
             text: qsTr("Remove")
-            onTriggered: contactView.model.remove(currentContact)
+            onTriggered: contactView.model.remove(window.currentContact)
         }
     }
 
     ContactView {
         id: contactView
         anchors.fill: parent
-        onPressAndHold: {
-            currentContact = index
+        onPressAndHold: function(index) {
+            window.currentContact = index
             contactMenu.open()
         }
     }
@@ -63,7 +63,7 @@ ApplicationWindow {
         anchors.right: parent.right
         anchors.bottom: parent.bottom
         onClicked: {
-            currentContact = -1
+            window.currentContact = -1
             contactDialog.createContact()
         }
     }
index 3b82b681e0e556e6a5f69201102bff4023a76c46..707888e7b85cd67228fde77573f72d30d17bd28d 100644 (file)
@@ -1,6 +1,8 @@
 // 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 Backend
@@ -25,6 +27,9 @@ ListView {
     delegate: ContactDelegate {
         id: delegate
         width: listView.width
+
+        required property int index
+
         onPressAndHold: listView.pressAndHold(index)
     }
 
index 3a62409a8219f330e1abc6b8123856dad9956f91..1ed587abd7a575d40eaa0c0271b79aed800c67f8 100644 (file)
@@ -7,9 +7,11 @@ import QtQuick.Controls
 ToolBar {
     id: background
 
+    required property string section
+
     Label {
         id: label
-        text: section
+        text: background.section
         anchors.fill: parent
         horizontalAlignment: Qt.AlignHCenter
         verticalAlignment: Qt.AlignVCenter
index 9f17786ce3ab18ac111e462d1f2ccb3634f9eec6..848ce54c63333545fc205ea818655da9bfaf3a9e 100644 (file)
@@ -80,7 +80,7 @@ class ContactModel(QAbstractListModel):
         default[ContactModel.ContactRole.NumberRole] = QByteArray(b"number")
         return default
 
-    @Slot(int)
+    @Slot(int, result="QVariantMap")
     def get(self, row: int):
         contact = self.m_contacts[row]
         return {"fullName": contact.fullName, "address": contact.address,
@@ -101,11 +101,11 @@ class ContactModel(QAbstractListModel):
             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])
+        roles = [ContactModel.ContactRole.FullNameRole,
+                 ContactModel.ContactRole.AddressRole,
+                 ContactModel.ContactRole.CityRole,
+                 ContactModel.ContactRole.NumberRole]
+        self.dataChanged.emit(self.index(row, 0), self.index(row, 0), roles)
 
     @Slot(int)
     def remove(self, row):
index 7f7798ed8dd983d7c5dd076df408db9f334a1854..36f2ac3b28372e7883a1b7b8a012e555067f68c7 100644 (file)
@@ -1,6 +1,5 @@
 // Copyright (C) 2023 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
 import QtQuick
 import QtQuick.Controls.Basic
 import QtQuick.Layouts
@@ -22,7 +21,7 @@ ApplicationWindow {
     visible: true
     color: Colors.background
     flags: Qt.Window | Qt.FramelessWindowHint
-    title: qsTr("File System Explorer Example")
+    title: qsTr("File System Explorer")
 
     function getInfoText() : string {
         let out = root.currentFilePath
index 178bf03e44a0407fa5ba764dcf15d0c99c3ae281..0d308a2a1d1391a5ce0afb8b9ebd0796f85b376d 100644 (file)
@@ -16,7 +16,7 @@ ApplicationWindow {
         id: menuBar
 
         dragWindow: root
-        implicitHeight: 27
+        implicitHeight: 30
         infoText: "About Qt"
     }
 
index 80f7c04c57242ac6a49ba3fc17753b46154170fa..2f995c88c932e197d61532ff101eaed6e9a88229 100644 (file)
@@ -36,6 +36,7 @@ Rectangle {
             Layout.preferredWidth: fontMetrics.averageCharacterWidth
                 * (Math.floor(Math.log10(textArea.lineCount)) + 1) + 10
             Layout.fillHeight: true
+            Layout.fillWidth: false
 
             interactive: false
             contentY: editorFlickable.contentY
index 0df65bf82f58f293499843980059d206d28b44d4..5d3b68b3510864bd1b59080ad8c5bcba50491b4e 100644 (file)
@@ -1,6 +1,7 @@
 // Copyright (C) 2023 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
+import QtQml
 import QtQuick.Controls
 import FileSystemModule
 
index aac53039426425e12ff921b1c2669fba9ba5659f..f739e0f93044b4b41dde1f45015c97db9ebcadbe 100644 (file)
@@ -50,6 +50,8 @@ Rectangle {
         id: tabBarComponent
 
         Layout.fillWidth: true
+        Layout.fillHeight: false
+
         // ButtonGroup ensures that only one button can be checked at a time.
         ButtonGroup {
             buttons: tabBarComponent.contentChildren
index b1f684600d34136b008f64fbb57220ad89193dc1..f94e68a8accbfee786efda02603bddd53b302d81 100644 (file)
@@ -1,8 +1,9 @@
 module FileSystemModule
+
 Main 1.0 Main.qml
 About 1.0 qml/About.qml
-Editor 1.0 qml/Editor.qml
 MyMenu 1.0 qml/MyMenu.qml
+Editor 1.0 qml/Editor.qml
 Sidebar 1.0 qml/Sidebar.qml
 MyMenuBar 1.0 qml/MyMenuBar.qml
 singleton Colors 1.0 qml/Colors.qml
index d454cf53e4aa6119452d4668bae70b7133dac938..a7660087dce95b94a0a6f35c6f0c2d6979fdfd84 100644 (file)
@@ -35,9 +35,11 @@ if __name__ == "__main__":
 
     engine = QQmlApplicationEngine()
 
-    built_in_styles = ["Basic", "Fusion", "Imagine", "Material", "Universal"]
+    built_in_styles = ["Basic", "Fusion", "Imagine", "Material", "Universal", "FluentWinUI3"]
+
     if platform.system() == "Darwin":
         built_in_styles.append("macOS")
+        built_in_styles.append("iOS")
     elif platform.system() == "Windows":
         built_in_styles.append("Windows")
     engine.setInitialProperties({"builtInStyles": built_in_styles})
index 5b5fe35da40cdd971a6b5b3b1e37a83897c5ac82..f285c24bc5b702481139edbcf531b69f57705b13 100644 (file)
@@ -6,31 +6,39 @@
               "qtquickcontrols2.conf",
               "ToolBar.qml",
               "+Material/ToolBar.qml",
+              "pages/BusyIndicatorPage.qml",
+              "pages/ButtonPage.qml",
+              "pages/CheckBoxPage.qml",
               "pages/ComboBoxPage.qml",
+              "pages/DelayButtonPage.qml",
+              "pages/DelegatePage.qml",
+              "pages/DialogPage.qml",
+              "pages/DialPage.qml",
               "pages/FramePage.qml",
+              "pages/GalleryConfig.qml",
+              "pages/GroupBoxPage.qml",
+              "pages/MenuBarPage.qml",
+              "pages/MonthGridPage.qml",
+              "pages/PageIndicatorPage.qml",
+              "pages/ProgressBarPage.qml",
+              "pages/RadioButtonPage.qml",
+              "pages/RangeSliderPage.qml",
+              "pages/ScrollablePage.qml",
+              "pages/ScrollBarPage.qml",
+              "pages/ScrollIndicatorPage.qml",
+              "pages/SearchFieldPage.qml",
               "pages/SliderPage.qml",
-              "pages/TumblerPage.qml",
               "pages/SpinBoxPage.qml",
-              "pages/ProgressBarPage.qml",
-              "pages/DelegatePage.qml",
+              "pages/SplitViewPage.qml",
               "pages/StackViewPage.qml",
-              "pages/DialPage.qml",
-              "pages/PageIndicatorPage.qml",
               "pages/SwipeViewPage.qml",
+              "pages/SwitchPage.qml",
               "pages/TabBarPage.qml",
+              "pages/TableViewPage.qml",
+              "pages/TextAreaPage.qml",
               "pages/TextFieldPage.qml",
-              "pages/GroupBoxPage.qml",
-              "pages/RadioButtonPage.qml",
-              "pages/ButtonPage.qml",
-              "pages/ScrollIndicatorPage.qml",
-              "pages/ScrollablePage.qml",
-              "pages/DialogPage.qml",
+              "pages/ToolBarPage.qml",
               "pages/ToolTipPage.qml",
-              "pages/CheckBoxPage.qml",
-              "pages/TextAreaPage.qml",
-              "pages/RangeSliderPage.qml",
-              "pages/DelayButtonPage.qml",
-              "pages/SwitchPage.qml",
-              "pages/ScrollBarPage.qml",
-              "pages/BusyIndicatorPage.qml"]
+              "pages/TreeViewPage.qml",
+              "pages/TumblerPage.qml"]
 }
index 65851f8c9a0513d0c63d84e37568123fd9efe4d1..4b699038ddb66bb420e5ae59e9eec467825329eb 100644 (file)
@@ -15,10 +15,10 @@ ApplicationWindow {
     width: 360
     height: 520
     visible: true
-    title: "Qt Quick Controls"
+    title: qsTr("Qt Quick Controls")
 
     //! [orientation]
-    readonly property bool portraitMode: window.width < window.height
+    readonly property bool portraitMode: !orientationCheckBox.checked || window.width < window.height
     //! [orientation]
 
     function help() {
@@ -46,7 +46,7 @@ ApplicationWindow {
     }
 
     Shortcut {
-        sequence: StandardKey.HelpContents
+        sequences: [StandardKey.HelpContents]
         onActivated: window.help()
     }
 
@@ -63,11 +63,6 @@ ApplicationWindow {
         }
     }
 
-    Shortcut {
-        sequence: "Menu"
-        onActivated: optionsMenuAction.trigger()
-    }
-
     Action {
         id: optionsMenuAction
         icon.name: "menu"
@@ -87,7 +82,7 @@ ApplicationWindow {
 
             Label {
                 id: titleLabel
-                text: listView.currentItem ? (listView.currentItem as ItemDelegate).text : "Gallery"
+                text: listView.currentItem ? (listView.currentItem as ItemDelegate).text : qsTr("Gallery")
                 font.pixelSize: 20
                 elide: Label.ElideRight
                 horizontalAlignment: Qt.AlignHCenter
@@ -104,15 +99,15 @@ ApplicationWindow {
                     transformOrigin: Menu.TopRight
 
                     Action {
-                        text: "Settings"
+                        text: qsTr("Settings")
                         onTriggered: settingsDialog.open()
                     }
                     Action {
-                        text: "Help"
+                        text: qsTr("Help")
                         onTriggered: window.help()
                     }
                     Action {
-                        text: "About"
+                        text: qsTr("About")
                         onTriggered: aboutDialog.open()
                     }
                 }
@@ -138,32 +133,39 @@ ApplicationWindow {
             anchors.fill: parent
 
             model: ListModel {
-                ListElement { title: "BusyIndicator"; source: "qrc:/pages/BusyIndicatorPage.qml" }
-                ListElement { title: "Button"; source: "qrc:/pages/ButtonPage.qml" }
-                ListElement { title: "CheckBox"; source: "qrc:/pages/CheckBoxPage.qml" }
-                ListElement { title: "ComboBox"; source: "qrc:/pages/ComboBoxPage.qml" }
-                ListElement { title: "DelayButton"; source: "qrc:/pages/DelayButtonPage.qml" }
-                ListElement { title: "Dial"; source: "qrc:/pages/DialPage.qml" }
-                ListElement { title: "Dialog"; source: "qrc:/pages/DialogPage.qml" }
-                ListElement { title: "Delegates"; source: "qrc:/pages/DelegatePage.qml" }
-                ListElement { title: "Frame"; source: "qrc:/pages/FramePage.qml" }
-                ListElement { title: "GroupBox"; source: "qrc:/pages/GroupBoxPage.qml" }
-                ListElement { title: "PageIndicator"; source: "qrc:/pages/PageIndicatorPage.qml" }
-                ListElement { title: "ProgressBar"; source: "qrc:/pages/ProgressBarPage.qml" }
-                ListElement { title: "RadioButton"; source: "qrc:/pages/RadioButtonPage.qml" }
-                ListElement { title: "RangeSlider"; source: "qrc:/pages/RangeSliderPage.qml" }
-                ListElement { title: "ScrollBar"; source: "qrc:/pages/ScrollBarPage.qml" }
-                ListElement { title: "ScrollIndicator"; source: "qrc:/pages/ScrollIndicatorPage.qml" }
-                ListElement { title: "Slider"; source: "qrc:/pages/SliderPage.qml" }
-                ListElement { title: "SpinBox"; source: "qrc:/pages/SpinBoxPage.qml" }
-                ListElement { title: "StackView"; source: "qrc:/pages/StackViewPage.qml" }
-                ListElement { title: "SwipeView"; source: "qrc:/pages/SwipeViewPage.qml" }
-                ListElement { title: "Switch"; source: "qrc:/pages/SwitchPage.qml" }
-                ListElement { title: "TabBar"; source: "qrc:/pages/TabBarPage.qml" }
-                ListElement { title: "TextArea"; source: "qrc:/pages/TextAreaPage.qml" }
-                ListElement { title: "TextField"; source: "qrc:/pages/TextFieldPage.qml" }
-                ListElement { title: "ToolTip"; source: "qrc:/pages/ToolTipPage.qml" }
-                ListElement { title: "Tumbler"; source: "qrc:/pages/TumblerPage.qml" }
+                ListElement { title: qsTr("BusyIndicator"); source: "qrc:/pages/BusyIndicatorPage.qml" }
+                ListElement { title: qsTr("Button"); source: "qrc:/pages/ButtonPage.qml" }
+                ListElement { title: qsTr("CheckBox"); source: "qrc:/pages/CheckBoxPage.qml" }
+                ListElement { title: qsTr("ComboBox"); source: "qrc:/pages/ComboBoxPage.qml" }
+                ListElement { title: qsTr("DelayButton"); source: "qrc:/pages/DelayButtonPage.qml" }
+                ListElement { title: qsTr("Dial"); source: "qrc:/pages/DialPage.qml" }
+                ListElement { title: qsTr("Dialog"); source: "qrc:/pages/DialogPage.qml" }
+                ListElement { title: qsTr("Delegates"); source: "qrc:/pages/DelegatePage.qml" }
+                ListElement { title: qsTr("Frame"); source: "qrc:/pages/FramePage.qml" }
+                ListElement { title: qsTr("GroupBox"); source: "qrc:/pages/GroupBoxPage.qml" }
+                ListElement { title: qsTr("MenuBar"); source: "qrc:/pages/MenuBarPage.qml" }
+                ListElement { title: qsTr("MonthGrid"); source: "qrc:/pages/MonthGridPage.qml" }
+                ListElement { title: qsTr("PageIndicator"); source: "qrc:/pages/PageIndicatorPage.qml" }
+                ListElement { title: qsTr("ProgressBar"); source: "qrc:/pages/ProgressBarPage.qml" }
+                ListElement { title: qsTr("RadioButton"); source: "qrc:/pages/RadioButtonPage.qml" }
+                ListElement { title: qsTr("RangeSlider"); source: "qrc:/pages/RangeSliderPage.qml" }
+                ListElement { title: qsTr("ScrollBar"); source: "qrc:/pages/ScrollBarPage.qml" }
+                ListElement { title: qsTr("ScrollIndicator"); source: "qrc:/pages/ScrollIndicatorPage.qml" }
+                ListElement { title: qsTr("SearchField"); source: "qrc:/pages/SearchFieldPage.qml" }
+                ListElement { title: qsTr("Slider"); source: "qrc:/pages/SliderPage.qml" }
+                ListElement { title: qsTr("SpinBox"); source: "qrc:/pages/SpinBoxPage.qml" }
+                ListElement { title: qsTr("SplitView"); source: "qrc:/pages/SplitViewPage.qml" }
+                ListElement { title: qsTr("StackView"); source: "qrc:/pages/StackViewPage.qml" }
+                ListElement { title: qsTr("SwipeView"); source: "qrc:/pages/SwipeViewPage.qml" }
+                ListElement { title: qsTr("Switch"); source: "qrc:/pages/SwitchPage.qml" }
+                ListElement { title: qsTr("TabBar"); source: "qrc:/pages/TabBarPage.qml" }
+                ListElement { title: qsTr("TableView"); source: "qrc:/pages/TableViewPage.qml" }
+                ListElement { title: qsTr("TextArea"); source: "qrc:/pages/TextAreaPage.qml" }
+                ListElement { title: qsTr("TextField"); source: "qrc:/pages/TextFieldPage.qml" }
+                ListElement { title: qsTr("ToolBar"); source: "qrc:/pages/ToolBarPage.qml" }
+                ListElement { title: qsTr("ToolTip"); source: "qrc:/pages/ToolTipPage.qml" }
+                ListElement { title: qsTr("TreeView"); source: "qrc:/pages/TreeViewPage.qml" }
+                ListElement { title: qsTr("Tumbler"); source: "qrc:/pages/TumblerPage.qml" }
             }
 
             delegate: ItemDelegate {
@@ -178,6 +180,9 @@ ApplicationWindow {
                 required property string source
 
                 onClicked: {
+                    if (stackView.depth > 1)
+                        return
+
                     listView.currentIndex = index
                     stackView.push(source)
                     if (window.portraitMode)
@@ -209,7 +214,7 @@ ApplicationWindow {
             }
 
             Label {
-                text: "Qt Quick Controls provides a set of controls that can be used to build complete interfaces in Qt Quick."
+                text: qsTr("Qt Quick Controls provides a set of controls that can be used to build complete interfaces in Qt Quick.")
                 anchors.margins: 20
                 anchors.top: logo.bottom
                 anchors.left: parent.left
@@ -234,14 +239,14 @@ ApplicationWindow {
         id: settingsDialog
         x: Math.round((window.width - width) / 2)
         y: Math.round(window.height / 6)
-        width: Math.round(Math.min(window.width, window.height) / 3 * 2)
         modal: true
         focus: true
-        title: "Settings"
+        title: qsTr("Settings")
 
         standardButtons: Dialog.Ok | Dialog.Cancel
         onAccepted: {
             settings.style = styleBox.displayText
+            GalleryConfig.disabled = disableControlsCheckBox.checked
             settingsDialog.close()
         }
         onRejected: {
@@ -257,7 +262,7 @@ ApplicationWindow {
                 spacing: 10
 
                 Label {
-                    text: "Style:"
+                    text: qsTr("Style:")
                 }
 
                 ComboBox {
@@ -273,8 +278,61 @@ ApplicationWindow {
                 }
             }
 
+            RowLayout {
+                id: colorSchemes
+                // Some Qt Quick styles prioritize the respective design system guidelines
+                // over the system palette.
+                enabled: ["FluentWinUI3", "Fusion", "iOS", "Basic"].includes(styleBox.currentText)
+                CheckBox {
+                    id: autoColorScheme
+                    checked: true
+                    text: qsTr("Auto")
+                }
+                CheckBox {
+                    id: darkColorScheme
+                    text: qsTr("Dark Mode")
+                }
+                CheckBox {
+                    id: lightColorScheme
+                    text: qsTr("Light Mode")
+                }
+                ButtonGroup {
+                    exclusive: true
+                    buttons: colorSchemes.children
+                    onCheckedButtonChanged: {
+                        let scheme;
+                        switch (checkedButton) {
+                            case autoColorScheme:
+                                scheme = Qt.Unknown
+                                break;
+                            case darkColorScheme:
+                                scheme = Qt.Dark
+                                break;
+                            case lightColorScheme:
+                                scheme = Qt.Light
+                                break;
+                        }
+                        Qt.styleHints.colorScheme = scheme
+                    }
+                }
+            }
+
+            CheckBox {
+                id: orientationCheckBox
+                text: qsTr("Enable Landscape")
+                checked: false
+                Layout.fillWidth: true
+            }
+
+            CheckBox {
+                id: disableControlsCheckBox
+                checked: GalleryConfig.disabled
+                text: qsTr("Disable Controls")
+                Layout.fillWidth: true
+            }
+
             Label {
-                text: "Restart required"
+                text: qsTr("Restart required")
                 color: "#e41e25"
                 opacity: styleBox.currentIndex !== styleBox.styleIndex ? 1.0 : 0.0
                 horizontalAlignment: Label.AlignHCenter
@@ -289,7 +347,7 @@ ApplicationWindow {
         id: aboutDialog
         modal: true
         focus: true
-        title: "About"
+        title: qsTr("About")
         x: (window.width - width) / 2
         y: window.height / 6
         width: Math.min(window.width, window.height) / 3 * 2
@@ -301,15 +359,15 @@ ApplicationWindow {
 
             Label {
                 width: aboutDialog.availableWidth
-                text: "The Qt Quick Controls module delivers the next generation user interface controls based on Qt Quick."
+                text: qsTr("The Qt Quick Controls module delivers the next generation user interface controls based on Qt Quick.")
                 wrapMode: Label.Wrap
                 font.pixelSize: 12
             }
 
             Label {
                 width: aboutDialog.availableWidth
-                text: "In comparison to Qt Quick Controls 1, Qt Quick Controls "
-                    + "are an order of magnitude simpler, lighter, and faster."
+                text: qsTr("In comparison to Qt Quick Controls 1, Qt Quick Controls "
+                    + "are an order of magnitude simpler, lighter, and faster.")
                 wrapMode: Label.Wrap
                 font.pixelSize: 12
             }
index 330197941ed39aa0091e71e603b4f3b7a08fd2bd..41c8d25a7176743023bc22ed5f6ff7d596a1c73e 100644 (file)
   <file>pages/DialPage.qml</file>
   <file>pages/DialogPage.qml</file>
   <file>pages/FramePage.qml</file>
+  <file>pages/GalleryConfig.qml</file>
   <file>pages/GroupBoxPage.qml</file>
+  <file>pages/MenuBarPage.qml</file>
+  <file>pages/MonthGridPage.qml</file>
   <file>pages/PageIndicatorPage.qml</file>
   <file>pages/ProgressBarPage.qml</file>
   <file>pages/RadioButtonPage.qml</file>
   <file>pages/ScrollBarPage.qml</file>
   <file>pages/ScrollIndicatorPage.qml</file>
   <file>pages/ScrollablePage.qml</file>
+  <file>pages/SearchFieldPage.qml</file>
   <file>pages/SliderPage.qml</file>
   <file>pages/SpinBoxPage.qml</file>
+  <file>pages/SplitViewPage.qml</file>
   <file>pages/StackViewPage.qml</file>
   <file>pages/SwipeViewPage.qml</file>
   <file>pages/SwitchPage.qml</file>
   <file>pages/TabBarPage.qml</file>
+  <file>pages/TableViewPage.qml</file>
   <file>pages/TextAreaPage.qml</file>
   <file>pages/TextFieldPage.qml</file>
+  <file>pages/ToolBarPage.qml</file>
   <file>pages/ToolTipPage.qml</file>
+  <file>pages/TreeViewPage.qml</file>
   <file>pages/TumblerPage.qml</file>
   <file>qmldir</file>
   <file>qtquickcontrols2.conf</file>
index 5f391abfbb67288138b7098cf8c078f7639e9d3c..7cdc4b49f5b90391b946ebbeccabfd57d775ae67 100644 (file)
@@ -17,8 +17,8 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "BusyIndicator is used to indicate activity while content is being loaded,"
-                  + " or when the UI is blocked waiting for a resource to become available."
+            text: qsTr("BusyIndicator is used to indicate activity while content is being loaded,"
+                  + " or when the UI is blocked waiting for a resource to become available.")
         }
 
         BusyIndicator {
index 06051767e9cfaa3a6597ea7df5dfb70851e13a74..a5aab7d33baeee527b30a9b9e47a6612d9c5fd33 100644 (file)
@@ -12,12 +12,30 @@ ScrollablePage {
         spacing: 40
         width: parent.width
 
+        Row {
+            CheckBox {
+                id: checkedCheckBox
+                text: qsTr("Checked")
+            }
+
+            CheckBox {
+                id: flatCheckBox
+                text: qsTr("Flat")
+            }
+
+            CheckBox {
+                id: pressedCheckBox
+                enabled: !GalleryConfig.disabled
+                text: qsTr("Pressed")
+            }
+        }
+
         Label {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Button presents a push-button that can be pushed or clicked by the user. "
-                + "Buttons are normally used to perform an action, or to answer a question."
+            text: qsTr("Button presents a push-button that can be pushed or clicked by the user. "
+                + "Buttons are normally used to perform an action, or to answer a question.")
         }
 
         ColumnLayout {
@@ -25,18 +43,28 @@ ScrollablePage {
             anchors.horizontalCenter: parent.horizontalCenter
 
             Button {
-                text: "First"
+                enabled: !GalleryConfig.disabled
+                text: qsTr("Button")
+                checked: checkedCheckBox.checked
+                flat: flatCheckBox.checked
+                down: pressedCheckBox.checked ? true : undefined
                 Layout.fillWidth: true
             }
             Button {
-                id: button
-                text: "Second"
+                enabled: !GalleryConfig.disabled
+                text: qsTr("Highlighted")
+                checked: checkedCheckBox.checked
+                flat: flatCheckBox.checked
+                down: pressedCheckBox.checked ? true : undefined
                 highlighted: true
                 Layout.fillWidth: true
             }
-            Button {
-                text: "Third"
-                enabled: false
+            RoundButton {
+                enabled: !GalleryConfig.disabled
+                text: qsTr("RoundButton")
+                checked: checkedCheckBox.checked
+                flat: flatCheckBox.checked
+                down: pressedCheckBox.checked ? true : undefined
                 Layout.fillWidth: true
             }
         }
index 003e44c82c427429022c22c55594914e5b862eea..cbc644e9d1c75ea777e4326bb73f99397adcd096 100644 (file)
@@ -15,8 +15,8 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "CheckBox presents an option button that can be toggled on or off. "
-                + "Check boxes are typically used to select one or more options from a set of options."
+            text: qsTr("CheckBox presents an option button that can be toggled on or off. "
+                + "Check boxes are typically used to select one or more options from a set of options.")
         }
 
         Column {
@@ -24,16 +24,13 @@ ScrollablePage {
             anchors.horizontalCenter: parent.horizontalCenter
 
             CheckBox {
-                text: "First"
+                enabled: !GalleryConfig.disabled
+                text: qsTr("First")
                 checked: true
             }
             CheckBox {
-                text: "Second"
-            }
-            CheckBox {
-                text: "Third"
-                checked: true
-                enabled: false
+                enabled: !GalleryConfig.disabled
+                text: qsTr("Second")
             }
         }
     }
index 2dc10cee3c8f15b3bd1100933680fb954caa8708..ff764d650ed13dc7cfc38358cbc148597f01908f 100644 (file)
@@ -15,12 +15,13 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "ComboBox is a combined button and popup list. It presents "
-                + "a list of options to the user that occupies minimal screen space."
+            text: qsTr("ComboBox is a combined button and popup list. It presents "
+                + "a list of options to the user that occupies minimal screen space.")
         }
 
         ComboBox {
-            model: ["First", "Second", "Third"]
+            enabled: !GalleryConfig.disabled
+            model: [qsTr("First"), qsTr("Second"), qsTr("Third")]
             anchors.horizontalCenter: parent.horizontalCenter
         }
 
@@ -28,18 +29,19 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "ComboBox can be made \l editable. An editable combo box auto-"
-                + "completes its text based on what is available in the model."
+            text: qsTr("ComboBox can be made editable. An editable combo box auto-"
+                + "completes its text based on what is available in the model.")
         }
 
         ComboBox {
             id: comboBox
 
+            enabled: !GalleryConfig.disabled
             editable: true
             model: ListModel {
-                ListElement { text: "Banana" }
-                ListElement { text: "Apple" }
-                ListElement { text: "Coconut" }
+                ListElement { text: qsTr("Banana") }
+                ListElement { text: qsTr("Apple") }
+                ListElement { text: qsTr("Coconut") }
             }
             onAccepted: {
                 if (find(editText) === -1)
index 4c0e8725b92470827d461948c15103069a0e0f4c..fc9b922a826002741f88f2811ddc00080ce2d09b 100644 (file)
@@ -15,12 +15,13 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "DelayButton is a checkable button that incorporates a delay before the "
-                + "button is activated. This delay prevents accidental presses."
+            text: qsTr("DelayButton is a checkable button that incorporates a delay before the "
+                + "button is activated. This delay prevents accidental presses.")
         }
 
         DelayButton {
-            text: "DelayButton"
+            enabled: !GalleryConfig.disabled
+            text: qsTr("DelayButton")
             anchors.horizontalCenter: parent.horizontalCenter
         }
     }
index 26d346a9156010d25d4f4f13168a93c567bcd6a3..2722f38147f875b379e38f39008e591d6a07f79b 100644 (file)
@@ -1,6 +1,8 @@
 // Copyright (C) 2017 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
+pragma ComponentBehavior: Bound
+
 import QtQuick
 import QtQuick.Layouts
 import QtQuick.Controls
@@ -15,7 +17,11 @@ Pane {
             Layout.fillWidth: true
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Delegate controls are used as delegates in views such as ListView."
+            text: qsTr("Delegate controls are used as delegates in views such as ListView.")
+        }
+
+        ButtonGroup {
+            id: radioButtonGroup
         }
 
         ListView {
@@ -37,165 +43,142 @@ Pane {
             Layout.fillWidth: true
             Layout.fillHeight: true
 
-            readonly property var delegateComponentMap: {
-                "ItemDelegate": itemDelegateComponent,
-                "SwipeDelegate": swipeDelegateComponent,
-                "CheckDelegate": checkDelegateComponent,
-                "RadioDelegate": radioDelegateComponent,
-                "SwitchDelegate": switchDelegateComponent
+            model: ListModel {
+                ListElement { type: "ItemDelegate"; value: qsTr("ItemDelegate1") }
+                ListElement { type: "ItemDelegate"; value: qsTr("ItemDelegate2") }
+                ListElement { type: "ItemDelegate"; value: qsTr("ItemDelegate3") }
+                ListElement { type: "SwipeDelegate"; value: qsTr("SwipeDelegate1") }
+                ListElement { type: "SwipeDelegate"; value: qsTr("SwipeDelegate2") }
+                ListElement { type: "SwipeDelegate"; value: qsTr("SwipeDelegate3") }
+                ListElement { type: "CheckDelegate"; value: qsTr("CheckDelegate1") }
+                ListElement { type: "CheckDelegate"; value: qsTr("CheckDelegate2") }
+                ListElement { type: "CheckDelegate"; value: qsTr("CheckDelegate3") }
+                ListElement { type: "RadioDelegate"; value: qsTr("RadioDelegate1") }
+                ListElement { type: "RadioDelegate"; value: qsTr("RadioDelegate2") }
+                ListElement { type: "RadioDelegate"; value: qsTr("RadioDelegate3") }
+                ListElement { type: "SwitchDelegate"; value: qsTr("SwitchDelegate1") }
+                ListElement { type: "SwitchDelegate"; value: qsTr("SwitchDelegate2") }
+                ListElement { type: "SwitchDelegate"; value: qsTr("SwitchDelegate3") }
             }
 
-            Component {
-                id: itemDelegateComponent
+            delegate: Loader {
+                id: delegateLoader
+                width: ListView.view.width
+                sourceComponent: delegateComponentMap[type]
 
-                ItemDelegate {
-                    // qmllint disable unqualified
-                    text: value
-                    // qmllint enable unqualified
-                    width: parent.width
+                required property string value
+                required property string type
+                required property var model
+                required property int index
+
+                property ListView view: listView
+
+                readonly property var delegateComponentMap: {
+                    "ItemDelegate": itemDelegateComponent,
+                    "SwipeDelegate": swipeDelegateComponent,
+                    "CheckDelegate": checkDelegateComponent,
+                    "RadioDelegate": radioDelegateComponent,
+                    "SwitchDelegate": switchDelegateComponent
                 }
-            }
 
-            Component {
-                id: swipeDelegateComponent
-
-                SwipeDelegate {
-                    id: swipeDelegate
-                    // qmllint disable unqualified
-                    text: value
-                    // qmllint enable unqualified
-                    width: parent.width
-
-                    Component {
-                        id: removeComponent
-
-                        Rectangle {
-                            color: SwipeDelegate.pressed ? "#333" : "#444"
-                            width: parent.width
-                            height: parent.height
-                            clip: true
-
-                            SwipeDelegate.onClicked: {
-                                // qmllint disable unqualified
-                                view.model.remove(ourIndex)
-                                // qmllint enable unqualified
-                            }
+                Component {
+                    id: itemDelegateComponent
+
+                    ItemDelegate {
+                        enabled: !GalleryConfig.disabled
+                        text: delegateLoader.value
+                        width: delegateLoader.width
+                    }
+                }
 
-                            Label {
-                                // qmllint disable unqualified
-                                font.pixelSize: swipeDelegate.font.pixelSize
-                                // qmllint enable unqualified
-                                text: "Remove"
-                                color: "white"
-                                anchors.centerIn: parent
+                Component {
+                    id: swipeDelegateComponent
+
+                    SwipeDelegate {
+                        id: swipeDelegate
+                        enabled: !GalleryConfig.disabled
+                        text: delegateLoader.value
+                        width: delegateLoader.width
+
+                        Component {
+                            id: removeComponent
+
+                            Rectangle {
+                                color: SwipeDelegate.pressed ? "#333" : "#444"
+                                width: parent.width
+                                height: parent.height
+                                clip: true
+
+                                SwipeDelegate.onClicked: {
+                                    if (delegateLoader.view !== undefined)
+                                        delegateLoader.view.model.remove(delegateLoader.index)
+                                }
+
+                                Label {
+                                    font.pixelSize: swipeDelegate.font.pixelSize
+                                    text: qsTr("Remove")
+                                    color: "white"
+                                    anchors.centerIn: parent
+                                }
                             }
                         }
-                    }
 
-                    SequentialAnimation {
-                        id: removeAnimation
+                        SequentialAnimation {
+                            id: removeAnimation
 
-                        PropertyAction {
-                            // qmllint disable unqualified
-                            target: delegateItem
-                            // qmllint enable unqualified
-                            property: "ListView.delayRemove"
-                            value: true
-                        }
-                        NumberAnimation {
-                            // qmllint disable unqualified
-                            target: delegateItem.item
-                            // qmllint enable unqualified
-                            property: "height"
-                            to: 0
-                            easing.type: Easing.InOutQuad
-                        }
-                        PropertyAction {
-                            // qmllint disable unqualified
-                            target: delegateItem
-                            // qmllint enable unqualified
-                            property: "ListView.delayRemove"
-                            value: false
+                            PropertyAction {
+                                target: delegateLoader
+                                property: "ListView.delayRemove"
+                                value: true
+                            }
+                            NumberAnimation {
+                                target: swipeDelegate
+                                property: "height"
+                                to: 0
+                                easing.type: Easing.InOutQuad
+                            }
+                            PropertyAction {
+                                target: delegateLoader
+                                property: "ListView.delayRemove"
+                                value: false
+                            }
                         }
-                    }
 
-                    swipe.left: removeComponent
-                    swipe.right: removeComponent
-                    ListView.onRemove: removeAnimation.start()
+                        swipe.left: removeComponent
+                        swipe.right: removeComponent
+                        ListView.onRemove: removeAnimation.start()
+                    }
                 }
-            }
 
-            Component {
-                id: checkDelegateComponent
+                Component {
+                    id: checkDelegateComponent
 
-                CheckDelegate {
-                    // qmllint disable unqualified
-                    text: value
-                    // qmllint enable unqualified
+                    CheckDelegate {
+                        enabled: !GalleryConfig.disabled
+                        text: delegateLoader.value
+                    }
                 }
-            }
 
-            ButtonGroup {
-                id: radioButtonGroup
-            }
+                Component {
+                    id: radioDelegateComponent
 
-            Component {
-                id: radioDelegateComponent
+                    RadioDelegate {
+                        enabled: !GalleryConfig.disabled
+                        text: delegateLoader.value
 
-                RadioDelegate {
-                    // qmllint disable unqualified
-                    text: value
-                    ButtonGroup.group: radioButtonGroup
-                    // qmllint enable unqualified
+                        ButtonGroup.group: radioButtonGroup
+                    }
                 }
-            }
 
-            Component {
-                id: switchDelegateComponent
+                Component {
+                    id: switchDelegateComponent
 
-                SwitchDelegate {
-                    // qmllint disable unqualified
-                    text: value
-                    // qmllint enable unqualified
+                    SwitchDelegate {
+                        enabled: !GalleryConfig.disabled
+                        text: delegateLoader.value
+                    }
                 }
             }
-
-            model: ListModel {
-                ListElement { type: "ItemDelegate"; value: "ItemDelegate1" }
-                ListElement { type: "ItemDelegate"; value: "ItemDelegate2" }
-                ListElement { type: "ItemDelegate"; value: "ItemDelegate3" }
-                ListElement { type: "SwipeDelegate"; value: "SwipeDelegate1" }
-                ListElement { type: "SwipeDelegate"; value: "SwipeDelegate2" }
-                ListElement { type: "SwipeDelegate"; value: "SwipeDelegate3" }
-                ListElement { type: "CheckDelegate"; value: "CheckDelegate1" }
-                ListElement { type: "CheckDelegate"; value: "CheckDelegate2" }
-                ListElement { type: "CheckDelegate"; value: "CheckDelegate3" }
-                ListElement { type: "RadioDelegate"; value: "RadioDelegate1" }
-                ListElement { type: "RadioDelegate"; value: "RadioDelegate2" }
-                ListElement { type: "RadioDelegate"; value: "RadioDelegate3" }
-                ListElement { type: "SwitchDelegate"; value: "SwitchDelegate1" }
-                ListElement { type: "SwitchDelegate"; value: "SwitchDelegate2" }
-                ListElement { type: "SwitchDelegate"; value: "SwitchDelegate3" }
-            }
-
-            delegate: Loader {
-                id: delegateLoader
-                width: ListView.view.width
-                // qmllint disable unqualified
-                sourceComponent: listView.delegateComponentMap[type]
-                // qmllint enable unqualified
-
-                required property string value
-                required property string type
-                required property var model
-                required property int index
-
-                property Loader delegateItem: delegateLoader
-                // qmllint disable unqualified
-                property ListView view: listView
-                // qmllint enable unqualified
-                property int ourIndex: index
-            }
         }
     }
 }
index 17c9e090ba2845956b35a94e2831de2f0ea66a6d..2ccd1fa93c8b3d4d0ffd2c6a2e37ddf0ab08167a 100644 (file)
@@ -15,11 +15,12 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "The Dial is similar to a traditional dial knob that is found on devices such as "
-                + "stereos or industrial equipment. It allows the user to specify a value within a range."
+            text: qsTr("The Dial is similar to a traditional dial knob that is found on devices such as "
+                + "stereos or industrial equipment. It allows the user to specify a value within a range.")
         }
 
         Dial {
+            enabled: !GalleryConfig.disabled
             value: 0.5
             anchors.horizontalCenter: parent.horizontalCenter
         }
index ffabb415ec31bfc1a04a8b796675d3fe4b0f041a..157c63256ddb4cdc2900ab9d915f6e379cdc5fc5 100644 (file)
@@ -18,56 +18,58 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Dialog is a popup that is mostly used for short-term tasks "
-                + "and brief communications with the user."
+            text: qsTr("Dialog is a popup that is mostly used for short-term tasks "
+                + "and brief communications with the user.")
         }
 
         Button {
-            text: "Message"
+            text: qsTr("Message")
             anchors.horizontalCenter: parent.horizontalCenter
             width: page.buttonWidth
             onClicked: messageDialog.open()
 
             Dialog {
                 id: messageDialog
+                enabled: !GalleryConfig.disabled
 
                 x: (parent.width - width) / 2
                 y: (parent.height - height) / 2
 
-                title: "Message"
+                title: qsTr("Message")
 
                 Label {
-                    text: "Lorem ipsum dolor sit amet..."
+                    text: qsTr("Lorem ipsum dolor sit amet...")
                 }
             }
         }
 
         Button {
             id: button
-            text: "Confirmation"
+            text: qsTr("Confirmation")
             anchors.horizontalCenter: parent.horizontalCenter
             width: page.buttonWidth
             onClicked: confirmationDialog.open()
 
             Dialog {
                 id: confirmationDialog
+                enabled: !GalleryConfig.disabled
 
                 x: (parent.width - width) / 2
                 y: (parent.height - height) / 2
                 parent: Overlay.overlay
 
                 modal: true
-                title: "Confirmation"
+                title: qsTr("Confirmation")
                 standardButtons: Dialog.Yes | Dialog.No
 
                 Column {
                     spacing: 20
                     anchors.fill: parent
                     Label {
-                        text: "The document has been modified.\nDo you want to save your changes?"
+                        text: qsTr("The document has been modified.\nDo you want to save your changes?")
                     }
                     CheckBox {
-                        text: "Do not ask again"
+                        text: qsTr("Do not ask again")
                         anchors.right: parent.right
                     }
                 }
@@ -75,13 +77,14 @@ ScrollablePage {
         }
 
         Button {
-            text: "Content"
+            text: qsTr("Content")
             anchors.horizontalCenter: parent.horizontalCenter
             width: page.buttonWidth
             onClicked: contentDialog.open()
 
             Dialog {
                 id: contentDialog
+                enabled: !GalleryConfig.disabled
 
                 x: (parent.width - width) / 2
                 y: (parent.height - height) / 2
@@ -90,7 +93,7 @@ ScrollablePage {
                 parent: Overlay.overlay
 
                 modal: true
-                title: "Content"
+                title: qsTr("Content")
                 standardButtons: Dialog.Close
 
                 Flickable {
@@ -114,13 +117,13 @@ ScrollablePage {
 
                         Label {
                             width: parent.width
-                            text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc finibus "
+                            text: qsTr("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc finibus "
                                 + "in est quis laoreet. Interdum et malesuada fames ac ante ipsum primis "
                                 + "in faucibus. Curabitur eget justo sollicitudin enim faucibus bibendum. "
                                 + "Suspendisse potenti. Vestibulum cursus consequat mauris id sollicitudin. "
                                 + "Duis facilisis hendrerit consectetur. Curabitur sapien tortor, efficitur "
                                 + "id auctor nec, efficitur et nisl. Ut venenatis eros in nunc placerat, "
-                                + "eu aliquam enim suscipit."
+                                + "eu aliquam enim suscipit.")
                             wrapMode: Label.Wrap
                         }
                     }
@@ -137,13 +140,14 @@ ScrollablePage {
         }
 
         Button {
-            text: "Input"
+            text: qsTr("Input")
             anchors.horizontalCenter: parent.horizontalCenter
             width: page.buttonWidth
             onClicked: inputDialog.open()
 
             Dialog {
                 id: inputDialog
+                enabled: !GalleryConfig.disabled
 
                 x: (parent.width - width) / 2
                 y: (parent.height - height) / 2
@@ -151,7 +155,7 @@ ScrollablePage {
 
                 focus: true
                 modal: true
-                title: "Input"
+                title: qsTr("Input")
                 standardButtons: Dialog.Ok | Dialog.Cancel
 
                 ColumnLayout {
@@ -159,16 +163,16 @@ ScrollablePage {
                     anchors.fill: parent
                     Label {
                         elide: Label.ElideRight
-                        text: "Please enter the credentials:"
+                        text: qsTr("Please enter the credentials:")
                         Layout.fillWidth: true
                     }
                     TextField {
                         focus: true
-                        placeholderText: "Username"
+                        placeholderText: qsTr("Username")
                         Layout.fillWidth: true
                     }
                     TextField {
-                        placeholderText: "Password"
+                        placeholderText: qsTr("Password")
                         echoMode: TextField.PasswordEchoOnEdit
                         Layout.fillWidth: true
                     }
index 852644253773388bafb890c0dacfa593a75fbf23..0f4800f4e317324356e70a47cad09e8909742604 100644 (file)
@@ -17,10 +17,11 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Frame is used to layout a logical group of controls together, within a visual frame."
+            text: qsTr("Frame is used to layout a logical group of controls together, within a visual frame.")
         }
 
         Frame {
+            enabled: !GalleryConfig.disabled
             anchors.horizontalCenter: parent.horizontalCenter
 
             Column {
@@ -28,17 +29,17 @@ ScrollablePage {
                 width: page.itemWidth
 
                 RadioButton {
-                    text: "First"
+                    text: qsTr("First")
                     checked: true
                     width: parent.width
                 }
                 RadioButton {
                     id: button
-                    text: "Second"
+                    text: qsTr("Second")
                     width: parent.width
                 }
                 RadioButton {
-                    text: "Third"
+                    text: qsTr("Third")
                     width: parent.width
                 }
             }
diff --git a/examples/quickcontrols/gallery/pages/GalleryConfig.qml b/examples/quickcontrols/gallery/pages/GalleryConfig.qml
new file mode 100644 (file)
index 0000000..7f230d9
--- /dev/null
@@ -0,0 +1,9 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma Singleton
+import QtQuick
+
+QtObject {
+    property bool disabled: false
+}
index 9e24d8e6cadf2321323006f4ff6970b45c6ebb9f..a3be0cfa45c9db700f2db76311171b4d46eb741c 100644 (file)
@@ -17,11 +17,12 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "A GroupBox provides a frame, a title on top of it, and a logical group of controls within that frame."
+            text: qsTr("A GroupBox provides a frame, a title on top of it, and a logical group of controls within that frame.")
         }
 
         GroupBox {
-            title: "Title"
+            enabled: !GalleryConfig.disabled
+            title: qsTr("Title")
             anchors.horizontalCenter: parent.horizontalCenter
 
             Column {
@@ -29,17 +30,17 @@ ScrollablePage {
                 width: page.itemWidth
 
                 RadioButton {
-                    text: "First"
+                    text: qsTr("First")
                     checked: true
                     width: parent.width
                 }
                 RadioButton {
                     id: button
-                    text: "Second"
+                    text: qsTr("Second")
                     width: parent.width
                 }
                 RadioButton {
-                    text: "Third"
+                    text: qsTr("Third")
                     width: parent.width
                 }
             }
diff --git a/examples/quickcontrols/gallery/pages/MenuBarPage.qml b/examples/quickcontrols/gallery/pages/MenuBarPage.qml
new file mode 100644 (file)
index 0000000..a59f536
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+Page {
+    id: page
+    enabled: !GalleryConfig.disabled
+
+    header: MenuBar {
+        Menu {
+            title: qsTr("&File")
+            Action { text: qsTr("&New...") }
+            Action { text: qsTr("&Open...") }
+            Action { text: qsTr("&Save") }
+            Action { text: qsTr("Save &As...") }
+            MenuSeparator { }
+            Action { text: qsTr("&Quit") }
+        }
+        Menu {
+            title: qsTr("&Edit")
+            Action { text: qsTr("Cu&t") }
+            Action { text: qsTr("&Copy") }
+            Action { text: qsTr("&Paste") }
+        }
+        Menu {
+            title: qsTr("&Help")
+            Action { text: qsTr("&About") }
+        }
+    }
+
+    Label {
+        anchors.verticalCenter: parent.verticalCenter
+        width: parent.width
+        wrapMode: Label.Wrap
+        horizontalAlignment: Qt.AlignHCenter
+        text: qsTr("MenuBar provides a horizontal bar with drop-down menus, "
+                 + "allowing users to access grouped commands and actions "
+                 + "within an application.")
+    }
+}
diff --git a/examples/quickcontrols/gallery/pages/MonthGridPage.qml b/examples/quickcontrols/gallery/pages/MonthGridPage.qml
new file mode 100644 (file)
index 0000000..bd99967
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Page {
+    id: page
+    enabled: !GalleryConfig.disabled
+
+    Column {
+        spacing: 40
+        width: parent.width
+
+        Label {
+            width: parent.width
+            wrapMode: Label.Wrap
+            horizontalAlignment: Qt.AlignHCenter
+            text: qsTr("MonthGrid presents a calendar month as a grid of days, "
+                     + "calculated for a specific month, year, and locale.")
+        }
+
+        ColumnLayout {
+            spacing: 20
+            anchors.horizontalCenter: parent.horizontalCenter
+
+            RowLayout {
+                spacing: 10
+                Layout.fillWidth: true
+
+                Button {
+                    implicitWidth: height
+                    enabled: !GalleryConfig.disabled
+                    flat: true
+                    text: qsTr("<")
+                    onClicked: {
+                        const new_month = monthGrid.month - 1
+                        if (new_month < 0) {
+                            monthGrid.month = 11
+                            --monthGrid.year
+                        } else {
+                            monthGrid.month = new_month
+                        }
+                    }
+                }
+                Item {
+                    Layout.fillHeight: true
+                    Layout.fillWidth: true
+                    Label {
+                        anchors.centerIn: parent
+                        text: qsTr("%1 %2").arg(monthGrid.locale.monthName(monthGrid.month))
+                                           .arg(monthGrid.year)
+                    }
+                }
+                Button {
+                    implicitWidth: height
+                    enabled: !GalleryConfig.disabled
+                    flat: true
+                    text: qsTr(">")
+                    onClicked: {
+                        const new_month = monthGrid.month + 1
+                        if (new_month >= 12) {
+                            monthGrid.month = 0
+                            ++monthGrid.year
+                        } else {
+                            monthGrid.month = new_month
+                        }
+                    }
+                }
+            }
+
+            GridLayout {
+                columns: 2
+                Layout.fillWidth: true
+                Layout.fillHeight: true
+
+                DayOfWeekRow {
+                    locale: monthGrid.locale
+                    Layout.fillWidth: true
+                    Layout.column: 1
+                }
+
+                WeekNumberColumn {
+                    locale: monthGrid.locale
+                    year: monthGrid.year
+                    month: monthGrid.month
+                    Layout.fillHeight: true
+                }
+
+                MonthGrid {
+                    id: monthGrid
+                    locale: Qt.locale("en_US")
+                    year: currentDate.getFullYear()
+                    month: currentDate.getMonth()
+                    readonly property date currentDate: new Date()
+                    Layout.fillWidth: true
+                }
+            }
+        }
+    }
+}
index e83c86563e7cf4d2ea3f134698c6fabcff355193..13620c12172192250939cf719581ab9cd4640a3b 100644 (file)
@@ -15,7 +15,7 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "PageIndicator is used to indicate the currently active page in a container of pages."
+            text: qsTr("PageIndicator is used to indicate the currently active page in a container of pages.")
         }
 
         PageIndicator {
index d712aae10c0b0901f0cc58ed4c60795e23bdd8b5..2a3f7158e154e750255cb122982585ee3316c4e0 100644 (file)
@@ -15,8 +15,8 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "ProgressBar indicates the progress of an operation. It can be set in an "
-                + "indeterminate mode to indicate that the length of the operation is unknown."
+            text: qsTr("ProgressBar indicates the progress of an operation. It can be set in an "
+                + "indeterminate mode to indicate that the length of the operation is unknown.")
         }
 
         ProgressBar {
index 644543c007d46e058bb0afe390fbe66c6ecfc8db..5358e6a3d5a6bdd708b792e88b8d06531610c1ed 100644 (file)
@@ -15,8 +15,8 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "RadioButton presents an option button that can be toggled on or off. "
-                + "Radio buttons are typically used to select one option from a set of options."
+            text: qsTr("RadioButton presents an option button that can be toggled on or off. "
+                + "Radio buttons are typically used to select one option from a set of options.")
         }
 
         Column {
@@ -24,14 +24,16 @@ ScrollablePage {
             anchors.horizontalCenter: parent.horizontalCenter
 
             RadioButton {
-                text: "First"
+                text: qsTr("First")
+                enabled: !GalleryConfig.disabled
             }
             RadioButton {
-                text: "Second"
+                text: qsTr("Second")
                 checked: true
+                enabled: !GalleryConfig.disabled
             }
             RadioButton {
-                text: "Third"
+                text: qsTr("Third")
                 enabled: false
             }
         }
index 0ca235822f3c9956baaf48284729b36bd6db2d8d..83dced76d55e5b10bd1612d3bc4e132990f89259 100644 (file)
@@ -15,17 +15,18 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "RangeSlider is used to select a range specified by two values, by sliding each handle along a track."
+            text: qsTr("RangeSlider is used to select a range specified by two values, by sliding each handle along a track.")
         }
 
         RangeSlider {
-            id: slider
+            enabled: !GalleryConfig.disabled
             first.value: 0.25
             second.value: 0.75
             anchors.horizontalCenter: parent.horizontalCenter
         }
 
         RangeSlider {
+            enabled: !GalleryConfig.disabled
             orientation: Qt.Vertical
             first.value: 0.25
             second.value: 0.75
index 248e74ca7f6f853f44855ae3845be00378e24415..04bd8c77034b3a2af813733b43d1692c5a5feeba 100644 (file)
@@ -6,7 +6,7 @@ import QtQuick.Controls
 
 Flickable {
     id: flickable
-
+    enabled: !GalleryConfig.disabled
     contentHeight: pane.height
 
     Pane {
@@ -19,13 +19,19 @@ Flickable {
             spacing: 40
             width: parent.width
 
+            CheckBox {
+                id: alwaysOnCheckBox
+                width: parent.width
+                text: qsTr("Always on")
+            }
+
             Label {
                 width: parent.width
                 wrapMode: Label.Wrap
                 horizontalAlignment: Qt.AlignHCenter
-                text: "ScrollBar is an interactive bar that can be used to scroll to a specific position. "
+                text: qsTr("ScrollBar is an interactive bar that can be used to scroll to a specific position. "
                     + "A scroll bar can be either vertical or horizontal, and can be attached to any Flickable, "
-                    + "such as ListView and GridView."
+                    + "such as ListView and GridView.")
             }
 
             Image {
@@ -36,5 +42,7 @@ Flickable {
         }
     }
 
-    ScrollBar.vertical: ScrollBar { }
+    ScrollBar.vertical: ScrollBar {
+        policy: alwaysOnCheckBox.checked ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded
+    }
 }
index 04ce97483aa17e9e2bf713458681ae831ae28691..e16d62c571daf1cc133dbf2cedd2da74a173ca97 100644 (file)
@@ -6,7 +6,7 @@ import QtQuick.Controls
 
 Flickable {
     id: flickable
-
+    enabled: !GalleryConfig.disabled
     contentHeight: pane.height
 
     Pane {
@@ -23,9 +23,9 @@ Flickable {
                 width: parent.width
                 wrapMode: Label.Wrap
                 horizontalAlignment: Qt.AlignHCenter
-                text: "ScrollIndicator is a non-interactive indicator that indicates the current scroll position. "
+                text: qsTr("ScrollIndicator is a non-interactive indicator that indicates the current scroll position. "
                     + "A scroll indicator can be either vertical or horizontal, and can be attached to any Flickable, "
-                    + "such as ListView and GridView."
+                    + "such as ListView and GridView.")
             }
 
             Image {
diff --git a/examples/quickcontrols/gallery/pages/SearchFieldPage.qml b/examples/quickcontrols/gallery/pages/SearchFieldPage.qml
new file mode 100644 (file)
index 0000000..e790994
--- /dev/null
@@ -0,0 +1,58 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+ScrollablePage {
+    id: page
+
+    Column {
+        spacing: 40
+        width: parent.width
+
+        Label {
+            width: parent.width
+            wrapMode: Label.Wrap
+            horizontalAlignment: Qt.AlignHCenter
+            text: qsTr("SearchField is a styled text input for searching, typically "
+                       + "with a magnifier and clear icon.")
+        }
+
+        ListModel {
+            id: colorModel
+            ListElement { color: "blue" }
+            ListElement { color: "green" }
+            ListElement { color: "red" }
+            ListElement { color: "yellow" }
+            ListElement { color: "orange" }
+            ListElement { color: "purple" }
+        }
+
+        SortFilterProxyModel {
+            id: colorFilter
+            model: colorModel
+            sorters: [
+                RoleSorter {
+                    roleName: "color"
+                }
+            ]
+            filters: [
+                FunctionFilter {
+                    component CustomData: QtObject { property string color }
+                    property var regExp: new RegExp(colorSearch.text, "i")
+                    onRegExpChanged: invalidate()
+                    function filter(data: CustomData): bool {
+                       return regExp.test(data.color);
+                    }
+                }
+            ]
+        }
+
+        SearchField {
+            id: colorSearch
+            suggestionModel: colorFilter
+            anchors.horizontalCenter: parent.horizontalCenter
+        }
+    }
+}
index fd03680a0e5135546a251b84f695cc92d0c5741f..b7d83fdf307e3792d54512009406668a900f3a79 100644 (file)
@@ -15,16 +15,17 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Slider is used to select a value by sliding a handle along a track."
+            text: qsTr("Slider is used to select a value by sliding a handle along a track.")
         }
 
         Slider {
-            id: slider
+            enabled: !GalleryConfig.disabled
             value: 0.5
             anchors.horizontalCenter: parent.horizontalCenter
         }
 
         Slider {
+            enabled: !GalleryConfig.disabled
             orientation: Qt.Vertical
             value: 0.5
             anchors.horizontalCenter: parent.horizontalCenter
index 18c9b06c1f690510b50e68b09843a875f706e6e7..4536e8a9d89a90aa1b2d97542702af1553b560fb 100644 (file)
@@ -15,12 +15,12 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "SpinBox allows the user to choose an integer value by clicking the up or down indicator buttons, "
-                + "by pressing up or down on the keyboard, or by entering a text value in the input field."
+            text: qsTr("SpinBox allows the user to choose an integer value by clicking the up or down indicator buttons, "
+                + "by pressing up or down on the keyboard, or by entering a text value in the input field.")
         }
 
         SpinBox {
-            id: box
+            enabled: !GalleryConfig.disabled
             value: 50
             anchors.horizontalCenter: parent.horizontalCenter
             editable: true
diff --git a/examples/quickcontrols/gallery/pages/SplitViewPage.qml b/examples/quickcontrols/gallery/pages/SplitViewPage.qml
new file mode 100644 (file)
index 0000000..b04e0bb
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Page {
+    id: page
+    enabled: !GalleryConfig.disabled
+
+    ColumnLayout {
+        anchors.fill: parent
+        spacing: 40
+
+        CheckBox {
+            id: orientationCheckBox
+            text: qsTr("Vertical")
+        }
+
+        Label {
+            wrapMode: Label.Wrap
+            horizontalAlignment: Qt.AlignHCenter
+            text: qsTr("SplitView provides a container that arranges items horizontally "
+                     + "or vertically, separated by draggable splitters, allowing users "
+                     + "to interactively resize adjacent views within an application.")
+            Layout.fillWidth: true
+        }
+
+        SplitView {
+            orientation: orientationCheckBox.checked ? Qt.Vertical : Qt.Horizontal
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+
+            Rectangle {
+                implicitWidth: 200
+                implicitHeight: 100
+                color: "lightblue"
+                SplitView.maximumWidth: 400
+
+                Label {
+                    text: "View 1"
+                    anchors.centerIn: parent
+                }
+            }
+
+            Rectangle {
+                id: centerItem
+                color: "lightgray"
+                SplitView.minimumWidth: 50
+                SplitView.minimumHeight: 50
+                SplitView.fillWidth: true
+                SplitView.fillHeight: true
+
+                Label {
+                    text: "View 2"
+                    anchors.centerIn: parent
+                }
+            }
+
+            Rectangle {
+                implicitWidth: 200
+                implicitHeight: 100
+                color: "lightgreen"
+
+                Label {
+                    text: "View 3"
+                    anchors.centerIn: parent
+                }
+            }
+        }
+    }
+}
index 841d491ef05ed01bec2b7f8e0ad5164b6aada883..b8db9f76f091adcec09ae598d45288d27c928199 100644 (file)
@@ -9,6 +9,7 @@ import QtQuick.Controls
 StackView {
     id: stackView
     initialItem: page
+    enabled: !GalleryConfig.disabled
 
     Component {
         id: page
@@ -25,21 +26,21 @@ StackView {
                     width: parent.width
                     wrapMode: Label.Wrap
                     horizontalAlignment: Qt.AlignHCenter
-                    text: "StackView provides a stack-based navigation model which can be used with a set of interlinked pages. "
+                    text: qsTr("StackView provides a stack-based navigation model which can be used with a set of interlinked pages. "
                     + "Items are pushed onto the stack as the user navigates deeper into the material, and popped off again "
-                    + "when he chooses to go back."
+                    + "when he chooses to go back.")
                 }
 
                 Button {
                     id: button
-                    text: "Push"
+                    text: qsTr("Push")
                     anchors.horizontalCenter: parent.horizontalCenter
                     width: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 2, pane.availableWidth / 3))
                     onClicked: stackView.push(page)
                 }
 
                 Button {
-                    text: "Pop"
+                    text: qsTr("Pop")
                     enabled: stackView.depth > 1
                     width: Math.max(button.implicitWidth, Math.min(button.implicitWidth * 2, pane.availableWidth / 3))
                     anchors.horizontalCenter: parent.horizontalCenter
@@ -50,7 +51,7 @@ StackView {
                     width: parent.width
                     wrapMode: Label.Wrap
                     horizontalAlignment: Qt.AlignHCenter
-                    text: "Stack Depth: " + stackView.depth
+                    text: qsTr("Stack Depth:") + " " + stackView.depth
                 }
             }
         }
index 03958320e94f3e253f051730248dbdf4c6aa85a0..7b3e2622bea82792b66a77236e60ebf513480ea7 100644 (file)
@@ -11,6 +11,7 @@ Pane {
         id: view
         currentIndex: 1
         anchors.fill: parent
+        enabled: !GalleryConfig.disabled
 
         Repeater {
             model: 3
@@ -27,8 +28,8 @@ Pane {
                         width: parent.width
                         wrapMode: Label.Wrap
                         horizontalAlignment: Qt.AlignHCenter
-                        text: "SwipeView provides a navigation model that simplifies horizontal paged scrolling. "
-                        + "The page indicator on the bottom shows which is the presently active page."
+                        text: qsTr("SwipeView provides a navigation model that simplifies horizontal paged scrolling. "
+                        + "The page indicator on the bottom shows which is the presently active page.")
                     }
 
                     Image {
index cca200582eaebb4138ce3dcdaf3ed51217902f27..2dd4291b25ff7f3f916e6adeecedab33caeb7fea 100644 (file)
@@ -15,8 +15,8 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Switch is an option button that can be dragged or toggled on or off. "
-                + "Switches are typically used to select between two states."
+            text: qsTr("Switch is an option button that can be dragged or toggled on or off. "
+                + "Switches are typically used to select between two states.")
         }
 
         Column {
@@ -24,16 +24,14 @@ ScrollablePage {
             anchors.horizontalCenter: parent.horizontalCenter
 
             Switch {
-                text: "First"
+                enabled: !GalleryConfig.disabled
+                text: qsTr("First")
             }
             Switch {
-                text: "Second"
+                enabled: !GalleryConfig.disabled
+                text: qsTr("Second")
                 checked: true
             }
-            Switch {
-                text: "Third"
-                enabled: false
-            }
         }
     }
 }
index d4dfeb8950ebc5ce70aa1cc08c3992388a8d2668..084772982a0d361eace06461cb8e4b7a4ec0bf3e 100644 (file)
@@ -6,6 +6,7 @@ import QtQuick.Controls
 
 Page {
     id: page
+    enabled: !GalleryConfig.disabled
 
     SwipeView {
         id: swipeView
@@ -27,8 +28,8 @@ Page {
                         width: parent.width
                         wrapMode: Label.Wrap
                         horizontalAlignment: Qt.AlignHCenter
-                        text: "TabBar is a bar with icons or text which allows the user "
-                              + "to switch between different subtasks, views, or modes."
+                        text: qsTr("TabBar is a bar with icons or text which allows the user "
+                              + "to switch between different subtasks, views, or modes.")
                     }
 
                     Image {
@@ -45,13 +46,13 @@ Page {
         currentIndex: swipeView.currentIndex
 
         TabButton {
-            text: "First"
+            text: qsTr("First")
         }
         TabButton {
-            text: "Second"
+            text: qsTr("Second")
         }
         TabButton {
-            text: "Third"
+            text: qsTr("Third")
         }
     }
 }
diff --git a/examples/quickcontrols/gallery/pages/TableViewPage.qml b/examples/quickcontrols/gallery/pages/TableViewPage.qml
new file mode 100644 (file)
index 0000000..e9ebfea
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.qmlmodels
+
+Page {
+    id: page
+    enabled: !GalleryConfig.disabled
+
+    GridLayout {
+        anchors.fill: parent
+
+        Label {
+            wrapMode: Label.Wrap
+            horizontalAlignment: Qt.AlignHCenter
+            text: qsTr("TableView provides a scrollable grid that displays data from "
+                       + "a model in rows and columns, allowing users to view and interact "
+                       + "with structured information within an application.")
+            Layout.fillWidth: true
+            Layout.columnSpan: 2
+        }
+
+        HorizontalHeaderView {
+            clip: true
+            syncView: tableView
+            model: tableModel.headerModel
+            Layout.column: 1
+            Layout.row: 1
+            Layout.fillWidth: true
+        }
+
+        VerticalHeaderView {
+            clip: true
+            syncView: tableView
+            Layout.column: 0
+            Layout.row: 2
+            Layout.fillHeight: true
+        }
+
+        TableView {
+            id: tableView
+            columnSpacing: 1
+            rowSpacing: 1
+            clip: true
+
+            selectionModel: ItemSelectionModel {}
+            model: tableModel
+
+            Layout.column: 1
+            Layout.row: 2
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            delegate: TableViewDelegate {
+                implicitWidth: 100
+                implicitHeight: 50
+                Component.onCompleted: {
+                    if (contentItem as Label) {
+                        contentItem.horizontalAlignment = Qt.AlignHCenter
+                        contentItem.verticalAlignment = Qt.AlignVCenter
+                    }
+                }
+            }
+        }
+    }
+
+    TableModel {
+        id: tableModel
+        property var headerModel: [qsTr("Name"), qsTr("Color")]
+        TableModelColumn { display: "name" }
+        TableModelColumn { display: "color" }
+        rows: [
+            {
+                "name": qsTr("cat"),
+                "color": qsTr("black")
+            },
+            {
+                "name": qsTr("dog"),
+                "color": qsTr("brown")
+            },
+            {
+                "name": qsTr("bird"),
+                "color": qsTr("white")
+            }
+        ]
+    }
+}
index 3e9d7ee52d3d22125241ecd65b7090786c4e7f15..d4f3ca0f7b76f24ce14f09ac9ae2f443c6ead98d 100644 (file)
@@ -15,15 +15,16 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "TextArea is a multi-line text editor."
+            text: qsTr("TextArea is a multi-line text editor.")
         }
 
         TextArea {
+            enabled: !GalleryConfig.disabled
             width: page.availableWidth / 3
             anchors.horizontalCenter: parent.horizontalCenter
 
             wrapMode: TextArea.Wrap
-            text: "TextArea\n...\n...\n..."
+            text: qsTr("TextArea\n...\n...\n...")
         }
     }
 }
index 2b06894c8fcc4295938d53089f917a691695172e..ba61145ec9e87aef5fe3c15c8ff109e1b72bc005 100644 (file)
@@ -15,12 +15,12 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "TextField is a single-line text editor."
+            text: qsTr("TextField is a single-line text editor.")
         }
 
         TextField {
-            id: field
-            placeholderText: "TextField"
+            enabled: !GalleryConfig.disabled
+            placeholderText: qsTr("TextField")
             anchors.horizontalCenter: parent.horizontalCenter
         }
     }
diff --git a/examples/quickcontrols/gallery/pages/ToolBarPage.qml b/examples/quickcontrols/gallery/pages/ToolBarPage.qml
new file mode 100644 (file)
index 0000000..b4b2cc8
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Page {
+    id: page
+    enabled: !GalleryConfig.disabled
+
+    header: ToolBar {
+        RowLayout {
+            anchors.fill: parent
+
+            Item {
+                Layout.fillHeight: true
+                Layout.preferredWidth: height
+            }
+
+            Label {
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: qsTr("Header")
+
+                Layout.fillHeight: true
+                Layout.fillWidth: true
+            }
+
+            ToolSeparator { }
+
+            ToolButton { text: "\u2699" }
+        }
+    }
+
+    Label {
+        anchors.centerIn: parent
+        width: parent.width - 20
+        wrapMode: Label.Wrap
+        horizontalAlignment: Qt.AlignHCenter
+        text: qsTr("ToolBar provides a horizontal container for application-wide "
+                 + "and context-sensitive controls, such as navigation buttons and "
+                 + "search fields, typically used as a header or footer within an "
+                 + "application window")
+    }
+
+    footer: ToolBar {
+        RowLayout {
+            anchors.fill: parent
+
+            Label {
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: "\u2139"
+
+                Layout.fillHeight: true
+                Layout.preferredWidth: height
+            }
+
+            Label {
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: qsTr("Footer")
+
+                Layout.fillHeight: true
+                Layout.fillWidth: true
+            }
+
+            ToolSeparator { }
+
+            ToolButton { text: "\u2630" }
+        }
+    }
+}
index dd92c89b4fa33bb45eec8f4d028d5c11eea50df6..9a6cfc572955a5643ce9e3c154307d6aa7d100be 100644 (file)
@@ -15,16 +15,16 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "A tool tip is a short piece of text that informs the user of a control's function."
+            text: qsTr("A tool tip is a short piece of text that informs the user of a control's function.")
         }
 
         Button {
-            text: "Tip"
+            text: qsTr("Tip")
             anchors.horizontalCenter: parent.horizontalCenter
 
             ToolTip.timeout: 5000
             ToolTip.visible: pressed
-            ToolTip.text: "This is a tool tip."
+            ToolTip.text: qsTr("This is a tool tip.")
         }
     }
 }
diff --git a/examples/quickcontrols/gallery/pages/TreeViewPage.qml b/examples/quickcontrols/gallery/pages/TreeViewPage.qml
new file mode 100644 (file)
index 0000000..b7ee2b2
--- /dev/null
@@ -0,0 +1,113 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.qmlmodels
+
+Page {
+    id: page
+
+    GridLayout {
+        anchors.fill: parent
+        anchors.margins: 10
+
+        Label {
+            wrapMode: Label.Wrap
+            horizontalAlignment: Qt.AlignHCenter
+            text: qsTr("TreeView provides a hierarchical view for displaying and "
+                     + "navigating tree-structured data, allowing users to expand and "
+                     + "collapse nodes to explore parent-child relationships within a model")
+
+            Layout.fillWidth: true
+            Layout.columnSpan: 2
+        }
+
+        Item {
+            implicitHeight: 40
+
+            Layout.columnSpan: 2
+            Layout.row: 1
+        }
+
+        HorizontalHeaderView {
+            clip: true
+            enabled: !GalleryConfig.disabled
+            syncView: treeView
+            model: [qsTr("Location")]
+
+            Layout.column: 1
+            Layout.row: 2
+            Layout.fillWidth: true
+        }
+
+        VerticalHeaderView {
+            clip: true
+            enabled: !GalleryConfig.disabled
+            syncView: treeView
+            model: Array.from({length: treeView.rows}, (v, k) => k + 1)
+
+            Layout.column: 0
+            Layout.row: 3
+            Layout.fillHeight: true
+        }
+
+        TreeView {
+            id: treeView
+            clip: true
+            enabled: !GalleryConfig.disabled
+            rowSpacing: 2
+            model: treeModel
+
+            Layout.column: 1
+            Layout.row: 3
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            selectionModel: ItemSelectionModel {}
+            delegate: TreeViewDelegate { }
+
+            columnWidthProvider: (column) => column === 0 ? treeView.width : 0
+
+            Component.onCompleted: expandRecursively()
+        }
+    }
+
+    TreeModel {
+        id: treeModel
+
+        TableModelColumn { display: "location" }
+
+        rows: [
+            {
+                location: qsTr("America"),
+                rows: [
+                    { location: qsTr("Brazil") },
+                    {
+                        location: qsTr("Canada"),
+                        rows: [
+                            { location: qsTr("Calgary") },
+                            { location: qsTr("Vancouver") }
+                        ]
+                    }
+                ]
+            },
+            { location: qsTr("Asia") },
+            {
+                location: qsTr("Europe"),
+                rows: [
+                    {
+                        location: qsTr("Italy"),
+                        rows: [
+                            { location: qsTr("Milan") },
+                            { location: qsTr("Rome") }
+                        ]
+                    },
+                    { location: qsTr("Portugal") }
+                ]
+            }
+
+        ]
+    }
+}
index 4d01f02e89722db45f54998dde0f8c21213fe566..471553a63e967606968b2d73616f0ec9ae767d61 100644 (file)
@@ -15,10 +15,11 @@ ScrollablePage {
             width: parent.width
             wrapMode: Label.Wrap
             horizontalAlignment: Qt.AlignHCenter
-            text: "Tumbler is used to select a value by spinning a wheel."
+            text: qsTr("Tumbler is used to select a value by spinning a wheel.")
         }
 
         Tumbler {
+            enabled: !GalleryConfig.disabled
             model: 10
             anchors.horizontalCenter: parent.horizontalCenter
         }
index 6b7f86bfacdbfde65043c9e0b2d41423c9bfa56b..212f9919c9c9e606be93e9f8fff05e6ea5bc5238 100644 (file)
@@ -1 +1,2 @@
 module App
+singleton GalleryConfig 1.0 pages/GalleryConfig.qml
index 786c9cfbcbd79cea9e0a4a9172af1d417e464a99..c7107ae2e77a72765ddf1d2cf034e17dfcd42380 100644 (file)
 # Resource object code (Python 3)
 # Created by: object code
-# Created by: The Resource Compiler for Qt version 6.5.0
+# Created by: The Resource Compiler for Qt version 6.10.0
 # WARNING! All changes made in this file will be lost!
 
 from PySide6 import QtCore
 
 qt_resource_data = b"\
-\x00\x00\x09\xc1\
+\x00\x00\x0b\xbc\
 \x00\
-\x00)\xbbx\xda\xc5Z\xdds\xdb6\x12\x7f\xd7_\x81\
-\xb0/RcQ\xb6s\xc9\x03\xdb\xdc\x8d-'\x8d\xe7\
-\x9cKb\xf9\x9a\xcet\xfa\x00\x91\x90\x843\x04\xd0\x00\
-h[\xcd\xf9\x7f\xbf\x05\xf8!~\x00\xb2\xc4I\xe78\
-\x93\x98\x04\x16\x8b\xc5b?~Xh2AS\x91n\
-$]\xae4\x1aNG\xe8\xf4\xf8\xf4\x04\xdd\xac\x08\xfa\
-\xa2\xa1g\x9db\xbeAW:\x09\x07\x93\x09\x9a}\xbe\
-\xf8m|Ec\xc2\x15\x19_&\x84k\xba\xa0DF\
-\xa8h\xbb&\x8b\xf1\x17=\x86ak\x22c\x8a\x19\xfa\
-t\x8d\xceg\x17\xe3W\xe3)\xc3\x99\x22\x83A*\xf1\
-r\x8d-g\xc1\x81\xc19Y\xe1{*\x80\xc7\xb9\xc8\
-x2\x18P\xe8\x90\x1af\x9f\x0aI\xb6__2\x1a\
-\xdf\xb6>\xc3+\xbc\x11\x99V\xed\xe6\xa9\xe0Z\x0a\xa6\
-*fA\x18 \xac\xd0Y\x9a\x0e\x06\xf0\x1f\xa31\xd6\
-T\xf0\xaf\x94'\xe2\x01}\x1b xh\x12\xa1\x07\xdb\
-`?\x1fh\xa2W\x11z\xf5\xe6\xd8~\xae\x88QP\
-\x84^\x9f\xe6\xdf\xf7T\xd19#\x11\xd22#\xb6E\
-Sm\xbe\x03P\x9b\x95\x02\x95R\x04\x03\xdb?\x99\xbc\
-@\xbf\x0bIa\xcdv\xf2?l\xab$8\x11\x9cm\
-P*EJ\xa4\xde\xa0\xb9\x10\x0c\x19\xa9%\xa6\xfa\xa3\
-HH)VhEB?\x97\x9f\xb9H\x1e\xde\xb6y\
-\x91\xf1\xd8|\x82\xf4,\x1d\x8e\x8a\x85\x9a\x87\x11\x8d\x12\
-\xaaR\x867\x94/\x0bI\xd1[\xc4\xa8\xd2\xbfR\xf2\
-\x10\xc6\x99\x94\xc0\xed\x92'\xe4\x11\xbdx\xfb\x16\x8dO\
-\x1a\x83\x8b\xfeb\xe4\xbf\xf0\x9a\xc0\xe8\x0e\xc7j\x88y\
-\xfe\xb1\xe5\xbe\x86e\xb1pI\xf4\xd09\xe1(\xb4\xca\
-\x0c\xb5\xb8\x12\x0fDN\xb1\x22 =\xe86h\xc8\x90\
-I#r\xb0\xd2:U\xd1d\x92\x888\xbc\xd3!\x15\
-\x93;=~3\x09\x1a\x93\xbfD\xc3\xdd\xd2Y\x01\x83\
-\xbb5\x1b\xdf\xe9;\xb3\x7f\xe3\xb8\xd8\xbf\xd3q\x00\xc3\
-\x1d\x0b~\x09v\xb5\xd2k\x16\xb4\x19\x81\xa4]&\xc0\
-\x1a\x96\x9d\xc1\xb2\xec\x98\xd1O\xd5\xa8/:\x84\xbd\xe7\
-\xff\x96\xec\xdd\xa3&\x92c\xc66CX\xdc\xc8R<\
-\x0d\x0aC\x01\x86\x92$[C\xb9\xc7\x12\xcd3\xca@\
-g3\xbdaD\xe5\x843\xa25\xacQ\xd5v\xdb\x98\
-\xb6*\x9a\xab\xc6\x8a\x8f\xd2\x12:\xe0\x0f\xf0\xa8\xcf8\
-[\x81\x11\xc6\x99\xae1R \x04\xe11Q\x11\xfa=\
-x\xa7\xe2\xe0\x08\x05\xe78\xbe\x0d\xfe\xa8H\x08\xc7\xe0\
-\x17fB\x0d\x1dvk\x13\x92\x82\xdd\xfe\x1dm-H\
-\xf030\xcc{\xac\x0d!\x07\xff_\xc2\xabatf\
-\xed5\x04\x89\x96K\x22\x87\xa3=\xe5\x89\xd0Lc\x9e\
-`\x99\xfc\x93l\xc2\x0f`\xedf\x9f`\xbb\x94{\xca\
-\xca\x83\x8c[\xd4\xe7\xc8\xe7o\xa9\xae+\xe0\xb6\x1b\xf6\
-7\xe4`\x0c\xce\xf5\x1a\x8b\x9a\x1b\xf5\x18\x8bH$\x06\
-[\x0ej\x02\xdd\xe4\xab4\x02}k\x98\x10]\xa0\xa1\
-\x83\xdd\xa8Ef5P\x91\xa5\xa2\x5cK\xfdq;t\
-\xc3\x9d\xed\xf2\x11a\x8a8\xf8\xe7R[\xfblq\x7f\
-\x1a4\xdf\xf6\xd9\xa5\xe0#\xe1Y\xe0\xde\x13\x91\x1a\xcd\
-*C\xb1\xcb\x0a\x9c;\xd4\x19\xeb\xda\xa0`\xdd\x9a\xbc\
-\xa6\xff\x1a\x83\xfaZ\x8b9W\x10\xa4M\x96\x83\xd4\x11\
-\xde@x>\x07\xcf\xdb\x0ap-\x1e\xf2<\xd4R\x9f\
-Jq\x0c\x8e\x15\xa1\x22e\x94\x0f\xe61\xe8H\x85\x0b\
-\xcaX\x84Rl\xb6\xc5I\xc0\xc8B\x7f\xc4rIy\
-\x84^\x14&[O\x0c`^\xc5\xf6\xe4\x99!B\x90\
-@\xc9\x82r\x92\x0c\x1a\x0c\xad\xcc\x99\xd6\x0d\xbdU\xb3\
-Y\x85\xed\xb4\xf2\xf2\xa9\x92\x9eC\x98\x96m4>\xaf\
-\xf0\x9c0\xc7\xd4f\xebl\xa4\xb7\x04\x9dnM\x1e!\
-\xe3v,X\x935\xac|\xe8l\x87$o\xfe^\x10\
-F\xccZ \x91\x00\x0f\xe3}\xbf@P%r\xd3\x09\
-\xd5h\x01\x81\x22L\xe9#a3\xfa'\xe9l\x96\x0d\
-i\x8c\x9a,l\x85\x0c\xdf\x99\x8f\xeb*\xf3\xd6\x1f\xd8\
-4\xfa'\xb0\xc3\xec\x8c\xd1%\x07s\x03\xf1!\xb8\xdb\
-\xaf\x0fS\xf8$\xb2\xabS\x88\xc1\x00G\x5cC~\xf5\
-\x0c\xc9\xad\xcd\xda\xcf\xd7\x1c\xa5T \xc4\xb3\x01{\x19\
-@\xd7\x89:\xa4\xa6\xcf\xc1\xc1\xe1\x86N\x9a\xc7\xd2\xdc\
-\x0b\x83\x1d\xe7(\xcbI\x0b\x86\xc5\xd5B\xc8\xf5'\xf0\
-R\xe3\x01\xd67oD\x9a\xeb\xde9\xa6\x13\x1b\xdc\x16\
-\x15\x94)2\xf0\x126\x82C\x99:/\x00\xd0\x8a\xa5\
-+\x16vcb\x1f\xb1L\xde\xdaS\xa4n\x02\xfb\xbe\
-\xa2\x9c\xcd\xc1\xc2\xf6\x94\x05\x1b\xda>\xbay\xda'\x9f\
-\x5c\xd8\x08\xd7\x8a\xf7y\xd8\xdb\x1aA\x01\xd6?b\xbd\
-\x0a\xd7\x94\x0f\xeb\x80\xf9\xa8\x89\x97Gh\x82^\xa1\x1f\
-\xd1i5\xb8\x84\xf6]Xm\x1e\xc0l\x98\xed\x8ex\
-\xd4x\xa9\xf1\xa2{wh4\xd1\xaa\x9d\xce\xdf\x02\xa4\
->1\x98v\x81!\xf1nA\x99P4\xf7F7\x9f\
-c\x18\xb1\xcd\xdcU<ve\x87\xadv\xae\x8aH\xd9\
-F\x19\xc96\xb86\xfdi!\xe2L9\xa2J\x1dC\
-Dm\x08\xe1Lk\x0d\x0a\x8b\xfa#+\x8e\x11\xd0\x95\
-\x11L\xdf;FL\x1cD\xdf\xaa#\xd5y\xa660\
-\xa99\xb5\x09\x19\xfc\x84\x94\xc8\xa4\x85\x13w2\x8e&\
-)^\x125i\xd0|\x86\xa6\x10\x00w\xe0\xb0:\xdf\
-\x14&:zy\x9b\xce\xc3\x99NW$\xbe=\x17\x8f\
-\x1e\xb6ew\x0f\xc6b=\x17;\x18\x17\xdd\x873\x86\
-\xc4\x897;uQ\xa3\xe8\xc1\x1e\xa2\x84\x8f/t\xf5\
-c(\x96;X\x8ae/%X\xf4\xa0\xfc*\xb0\xfd\
-\x87s~/\x01\x89z\xb8\xda\xbe\xc3Y\xfe\x22E\x96\
-\xfaM\xa1\xec>\x9c\xb1\x19\xf1\x9c\xd35hzL!\
-\xc5R\x12\xa5\x00O\xfb&\xd8R\x1c\xce\xfe\x1a'T\
-\xec4\xe6\x1aE\x1f\xf6|If\x06\x0eJ/\xfb\x8a\
-\xe2p\xf6\xb3X\x0a\xc6\xfc\xaa\xa9\xfa\xfb\xb2~ns\
-[T=\xa6\xd9\xa5\x9b\xdejI)\xf7\x9b{\xd1\xdb\
-\x83m\x99\x9f}\x8c\xcb\xfe\x1e\xac\x1fhJv\xb1.\
-\xfb{\xb1\xd6\xf1\xca\xcf\x17:\x0fgz\x83\xe7~\xab\
-\xcb;{0\x05\x5cy&\x09\xf6\xb1-\xba\xfb1~\
-O\x09Kvp\xb6\xfd=X\xc3\xa9\xe9\x86\xa6>\xc6\
-yo\x0f\xb6\xd9\x1a@\x9bW\xc1y\xaf\x8fm\xeb`\
-\x97\x14\xa9(j\x1c{=G\xed\x92\xd8\x90v\x08\x0a\
-\x08]b\xc5\xf0\xde\xfc\xe7>\x9e\xe5g\x04\xbb\x9c\xee\
-\x19\x18\xc033\x00\xda\x9c\x0c*fTM\xb7\x87\xf4\
-\xee\xd9\xad[\xdb\x04H\x0d\xff\x00f\xeeAk\xea\xa0\
-\x16Z\xeeA[\xd4:\xdd\xb2{\xc9\xf3}\xea\xca-\
-\xf8\x94\xd1\xf8\xb6[\xc0{\xae\xfa\xe6^Z\xab\x9e\x97\
-\xa9\xd50\x9f\xd9}\xa02UB\x07\xee\x1fyOm\
-E\xb1(f\xc2\xd4\xd3\x9f=\x8e5>[\xc9 ,\
-\xcb\x16Q\xbb\x07\x0c\xddW\x19,\x97\xd6\xaeL\x97\xed\
-\xdb\x19w\x96\xc8\xbeky\x8cr8oaf\xec2\
-B\x9f1'\x8eCR\x0a\xcdMm\x5c\xae\xc1==\
-^\x06\x90S\xf8\xbc\xcbp\x0a\xf1=\xa6\xcc\x14\xc9m\
-\xf1\x06\x8e\xa3\xa7]/*\x8e\xa4M\xfa\x0f\xb6\xd59\
-\xa0\xd4Il+F\x97\xdcYW\xac\x13\x96\xfb\x97\x97\
-\x98>-\x16\x8a\xc0|\xe3\xd7\xdd\xea\x97\xd9\x83\xfc\x1a\
-\xca\xae:\xfc\x0cx\x8c\xc8{r\xa6R\x12Ch\xed\
-\xceQ\x855\xba\xb6!\xedN\x8f\x8dR\xc2\x94/\x83\
->e\xc2\xa2&\xd1\xb9X3~z\x0f(B!l\
-j4H,Py\xdd\x82\xf4\x0ak\x14c\x8e\xe6\x04\
-e\x0a|Z\x0b{Y\x92\x00\xc9:e\x04\x82\xa4=\
-\xb6/p\x0c\xe3)G%\xf70\xf0\xealm\x8dM\
-9k\x84%\x8d\x16in\x02\xe1\x5c\x00\xac\x5c{\x09\
-\x8d\xf5V\xe50\xf3\xe1\xa5\x94\xa5-XR\xe9,=\
-\x96\xb4\xf9\xa4\x11\xc2R\x827\x800\xfb\x15)\xf3\xd2\
-\xe6\xa1u\xca\xda(_\xa9\xf2A\xe247\x9e\x9c\xf8\
-+|\xef2\x81]\x8ee\xd7\xf4\xac\xb1\xe5+\xef\x98\
-Z?\xd5\x97\xea,h=[z@Y\xdc]\xe2\xb2\
-\xe7T\xcf}]\xde9\xa8\x95Pm\x95K\x9a\xcb\xf2\
-a\xa3\xd0U\x96SM\x85\xebt\x1b\xde7\x8d\x11\x8d\
-\x22\x17\x10\xbe\x199\x8bh9\xf1A\xf5\xb4Q\xbbd\
-\xd6(\x1f\xb9JJ\x15\x8a\xad\xca\xb1\x83Z\x1a\xb4\xd7\
-y\xf9\xd9\x0c\x06\x16\xf5\xc5O\xb7\xe8\xbf\xe5\xfb\x14v\
-\xa9\x96\xf4\xcdeRLR\xdd\xcd\xc6\xa5*C{\xc5\
-\x09\xe9\xd7\xfe\x85cBX\x5c\x09\x1b\x8c\xe8\x1cQ\xcc\
-\xd4\xce\x98O\xb5I\xaf\xc9\x7f \x0c:&-\xe7h\
-\xa5\xfe\xaa\xdd\xbe\x5cv\x80\xc0\xb3SW\xafq~\xc5\
-\x99\xa7\xae\xa9`\xd9\x9a;\xef\xa1\xea\xd6\x94\x93y\xaf\
-\xa9\x1a\x1d\xbe{\xad\xc6\xa0\x93\xe3\x81\xe3~\xc2\x1d\xc7\
-\xeb\x15x\xb3\xf8(p`\x90NSY\xcc\xdaq\xef\
-P\xea\xd4I\xd0\xc0\x94[\xa5w\xea\x97\xad*ea\
-\xe4\xcd\x0bv\x17}\xf5K\x96\x10\xe0`\x91[\xbc\x80\
-\xb0\xb2\x8c\xd2\x1c\x00\x8e$\xc3\xa6\x81\x1e\x99\x0b p\
-\xbdx\xf5\x9e>\x92df1\xa8\x1f\xd5\xe5\x97\xc5\x15\
-\xcb\xfc\xb7\x1a~\xf2v\x1d\xb74\xc9K/$u\xdf\
-$\xecq\x09\xf5,\xa8|&\xe1_\x13\x08\x03RW\
-\xb8\xbck.\xb1`\xe6\x97C\xc1\x0f\xe4o'\xe4\xf4\
-u\x97@\x18;\xd5\x9b\xc8\xe3\x8eFY\x0e\x87\x04\xe4\
-x\x12\x9aR\xfbqx\xfc\x7fN\xa0{\xaa\xb9F\xf6\
-\xa1\xc0\x8e\x8e;\xc1\xfd\xb3O\xedN\xa7wXo\xdd\
-!\x81\xc7\xf9\xd3U=[uR\xd4\xf7\xb9\xe6)\xc2\
-e\xa9\x1e\xbb\xc2<\x1c\x96W>\x83\xadK\x9bfG\
-\x18\xad\x0d\xda/\x86\xfa,\xbcXI\xfd\xea\xacy,\
-\xf0\xf9D\xf1\xc3\xbc\x16\x10\xce\x7fXd\x0e\xf8\x14\xcc\
-\xcc\xa0_\x82\xb8\xb9\xf9^\x12N\xa4\xfdI\x98A\xc1\
-r\x8by\xb7Hy\x8e\x0d<\x16;\x11\xf0\xb30\xce\
-u\x93~r\xda\xc7\xf7{k\xe6\x92[h\x8f%U\
-\xb0\x18\x80\xfb]5\x9d\x1c9\x1a\xdd\xb7\x9c/Q\x00\
-\x98\x0f\x80 \x122\x01\xc5\xc1\xd9\x02\xf0%\x1c\x163\
-8X*j\xc2\xbc<By\xa5\x03^\x00\xad\xa0\x05\
-V\xf0\xfe\x97i\xaf\xed\xbcO\x83\xff\x01\x9e\xca\xab\x1d\
-\
+\x004\xd3x\x9c\xbd\x1b]s\xdb\xb8\xf1]\xbf\x02\xa7\
+\xbe\xc8\x97\x88\x8a\x9d&\x0f\xba\xa67\xb6\x9c\x0fO\x9d\
+&\xb1\x9dKg2y\x80HHBM\x114\x00\xda\
+V\x12\xff\xf7\xee\x82\x04\xc5\x0f\x80\x96\xe9\xbbb&\x16\
+I,v\x17\x8b\xc5~\x01\x99L\xc8L\xa4\x1b\xc9\x97\
++MF\xb3=r\xf0\xec`\x9f\x5c\xac\x18\xf9\xa4\xa1\
+g\x9d\xd2dCNu\x14\x0c&\x13r\xfe\xf1\xf8?\
+\xe3S\x1e\xb2D\xb1\xf1I\xc4\x12\xcd\x17\x9c\xc9))\
+\xbe\x9d\xb1\xc5\xf8\x93\x1e\xc3\xb05\x93!\xa71\xf9p\
+F\x8e\xce\x8f\xc7\xcf\xc7\xb3\x98f\x8a\x0d\x06\xa9\xa4\xcb\
+55\x98E\x02\x08\x8e\xd8\x8a^s\x018\x8eD\x96\
+D\x83\x01\x87\x0e\xa9\x81\xfaLH\xb6}\xfb\x94\xf1\xf0\
+\xb2\xf1\x1a\x9c\xd2\x8d\xc8\xb4j~\x9e\x89DK\x11\xab\
+\x12\xd90\x18\x12\xaa\xc8a\x9a\x0e\x06\xf0'\xe6!\xd5\
+\x5c$_x\x12\x89\x1b\xf2c@\xa0\xf1hJn\xcc\
+\x07\xf3z\xc3#\xbd\x9a\x92\xe7/\x9f\x99\xd7\x15C\x01\
+M\xc9\x8b\x83\xfc\xfd\x9a+>\x8f\xd9\x94h\x991\xf3\
+Es\x8d\xefW\xeaB\x8e\x86 ;\xc3\x0a\xb1\xac\x0c\
+\xf7\x06\x06j2\xf9\x85|\x15\x92\xc3\xcc\x0d\x0b\xdf\xcc\
+W\xc9h$\x92xCR)R&\xf5\x86\xcc\x85\x88\
+\x09\xf2.)\xd7\xefE\x04\x98\x7f\xa9\x0c\x9b\xadXx\
+y$n\x83\x10\x1fXD~\xfe,\x98\x0f\x0c\xe3\xe4\
+\x1f\xf65g\xdcC\xdb|^dI\x88\xaf0\xc78\
+\x1d\xed\x15\xe2\xc0\x163M\x22\xae\xd2\x98nx\xb2,\
+\xa6B^\x91\x98+\xfd\x07g7A\x98I\x09\xd8N\
+\x92\x88\xdd\x92_^\xbd\x22\xe3\xfd\xda\xe0\xa2\xbf\x18\xf9\
+o\xbaf0\xba\x85\xb1\x1c\x82\xed\xf7-\xf65L;\
+\x0e\x96L\x8f\x9c\x04\xf7\x02#\xf2@\x8bSq\xc3\xe4\
+\x8c*\x06\xdcO\xc9pX\xe3!\x93\xc8\xf2p\xa5u\
+\xaa\xa6\x93I$\xc2\xe0J\x07\x5cL\xae\xf4\xf8\xe5d\
+X#\xfe\x84\x8c\xba\xb93\x0c\x0e\xaf\xd6\xf1\xf8J_\
+\xe1\x02\x8f\xc3b\x81\x0f\xc6C\x18\xee\x98\xf0\x13\xd0\xbe\
+\x95^\xc7\xc3&\x22\xe0\xb4\x8d\x04P\xc3\xb43\x98\x96\
+\x19\xb3\xf7[9\xea\x93\x0e@7\x92\xcf2~}\xab\
+\x99Lh\x1coF0\xb9=\x03q7(\x14\x09\x10\
+J\xd0\x87R\x91\xae\xa9$\xf3\x8c\xc7 \xb3s\xbd\x89\
+\x99\xca\x01\xcf\x99\xd60GUYm\xdc\x00\xaa\xf8\x5c\
+~,\xf1(-\xa1\x03~\x00G\x95\xe2\xf9\x0a\x944\
+\xcct\x05\x91\x02&X\x1225%_\x87\xafU8\
+|J\x86G4\xbc\x1c~+AXBa\xf7 A\
+\x0d\x1dfi#\x96\x82\xde\xfe\x93l5H$\x87\xa0\
+\x98\xd7T#`\x02Vb\x09\x8f\x88\xe8\xd0\xe8k\x00\
+\x1c-\x97L\x8e\xf6\x1e\xc0\xcf\xb9\xa6IDe\xf4/\
+\xb6\x09\xde\x81\xbe\xe3J\xc1\x82\xa9on\xaa\xe5&\xc2\
+\x9dQ%\x93\xb3\xd0\x90^\x9b\xc7m7,q\x90\x80\
+>8\xa7\x8cJ5G\x09\xa1RD\x92\x82:\x0f+\
+\x0c]\xe4\x13E\x86~\xd4\xb4\x88/\xc8\xc8\x81n\xaf\
+\x01f\x84P\x82\xa5\xc2\xce\xa5\xda\xdc{\xba\xb6\xa3\xcd\
+\xf4\x09\x8b\x15s\xe0\xcf\xb96*\xda\xc0~7\xa8?\
+uIP\xa4\xf8Q\xbdgI\xe6\x17\xe0p\x0d\xdd\x1e\
+\xf9T\x10Ty)h\xae\xc0\xce\xa2\xbb\x02\x1f\x10\x5c\
+\x80\x85=\x82\xcd\xb1e\xe0L\xdc\xe4\x0e\xa51=\x95\
+\xd2\x10t\x7fJ\x0a\xdbo\x1bMBP6\x15,x\
+\x1cOIJQlN\x80\x98-\xf4{*\x97<\x01\
++^\xa8T\xd5\xb6\xc3\xf2\x17\xe2\xcb\x8d\xf7\x94\x80'\
+d\x0b\x9e\xb0hPChx\xce\xb4\xae\xc9\xad\xa4f\
+\x04\xd6\xa9\x85\xb6\x95\xde\xcb\xc1Lc\xedj\xaf\xa7t\
+\xceb\x07i\x5c:c\x8c\x0d@\xab[\xb3[p\x9d\
+-\x0d\xd3l\x0d3o\x1bw\xfc\x0e\xde\x1a\x7f\x8fY\
+\xccp.`\xeb\x01\x07\xb1\xee\xf5-\x18?&7\xc3\
+\xb6\x1a/`?\x07)\xbfe\xf19\xff\xceZK\x86\
+\x8d\xc5\x1c\xdd\xa9a5x\x8d/g\xa5\x8b\xac6X\
+:\xfe\x1d\xd0\xd1\xf80\xe6\xcb\x04\x94\x0e&\x01V\xd8\
+\xbc\xbd\x9b\xc1+\x93m\xc9\x82\xb1\x84\xe8\xc25\xe4\x0f\
+\xcf\x90\x5c\xe7\x8c\x16}\xc9\x83\x8e2\xa6\xf0,\xc3N\
+j\xd0\xdeJ-P\xecs`\xc0\xd6\xd8\x8cN\x98[\
+\xab\xf4\x85\xda\x8e\xf3\xa0\xc9\x09\x0b\xea\x95\xa8\x85\x90\xeb\
+\x0f\xb0Wq\x1f\x98\x1dz!\xd2\x5c\xf6\xce1-\x0b\
+\xd1Bj\xf4*W\x09\xeb\xd0\x1c:a[\xcdPX\
+Ow\x0cQ\xaaX\xba\xec\x96mw\x8fg\x0e=\xcd\
+\xae\x8c\xb5]\xce_\xc0\xd0\xe1\x1cTnW\x8e(\x02\
+\xf7\x91\xd3\xdd\xc0\xfdV\xf3\x03\xc7\xc6\xf25\xfc@n\
+\x0e\xb7jQD\xe3\xef\xa9^\x05k\x9e\x8c\xaa\xb1\xee\
+\xd3z\xa8\xbbG&\xe49\xf9\x95\x1c\x94\x83m\xec\xde\
+\x8e\x88\xb1A\xb8E\xe3nK\xc8q\xdf\xe2\xbe\xbav\
+\x9bL\xb4bM7\xfc\x0a\xa2\xe1}\x0cG\x17\x14\x1c\
+\xe66\x9e\x12\x8a\xe7\xfb\xd3\x8d\xe7\x19\x8c\xd8z\xdc\xd2\
+N\xbb\xbc\xc6V:\xa7\x85\x05mF\x07\xd1\xd6\xe8\xd6\
+w\xd8B\x84\x99r\xd8\x99\xaa\xef\x9f6]\xbf\xd3\xdd\
+\xd5 L\xc0>5\xec \x83.O\x81}\xafc\x86\
+\x96\x91\xfc\xa8\xe7LG\x99\xda\x00e\xcc\xcd\x84\x84\xc8\
+\x97(\x91\xc9\x10\xfd\xfd\x95\x0c\xa7\x93\x94.\x99\x9a\xd4\
+\x80>\xc2\xa7\x00\x22\xe6\xa1C\xf7:\x09\xa1\xe9\xf4S\
+\xc0\xde\x9e\xa8m^\xe6Cn\xfb\xfb\xa2\x17\xeb\xb9\xe8\
+B_\xf4\xf7D\x0f\xde\x96n\xba\xa5S\x01\xe9K\x04\
+\x0c\x89\x17;\xf4=\x02\xadXv!\x16\xcb\xfeb1\
+A\x88\xea\x10\x8a\x01\xe8\x89\xff\x8d\x84\xb0\xd6\x87\xdbt\
+\xf6D\xfcV\x8a,\xedP\x17\xdb\xdf\x13=\xbap\x88\
+\xa0}\xd8\x8b\xee\xbe\xc8!\xf2Z\xbd\x95<\xf2\xa2\xb7\
+\x00=\x09\xe0\xb0{\xcdM\x0d\xa8/!)\x96\x92)\
+\xd5!\xaa\x0aHO\x22g4\xe2\xa2{\xebV@z\
+\x13I\x96\xec\x1c#f\xefL* =\x89\x9c\x87R\
+\xc4q\x87\xb0J\x80G\x11\xb8w\xe9\x1b`}\x891\
+*\xc3\xd5\x1b\xceb\xaf\x22W@\xfa\x12\xe9\x5c\x92\xc7\
+\xadF\xca\x93\x0e\x0bRt\xf7F\x1es\x13\x9d\xf8\xd1\
+\x17\x00}\x09\xd8\xa0\xccK\xc0\x02\xf4%p\xc3S\xd6\
+I\xc0\x02\xf4'\xa0\xc3U\x07v\xe8\xed\x89\xfa\x82\xce\
+;vY\xde\xdb\x1fu\xdc)\x96\x12\xa0/\x01\xc8e\
+\x0e%\xa3^\xfcE\xff#\xd0w\xee\xd9\x12\xa0/\x81\
+\xbc\xf8\xe4E\x9fw?\x02\xf9\x05O\xbb\x90Cw_\
+\xe4\x92u\xafl\xd1\xdf\x17}\xb6\x06\xcd\xf0\x0b&\xef\
+\xf6!o\xd4I\xa2\x22.\x9b\xd6jI\x9e\xfa\x95\x05\
+F\xd0\x16@\x91\x7f\xdaD+\xb8\xc6?\xeejG\x9e\
+f\x9bI\xb5KJ\x90y\xc6\x98}bZ]\x22\xe3\
+j\xb6\xad|\xb5K!\xed\x9a>\xe4\xa3\xf0\x0fr\xb4\
+\x1d`\xb1\xfeo\xf2\xb2\x1d`\x8b\x1a\xbf\x9bw/x\
+\xbeNm\xbeE2\x8b9\x1eN5\xab\xd6\xb6\xf9\xaa\
+\xd7\xde\xaa\x84d:\x93\x8e:\x166_\xfd\xda-'\
+l\x95\x8ax\xa6V\xa3|\x1an\xe2\xc8\xa9#\x03\xf7\
+sZ\x94s\xc3X\xe0\xa1\xd4\xbd\x85\x91\xdak#\xf0\
+\x08lIq\xda\xec\x81\xbd\xe3\xa9\xa9\x94n\xady\xbc\
+c\xbfo)v\x16\xb1\xff\xd4\x026O\xb8\x864\x0c\
+\x95|J>\xd2\xa4\xb9\x11\x91\xc1\x14>\xd7\xa5q\xb2\
+\x86\xbd\xee\xd9\xb2\x90\xd2\x09\xdfVEL\x01\xbd\xa6<\
+FWc\x0a\xabdR)\x0a\xd9f\x8bCu\xf8w\
+\xe6\xabs\x80\x95Ih\xaa\xb9'\x89\xb3\xf2_\x05\xb4\
+\xeb\x97\x97\x7f?,\x16\x8a\x01\xbd\xf1\x8bve\x1a\xd7\
+ ?\xeb5\xb3\x0e>BN\xc0\xe45;T)\x0b\
+\xc1\xdf\xb4i\x946\x92\xaf\x8d}\xbc\xd2c\x14J\x90\
+&\xcba\x97\x8e\xf9\x0a\xf9\xd5\x1aa\xeb\x0c\x1bw\xfe\
+5\xc4\x92\x8aP,\x9f\x12\xb1 \xf6\xe0\x92\xe8\x15\xd5\
+$\xa4\x09\x993\x92)\xb0\x12Z\x98c\xc7\x08@\xd6\
+i\xcc\xc0\xec\x9a*\xda\x82\x860\x9e'\xc4b\x0f\x1c\
+UH+\xb9\xb5Q9\xe5\xac\xe2[\x18-\xd2\x5c\x11\
+\x82\xb9\x80\xd4\xa6m\xba\xab:\x5c\x16\xac\xf1\xc5\x0b)\
+\xadF\x18P\xe9<\x1c\xb0\xb09\xd1)\xa1R\xc2\x9e\
+\x00fv;F\xc8\x0f\x1f\x1ez\x92P\x19\xe5;L\
+\xb8\x914\xcdU(\x07\xfe\x02\xef]\x8a\xd0\xb5\xbd\xcc\
+\x9c\xeeU\xb9|\xe6-\x85\xab\x0aiw\xd1[q\x16\
+\xb0\x9e%}\xc0\xf1U\xfd\xc9\x96\x9cM5\xc8s\xf4\
+\x9dw\x96]\xb7E\xd5Y\xe2\xed\x94Q\xad\xf0l\x0f\
+<\xb0\xe2|\xb0\xd5\xe2MmD\xad\xe8\x0c\x80/\xb7\
+\x80E\xe5\xb9V\x85uUf\x1b\x09ey\xceQ\x02\
+\xa8\xe2P;O\xeeaxQ\xac\xffpI~\xda\xe7\
+\x19\x88\xb8\x12\x04\xe0)w\x08\x1e\xb7\xed\x9d\xad\x1c\x02\
+s\xd4\x0f\x1e\xd4\xfc\xe2]\x93\xe2j\x04\xc6\xbe\xb5\x11\
+\xc5i\x1c\x98\x89\x05_\x22\x949\xdb\xcfoz\xe0\xa3\
+\xb5\x1f\xcdK+N\xb2\x05\xbbM\xcfyW\xe1\xfc\x8c\
+\xfd\x17\xcc\xa1\x83s\xcbh#\x04(\xbf\x9b\x87\x93V\
+@p/\xe9\xf21\xcco\x0b\xe4.l&\xe2l\x9d\
+8O\x8c\xab\xfa\x94\x83\xd5\x09V\x0e\x94k\x1d\xbe\x13\
+\xe8\xda\xa0\xfdg\xed\x08\xc8g\xcf\xb1\xd5N\xc9P\x02\
+S\x87\xc9\xbdk\xe3\xb4\xc5\xe4\x8e\x13B+Y'@\
+-\x5c\xdd\x8a\xbeu\xae`[qzPl\x98\xfa\x9d\
+\x15\x17|y\x85,\x80H\xb3p2\xdeX\x13\xdb\x96\
+\x07P\x0a\x08N\xa2Q]\xd7\x9f\xe2Q-l],\
+\xc6\xdc\xb2\xe8\xdc\x84\xb7\xfe\x18/\x0f_K\x94\xf9\xf5\
+'?86\x97b\xb6\xf5\xd16\xf79\xdf\x0e\xc7\xc5\
+\xed\xc1w\xbb\xeb\x19\xaek(b!\xcfa\x9b\xae\x1d\
+\xb2\xc7{\x80b\xcdJ\x0f\x9eO\x03\xa3\x03\x0e\xaeN\
+\xf3\xef\x0c\xa2\x01\x06\xc1\xba\x89Z\xf85\x83\xd4J\x81\
+\xcb\x22j\xa3\xf0h\x7f\x99A\x0c\x11Cl\xe8D-\
+\xc0\xef\x99\xf1\x05tJa]5\x0bZ\xb0\xe5\x15\xa2\
+\xaf\xc37q\x06R\xfd\xc2\x93\xcf'\xcf\xf1\xb2\xd1\x9b\
+Lq\x91\xe0\x13\xffp\x9e\xdf>R<\x1c~\x0bx\
+\x12\xc6\x19p3j\x9a\x0a\xb4i\xed\xa5\xb3\x06\xabc\
+\x07\xd0L\x8b\xd9VZN\xb8\xc2\xdcy\x16\x0a[\xed\
+h\x160:7h\x0f\xee\xc0)\x5c\xde\xc7]\x95\xf6\
+1\xc0\x13\xf4\xa2\x7f\x16\x03&\xd9}\x08\x07\xa7\xc6M\
+\xee\xccB\xee\xf1\xcc9\x86\x87\x0bv\x0bK\xae\xcc\xc9\
+\xadW\xfcs\xeb7\xabz\x0f^\x0a\xc2WP\x0e\xe7\
+\x90\xe2\x0a&+|\xeel\x85u\xefN\xeb\x83w\x11\
+\x95A\xfd\x9b\xdfB\x99z\x1e\x19\x85U\xdc\xae\xdb\x5c\
+\xd5\x16R\xc5\x9az8\xed\x1caH\x1980B`\
+\xf3>'\x97\x89\xb8qO\xb4\xda\xe6\x92\xd1K?\xf7\
+%3\x0d\xb5{\x183\xa8\x83\x7f\x1a'M\xfd{\x18\
++\xa7\xce\xd0\xff\x81\xbc\xb8\xad86\xa0`\xec\xd0;\
+p\x90\xa0n[.\xd15\xf8\xb7\xcb\xfd\xf7,j\xaf\
+\x1d\xbb\xd4\x5c\xf2i_*\xee\xcc\x0a_\x1b\xb3\x0b^\
+(\x89THS\xd76-\xed]\xfd\xea\x83m\x0f\xbf\
+\xeft\xcf\x14<a\xa6\x9f/w\xc0\xda9\xed\xe3\x1c\
+\xa8z\x9f\xfb\xf1\xf3\xda%\x0b?c\x10\xddK]\x96\
+\xdf\x5c\xf2F\xcd\x81\x84\xeco\xec\xef\xfb\xec\xe0E;\
+\x0f\x13\x18:\xea\xcd\xd4\x13!c\xe4\xe2\x88\x91\xc9\xef\
+d?\xc0\xfb(\xcf\x82v\xfa\xfd\xff\xcdjw\x8cy\
+*`\xef\x8a\xb2\x8ec\x09\xeaO])a\xe5\xe2\xd3\
+\xe3\x92\xb6\xe6u+\x08\x82\xfd\x99d5\x91le\x8f\
+e\xe7\xa3nD\x15y\x8c\x15\x92\x99g\x9e\xa7\xd8\xdb\
+Q%h\xfe\xd9\x91\xdfT\x06\xed\x96\xdc\xf8\xb4\xbd\x98\
+I\xf5\x96Y\xbdn\xd7\xb9?\x8a\xff\xa9\xd2\xa8T\xe5\
+w\xe8\xb1\xa6\x0f\x9e_*\x13P&x\x83t\xc9\x12\
+&\x8d\xb5\xc32\x95\xdc\x16\xa5\xb6\xa5\xac9\xc5\xfa\x95\
+\xe8.Q\xdd[g\xc9\xf5\xa2~\x19u\xff\xa0\x8f5\
+x\x9c|N\x12S\x81\xa3\x92+\x98\x92\x16\x0ea\xed\
+?u|l[\x11lO\xc8\x90J\x886\x12\xf0\x1b\
+\x11\x88O,\xc8\x9a.\x13\xae!\xaa&\x8ac\x12&\
+\x9f\xe6^\x17\x1f\xc0I\x80\x13\x808^\xfeu2\xac\
+?\xdd\x0d\xee\x06\xff\x03\xeeC\x8b\x0c\
 \x00\x00\x00`\
 [\
 Material]\x0aPrimar\
@@ -185,10 +216,13 @@ Qt-Commercial OR\
 mport QtQuick.Co\
 ntrols\x0a\x0aToolBar \
 {}\x0a\
-\x00\x00\x00\x0b\
+\x00\x00\x00?\
 m\
-odule App\x0a\
-\x00\x00\x03\x0b\
+odule App\x0asingle\
+ton GalleryConfi\
+g 1.0 pages/Gall\
+eryConfig.qml\x0a\
+\x00\x00\x03*\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -214,32 +248,34 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22SpinBox\
- allows the user\
- to choose an in\
-teger value by c\
-licking the up o\
-r down indicator\
- buttons, \x22\x0a    \
-            + \x22b\
-y pressing up or\
- down on the key\
-board, or by ent\
-ering a text val\
-ue in the input \
-field.\x22\x0a        \
-}\x0a\x0a        SpinB\
-ox {\x0a           \
- id: box\x0a       \
-     value: 50\x0a \
-           ancho\
-rs.horizontalCen\
-ter: parent.hori\
-zontalCenter\x0a   \
-         editabl\
-e: true\x0a        \
-}\x0a    }\x0a}\x0a\
-\x00\x00\x02\x84\
+  text: qsTr(\x22Sp\
+inBox allows the\
+ user to choose \
+an integer value\
+ by clicking the\
+ up or down indi\
+cator buttons, \x22\
+\x0a               \
+ + \x22by pressing \
+up or down on th\
+e keyboard, or b\
+y entering a tex\
+t value in the i\
+nput field.\x22)\x0a  \
+      }\x0a\x0a       \
+ SpinBox {\x0a     \
+       enabled: \
+!GalleryConfig.d\
+isabled\x0a        \
+    value: 50\x0a  \
+          anchor\
+s.horizontalCent\
+er: parent.horiz\
+ontalCenter\x0a    \
+        editable\
+: true\x0a        }\
+\x0a    }\x0a}\x0a\
+\x00\x00\x02\x8a\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -265,24 +301,24 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22PageInd\
-icator is used t\
-o indicate the c\
-urrently active \
-page in a contai\
-ner of pages.\x22\x0a \
-       }\x0a\x0a      \
-  PageIndicator \
-{\x0a            co\
-unt: 5\x0a         \
-   currentIndex:\
- 2\x0a            a\
-nchors.horizonta\
-lCenter: parent.\
-horizontalCenter\
-\x0a        }\x0a    }\
-\x0a}\x0a\
-\x00\x00\x05{\
+  text: qsTr(\x22Pa\
+geIndicator is u\
+sed to indicate \
+the currently ac\
+tive page in a c\
+ontainer of page\
+s.\x22)\x0a        }\x0a\x0a\
+        PageIndi\
+cator {\x0a        \
+    count: 5\x0a   \
+         current\
+Index: 2\x0a       \
+     anchors.hor\
+izontalCenter: p\
+arent.horizontal\
+Center\x0a        }\
+\x0a    }\x0a}\x0a\
+\x00\x00\x05\xb8\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -294,85 +330,89 @@ Qt-Commercial OR\
 mport QtQuick\x0aim\
 port QtQuick.Con\
 trols\x0a\x0aPage {\x0a  \
-  id: page\x0a\x0a    \
-SwipeView {\x0a    \
-    id: swipeVie\
-w\x0a        anchor\
-s.fill: parent\x0a \
-       currentIn\
-dex: tabBar.curr\
-entIndex\x0a\x0a      \
-  Repeater {\x0a   \
-         model: \
-3\x0a\x0a            P\
-ane {\x0a          \
-      width: Swi\
-peView.view.widt\
-h\x0a              \
-  height: SwipeV\
-iew.view.height\x0a\
-\x0a               \
- Column {\x0a      \
-              sp\
-acing: 40\x0a      \
-              wi\
-dth: parent.widt\
-h\x0a\x0a             \
-       Label {\x0a \
+  id: page\x0a    e\
+nabled: !Gallery\
+Config.disabled\x0a\
+\x0a    SwipeView {\
+\x0a        id: swi\
+peView\x0a        a\
+nchors.fill: par\
+ent\x0a        curr\
+entIndex: tabBar\
+.currentIndex\x0a\x0a \
+       Repeater \
+{\x0a            mo\
+del: 3\x0a\x0a        \
+    Pane {\x0a     \
+           width\
+: SwipeView.view\
+.width\x0a         \
+       height: S\
+wipeView.view.he\
+ight\x0a\x0a          \
+      Column {\x0a \
                 \
-       width: pa\
-rent.width\x0a     \
+   spacing: 40\x0a \
                 \
-   wrapMode: Lab\
-el.Wrap\x0a        \
+   width: parent\
+.width\x0a\x0a        \
+            Labe\
+l {\x0a            \
+            widt\
+h: parent.width\x0a\
                 \
-horizontalAlignm\
-ent: Qt.AlignHCe\
-nter\x0a           \
-             tex\
-t: \x22TabBar is a \
-bar with icons o\
-r text which all\
-ows the user \x22\x0a \
+        wrapMode\
+: Label.Wrap\x0a   \
                 \
-             + \x22\
-to switch betwee\
-n different subt\
-asks, views, or \
-modes.\x22\x0a        \
-            }\x0a\x0a \
+     horizontalA\
+lignment: Qt.Ali\
+gnHCenter\x0a      \
                 \
-   Image {\x0a     \
+  text: qsTr(\x22Ta\
+bBar is a bar wi\
+th icons or text\
+ which allows th\
+e user \x22\x0a       \
                 \
-   source: \x22../i\
-mages/arrows.png\
-\x22\x0a              \
-          anchor\
-s.horizontalCent\
-er: parent.horiz\
-ontalCenter\x0a    \
+       + \x22to swi\
+tch between diff\
+erent subtasks, \
+views, or modes.\
+\x22)\x0a             \
+       }\x0a\x0a      \
+              Im\
+age {\x0a          \
+              so\
+urce: \x22../images\
+/arrows.png\x22\x0a   \
                 \
-}\x0a              \
-  }\x0a            \
-}\x0a        }\x0a    \
-}\x0a\x0a    footer: T\
-abBar {\x0a        \
-id: tabBar\x0a     \
-   currentIndex:\
- swipeView.curre\
-ntIndex\x0a\x0a       \
- TabButton {\x0a   \
-         text: \x22\
-First\x22\x0a        }\
+     anchors.hor\
+izontalCenter: p\
+arent.horizontal\
+Center\x0a         \
+           }\x0a   \
+             }\x0a \
+           }\x0a   \
+     }\x0a    }\x0a\x0a  \
+  footer: TabBar\
+ {\x0a        id: t\
+abBar\x0a        cu\
+rrentIndex: swip\
+eView.currentInd\
+ex\x0a\x0a        TabB\
+utton {\x0a        \
+    text: qsTr(\x22\
+First\x22)\x0a        \
+}\x0a        TabBut\
+ton {\x0a          \
+  text: qsTr(\x22Se\
+cond\x22)\x0a        }\
 \x0a        TabButt\
 on {\x0a           \
- text: \x22Second\x22\x0a\
-        }\x0a      \
-  TabButton {\x0a  \
-          text: \
-\x22Third\x22\x0a        \
-}\x0a    }\x0a}\x0a\
-\x00\x00\x02\xc6\
+ text: qsTr(\x22Thi\
+rd\x22)\x0a        }\x0a \
+   }\x0a}\x0a\
+\x00\x00\x02\xcc\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -400,25 +440,25 @@ idth\x0a           \
   horizontalAlig\
 nment: Qt.AlignH\
 Center\x0a         \
-   text: \x22BusyIn\
-dicator is used \
-to indicate acti\
-vity while conte\
-nt is being load\
-ed,\x22\x0a           \
-       + \x22 or wh\
-en the UI is blo\
-cked waiting for\
- a resource to b\
-ecome available.\
-\x22\x0a        }\x0a\x0a   \
-     BusyIndicat\
-or {\x0a           \
anchors.horizon\
-talCenter: paren\
-t.horizontalCent\
-er\x0a        }\x0a   \
- }\x0a}\x0a\
+   text: qsTr(\x22B\
+usyIndicator is \
+used to indicate\
+ activity while \
+content is being\
+ loaded,\x22\x0a      \
+            + \x22 \
+or when the UI i\
+s blocked waitin\
+g for a resource\
+ to become avail\
+able.\x22)\x0a        \
+}\x0a\x0a        BusyI\
+ndicator {\x0a     \
      anchors.h\
+orizontalCenter:\
+ parent.horizont\
+alCenter\x0a       \
+ }\x0a    }\x0a}\x0a\
 \x00\x00\x02\x01\
 /\
 / Copyright (C) \
@@ -454,7 +494,7 @@ Indicator.vertic\
 al: ScrollIndica\
 tor { }\x0a    }\x0a}\x0a\
 \
-\x00\x00\x03O\
+\x00\x00\x03U\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -480,39 +520,40 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22Progres\
-sBar indicates t\
-he progress of a\
-n operation. It \
-can be set in an\
- \x22\x0a             \
-   + \x22indetermin\
-ate mode to indi\
-cate that the le\
-ngth of the oper\
-ation is unknown\
-.\x22\x0a        }\x0a\x0a  \
+  text: qsTr(\x22Pr\
+ogressBar indica\
+tes the progress\
+ of an operation\
+. It can be set \
+in an \x22\x0a        \
+        + \x22indet\
+erminate mode to\
+ indicate that t\
+he length of the\
+ operation is un\
+known.\x22)\x0a       \
+ }\x0a\x0a        Prog\
+ressBar {\x0a      \
+      id: bar\x0a  \
+          value:\
+ 0.5\x0a           \
+ anchors.horizon\
+talCenter: paren\
+t.horizontalCent\
+er\x0a        }\x0a\x0a  \
       ProgressBa\
 r {\x0a            \
-id: bar\x0a        \
-    value: 0.5\x0a \
-           ancho\
-rs.horizontalCen\
-ter: parent.hori\
-zontalCenter\x0a   \
-     }\x0a\x0a        \
-ProgressBar {\x0a  \
-          indete\
-rminate: true\x0a  \
-          anchor\
-s.horizontalCent\
-er: parent.horiz\
-ontalCenter\x0a    \
-    }\x0a    }\x0a}\x0a\
-\x00\x00\x02\xda\
+indeterminate: t\
+rue\x0a            \
+anchors.horizont\
+alCenter: parent\
+.horizontalCente\
+r\x0a        }\x0a    \
+}\x0a}\x0a\
+\x00\x00\x06\x87\
 /\
 / Copyright (C) \
-2017 The Qt Comp\
+2025 The Qt Comp\
 any Ltd.\x0a// SPDX\
 -License-Identif\
 ier: LicenseRef-\
@@ -535,29 +576,88 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22A tool \
-tip is a short p\
-iece of text tha\
-t informs the us\
-er of a control'\
-s function.\x22\x0a   \
-     }\x0a\x0a        \
-Button {\x0a       \
-     text: \x22Tip\x22\
-\x0a            anc\
-hors.horizontalC\
-enter: parent.ho\
-rizontalCenter\x0a\x0a\
-            Tool\
-Tip.timeout: 500\
-0\x0a            To\
-olTip.visible: p\
-ressed\x0a         \
-   ToolTip.text:\
- \x22This is a tool\
- tip.\x22\x0a        }\
-\x0a    }\x0a}\x0a\
-\x00\x00\x02\x9b\
+  text: qsTr(\x22Se\
+archField is a s\
+tyled text input\
+ for searching, \
+typically \x22\x0a    \
+                \
+   + \x22with a mag\
+nifier and clear\
+ icon.\x22)\x0a       \
+ }\x0a\x0a        List\
+Model {\x0a        \
+    id: colorMod\
+el\x0a            L\
+istElement { col\
+or: \x22blue\x22 }\x0a   \
+         ListEle\
+ment { color: \x22g\
+reen\x22 }\x0a        \
+    ListElement \
+{ color: \x22red\x22 }\
+\x0a            Lis\
+tElement { color\
+: \x22yellow\x22 }\x0a   \
+         ListEle\
+ment { color: \x22o\
+range\x22 }\x0a       \
+     ListElement\
+ { color: \x22purpl\
+e\x22 }\x0a        }\x0a\x0a\
+        SortFilt\
+erProxyModel {\x0a \
+           id: c\
+olorFilter\x0a     \
+       model: co\
+lorModel\x0a       \
+     sorters: [\x0a\
+                \
+RoleSorter {\x0a   \
+                \
+ roleName: \x22colo\
+r\x22\x0a             \
+   }\x0a           \
+ ]\x0a            f\
+ilters: [\x0a      \
+          Functi\
+onFilter {\x0a     \
+               c\
+omponent CustomD\
+ata: QtObject { \
+property string \
+color }\x0a        \
+            prop\
+erty var regExp:\
+ new RegExp(colo\
+rSearch.text, \x22i\
+\x22)\x0a             \
+       onRegExpC\
+hanged: invalida\
+te()\x0a           \
+         functio\
+n filter(data: C\
+ustomData): bool\
+ {\x0a             \
+          return\
+ regExp.test(dat\
+a.color);\x0a      \
+              }\x0a\
+                \
+}\x0a            ]\x0a\
+        }\x0a\x0a     \
+   SearchField {\
+\x0a            id:\
+ colorSearch\x0a   \
+         suggest\
+ionModel: colorF\
+ilter\x0a          \
+  anchors.horizo\
+ntalCenter: pare\
+nt.horizontalCen\
+ter\x0a        }\x0a  \
+  }\x0a}\x0a\
+\x00\x00\x02\xd4\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -583,25 +683,29 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22TextAre\
-a is a multi-lin\
-e text editor.\x22\x0a\
-        }\x0a\x0a     \
-   TextArea {\x0a  \
-          width:\
- page.availableW\
-idth / 3\x0a       \
-     anchors.hor\
-izontalCenter: p\
-arent.horizontal\
-Center\x0a\x0a        \
-    wrapMode: Te\
-xtArea.Wrap\x0a    \
-        text: \x22T\
-extArea\x5cn...\x5cn..\
-.\x5cn...\x22\x0a        \
-}\x0a    }\x0a}\x0a\
-\x00\x00\x02\xff\
+  text: qsTr(\x22Te\
+xtArea is a mult\
+i-line text edit\
+or.\x22)\x0a        }\x0a\
+\x0a        TextAre\
+a {\x0a            \
+enabled: !Galler\
+yConfig.disabled\
+\x0a            wid\
+th: page.availab\
+leWidth / 3\x0a    \
+        anchors.\
+horizontalCenter\
+: parent.horizon\
+talCenter\x0a\x0a     \
+       wrapMode:\
+ TextArea.Wrap\x0a \
+           text:\
+ qsTr(\x22TextArea\x5c\
+n...\x5cn...\x5cn...\x22)\
+\x0a        }\x0a    }\
+\x0a}\x0a\
+\x00\x00\x02\xec\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -627,31 +731,68 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22Slider \
-is used to selec\
-t a value by sli\
-ding a handle al\
-ong a track.\x22\x0a  \
-      }\x0a\x0a       \
- Slider {\x0a      \
-      id: slider\
-\x0a            val\
-ue: 0.5\x0a        \
-    anchors.hori\
-zontalCenter: pa\
-rent.horizontalC\
-enter\x0a        }\x0a\
-\x0a        Slider \
-{\x0a            or\
-ientation: Qt.Ve\
-rtical\x0a         \
-   value: 0.5\x0a  \
-          anchor\
-s.horizontalCent\
-er: parent.horiz\
-ontalCenter\x0a    \
-    }\x0a    }\x0a}\x0a\
-\x00\x00\x02E\
+  text: qsTr(\x22A \
+tool tip is a sh\
+ort piece of tex\
+t that informs t\
+he user of a con\
+trol's function.\
+\x22)\x0a        }\x0a\x0a  \
+      Button {\x0a \
+           text:\
+ qsTr(\x22Tip\x22)\x0a   \
+         anchors\
+.horizontalCente\
+r: parent.horizo\
+ntalCenter\x0a\x0a    \
+        ToolTip.\
+timeout: 5000\x0a  \
+          ToolTi\
+p.visible: press\
+ed\x0a            T\
+oolTip.text: qsT\
+r(\x22This is a too\
+l tip.\x22)\x0a       \
+ }\x0a    }\x0a}\x0a\
+\x00\x00\x02=\
+\x00\
+\x00\x07\x7fx\x9c\xd5TM\x8f\xd30\x10\xbd\xe7W\x0c\
+9\xed\x0a\x92.]\x81\xd4\xdchW\xd0JEl?\
+\xc4r\xe0\xe2\xc6\x93\xc6\xc2\xb5\x83\xed\xb4\x14\xb4\xff\x9d\
+\xb1\xd3n\xbfO\xcb\x81\xf5\xc5\x9a\xf1\x9b7\xe37\x1e\
+\xb7Z\xd0\xd3\xd5\xda\x88y\xe9\xe0\xaaw\x0d\xed\x9b\xf6\
+;\x98\x96\x08#G'\x8b\x8a\xa95\x0c\x1dO\xa3V\
+\x0b&\xf7w\xdf\x92\xa1\xc8QYL\x06\x1c\x95\x13\x85\
+@\x93\xc1\xc67\xc6\x22\x19\xb9\x84\xc2\x16hr\xc1$\
+|\x19Cwr\x97\xdc&=\xc9j\x8bQ$\x16\x95\
+6\x8e\xc8G\xb5\xc8\x7f\x1c\x99iO+g\xb4\xb4\xc7\
+\xfe![\xeb\xda\xd9(\xbags\x84?\x11\xd0\x12<\
+\x83\x8a\xcc`\xa0b3\x89\xe4y\xf5\x89I\x89fM\
+L\x85\x98\xa7\x5c\xd8p\x10\x05T\x89\x8c\xfbr\xa7Z\
+\xcb.3\x1b\x22\xbf\xc6z\xd5\xe4\xd8\xf3\xf9\xc5T^\
+jc\xd3BH\xe9\xd3\x19\xbast\x80\x188\x5c\x1c\
+\x05\xf9\xd5\xb0\x85\xb8>zq3p\xa6\xc6K\xb8\xca\
+`\x81\xc6 \x7f\x10\xdc\x95\x19U\xeac\x0e\xd0\x8f\x87\
+y\x87l\x86\xf2Lb*W\xfc&\x19\x99\xfc \xc5\
+\x5c-\xa8`\xba0\xferi\xb0\xfb=r\xa09\x09\
+[\xa2q\x22\xbf\x10\xf4\xf5B\x90#D\x06?\xed\xd4\
+\x5c\xc5\xfd\xa0m|\x1d=W\x0a\x8f\xdb\xa8p\x02;\
+\xd2\xc0\xf7q\x82\xd4\x15\xe64u\xf3\xdcq\xb7vN\
++:kj\x8d\xbf\xd7\xed\xf7\x9dNL\xd0\x1d\xe7\x1e\
+\xf3\xb1\xaa\xdb\xfe\xe7A\x80\x81zz\x03[\xc0\xaa\xa9\
+\xb4\xf1\xa6\xc1\x82\x84\x86h\x070\xac\xfa\xac9f\x0d\
+w\xfa@\xf6\xd3\xe1\xd9f\x8d.\xb4j_\xed\xed\x0b\
+\xae\x8c^\x0a\x8e\x16\xd8\x1e\x17\xe4~\x13\x0a\x0d\x14\xa4\
+\x0b\xab*I\xaduB\xab\x84\x0aD\x88O\xa4\x87\xd7\
+\x103\xc5C eI,M\xb3pb\x89\xc1\xe3'\
+\xf2\x0d\xd8:/\x81YPl)\xe6\x81\x0dfA\x5c\
+JN\xa1\xe7I-2Ca\xf4KHN\x1cn]\
+\xf9G&\xd7@\x9f\x01\xf7ll3\x94\xa0}\xb1\x9a\
+\xeeK\x92\xbaR(\x22\xbdT\xe8\xee>\x84U\x5c\xaf\
+\xe8\xd5\xed\xf5\xb0\xa1\xf9\xb7S\xfe\xffL\x9b\x7f\xc1o\
+o;\xf1\xb3\xe7\xec\xe5\x7f9\x1fC\xa3_\xc4\x97s\
+{s\xee\xcby\x8c\xfe\x02O\xe4\x17\xf2\
+\x00\x00\x03H\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -677,20 +818,36 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22Tumbler\
- is used to sele\
-ct a value by sp\
-inning a wheel.\x22\
-\x0a        }\x0a\x0a    \
-    Tumbler {\x0a  \
-          model:\
- 10\x0a            \
+  text: qsTr(\x22Sl\
+ider is used to \
+select a value b\
+y sliding a hand\
+le along a track\
+.\x22)\x0a        }\x0a\x0a \
+       Slider {\x0a\
+            enab\
+led: !GalleryCon\
+fig.disabled\x0a   \
+         value: \
+0.5\x0a            \
 anchors.horizont\
 alCenter: parent\
 .horizontalCente\
-r\x0a        }\x0a    \
-}\x0a}\x0a\
-\x00\x00\x04i\
+r\x0a        }\x0a\x0a   \
+     Slider {\x0a  \
+          enable\
+d: !GalleryConfi\
+g.disabled\x0a     \
+       orientati\
+on: Qt.Vertical\x0a\
+            valu\
+e: 0.5\x0a         \
+   anchors.horiz\
+ontalCenter: par\
+ent.horizontalCe\
+nter\x0a        }\x0a \
+   }\x0a}\x0a\
+\x00\x00\x02x\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -700,70 +857,618 @@ ier: LicenseRef-\
 Qt-Commercial OR\
  BSD-3-Clause\x0a\x0ai\
 mport QtQuick\x0aim\
-port QtQuick.Lay\
-outs\x0aimport QtQu\
-ick.Controls\x0a\x0aSc\
-rollablePage {\x0a \
-   id: page\x0a\x0a   \
- Column {\x0a      \
-  spacing: 40\x0a  \
-      width: par\
-ent.width\x0a\x0a     \
-   Label {\x0a     \
-       width: pa\
-rent.width\x0a     \
-       wrapMode:\
- Label.Wrap\x0a    \
-        horizont\
-alAlignment: Qt.\
-AlignHCenter\x0a   \
-         text: \x22\
-Button presents \
-a push-button th\
-at can be pushed\
- or clicked by t\
-he user. \x22\x0a     \
-           + \x22Bu\
-ttons are normal\
-ly used to perfo\
-rm an action, or\
- to answer a que\
-stion.\x22\x0a        \
-}\x0a\x0a        Colum\
-nLayout {\x0a      \
-      spacing: 2\
-0\x0a            an\
-chors.horizontal\
-Center: parent.h\
-orizontalCenter\x0a\
-\x0a            But\
-ton {\x0a          \
-      text: \x22Fir\
-st\x22\x0a            \
-    Layout.fillW\
-idth: true\x0a     \
-       }\x0a       \
-     Button {\x0a  \
-              id\
-: button\x0a       \
-         text: \x22\
-Second\x22\x0a        \
-        highligh\
-ted: true\x0a      \
+port QtQuick.Con\
+trols\x0a\x0aScrollabl\
+ePage {\x0a    id: \
+page\x0a\x0a    Column\
+ {\x0a        spaci\
+ng: 40\x0a        w\
+idth: parent.wid\
+th\x0a\x0a        Labe\
+l {\x0a            \
+width: parent.wi\
+dth\x0a            \
+wrapMode: Label.\
+Wrap\x0a           \
+ horizontalAlign\
+ment: Qt.AlignHC\
+enter\x0a          \
+  text: qsTr(\x22Tu\
+mbler is used to\
+ select a value \
+by spinning a wh\
+eel.\x22)\x0a        }\
+\x0a\x0a        Tumble\
+r {\x0a            \
+enabled: !Galler\
+yConfig.disabled\
+\x0a            mod\
+el: 10\x0a         \
+   anchors.horiz\
+ontalCenter: par\
+ent.horizontalCe\
+nter\x0a        }\x0a \
+   }\x0a}\x0a\
+\x00\x00\x0b\xb4\
+/\
+/ Copyright (C) \
+2025 The Qt Comp\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ai\
+mport QtQuick\x0aim\
+port QtQuick.Con\
+trols\x0aimport QtQ\
+uick.Layouts\x0aimp\
+ort Qt.labs.qmlm\
+odels\x0a\x0aPage {\x0a  \
+  id: page\x0a\x0a    \
+GridLayout {\x0a   \
+     anchors.fil\
+l: parent\x0a      \
+  anchors.margin\
+s: 10\x0a\x0a        L\
+abel {\x0a         \
+   wrapMode: Lab\
+el.Wrap\x0a        \
+    horizontalAl\
+ignment: Qt.Alig\
+nHCenter\x0a       \
+     text: qsTr(\
+\x22TreeView provid\
+es a hierarchica\
+l view for displ\
+aying and \x22\x0a    \
+                \
+ + \x22navigating t\
+ree-structured d\
+ata, allowing us\
+ers to expand an\
+d \x22\x0a            \
+         + \x22coll\
+apse nodes to ex\
+plore parent-chi\
+ld relationships\
+ within a model\x22\
+)\x0a\x0a            L\
+ayout.fillWidth:\
+ true\x0a          \
+  Layout.columnS\
+pan: 2\x0a        }\
+\x0a\x0a        Item {\
+\x0a            imp\
+licitHeight: 40\x0a\
+\x0a            Lay\
+out.columnSpan: \
+2\x0a            La\
+yout.row: 1\x0a    \
+    }\x0a\x0a        H\
+orizontalHeaderV\
+iew {\x0a          \
+  clip: true\x0a   \
+         enabled\
+: !GalleryConfig\
+.disabled\x0a      \
+      syncView: \
+treeView\x0a       \
+     model: [qsT\
+r(\x22Location\x22)]\x0a\x0a\
+            Layo\
+ut.column: 1\x0a   \
+         Layout.\
+row: 2\x0a         \
+   Layout.fillWi\
+dth: true\x0a      \
+  }\x0a\x0a        Ver\
+ticalHeaderView \
+{\x0a            cl\
+ip: true\x0a       \
+     enabled: !G\
+alleryConfig.dis\
+abled\x0a          \
+  syncView: tree\
+View\x0a           \
+ model: Array.fr\
+om({length: tree\
+View.rows}, (v, \
+k) => k + 1)\x0a\x0a  \
           Layout\
-.fillWidth: true\
-\x0a            }\x0a \
-           Butto\
-n {\x0a            \
-    text: \x22Third\
-\x22\x0a              \
-  enabled: false\
+.column: 0\x0a     \
+       Layout.ro\
+w: 3\x0a           \
+ Layout.fillHeig\
+ht: true\x0a       \
+ }\x0a\x0a        Tree\
+View {\x0a         \
+   id: treeView\x0a\
+            clip\
+: true\x0a         \
+   enabled: !Gal\
+leryConfig.disab\
+led\x0a            \
+rowSpacing: 2\x0a  \
+          model:\
+ treeModel\x0a\x0a    \
+        Layout.c\
+olumn: 1\x0a       \
+     Layout.row:\
+ 3\x0a            L\
+ayout.fillWidth:\
+ true\x0a          \
+  Layout.fillHei\
+ght: true\x0a\x0a     \
+       selection\
+Model: ItemSelec\
+tionModel {}\x0a   \
+         delegat\
+e: TreeViewDeleg\
+ate { }\x0a\x0a       \
+     columnWidth\
+Provider: (colum\
+n) => column ===\
+ 0 ? treeView.wi\
+dth : 0\x0a\x0a       \
+     Component.o\
+nCompleted: expa\
+ndRecursively()\x0a\
+        }\x0a    }\x0a\
+\x0a    TreeModel {\
+\x0a        id: tre\
+eModel\x0a\x0a        \
+TableModelColumn\
+ { display: \x22loc\
+ation\x22 }\x0a\x0a      \
+  rows: [\x0a      \
+      {\x0a        \
+        location\
+: qsTr(\x22America\x22\
+),\x0a             \
+   rows: [\x0a     \
+               {\
+ location: qsTr(\
+\x22Brazil\x22) },\x0a   \
+                \
+ {\x0a             \
+           locat\
+ion: qsTr(\x22Canad\
+a\x22),\x0a           \
+             row\
+s: [\x0a           \
+                \
+ { location: qsT\
+r(\x22Calgary\x22) },\x0a\
+                \
+            { lo\
+cation: qsTr(\x22Va\
+ncouver\x22) }\x0a    \
+                \
+    ]\x0a          \
+          }\x0a    \
+            ]\x0a  \
+          },\x0a   \
+         { locat\
+ion: qsTr(\x22Asia\x22\
+) },\x0a           \
+ {\x0a             \
+   location: qsT\
+r(\x22Europe\x22),\x0a   \
+             row\
+s: [\x0a           \
+         {\x0a     \
+                \
+   location: qsT\
+r(\x22Italy\x22),\x0a    \
+                \
+    rows: [\x0a    \
+                \
+        { locati\
+on: qsTr(\x22Milan\x22\
+) },\x0a           \
+                \
+ { location: qsT\
+r(\x22Rome\x22) }\x0a    \
+                \
+    ]\x0a          \
+          },\x0a   \
+                \
+ { location: qsT\
+r(\x22Portugal\x22) }\x0a\
+                \
+]\x0a            }\x0a\
+\x0a        ]\x0a    }\
+\x0a}\x0a\
+\x00\x00\x03&\
+\x00\
+\x00\x0c\xb5x\x9c\xd5VKo\xdb0\x0c\xbe\xfbWp\
+\x01\x0a$H\xed<\xb6]\x8c\xb6\xc0\x9a`]\x81\xf4\
+\xdd\xa1\xdb\xa9Pm:\x11*K\x9e,\xa3\xf3\x86\xfc\
+\xf7Iv\x9a\xf8\x99\xa5+v(O\x12\xc9\x8f\x22)\
+\x92\xd2`\x00\x13\x11\xa5\x92\xce\x17\x0a\xba\x93\x1e\x8c\x87\
+\xe3\x8fp\xbb@\xb8RZ\x12F\x84\xa70S\xbec\
+\x0d\x06ps9\xfdf\xcf\xa8\x87<F\xfb\xd4G\xae\
+h@Q\xba\xb0\xe2]c`_)[\xc3B\x94\x1e\
+%\x0c.\xae\xe1\xf8fj\xbf\xb7'\x8c$1Z\x16\
+\x0d#!\x956~\x95P\xef\xb1\xb2u&\x82+)\
+X\x5c\xe5\xcfH*\x12\x15[\xd6%\x99#\xfc\xb6@\
+\x13\xf5]\x88\xf46\xdb '\x0f\x0c5\xe7\xdd\x09a\
+\x0ce\xaa-\x05t\xee\xf84\xce\x04V\xa65\x11,\
+\x09\xf9\x0ao(\x8e\x88G\xf9\xdc\x85\x0f\xc35\xef\x89\
+\xfajaLK\x1d\x9f\x93\xed\xac\xb5pF\x1e\x90\x15\
+\x0c\xb4\x01JrI\xa23\xe1\xa3\x9b\xa3\x9d;\xbd/\
+),\x84\xa4\xbft\xe4\x84}bt\xceCm\xc6\xd5\
+\xa1;\xd9\xee\xcbDoQ\x96\x00\x0a\x7fj\x8d\x1f\xf1\
+\xad\xecv\xce4pq\x22\xa9\x0f\x91\xc4X\xeb\xc6@\
+\xc0#\x0c\xb9O$\x84F\x0a\xc4\xf0\xe6FG\x04\xe0\
+\x934\xde\x87N\xc9\xe0\x9a\xfa\xd0\xd1X/aD\xa1\
+\x0f\x81\x90\x1a\x18G\xe8\xe9{\xf6rc\xfb\x90\x22\x91\
+\xfb@\xb8\x0fL\x98\x83\x9cNoml\xb9IU\x9e\
+\xec\xfc\xe6*\x19[\xa7}<,\xf1\x09\xf7t.b\
+g\x93\x90<\xf8ur\xab\x02\xab\x04\xbf\x16O\x8d\xc7\
+\x95\x8e\x1c\x0dk\xb2\x1c\xe4\x04\x94\xb1\xbb\xfc.\x95L\
+\xd0\xaa\xe9\x1d'J\x09\xde`\xdc\x90\xaeXF=\xaa\
+V\x16\x16h:\xaaQ\xf3\xaf\xb5\xda\x04\x0a\xf4\x8d\xac\
+\xfcj\x12\x17+\xe2\xa0p\x1fE\x12|\xa2=|4\
+'7\x87`\xc8\x13<V\xc0\xf1\xe9>/\x9d\xc3\xfc\
+\xd6M\x8199\xc7\x86Q+\x9a\x06\xd0\xdd`\x0f`\
+\xd8\xdbr\x94\xa1\xaa\xf1C\x18\xb5[7d\xdb\x1b\x88\
+)\xc4V\xe5% \x8b\xf1\xc5\xc7\xaf\xbdo7\xdc(\
+\xa9s\xeb\x9cS\x85a\x8bC\x85\x12\xfc\x92U\xce\x96\
+\xbbn)\xd7f\xd5\xfa\xb8*\xd2s\xc3yY7\x9d\
+\xf2\xe7Fk\x05\x14\xeblo\x04{\xe3N\xcf!r\
+\xde\xdd$r5\x132\xc69\x09\xb1[\xc9q\xaf\xb9\
+8[\xa8b\xdc\xdcx3~\x97\xfc\xbf\x91\xfe=\xfa\
+\xbf\xfd\xdb\xdf\xb9\x7f\x8ft7\x8e_\xde\xc1\xf5\x01[\
+\xa4~\xffm6\xf0\xb2\xfc\x1e\x983[\x1f\x1b/{\
+\xfab\xfd\xbe\xed\xfa\xd6lQ+\xcd\x83\x9a\xe2\x94\xa4\
+\x17\xc1\x1d\xe2\xa3~\xfcZr\x95w\xa4\x0b\xd5\x1e}\
+\xfdp\xc9T\xf3`\xdd\x86\xaaZ\xd6\xdd5\x9e\x9e'\
+\xe1\x03\xca\xda_\xec\x9f]6UTTm\xad\xaaL\
+\xc5\xad\xd6\xcc\xab\xe6qC\x88\x9b\x0fY\xcb\xa0\xf1\x0b\
+.l\x8d^\x7f\x01\xf3e\xb7\x83\xfc\xfe\xebM\xcb\x5c\
+\xc8\xe3\xf7\x12i&\xf7T\x7f\xdc\x9c9\xaa\xcf\x09c\
+\xdf\xb5\xa0\xdb\x8cY\xa5\xa2\x02\xca\x5coAH$\xbe\
+\xe0,\xd5\xdfL\x11\xa1T\xa9\xfeH*,ZpM\
+\xef\x81Y\xb5\x98\xd8\xb1\xb6\xaa}W^-\xad\xa5\xf5\
+\x07\x08T\x18\x9d\
+\x00\x00\x07\xc4\
+/\
+/ Copyright (C) \
+2025 The Qt Comp\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ai\
+mport QtQuick\x0aim\
+port QtQuick.Con\
+trols\x0aimport QtQ\
+uick.Layouts\x0a\x0aPa\
+ge {\x0a    id: pag\
+e\x0a    enabled: !\
+GalleryConfig.di\
+sabled\x0a\x0a    Colu\
+mnLayout {\x0a     \
+   anchors.fill:\
+ parent\x0a        \
+spacing: 40\x0a\x0a   \
+     CheckBox {\x0a\
+            id: \
+orientationCheck\
+Box\x0a            \
+text: qsTr(\x22Vert\
+ical\x22)\x0a        }\
+\x0a\x0a        Label \
+{\x0a            wr\
+apMode: Label.Wr\
+ap\x0a            h\
+orizontalAlignme\
+nt: Qt.AlignHCen\
+ter\x0a            \
+text: qsTr(\x22Spli\
+tView provides a\
+ container that \
+arranges items h\
+orizontally \x22\x0a  \
+                \
+   + \x22or vertica\
+lly, separated b\
+y draggable spli\
+tters, allowing \
+users \x22\x0a        \
+             + \x22\
+to interactively\
+ resize adjacent\
+ views within an\
+ application.\x22)\x0a\
+            Layo\
+ut.fillWidth: tr\
+ue\x0a        }\x0a\x0a  \
+      SplitView \
+{\x0a            or\
+ientation: orien\
+tationCheckBox.c\
+hecked ? Qt.Vert\
+ical : Qt.Horizo\
+ntal\x0a           \
+ Layout.fillHeig\
+ht: true\x0a       \
+     Layout.fill\
+Width: true\x0a\x0a   \
+         Rectang\
+le {\x0a           \
+     implicitWid\
+th: 200\x0a        \
+        implicit\
+Height: 100\x0a    \
+            colo\
+r: \x22lightblue\x22\x0a \
+               S\
+plitView.maximum\
+Width: 400\x0a\x0a    \
+            Labe\
+l {\x0a            \
+        text: \x22V\
+iew 1\x22\x0a         \
+           ancho\
+rs.centerIn: par\
+ent\x0a            \
+    }\x0a          \
+  }\x0a\x0a           \
+ Rectangle {\x0a   \
+             id:\
+ centerItem\x0a    \
+            colo\
+r: \x22lightgray\x22\x0a \
+               S\
+plitView.minimum\
+Width: 50\x0a      \
+          SplitV\
+iew.minimumHeigh\
+t: 50\x0a          \
+      SplitView.\
+fillWidth: true\x0a\
+                \
+SplitView.fillHe\
+ight: true\x0a\x0a    \
+            Labe\
+l {\x0a            \
+        text: \x22V\
+iew 2\x22\x0a         \
+           ancho\
+rs.centerIn: par\
+ent\x0a            \
+    }\x0a          \
+  }\x0a\x0a           \
+ Rectangle {\x0a   \
+             imp\
+licitWidth: 200\x0a\
+                \
+implicitHeight: \
+100\x0a            \
+    color: \x22ligh\
+tgreen\x22\x0a\x0a       \
+         Label {\
 \x0a               \
+     text: \x22View\
+ 3\x22\x0a            \
+        anchors.\
+centerIn: parent\
+\x0a               \
+ }\x0a            }\
+\x0a        }\x0a    }\
+\x0a}\x0a\
+\x00\x00\x09\x93\
+/\
+/ Copyright (C) \
+2025 The Qt Comp\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ai\
+mport QtQuick\x0aim\
+port QtQuick.Con\
+trols\x0aimport QtQ\
+uick.Layouts\x0aimp\
+ort Qt.labs.qmlm\
+odels\x0a\x0aPage {\x0a  \
+  id: page\x0a    e\
+nabled: !Gallery\
+Config.disabled\x0a\
+\x0a    GridLayout \
+{\x0a        anchor\
+s.fill: parent\x0a\x0a\
+        Label {\x0a\
+            wrap\
+Mode: Label.Wrap\
+\x0a            hor\
+izontalAlignment\
+: Qt.AlignHCente\
+r\x0a            te\
+xt: qsTr(\x22TableV\
+iew provides a s\
+crollable grid t\
+hat displays dat\
+a from \x22\x0a       \
+                \
++ \x22a model in ro\
+ws and columns, \
+allowing users t\
+o view and inter\
+act \x22\x0a          \
+             + \x22\
+with structured \
+information with\
+in an applicatio\
+n.\x22)\x0a           \
  Layout.fillWidt\
 h: true\x0a        \
-    }\x0a        }\x0a\
-    }\x0a}\x0a\
-\x00\x00\x03\xa5\
+    Layout.colum\
+nSpan: 2\x0a       \
+ }\x0a\x0a        Hori\
+zontalHeaderView\
+ {\x0a            c\
+lip: true\x0a      \
+      syncView: \
+tableView\x0a      \
+      model: tab\
+leModel.headerMo\
+del\x0a            \
+Layout.column: 1\
+\x0a            Lay\
+out.row: 1\x0a     \
+       Layout.fi\
+llWidth: true\x0a  \
+      }\x0a\x0a       \
+ VerticalHeaderV\
+iew {\x0a          \
+  clip: true\x0a   \
+         syncVie\
+w: tableView\x0a   \
+         Layout.\
+column: 0\x0a      \
+      Layout.row\
+: 2\x0a            \
+Layout.fillHeigh\
+t: true\x0a        \
+}\x0a\x0a        Table\
+View {\x0a         \
+   id: tableView\
+\x0a            col\
+umnSpacing: 1\x0a  \
+          rowSpa\
+cing: 1\x0a        \
+    clip: true\x0a\x0a\
+            sele\
+ctionModel: Item\
+SelectionModel {\
+}\x0a            mo\
+del: tableModel\x0a\
+\x0a            Lay\
+out.column: 1\x0a  \
+          Layout\
+.row: 2\x0a        \
+    Layout.fillW\
+idth: true\x0a     \
+       Layout.fi\
+llHeight: true\x0a\x0a\
+            dele\
+gate: TableViewD\
+elegate {\x0a      \
+          implic\
+itWidth: 100\x0a   \
+             imp\
+licitHeight: 50\x0a\
+                \
+Component.onComp\
+leted: {\x0a       \
+             if \
+(contentItem as \
+Label) {\x0a       \
+                \
+ contentItem.hor\
+izontalAlignment\
+ = Qt.AlignHCent\
+er\x0a             \
+           conte\
+ntItem.verticalA\
+lignment = Qt.Al\
+ignVCenter\x0a     \
+               }\
+\x0a               \
+ }\x0a            }\
+\x0a        }\x0a    }\
+\x0a\x0a    TableModel\
+ {\x0a        id: t\
+ableModel\x0a      \
+  property var h\
+eaderModel: [qsT\
+r(\x22Name\x22), qsTr(\
+\x22Color\x22)]\x0a      \
+  TableModelColu\
+mn { display: \x22n\
+ame\x22 }\x0a        T\
+ableModelColumn \
+{ display: \x22colo\
+r\x22 }\x0a        row\
+s: [\x0a           \
+ {\x0a             \
+   \x22name\x22: qsTr(\
+\x22cat\x22),\x0a        \
+        \x22color\x22:\
+ qsTr(\x22black\x22)\x0a \
+           },\x0a  \
+          {\x0a    \
+            \x22nam\
+e\x22: qsTr(\x22dog\x22),\
+\x0a               \
+ \x22color\x22: qsTr(\x22\
+brown\x22)\x0a        \
+    },\x0a         \
+   {\x0a           \
+     \x22name\x22: qsT\
+r(\x22bird\x22),\x0a     \
+           \x22colo\
+r\x22: qsTr(\x22white\x22\
+)\x0a            }\x0a\
+        ]\x0a    }\x0a\
+}\x0a\
+\x00\x00\x02D\
+\x00\
+\x00\x08Ux\x9c\xd5TMo\x9b@\x10\xbd\xf3+\xa6\
+>%j\xc1nZ\xa9\x12\x97\xaa&jS\xc9U\xfd\
+\x11)\xbd\xaea0\xab,\xbbdw\x91C\xab\xfc\xf7\
+\xce\x02v\x0c8Q\xd3(R\xcb\xc5\x9e\x997o\x87\
+\xb7\xf3\x18\x8f!RE\xa5\xf9&\xb3p\x12\x9d\xc2\xd9\
+\xe4\xed\x07\xb8\xcc\x10\x16\x96*y\xc1d\x053\x9b\x04\
+\xdex\x0c\xab\xf9\xf9\x0f\x7f\xc6c\x94\x06\xfd\xaf\x09J\
+\xcbS\x8e:\x846\xb7\xc4\xd4_X\x9f\xdar\xd41\
+g\x02\xbe/a\xba:\xf7\xdf\xf9\x91`\xa5A\xcf\xe3\
+y\xa1\xb4%\xf2E\xc9\xe3\xeb^\x18\xccX\xa5Jk\
+\xfa\xe9HI\xab\x950\x9e\xb7\x8a\xe9W\xb0\xb5\xc09\
+\xdb \xfc\xf2\x80\x1e\x9e\x84PP\xe8\xd5Q\xa4D\x99\
+\xcb\xb6\xe4\x1eS\xb0\x98\xcbM\x08\xef'\xfb\xdc\x96'\
+6s]\x9a^\x22\xa8#o_\x5c\xaa\xedA{\xcd\
+\x99a|=U\xb7\xbd\xf4\xee\xf0\xd8\x951\xd9\xa1\x06\
+\x18\x8b\xb76\x84\x1bs\xa9OFQ\x83\x1d\x9dvP\
+w\xdeS\xceK\x05\xb3\x7ft\xd8g\x02>\xeb\xa4B\
+\xa31\x8f\xbc\x19Jw\x17\x04|\xf5\x85\x09\x81\xba\xa2\
+\xabJ\xf9&H\xb8\xa9\x0b\x8fN7o\xc8\x07\x03\x1e\
+\x19u\xc6\xd6(z3\x1e\xbb\xc3N]\xb3\xe2\x9bJ\
+0l\xba\x83+\x8a;\x80Li\xfe\x93V\x8b\x89O\
+\x82odN4!\xed\x5cPG\x17\x11\x85\xa8;\x0d\
+\x87\xc3OKk\x95\xac\x05\x22\xa0\x01\x06Ei2\x7f\
+\xdd\xa4m\xc6,\xc4L\xc2\x1a\xeb<&\xa04\xc4\x82\
+\xbb\xbb\x87uE\x00\x042\x84\x0e`4\xd0\xe85\xb4\
+\xec\xc4\xaa\x11\xa4\xd29\x89[9|\x02VA\x81:\
+\xa5\x1c\x10=\x8b-W\xf2\x8d#\xa7\x02\x93f\x8b\x9a\
+F\xb9)\xd1\xb8Bp\xa0\xed\x81\x9a\x8dE\x1a\xb3\xf5\
+D\xdd\x9b\xe5l\xd2\xc93\x19\x93\x5c&\xb8\xd7\xac\xd1\
+g\xaf\x7f\xbf\xd0]\xb3V\xad\xe1\x92=k\x81\x1a\xd6\
+\xde\xfe\xb8\xa75\xe4\xc0\x99A\x1b\x0f\x1a\x9c\xa3\xba\xbe\
+z\x10\x9a\xa8\xad\x1c\x18c\x87\x86\x8f`u\x89\x10B\
+)\x13L\xb9<B\xd0\xe8\x1e\xa4\x5c\x88\xabf\x85]\
+\xcb\x03\x1ex9\xf1.\xe8\x93/\xdcg\x7f\xe0\xc0\x7f\
+^\xc1\xec~\xf4#\xe2\xfd\x95\xc6KE\xc7\xbd\x8c\xd0\
+\x07\xd4\xff\x9b\xd0O\x92\xb1\xf9w\xe7\xddy\xbf\x01\xba\
+\x1cE\xe8\
+\x00\x00\x00\xc0\
+/\
+/ Copyright (C) \
+2025 The Qt Comp\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ap\
+ragma Singleton\x0a\
+import QtQuick\x0a\x0a\
+QtObject {\x0a    p\
+roperty bool dis\
+abled: false\x0a}\x0a\
+\x00\x00\x03\xb9\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -789,42 +1494,43 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22Switch \
-is an option but\
-ton that can be \
-dragged or toggl\
-ed on or off. \x22\x0a\
-                \
-+ \x22Switches are \
-typically used t\
-o select between\
- two states.\x22\x0a  \
-      }\x0a\x0a       \
- Column {\x0a      \
-      spacing: 2\
-0\x0a            an\
-chors.horizontal\
-Center: parent.h\
-orizontalCenter\x0a\
-\x0a            Swi\
-tch {\x0a          \
-      text: \x22Fir\
-st\x22\x0a            \
-}\x0a            Sw\
-itch {\x0a         \
-       text: \x22Se\
-cond\x22\x0a          \
-      checked: t\
-rue\x0a            \
-}\x0a            Sw\
-itch {\x0a         \
-       text: \x22Th\
-ird\x22\x0a           \
-     enabled: fa\
-lse\x0a            \
-}\x0a        }\x0a    \
-}\x0a}\x0a\
-\x00\x00\x02a\
+  text: qsTr(\x22Sw\
+itch is an optio\
+n button that ca\
+n be dragged or \
+toggled on or of\
+f. \x22\x0a           \
+     + \x22Switches\
+ are typically u\
+sed to select be\
+tween two states\
+.\x22)\x0a        }\x0a\x0a \
+       Column {\x0a\
+            spac\
+ing: 20\x0a        \
+    anchors.hori\
+zontalCenter: pa\
+rent.horizontalC\
+enter\x0a\x0a         \
+   Switch {\x0a    \
+            enab\
+led: !GalleryCon\
+fig.disabled\x0a   \
+             tex\
+t: qsTr(\x22First\x22)\
+\x0a            }\x0a \
+           Switc\
+h {\x0a            \
+    enabled: !Ga\
+lleryConfig.disa\
+bled\x0a           \
+     text: qsTr(\
+\x22Second\x22)\x0a      \
+          checke\
+d: true\x0a        \
+    }\x0a        }\x0a\
+    }\x0a}\x0a\
+\x00\x00\x02\x84\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -850,22 +1556,24 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22TextFie\
-ld is a single-l\
-ine text editor.\
-\x22\x0a        }\x0a\x0a   \
-     TextField {\
-\x0a            id:\
- field\x0a         \
-   placeholderTe\
-xt: \x22TextField\x22\x0a\
-            anch\
-ors.horizontalCe\
-nter: parent.hor\
-izontalCenter\x0a  \
-      }\x0a    }\x0a}\x0a\
-\
-\x00\x00\x05\xcc\
+  text: qsTr(\x22Te\
+xtField is a sin\
+gle-line text ed\
+itor.\x22)\x0a        \
+}\x0a\x0a        TextF\
+ield {\x0a         \
+   enabled: !Gal\
+leryConfig.disab\
+led\x0a            \
+placeholderText:\
+ qsTr(\x22TextField\
+\x22)\x0a            a\
+nchors.horizonta\
+lCenter: parent.\
+horizontalCenter\
+\x0a        }\x0a    }\
+\x0a}\x0a\
+\x00\x00\x06S\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -891,155 +1599,166 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22ComboBo\
-x is a combined \
-button and popup\
- list. It presen\
-ts \x22\x0a           \
-     + \x22a list o\
-f options to the\
- user that occup\
-ies minimal scre\
-en space.\x22\x0a     \
-   }\x0a\x0a        Co\
-mboBox {\x0a       \
-     model: [\x22Fi\
-rst\x22, \x22Second\x22, \
-\x22Third\x22]\x0a       \
-     anchors.hor\
-izontalCenter: p\
-arent.horizontal\
-Center\x0a        }\
-\x0a\x0a        Label \
-{\x0a            wi\
-dth: parent.widt\
-h\x0a            wr\
-apMode: Label.Wr\
-ap\x0a            h\
-orizontalAlignme\
-nt: Qt.AlignHCen\
-ter\x0a            \
-text: \x22ComboBox \
-can be made \x5cl e\
-ditable. An edit\
-able combo box a\
-uto-\x22\x0a          \
-      + \x22complet\
-es its text base\
-d on what is ava\
-ilable in the mo\
-del.\x22\x0a        }\x0a\
-\x0a        ComboBo\
-x {\x0a            \
-id: comboBox\x0a\x0a  \
-          editab\
-le: true\x0a       \
-     model: List\
-Model {\x0a        \
-        ListElem\
-ent { text: \x22Ban\
-ana\x22 }\x0a         \
-       ListEleme\
-nt { text: \x22Appl\
-e\x22 }\x0a           \
-     ListElement\
- { text: \x22Coconu\
-t\x22 }\x0a           \
- }\x0a            o\
-nAccepted: {\x0a   \
-             if \
-(find(editText) \
-=== -1)\x0a        \
-            comb\
-oBox.model.appen\
-d({text: comboBo\
-x.editText})\x0a   \
-         }\x0a     \
-       anchors.h\
-orizontalCenter:\
- parent.horizont\
-alCenter\x0a       \
- }\x0a    }\x0a}\x0a\
-\x00\x00\x04\xce\
+  text: qsTr(\x22Co\
+mboBox is a comb\
+ined button and \
+popup list. It p\
+resents \x22\x0a      \
+          + \x22a l\
+ist of options t\
+o the user that \
+occupies minimal\
+ screen space.\x22)\
+\x0a        }\x0a\x0a    \
+    ComboBox {\x0a \
+           enabl\
+ed: !GalleryConf\
+ig.disabled\x0a    \
+        model: [\
+qsTr(\x22First\x22), q\
+sTr(\x22Second\x22), q\
+sTr(\x22Third\x22)]\x0a  \
+          anchor\
+s.horizontalCent\
+er: parent.horiz\
+ontalCenter\x0a    \
+    }\x0a\x0a        L\
+abel {\x0a         \
+   width: parent\
+.width\x0a         \
+   wrapMode: Lab\
+el.Wrap\x0a        \
+    horizontalAl\
+ignment: Qt.Alig\
+nHCenter\x0a       \
+     text: qsTr(\
+\x22ComboBox can be\
+ made editable. \
+An editable comb\
+o box auto-\x22\x0a   \
+             + \x22\
+completes its te\
+xt based on what\
+ is available in\
+ the model.\x22)\x0a  \
+      }\x0a\x0a       \
+ ComboBox {\x0a    \
+        id: comb\
+oBox\x0a\x0a          \
+  enabled: !Gall\
+eryConfig.disabl\
+ed\x0a            e\
+ditable: true\x0a  \
+          model:\
+ ListModel {\x0a   \
+             Lis\
+tElement { text:\
+ qsTr(\x22Banana\x22) \
+}\x0a              \
+  ListElement { \
+text: qsTr(\x22Appl\
+e\x22) }\x0a          \
+      ListElemen\
+t { text: qsTr(\x22\
+Coconut\x22) }\x0a    \
+        }\x0a      \
+      onAccepted\
+: {\x0a            \
+    if (find(edi\
+tText) === -1)\x0a \
+                \
+   comboBox.mode\
+l.append({text: \
+comboBox.editTex\
+t})\x0a            \
+}\x0a            an\
+chors.horizontal\
+Center: parent.h\
+orizontalCenter\x0a\
+        }\x0a    }\x0a\
+}\x0a\
+\x00\x00\x04\xea\
 \x00\
-\x00\x1c\xd2x\xda\xedY\xddo\xdb6\x10\x7f\xd7_A\
-\xa8/\x096+\xb1\x13`\x80\xfa0\xb4N\xb1\x04p\
-\xd6\xc4\x1e\xd6\x02\xc3\x1e\x18\xe9l\x13\xa1H\x85\xa2\xe2\
-\xb8C\xfe\xf7\x91\x92,\x93\xd6\x87\xe5D)V`~\
-\x08B\xde\x07y\xbf;\xde\x1d\xa9\x93\x134\xe6\xf1Z\
-\x90\xc5R\xa2\xa3\xf11\x1a\x9d\x0e\x7fA\x7f,\x01\xdd\
-JE\x89b\xcc\xd6h\x22C\xcf99A\xb3\x9b\x8b\
-\xaf\x83\x09\x09\x80%0\xb8\x0a\x81I2' |T\
-\xccMa>\xb8\x95\x03%\x16\x81\x08\x08\xa6\xe8\xf3\x14\
-}\x9c]\x0c\xce\x06c\x8a\xd3\x04\x1c\x87D1\x17R\
-)\xbfMIp\xbf3\xf4&x\xcdS\x99\xecN\x8f\
-9\x93\x82\xd3\xc4qn0\x03\xf4\x8f\x83\xd4o\xcci\
-\x1a\xb1\x5c\xa2\x98\xd2\xbf$\xc6\x01a\x0b\x1f\x9d\x9f\x96\
-s\x98\x05K.\x12oN(\xf5Q\x8c\x85\xday\x85\
-(y|\x8d\xc5\x820_a\xe0\x94\xe4\x09\xbe\x03j\
-\xe8\xcf\xe7\xf4\xa2\x99\xba/$\x94K\x1fI\x91\x82\xc5\
-\xb2\x128\xbe\xe6!\xf8\xb9\x02\xef\x8b\x1a[\x0cjM\
-\xf2M\xd9\x85\xe9\x07J\x16,R[\xf2\x95\xc1^6\
-\xba\x1c\xab!\x08K@\xc2\x93\xe2p/\x80\xc2\x02K\
-@A\x01\x0aR\xe6 \x85m\x88p\x82\xc2\x82\x9a \
-\xc2\xd0#\x81U\x82\x924Xj\xd2\x84$\xf2O5\
-\xe3\xb9\xa5\xdag\xc3\xcc\x82\xbac)\x09}D\x0b\x92\
-E\x08(\x89k\xccN \x90\x843/\x16<\x06!\
-\xd7j\xc3r\x1d\x83[\xcb\xb4\xd9\xac\x8f\x0c\xb7\xee\xae\
-^0k\x8e\x0a]\xc0CJ\x84\xb2|\xb3\x1cJ\xa4\
-P\xce\xdf\x08U\x04V\xb9\xb7J(4B^6Y\
-a]\x82>\x12\xe5\xfa\xb9\x17U\x5cR\x12\x10y\x99\
-\x11\xd1O:Rv\x05\xeb\x02\xa6\xc6\xa0\x8c\xad\x96)\
-w\xb4a\xb7\xd7d\x8e\x19\xbeA\x161W\xac\x12\xdf\
-[g\xdb\xa3C#\xda`\xb9,\xb0\xc9x\x1c\xdb#\
-8\xe4\x8c\xae\xb7\x1ey\xc4\xa2\x8cJ\x9dO8S\x9b\
-\xbb\xc6*x\xaa\x10\xb9W\x12\xa2M\x80\xbb>\x22\xc6\
-\xb0\x94\xfd\xb9*6[\x91\x18\x0c\xb9\xc4\x1c\xb7\x09\x8e\
-\x97\x10\xdc\x1b\x82\x819n\x13\x9c\xe2\x90pCP\x98\
-\xe3=[\x95\xc1\xd2\xde\xab1Q\x8a\xb69\xabdj\
-82\xb5\xb09\x15V\x13\xec\x86xU9\xff!\xa2\
-\x94\xa8\xa5B\x92\xe0;\xaa2\x0d{H1\xd5y?\
-l\x89\xdeGLS\xd8\xa7\x12X'\x8d\xc5\xa1\xcd\xc3\
-\xba\xe1\xb4>\xbf\x06\xaf\xfap\xa9\x02f\x85Y\xdb\x09\
-7\xf9~,\x5ck\x19\xdb\xf03\xed\x16\x10\xf1\xc76\
-\x007\xbf\xa9\xcag\x98-(\xb4(\xcc\x8a\x0c\xa7\x5c\
-u\x17\x16\xee\xaa\xb4@\xa2\xab\xdd\xaf\xc8}wvv\
-\xe6\x22Uc\xde\x9d\x9f\x9f\xbb\xad\xaa\xba\x04Q]\xfa\
-/\x04\xf2a\xfbf\xb7\x15\xb1\x95\xcf\xb6\x86\xb3\xb1\xaa\
-(\xf7\x10\xfa{\xb0xa\xd8\x98\xbf\xac\xdaE\xaa)\
-\xa1^\xee\xaa#\x9e\xaa\x8a\x11\xc2\xd3\xf1!Kw\x0c\
-\xaf\x86\xc3xH\xb9\xec\xd1\xf6\xb9\xea\x96\xbc\x98<\x01\
-\x9d\x91o\xb0sF=\x9b\xfa\x86`\x18\x8d\xdc4s\
-\x81\xbb\x97\xbd8\x04\xeej\xa9\xf2\xfa~\xf6\xce\xfd@\
-s\xfa\xdcOi\xf0\xe9L\xf5c\xfaR\xa0zZF\
-\x22\xac{\x96N)\xa3\xe4n\x0e\x95\x9b\xa2\x9f\xf8\x10\
-\xec\xd1\xfa\xcaX\x91\xea\x0a\x00\xca=\x9b\x96E\x17I\
-\xe7mb\xc1\xe8\x91\xcb\x86T-\x8b\xd7]\x22#+\
-\x045]Z7\xaf\xfe\x9eFw \xba8\xe9\x0d\xe0\
-\xf4\xc8w\xc14O\xd9\xed(J\xee\xa3\xd3V\x0e\xc0\
-\x89\xbaPx\xfa\x12\xe3\xa3O\xf9\xe0\x8a}N\xd5\xcd\
-\x14\x87/@\xfe\xff8\xae\x8d\xe39\xa6\x09\xf4\x93\x84\
-\xb2\xcc\xeeQ\x98\xcbjG\xd2\xcc/\xf2r\xdfE\xa0\
-4\x92\xb3\xdc\xc2J\x1a\xf3\x12\x05\xbf<:\xee\xb7M\
-\xad\xbf\x9cTA\xb0.5\xff\xd5\xc6\xbe\x15\x89\x8f\xa9\
-\x94\x9c\xfd&x\x1a7`\x91\xdd\xb7\x0c\xb6\xd7\xe0Z\
-\x7fw\xab\xe2j\xdd\xf9\xbe\x1f\xae\x86\x95\xdeB\xff\xdd\
-c\xfd\x1b\xf8\xa3\xc3\x05\xaa\xf6\x0e[w\x832\x18\x7f\
-\xc8\xd8\xcc\xfa\xe7\xfc\x09I\xbf\xef\xd5\xb5\xae\x9a\xf6\x89\
-B\x94\x01\x86\xf2\xd2a?m\xbc\xdf\xe4=kz\xe8\
-\xd6d\xba\x97*\x1b\xf5\xa9\xec\xac\xb32\xfb)f\xab\
-\xcd\x9a\x1f\xf6\xabn\xd4\xaf\xba\xee\xc6\xda\xcfG[u\
-\xd6\xfc\xb0_u\xa3~\xd5u7\xd6~\xf2\xda\xaa\xb3\
-\xe6\x87\xfd\xaa\x1b\xf5\xab\xee\xa006\x9f\xe9\xacH1\
-\x08\xc3\xbe\x15\x8e\xfaVX1y'\x9dm\xdf\xe0'\
-\x1c\x87 \x1a2\xfc\x86-gz\xcd\xbb\xfa\x81\xd9=\
-\xe1\xa9\x08\xb6%e\xfb5\xc2\xab{T\xfeK\x83\xf3\
-\xb7sX\xfa\xef\xfeY\xa1\xbe\xc04\xb2\xeb\xcdt\xe0\
-\xd6/\xe4YU\xe9\xc0\xabM \xfa\xc5\xa6\xba\xeb\x92\
-\xa7p\xa4\xd9\xf9\xefu\xe0\x81^\xd9\xae\xb5\xf9l\xa4\
-=\xde\xf0\xa9\xe8\xf0\x02l\x99\xbby\xa3\xf2\x0b\xc3\xeb\
-+u\xfe\xdf\xb3\xf3\xec\xfc\x0b\x01\xe6r&\
-\x00\x00\x02\xc8\
+\x00\x1a\xf5x\x9c\xd5XmO\xdc8\x10\xfe\x9e_\xe1\
+\xa6_@\xd7\x0dmA\xaa\x94Su\x82\xa5*HK\
+\xcbKu=\xe9t\x1fL2\xbbk\xe1\xb5S\xc7\x01\
+\xb6\x15\xff\xfd\xec\xbc\xe1$N\xe2\xb4\xe8\xc4\xcd\x87\xd5\
+\xda3\xf3$\xf3x\xc6\x1ego\x0f\xcdy\xb2\x15d\
+\xb5\x96hg\xbe\x8b\xde\xbe~\xf3\x0e}Y\x03\xba\x90\
+J\xb3I0\xdb\xa2\x85\x8c\x03oo\x0f]\x9d\x1f\xff\
+5[\x90\x08X\x0a\xb3\xd3\x18\x98$K\x02\x22D\xe5\
+\xdc%,g\x17r\xa6\xdc6 \x22\x82)\xfa|\x89\
+\x8e\xae\x8eg\xfb\xb39\xc5Y\x0a\x9e\x97\x08\xbc\xda\xe0\
+\x1c\x993\x05p\x04k|K\xb8\xc28\xe2\x19\x8b=\
+\x8f(\x85\x90\xea\xe9\x17\x19\x89nZ\xc3`\x81\xb7<\
+\x93i{z\xce\x99\x14\x9c\xa6\x9ew\x8e\x19\xa0\x1f\x1e\
+R2\xe74\xdb\xb0\xc2\xa3\x9c\xd2\x92&8\x22l\x15\
+\xa2\x83\xd7\xf5\x1cf\xd1\x9a\x8b4X\x12JC\x94`\
+\xa1\xde\xac\xa3\x94<9\xc3bEX\xa8H\xf2j\xf5\
+\x02_\x035\xf0\x8b9\xfd\xd0\x1c\xee+\x89\xe5:D\
+Rd\xd00\xb9\x1389\xe31\x84\x05@\xf0U\x8d\
+\x1b\x06\xea\x99\xe4\xbb\x8a\x0b\xd3CJVl\xa3^)\
+T\x01\x07\xf9\xe8d\xae\x86 \x1a\x0e\x12\xee\x95\xc5\xb7\
+\xf4\x8b\xd8\xf1\x8f\x81\xc2\x0aK@Q\xc9\x0cR1!\
+\xb5\x021\xc2)\x8aKm\x8a\x08C\xb7\x04\xeeR\x94\
+f\xd1Z\xab\x16$\x95\x7f\xaa\x99\xc0\xdf\xad\xc1\x1f\x1e\
+\x83=\xca\xa4\xe4\xec\xa3\xe0Y\xd2\x0a\x99\xc4!\x128\
+&\xdc0\xb1!T\x0f\xb0\xb8\xd3R\xd5PD\x94$\
+\x16\xfaR\x88$\xe1,H\x04O@\xc8m\x88|\xb9\
+M\xc0\xb7\x1aU\xf1\x86\xc8H\x8f\xf6\xd3Kcm\xd1\
+\xd1\x0b\xf8\x96\x11\xa1\xc8\xab\x1e\x87R)T\x12UN\
+\x1d\x87\xbbb\xd5k65\xc9A>\xd91]\x83\xae\
+\xbd\xfa\xf9E6\xa8\xfc\xa6$\x22\xf2$W\xa2\xdft\
+\xc6\xb5\x1dm\x89g\x09(7\xb3\x1a\x15\x09c\xc4\x1d\
+\xf4\x85\xa3\xa5*\x83(\xcf\xbcS\xd6\xa9\x93J\x1e\xbc\
+\xe6hje\x18&'%7\xb9M\xc3h\xa3J\x87\
+\x16\x04\xeb*\xb2\xf1\xa0u\x1f(\xe8\xc2A?\x90\xce\
+\x0e\x95$\xa7\x126Uq\xf8\xbf\xa3[L3\xa8\x8a\
+\xc6\xd4\xbd\xf1w[\x81\xfc2\xe4\xdb\xa7\x87\xdcw\x87\
+\xbc\xba#\x09\xf4a6\x94\x13Bw\x07\x9d\x10\xbc;\
+\xe8\x84\xf0\xe7k\x88n\xfa@\x1b\xca\x09\xe1\xbb\x83N\
+\x08\xdf\x1dtB\xf8\x97zg\xee\x03m('\x84\xef\
+\x0e:!|w\xd0i\xc9/\xa3\xf5@N\x19\xdai\
+\xe9\xef\x0c;\xad\x00\x9ca\xbb$\xb46\xdc\xc7\xb3o\
+\xc1q\x0c\xa2\xe7\xf4\xab\xcc\x0a\xa3_9\xcfR\x9e\x89\
+\x08\xea\x06\xef\x11\xba\x9e:\xc3\xc9\xdf:\xd4\x7f<\xf7\
+\x836\xa7\xc0\xdd\x5c\xc3;X\xdfbQ\x9c$\x0e\xb6\
+D-\x11a1\xdcw\xdf\xba\xb6\xa9\x9b\x1b\xcd\x8f\xd1\
+\xd0X\xe0q\xcc\x19\xdd6_\xc5\xc6T\xd8s\xc27\
+\xcf\x88\x10\x11cX\xfb\xbf\xb2\xbb6wX\xd5\x02\x98\
+\xe31\xe7\xe6\xfe\x14\xa2\xc8\x1c\x8f97\xab\xbb\xec\x18\
+'\xbc\xb6Y\x17\xf9{\x1b\x13\xb5\xbb\xa5#\xe9L\xd5\
+\xc6\x03\x0d\x94\x95\xd2.\x94\x16s1z\x10\xb5\x00\xc3\
+\xd7\x14\x14\xf4\x8b\x8f\x98R\x10[uqY\x92U\x10\
+\x934W\xf4:\x16\xadZ\xb3H\x03{MTR\x96\
+l\xcb\xc7^\xb3Z\xba\x9b\xd3O\xd2f\xcf&;o\
+\x8dL\x1c \xae\x83\xfb\xec)\xeeu\x1a\xa3\xd0\x0cY\
+\xc0\x86\xdf\x8eqX\xc9\xa5j\xde1[\xd1!\x1e+\
+\x898\xd5w\xee\x06\xfd\xea.\x05\xa9\xbe!\xfe\x81\xfc\
+\x97\xfb\xfb\xfb>Rg\xd1\xcb\x83\x83\x03\x7f\x14\xae$\
+\xa2\xb8\x10\x0c\xe4\x98)\xd5\xbd\xa7t*\x86\xe3/\xfe\
+x\x1d\x1c\xb5mF\xc7\xd9\x5c]\xa9ntj\x8c\xf3\
+\xa3\x85,\xd1N;\x1d\xf4\xde\xfe\xe2\xfd{\x94\xa9\x83\
+`I\x18\xc4\xbbNPZ,PA~\xf6\x04\xc5*\
+\xb7\x9f\x95\x9f5\xe3\xf0\x96\x22m\xcb\xd0E\xb1-K\
+\xae\xd6\x22!\xf7@\xaf\xc8wh\x15]\xd0\xd4:\x01\
+\x9a_%.\xf3@}7\xce\xca\x14\xf5\xef\xd6j\x1f\
+\x1eOA-\xceW\xd4\xb6t\xf7>7\xed\x00\xf9W\
+\xaa\x81\xd0\x1f\xc80=dd\x83\xf5\xb5\xda\xb9\xe0k\
+\x8f\xe1\xb5=/{\x87\xc3\xc8\x01]\x8b\xc4b\x05\x9d\
+Mn\xd4\xcd\xf8\xc6R7\x80\x0a\x02o\xcb\xf5\x1c\x05\
+(\xfb\xd7\xceM\xbf-\xc3\xcb\xf0)\xdb\x5c\x83pe\
+SK\x15\xaf\xdb\xc9Q\x89\x11n\xb1)\x8d\x07(y\
+\x88\xba\x1fh\xda\x028U\xbdiPt\xf9\x1f\x8a\xc1\
+)\xfb\x9c\xc9\x8b\x0c\xf7\x9fKZ\x86\x89\xf9\xbf\xe5\xc1\
+\x12\xd3\xf4g\x13a\xa0\xe2\xf2U\x0e(,e\xf7\xe0\
+\x1c\xf6\x11\xc5I\xe4\xeaT\x07\xcfY\x11y\xa7n\x83\
+TQ,w\xec\x1b\xdd\x935Z\xf6\xce\xdbNP\xa3\
+k\x7f\x06\x1d\xea\x93\x91`\xbfA\xd8Ih\xdc>\xfe\
+c\x12z}\x8c\xef\xe5\xc1J\xff\x0e|G7\xe5)\
+\xfbu\xeb-\xaa\xafa7\x8c\x9fi\x22\xd9G\xc5\xbf\
+\x07\xef\xc1\xfb\x17\xe7\x1f\xdf\x92\
+\x00\x00\x02\xfb\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1065,28 +1784,31 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22The Dia\
-l is similar to \
-a traditional di\
-al knob that is \
-found on devices\
- such as \x22\x0a     \
-           + \x22st\
-ereos or industr\
-ial equipment. I\
-t allows the use\
-r to specify a v\
-alue within a ra\
-nge.\x22\x0a        }\x0a\
-\x0a        Dial {\x0a\
-            valu\
-e: 0.5\x0a         \
-   anchors.horiz\
-ontalCenter: par\
-ent.horizontalCe\
-nter\x0a        }\x0a \
-   }\x0a}\x0a\
-\x00\x00\x04\xe8\
+  text: qsTr(\x22Th\
+e Dial is simila\
+r to a tradition\
+al dial knob tha\
+t is found on de\
+vices such as \x22\x0a\
+                \
++ \x22stereos or in\
+dustrial equipme\
+nt. It allows th\
+e user to specif\
+y a value within\
+ a range.\x22)\x0a    \
+    }\x0a\x0a        D\
+ial {\x0a          \
+  enabled: !Gall\
+eryConfig.disabl\
+ed\x0a            v\
+alue: 0.5\x0a      \
+      anchors.ho\
+rizontalCenter: \
+parent.horizonta\
+lCenter\x0a        \
+}\x0a    }\x0a}\x0a\
+\x00\x00\x05-\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1120,54 +1842,137 @@ Label.Wrap\x0a     \
        horizonta\
 lAlignment: Qt.A\
 lignHCenter\x0a    \
-        text: \x22F\
-rame is used to \
-layout a logical\
- group of contro\
-ls together, wit\
-hin a visual fra\
-me.\x22\x0a        }\x0a\x0a\
-        Frame {\x0a\
-            anch\
-ors.horizontalCe\
-nter: parent.hor\
-izontalCenter\x0a\x0a \
-           Colum\
-n {\x0a            \
-    spacing: 20\x0a\
-                \
-width: page.item\
-Width\x0a\x0a         \
-       RadioButt\
-on {\x0a           \
-         text: \x22\
-First\x22\x0a         \
-           check\
-ed: true\x0a       \
-             wid\
-th: parent.width\
-\x0a               \
- }\x0a             \
-   RadioButton {\
-\x0a               \
-     id: button\x0a\
+        text: qs\
+Tr(\x22Frame is use\
+d to layout a lo\
+gical group of c\
+ontrols together\
+, within a visua\
+l frame.\x22)\x0a     \
+   }\x0a\x0a        Fr\
+ame {\x0a          \
+  enabled: !Gall\
+eryConfig.disabl\
+ed\x0a            a\
+nchors.horizonta\
+lCenter: parent.\
+horizontalCenter\
+\x0a\x0a            Co\
+lumn {\x0a         \
+       spacing: \
+20\x0a             \
+   width: page.i\
+temWidth\x0a\x0a      \
+          RadioB\
+utton {\x0a        \
+            text\
+: qsTr(\x22First\x22)\x0a\
                 \
-    text: \x22Secon\
-d\x22\x0a             \
-       width: pa\
-rent.width\x0a     \
-           }\x0a   \
-             Rad\
-ioButton {\x0a     \
-               t\
-ext: \x22Third\x22\x0a   \
+    checked: tru\
+e\x0a              \
+      width: par\
+ent.width\x0a      \
+          }\x0a    \
+            Radi\
+oButton {\x0a      \
+              id\
+: button\x0a       \
+             tex\
+t: qsTr(\x22Second\x22\
+)\x0a              \
+      width: par\
+ent.width\x0a      \
+          }\x0a    \
+            Radi\
+oButton {\x0a      \
+              te\
+xt: qsTr(\x22Third\x22\
+)\x0a              \
+      width: par\
+ent.width\x0a      \
+          }\x0a    \
+        }\x0a      \
+  }\x0a    }\x0a}\x0a\
+\x00\x00\x04\xc1\
+/\
+/ Copyright (C) \
+2025 The Qt Comp\
+any Ltd.\x0a// SPDX\
+-License-Identif\
+ier: LicenseRef-\
+Qt-Commercial OR\
+ BSD-3-Clause\x0a\x0ai\
+mport QtQuick\x0aim\
+port QtQuick.Con\
+trols\x0a\x0aPage {\x0a  \
+  id: page\x0a    e\
+nabled: !Gallery\
+Config.disabled\x0a\
+\x0a    header: Men\
+uBar {\x0a        M\
+enu {\x0a          \
+  title: qsTr(\x22&\
+File\x22)\x0a         \
+   Action { text\
+: qsTr(\x22&New...\x22\
+) }\x0a            \
+Action { text: q\
+sTr(\x22&Open...\x22) \
+}\x0a            Ac\
+tion { text: qsT\
+r(\x22&Save\x22) }\x0a   \
+         Action \
+{ text: qsTr(\x22Sa\
+ve &As...\x22) }\x0a  \
+          MenuSe\
+parator { }\x0a    \
+        Action {\
+ text: qsTr(\x22&Qu\
+it\x22) }\x0a        }\
+\x0a        Menu {\x0a\
+            titl\
+e: qsTr(\x22&Edit\x22)\
+\x0a            Act\
+ion { text: qsTr\
+(\x22Cu&t\x22) }\x0a     \
+       Action { \
+text: qsTr(\x22&Cop\
+y\x22) }\x0a          \
+  Action { text:\
+ qsTr(\x22&Paste\x22) \
+}\x0a        }\x0a    \
+    Menu {\x0a     \
+       title: qs\
+Tr(\x22&Help\x22)\x0a    \
+        Action {\
+ text: qsTr(\x22&Ab\
+out\x22) }\x0a        \
+}\x0a    }\x0a\x0a    Lab\
+el {\x0a        anc\
+hors.verticalCen\
+ter: parent.vert\
+icalCenter\x0a     \
+   width: parent\
+.width\x0a        w\
+rapMode: Label.W\
+rap\x0a        hori\
+zontalAlignment:\
+ Qt.AlignHCenter\
+\x0a        text: q\
+sTr(\x22MenuBar pro\
+vides a horizont\
+al bar with drop\
+-down menus, \x22\x0a \
                 \
- width: parent.w\
-idth\x0a           \
-     }\x0a         \
-   }\x0a        }\x0a \
-   }\x0a}\x0a\
-\x00\x00\x05B\
++ \x22allowing user\
+s to access grou\
+ped commands and\
+ actions \x22\x0a     \
+            + \x22w\
+ithin an applica\
+tion.\x22)\x0a    }\x0a}\x0a\
+\
+\x00\x00\x05q\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1185,76 +1990,79 @@ SwipeView {\x0a    \
      currentInde\
 x: 1\x0a        anc\
 hors.fill: paren\
-t\x0a\x0a        Repea\
-ter {\x0a          \
-  model: 3\x0a\x0a    \
-        Pane {\x0a \
-               w\
-idth: SwipeView.\
-view.width\x0a     \
-           heigh\
-t: SwipeView.vie\
-w.height\x0a\x0a      \
-          Column\
- {\x0a             \
-       spacing: \
-40\x0a             \
-       width: pa\
-rent.width\x0a\x0a    \
+t\x0a        enable\
+d: !GalleryConfi\
+g.disabled\x0a\x0a    \
+    Repeater {\x0a \
+           model\
+: 3\x0a\x0a           \
+ Pane {\x0a        \
+        width: S\
+wipeView.view.wi\
+dth\x0a            \
+    height: Swip\
+eView.view.heigh\
+t\x0a\x0a             \
+   Column {\x0a    \
                 \
-Label {\x0a        \
+spacing: 40\x0a    \
                 \
 width: parent.wi\
-dth\x0a            \
-            wrap\
-Mode: Label.Wrap\
+dth\x0a\x0a           \
+         Label {\
 \x0a               \
-         horizon\
-talAlignment: Qt\
-.AlignHCenter\x0a  \
+         width: \
+parent.width\x0a   \
                 \
-      text: \x22Swi\
-peView provides \
-a navigation mod\
-el that simplifi\
-es horizontal pa\
-ged scrolling. \x22\
-\x0a               \
-         + \x22The \
-page indicator o\
-n the bottom sho\
-ws which is the \
-presently active\
- page.\x22\x0a        \
-            }\x0a\x0a \
+     wrapMode: L\
+abel.Wrap\x0a      \
                 \
-   Image {\x0a     \
+  horizontalAlig\
+nment: Qt.AlignH\
+Center\x0a         \
+               t\
+ext: qsTr(\x22Swipe\
+View provides a \
+navigation model\
+ that simplifies\
+ horizontal page\
+d scrolling. \x22\x0a \
                 \
-   source: \x22../i\
-mages/arrows.png\
-\x22\x0a              \
         anchor\
-s.horizontalCent\
-er: parent.horiz\
-ontalCenter\x0a    \
+       + \x22The pa\
+ge indicator on \
+the bottom shows\
which is the pr\
+esently active p\
+age.\x22)\x0a         \
+           }\x0a\x0a  \
                 \
-}\x0a              \
-  }\x0a            \
-}\x0a        }\x0a    \
-}\x0a\x0a    PageIndic\
-ator {\x0a        c\
-ount: view.count\
-\x0a        current\
-Index: view.curr\
-entIndex\x0a       \
- anchors.bottom:\
- parent.bottom\x0a \
-       anchors.h\
-orizontalCenter:\
- parent.horizont\
-alCenter\x0a    }\x0a}\
-\x0a\
-\x00\x00\x04;\
+  Image {\x0a      \
+                \
+  source: \x22../im\
+ages/arrows.png\x22\
+\x0a               \
+         anchors\
+.horizontalCente\
+r: parent.horizo\
+ntalCenter\x0a     \
+               }\
+\x0a               \
+ }\x0a            }\
+\x0a        }\x0a    }\
+\x0a\x0a    PageIndica\
+tor {\x0a        co\
+unt: view.count\x0a\
+        currentI\
+ndex: view.curre\
+ntIndex\x0a        \
+anchors.bottom: \
+parent.bottom\x0a  \
+      anchors.ho\
+rizontalCenter: \
+parent.horizonta\
+lCenter\x0a    }\x0a}\x0a\
+\
+\x00\x00\x05S\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1267,32 +2075,44 @@ mport QtQuick\x0aim\
 port QtQuick.Con\
 trols\x0a\x0aFlickable\
  {\x0a    id: flick\
-able\x0a\x0a    conten\
-tHeight: pane.he\
-ight\x0a\x0a    Pane {\
-\x0a        id: pan\
-e\x0a        width:\
- flickable.width\
-\x0a        height:\
- flickable.heigh\
-t * 1.25\x0a\x0a      \
-  Column {\x0a     \
-       id: colum\
-n\x0a            sp\
-acing: 40\x0a      \
-      width: par\
-ent.width\x0a\x0a     \
-       Label {\x0a \
-               w\
-idth: parent.wid\
-th\x0a             \
-   wrapMode: Lab\
-el.Wrap\x0a        \
-        horizont\
-alAlignment: Qt.\
-AlignHCenter\x0a   \
-             tex\
-t: \x22ScrollBar is\
+able\x0a    enabled\
+: !GalleryConfig\
+.disabled\x0a    co\
+ntentHeight: pan\
+e.height\x0a\x0a    Pa\
+ne {\x0a        id:\
+ pane\x0a        wi\
+dth: flickable.w\
+idth\x0a        hei\
+ght: flickable.h\
+eight * 1.25\x0a\x0a  \
+      Column {\x0a \
+           id: c\
+olumn\x0a          \
+  spacing: 40\x0a  \
+          width:\
+ parent.width\x0a\x0a \
+           Check\
+Box {\x0a          \
+      id: always\
+OnCheckBox\x0a     \
+           width\
+: parent.width\x0a \
+               t\
+ext: qsTr(\x22Alway\
+s on\x22)\x0a         \
+   }\x0a\x0a          \
+  Label {\x0a      \
+          width:\
+ parent.width\x0a  \
+              wr\
+apMode: Label.Wr\
+ap\x0a             \
+   horizontalAli\
+gnment: Qt.Align\
+HCenter\x0a        \
+        text: qs\
+Tr(\x22ScrollBar is\
  an interactive \
 bar that can be \
 used to scroll t\
@@ -1308,23 +2128,29 @@ ickable, \x22\x0a     \
                +\
  \x22such as ListVi\
 ew and GridView.\
-\x22\x0a            }\x0a\
-\x0a            Ima\
-ge {\x0a           \
-     rotation: 9\
-0\x0a              \
-  source: \x22../im\
-ages/arrows.png\x22\
-\x0a               \
- anchors.horizon\
-talCenter: paren\
-t.horizontalCent\
-er\x0a            }\
-\x0a        }\x0a    }\
-\x0a\x0a    ScrollBar.\
-vertical: Scroll\
-Bar { }\x0a}\x0a\
-\x00\x00\x03v\
+\x22)\x0a            }\
+\x0a\x0a            Im\
+age {\x0a          \
+      rotation: \
+90\x0a             \
+   source: \x22../i\
+mages/arrows.png\
+\x22\x0a              \
+  anchors.horizo\
+ntalCenter: pare\
+nt.horizontalCen\
+ter\x0a            \
+}\x0a        }\x0a    \
+}\x0a\x0a    ScrollBar\
+.vertical: Scrol\
+lBar {\x0a        p\
+olicy: alwaysOnC\
+heckBox.checked \
+? ScrollBar.Alwa\
+ysOn : ScrollBar\
+.AsNeeded\x0a    }\x0a\
+}\x0a\
+\x00\x00\x03\xbf\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1350,39 +2176,43 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22RangeSl\
-ider is used to \
-select a range s\
-pecified by two \
-values, by slidi\
-ng each handle a\
-long a track.\x22\x0a \
-       }\x0a\x0a      \
-  RangeSlider {\x0a\
-            id: \
-slider\x0a         \
-   first.value: \
-0.25\x0a           \
- second.value: 0\
-.75\x0a            \
-anchors.horizont\
-alCenter: parent\
-.horizontalCente\
-r\x0a        }\x0a\x0a   \
-     RangeSlider\
- {\x0a            o\
-rientation: Qt.V\
-ertical\x0a        \
-    first.value:\
- 0.25\x0a          \
-  second.value: \
-0.75\x0a           \
- anchors.horizon\
-talCenter: paren\
-t.horizontalCent\
-er\x0a        }\x0a   \
- }\x0a}\x0a\
-\x00\x00\x05\x17\
+  text: qsTr(\x22Ra\
+ngeSlider is use\
+d to select a ra\
+nge specified by\
+ two values, by \
+sliding each han\
+dle along a trac\
+k.\x22)\x0a        }\x0a\x0a\
+        RangeSli\
+der {\x0a          \
+  enabled: !Gall\
+eryConfig.disabl\
+ed\x0a            f\
+irst.value: 0.25\
+\x0a            sec\
+ond.value: 0.75\x0a\
+            anch\
+ors.horizontalCe\
+nter: parent.hor\
+izontalCenter\x0a  \
+      }\x0a\x0a       \
+ RangeSlider {\x0a \
+           enabl\
+ed: !GalleryConf\
+ig.disabled\x0a    \
+        orientat\
+ion: Qt.Vertical\
+\x0a            fir\
+st.value: 0.25\x0a \
+           secon\
+d.value: 0.75\x0a  \
+          anchor\
+s.horizontalCent\
+er: parent.horiz\
+ontalCenter\x0a    \
+    }\x0a    }\x0a}\x0a\
+\x00\x00\x05b\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1416,57 +2246,62 @@ Label.Wrap\x0a     \
        horizonta\
 lAlignment: Qt.A\
 lignHCenter\x0a    \
-        text: \x22A\
- GroupBox provid\
-es a frame, a ti\
-tle on top of it\
-, and a logical \
-group of control\
-s within that fr\
-ame.\x22\x0a        }\x0a\
-\x0a        GroupBo\
-x {\x0a            \
-title: \x22Title\x22\x0a \
-           ancho\
-rs.horizontalCen\
-ter: parent.hori\
-zontalCenter\x0a\x0a  \
-          Column\
- {\x0a             \
-   spacing: 20\x0a \
-               w\
-idth: page.itemW\
-idth\x0a\x0a          \
-      RadioButto\
-n {\x0a            \
-        text: \x22F\
-irst\x22\x0a          \
-          checke\
-d: true\x0a        \
-            widt\
-h: parent.width\x0a\
+        text: qs\
+Tr(\x22A GroupBox p\
+rovides a frame,\
+ a title on top \
+of it, and a log\
+ical group of co\
+ntrols within th\
+at frame.\x22)\x0a    \
+    }\x0a\x0a        G\
+roupBox {\x0a      \
+      enabled: !\
+GalleryConfig.di\
+sabled\x0a         \
+   title: qsTr(\x22\
+Title\x22)\x0a        \
+    anchors.hori\
+zontalCenter: pa\
+rent.horizontalC\
+enter\x0a\x0a         \
+   Column {\x0a    \
+            spac\
+ing: 20\x0a        \
+        width: p\
+age.itemWidth\x0a\x0a \
+               R\
+adioButton {\x0a   \
                 \
-}\x0a              \
-  RadioButton {\x0a\
+ text: qsTr(\x22Fir\
+st\x22)\x0a           \
+         checked\
+: true\x0a         \
+           width\
+: parent.width\x0a \
+               }\
+\x0a               \
+ RadioButton {\x0a \
                 \
-    id: button\x0a \
+   id: button\x0a  \
                 \
-   text: \x22Second\
-\x22\x0a              \
-      width: par\
-ent.width\x0a      \
-          }\x0a    \
-            Radi\
-oButton {\x0a      \
-              te\
-xt: \x22Third\x22\x0a    \
+  text: qsTr(\x22Se\
+cond\x22)\x0a         \
+           width\
+: parent.width\x0a \
+               }\
+\x0a               \
+ RadioButton {\x0a \
                 \
-width: parent.wi\
-dth\x0a            \
-    }\x0a          \
-  }\x0a        }\x0a  \
-  }\x0a}\x0a\
-\x00\x00\x03\xc7\
+   text: qsTr(\x22T\
+hird\x22)\x0a         \
+           width\
+: parent.width\x0a \
+               }\
+\x0a            }\x0a \
+       }\x0a    }\x0a}\
+\x0a\
+\x00\x00\x04A\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1492,44 +2327,52 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22RadioBu\
-tton presents an\
- option button t\
-hat can be toggl\
-ed on or off. \x22\x0a\
-                \
-+ \x22Radio buttons\
- are typically u\
-sed to select on\
-e option from a \
-set of options.\x22\
-\x0a        }\x0a\x0a    \
-    Column {\x0a   \
-         spacing\
-: 20\x0a           \
- anchors.horizon\
-talCenter: paren\
-t.horizontalCent\
-er\x0a\x0a            \
-RadioButton {\x0a  \
-              te\
-xt: \x22First\x22\x0a    \
-        }\x0a      \
+  text: qsTr(\x22Ra\
+dioButton presen\
+ts an option but\
+ton that can be \
+toggled on or of\
+f. \x22\x0a           \
+     + \x22Radio bu\
+ttons are typica\
+lly used to sele\
+ct one option fr\
+om a set of opti\
+ons.\x22)\x0a        }\
+\x0a\x0a        Column\
+ {\x0a            s\
+pacing: 20\x0a     \
+       anchors.h\
+orizontalCenter:\
+ parent.horizont\
+alCenter\x0a\x0a      \
       RadioButto\
 n {\x0a            \
-    text: \x22Secon\
-d\x22\x0a             \
-   checked: true\
-\x0a            }\x0a \
-           Radio\
-Button {\x0a       \
-         text: \x22\
-Third\x22\x0a         \
-       enabled: \
-false\x0a          \
-  }\x0a        }\x0a  \
-  }\x0a}\x0a\
-\x00\x00\x04U\
+    text: qsTr(\x22\
+First\x22)\x0a        \
+        enabled:\
+ !GalleryConfig.\
+disabled\x0a       \
+     }\x0a         \
+   RadioButton {\
+\x0a               \
+ text: qsTr(\x22Sec\
+ond\x22)\x0a          \
+      checked: t\
+rue\x0a            \
+    enabled: !Ga\
+lleryConfig.disa\
+bled\x0a           \
+ }\x0a            R\
+adioButton {\x0a   \
+             tex\
+t: qsTr(\x22Third\x22)\
+\x0a               \
+ enabled: false\x0a\
+            }\x0a  \
+      }\x0a    }\x0a}\x0a\
+\
+\x00\x00\x04\x7f\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1542,158 +2385,163 @@ mport QtQuick\x0aim\
 port QtQuick.Con\
 trols\x0a\x0aFlickable\
  {\x0a    id: flick\
-able\x0a\x0a    conten\
-tHeight: pane.he\
-ight\x0a\x0a    Pane {\
-\x0a        id: pan\
-e\x0a        width:\
- flickable.width\
-\x0a        height:\
- flickable.heigh\
-t * 1.25\x0a\x0a      \
-  Column {\x0a     \
-       id: colum\
-n\x0a            sp\
-acing: 40\x0a      \
-      width: par\
-ent.width\x0a\x0a     \
-       Label {\x0a \
-               w\
-idth: parent.wid\
-th\x0a             \
-   wrapMode: Lab\
-el.Wrap\x0a        \
-        horizont\
-alAlignment: Qt.\
-AlignHCenter\x0a   \
-             tex\
-t: \x22ScrollIndica\
-tor is a non-int\
-eractive indicat\
-or that indicate\
-s the current sc\
-roll position. \x22\
-\x0a               \
-     + \x22A scroll\
- indicator can b\
-e either vertica\
-l or horizontal,\
- and can be atta\
-ched to any Flic\
-kable, \x22\x0a       \
-             + \x22\
-such as ListView\
- and GridView.\x22\x0a\
-            }\x0a\x0a \
-           Image\
+able\x0a    enabled\
+: !GalleryConfig\
+.disabled\x0a    co\
+ntentHeight: pan\
+e.height\x0a\x0a    Pa\
+ne {\x0a        id:\
+ pane\x0a        wi\
+dth: flickable.w\
+idth\x0a        hei\
+ght: flickable.h\
+eight * 1.25\x0a\x0a  \
+      Column {\x0a \
+           id: c\
+olumn\x0a          \
+  spacing: 40\x0a  \
+          width:\
+ parent.width\x0a\x0a \
+           Label\
  {\x0a             \
-   rotation: 90\x0a\
-                \
-source: \x22../imag\
-es/arrows.png\x22\x0a \
-               a\
-nchors.horizonta\
-lCenter: parent.\
-horizontalCenter\
-\x0a            }\x0a \
-       }\x0a    }\x0a\x0a\
-    ScrollIndica\
-tor.vertical: Sc\
-rollIndicator { \
-}\x0a}\x0a\
-\x00\x00\x05\x99\
+   width: parent\
+.width\x0a         \
+       wrapMode:\
+ Label.Wrap\x0a    \
+            hori\
+zontalAlignment:\
+ Qt.AlignHCenter\
+\x0a               \
+ text: qsTr(\x22Scr\
+ollIndicator is \
+a non-interactiv\
+e indicator that\
+ indicates the c\
+urrent scroll po\
+sition. \x22\x0a      \
+              + \
+\x22A scroll indica\
+tor can be eithe\
+r vertical or ho\
+rizontal, and ca\
+n be attached to\
+ any Flickable, \
+\x22\x0a              \
+      + \x22such as\
+ ListView and Gr\
+idView.\x22)\x0a      \
+      }\x0a\x0a       \
+     Image {\x0a   \
+             rot\
+ation: 90\x0a      \
+          source\
+: \x22../images/arr\
+ows.png\x22\x0a       \
+         anchors\
+.horizontalCente\
+r: parent.horizo\
+ntalCenter\x0a     \
+       }\x0a       \
+ }\x0a    }\x0a\x0a    Sc\
+rollIndicator.ve\
+rtical: ScrollIn\
+dicator { }\x0a}\x0a\
+\x00\x00\x05\xcd\
 \x00\
-\x00\x17\x96x\xda\xcdXKs\xdb6\x10\xbe\xf3W\xec\
-\xf8d'\x16\x948\x99\xe9\x8c.\x9dD\x8e'\x9e\xb1\
-\xe3W\xd2\xb43\xbd@\xc4RB\x0d\x024\x00\xdaV\
-S\xff\xf7.H=(\xf1!\xc5I\xed\xf2 \x11\xc0\
-.\xf6\xf5\xedb\xc1~\x1f\x86&\x9bZ9\x9ex\xd8\
-\x1d\xee\xc1\xc1\xab\xd7\xbf\xc0\xe7\x09\xc2\x85\xa7\x954\xe3\
-z\x0a'^\xb0\xa8\xdf\x87\xab\xf3\xc3\xdf{'2F\
-\xed\xb0w,P{\x99H\xb4\x03\x98\xcd]b\xd2\xbb\
-\xf0=bK\xd1\xc6\x92+8\xbb\x84\xf7W\x87\xbd7\
-\xbd\xa1\xe2\xb9\xc3(\x92if\xac\xa7\xcd/r\x19_\
-\xaf\x0d\xd9\x09\x9f\x9a\xdc\xbb\xf5\xe9\xa1\xd1\xde\x1a\xe5\xa2\
-\xe8*\xa6\x7f\xc5G\x0a\xcf\xf9\x18\xe1[\x04\xf4H1\
-\x80\x8c\x86Q1\xb2\xc8\x85\xd1j\x0a\x995\x19Z?\
-\x05\xa9=\x8cr\xef\x8d\xfe*\x85\x9f\x0c\xe0\x94\xfb\x09\
-K\xf9\xfdn9\xcbH\x9c\x92\xb1\xf4\xc5\xf2\xfelY\
-\xea\xc6ex\x01\x07\xfb\x858\xc6o\xb9,t)\x17\
-\xfa\xf0fo\xaf\xd4ahT\x9e\xea\x99z\xe1q\x19\
-\x8f\xa5\x1e\x0f\xe0\xed\xab\xc5\xdc]\xa9L\xc6-9\x92\
-\x15\xa3h\xb1x\xc2G\xa8*\x1b\xb41\xac\xac[\x9e\
-\x9d\x1a\x81\x83\x92\x9b}\xa5\xf1\x0a\xc1\xc4X\xf97\xf9\
-\x92\xabwJ\x8euJ\xdb\x0c\xc8\xc9\xac\x18}\x1c\xd2\
-\x10\xed\x0a\x83\xc7{\xa2\xd89\xa4P\x9a1H\x07\x1c\
-2\x93\xe5\x19\xf8\x09\xf7a\x9c\x1a\xe7\xc9\xd5\x14Z\x01\
-\x89\xb1\xe0H\x84\xef\xd16)x\xee\xae\x1d\xec\xac\xec\
-\x17\x9e\x97\xb0\xc3\xb5\x80\x91\x95\x98@LP\xc9\xb5\x8c\
-\xb9\x97F;\xb2\x90\xfc\xe8\x09|\xb4\xa1eK\xde\x87\
-\xa5c\xde\x171Y\xf3\xccL\xcfSt\x8e\x02\xb3*\
-\x93\xeb\x98\x94rli|i\xe8\xc2\x91\xeb\x0b\xcd>\
-\xa7xW0\xb4Bc\xf4\x90\xf0q\x8d\x04\xc3\xb4\xd4\
-\xa0t\x18#\xfc\xe9\xdd\xbdh\x85x\xe6\xcbo5\xbf\
-\xc8u\xf6\xa8Fr?\x80\xddj\xf4\xa1W\xea\xb7G\
-\xd8;\xa8QO\x97\xd4\x13,\xf2\xbb\x07\xe5KI_\
-c\xf0\xd2+\xac\xba\xb1F\xd1\x84\xca\xb5\x18\x9c\x18\x8b\
-)\xc8\xcc\xe5)\x08\xa3\x02&\xa4\x07\x9e\xa2g\x8c\xd5\
-\xd1\xf0\x105\x8f6E<x\xab\x8cG\x13\x10\xa8`\
-$\xd2\xa6\x05\xaa\x9e\x11\x0dqE\x8d\xc7B\xa2\xbe\xc7\
-\x7f\x8d\x8bu\xfa\x92v\x00g\xb7h\x15\x9f2S\xfe\
-\xd7\xd5H\x8d\xe0j\x00\xde\xe6\xd8\x8a\xad\xf6\xc8\x14e\
-\xd2Si\xe0V\x94!w\x83\x99k\xd8\x1f\xe8\xe0\x9f\
-\xf9\xe0\x93\xa9\x8b\xae\x15\xdc\x95]\xe7\xc5\xf7\xe0U\xe3\
-\xfa\x1c\x13\x89Tj\x8e\x83F\xc2.\xf8W\xd0\x17N\
-Oa\xe2<\xd4W\x98p\x07#D\x1d\xbc\x13NK\
-\xc1\xfe\xd4\x87\x06\xe8\x9c\x83;N\xeb\xde\x80\xe3\xb7\x18\
-&,\xc4\x13\xae\xc7\xe8~\xddi\x14\xf1\xd08;\x9c\
-`|\xfd\xde\xdco\xd6\x8b\xc4jC\xb9\xe8\xae\x81\x8f\
-\xb9\xd4;\xad\x0cs\x8f\x14m\xc1\x225\x8a\xd1\x96\x9a\
-=6\xaf\x97\x09\xecI\xe4\xf3\xe6n\xd0\xe0\x07\xd2v\
-\xc9\xfe\xd4\x19{Wms\xa8\x8f)l\xbe+\x9b\x9b\
-\xe2}\xc9\xfc&\xf43\xb5\x0df\xda\x7f\xc42\xfed\
-\x82\x99K~\xf1\x04\x15\xa2\x1e\xfa\xae\xe20T\xc6a\
-]\xd6Q\x88d\xe8\xceZ\x12#D)\x99\xd34R\
-\xc4Jf-\xca~W\xd9X\xf3f\x5c\xd4\xaa\x99?\
-\xa3\xe6\x9c\xee*gK\x88\x05\xa2V\x92MUoc\
-\x0b\xba\xfe\x1c\xa7\xcb\x96\xbbK\xaf\x80\x96N\xa2\x06\xa9\
-\x8d0\xfey\xb9\xbf\xfe\x84\x90\x95\x9dra\x14;\xb7\
-H\x0d\xe7-\xbes\x19\xc6\xfeH\xfaNnG\xa5:\
-\x0ePe\xac/\x03\xbf\xeb\xdf\xf8^\x91$\x99\x1e\xb7\
-W\xd5\x87v\xdfn:Y\xb6i\xff\xbf\xbb!\xdb\x0f\
-\xc8td0z:{\xb8\x90\x99t\x012\x80Jz\
-\x06\x9fr\x1d\x93\xa7\xb4\x1c\xe5M}|C_/5\
-\xa0\xf3p\x93\xd3\xcd@q\x92KM\x1f\x1c\x87h\x08\
-\x12\x8e\x1eR\xae\xd0\xe5\x5cpHH\x01\xbaN\xc4\x14\
-X\x8f3\xed2+S\xb9\xbd\xa8\x84\xe7q\xd0\x8d\xc1\
-0\xb7|$\x83\x118&)\x7f\xe5.\x9c\xaatQ\
-\x0c\xb7\xb6\x5c\x04\xb5\xb4L\x17\x0c0\x92#\xd4\xa4\x12\
-\xdbN\xd6UN\xb0\xd0B:\x87t\xfd\x09\xa9,\x19\
-\xfcF\x96\xd2f\x94\x82\x10\xe7\xd6\xd1\xae\x853or\
-\x1e\xcc\xcc-\x19\x22\xc5\x8a\x12[J;\x0c\xdeK(\
-w\x95t\xf46!\xc9\x16-\x85\xac\x12\xac\xaa\xc9\x8e\
-g\x92\xda\x0bO\x17/c\xf7\x01\x93\xa4\x10h\xb7\xf4\
-\xa3\x00\xf2\x0aq\x82\xc6\xb8\xcaM~\xd4\xd2)\x06_\
-<\xdc\xa2FM-\x9b\x03\xb4\x86\xec\xd2\xa0\x0362\
-\xc5c\xb4\x9cp\xb4\x95(\xcc\x81+I\x0eJ\xcbp\
-\x90\xcfb\x82\x9cg\xdd\xdc\x1bo\xb5\x9b[\xa4\x96\xc4\
-+?&\x1cSd\xe9\xfei,\xa3S\xcb\xd3+\x15\
-\xf3\xb5\x95\x8e\xc4\x9c\x1f}\xab\xed\xc2lt\xec1\xdd\
-\xd8cy\x93U\x0e\xa20\xdc\xc822t\x02\xa6U\
-\xaer\xe6\xa7\xf5s5\x9eSn\xc7R\x0f\xa0\xb7j\
-g\xb1v\xce\x85\x08U\xe3%\xbc~\x9a\xe6\xf0Xg\
-\xf9s\xb6\x862\xc8\x7flcXa\xfe\xdf^\xe4\x12\
-\xba\xbc\xb8\x96\xceg\x9b\x16\xae!@]\x0d\xdc\xd9\xf5\
-\xf2r7\xa4H\xa2j\xbb\xe0\x95\xdf\x07\x9f\xfb\x9aG\
-\xc7\xe3\xb2\x1c}\x08\x83\xcb\xceL\x9a\xc1\xf6\x5c!\xa7\
-\x13\xa4\x80_\xf1\x91+\xb6X|C\xe5\xca\x0dv:\
-Z\x83`s\xa1\xf5\xec\xf3ekG\xda\x5c\xfd>\x93\
-\xf8#\x89Jt\x98\xd4\x15\xf1\x05|B\xb9\x9f\x18%\
-\xd0~.-\xfaBM\x93\xa6\x83\xfc\x99\x95\xaf+v\
-\xce\x9d\xbb3V\xb4+\x86\x84\x85\xf2TYH`s\
-\xae\x0f\xb4v\xa6?\x88\x8e6\xf0\x87\xecj/\x7f\xe5\
-\xefC\xf4/yY\x0ax\
-\x00\x00\x03\xe0\
+\x00\x18\xbax\x9c\xcdXKo\x1b7\x10\xbe\xebWL\
+}\xb2\x1b\x8bJ\xec\x02\x05t)\x129i\x0c\xd8\xb1\
+\x13;M\x0b\xf4B\xed\x8e$\xd6\x5crMrm+\
+\xa9\xff{\x87\xa4\x1e\xab}IH\xda:s\x90\x96\xcb\
+\x19\xce\xeb\xe3p\xb8\x83\x01\x8ct>7b:s\xb0\
+?:\x80\xa3\xe7/~\x86\xeb\x19\xc2{G3Y\xce\
+\xd5\x1c\xce\x5c\xcaz\x83\x01\x5c]\x9e\xfc\xde?\x13\x09\
+*\x8b\xfd\xd3\x14\x95\x13\x13\x81f\x08\x8bw\x1fp\xd2\
+\x7f\xef\xfa$\x96\xa1I\x04\x97p\xf1\x01^]\x9d\xf4\
+\x8f\xfb#\xc9\x0b\x8b\xbd\x9e\xc8rm\x1c-\xfe\xbe\x10\
+\xc9Me\xc8\xce\xf8\x5c\x17\xceV_\x8f\xb4rFK\
+\xdb\xeb]%\xf4/\xf9X\xe2%\x9f\x22|\xe9\x01\x91\
+H\x87\x90\xd3\xb0\x17F\x06y\xaa\x95\x9cCnt\x8e\
+\xc6\xcdA(\x07\xe3\xc29\xad>\x89\xd4\xcd\x86p\xce\
+\xdd\x8ce\xfca?\xbee\xa4N\x8aD\xb80}\xb8\
+\x98\x16\xaaq\x1a~\x84\xa3\xc3\xa0\x8e\xf1;.\x82-\
+qb\x00\xc7\x07\x07\xd1\x86\x91\x96E\xa6\x16\xe6y\xb2\
+9O\x84\x9a\x0e\xe1\xa7\xe7\xabw\xf7\xd1\x98\x9c\x1b\x0a\
+$\x0b\xa3\xdej\xf2\x8c\x8fQ\x96\x16h\x13\xd8\x987\
+<?\xd7)\x0e\xa34\xfbD\xe3\x0d\x86\x996\xe23\
+\xc5\x92\xcb\x97RLUF\xcb\x0c)\xc8,\x8c\xde\x8e\
+h\x88fC\xc0\xe1\x03q\xdc\xdak\xb3\xbfwB\xf9\
+\xd4S\x10\x168\xe4:/rp3\xee\xfc8\xd3\xd6\
+Q\xbc)\xbf)L\xb4\x01Kz\x5c\x9f\xd6\xca\xc0q\
+{caocQO\xcf`\x8f\xab\x14\xc6F\xe0\x04\
+\x12\xc2K\xa1D\xc2\x9d\xd0\xca\x92\x9b\x14LG\x08\xa4\
+\x05\x0d\xdb;X\x09?\xae\xc3\xf3*d\xa6\x12\x9f\xb2\
+\xb5\xe7h-\xe5\xa8$\xed\x89\xab\x84l\xb3l\x1d\x88\
+\xe8\xf4*\xa8\xd5\x89\xe6\xf8S\xeeKx\xda\xe0\xd1j\
+DX\xb9A\x82d\x16M\x88qc\x84E\xb5\x7f\xd0\
+\xdb`^\x84\xf4K-<\xa2*^\xe3@\xe5\x81G\
+l?\xfc\xca\xa5D3\xa7=2\x11S\x96\x0a\x1b&\
+z5\x89\x87!\xec\x97\xa1\x03\xfd\xe8\xd0\x01\x01\xf7\xa8\
+\xc6=_s\xcf0\x14\x87>\xc4\x87\xc8_\x13p\xc2\
+I\xacG\xbf\xc6\xd7\x04\xec\xd5\x1a\xa5\x04\x9ei\x83\x19\
+\x88\xdc\x16\x19\xa4ZzX\x09\x07<C\xc7\x18\xab\xa4\
+\xd5\xd3c\xafy\xb4\x0d4>\xd41\x99\xadX\x0a\xa1\
+5Y\x80\xe7S\x02*)\xd9\xf1\xb5\xa8\xaa\xaf\xf1\xdd\
+A\xab\xca\x1fy\x87pq\x87F\xf29\xd3\xf1\xbfn\
+F\xa6S.\x87\xe0L\x81\xdd\xf0\xecH\xa8'\xeb\xa8\
+6q\x93F\xbc\xd8\xe1\x22\xa2\xec\x0f\xb4\xf0\xf7r\xf0\
+N\xd7\x0d\xa8\x95\xfd\x8dU\x97G\xc0\xd1\xf3\xc6\xf9%\
+\x94&B\xca%|\x1a\x19\xbbvPp\xb5\x04]\x7f\
+\x90\xa7:)|\xa9\x87\x19\xb70FT>P\xfe\xe0\
+N\xd9\x9f\xeaD\x03\x1d\xb9p\xcfi\xdei\xb0\xfc\x0e\
+\xfd\x0b\x03\xc9\x8c\xab)\xda_\x1a\xe2\xe3\xe9\xb1\xf1\xed\
+h\x86\xc9\xcd+\xfd\xb0\xa3u\xa4\x5ci\xda\xd4\xf6\x06\
+\xf8\x94\x8b\xa6\x5cT\xa3\x13\x1a\x95\xd5\xee\x0a\xa3\x1d\xed\
+\xfb\xda\x02Q\xa9\x04\x8e\xf4>q\x11\xf0&|\xc3\xfe\
+_\x8b\x7f\xf7[\xff\xbe\xdc\xafQC\x16\x82t\x1f\xbb\
+\xb4\xf0\xbc\x16>\xf6\x8dYm\x81\x85\xbbo1\xc2\x86\
+|\xd6K\xcdM\xec\xffI\xa9i@\x8c\xa7\xb6*3\
+\x92\xdab]\xe3\x1b\x0f\x00\x9f\x84\x96\xbd\xe5\x93;Y\
+\xf24r$R\xe4-&{\xda\xb9\xfeTb\x9a\x84\
+\xa2\xb7\x88j\xddnO\x9duqi|\x5c\xa7\x95e\
+[\xf9\xf4\xd4\xd9QW\xe94[\xdf \xba\xec\xf2\x98\
+\xe9dj\xd0\xda\x08\xe62}[\xc9\xa8\x92OYl\
+\xfc\x83S\xec\xd2 \xb5\xcew\xf8\xd2\xe6\x98\xb87\xa2\
+9\x8fK\xb2T\xee\x13\x92\xddcl \xbc\xbc\x1d\xdc\
+\xba~\xd8*\xb9\x9a\xd6{\xf7%=\xb6\xc7v\xdb\x11\
+\xe5i\xdbm\xa6J\xbb5\x87\x87\x1e\x9e\x96\xbcFG\
+\x87\x18OE.\xac\xc7\x0d\xa0\x14\x8e\xc1\xbbB%\x14\
+.%\xc6E\xd3\xb5\xa4JtM\x11\x0a\xd0:\xb8-\
+\xe8\xa2#9\xe9\xa5\x06\x14N}JRR\x8e\x0e2\
+.\xd1\x16<\xe50!\x03\xe8v\x94Pv\x1d.\xac\
+\xcb\x8d\xc8\xc4\xee\xaa&\xbcH\xbcm\x0cF\x85\xe1c\
+\xe1\x9d\xc0)i\xf9\xab\xb0\xfex\xa6\xcb\xaf\xbf\x89\x16\
+\xa97K\x89l%\x00c1FE&\xb1\xddt]\
+\x15\x84\x0dEu\xdd\x22\xdd\xe6\xfc~\x16\x0c~#O\
+i1\xda\x87\x90\x14\xc6\xd2\xaa!\x98\xb7\x05\xf7n\x16\
+\x86\x1c\x11\xe9\x86\x11;j;\xf1\xd1\x9b\xd0\x06\x96\xc2\
+\xd2\xd3\x8c4\x1b4\x94\xb2R\xb2\xca.[\x9e\x0b\xea\
+S\x1c\xdd#\xb59\x04\x9cL\x82B\xb3c\x1cS\xa0\
+\xa8\x90$(L\xca\xd2\x14G%\xacd\xf0\xd1\xc1\x1d\
+*:\xed\x1cY\x83F\x93_\x0a\x94\xc7F.y\x82\
+\x86\x13\x8evR\x85\x05p)(@YL\x07\xc5,\
+!\xc8\xb9\xa6\x1bJ\x99\xb6^\xd5\xcb\xd4\xdck\xb5l\
+\xbf\xf8\x85\xe4\x94RK\xf7im\x18\x9d`\x8e\x1e\xa9\
+\xa4Wf:\xb6\xe7\xf2\x18\xdc\xec5\x16\xa3S\x87\xd9\
+\xd66\xcd\xe9\xbct\x1c\xf9\xe1V\x91\xb1\xa6s0+\
+K\xc57\xffZKX\x939\xe7f*\xd4\x10\xfa\x9b\
+~\x86\xb9K\x9e\xa6\xbel<\x83\x17\xffc\x7fy\xaa\
+\xf2\xe2I\xbbK\xe1\x0d\xf8\xda\xde\xb2$\xfc\xddw\x96\
+;wz\x13\xba=\xd9\x96\xb6i\xe7.\xb0)\xaf\x9e\
+\xdaz\xc0\x8b\x9b\xf5EsD\x00@\xd9v\xd9\x8c_\
+L\x9f\xfa\xcaI\x87\xeb\xba\x96\xbd\xf6\x83\x0f\x9d\xdb\xb0\
+\x8c\xf9K\x89\x9c\x0e\xa1\x00\xdd\xf0\xd9/1\x18>-\
+si\x87\x1de4z\x1el_|\xd6mmm\x9b\
+\x0b\xe85\x19\xf1F\xa0L;\x1c\xeb\xca\xfe\x92\xc2\x91\
+1\xd32Es]\xf2\xeb#\xb5`\x8a:\x82\xa7v\
+\xa1\xc5\xbcKn\xed\xbd6i\x87yH\xe8\x88\x87\xd4\
+J\x0f[\x8a\xbd\xa6\xb9\x0b\xf5:\xed\xe8-\xbf\xc9\xbb\
+\xf6j\x1a\x7f\x1f{\xff\x00\x94ma\x98\
+\x00\x00\x03\xd4\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1719,45 +2567,45 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22CheckBo\
-x presents an op\
-tion button that\
- can be toggled \
-on or off. \x22\x0a   \
-             + \x22\
-Check boxes are \
-typically used t\
-o select one or \
-more options fro\
-m a set of optio\
-ns.\x22\x0a        }\x0a\x0a\
-        Column {\
-\x0a            spa\
-cing: 20\x0a       \
-     anchors.hor\
-izontalCenter: p\
-arent.horizontal\
-Center\x0a\x0a        \
-    CheckBox {\x0a \
-               t\
-ext: \x22First\x22\x0a   \
-             che\
-cked: true\x0a     \
-       }\x0a       \
-     CheckBox {\x0a\
-                \
-text: \x22Second\x22\x0a \
-           }\x0a   \
-         CheckBo\
-x {\x0a            \
-    text: \x22Third\
+  text: qsTr(\x22Ch\
+eckBox presents \
+an option button\
+ that can be tog\
+gled on or off. \
 \x22\x0a              \
-  checked: true\x0a\
-                \
-enabled: false\x0a \
-           }\x0a   \
-     }\x0a    }\x0a}\x0a\
-\x00\x00\x02\xb5\
+  + \x22Check boxes\
+ are typically u\
+sed to select on\
+e or more option\
+s from a set of \
+options.\x22)\x0a     \
+   }\x0a\x0a        Co\
+lumn {\x0a         \
+   spacing: 20\x0a \
+           ancho\
+rs.horizontalCen\
+ter: parent.hori\
+zontalCenter\x0a\x0a  \
+          CheckB\
+ox {\x0a           \
+     enabled: !G\
+alleryConfig.dis\
+abled\x0a          \
+      text: qsTr\
+(\x22First\x22)\x0a      \
+          checke\
+d: true\x0a        \
+    }\x0a          \
+  CheckBox {\x0a   \
+             ena\
+bled: !GalleryCo\
+nfig.disabled\x0a  \
+              te\
+xt: qsTr(\x22Second\
+\x22)\x0a            }\
+\x0a        }\x0a    }\
+\x0a}\x0a\
+\x00\x00\x02\xee\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1783,27 +2631,30 @@ Wrap\x0a           \
  horizontalAlign\
 ment: Qt.AlignHC\
 enter\x0a          \
-  text: \x22DelayBu\
-tton is a checka\
-ble button that \
-incorporates a d\
-elay before the \
-\x22\x0a              \
-  + \x22button is a\
-ctivated. This d\
-elay prevents ac\
-cidental presses\
-.\x22\x0a        }\x0a\x0a  \
-      DelayButto\
-n {\x0a            \
-text: \x22DelayButt\
-on\x22\x0a            \
-anchors.horizont\
-alCenter: parent\
+  text: qsTr(\x22De\
+layButton is a c\
+heckable button \
+that incorporate\
+s a delay before\
+ the \x22\x0a         \
+       + \x22button\
+ is activated. T\
+his delay preven\
+ts accidental pr\
+esses.\x22)\x0a       \
+ }\x0a\x0a        Dela\
+yButton {\x0a      \
+      enabled: !\
+GalleryConfig.di\
+sabled\x0a         \
+   text: qsTr(\x22D\
+elayButton\x22)\x0a   \
+         anchors\
 .horizontalCente\
-r\x0a        }\x0a    \
-}\x0a}\x0a\
-\x00\x00\x07\x9f\
+r: parent.horizo\
+ntalCenter\x0a     \
+   }\x0a    }\x0a}\x0a\
+\x00\x00\x07\xe1\
 /\
 / Copyright (C) \
 2017 The Qt Comp\
@@ -1819,114 +2670,119 @@ mport QtQuick.Co\
 ntrols\x0a\x0aStackVie\
 w {\x0a    id: stac\
 kView\x0a    initia\
-lItem: page\x0a\x0a   \
- Component {\x0a   \
-     id: page\x0a\x0a \
-       Pane {\x0a  \
-          id: pa\
-ne\x0a            w\
-idth: parent ? p\
-arent.width : 0 \
-// TODO: fix nul\
-l parent on dest\
-ruction\x0a\x0a       \
-     Column {\x0a  \
             sp\
-acing: 40\x0a      \
         width:\
parent.width\x0a\x0a \
-               L\
-abel {\x0a         \
-           width\
-: parent.width\x0a \
+lItem: page\x0a    \
+enabled: !Galler\
+yConfig.disabled\
+\x0a\x0a    Component \
+{\x0a        id: pa\
+ge\x0a\x0a        Pane\
+ {\x0a            i\
+d: pane\x0a        \
+    width: paren\
+t ? parent.width\
+ : 0 // TODO: fi\
+x null parent on\
destruction\x0a\x0a  \
+          Column\
{\x0a             \
  spacing: 40\x0a \
+               w\
+idth: parent.wid\
+th\x0a\x0a            \
+    Label {\x0a    \
                 \
-   wrapMode: Lab\
-el.Wrap\x0a        \
-            hori\
-zontalAlignment:\
- Qt.AlignHCenter\
-\x0a               \
-     text: \x22Stac\
-kView provides a\
- stack-based nav\
-igation model wh\
-ich can be used \
-with a set of in\
-terlinked pages.\
- \x22\x0a             \
-       + \x22Items \
-are pushed onto \
-the stack as the\
- user navigates \
-deeper into the \
-material, and po\
-pped off again \x22\
-\x0a               \
-     + \x22when he \
-chooses to go ba\
-ck.\x22\x0a           \
-     }\x0a\x0a        \
-        Button {\
-\x0a               \
-     id: button\x0a\
+width: parent.wi\
+dth\x0a            \
+        wrapMode\
+: Label.Wrap\x0a   \
                 \
-    text: \x22Push\x22\
-\x0a               \
-     anchors.hor\
-izontalCenter: p\
-arent.horizontal\
-Center\x0a         \
-           width\
-: Math.max(butto\
-n.implicitWidth,\
- Math.min(button\
-.implicitWidth *\
- 2, pane.availab\
-leWidth / 3))\x0a  \
+ horizontalAlign\
+ment: Qt.AlignHC\
+enter\x0a          \
+          text: \
+qsTr(\x22StackView \
+provides a stack\
+-based navigatio\
+n model which ca\
+n be used with a\
+ set of interlin\
+ked pages. \x22\x0a   \
                 \
-  onClicked: sta\
-ckView.push(page\
-)\x0a              \
-  }\x0a\x0a           \
-     Button {\x0a  \
+ + \x22Items are pu\
+shed onto the st\
+ack as the user \
+navigates deeper\
+ into the materi\
+al, and popped o\
+ff again \x22\x0a     \
+               +\
+ \x22when he choose\
+s to go back.\x22)\x0a\
                 \
-  text: \x22Pop\x22\x0a  \
+}\x0a\x0a             \
+   Button {\x0a    \
                 \
-  enabled: stack\
-View.depth > 1\x0a \
+id: button\x0a     \
+               t\
+ext: qsTr(\x22Push\x22\
+)\x0a              \
+      anchors.ho\
+rizontalCenter: \
+parent.horizonta\
+lCenter\x0a        \
+            widt\
+h: Math.max(butt\
+on.implicitWidth\
+, Math.min(butto\
+n.implicitWidth \
+* 2, pane.availa\
+bleWidth / 3))\x0a \
                 \
-   width: Math.m\
-ax(button.implic\
-itWidth, Math.mi\
-n(button.implici\
-tWidth * 2, pane\
-.availableWidth \
-/ 3))\x0a          \
-          anchor\
-s.horizontalCent\
-er: parent.horiz\
-ontalCenter\x0a    \
+   onClicked: st\
+ackView.push(pag\
+e)\x0a             \
+   }\x0a\x0a          \
+      Button {\x0a \
                 \
-onClicked: stack\
-View.pop()\x0a     \
-           }\x0a\x0a  \
-              La\
-bel {\x0a          \
+   text: qsTr(\x22P\
+op\x22)\x0a           \
+         enabled\
+: stackView.dept\
+h > 1\x0a          \
           width:\
- parent.width\x0a  \
+ Math.max(button\
+.implicitWidth, \
+Math.min(button.\
+implicitWidth * \
+2, pane.availabl\
+eWidth / 3))\x0a   \
                 \
-  wrapMode: Labe\
-l.Wrap\x0a         \
-           horiz\
-ontalAlignment: \
-Qt.AlignHCenter\x0a\
+ anchors.horizon\
+talCenter: paren\
+t.horizontalCent\
+er\x0a             \
+       onClicked\
+: stackView.pop(\
+)\x0a              \
+  }\x0a\x0a           \
+     Label {\x0a   \
+                \
+ width: parent.w\
+idth\x0a           \
+         wrapMod\
+e: Label.Wrap\x0a  \
                 \
-    text: \x22Stack\
- Depth: \x22 + stac\
-kView.depth\x0a    \
+  horizontalAlig\
+nment: Qt.AlignH\
+Center\x0a         \
+           text:\
+ qsTr(\x22Stack Dep\
+th:\x22) + \x22 \x22 + st\
+ackView.depth\x0a  \
+              }\x0a\
             }\x0a  \
-          }\x0a    \
-    }\x0a    }\x0a}\x0a\
+      }\x0a    }\x0a}\x0a\
+\
 \x00\x00\x01?\
 \x89\
 PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -4086,14 +4942,23 @@ qt_resource_name = b"\
 \x00P\
 \x00r\x00o\x00g\x00r\x00e\x00s\x00s\x00B\x00a\x00r\x00P\x00a\x00g\x00e\x00.\x00q\
 \x00m\x00l\
+\x00\x13\
+\x08\xce#\x1c\
+\x00S\
+\x00e\x00a\x00r\x00c\x00h\x00F\x00i\x00e\x00l\x00d\x00P\x00a\x00g\x00e\x00.\x00q\
+\x00m\x00l\
+\x00\x10\
+\x06\xd3\x8b\x1c\
+\x00T\
+\x00e\x00x\x00t\x00A\x00r\x00e\x00a\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
 \x00\x0f\
 \x0b\xe33|\
 \x00T\
 \x00o\x00o\x00l\x00T\x00i\x00p\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
-\x00\x10\
-\x06\xd3\x8b\x1c\
+\x00\x0f\
+\x0b\xc87|\
 \x00T\
-\x00e\x00x\x00t\x00A\x00r\x00e\x00a\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\x00o\x00o\x00l\x00B\x00a\x00r\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
 \x00\x0e\
 \x02%\xd0|\
 \x00S\
@@ -4102,10 +4967,34 @@ qt_resource_name = b"\
 \x00\xf4\xb9\xfc\
 \x00T\
 \x00u\x00m\x00b\x00l\x00e\x00r\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\x00\x10\
+\x02a.\x1c\
+\x00T\
+\x00r\x00e\x00e\x00V\x00i\x00e\x00w\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\x00\x11\
+\x0fX4\x5c\
+\x00M\
+\x00o\x00n\x00t\x00h\x00G\x00r\x00i\x00d\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\
+\x00\x11\
+\x05\x22\x04\xdc\
+\x00S\
+\x00p\x00l\x00i\x00t\x00V\x00i\x00e\x00w\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\
+\x00\x11\
+\x03\x22,\x5c\
+\x00T\
+\x00a\x00b\x00l\x00e\x00V\x00i\x00e\x00w\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\
 \x00\x0e\
 \x0e\xa2\x84\x9c\
 \x00B\
 \x00u\x00t\x00t\x00o\x00n\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\x00\x11\
+\x04\xf7 \x1c\
+\x00G\
+\x00a\x00l\x00l\x00e\x00r\x00y\x00C\x00o\x00n\x00f\x00i\x00g\x00.\x00q\x00m\x00l\
+\
 \x00\x0e\
 \x0b\xc5|\x5c\
 \x00S\
@@ -4131,6 +5020,10 @@ qt_resource_name = b"\
 \x0c\xc8%\xdc\
 \x00F\
 \x00r\x00a\x00m\x00e\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
+\x00\x0f\
+\x0c\xe8\x19\xfc\
+\x00M\
+\x00e\x00n\x00u\x00B\x00a\x00r\x00P\x00a\x00g\x00e\x00.\x00q\x00m\x00l\
 \x00\x11\
 \x03$Q\x5c\
 \x00S\
@@ -4267,138 +5160,154 @@ qt_resource_name = b"\
 qt_resource_struct = b"\
 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x01\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x001\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x009\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\xb4\x00\x02\x00\x00\x00\x1b\x00\x00\x00\x16\
+\x00\x00\x00\xb4\x00\x02\x00\x00\x00#\x00\x00\x00\x16\
 \x00\x00\x00\x00\x00\x00\x00\x00\
 \x00\x00\x00\x90\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x0a\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xc1\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x09\xc5\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\x0a)\
-\x00\x00\x01\x86\xb7m\x07\x8b\
+\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xbc\
+\x00\x00\x01\x99{\x95\xd3\xc8\
+\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xc0\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\x0c$\
+\x00\x00\x01\x99\xa0\x15<\x1b\
 \x00\x00\x00\x5c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x09\
 \x00\x00\x00\x00\x00\x00\x00\x00\
 \x00\x00\x00\x10\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\xe93\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x00\xd5\x9d\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x062\x00\x00\x00\x00\x00\x01\x00\x00\xe5\x81\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x058\x00\x00\x00\x00\x00\x01\x00\x00\x9b\xf7\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\x16\x00\x00\x00\x00\x00\x01\x00\x00\xda\x5c\
-\x00\x00\x01\x87\x137-\xff\
-\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x00u\x04\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x04\xe4\x00\x00\x00\x00\x00\x01\x00\x00s\xc1\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x05\xd4\x00\x00\x00\x00\x00\x01\x00\x00\xc2Q\
-\x00\x00\x01\x87\x137\xd0\xfa\
-\x00\x00\x05\x94\x00\x00\x00\x00\x00\x01\x00\x00\xa4\x17\
-\x00\x00\x01\x87\x137\xd0\xfa\
-\x00\x00\x05\x16\x00\x00\x00\x00\x00\x01\x00\x00v\xd2\
-\x00\x00\x01\x87\x137\xd0\xfe\
-\x00\x00\x05\xb6\x00\x00\x00\x00\x00\x01\x00\x00\xc0q\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x05X\x00\x00\x00\x00\x00\x01\x00\x00\x9e\xb6\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x05v\x00\x00\x00\x00\x00\x01\x00\x00\xa1\xb0\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x03\x96\x00\x00\x00\x00\x00\x01\x00\x00O+\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02\xe2\x00\x01\x00\x00\x00\x01\x00\x009\x1c\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x03n\x00\x00\x00\x00\x00\x01\x00\x00J\xec\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02,\x00\x00\x00\x00\x00\x01\x00\x00&\x88\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02\x0a\x00\x00\x00\x00\x00\x01\x00\x00#\x85\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xdf\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x03F\x00\x00\x00\x00\x00\x01\x00\x00E\xa6\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x04j\x00\x00\x00\x00\x00\x01\x00\x00e\x81\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x01j\x00\x00\x00\x00\x00\x01\x00\x00\x18\xb0\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x04\x90\x00\x00\x00\x00\x00\x01\x00\x00ie\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02\xbc\x00\x00\x00\x00\x00\x01\x00\x003L\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x00 \xe6\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x04\xbc\x00\x00\x00\x00\x00\x01\x00\x00l\x1e\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xd0\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x04H\x00\x01\x00\x00\x00\x01\x00\x00_\xe4\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x10g\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x04\x14\x00\x00\x00\x00\x00\x01\x00\x00[\x8b\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02r\x00\x00\x00\x00\x00\x01\x00\x00->\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x08\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x03&\x00\x00\x00\x00\x00\x01\x00\x00@\xba\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xb5\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00=\xee\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00(\xd1\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00\x15\xe6\
-\x00\x00\x01\x86\xcc\xe0s'\
-\x00\x00\x03\xe8\x00\x00\x00\x00\x00\x01\x00\x00W\xc0\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x03\xc2\x00\x00\x00\x00\x00\x01\x00\x00R\xa5\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x02\x94\x00\x00\x00\x00\x00\x01\x00\x000\xe7\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06R\x00\x02\x00\x00\x00\x05\x00\x00\x002\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x01\x1d|\
+\x00\x00\x01\x99\xa0\x15<\x1b\
+\x00\x00\x070\x00\x00\x00\x00\x00\x01\x00\x01\x09\xe6\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x07l\x00\x00\x00\x00\x00\x01\x00\x01\x19\xca\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x06r\x00\x00\x00\x00\x00\x01\x00\x00\xd0@\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x07P\x00\x00\x00\x00\x00\x01\x00\x01\x0e\xa5\
+\x00\x00\x01\x87,\x09\xa2X\
+\x00\x00\x066\x00\x00\x00\x00\x00\x01\x00\x00\xa9M\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x06\x1e\x00\x00\x00\x00\x00\x01\x00\x00\xa8\x0a\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xf6\x9a\
+\x00\x00\x01\x87,\x09\xa2X\
+\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xd8`\
+\x00\x00\x01\x87,\x09\xa2X\
+\x00\x00\x06P\x00\x00\x00\x00\x00\x01\x00\x00\xab\x1b\
+\x00\x00\x01\x87,\x09\xa2X\
+\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xf4\xba\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x00\xd2\xff\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x06\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xd5\xf9\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x81\x99\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x03\xf8\x00\x01\x00\x00\x00\x01\x00\x00d\xea\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x04\xa8\x00\x00\x00\x00\x00\x01\x00\x00|B\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x002\x85\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x02Z\x00\x00\x00\x00\x00\x01\x00\x00/9\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x10-\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x005\x01\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x03\x16\x00\x00\x00\x00\x00\x01\x00\x00K\xab\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00v\xcd\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x99[\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x03`\x00\x00\x00\x00\x00\x01\x00\x00W\x8a\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00C\xe3\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x01j\x00\x00\x00\x00\x00\x01\x00\x00\x1bG\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\x9d3\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00^\x93\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00'0\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x00\xa0%\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xff\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00 \xa5\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x05\x82\x00\x01\x00\x00\x00\x01\x00\x00\x93\x8a\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x12\xbb\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x05N\x00\x00\x00\x00\x00\x01\x00\x00\x8f\x07\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x03\x88\x00\x00\x00\x00\x00\x01\x00\x00XN\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x026\x00\x01\x00\x00\x00\x01\x00\x00,\xf8\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x02\x12\x00\x00\x00\x00\x00\x01\x00\x00*\x08\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00l\xd7\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00r\x08\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1dL\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x04\x1e\x00\x00\x00\x00\x00\x01\x00\x00i\xd8\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x03>\x00\x01\x00\x00\x00\x01\x00\x00UB\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00\x18w\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x05\x22\x00\x00\x00\x00\x00\x01\x00\x00\x8a\xc2\
+\x00\x00\x01\x99\xa0\x15<\x1d\
+\x00\x00\x02\xc6\x00\x01\x00\x00\x00\x01\x00\x00@\xb9\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x85\x5c\
+\x00\x00\x01\x99\xa0\x15<\x1c\
+\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00\x5c\x0b\
+\x00\x00\x01\x99\xa0\x15<\x1e\
+\x00\x00\x07\x8c\x00\x02\x00\x00\x00\x05\x00\x00\x00:\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x06z\x00\x02\x00\x00\x00\x03\x00\x00\x00@\
+\x00\x00\x07\xb4\x00\x02\x00\x00\x00\x03\x00\x00\x00H\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x06f\x00\x02\x00\x00\x00\x03\x00\x00\x00=\
+\x00\x00\x07\xa0\x00\x02\x00\x00\x00\x03\x00\x00\x00E\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x06\xba\x00\x02\x00\x00\x00\x03\x00\x00\x00:\
+\x00\x00\x07\xf4\x00\x02\x00\x00\x00\x03\x00\x00\x00B\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x06\x8a\x00\x02\x00\x00\x00\x03\x00\x00\x007\
+\x00\x00\x07\xc4\x00\x02\x00\x00\x00\x03\x00\x00\x00?\
 \x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x06\x9e\x00\x00\x00\x00\x00\x01\x00\x00\xe9\xf6\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xed6\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xfe\x00\x00\x00\x00\x00\x01\x00\x00\xee\xb7\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xe4\x00\x00\x00\x00\x00\x01\x00\x00\xee0\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xeb\x04\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xfe\x00\x00\x00\x00\x00\x01\x00\x00\xecq\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xe4\x00\x00\x00\x00\x00\x01\x00\x00\xeb\xeb\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xf1.\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xfe\x00\x00\x00\x00\x00\x01\x00\x00\xf2l\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xe4\x00\x00\x00\x00\x00\x01\x00\x00\xf1\xea\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xef\x9a\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xfe\x00\x00\x00\x00\x00\x01\x00\x00\xf0\xaf\
-\x00\x00\x01\x86\xb7m\x07\x8b\
-\x00\x00\x06\xe4\x00\x00\x00\x00\x00\x01\x00\x00\xf00\
-\x00\x00\x01\x86\xb7m\x07\x8b\
+\x00\x00\x07\xd8\x00\x00\x00\x00\x00\x01\x00\x01\x1e?\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01!\x7f\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01#\x00\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01\x22y\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01\x1fM\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01 \xba\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01 4\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01%w\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01&\xb5\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01&3\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01#\xe3\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01$\xf8\
+\x00\x00\x01\x86\xe4%hu\
+\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01$y\
+\x00\x00\x01\x86\xe4%hu\
 "
 
 def qInitResources():
diff --git a/examples/remoteobjects/modelview/doc/modelview.png b/examples/remoteobjects/modelview/doc/modelview.png
new file mode 100644 (file)
index 0000000..afa275c
Binary files /dev/null and b/examples/remoteobjects/modelview/doc/modelview.png differ
index 4807904c1da952dc224365ba216462c3f695bc63..3f355d3614e3a5d203e9894d0570473e98c876d7 100644 (file)
@@ -42,63 +42,24 @@ set(generated_sources
     ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/truck_wrapper.cpp)
 
 
-# ================================== Shiboken detection ======================================
-# Use provided python interpreter if given.
-if(NOT python_interpreter)
-    if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-        find_program(python_interpreter "python_d")
-        if(NOT python_interpreter)
-            message(FATAL_ERROR
-                "A debug Python interpreter could not be found, which is a requirement when "
-                "building this example in a debug configuration. Make sure python_d.exe is in "
-                "PATH.")
-        endif()
-    else()
-        find_program(python_interpreter "python")
-        if(NOT python_interpreter)
-            message(FATAL_ERROR
-                "No Python interpreter could be found. Make sure python is in PATH.")
-        endif()
-    endif()
-endif()
-message(STATUS "Using python interpreter: ${python_interpreter}")
-
-# Macro to get various pyside / python include / link flags and paths.
-# Uses the not entirely supported utils/pyside_config.py file.
-macro(pyside_config option output_var)
-    if(${ARGC} GREATER 2)
-        set(is_list ${ARGV2})
-    else()
-        set(is_list "")
-    endif()
-
-    execute_process(
-      COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside_config.py"
-              ${option}
-      OUTPUT_VARIABLE ${output_var}
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-    if ("${${output_var}}" STREQUAL "")
-        message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.")
-    endif()
-    if(is_list)
-        string (REPLACE " " ";" ${output_var} "${${output_var}}")
-    endif()
-endmacro()
-
-# Query for the shiboken generator path, Python path, include paths and linker flags.
-pyside_config(--shiboken-module-path shiboken_module_path)
-pyside_config(--shiboken-generator-path shiboken_generator_path)
-pyside_config(--python-include-path python_include_dir)
-pyside_config(--shiboken-generator-include-path shiboken_include_dir 1)
-pyside_config(--shiboken-module-shared-libraries-cmake shiboken_shared_libraries 0)
-pyside_config(--python-link-flags-cmake python_linking_data 0)
-
-set(shiboken_path "${shiboken_generator_path}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}")
-if(NOT EXISTS ${shiboken_path})
-    message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}")
-endif()
-
+# ================================== Dependency detection ======================================
+
+find_package(Python COMPONENTS Interpreter Development REQUIRED)
+# On RHEL and some other distros, Python wheels and site-packages may be installed under 'lib64'
+# instead of 'lib'. The FindPython CMake module may set Python_SITELIB to 'lib', which is incorrect
+# for these cases. To ensure compatibility, we override Python_SITELIB by querying Python directly.
+# This guarantees the correct site-packages path is used regardless of platform or Python build.
+execute_process(
+    COMMAND ${Python_EXECUTABLE} -c
+    "import site; print(next(p for p in site.getsitepackages() if 'site-packages' in p))"
+    OUTPUT_VARIABLE Python_SITELIB
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+message(STATUS "Python site-packages directory: ${Python_SITELIB}")
+list(APPEND CMAKE_PREFIX_PATH
+    "${Python_SITELIB}/shiboken6_generator/lib/cmake"
+)
+find_package(Shiboken6Tools REQUIRED)
 
 # ==================================== RPATH configuration ====================================
 
@@ -110,7 +71,7 @@ endif()
 # Enable rpaths so that the built shared libraries find their dependencies.
 set(CMAKE_SKIP_BUILD_RPATH FALSE)
 set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
-set(CMAKE_INSTALL_RPATH ${shiboken_module_path} ${CMAKE_CURRENT_SOURCE_DIR})
+set(CMAKE_INSTALL_RPATH ${CMAKE_CURRENT_SOURCE_DIR})
 set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
 # =============================================================================================
 # !!! End of dubious section.
@@ -129,76 +90,17 @@ set_property(TARGET ${sample_library} PROPERTY PREFIX "")
 # library can't link to the sample library.
 target_compile_definitions(${sample_library} PRIVATE BINDINGS_BUILD)
 
-
-# ====================== Shiboken target for generating binding C++ files  ====================
-
-
-# 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-bool
-    --avoid-protected-hack
-    -I${CMAKE_SOURCE_DIR}
-    -T${CMAKE_SOURCE_DIR}
-    --output-directory=${CMAKE_CURRENT_BINARY_DIR}
-    )
-
-set(generated_sources_dependencies ${wrapped_header} ${typesystem_file})
-
-# Add custom target to run shiboken to generate the binding cpp files.
-add_custom_command(OUTPUT ${generated_sources}
-                    COMMAND ${shiboken_path}
-                    ${shiboken_options} ${wrapped_header} ${typesystem_file}
-                    DEPENDS ${generated_sources_dependencies}
-                    IMPLICIT_DEPENDS CXX ${wrapped_header}
-                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-                    COMMENT "Running generator for ${typesystem_file}.")
-
-
 # =============================== CMake target - bindings_library =============================
 
-
-# Set the cpp files which will be used for the bindings library.
-set(${bindings_library}_sources ${generated_sources})
-
-# Define and build the bindings library.
-add_library(${bindings_library} MODULE ${${bindings_library}_sources})
-
-# Apply relevant include and link flags.
-target_include_directories(${bindings_library} PRIVATE ${python_include_dir})
-target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir})
-target_include_directories(${bindings_library} PRIVATE ${CMAKE_SOURCE_DIR})
-
-target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries})
-target_link_libraries(${bindings_library} PRIVATE ${sample_library})
-
-# Adjust the name of generated module.
-set_property(TARGET ${bindings_library} PROPERTY PREFIX "")
-set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME
-             "${bindings_library}${PYTHON_EXTENSION_SUFFIX}")
-if(WIN32)
-    if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-        set_property(TARGET ${bindings_library} PROPERTY SUFFIX "_d.pyd")
-    else()
-        set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd")
-    endif()
-endif()
-
-# Make sure the linker doesn't complain about not finding Python symbols on macOS.
-if(APPLE)
-  set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
-endif(APPLE)
-
-# Find and link to the python import library only on Windows.
-# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker
-# (the symbols will be picked up in the Python executable).
-if (WIN32)
-    list(GET python_linking_data 0 python_libdir)
-    list(GET python_linking_data 1 python_lib)
-    find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir})
-    target_link_libraries(${bindings_library} PRIVATE ${python_link_flags})
-endif()
-
-
+# Create Python bindings using Shiboken6Tools macro
+shiboken_generator_create_binding(
+    EXTENSION_TARGET ${bindings_library}
+    GENERATED_SOURCES ${generated_sources}
+    HEADERS ${wrapped_header}
+    TYPESYSTEM_FILE ${typesystem_file}
+    LIBRARY_TARGET ${sample_library}
+    FORCE_LIMITED_API
+)
 # ================================= Dubious deployment section ================================
 
 set(windows_shiboken_shared_libraries)
@@ -224,13 +126,14 @@ if(WIN32)
     set_target_properties(${bindings_library}
                            PROPERTIES LINK_FLAGS "${python_additional_link_flags}")
 
-    # Compile a list of shiboken shared libraries to be installed, so that
-    # the user doesn't have to set the PATH manually to point to the PySide6 package.
-    foreach(library_path ${shiboken_shared_libraries})
-        string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path})
-        file(TO_CMAKE_PATH ${library_path} library_path)
-        list(APPEND windows_shiboken_shared_libraries "${library_path}")
-    endforeach()
+    # Get the correct DLL path for the current build type
+    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+        get_target_property(dll_path Shiboken6::libshiboken IMPORTED_LOCATION_DEBUG)
+    else()
+        get_target_property(dll_path Shiboken6::libshiboken IMPORTED_LOCATION_RELEASE)
+    endif()
+    file(TO_CMAKE_PATH "${dll_path}" dll_path)
+    set(windows_shiboken_shared_libraries "${dll_path}")
     # =========================================================================================
     # !!! End of dubious section.
     # =========================================================================================
@@ -250,3 +153,4 @@ install(FILES ${windows_shiboken_shared_libraries} DESTINATION ${CMAKE_CURRENT_S
 # =============================================================================================
 # !!! End of dubious section.
 # =============================================================================================
+
index 883c74c090309399fbedc9f9097f171a8bcb34f1..b0786355fcbf00f84871d3a33d24bafb96c8f774 100644 (file)
@@ -7,6 +7,5 @@
               "../main.py",
               "../truck.cpp",
               "../truck.h",
-              "../CMakeLists.txt",
-              "../../utils/pyside_config.py"]
+              "../CMakeLists.txt"]
 }
index 930405c5d2a3192735de0ea56e5c2d8348989003..42c4529ed19442eb39ca6378c77745cd24604af5 100644 (file)
@@ -156,8 +156,9 @@ For Windows you will also need:
   configuration is the same (all Release, which is more likely,
   or all Debug).
 
-The build uses the ``pyside_config.py`` file to configure the project
-using the current PySide/Shiboken installation.
+The build uses the ``Shiboken6``, ``Shiboken6Tools``, and ``PySide6``
+CMake packages to configure the project with the current PySide/Shiboken
+installation.
 
 Using CMake
 ===========
index fbfa00b981a41a62746f74c779ca8131107bdb92..18bb17c42135e22c3a4aa21c4c8f15e50983adcc 100644 (file)
@@ -65,13 +65,24 @@ pyside_config(--shiboken-generator-path SHIBOKEN_GENERATOR_PATH)
 pyside_config(--pyside-path PYSIDE_PATH)
 
 pyside_config(--python-include-path PYTHON_INCLUDE_DIR)
-pyside_config(--shiboken-generator-include-path SHIBOKEN_GENERATOR_INCLUDE_DIR 1)
+pyside_config(--shiboken-include-path SHIBOKEN_INCLUDE_DIR 1)
 pyside_config(--pyside-include-path PYSIDE_INCLUDE_DIR 1)
 
 pyside_config(--python-link-flags-cmake PYTHON_LINKING_DATA 0)
 pyside_config(--shiboken-module-shared-libraries-cmake SHIBOKEN_MODULE_SHARED_LIBRARIES 0)
 pyside_config(--pyside-shared-libraries-cmake PYSIDE_SHARED_LIBRARIES 0)
 
+# Print the computed variables
+message(STATUS "Shiboken module path: ${SHIBOKEN_MODULE_PATH}")
+message(STATUS "Shiboken generator path: ${SHIBOKEN_GENERATOR_PATH}")
+message(STATUS "PySide path: ${PYSIDE_PATH}")
+message(STATUS "Python include path: ${PYTHON_INCLUDE_DIR}")
+message(STATUS "Shiboken include path: ${SHIBOKEN_INCLUDE_DIR}")
+message(STATUS "PySide include path: ${PYSIDE_INCLUDE_DIR}")
+message(STATUS "Python linking data: ${PYTHON_LINKING_DATA}")
+message(STATUS "Shiboken module shared libraries: ${SHIBOKEN_MODULE_SHARED_LIBRARIES}")
+message(STATUS "PySide shared libraries: ${PYSIDE_SHARED_LIBRARIES}")
+
 set(SHIBOKEN_PATH "${SHIBOKEN_GENERATOR_PATH}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}")
 
 if(NOT EXISTS ${SHIBOKEN_PATH})
@@ -167,7 +178,7 @@ target_sources(${PROJECT_NAME} PUBLIC ${SOURCES})
 
 # Apply relevant include and link flags.
 target_include_directories(${PROJECT_NAME} PRIVATE ${PYTHON_INCLUDE_DIR})
-target_include_directories(${PROJECT_NAME} PRIVATE ${SHIBOKEN_GENERATOR_INCLUDE_DIR})
+target_include_directories(${PROJECT_NAME} PRIVATE ${SHIBOKEN_INCLUDE_DIR})
 target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE_INCLUDE_DIR})
 target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE_ADDITIONAL_INCLUDES})
 target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR})
index 8104bb167534ba010ab0568f40ab3bc8399ceaa1..f726353f06e582ff92f2cfb03eca3e9a4c551979 100644 (file)
@@ -69,9 +69,10 @@ State init()
     Py_Initialize();
     qAddPostRoutine(cleanup);
     state = PythonInitialized;
-    const bool pythonInitialized = PyInit_AppLib() != nullptr;
+    auto *appLibModule = PyImport_ImportModule("AppLib");
     const bool pyErrorOccurred = PyErr_Occurred() != nullptr;
-    if (pythonInitialized && !pyErrorOccurred) {
+    if (appLibModule != nullptr && !pyErrorOccurred) {
+        Py_DECREF(appLibModule);
         state = AppModuleLoaded;
     } else {
         if (pyErrorOccurred)
diff --git a/examples/tutorials/drumpad/final_project/.gitignore b/examples/tutorials/drumpad/final_project/.gitignore
new file mode 100644 (file)
index 0000000..855f31d
--- /dev/null
@@ -0,0 +1,11 @@
+__pycache__/
+.DS_Store
+build/
+deployment/
+pysidedeploy.spec
+resources.py
+*.autosave
+*.dist/
+Dependencies/
+*.qtds
+.qmlls.ini
diff --git a/examples/tutorials/drumpad/final_project/Drumpad.qmlproject b/examples/tutorials/drumpad/final_project/Drumpad.qmlproject
new file mode 100644 (file)
index 0000000..d3105e3
--- /dev/null
@@ -0,0 +1,69 @@
+// prop: json-converted
+// prop: auto-generated
+
+import QmlProject
+
+Project {
+    mainFile: "DrumpadContent/App.qml"
+    mainUiFile: "DrumpadContent/MainScreen.qml"
+    targetDirectory: "/opt/Drumpad"
+    enableCMakeGeneration: false
+    enablePythonGeneration: true
+    widgetApp: true
+    importPaths: [ "." ]
+    mockImports: [ "Mocks" ]
+
+    qdsVersion: "4.5"
+    quickVersion: "6.7"
+    qt6Project: true
+    qtForMCUs: false
+
+    multilanguageSupport: true
+    primaryLanguage: "en"
+    supportedLanguages: [ "en" ]
+
+    Environment {
+        QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1"
+        QT_AUTO_SCREEN_SCALE_FACTOR: "1"
+        QT_ENABLE_HIGHDPI_SCALING: "0"
+        QT_LOGGING_RULES: "qt.qml.connections=false"
+        QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
+    }
+
+    QmlFiles {
+        directory: "Drumpad"
+    }
+
+    QmlFiles {
+        directory: "DrumpadContent"
+    }
+
+    QmlFiles {
+        directory: "Generated"
+    }
+
+    Files {
+        directory: "Sounds"
+        filter: "*.mp3;*.wav"
+    }
+
+    QmlFiles {
+        directory: "Mocks/Audio"
+    }
+
+    Files {
+        files: [
+            "qtquickcontrols2.conf"
+        ]
+    }
+
+    Files {
+        directory: "Drumpad"
+        filter: "qmldir"
+    }
+
+    Files {
+        directory: "DrumpadContent"
+        filter: "*.ttf;*.otf"
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad.qrc b/examples/tutorials/drumpad/final_project/Drumpad.qrc
new file mode 100644 (file)
index 0000000..6786872
--- /dev/null
@@ -0,0 +1,23 @@
+<RCC>
+    <qresource>
+        <file>Drumpad.qmlproject</file>
+        <file>Drumpad/AvailableSoundsComboBox.qml</file>
+        <file>Drumpad/CenteredFlow.qml</file>
+        <file>Drumpad/Constants.qml</file>
+        <file>Drumpad/PadButton.qml</file>
+        <file>Drumpad/qmldir</file>
+        <file>Drumpad/SoundEffectPlayer.qml</file>
+        <file>Drumpad/StyledSpinBox.qml</file>
+        <file>Drumpad/VolumeSlider.qml</file>
+        <file>DrumpadContent/App.qml</file>
+        <file>DrumpadContent/MainScreen.qml</file>
+        <file>DrumpadContent/qmldir</file>
+        <file>qtquickcontrols2.conf</file>
+        <file>Sounds/Bongo Loop 125bpm.wav</file>
+        <file>Sounds/Clap.wav</file>
+        <file>Sounds/Closed Hat.wav</file>
+        <file>Sounds/Kick Drum.wav</file>
+        <file>Sounds/Open Hat.wav</file>
+        <file>Sounds/Sine Bass Ebm.wav</file>
+    </qresource>
+</RCC>
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/AvailableSoundsComboBox.qml b/examples/tutorials/drumpad/final_project/Drumpad/AvailableSoundsComboBox.qml
new file mode 100644 (file)
index 0000000..2a3330d
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+pragma ComponentBehavior: Bound
+import QtQuick
+import QtQuick.Controls
+import Audio
+
+ComboBox {
+    id: root
+
+    property string currentFile: currentText ? `Sounds/${currentText}` : ""
+    required property int initialIndex
+
+    model: audioFilesModel.getModel()
+
+    background: Rectangle {
+        border.color: root.pressed ? Constants.primaryColor : Constants.secondaryColor
+        border.width: root.visualFocus ? 3 : 2
+        color: root.pressed ? Constants.secondaryColor : "black"
+        implicitHeight: 30
+        radius: 2
+    }
+    contentItem: Text {
+        color: "white"
+        elide: Text.ElideRight
+        leftPadding: 10
+        rightPadding: root.indicator.width + 10
+        text: root.displayText
+        verticalAlignment: Text.AlignVCenter
+    }
+    delegate: ItemDelegate {
+        id: delegate
+
+        required property int index
+
+        highlighted: root.highlightedIndex === index
+
+        background: Rectangle {
+            color: delegate.highlighted ? Constants.darkGray : "black"
+            implicitWidth: delegate.contentItem.implicitWidth
+            width: popup.width
+        }
+        contentItem: Text {
+            anchors.fill: parent
+            color: delegate.highlighted ? "#ff0000" : "white"
+            elide: Text.ElideRight
+            leftPadding: 10
+            text: root.model[delegate.index]
+            verticalAlignment: Text.AlignVCenter
+        }
+    }
+    indicator: Canvas {
+        id: canvas
+
+        contextType: "2d"
+        height: 8
+        width: 12
+        x: root.width - canvas.width - root.rightPadding
+        y: root.topPadding + (root.availableHeight - canvas.height) / 2
+
+        onPaint: {
+            let margin = 2;
+            context.reset();
+            context.lineWidth = 2;
+            context.strokeStyle = "white";
+            context.lineCap = "round";
+            context.beginPath();
+            context.moveTo(margin, margin);
+            context.lineTo(width / 2, height - margin);
+            context.lineTo(width - margin, margin);
+            context.stroke();
+        }
+
+        Connections {
+            function onPressedChanged() {
+                canvas.requestPaint();
+            }
+
+            target: root
+        }
+    }
+    popup: Popup {
+        id: popup
+
+        implicitHeight: contentItem.implicitHeight
+        implicitWidth: 200
+        padding: 2
+        y: root.height + 2
+
+        background: Rectangle {
+            border.color: Constants.primaryColor
+            border.width: 2
+            color: "black"
+        }
+        contentItem: ListView {
+            clip: true
+            currentIndex: root.highlightedIndex
+            implicitHeight: Math.min(contentHeight, 200)
+            model: popup.visible ? root.delegateModel : null
+        }
+    }
+
+    Component.onCompleted: {
+        currentIndex = root.initialIndex % model.length;
+    }
+
+    AudioFilesModel {
+        id: audioFilesModel
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/CenteredFlow.qml b/examples/tutorials/drumpad/final_project/Drumpad/CenteredFlow.qml
new file mode 100644 (file)
index 0000000..44911c1
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+// A Flow layout that centers its children horizontally
+// Note that the implementation adds unnecessary spacing in rows that are not full
+Flow {
+    property int customMargin: (children.length && (children[0].width + spacing <= parentWidth))
+        ? (parentWidth - rowWidth) / 2 + padding
+        : padding
+    property int parentWidth: parent.width - 2 * padding
+    property int rowCount: children.length ? parentWidth / (children[0].width + spacing) : 0
+    property int rowWidth: children.length
+        ? rowCount * children[0].width + (rowCount - 1) * spacing + 2 * padding
+        : 0
+
+    anchors {
+        leftMargin: customMargin
+        rightMargin: customMargin
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/Constants.qml b/examples/tutorials/drumpad/final_project/Drumpad/Constants.qml
new file mode 100644 (file)
index 0000000..6afab9c
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+pragma Singleton
+import QtQuick
+
+QtObject {
+    readonly property string darkGray: "#333333"
+    readonly property string mediumGray: "#9B9B9B"
+    readonly property string primaryColor: "#FF0000"
+    readonly property string secondaryColor: "#8C0000"
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/PadButton.qml b/examples/tutorials/drumpad/final_project/Drumpad/PadButton.qml
new file mode 100644 (file)
index 0000000..b956426
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Shapes
+
+Rectangle {
+    id: root
+
+    property bool isPlaying: false
+    property bool isError: false
+    property bool isLoading: false
+    property int cornerRadius: 10
+    signal pressed()
+
+    color: "transparent"
+
+    Shape {
+        anchors.fill: parent
+
+        ShapePath {
+            strokeColor: "black"
+            strokeWidth: 2
+
+            fillGradient: RadialGradient {
+                centerRadius: root.height
+                centerX: root.width / 2
+                centerY: root.height / 2
+                focalX: centerX
+                focalY: centerY
+
+                GradientStop {
+                    position: 0
+                    color: {
+                        if (isError)
+                            return "black";
+                        if (isLoading)
+                            return "yellow";
+                        if (isPlaying)
+                            return Qt.darker(Constants.primaryColor, 1.25);
+                        return Qt.darker(Constants.secondaryColor, 1.25);
+                    }
+                }
+                GradientStop {
+                    position: 0.5
+                    color: {
+                        if (isError)
+                            return Constants.darkGray;
+                        if (isLoading)
+                            return "orange";
+                        if (isPlaying)
+                            return Constants.primaryColor;
+                        return Constants.secondaryColor;
+                    }
+                }
+            }
+
+            // Rounded shape path
+            PathMove {
+                x: root.cornerRadius
+                y: 0
+            }
+            PathQuad {
+                controlX: 0
+                controlY: 0
+                x: 0
+                y: root.cornerRadius
+            }
+            PathLine {
+                x: 0
+                y: root.height - root.cornerRadius
+            }
+            PathQuad {
+                controlX: 0
+                controlY: root.height
+                x: root.cornerRadius
+                y: root.height
+            }
+            PathLine {
+                x: root.width - root.cornerRadius
+                y: root.height
+            }
+            PathQuad {
+                controlX: root.width
+                controlY: root.height
+                x: root.width
+                y: root.height - root.cornerRadius
+            }
+            PathLine {
+                x: root.width
+                y: root.cornerRadius
+            }
+            PathQuad {
+                controlX: root.width
+                controlY: 0
+                x: root.width - root.cornerRadius
+                y: 0
+            }
+            PathLine {
+                x: root.cornerRadius
+                y: 0
+            }
+        }
+    }
+
+    MouseArea {
+        anchors.fill: parent
+        onClicked: root.pressed()
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/SoundEffectPlayer.qml b/examples/tutorials/drumpad/final_project/Drumpad/SoundEffectPlayer.qml
new file mode 100644 (file)
index 0000000..a50b330
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Dialogs
+import QtMultimedia
+
+import Drumpad
+import Audio
+
+Rectangle {
+    id: root
+
+    property string decodingError: ""
+    required property int index
+    property int status: SoundEffect.Null
+    property bool isLoading: status == SoundEffect.Loading
+    property bool isError: status == SoundEffect.Error || status == SoundEffect.Null
+    property bool isReady: status == SoundEffect.Ready
+
+    function play() {
+        if (root.status == SoundEffect.Ready) {
+            audioEngine.play();
+        }
+    }
+
+    color: Constants.darkGray
+    implicitHeight: layout.implicitHeight + 2 * layout.anchors.margins
+    implicitWidth: layout.implicitWidth + 2 * layout.anchors.margins
+    radius: 10
+
+    onDecodingErrorChanged: {
+        if (status == SoundEffect.Error && root.decodingError) {
+            errorMessageDialog.text = root.decodingError;
+            errorMessageDialog.open();
+        }
+    }
+
+    AudioEngine {
+        id: audioEngine
+
+        file: availableSoundsComboBox.currentFile
+        volume: volumeSlider.value
+
+        onDecodingStatusChanged: (status, error) => {
+            root.status = status;
+            if (status == SoundEffect.Error && error) {
+                root.decodingError = error;
+            } else {
+                root.decodingError = "";
+            }
+        }
+    }
+
+    MessageDialog {
+        id: errorMessageDialog
+
+        buttons: MessageDialog.Ok
+        title: "Error decoding file"
+    }
+
+    ColumnLayout {
+        id: layout
+
+        anchors.fill: parent
+        anchors.margins: 10
+        spacing: 10
+
+        RowLayout {
+            spacing: 10
+
+            Text {
+                Layout.alignment: Qt.AlignVCenter
+                Layout.fillWidth: true
+                color: "white"
+                text: `Player ${root.index + 1}`
+            }
+            AvailableSoundsComboBox {
+                id: availableSoundsComboBox
+
+                Layout.alignment: Qt.AlignCenter
+                initialIndex: root.index
+            }
+        }
+
+        WaveformItem {
+            id: waveformItem
+
+            file: audioEngine.file
+            height: 100
+            width: 300
+        }
+
+        Row {
+            Layout.alignment: Qt.AlignCenter
+            spacing: 10
+
+            PadButton {
+                id: padRectangle
+                height: 100
+                width: 100
+                isPlaying: audioEngine.isPlaying
+                isError: root.isError
+                isLoading: root.isLoading
+                onPressed: root.play()
+            }
+
+            VolumeSlider {
+                id: volumeSlider
+
+                height: padRectangle.height
+                value: 0.75
+                width: 16
+            }
+        }
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/StyledSpinBox.qml b/examples/tutorials/drumpad/final_project/Drumpad/StyledSpinBox.qml
new file mode 100644 (file)
index 0000000..de95412
--- /dev/null
@@ -0,0 +1,68 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+
+SpinBox {
+    id: root
+
+    property int innerPadding: 10
+
+    height: contentItem.implicitHeight + innerPadding
+    width: contentItem.width + up.indicator.implicitWidth + down.indicator.implicitWidth
+
+    background: Rectangle {
+        border.color: Constants.secondaryColor
+    }
+
+    contentItem: Text {
+        color: "black"
+        height: parent.height
+        horizontalAlignment: Text.AlignHCenter
+        text: root.textFromValue(root.value, root.locale)
+        verticalAlignment: Text.AlignVCenter
+        width: implicitWidth + innerPadding * 2
+    }
+
+    down.indicator: Rectangle {
+        border.color: Constants.secondaryColor
+        color: root.down.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
+        height: parent.height
+        implicitWidth: downText.implicitWidth + innerPadding * 2
+        x: root.mirrored ? parent.width - width : 0
+
+        Text {
+            id: downText
+
+            anchors.fill: parent
+            color: "white"
+            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
+            fontSizeMode: Text.Fit
+            horizontalAlignment: Text.AlignHCenter
+            text: "-"
+            verticalAlignment: Text.AlignVCenter
+        }
+    }
+
+    up.indicator: Rectangle {
+        border.color: Constants.secondaryColor
+        color: root.up.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
+        height: parent.height
+        implicitWidth: upText.implicitWidth + innerPadding * 2
+        x: root.mirrored ? 0 : parent.width - width
+
+        Text {
+            id: upText
+
+            anchors.centerIn: parent
+            anchors.fill: parent
+            color: "white"
+            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
+            fontSizeMode: Text.Fit
+            horizontalAlignment: Text.AlignHCenter
+            text: "+"
+            verticalAlignment: Text.AlignVCenter
+        }
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/VolumeSlider.qml b/examples/tutorials/drumpad/final_project/Drumpad/VolumeSlider.qml
new file mode 100644 (file)
index 0000000..1022912
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+
+Slider {
+    id: root
+
+    orientation: Qt.Vertical
+    padding: 0
+
+    background: Rectangle {
+        color: Constants.mediumGray
+        implicitHeight: root.height
+        implicitWidth: root.width
+        radius: width / 2
+
+        Rectangle {
+            anchors.bottom: parent.bottom
+            anchors.horizontalCenter: parent.horizontalCenter
+            color: Qt.lighter(Constants.primaryColor, 1 - (root.visualPosition * 0.3))
+            height: (1 - root.visualPosition) * parent.height + (root.visualPosition * handle.height)
+            radius: parent.width / 2
+            width: parent.width
+        }
+    }
+
+    handle: Rectangle {
+        border.color: "#b0b0b0"
+        border.width: 1
+        color: root.pressed ? "#e0e0e0" : "#ffffff"
+        height: root.width
+        radius: width / 2
+        width: root.width
+        x: root.availableWidth / 2 - height / 2
+        y: root.visualPosition * (root.availableHeight - height)
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Drumpad/qmldir b/examples/tutorials/drumpad/final_project/Drumpad/qmldir
new file mode 100644 (file)
index 0000000..7dba78c
--- /dev/null
@@ -0,0 +1,10 @@
+module Drumpad
+
+AvailableSoundsComboBox 1.0 AvailableSoundsComboBox.qml
+SoundEffectPlayer 1.0 SoundEffectPlayer.qml
+CenteredFlow 1.0 CenteredFlow.qml
+VolumeSlider 1.0 VolumeSlider.qml
+StyledSpinBox 1.0 StyledSpinBox.qml
+PadButton 1.0 PadButton.qml
+
+singleton Constants 1.0 Constants.qml
diff --git a/examples/tutorials/drumpad/final_project/DrumpadContent/App.qml b/examples/tutorials/drumpad/final_project/DrumpadContent/App.qml
new file mode 100644 (file)
index 0000000..773cbbf
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import Drumpad 1.0
+
+Window {
+    id: root
+
+    height: 800
+    title: "Drumpad"
+    visible: true
+    width: 1200
+
+    MainScreen {
+        id: mainScreen
+
+        anchors.fill: parent
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/DrumpadContent/MainScreen.qml b/examples/tutorials/drumpad/final_project/DrumpadContent/MainScreen.qml
new file mode 100644 (file)
index 0000000..fdbd7b6
--- /dev/null
@@ -0,0 +1,99 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Drumpad
+import Audio
+
+Rectangle {
+    id: root
+
+    property QtObject soundEffectPlayer: Qt.createComponent("../Drumpad/SoundEffectPlayer.qml",
+                                                            Component.PreferSynchronous)
+
+    color: "black"
+    focus: true
+
+    Component.onCompleted: {
+        // Initialize the default sound effect players
+        for (var i = 0; i < audioPlayersSpinBox.value; i++) {
+            root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
+                index: i
+            });
+        }
+    }
+    Keys.onPressed: event => {
+        if (event.key < Qt.Key_1 || event.key > Qt.Key_9) {
+            // Ignore key out of scope
+            return;
+        }
+
+        let digit = event.key - Qt.Key_1;
+        if (digit < soundEffectPlayersFlow.children.length) {
+            soundEffectPlayersFlow.children[digit].play();
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+        anchors.margins: 10
+
+        Row {
+            id: audioPlayersCountRow
+
+            Layout.alignment: Qt.AlignHCenter
+            spacing: 5
+
+            Text {
+                anchors.verticalCenter: parent.verticalCenter
+                color: "white"
+                text: "Audio players:"
+            }
+
+            StyledSpinBox {
+                id: audioPlayersSpinBox
+
+                value: 5
+
+                onValueModified: {
+                    let soundPlayersCount = soundEffectPlayersFlow.children.length;
+                    if (audioPlayersSpinBox.value < soundPlayersCount) {
+                        // Remove extra sound effect players
+                        soundEffectPlayersFlow.children.length = audioPlayersSpinBox.value;
+                        return;
+                    }
+
+                    if (audioPlayersSpinBox.value < soundPlayersCount) {
+                        return;
+                    }
+                    // Create more sound effect players
+                    for (var i = soundPlayersCount; i < audioPlayersSpinBox.value; i++) {
+                        root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
+                            index: i
+                        });
+                    }
+                }
+            }
+        }
+
+        ScrollView {
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+            contentWidth: width
+
+            background: Rectangle {
+                color: "#232323"
+            }
+
+            CenteredFlow {
+                id: soundEffectPlayersFlow
+
+                anchors.fill: parent
+                padding: 10
+                spacing: 10
+            }
+        }
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/DrumpadContent/qmldir b/examples/tutorials/drumpad/final_project/DrumpadContent/qmldir
new file mode 100644 (file)
index 0000000..f1f34c5
--- /dev/null
@@ -0,0 +1,4 @@
+module DrumpadContent
+
+App 1.0 App.qml
+MainScreen 1.0 MainScreen.qml
diff --git a/examples/tutorials/drumpad/final_project/Mocks/Audio/AudioEngine.qml b/examples/tutorials/drumpad/final_project/Mocks/Audio/AudioEngine.qml
new file mode 100644 (file)
index 0000000..4bfbc24
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtMultimedia
+
+Item {
+    id: root
+
+    property double volume
+    property url file
+
+    MediaPlayer {
+        id: player
+        source: file
+        audioOutput: AudioOutput {}
+    }
+
+    onVolumeChanged : {
+        console.log("Mock: VolumeChanaged ", volume )
+    }
+
+    function play() {
+        console.log("Mock: play()")
+        player.play()
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Mocks/Audio/WaveformItem.qml b/examples/tutorials/drumpad/final_project/Mocks/Audio/WaveformItem.qml
new file mode 100644 (file)
index 0000000..fcb6041
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+Rectangle {
+    id: root
+    width: 1920
+    height: 1080
+    color: "blue"
+    property url file
+}
diff --git a/examples/tutorials/drumpad/final_project/Mocks/Audio/qmldir b/examples/tutorials/drumpad/final_project/Mocks/Audio/qmldir
new file mode 100644 (file)
index 0000000..189d68e
--- /dev/null
@@ -0,0 +1,3 @@
+module Audio
+AudioEngine 1.0 AudioEngine.qml
+WaveformItem 1.0 WaveformItem.qml
diff --git a/examples/tutorials/drumpad/final_project/Mocks/Components/AudioFilesModel.qml b/examples/tutorials/drumpad/final_project/Mocks/Components/AudioFilesModel.qml
new file mode 100644 (file)
index 0000000..7e4ff22
--- /dev/null
@@ -0,0 +1,8 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+Item {
+    getFiles: function() {
+        console.log("AudioFilesModel mock: getFiles()")
+    }
+}
diff --git a/examples/tutorials/drumpad/final_project/Mocks/Components/qmldir b/examples/tutorials/drumpad/final_project/Mocks/Components/qmldir
new file mode 100644 (file)
index 0000000..2d8e989
--- /dev/null
@@ -0,0 +1,2 @@
+module Components
+AudioFilesModel 1.0 AudioFilesModel.qml
diff --git a/examples/tutorials/drumpad/final_project/Python/audio/__init__.py b/examples/tutorials/drumpad/final_project/Python/audio/__init__.py
new file mode 100644 (file)
index 0000000..817bc3e
--- /dev/null
@@ -0,0 +1,6 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from .audio_engine import AudioEngine
+from .waveform_item import WaveformItem
+from .audio_files_model import AudioFilesModel
diff --git a/examples/tutorials/drumpad/final_project/Python/audio/audio_engine.py b/examples/tutorials/drumpad/final_project/Python/audio/audio_engine.py
new file mode 100644 (file)
index 0000000..daf2b35
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from PySide6.QtQml import QmlElement
+from PySide6.QtCore import QObject, Slot, Property, Signal, QUrl
+from PySide6.QtMultimedia import QSoundEffect
+
+from autogen.settings import project_root
+
+QML_IMPORT_NAME = "Audio"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class AudioEngine(QObject):
+    volumeChanged = Signal()
+    fileChanged = Signal()
+    isPlayingChanged = Signal()
+    decodingStatusChanged = Signal(QSoundEffect.Status, str)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._sound_effect = QSoundEffect()
+        self._sound_effect.playingChanged.connect(self.isPlayingChanged.emit)  #
+        self._sound_effect.statusChanged.connect(self.reportStatus)
+
+    def reportStatus(self):
+        if self._sound_effect.status() == QSoundEffect.Status.Error:
+            self.decodingStatusChanged.emit(
+                QSoundEffect.Status.Error,
+                f"Error decoding file: {self._sound_effect.source().path()}",
+            )
+        else:
+            self.decodingStatusChanged.emit(self._sound_effect.status(), "")
+
+    @Slot(result=None)
+    def play(self):
+        self._sound_effect.play()
+
+    def volume(self):
+        return self._sound_effect.volume()
+
+    def setVolume(self, value):
+        self._sound_effect.setVolume(value)
+        self.volumeChanged.emit()
+
+    def file(self):
+        return self._sound_effect.source()
+
+    def setFile(self, value: QUrl):
+        if self._sound_effect.source() == value or value.isEmpty():
+            return
+
+        if "__compiled__" in globals():
+            self._sound_effect.setSource(f"qrc:/{value.toString()}")
+        else:
+            self._sound_effect.setSource(f"file:{project_root / value.toString()}")
+        self.fileChanged.emit()
+
+    def isPlaying(self):
+        return self._sound_effect.isPlaying()
+
+    volume = Property(float, volume, setVolume, notify=volumeChanged)
+    file = Property(QUrl, file, setFile, notify=fileChanged)
+    isPlaying = Property(bool, isPlaying, notify=isPlayingChanged)
diff --git a/examples/tutorials/drumpad/final_project/Python/audio/audio_files_model.py b/examples/tutorials/drumpad/final_project/Python/audio/audio_files_model.py
new file mode 100644 (file)
index 0000000..b92bc24
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from pathlib import Path
+
+from PySide6.QtCore import QObject, Slot, QDirIterator
+from PySide6.QtQml import QmlElement
+
+from autogen.settings import project_root
+
+
+QML_IMPORT_NAME = "Audio"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class AudioFilesModel(QObject):
+    @Slot(result=list)
+    def getModel(self):
+        if "__compiled__" in globals():
+            resource_prefix = ":/Sounds/"
+            iterator = QDirIterator(resource_prefix, QDirIterator.Subdirectories)
+            audio_files = []
+            while iterator.hasNext():
+                resource = iterator.next()
+                audio_files.append(resource.split(resource_prefix)[-1])
+            return audio_files
+
+        return list(p.name for p in Path(project_root / "Sounds").glob("*.wav"))
diff --git a/examples/tutorials/drumpad/final_project/Python/audio/waveform_item.py b/examples/tutorials/drumpad/final_project/Python/audio/waveform_item.py
new file mode 100644 (file)
index 0000000..d3ce0f4
--- /dev/null
@@ -0,0 +1,113 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import struct
+
+from PySide6.QtCore import Qt, Property, QUrl, Signal, QFile, QPointF
+from PySide6.QtGui import QPen, QPainter
+from PySide6.QtMultimedia import QAudioFormat, QAudioDecoder
+from PySide6.QtQml import QmlElement
+from PySide6.QtQuick import QQuickPaintedItem
+
+QML_IMPORT_NAME = "Audio"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class WaveformItem(QQuickPaintedItem):
+
+    fileChanged = Signal()
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._waveformData = []
+        self._background_color = Qt.black
+
+        audio_format = QAudioFormat()
+        audio_format.setChannelCount(1)
+        audio_format.setSampleRate(44100)
+        audio_format.setSampleFormat(QAudioFormat.Float)
+
+        self._file_url: QUrl | None = None
+        self._audio_file: QFile | None = None
+
+        self._decoder = QAudioDecoder()
+        self._decoder.setAudioFormat(audio_format)
+
+        self._decoder.bufferReady.connect(self.onBufferReady)
+        self._decoder.finished.connect(self.decoderFinished)
+
+    def file(self) -> QUrl | None:
+        return self._file_url
+
+    def setFile(self, value: QUrl):
+        if self._decoder.source() == value:
+            return
+
+        if self._audio_file and self._audio_file.isOpen():
+            self._audio_file.close()
+
+        self._waveformData = []
+        self._decoder.stop()
+
+        self._file_url = value
+        if "__compiled__" in globals():
+            path = self._file_url.toString().replace("qrc:/", ":/")
+        else:
+            path = self._file_url.path()
+        self._audio_file = QFile(path)
+        self._audio_file.open(QFile.ReadOnly)
+        self._decoder.setSourceDevice(self._audio_file)
+        self._decoder.start()
+        self.fileChanged.emit()
+
+    def paint(self, painter):
+        # Fill the bounding rectangle with the specified color
+        painter.fillRect(self.boundingRect(), self._background_color)
+
+        # If no waveform data is available, draw the text
+        if not self._waveformData:
+            painter.setPen(Qt.white)
+            painter.drawText(self.boundingRect(), Qt.AlignCenter, "Waveform not available")
+            return
+
+        painter.setRenderHint(QPainter.Antialiasing)
+
+        # Set the pen for drawing the waveform
+        pen = QPen(Qt.blue)
+        pen.setWidth(1)
+        painter.setPen(pen)
+
+        # Get container dimensions
+        rect = self.boundingRect()
+        data_size = len(self._waveformData)
+
+        # Calculate step size and center line
+        x_step = rect.width() / data_size
+        center_y = rect.height() / 2.0
+
+        # Draw the waveform as connected lines
+        for i in range(1, data_size):
+            x1 = (i - 1) * x_step
+            y1 = center_y - self._waveformData[i - 1] * center_y
+            x2 = i * x_step
+            y2 = center_y - self._waveformData[i] * center_y
+            painter.drawLine(QPointF(x1, y1), QPointF(x2, y2))
+
+    @staticmethod
+    def float_buffer_to_list(data):
+        # Calculate the number of 32-bit floats in the buffer
+        float_count = len(data) // 4  # Each float32 is 4 bytes
+        # Unpack the binary data into a list of floats
+        return list(struct.unpack(f"{float_count}f", data))
+
+    def onBufferReady(self):
+        buffer = self._decoder.read()
+        data = buffer.constData()
+        self._waveformData.extend(self.float_buffer_to_list(data))
+        self.update()
+
+    file: QUrl = Property(QUrl, file, setFile, notify=fileChanged)
+
+    def decoderFinished(self):
+        self._audio_file.close()
diff --git a/examples/tutorials/drumpad/final_project/Python/autogen/settings.py b/examples/tutorials/drumpad/final_project/Python/autogen/settings.py
new file mode 100644 (file)
index 0000000..39386a2
--- /dev/null
@@ -0,0 +1,39 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+# This file is automatically generated by Qt Design Studio.
+import os
+import sys
+from pathlib import Path
+
+from PySide6.QtQml import QQmlApplicationEngine
+
+project_root = Path(__file__).parent.parent.parent
+
+
+def setup_qt_environment(qml_engine: QQmlApplicationEngine):
+    """
+    Load the QML application. Import the compiled resources when the application is deployed.
+    """
+    qml_app_url = "DrumpadContent/App.qml"
+
+    if "__compiled__" in globals():
+        # Application has been deployed using pyside6-deploy
+        try:
+            import autogen.resources  # noqa: F401
+        except ImportError:
+            resource_file = Path(__file__).parent / "resources.py"
+            print(
+                f"Error: No compiled resources found in {resource_file.absolute()}\n"
+                f"Please compile the resources using pyside6-rcc or pyside6-project build",
+                file=sys.stderr,
+            )
+            sys.exit(1)
+
+        qml_engine.addImportPath(":/")
+        qml_engine.load(f":/{qml_app_url}")
+        return
+
+    qml_engine.addImportPath(str(project_root.absolute()))
+    os.environ["QT_QUICK_CONTROLS_CONF"] = str(project_root / "qtquickcontrols2.conf")
+    qml_engine.load(str(project_root / qml_app_url))
diff --git a/examples/tutorials/drumpad/final_project/Python/main.py b/examples/tutorials/drumpad/final_project/Python/main.py
new file mode 100644 (file)
index 0000000..166f251
--- /dev/null
@@ -0,0 +1,28 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import sys
+
+from PySide6.QtGui import QGuiApplication
+from PySide6.QtQml import QQmlApplicationEngine
+
+from autogen.settings import setup_qt_environment
+from audio import *  # noqa: F401,F403
+
+
+def main():
+    app = QGuiApplication(sys.argv)
+    engine = QQmlApplicationEngine()
+
+    setup_qt_environment(engine)
+
+    if not engine.rootObjects():
+        sys.exit(-1)
+
+    ex = app.exec()
+    del engine
+    return ex
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/examples/tutorials/drumpad/final_project/Python/pyproject.toml b/examples/tutorials/drumpad/final_project/Python/pyproject.toml
new file mode 100644 (file)
index 0000000..fcb5bbb
--- /dev/null
@@ -0,0 +1,5 @@
+[project]
+name = "Drumpad"
+
+[tool.pyside6-project]
+files = ["main.py", "autogen/settings.py", "audio/audio_files_model.py", "audio/audio_engine.py", "audio/waveform_item.py", "../Drumpad.qmlproject", "../Drumpad.qrc", "../qtquickcontrols2.conf", "../Drumpad/AvailableSoundsComboBox.qml", "../Drumpad/CenteredFlow.qml", "../Drumpad/Constants.qml", "../Drumpad/PadButton.qml", "../Drumpad/qmldir", "../Drumpad/SoundEffectPlayer.qml", "../Drumpad/StyledSpinBox.qml", "../Drumpad/VolumeSlider.qml", "../DrumpadContent/App.qml", "../DrumpadContent/MainScreen.qml", "../DrumpadContent/qmldir", "../Mocks/Audio/AudioEngine.qml", "../Mocks/Audio/qmldir", "../Mocks/Audio/WaveformItem.qml", "../Mocks/Components/AudioFilesModel.qml", "../Mocks/Components/qmldir"]
diff --git a/examples/tutorials/drumpad/final_project/Sounds/Bongo Loop 125bpm.wav b/examples/tutorials/drumpad/final_project/Sounds/Bongo Loop 125bpm.wav
new file mode 100644 (file)
index 0000000..b90bc45
Binary files /dev/null and b/examples/tutorials/drumpad/final_project/Sounds/Bongo Loop 125bpm.wav differ
diff --git a/examples/tutorials/drumpad/final_project/Sounds/Clap.wav b/examples/tutorials/drumpad/final_project/Sounds/Clap.wav
new file mode 100644 (file)
index 0000000..aceee33
Binary files /dev/null and b/examples/tutorials/drumpad/final_project/Sounds/Clap.wav differ
diff --git a/examples/tutorials/drumpad/final_project/Sounds/Closed Hat.wav b/examples/tutorials/drumpad/final_project/Sounds/Closed Hat.wav
new file mode 100644 (file)
index 0000000..d062e72
Binary files /dev/null and b/examples/tutorials/drumpad/final_project/Sounds/Closed Hat.wav differ
diff --git a/examples/tutorials/drumpad/final_project/Sounds/Kick Drum.wav b/examples/tutorials/drumpad/final_project/Sounds/Kick Drum.wav
new file mode 100644 (file)
index 0000000..e283371
Binary files /dev/null and b/examples/tutorials/drumpad/final_project/Sounds/Kick Drum.wav differ
diff --git a/examples/tutorials/drumpad/final_project/Sounds/Open Hat.wav b/examples/tutorials/drumpad/final_project/Sounds/Open Hat.wav
new file mode 100644 (file)
index 0000000..e6fcb13
Binary files /dev/null and b/examples/tutorials/drumpad/final_project/Sounds/Open Hat.wav differ
diff --git a/examples/tutorials/drumpad/final_project/Sounds/Sine Bass Ebm.wav b/examples/tutorials/drumpad/final_project/Sounds/Sine Bass Ebm.wav
new file mode 100644 (file)
index 0000000..5925d0f
Binary files /dev/null and b/examples/tutorials/drumpad/final_project/Sounds/Sine Bass Ebm.wav differ
diff --git a/examples/tutorials/drumpad/final_project/doc/final_project.md b/examples/tutorials/drumpad/final_project/doc/final_project.md
new file mode 100644 (file)
index 0000000..877bc01
--- /dev/null
@@ -0,0 +1,12 @@
+# Drumpad example (Qt Design Studio) - Final project
+
+This example contains the final [Qt Design Studio] project of the [Qt Design Studio integration tutorial].
+It contains all the necessary files to execute the project, including the Python code developed
+along the tutorial.
+
+For more details, see the [Qt Design Studio integration tutorial].
+
+To download the initial project source code, visit {ref}`example_tutorials_drumpad_initial_project`.
+
+[Qt Design Studio]: https://www.qt.io/product/ui-design-tools/
+[Qt Design Studio integration tutorial]: tutorial_qt_design_studio_integration
diff --git a/examples/tutorials/drumpad/final_project/qtquickcontrols2.conf b/examples/tutorials/drumpad/final_project/qtquickcontrols2.conf
new file mode 100644 (file)
index 0000000..87a95d0
--- /dev/null
@@ -0,0 +1,6 @@
+; This file can be edited to change the style of the application
+; Read "Qt Quick Controls 2 Configuration File" for details:
+; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html
+
+[Controls]
+Style=Basic
diff --git a/examples/tutorials/drumpad/initial_project/.gitignore b/examples/tutorials/drumpad/initial_project/.gitignore
new file mode 100644 (file)
index 0000000..855f31d
--- /dev/null
@@ -0,0 +1,11 @@
+__pycache__/
+.DS_Store
+build/
+deployment/
+pysidedeploy.spec
+resources.py
+*.autosave
+*.dist/
+Dependencies/
+*.qtds
+.qmlls.ini
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad.qmlproject b/examples/tutorials/drumpad/initial_project/Drumpad.qmlproject
new file mode 100644 (file)
index 0000000..b92c65c
--- /dev/null
@@ -0,0 +1,69 @@
+// prop: json-converted
+// prop: auto-generated
+
+import QmlProject
+
+Project {
+    mainFile: "DrumpadContent/App.qml"
+    mainUiFile: "DrumpadContent/MainScreen.qml"
+    targetDirectory: "/opt/Drumpad"
+    enableCMakeGeneration: false
+    enablePythonGeneration: false
+    widgetApp: true
+    importPaths: [ "." ]
+    mockImports: [ "Mocks" ]
+
+    qdsVersion: "4.5"
+    quickVersion: "6.7"
+    qt6Project: true
+    qtForMCUs: false
+
+    multilanguageSupport: true
+    primaryLanguage: "en"
+    supportedLanguages: [ "en" ]
+
+    Environment {
+        QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1"
+        QT_AUTO_SCREEN_SCALE_FACTOR: "1"
+        QT_ENABLE_HIGHDPI_SCALING: "0"
+        QT_LOGGING_RULES: "qt.qml.connections=false"
+        QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
+    }
+
+    QmlFiles {
+        directory: "Drumpad"
+    }
+
+    QmlFiles {
+        directory: "DrumpadContent"
+    }
+
+    QmlFiles {
+        directory: "Generated"
+    }
+
+    Files {
+        directory: "Sounds"
+        filter: "*.mp3;*.wav"
+    }
+
+    QmlFiles {
+        directory: "Mocks/Audio"
+    }
+
+    Files {
+        files: [
+            "qtquickcontrols2.conf"
+        ]
+    }
+
+    Files {
+        directory: "Drumpad"
+        filter: "qmldir"
+    }
+
+    Files {
+        directory: "DrumpadContent"
+        filter: "*.ttf;*.otf"
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad.qrc b/examples/tutorials/drumpad/initial_project/Drumpad.qrc
new file mode 100644 (file)
index 0000000..7415635
--- /dev/null
@@ -0,0 +1,18 @@
+<RCC>
+    <qresource>
+        <file>Drumpad.qmlproject</file>
+        <file>Drumpad/AvailableSoundsComboBox.qml</file>
+        <file>Drumpad/CenteredFlow.qml</file>
+        <file>Drumpad/Constants.qml</file>
+        <file>Drumpad/PadButton.qml</file>
+        <file>Drumpad/qmldir</file>
+        <file>Drumpad/SoundEffectPlayer.qml</file>
+        <file>Drumpad/StyledSpinBox.qml</file>
+        <file>Drumpad/VolumeSlider.qml</file>
+        <file>DrumpadContent/App.qml</file>
+        <file>DrumpadContent/MainScreen.qml</file>
+        <file>DrumpadContent/qmldir</file>
+        <file>qtquickcontrols2.conf</file>
+        <file>Sounds/Clap.wav</file>
+    </qresource>
+</RCC>
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/AvailableSoundsComboBox.qml b/examples/tutorials/drumpad/initial_project/Drumpad/AvailableSoundsComboBox.qml
new file mode 100644 (file)
index 0000000..e105e22
--- /dev/null
@@ -0,0 +1,111 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma ComponentBehavior: Bound
+import QtQuick
+import QtQuick.Controls
+import Audio
+
+ComboBox {
+    id: root
+
+    property string currentFile: currentText ? `../Sounds/${currentText}` : ""
+    required property int initialIndex
+
+    model: audioFilesModel.getModel()
+
+    background: Rectangle {
+        border.color: root.pressed ? Constants.primaryColor : Constants.secondaryColor
+        border.width: root.visualFocus ? 3 : 2
+        color: root.pressed ? Constants.secondaryColor : "black"
+        implicitHeight: 30
+        radius: 2
+    }
+    contentItem: Text {
+        color: "white"
+        elide: Text.ElideRight
+        leftPadding: 10
+        rightPadding: root.indicator.width + 10
+        text: root.displayText
+        verticalAlignment: Text.AlignVCenter
+    }
+    delegate: ItemDelegate {
+        id: delegate
+
+        required property int index
+
+        highlighted: root.highlightedIndex === index
+
+        background: Rectangle {
+            color: delegate.highlighted ? Constants.darkGray : "black"
+            implicitWidth: delegate.contentItem.implicitWidth
+            width: popup.width
+        }
+        contentItem: Text {
+            anchors.fill: parent
+            color: delegate.highlighted ? "#ff0000" : "white"
+            elide: Text.ElideRight
+            leftPadding: 10
+            text: root.model[delegate.index]
+            verticalAlignment: Text.AlignVCenter
+        }
+    }
+    indicator: Canvas {
+        id: canvas
+
+        contextType: "2d"
+        height: 8
+        width: 12
+        x: root.width - canvas.width - root.rightPadding
+        y: root.topPadding + (root.availableHeight - canvas.height) / 2
+
+        onPaint: {
+            let margin = 2;
+            context.reset();
+            context.lineWidth = 2;
+            context.strokeStyle = "white";
+            context.lineCap = "round";
+            context.beginPath();
+            context.moveTo(margin, margin);
+            context.lineTo(width / 2, height - margin);
+            context.lineTo(width - margin, margin);
+            context.stroke();
+        }
+
+        Connections {
+            function onPressedChanged() {
+                canvas.requestPaint();
+            }
+
+            target: root
+        }
+    }
+    popup: Popup {
+        id: popup
+
+        implicitHeight: contentItem.implicitHeight
+        implicitWidth: 200
+        padding: 2
+        y: root.height + 2
+
+        background: Rectangle {
+            border.color: Constants.primaryColor
+            border.width: 2
+            color: "black"
+        }
+        contentItem: ListView {
+            clip: true
+            currentIndex: root.highlightedIndex
+            implicitHeight: Math.min(contentHeight, 200)
+            model: popup.visible ? root.delegateModel : null
+        }
+    }
+
+    Component.onCompleted: {
+        currentIndex = root.initialIndex % model.length;
+    }
+
+    AudioFilesModel {
+        id: audioFilesModel
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/CenteredFlow.qml b/examples/tutorials/drumpad/initial_project/Drumpad/CenteredFlow.qml
new file mode 100644 (file)
index 0000000..a5e9fe2
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+// A Flow layout that centers its children horizontally
+// Note that the implementation adds unnecessary spacing in rows that are not full
+Flow {
+    property int customMargin: (children.length && (children[0].width + spacing <= parentWidth))
+        ? (parentWidth - rowWidth) / 2 + padding
+        : padding
+    property int parentWidth: parent.width - 2 * padding
+    property int rowCount: children.length ? parentWidth / (children[0].width + spacing) : 0
+    property int rowWidth: children.length
+        ? rowCount * children[0].width + (rowCount - 1) * spacing + 2 * padding
+        : 0
+
+    anchors {
+        leftMargin: customMargin
+        rightMargin: customMargin
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/Constants.qml b/examples/tutorials/drumpad/initial_project/Drumpad/Constants.qml
new file mode 100644 (file)
index 0000000..7305822
--- /dev/null
@@ -0,0 +1,12 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+pragma Singleton
+import QtQuick
+
+QtObject {
+    readonly property string darkGray: "#333333"
+    readonly property string mediumGray: "#9B9B9B"
+    readonly property string primaryColor: "#FF0000"
+    readonly property string secondaryColor: "#8C0000"
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/PadButton.qml b/examples/tutorials/drumpad/initial_project/Drumpad/PadButton.qml
new file mode 100644 (file)
index 0000000..e00d77d
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Shapes
+
+Rectangle {
+    id: root
+
+    property bool isPlaying: false
+    property bool isError: false
+    property bool isLoading: false
+    property int cornerRadius: 10
+    signal pressed()
+
+    color: "transparent"
+
+    Shape {
+        anchors.fill: parent
+
+        ShapePath {
+            strokeColor: "black"
+            strokeWidth: 2
+
+            fillGradient: RadialGradient {
+                centerRadius: root.height
+                centerX: root.width / 2
+                centerY: root.height / 2
+                focalX: centerX
+                focalY: centerY
+
+                GradientStop {
+                    position: 0
+                    color: {
+                        if (isError)
+                            return "black";
+                        if (isLoading)
+                            return "yellow";
+                        if (isPlaying)
+                            return Qt.darker(Constants.primaryColor, 1.25);
+                        return Qt.darker(Constants.secondaryColor, 1.25);
+                    }
+                }
+                GradientStop {
+                    position: 0.5
+                    color: {
+                        if (isError)
+                            return Constants.darkGray;
+                        if (isLoading)
+                            return "orange";
+                        if (isPlaying)
+                            return Constants.primaryColor;
+                        return Constants.secondaryColor;
+                    }
+                }
+            }
+
+            // Rounded shape path
+            PathMove {
+                x: root.cornerRadius
+                y: 0
+            }
+            PathQuad {
+                controlX: 0
+                controlY: 0
+                x: 0
+                y: root.cornerRadius
+            }
+            PathLine {
+                x: 0
+                y: root.height - root.cornerRadius
+            }
+            PathQuad {
+                controlX: 0
+                controlY: root.height
+                x: root.cornerRadius
+                y: root.height
+            }
+            PathLine {
+                x: root.width - root.cornerRadius
+                y: root.height
+            }
+            PathQuad {
+                controlX: root.width
+                controlY: root.height
+                x: root.width
+                y: root.height - root.cornerRadius
+            }
+            PathLine {
+                x: root.width
+                y: root.cornerRadius
+            }
+            PathQuad {
+                controlX: root.width
+                controlY: 0
+                x: root.width - root.cornerRadius
+                y: 0
+            }
+            PathLine {
+                x: root.cornerRadius
+                y: 0
+            }
+        }
+    }
+
+    MouseArea {
+        anchors.fill: parent
+        onClicked: root.pressed()
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/SoundEffectPlayer.qml b/examples/tutorials/drumpad/initial_project/Drumpad/SoundEffectPlayer.qml
new file mode 100644 (file)
index 0000000..7232b96
--- /dev/null
@@ -0,0 +1,118 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Dialogs
+import QtMultimedia
+
+import Drumpad
+import Audio
+
+Rectangle {
+    id: root
+
+    property string decodingError: ""
+    required property int index
+    property int status: SoundEffect.Null
+    property bool isLoading: status == SoundEffect.Loading
+    property bool isError: status == SoundEffect.Error || status == SoundEffect.Null
+    property bool isReady: status == SoundEffect.Ready
+
+    function play() {
+        if (root.status == SoundEffect.Ready) {
+            audioEngine.play();
+        }
+    }
+
+    color: Constants.darkGray
+    implicitHeight: layout.implicitHeight + 2 * layout.anchors.margins
+    implicitWidth: layout.implicitWidth + 2 * layout.anchors.margins
+    radius: 10
+
+    onDecodingErrorChanged: {
+        if (status == SoundEffect.Error && root.decodingError) {
+            errorMessageDialog.text = root.decodingError;
+            errorMessageDialog.open();
+        }
+    }
+
+    AudioEngine {
+        id: audioEngine
+
+        file: availableSoundsComboBox.currentFile
+        volume: volumeSlider.value
+
+        onDecodingStatusChanged: (status, error) => {
+            root.status = status;
+            if (status == SoundEffect.Error && error) {
+                root.decodingError = error;
+            } else {
+                root.decodingError = "";
+            }
+        }
+    }
+
+    MessageDialog {
+        id: errorMessageDialog
+
+        buttons: MessageDialog.Ok
+        title: "Error decoding file"
+    }
+
+    ColumnLayout {
+        id: layout
+
+        anchors.fill: parent
+        anchors.margins: 10
+        spacing: 10
+
+        RowLayout {
+            spacing: 10
+
+            Text {
+                Layout.alignment: Qt.AlignVCenter
+                Layout.fillWidth: true
+                color: "white"
+                text: `Player ${root.index + 1}`
+            }
+            AvailableSoundsComboBox {
+                id: availableSoundsComboBox
+
+                Layout.alignment: Qt.AlignCenter
+                initialIndex: root.index
+            }
+        }
+
+        WaveformItem {
+            id: waveformItem
+
+            file: audioEngine.file
+            height: 100
+            width: 300
+        }
+
+        Row {
+            Layout.alignment: Qt.AlignCenter
+            spacing: 10
+
+            PadButton {
+                id: padRectangle
+                height: 100
+                width: 100
+                isPlaying: audioEngine.isPlaying
+                isError: root.isError
+                isLoading: root.isLoading
+                onPressed: root.play()
+            }
+
+            VolumeSlider {
+                id: volumeSlider
+
+                height: padRectangle.height
+                value: 0.75
+                width: 16
+            }
+        }
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/StyledSpinBox.qml b/examples/tutorials/drumpad/initial_project/Drumpad/StyledSpinBox.qml
new file mode 100644 (file)
index 0000000..c403be0
--- /dev/null
@@ -0,0 +1,68 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+SpinBox {
+    id: root
+
+    property int innerPadding: 10
+
+    height: contentItem.implicitHeight + innerPadding
+    width: contentItem.width + up.indicator.implicitWidth + down.indicator.implicitWidth
+
+    background: Rectangle {
+        border.color: Constants.secondaryColor
+    }
+
+    contentItem: Text {
+        color: "black"
+        height: parent.height
+        horizontalAlignment: Text.AlignHCenter
+        text: root.textFromValue(root.value, root.locale)
+        verticalAlignment: Text.AlignVCenter
+        width: implicitWidth + innerPadding * 2
+    }
+
+    down.indicator: Rectangle {
+        border.color: Constants.secondaryColor
+        color: root.down.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
+        height: parent.height
+        implicitWidth: downText.implicitWidth + innerPadding * 2
+        x: root.mirrored ? parent.width - width : 0
+
+        Text {
+            id: downText
+
+            anchors.fill: parent
+            color: "white"
+            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
+            fontSizeMode: Text.Fit
+            horizontalAlignment: Text.AlignHCenter
+            text: "-"
+            verticalAlignment: Text.AlignVCenter
+        }
+    }
+
+    up.indicator: Rectangle {
+        border.color: Constants.secondaryColor
+        color: root.up.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
+        height: parent.height
+        implicitWidth: upText.implicitWidth + innerPadding * 2
+        x: root.mirrored ? 0 : parent.width - width
+
+        Text {
+            id: upText
+
+            anchors.centerIn: parent
+            anchors.fill: parent
+            color: "white"
+            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
+            fontSizeMode: Text.Fit
+            horizontalAlignment: Text.AlignHCenter
+            text: "+"
+            verticalAlignment: Text.AlignVCenter
+        }
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/VolumeSlider.qml b/examples/tutorials/drumpad/initial_project/Drumpad/VolumeSlider.qml
new file mode 100644 (file)
index 0000000..0fd1eea
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+Slider {
+    id: root
+
+    orientation: Qt.Vertical
+    padding: 0
+
+    background: Rectangle {
+        color: Constants.mediumGray
+        implicitHeight: root.height
+        implicitWidth: root.width
+        radius: width / 2
+
+        Rectangle {
+            anchors.bottom: parent.bottom
+            anchors.horizontalCenter: parent.horizontalCenter
+            color: Qt.lighter(Constants.primaryColor, 1 - (root.visualPosition * 0.3))
+            height: (1 - root.visualPosition) * parent.height + (root.visualPosition * handle.height)
+            radius: parent.width / 2
+            width: parent.width
+        }
+    }
+
+    handle: Rectangle {
+        border.color: "#b0b0b0"
+        border.width: 1
+        color: root.pressed ? "#e0e0e0" : "#ffffff"
+        height: root.width
+        radius: width / 2
+        width: root.width
+        x: root.availableWidth / 2 - height / 2
+        y: root.visualPosition * (root.availableHeight - height)
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Drumpad/qmldir b/examples/tutorials/drumpad/initial_project/Drumpad/qmldir
new file mode 100644 (file)
index 0000000..7dba78c
--- /dev/null
@@ -0,0 +1,10 @@
+module Drumpad
+
+AvailableSoundsComboBox 1.0 AvailableSoundsComboBox.qml
+SoundEffectPlayer 1.0 SoundEffectPlayer.qml
+CenteredFlow 1.0 CenteredFlow.qml
+VolumeSlider 1.0 VolumeSlider.qml
+StyledSpinBox 1.0 StyledSpinBox.qml
+PadButton 1.0 PadButton.qml
+
+singleton Constants 1.0 Constants.qml
diff --git a/examples/tutorials/drumpad/initial_project/DrumpadContent/App.qml b/examples/tutorials/drumpad/initial_project/DrumpadContent/App.qml
new file mode 100644 (file)
index 0000000..e1e4b4d
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import Drumpad 1.0
+
+Window {
+    id: root
+
+    height: 800
+    title: "Drumpad"
+    visible: true
+    width: 1200
+
+    MainScreen {
+        id: mainScreen
+
+        anchors.fill: parent
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/DrumpadContent/MainScreen.qml b/examples/tutorials/drumpad/initial_project/DrumpadContent/MainScreen.qml
new file mode 100644 (file)
index 0000000..2754c4c
--- /dev/null
@@ -0,0 +1,99 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Drumpad
+import Audio
+
+Rectangle {
+    id: root
+
+    property QtObject soundEffectPlayer: Qt.createComponent("../Drumpad/SoundEffectPlayer.qml",
+                                                            Component.PreferSynchronous)
+
+    color: "black"
+    focus: true
+
+    Component.onCompleted: {
+        // Initialize the default sound effect players
+        for (var i = 0; i < audioPlayersSpinBox.value; i++) {
+            root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
+                index: i
+            });
+        }
+    }
+    Keys.onPressed: event => {
+        if (event.key < Qt.Key_1 || event.key > Qt.Key_9) {
+            // Ignore key out of scope
+            return;
+        }
+
+        let digit = event.key - Qt.Key_1;
+        if (digit < soundEffectPlayersFlow.children.length) {
+            soundEffectPlayersFlow.children[digit].play();
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+        anchors.margins: 10
+
+        Row {
+            id: audioPlayersCountRow
+
+            Layout.alignment: Qt.AlignHCenter
+            spacing: 5
+
+            Text {
+                anchors.verticalCenter: parent.verticalCenter
+                color: "white"
+                text: "Audio players:"
+            }
+
+            StyledSpinBox {
+                id: audioPlayersSpinBox
+
+                value: 5
+
+                onValueModified: {
+                    let soundPlayersCount = soundEffectPlayersFlow.children.length;
+                    if (audioPlayersSpinBox.value < soundPlayersCount) {
+                        // Remove extra sound effect players
+                        soundEffectPlayersFlow.children.length = audioPlayersSpinBox.value;
+                        return;
+                    }
+
+                    if (audioPlayersSpinBox.value < soundPlayersCount) {
+                        return;
+                    }
+                    // Create more sound effect players
+                    for (var i = soundPlayersCount; i < audioPlayersSpinBox.value; i++) {
+                        root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
+                            index: i
+                        });
+                    }
+                }
+            }
+        }
+
+        ScrollView {
+            Layout.fillHeight: true
+            Layout.fillWidth: true
+            contentWidth: width
+
+            background: Rectangle {
+                color: "#232323"
+            }
+
+            CenteredFlow {
+                id: soundEffectPlayersFlow
+
+                anchors.fill: parent
+                padding: 10
+                spacing: 10
+            }
+        }
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/DrumpadContent/qmldir b/examples/tutorials/drumpad/initial_project/DrumpadContent/qmldir
new file mode 100644 (file)
index 0000000..f1f34c5
--- /dev/null
@@ -0,0 +1,4 @@
+module DrumpadContent
+
+App 1.0 App.qml
+MainScreen 1.0 MainScreen.qml
diff --git a/examples/tutorials/drumpad/initial_project/Mocks/Audio/AudioEngine.qml b/examples/tutorials/drumpad/initial_project/Mocks/Audio/AudioEngine.qml
new file mode 100644 (file)
index 0000000..4bfbc24
--- /dev/null
@@ -0,0 +1,27 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtMultimedia
+
+Item {
+    id: root
+
+    property double volume
+    property url file
+
+    MediaPlayer {
+        id: player
+        source: file
+        audioOutput: AudioOutput {}
+    }
+
+    onVolumeChanged : {
+        console.log("Mock: VolumeChanaged ", volume )
+    }
+
+    function play() {
+        console.log("Mock: play()")
+        player.play()
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Mocks/Audio/WaveformItem.qml b/examples/tutorials/drumpad/initial_project/Mocks/Audio/WaveformItem.qml
new file mode 100644 (file)
index 0000000..fcb6041
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+
+Rectangle {
+    id: root
+    width: 1920
+    height: 1080
+    color: "blue"
+    property url file
+}
diff --git a/examples/tutorials/drumpad/initial_project/Mocks/Audio/qmldir b/examples/tutorials/drumpad/initial_project/Mocks/Audio/qmldir
new file mode 100644 (file)
index 0000000..189d68e
--- /dev/null
@@ -0,0 +1,3 @@
+module Audio
+AudioEngine 1.0 AudioEngine.qml
+WaveformItem 1.0 WaveformItem.qml
diff --git a/examples/tutorials/drumpad/initial_project/Mocks/Components/AudioFilesModel.qml b/examples/tutorials/drumpad/initial_project/Mocks/Components/AudioFilesModel.qml
new file mode 100644 (file)
index 0000000..b06a1e1
--- /dev/null
@@ -0,0 +1,8 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+Item {
+    getFiles: function() {
+        console.log("AudioFilesModel mock: getFiles()")
+    }
+}
diff --git a/examples/tutorials/drumpad/initial_project/Mocks/Components/qmldir b/examples/tutorials/drumpad/initial_project/Mocks/Components/qmldir
new file mode 100644 (file)
index 0000000..2d8e989
--- /dev/null
@@ -0,0 +1,2 @@
+module Components
+AudioFilesModel 1.0 AudioFilesModel.qml
diff --git a/examples/tutorials/drumpad/initial_project/Sounds/Clap.wav b/examples/tutorials/drumpad/initial_project/Sounds/Clap.wav
new file mode 100644 (file)
index 0000000..aceee33
Binary files /dev/null and b/examples/tutorials/drumpad/initial_project/Sounds/Clap.wav differ
diff --git a/examples/tutorials/drumpad/initial_project/doc/drumpad_initial_project.pyproject b/examples/tutorials/drumpad/initial_project/doc/drumpad_initial_project.pyproject
new file mode 100644 (file)
index 0000000..1841cd8
--- /dev/null
@@ -0,0 +1,21 @@
+{
+    "files": ["../Drumpad.qmlproject",
+              "../Drumpad.qrc",
+              "../qtquickcontrols2.conf",
+              "../Drumpad/AvailableSoundsComboBox.qml",
+              "../Drumpad/CenteredFlow.qml",
+              "../Drumpad/Constants.qml",
+              "../Drumpad/PadButton.qml",
+              "../Drumpad/qmldir",
+              "../Drumpad/SoundEffectPlayer.qml",
+              "../Drumpad/StyledSpinBox.qml",
+              "../Drumpad/VolumeSlider.qml",
+              "../DrumpadContent/App.qml",
+              "../DrumpadContent/MainScreen.qml",
+              "../DrumpadContent/qmldir",
+              "../Mocks/Audio/AudioEngine.qml",
+              "../Mocks/Audio/qmldir",
+              "../Mocks/Audio/WaveformItem.qml",
+              "../Mocks/Components/AudioFilesModel.qml",
+              "../Mocks/Components/qmldir"]
+}
diff --git a/examples/tutorials/drumpad/initial_project/doc/initial_project.md b/examples/tutorials/drumpad/initial_project/doc/initial_project.md
new file mode 100644 (file)
index 0000000..b874d85
--- /dev/null
@@ -0,0 +1,12 @@
+# Drumpad example (Qt Design Studio) - Initial project
+
+This example contains the initial [Qt Design Studio] project to be used as a starting point for the
+[Qt Design Studio integration tutorial]. **It is not an executable project as is**, since it does
+**not** contain the required Python code developed along the tutorial.
+
+For more details, see the [Qt Design Studio integration tutorial].
+
+To download the final project source code, visit {ref}`example_tutorials_drumpad_final_project`.
+
+[Qt Design Studio]: https://www.qt.io/product/ui-design-tools/
+[Qt Design Studio integration tutorial]: tutorial_qt_design_studio_integration
diff --git a/examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf b/examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf
new file mode 100644 (file)
index 0000000..87a95d0
--- /dev/null
@@ -0,0 +1,6 @@
+; This file can be edited to change the style of the application
+; Read "Qt Quick Controls 2 Configuration File" for details:
+; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html
+
+[Controls]
+Style=Basic
index c4bb873ee48397815c017fc2ae1da2b5b99e1781..ea021030f182f27308319c727cc3b1fa77ad7b78 100644 (file)
@@ -49,10 +49,10 @@ options.append(("--python-include-path",
                 lambda: get_python_include_path(),
                 python_include_error,
                 "Print Python include path"))
-options.append(("--shiboken-generator-include-path",
-                lambda: get_package_include_path(Package.SHIBOKEN_GENERATOR),
+options.append(("--shiboken-include-path",
+                lambda: get_package_include_path(Package.SHIBOKEN_MODULE),
                 pyside_error,
-                "Print shiboken generator include paths"))
+                "Print shiboken module include paths"))
 options.append(("--pyside-include-path",
                 lambda: get_package_include_path(Package.PYSIDE_MODULE),
                 pyside_error,
index f362498020ef50bf8d5d5ffc3b5809afaea34c60..ec5716207315a9b1b02a5647c09f9f7b0b4a833c 100644 (file)
@@ -1,22 +1,24 @@
 // Copyright (C) 2022 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
+pragma ComponentBehavior: Bound
+
 import QtQuick
 import QtWebEngine
 
 QtObject {
     id: root
 
-    property QtObject defaultProfilePrototype : WebEngineProfilePrototype {
+    property WebEngineProfilePrototype defaultProfilePrototype : WebEngineProfilePrototype {
         storageName: "Profile"
         Component.onCompleted: {
-            let fullVersionList = defaultProfilePrototype.instance().clientHints.fullVersionList;
+            let fullVersionList = root.defaultProfilePrototype.instance().clientHints.fullVersionList;
             fullVersionList["QuickNanoBrowser"] = "1.0";
-            defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
+            root.defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
         }
     }
 
-    property QtObject otrPrototype : WebEngineProfilePrototype {
+    property WebEngineProfilePrototype otrPrototype : WebEngineProfilePrototype {
     }
 
     property Component browserWindowComponent: BrowserWindow {
@@ -26,18 +28,18 @@ QtObject {
         onClosing: destroy()
     }
     function createWindow(profile) {
-        var newWindow = browserWindowComponent.createObject(root);
+        var newWindow = browserWindowComponent.createObject(root) as BrowserWindow;
         newWindow.currentWebView.profile = profile;
         profile.downloadRequested.connect(newWindow.onDownloadRequested);
         return newWindow;
     }
     function createDialog(profile) {
-        var newDialog = browserDialogComponent.createObject(root);
+        var newDialog = browserDialogComponent.createObject(root) as BrowserDialog;
         newDialog.currentWebView.profile = profile;
         return newDialog;
     }
     function load(url) {
-        var browserWindow = createWindow(defaultProfilePrototype.instance());
+        var browserWindow = createWindow(root.defaultProfilePrototype.instance());
         browserWindow.currentWebView.url = url;
     }
 }
index 365d77d217b12e17da8b233c1fdfc1690a85c0a4..474968b87441084814567f5c6ec4192dea425be2 100644 (file)
@@ -1,6 +1,8 @@
 // Copyright (C) 2022 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
+pragma ComponentBehavior: Bound
+
 import QtCore
 import QtQml
 import QtQuick
@@ -12,16 +14,17 @@ import QtWebEngine
 import BrowserUtils
 
 ApplicationWindow {
-    id: browserWindow
-    property QtObject applicationRoot
-    property Item currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
+    id: win
+    required property QtObject applicationRoot
+    property WebEngineView currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null
     property int previousVisibility: Window.Windowed
     property int createdTabs: 0
+    property bool lastTabClosing: false
 
     width: 1300
     height: 900
     visible: true
-    title: currentWebView && currentWebView.title
+    title: win.currentWebView?.title ?? ""
 
     // Make sure the Qt.WindowFullscreenButtonHint is set on OS X.
     Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint
@@ -31,7 +34,7 @@ ApplicationWindow {
     }
 
     // When using style "mac", ToolButtons are not supposed to accept focus.
-    property bool platformIsMac: Qt.platform.os == "osx"
+    property bool platformIsMac: Qt.platform.os === "osx"
 
     Settings {
         id : appSettings
@@ -46,6 +49,8 @@ ApplicationWindow {
         property alias devToolsEnabled: devToolsEnabled.checked
         property alias pdfViewerEnabled: pdfViewerEnabled.checked
         property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow
+        property alias javascriptCanAccessClipboard: javascriptCanAccessClipboard.checked
+        property alias javascriptCanPaste: javascriptCanPaste.checked
     }
 
     Action {
@@ -65,14 +70,16 @@ ApplicationWindow {
     Action {
         shortcut: StandardKey.Refresh
         onTriggered: {
-            if (currentWebView)
-                currentWebView.reload();
+            if (win.currentWebView)
+                win.currentWebView.reload();
         }
     }
     Action {
         shortcut: StandardKey.AddTab
         onTriggered: {
-            tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfilePrototype.instance());
+            tabBar.createTab(tabBar.count !== 0
+                             ? win.currentWebView.profile
+                             : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance());
             addressBar.forceActiveFocus();
             addressBar.selectAll();
         }
@@ -80,20 +87,20 @@ ApplicationWindow {
     Action {
         shortcut: StandardKey.Close
         onTriggered: {
-            currentWebView.triggerWebAction(WebEngineView.RequestClose);
+            win.currentWebView.triggerWebAction(WebEngineView.RequestClose);
         }
     }
     Action {
         shortcut: StandardKey.Quit
-        onTriggered: browserWindow.close()
+        onTriggered: win.close()
     }
     Action {
         shortcut: "Escape"
         onTriggered: {
-            if (currentWebView.state == "FullScreen") {
-                browserWindow.visibility = browserWindow.previousVisibility;
+            if (win.currentWebView.state === "FullScreen") {
+                win.visibility = win.previousVisibility;
                 fullScreenNotification.hide();
-                currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
+                win.currentWebView.triggerWebAction(WebEngineView.ExitFullScreen);
             }
 
             if (findBar.visible)
@@ -102,52 +109,52 @@ ApplicationWindow {
     }
     Action {
         shortcut: "Ctrl+0"
-        onTriggered: currentWebView.zoomFactor = 1.0
+        onTriggered: win.currentWebView.zoomFactor = 1.0
     }
     Action {
         shortcut: StandardKey.ZoomOut
-        onTriggered: currentWebView.zoomFactor -= 0.1
+        onTriggered: win.currentWebView.zoomFactor -= 0.1
     }
     Action {
         shortcut: StandardKey.ZoomIn
-        onTriggered: currentWebView.zoomFactor += 0.1
+        onTriggered: win.currentWebView.zoomFactor += 0.1
     }
 
     Action {
         shortcut: StandardKey.Copy
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Copy)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Copy)
     }
     Action {
         shortcut: StandardKey.Cut
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Cut)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Cut)
     }
     Action {
         shortcut: StandardKey.Paste
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Paste)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Paste)
     }
     Action {
         shortcut: "Shift+"+StandardKey.Paste
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.PasteAndMatchStyle)
     }
     Action {
         shortcut: StandardKey.SelectAll
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.SelectAll)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.SelectAll)
     }
     Action {
         shortcut: StandardKey.Undo
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Undo)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Undo)
     }
     Action {
         shortcut: StandardKey.Redo
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Redo)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Redo)
     }
     Action {
         shortcut: StandardKey.Back
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Back)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Back)
     }
     Action {
         shortcut: StandardKey.Forward
-        onTriggered: currentWebView.triggerWebAction(WebEngineView.Forward)
+        onTriggered: win.currentWebView.triggerWebAction(WebEngineView.Forward)
     }
     Action {
         shortcut: StandardKey.Find
@@ -170,16 +177,17 @@ ApplicationWindow {
         RowLayout {
             anchors.fill: parent
             ToolButton {
-                enabled: currentWebView && (currentWebView.canGoBack || currentWebView.canGoForward)
+                enabled: win.currentWebView?.canGoBack || win.currentWebView?.canGoForward
                 onClicked: historyMenu.open()
                 text: qsTr("â–¼")
                 Menu {
                     id: historyMenu
                     Instantiator {
-                        model: currentWebView && currentWebView.history.items
+                        model: win.currentWebView?.history?.items
                         MenuItem {
+                            required property var model
                             text: model.title
-                            onTriggered: currentWebView.goBackOrForward(model.offset)
+                            onTriggered: win.currentWebView.goBackOrForward(model.offset)
                             checkable: !enabled
                             checked: !enabled
                             enabled: model.offset
@@ -197,23 +205,25 @@ ApplicationWindow {
 
             ToolButton {
                 id: backButton
-                icon.source: "qrc:/icons/go-previous.png"
-                onClicked: currentWebView.goBack()
-                enabled: currentWebView && currentWebView.canGoBack
-                activeFocusOnTab: !browserWindow.platformIsMac
+                icon.source: "icons/3rdparty/go-previous.png"
+                onClicked: win.currentWebView.goBack()
+                enabled: win.currentWebView?.canGoBack ?? false
+                activeFocusOnTab: !win.platformIsMac
             }
             ToolButton {
                 id: forwardButton
-                icon.source: "qrc:/icons/go-next.png"
-                onClicked: currentWebView.goForward()
-                enabled: currentWebView && currentWebView.canGoForward
-                activeFocusOnTab: !browserWindow.platformIsMac
+                icon.source: "icons/3rdparty/go-next.png"
+                onClicked: win.currentWebView.goForward()
+                enabled: win.currentWebView?.canGoForward ?? false
+                activeFocusOnTab: !win.platformIsMac
             }
             ToolButton {
                 id: reloadButton
-                icon.source: currentWebView && currentWebView.loading ? "qrc:/icons/process-stop.png" : "qrc:/icons/view-refresh.png"
-                onClicked: currentWebView && currentWebView.loading ? currentWebView.stop() : currentWebView.reload()
-                activeFocusOnTab: !browserWindow.platformIsMac
+                icon.source: win.currentWebView?.loading
+                             ? "icons/3rdparty/process-stop.png"
+                             : "icons/3rdparty/view-refresh.png"
+                onClicked: win.currentWebView?.loading ? win.currentWebView.stop() : win.currentWebView.reload()
+                activeFocusOnTab: !win.platformIsMac
             }
             TextField {
                 id: addressBar
@@ -224,7 +234,7 @@ ApplicationWindow {
                     id: faviconImage
                     width: 16; height: 16
                     sourceSize: Qt.size(width, height)
-                    source: currentWebView && currentWebView.icon ? currentWebView.icon : ''
+                    source: win.currentWebView?.icon ? win.currentWebView.icon : ''
                 }
                 MouseArea {
                     id: textFieldMouseArea
@@ -272,10 +282,10 @@ ApplicationWindow {
                 focus: true
                 Layout.fillWidth: true
                 Binding on text {
-                    when: currentWebView
-                    value: currentWebView.url
+                    when: win.currentWebView
+                    value: win.currentWebView.url
                 }
-                onAccepted: currentWebView.url = Utils.fromUserInput(text)
+                onAccepted: win.currentWebView.url = Utils.fromUserInput(text)
                 selectByMouse: true
             }
             ToolButton {
@@ -319,21 +329,25 @@ ApplicationWindow {
                         id: offTheRecordEnabled
                         text: "Off The Record"
                         checkable: true
-                        checked: currentWebView && currentWebView.profile === otrPrototype.instance()
-                        onToggled: function(checked) {
-                            if (currentWebView) {
-                                currentWebView.profile = checked ? otrPrototype.instance() : defaultProfilePrototype.instance();
+                        checked: win.currentWebView?.profile === (win.applicationRoot as ApplicationRoot).otrPrototype.instance()
+                        onToggled: function() {
+                            if (win.currentWebView) {
+                                win.currentWebView.profile = offTheRecordEnabled.checked
+                                        ? (win.applicationRoot as ApplicationRoot).otrPrototype.instance()
+                                        : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance();
                             }
                         }
                     }
                     MenuItem {
                         id: httpDiskCacheEnabled
                         text: "HTTP Disk Cache"
-                        checkable: currentWebView && !currentWebView.profile.offTheRecord
-                        checked: currentWebView && (currentWebView.profile.httpCacheType === WebEngineProfile.DiskHttpCache)
-                        onToggled: function(checked) {
-                            if (currentWebView) {
-                                currentWebView.profile.httpCacheType = checked ? WebEngineProfile.DiskHttpCache : WebEngineProfile.MemoryHttpCache;
+                        checkable: !win.currentWebView?.profile?.offTheRecord ?? false
+                        checked: win.currentWebView?.profile.httpCacheType === WebEngineProfile.DiskHttpCache
+                        onToggled: function() {
+                            if (win.currentWebView) {
+                                win.currentWebView.profile.httpCacheType = httpDiskCacheEnabled.checked
+                                        ? WebEngineProfile.DiskHttpCache
+                                        : WebEngineProfile.MemoryHttpCache;
                             }
                         }
                     }
@@ -368,7 +382,6 @@ ApplicationWindow {
                         checkable: true
                         checked: WebEngine.settings.pdfViewerEnabled
                     }
-
                     Menu {
                         id: imageAnimationPolicy
                         title: "Image Animation Policy"
@@ -407,6 +420,18 @@ ApplicationWindow {
                         }
                     }
 
+                    MenuItem {
+                        id: javascriptCanAccessClipboard
+                        text: "JavaScript can access clipboard"
+                        checkable: true
+                        checked: WebEngine.settings.javascriptCanAccessClipboard
+                    }
+                    MenuItem {
+                        id: javascriptCanPaste
+                        text: "JavaScript can paste"
+                        checkable: true
+                        checked: WebEngine.settings.javascriptCanPaste
+                    }
                 }
             }
         }
@@ -417,14 +442,14 @@ ApplicationWindow {
                 left: parent.left
                 top: parent.bottom
                 right: parent.right
-                leftMargin: parent.leftMargin
-                rightMargin: parent.rightMargin
+                leftMargin: parent.anchors.leftMargin
+                rightMargin: parent.anchors.rightMargin
             }
             background: Item {}
             z: -2
             from: 0
             to: 100
-            value: (currentWebView && currentWebView.loadProgress < 100) ? currentWebView.loadProgress : 0
+            value: (win.currentWebView?.loadProgress < 100) ? win.currentWebView.loadProgress : 0
         }
     }
 
@@ -442,22 +467,22 @@ ApplicationWindow {
         id: tabButtonComponent
 
         TabButton {
-            property color frameColor: "#999"
-            property color fillColor: "#eee"
-            property color nonSelectedColor: "#ddd"
+            id: tabButton
+            property color frameColor: "#999999"
+            property color fillColor: "#eeeeee"
+            property color nonSelectedColor: "#dddddd"
             property string tabTitle: "New Tab"
 
-            id: tabButton
             contentItem: Rectangle {
                 id: tabRectangle
-                color: tabButton.down ? fillColor : nonSelectedColor
+                color: tabButton.down ? tabButton.fillColor : tabButton.nonSelectedColor
                 border.width: 1
-                border.color: frameColor
+                border.color: tabButton.frameColor
                 implicitWidth: Math.max(text.width + 30, 80)
                 implicitHeight: Math.max(text.height + 10, 20)
-                Rectangle { height: 1 ; width: parent.width ; color: frameColor}
-                Rectangle { height: parent.height ; width: 1; color: frameColor}
-                Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: frameColor}
+                Rectangle { height: 1 ; width: parent.width ; color: tabButton.frameColor}
+                Rectangle { height: parent.height ; width: 1; color: tabButton.frameColor}
+                Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: tabButton.frameColor}
                 Text {
                     id: text
                     anchors.left: parent.left
@@ -465,7 +490,7 @@ ApplicationWindow {
                     anchors.leftMargin: 6
                     text: tabButton.tabTitle
                     elide: Text.ElideRight
-                    color: tabButton.down ? "black" : frameColor
+                    color: tabButton.down ? "black" : tabButton.frameColor
                     width: parent.width - button.background.width
                 }
                 Button {
@@ -477,16 +502,16 @@ ApplicationWindow {
                     background: Rectangle {
                         implicitWidth: 12
                         implicitHeight: 12
-                        color: button.hovered ? "#ccc" : tabRectangle.color
+                        color: button.hovered ? "#cccccc" : tabRectangle.color
                         Text {text: "x"; anchors.centerIn: parent; color: "gray"}
                     }
                     onClicked: tabButton.closeTab()
                 }
             }
 
-            onClicked: addressBar.text = tabLayout.itemAt(TabBar.index).url;
+            onClicked: addressBar.text = (tabLayout.itemAt(TabBar.index) as WebEngineView).url;
             function closeTab() {
-                tabBar.removeView(TabBar.index);
+                tabBar.tryCloseView(TabBar.index);
             }
         }
     }
@@ -496,10 +521,10 @@ ApplicationWindow {
         anchors.top: parent.top
         anchors.left: parent.left
         anchors.right: parent.right
-        Component.onCompleted: createTab(defaultProfilePrototype.instance())
+        Component.onCompleted: createTab((win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance())
 
         function createTab(profile, focusOnNewTab = true, url = undefined) {
-            var webview = tabComponent.createObject(tabLayout, {profile: profile});
+            var webview = tabComponent.createObject(tabLayout, {index: tabBar.count , profile: profile});
             var newTabButton = tabButtonComponent.createObject(tabBar, {tabTitle: Qt.binding(function () { return webview.title; })});
             tabBar.addItem(newTabButton);
             if (focusOnNewTab) {
@@ -511,12 +536,17 @@ ApplicationWindow {
             return webview;
         }
 
+        function tryCloseView(index) {
+            tabLayout.children[index].triggerWebAction(WebEngineView.RequestClose);
+        }
+
         function removeView(index) {
             if (tabBar.count > 1) {
                 tabBar.removeItem(tabBar.itemAt(index));
                 tabLayout.children[index].destroy();
             } else {
-                browserWindow.close();
+                win.lastTabClosing = true;
+                win.close();
             }
         }
 
@@ -524,10 +554,11 @@ ApplicationWindow {
             id: tabComponent
             WebEngineView {
                 id: webEngineView
+                property int index;
                 focus: true
 
                 onLinkHovered: function(hoveredUrl) {
-                    if (hoveredUrl == "")
+                    if (hoveredUrl === "")
                         hideStatusText.start();
                     else {
                         statusText.text = hoveredUrl;
@@ -563,6 +594,12 @@ ApplicationWindow {
                 settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
                 settings.imageAnimationPolicy: appSettings.imageAnimationPolicy
                 settings.screenCaptureEnabled: true
+                settings.javascriptCanAccessClipboard: appSettings.javascriptCanAccessClipboard
+                settings.javascriptCanPaste: appSettings.javascriptCanPaste
+
+                onWindowCloseRequested: function() {
+                    tabBar.removeView(webEngineView.index);
+                }
 
                 onCertificateError: function(error) {
                     if (!error.isMainFrame) {
@@ -578,29 +615,29 @@ ApplicationWindow {
                     if (!request.userInitiated)
                         console.warn("Blocked a popup window.");
                     else if (request.destination === WebEngineNewWindowRequest.InNewTab) {
-                        var tab = tabBar.createTab(currentWebView.profile, true, request.requestedUrl);
+                        var tab = tabBar.createTab(win.currentWebView.profile, true, request.requestedUrl);
                         tab.acceptAsNewWindow(request);
                     } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) {
-                        var backgroundTab = tabBar.createTab(currentWebView.profile, false);
+                        var backgroundTab = tabBar.createTab(win.currentWebView.profile, false);
                         backgroundTab.acceptAsNewWindow(request);
                     } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) {
-                        var dialog = applicationRoot.createDialog(currentWebView.profile);
-                        dialog.currentWebView.acceptAsNewWindow(request);
+                        var dialog = (win.applicationRoot as ApplicationRoot).createDialog(win.currentWebView.profile);
+                        dialog.win.currentWebView.acceptAsNewWindow(request);
                     } else {
-                        var window = applicationRoot.createWindow(currentWebView.profile);
-                        window.currentWebView.acceptAsNewWindow(request);
+                        var window = (win.applicationRoot as ApplicationRoot).createWindow(win.currentWebView.profile);
+                        window.win.currentWebView.acceptAsNewWindow(request);
                     }
                 }
 
                 onFullScreenRequested: function(request) {
                     if (request.toggleOn) {
                         webEngineView.state = "FullScreen";
-                        browserWindow.previousVisibility = browserWindow.visibility;
-                        browserWindow.showFullScreen();
+                        win.previousVisibility = win.visibility;
+                        win.showFullScreen();
                         fullScreenNotification.show();
                     } else {
                         webEngineView.state = "";
-                        browserWindow.visibility = browserWindow.previousVisibility;
+                        win.visibility = win.previousVisibility;
                         fullScreenNotification.hide();
                     }
                     request.accept();
@@ -651,7 +688,7 @@ ApplicationWindow {
                 }
 
                 onLoadingChanged: function(loadRequest) {
-                    if (loadRequest.status == WebEngineView.LoadStartedStatus)
+                    if (loadRequest.status === WebEngineView.LoadStartedStatus)
                         findBar.reset();
                 }
 
@@ -668,7 +705,7 @@ ApplicationWindow {
                     interval: 0
                     running: false
                     repeat: false
-                    onTriggered: currentWebView.reload()
+                    onTriggered: win.currentWebView.reload()
                 }
             }
         }
@@ -682,7 +719,7 @@ ApplicationWindow {
         anchors.right: parent.right
         anchors.bottom: parent.bottom
         onNewWindowRequested: function(request) {
-            var tab = tabBar.createTab(currentWebView.profile);
+            var tab = tabBar.createTab(win.currentWebView.profile);
             request.openIn(tab);
         }
 
@@ -693,7 +730,7 @@ ApplicationWindow {
             repeat: false
             onTriggered: devToolsEnabled.checked = false
         }
-        onWindowCloseRequested: function(request) {
+        onWindowCloseRequested: function() {
             // Delay hiding for keep the inspectedView set to receive the ACK message of close.
             hideTimer.running = true;
         }
@@ -744,7 +781,7 @@ ApplicationWindow {
     Dialog {
         id: permissionDialog
         anchors.centerIn: parent
-        width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2
+        width: Math.min(win.width, win.height) / 3 * 2
         contentWidth: mainTextForPermissionDialog.width
         contentHeight: mainTextForPermissionDialog.height
         standardButtons: Dialog.No | Dialog.Yes
@@ -859,13 +896,13 @@ ApplicationWindow {
 
         onFindNext: {
             if (text)
-                currentWebView && currentWebView.findText(text);
+                win.currentWebView?.findText(text);
             else if (!visible)
                 visible = true;
         }
         onFindPrevious: {
             if (text)
-                currentWebView && currentWebView.findText(text, WebEngineView.FindBackward);
+                win.currentWebView?.findText(text, WebEngineView.FindBackward);
             else if (!visible)
                 visible = true;
         }
@@ -898,4 +935,14 @@ ApplicationWindow {
             }
         }
     }
+
+    onClosing: function(closeEvent) {
+       if (lastTabClosing) {
+           return;
+       }
+       closeEvent.accepted = false
+       for (var i = 0; i < tabBar.count; i++)  {
+           tabBar.tryCloseView(i);
+       }
+    }
 }
index b116ab86734e12fb6d48ce64dc4eb504845211d5..ef0c7f5a7d72a0c4e1759f2e563d248a68aa1c13 100644 (file)
@@ -1,10 +1,10 @@
 // Copyright (C) 2022 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 
+pragma ComponentBehavior: Bound
+
 import QtQuick
 import QtQuick.Controls.Fusion
-import QtWebEngine
-import QtQuick.Layouts
 
 Rectangle {
     id: downloadView
@@ -25,20 +25,26 @@ Rectangle {
         id: downloadItemDelegate
 
         Rectangle {
+            id: downloadItem
             width: listView.width
             height: childrenRect.height
             anchors.margins: 10
             radius: 3
             color: "transparent"
             border.color: "black"
+
+            required property int index
+
             Rectangle {
                 id: progressBar
 
-                property real progress: downloadModel.downloads[index]
-                                       ? downloadModel.downloads[index].receivedBytes / downloadModel.downloads[index].totalBytes : 0
+                property real progress: {
+                    let d = downloadModel.downloads[downloadItem.index]
+                    return d ? d.receivedBytes / d.totalBytes : 0
+                }
 
                 radius: 3
-                color: width == listView.width ? "green" : "#2b74c7"
+                color: width === listView.width ? "green" : "#2b74c7"
                 width: listView.width * progress
                 height: cancelButton.height
 
@@ -54,7 +60,10 @@ Rectangle {
                 }
                 Label {
                     id: label
-                    text: downloadModel.downloads[index] ? downloadModel.downloads[index].downloadDirectory + "/" + downloadModel.downloads[index].downloadFileName : qsTr("")
+                    text: {
+                        let d = downloadModel.downloads[downloadItem.index]
+                        return d ? d.downloadDirectory + "/" + d.downloadFileName : qsTr("")
+                    }
                     anchors {
                         verticalCenter: cancelButton.verticalCenter
                         left: parent.left
@@ -64,16 +73,16 @@ Rectangle {
                 Button {
                     id: cancelButton
                     anchors.right: parent.right
-                    icon.source: "qrc:/icons/process-stop.png"
+                    icon.source: "icons/3rdparty/process-stop.png"
                     onClicked: {
-                        var download = downloadModel.downloads[index];
+                        var download = downloadModel.downloads[downloadItem.index];
 
                         download.cancel();
 
                         downloadModel.downloads = downloadModel.downloads.filter(function (el) {
                             return el.id !== download.id;
                         });
-                        downloadModel.remove(index);
+                        downloadModel.remove(downloadItem.index);
                     }
                 }
             }
index 409d8dcffe8ef2c830273c4ab5db10cbbba51a3e..013f28e88a64988313535bcfa5da941f3caab682 100644 (file)
@@ -63,46 +63,47 @@ Rectangle {
         }
 
         Label {
-            text: activeMatch + "/" + numberOfMatches
-            visible: findTextField.text != ""
+            text: root.activeMatch + "/" + root.numberOfMatches
+            visible: findTextField.text !== ""
             color: "black"
         }
 
         Rectangle {
             border.width: 1
-            border.color: "#ddd"
-            width: 2
-            height: parent.height
-            anchors.topMargin: 5
-            anchors.bottomMargin: 5
+            border.color: "#dddddd"
+            Layout.preferredWidth: 2
+            Layout.preferredHeight: parent.height
         }
 
         ToolButton {
+            id: findBtnLeft
             text: "<"
-            enabled: numberOfMatches > 0
+            enabled: root.numberOfMatches > 0
             onClicked: root.findPrevious()
             contentItem: Text {
                 color: "black"
-                text: parent.text
+                text: findBtnLeft.text
             }
         }
 
         ToolButton {
+            id: findBtnRight
             text: ">"
-            enabled: numberOfMatches > 0
+            enabled: root.numberOfMatches > 0
             onClicked: root.findNext()
             contentItem: Text {
                 color: "black"
-                text: parent.text
+                text: findBtnRight.text
             }
         }
 
         ToolButton {
+            id: findBtnClose
             text: "x"
             onClicked: root.visible = false
             contentItem: Text {
                 color: "black"
-                text: parent.text
+                text: findBtnClose.text
             }
         }
     }
index 7794064327fe7e5f67810d8f7aa3200cd38b5b97..cdf154c78b0e42cfa10355a741e6b7028a1c3ae3 100644 (file)
@@ -28,8 +28,8 @@ Rectangle {
         NumberAnimation {
             duration: 750
             onStopped: {
-                if (opacity == 0)
-                    visible = false;
+                if (fullScreenNotification.opacity === 0)
+                    fullScreenNotification.visible = false;
             }
         }
     }
@@ -37,7 +37,7 @@ Rectangle {
     Timer {
         id: reset
         interval: 5000
-        onTriggered: hide()
+        onTriggered: fullScreenNotification.hide()
     }
 
     anchors.horizontalCenter: parent.horizontalCenter
diff --git a/examples/webenginequick/nanobrowser/WebAuthDialog.qml b/examples/webenginequick/nanobrowser/WebAuthDialog.qml
new file mode 100644 (file)
index 0000000..8d5cf59
--- /dev/null
@@ -0,0 +1,285 @@
+// Copyright (C) 2022 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 QtWebEngine
+
+Dialog {
+    id: webAuthDialog
+    anchors.centerIn: parent
+    width: Math.min(parent.parent.width, parent.parent.height) / 3 * 2
+    contentWidth: verticalLayout.width +10;
+    contentHeight: verticalLayout.height +10;
+    standardButtons: Dialog.Cancel | Dialog.Apply
+    title: "WebAuth Request"
+
+    property var selectAccount;
+    property var authrequest: null;
+
+    Connections {
+        id: webauthConnection
+        ignoreUnknownSignals: true
+        function onStateChanged(state) {
+            webAuthDialog.setupUI(state);
+        }
+    }
+
+    onApplied: {
+        switch (webAuthDialog.authrequest.state) {
+            case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+                webAuthDialog.authrequest.setPin(pinEdit.text);
+                break;
+            case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+                webAuthDialog.authrequest.setSelectedAccount(webAuthDialog.selectAccount);
+                break;
+             default:
+                 break;
+        }
+    }
+
+    onRejected: {
+        webAuthDialog.authrequest.cancel();
+    }
+
+    function init(request) {
+        pinLabel.visible = false;
+        pinEdit.visible = false;
+        confirmPinLabel.visible = false;
+        confirmPinEdit.visible = false;
+        selectAccountModel.clear();
+        webAuthDialog.authrequest = request;
+        webauthConnection.target = request;
+        setupUI(webAuthDialog.authrequest.state)
+        webAuthDialog.visible = true;
+        pinEntryError.visible = false;
+    }
+
+    function setupUI(state) {
+        switch (state) {
+        case WebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+            setupSelectAccountUI();
+            break;
+        case WebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+            setupCollectPin();
+            break;
+        case WebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection:
+            setupFinishCollectToken();
+            break;
+        case WebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
+            setupErrorUI();
+            break;
+        case WebEngineWebAuthUxRequest.WebAuthUxState.Completed:
+            webAuthDialog.close();
+            break;
+        }
+    }
+
+    ButtonGroup {
+        id : selectAccount;
+        exclusive: true;
+    }
+
+    ListModel {
+        id: selectAccountModel
+
+    }
+    contentItem: Item {
+        ColumnLayout  {
+            id : verticalLayout
+            spacing : 10
+
+            Label {
+                id: heading
+                text: "";
+            }
+
+            Label {
+                id: description
+                text: "";
+            }
+
+            Row {
+                spacing : 10
+                Label {
+                    id: pinLabel
+                    text: "PIN";
+                }
+                TextInput {
+                    id: pinEdit
+                    text: "EnterPin"
+                    enabled: true
+                    focus: true
+                    color: "white"
+                    layer.sourceRect: Qt.rect(0, 0, 20, 20)
+                }
+            }
+
+            Row {
+                spacing : 10
+                Label {
+                    id: confirmPinLabel
+                    text: "Confirm PIN";
+                }
+                TextEdit {
+                    id: confirmPinEdit
+                    text: ""
+                }
+            }
+
+            Label {
+                id: pinEntryError
+                text: "";
+            }
+
+            Repeater {
+                id : selectAccountRepeater
+                model: selectAccountModel
+                Column {
+                    id: selectAccountRepeaterColumn
+                    required property string modelData
+                    spacing : 5
+                    RadioButton {
+                        text: selectAccountRepeaterColumn.modelData
+                        ButtonGroup.group : webAuthDialog.selectAccount;
+                        onClicked: function(){
+                            webAuthDialog.selectAccount = text;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    function setupSelectAccountUI() {
+        webAuthDialog.selectAccount = "";
+        heading.text = "Choose a passkey";
+        description.text = "Which passkey do you want to use for " + webAuthDialog.authrequest.relyingPartyId;
+
+        selectAccountModel.clear();
+        var userNames = webAuthDialog.authrequest.userNames;
+        for (var i = 0; i < userNames.length; i++) {
+            selectAccountModel.append( {"name" : userNames[i]});
+        }
+        pinLabel.visible = false;
+        pinEdit.visible = false;
+        confirmPinLabel.visible = false;
+        confirmPinEdit.visible = false;
+        pinEntryError.visible = false;
+        standardButton(Dialog.Apply).visible = true;
+        standardButton(Dialog.Cancel).visible = true;
+        standardButton(Dialog.Cancel).text ="Cancel"
+    }
+
+    function setupCollectPin() {
+        var requestInfo = webAuthDialog.authrequest.pinRequest;
+
+        pinEdit.clear();
+
+        if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Challenge) {
+            heading.text = "PIN required";
+            description.text = "Enter the PIN for your security key";
+            pinLabel.visible = true;
+            pinEdit.visible = true;
+            confirmPinLabel.visible = false;
+            confirmPinEdit.visible = false;
+        } else if (requestInfo.reason === WebEngineWebAuthUxRequest.PinEntryReason.Set) {
+            heading.text = "Set PIN ";
+            description.text = "Set new PIN for your security key";
+            pinLabel.visible = true;
+            pinEdit.visible = true;
+            confirmPinLabel.visible = true;
+            confirmPinEdit.visible = true;
+        }
+        pinEntryError.text = getPINErrorDetails() + " " + requestInfo.remainingAttempts + " attempts reamining";
+        pinEntryError.visible = true;
+        selectAccountModel.clear();
+        standardButton(Dialog.Cancel).visible = true;
+        standardButton(Dialog.Cancel).text ="Cancel"
+        standardButton(Dialog.Apply).visible = true;
+    }
+
+    function getPINErrorDetails() {
+        var requestInfo = webAuthDialog.authrequest.pinRequest;
+        switch (requestInfo.error) {
+        case WebEngineWebAuthUxRequest.PinEntryError.NoError:
+            return "";
+        case WebEngineWebAuthUxRequest.PinEntryError.TooShort:
+            return "Too short";
+        case WebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked:
+            return "Internal Uv locked";
+        case WebEngineWebAuthUxRequest.PinEntryError.WrongPin:
+            return "Wrong PIN";
+        case WebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters:
+            return "Invalid characters";
+        case WebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin:
+            return "Same as current PIN";
+        }
+    }
+
+    function getRequestFailureResaon() {
+        var requestFailureReason = webAuthDialog.authrequest.requestFailureReason;
+        switch (requestFailureReason) {
+        case WebEngineWebAuthUxRequest.RequestFailureReason.Timeout:
+            return " Request Timeout";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.KeyNotRegistered:
+            return "Key not registered";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.KeyAlreadyRegistered:
+            return "You already registered this device. You don't have to register it again\n"
+                     + "Try again with different key or device.";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.SoftPinBlock:
+            return "The security key is locked because the wrong PIN was entered too many times.\n"
+                    + "To unlock it, remove and reinsert it.";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.HardPinBlock:
+            return "The security key is locked because the wrong PIN was entered too many times.\n"
+                    + "You'll need to reset the security key.";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorRemovedDuringPinEntry:
+            return "Authenticator removed during verification. Please reinsert and try again";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingResidentKeys:
+            return "Authenticator doesn't have resident key support";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingUserVerification:
+            return "Authenticator missing user verification";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.AuthenticatorMissingLargeBlob:
+            return "Authenticator missing Large Blob support";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.NoCommonAlgorithms:
+            return "No common Algorithms";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.StorageFull:
+            return "Storage full";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.UserConsentDenied:
+            return "User consent denied";
+        case WebEngineWebAuthUxRequest.RequestFailureReason.WinUserCancelled:
+            return "User cancelled request";
+        }
+    }
+
+    function setupFinishCollectToken() {
+        heading.text = "Use your security key with " + webAuthDialog.authrequest.relyingPartyId;
+        description.text = "Touch your security key again to complete the request.";
+        pinLabel.visible = false;
+        pinEdit.visible = false;
+        confirmPinLabel.visible = false;
+        confirmPinEdit.visible = false;
+        selectAccountModel.clear();
+        pinEntryError.visible = false;
+        standardButton(Dialog.Apply).visible = false;
+        standardButton(Dialog.Cancel).visible = true;
+        standardButton(Dialog.Cancel).text ="Cancel"
+    }
+
+    function setupErrorUI() {
+        heading.text = "Something went wrong";
+        description.text = getRequestFailureResaon();
+        pinLabel.visible = false;
+        pinEdit.visible = false;
+        confirmPinLabel.visible = false;
+        confirmPinEdit.visible = false;
+        selectAccountModel.clear();
+        pinEntryError.visible = false;
+        standardButton(Dialog.Apply).visible = false;
+        standardButton(Dialog.Cancel).visible = true;
+        standardButton(Dialog.Cancel).text ="Close"
+    }
+}
index c86c57f678f6587a7a0a420a9c1f632dd8895f54..392748d6828a9d38af083f48fd98f97aabbd1a42 100644 (file)
@@ -1,6 +1,6 @@
 {
     "files": ["quicknanobrowser.py", "ApplicationRoot.qml",
               "BrowserDialog.qml", "BrowserWindow.qml", "DownloadView.qml",
-              "FindBar.qml", "FullScreenNotification.qml",
+              "FindBar.qml", "FullScreenNotification.qml", "WebAuthDialog.qml",
               "resources.qrc"]
 }
diff --git a/examples/webenginequick/nanobrowser/qmldir b/examples/webenginequick/nanobrowser/qmldir
new file mode 100644 (file)
index 0000000..d4d0abd
--- /dev/null
@@ -0,0 +1,9 @@
+module BrowserUtils
+ApplicationRoot 254.0 ApplicationRoot.qml
+BrowserDialog 254.0 BrowserDialog.qml
+BrowserWindow 254.0 BrowserWindow.qml
+DownloadView 254.0 DownloadView.qml
+FindBar 254.0 FindBar.qml
+FullScreenNotification 254.0 FullScreenNotification.qml
+WebAuthDialog 254.0 WebAuthDialog.qml
+depends QtQuick
index 990f102740aed7723f9f15b720287dbbafff17c4..b517c39a055e3e0ae37db8600bea494c00e498cc 100644 (file)
@@ -1,6 +1,6 @@
 # Resource object code (Python 3)
 # Created by: object code
-# Created by: The Resource Compiler for Qt version 6.4.0
+# Created by: The Resource Compiler for Qt version 6.10.0
 # WARNING! All changes made in this file will be lost!
 
 from PySide6 import QtCore
@@ -330,13 +330,13 @@ qt_resource_struct = b"\
 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x02\
 \x00\x00\x00\x00\x00\x00\x00\x00\
 \x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x03\xa6\
-\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x01\x975l\xc7\xfb\
 \x00\x00\x00R\x00\x00\x00\x00\x00\x01\x00\x00\x08\xfe\
-\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x01\x975l\xc7\xfb\
 \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x01\x975l\xc7\xfb\
 \x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xfa\
-\x00\x00\x01{\xe0\xa8\xe4\xe2\
+\x00\x00\x01\x975l\xc7\xfb\
 "
 
 def qInitResources():
index 1c5eefa50f8d86ffdf7c8a88239e748c4236e9b8..acaa897d0559ff71f9d1e2b2d1ea3bd8d02507cc 100644 (file)
@@ -50,66 +50,24 @@ set(generated_sources
     ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wigglywidget_wrapper.cpp)
 
 
-# ================================== Shiboken detection ======================================
-# Use provided python interpreter if given.
-if(NOT python_interpreter)
-    if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-        find_program(python_interpreter "python_d")
-        if(NOT python_interpreter)
-            message(FATAL_ERROR
-                "A debug Python interpreter could not be found, which is a requirement when "
-                "building this example in a debug configuration. Make sure python_d.exe is in "
-                "PATH.")
-        endif()
-    else()
-        find_program(python_interpreter "python")
-        if(NOT python_interpreter)
-            message(FATAL_ERROR
-                "No Python interpreter could be found. Make sure python is in PATH.")
-        endif()
-    endif()
-endif()
-message(STATUS "Using python interpreter: ${python_interpreter}")
-
-# Macro to get various pyside / python include / link flags and paths.
-# Uses the not entirely supported utils/pyside_config.py file.
-macro(pyside_config option output_var)
-    if(${ARGC} GREATER 2)
-        set(is_list ${ARGV2})
-    else()
-        set(is_list "")
-    endif()
-
-    execute_process(
-      COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside_config.py"
-              ${option}
-      OUTPUT_VARIABLE ${output_var}
-      OUTPUT_STRIP_TRAILING_WHITESPACE)
-
-    if ("${${output_var}}" STREQUAL "")
-        message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.")
-    endif()
-    if(is_list)
-        string (REPLACE " " ";" ${output_var} "${${output_var}}")
-    endif()
-endmacro()
-
-# Query for the shiboken generator path, Python path, include paths and linker flags.
-pyside_config(--shiboken-module-path shiboken_module_path)
-pyside_config(--shiboken-generator-path shiboken_generator_path)
-pyside_config(--pyside-path pyside_path)
-pyside_config(--pyside-include-path pyside_include_dir 1)
-pyside_config(--python-include-path python_include_dir)
-pyside_config(--shiboken-generator-include-path shiboken_include_dir 1)
-pyside_config(--shiboken-module-shared-libraries-cmake shiboken_shared_libraries 0)
-pyside_config(--python-link-flags-cmake python_linking_data 0)
-pyside_config(--pyside-shared-libraries-cmake pyside_shared_libraries 0)
-
-set(shiboken_path "${shiboken_generator_path}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}")
-if(NOT EXISTS ${shiboken_path})
-    message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}")
-endif()
-
+# ================================== Dependency detection ======================================
+
+# Find required packages
+find_package(Python COMPONENTS Interpreter Development REQUIRED)
+# On RHEL and some other distros, Python wheels and site-packages may be installed under 'lib64'
+# instead of 'lib'. The FindPython CMake module may set Python_SITELIB to 'lib', which is incorrect
+# for these cases. To ensure compatibility, we override Python_SITELIB by querying Python directly.
+# This guarantees the correct site-packages path is used regardless of platform or Python build.
+execute_process(
+    COMMAND ${Python_EXECUTABLE} -c
+    "import site; print(next(p for p in site.getsitepackages() if 'site-packages' in p))"
+    OUTPUT_VARIABLE Python_SITELIB
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+list(APPEND CMAKE_PREFIX_PATH
+    "${Python_SITELIB}/shiboken6_generator/lib/cmake"
+)
+find_package(Shiboken6Tools REQUIRED)
 
 # ==================================== RPATH configuration ====================================
 
@@ -121,7 +79,7 @@ endif()
 # Enable rpaths so that the built shared libraries find their dependencies.
 set(CMAKE_SKIP_BUILD_RPATH FALSE)
 set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
-set(CMAKE_INSTALL_RPATH ${shiboken_module_path} ${CMAKE_CURRENT_SOURCE_DIR})
+set(CMAKE_INSTALL_RPATH ${CMAKE_CURRENT_SOURCE_DIR})
 set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
 # =============================================================================================
 # !!! End of dubious section.
@@ -165,81 +123,23 @@ set_property(TARGET ${wiggly_library} PROPERTY PREFIX "")
 # library can't link to the wiggly library.
 target_compile_definitions(${wiggly_library} PRIVATE BINDINGS_BUILD)
 
+target_link_libraries(${wiggly_library} PRIVATE Qt6::Widgets)
 
-# ====================== Shiboken target for generating binding C++ files  ====================
-
-
-# Set up the options to pass to shiboken.
-set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic
-    --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero
-    --avoid-protected-hack
-    ${INCLUDES}
-    -I${CMAKE_SOURCE_DIR}
-    -T${CMAKE_SOURCE_DIR}
-    -T${pyside_path}/typesystems
-    --output-directory=${CMAKE_CURRENT_BINARY_DIR}
-    )
-
-set(generated_sources_dependencies ${wrapped_header} ${typesystem_file})
-
-# Add custom target to run shiboken to generate the binding cpp files.
-add_custom_command(OUTPUT ${generated_sources}
-                    COMMAND ${shiboken_path}
-                    ${shiboken_options} ${wrapped_header} ${typesystem_file}
-                    DEPENDS ${generated_sources_dependencies}
-                    #IMPLICIT_DEPENDS CXX ${wrapped_header}
-                    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
-                    COMMENT "Running generator for ${typesystem_file}.")
-
-
-# =============================== CMake target - bindings_library =============================
-
-
-# Set the cpp files which will be used for the bindings library.
-set(${bindings_library}_sources ${generated_sources})
 
-# Define and build the bindings library.
-add_library(${bindings_library} SHARED ${${bindings_library}_sources})
+# ====================== Shiboken target for generating binding C++ files  ====================
 
+# Define Qt modules needed
+set(qt_modules Core Gui Widgets)
 
-# Apply relevant include and link flags.
-target_include_directories(${bindings_library} PRIVATE ${pyside_additional_includes})
-target_include_directories(${bindings_library} PRIVATE ${pyside_include_dir})
-target_include_directories(${bindings_library} PRIVATE ${python_include_dir})
-target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir})
-
-target_link_libraries(${wiggly_library} PRIVATE Qt6::Widgets)
-target_link_libraries(${bindings_library} PRIVATE Qt6::Widgets)
-target_link_libraries(${bindings_library} PRIVATE ${wiggly_library})
-target_link_libraries(${bindings_library} PRIVATE ${pyside_shared_libraries})
-target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries})
-
-# Adjust the name of generated module.
-set_property(TARGET ${bindings_library} PROPERTY PREFIX "")
-set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME
-             "${bindings_library}${PYTHON_EXTENSION_SUFFIX}")
-if(WIN32)
-    if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
-        set_property(TARGET ${bindings_library} PROPERTY SUFFIX "_d.pyd")
-    else()
-        set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd")
-    endif()
-endif()
-
-# Make sure the linker doesn't complain about not finding Python symbols on macOS.
-if(APPLE)
-  set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
-endif(APPLE)
-
-# Find and link to the python import library only on Windows.
-# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker
-# (the symbols will be picked up in the Python executable).
-if (WIN32)
-    list(GET python_linking_data 0 python_libdir)
-    list(GET python_linking_data 1 python_lib)
-    find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir})
-    target_link_libraries(${bindings_library} PRIVATE ${python_link_flags})
-endif()
+# Create Python bindings using Shiboken6Tools function
+shiboken_generator_create_binding(
+    EXTENSION_TARGET ${bindings_library}
+    GENERATED_SOURCES ${generated_sources}
+    HEADERS ${wrapped_header}
+    TYPESYSTEM_FILE ${typesystem_file}
+    LIBRARY_TARGET ${wiggly_library}
+    QT_MODULES Core Gui Widgets
+)
 
 # ================================= Dubious deployment section ================================
 
@@ -266,15 +166,14 @@ if(WIN32)
     set_target_properties(${bindings_library}
                            PROPERTIES LINK_FLAGS "${python_additional_link_flags}")
 
-    # Compile a list of shiboken shared libraries to be installed, so that
-    # the user doesn't have to set the PATH manually to point to the PySide package.
-    foreach(library_path ${shiboken_shared_libraries})
-        string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path})
-        file(TO_CMAKE_PATH ${library_path} library_path)
-        list(APPEND windows_shiboken_shared_libraries "${library_path}")
-    endforeach()
-    # =========================================================================================
-    # !!! End of dubious section.
+    # Get the correct DLL path for the current build type
+    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+        get_target_property(dll_path Shiboken6::libshiboken IMPORTED_LOCATION_DEBUG)
+    else()
+        get_target_property(dll_path Shiboken6::libshiboken IMPORTED_LOCATION_RELEASE)
+    endif()
+    file(TO_CMAKE_PATH "${dll_path}" dll_path)
+    set(windows_shiboken_shared_libraries "${dll_path}")
     # =========================================================================================
 endif()
 
index 910961b1e473a2fc042f49a18a9c2b2bbff2cc3d..5f8232fe8fd7222b940d22bbd02456e306420e91 100644 (file)
@@ -34,8 +34,6 @@ The most important files are:
  * `bindings.h` to include the header of the classes we want to expose
  * `CMakeList.txt`, with all the instructions to build the shared libraries
    (DLL, or dylib)
- * `pyside_config.py` which is located in the utils directory, one level
-   up, to get the path for Shiboken and PySide.
 
 Now create a `build/` directory, and from inside run `cmake` to use
 the provided `CMakeLists.txt`:
index da4219d88960e8d1a7bed63f67397be3a0e6950c..ce3f1faec9b3ba15dea62e8fa5e22208906b2054 100644 (file)
@@ -8,6 +8,5 @@
               "../wigglywidget.cpp",
               "../wigglywidget.h",
               "../wigglywidget.py",
-              "../CMakeLists.txt",
-              "../../utils/pyside_config.py"]
+              "../CMakeLists.txt"]
 }
index 5ae9c0be66ddd61217b023cf81a33eb1a6df1a51..ccb29f387e4f427c4eb1472713278816ab61d221 100644 (file)
@@ -22,9 +22,21 @@ class PathType(IntEnum):
     CIRCLE_PATH = 1
 
 
+def createEasingCurve(curveType):
+    curve = QEasingCurve(curveType)
+    if curveType == QEasingCurve.Type.BezierSpline:
+        curve.addCubicBezierSegment(QPointF(0.4, 0.1), QPointF(0.6, 0.9), QPointF(1.0, 1.0))
+    elif curveType == QEasingCurve.Type.TCBSpline:
+        curve.addTCBSegment(QPointF(0.0, 0.0), 0, 0, 0)
+        curve.addTCBSegment(QPointF(0.3, 0.4), 0.2, 1, -0.2)
+        curve.addTCBSegment(QPointF(0.7, 0.6), -0.2, 1, 0.2)
+        curve.addTCBSegment(QPointF(1.0, 1.0), 0, 0, 0)
+    return curve
+
+
 class Animation(QPropertyAnimation):
-    def __init__(self, target, prop):
-        super().__init__(target, prop)
+    def __init__(self, target, prop, parent=None):
+        super().__init__(target, prop, parent)
         self.set_path_type(PathType.LINEAR_PATH)
 
     def set_path_type(self, pathType):
@@ -109,7 +121,7 @@ class Window(QWidget):
         self._scene.addItem(self._item.pixmap_item)
         self._ui.graphicsView.setScene(self._scene)
 
-        self._anim = Animation(self._item, b'pos')
+        self._anim = Animation(self._item, b'pos', self)
         self._anim.setEasingCurve(QEasingCurve.Type.OutBounce)
         self._ui.easingCurvePicker.setCurrentRow(0)
 
@@ -124,13 +136,15 @@ class Window(QWidget):
 
         brush = QBrush(gradient)
 
-        curve_types = [(f"QEasingCurve.{e.name}", e) for e in QEasingCurve.Type if e.value <= 40]
+        curve_count = QEasingCurve.Type.Custom.value
+        curve_types = [(f"QEasingCurve.{e.name}", e)
+                       for e in QEasingCurve.Type if e.value < curve_count]
 
         with QPainter(pix) as painter:
 
             for curve_name, curve_type in curve_types:
                 painter.fillRect(QRect(QPoint(0, 0), self._iconSize), brush)
-                curve = QEasingCurve(curve_type)
+                curve = createEasingCurve(curve_type)
 
                 painter.setPen(QColor(0, 0, 255, 64))
                 x_axis = self._iconSize.height() / 1.5
@@ -180,7 +194,7 @@ class Window(QWidget):
 
     def curve_changed(self, row):
         curve_type = QEasingCurve.Type(row)
-        self._anim.setEasingCurve(curve_type)
+        self._anim.setEasingCurve(createEasingCurve(curve_type))
         self._anim.setCurrentTime(0)
 
         is_elastic = (curve_type.value >= QEasingCurve.Type.InElastic.value
index 7bd68620a4226456a0e869b62e94e6291019ee52..541aab305de6ce16d2666d0cd284d4ea45cc4eee 100644 (file)
@@ -8,7 +8,7 @@ from __future__ import annotations
 import sys
 from textwrap import dedent
 
-from PySide6.QtCore import QDir, Qt, Slot
+from PySide6.QtCore import QDir, QLibraryInfo, QLocale, QTranslator, Qt, Slot
 from PySide6.QtGui import QFont, QPalette
 from PySide6.QtWidgets import (QApplication, QColorDialog, QCheckBox, QDialog,
                                QErrorMessage, QFontDialog, QFileDialog, QFrame,
@@ -432,6 +432,10 @@ class Dialog(QDialog):
 
 if __name__ == '__main__':
     app = QApplication(sys.argv)
+    translator = QTranslator(app)
+    if translator.load(QLocale.system(), "qtbase", "_",
+                       QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)):
+        app.installTranslator(translator)
     dialog = Dialog()
     availableGeometry = dialog.screen().availableGeometry()
     dialog.resize(availableGeometry.width() / 3, availableGeometry.height() * 2 / 3)
index 504e6bd02cbd126c1b1be894c16cdc815023bcf7..b64c499ecaaec6b827921e8ab25527eb66f71306 100644 (file)
@@ -7,7 +7,7 @@ from __future__ import annotations
    originating from PyQt"""
 
 from PySide6.QtCore import QFile, QIODevice, QMimeData, QPoint, Qt, QTextStream
-from PySide6.QtGui import QDrag, QPalette, QPixmap
+from PySide6.QtGui import QDrag, QPixmap
 from PySide6.QtWidgets import QApplication, QFrame, QLabel, QWidget
 
 import draggabletext_rc  # noqa: F401
@@ -65,10 +65,6 @@ class DragWidget(QWidget):
                 x = 5
                 y += word_label.height() + 2
 
-        new_palette = self.palette()
-        new_palette.setColor(QPalette.ColorRole.Window, Qt.GlobalColor.white)
-        self.setPalette(new_palette)
-
         self.setAcceptDrops(True)
         self.setMinimumSize(400, max(200, y))
         self.setWindowTitle("Draggable Text")
diff --git a/examples/widgets/itemviews/rangemodel/doc/rangemodel.rst b/examples/widgets/itemviews/rangemodel/doc/rangemodel.rst
new file mode 100644 (file)
index 0000000..8617620
--- /dev/null
@@ -0,0 +1,10 @@
+QRangeModel Example
+===================
+
+A Python application that demonstrates how to populate
+a :class:`~PySide6.QtCore.QRangeModel` using
+`numpy <https://numpy.org>`_ or Python lists.
+
+Models created from numpy arrays are editable.
+
+It requires building PySide6 with the ``--pyside-numpy-support`` option.
diff --git a/examples/widgets/itemviews/rangemodel/main.py b/examples/widgets/itemviews/rangemodel/main.py
new file mode 100644 (file)
index 0000000..485f8a6
--- /dev/null
@@ -0,0 +1,82 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import numpy
+import sys
+
+from PySide6.QtCore import QRangeModel
+from PySide6.QtGui import QKeySequence
+from PySide6.QtWidgets import QApplication, QListView, QMainWindow, QTableView, QTabWidget
+
+
+STRING_LIST = ["item1", "item2", "item3", "item4"]
+INT_LIST = [1, 2, 3]
+INT_TABLE = [[1, 2], [3, 4], [5, 6]]
+
+NP_INT_ARRAY = numpy.array([1, 2, 3], dtype=numpy.int32)
+NP_DOUBLE_ARRAY = numpy.array([1.1, 2.2, 3.3], dtype=numpy.double)
+
+NP_INT_TABLE = numpy.array([[1, 2, 3], [4, 5, 6]], dtype=numpy.int32)
+NP_DOUBLE_TABLE = numpy.array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6]], dtype=numpy.double)
+
+
+def print_numpy_data():
+    print("--------------------------------")
+    print("NP_INT_ARRAY=", NP_INT_ARRAY)
+    print("NP_INT_TABLE=", NP_INT_TABLE)
+    print("NP_DOUBLE_ARRAY=", NP_DOUBLE_ARRAY)
+    print("NP_DOUBLE_TABLE=", NP_DOUBLE_TABLE)
+    print("---------------------------------\n")
+
+
+if __name__ == '__main__':
+    app = QApplication(sys.argv)
+    window = QMainWindow()
+    window.setMinimumWidth(600)
+    file_menu = window.menuBar().addMenu("File")
+    file_menu.addAction("Output numpy data", print_numpy_data)
+    file_menu.addAction("Quit", QKeySequence(QKeySequence.StandardKey.Quit), window.close)
+    help_menu = window.menuBar().addMenu("Help")
+    help_menu.addAction("About Qt", app.aboutQt)
+
+    tab_widget = QTabWidget()
+    window.setCentralWidget(tab_widget)
+
+    list_view = QListView()
+    model = QRangeModel(STRING_LIST)
+    list_view.setModel(model)
+    tab_widget.addTab(list_view, "Python String List")
+
+    list_view = QListView()
+    model = QRangeModel(INT_LIST)
+    list_view.setModel(model)
+    tab_widget.addTab(list_view, "Python int List")
+
+    table_view = QTableView()
+    model = QRangeModel(INT_TABLE)
+    table_view.setModel(model)
+    tab_widget.addTab(table_view, "Python Int Table")
+
+    list_view = QListView()
+    model = QRangeModel(NP_INT_ARRAY)
+    list_view.setModel(model)
+    tab_widget.addTab(list_view, "Numpy Int List")
+
+    list_view = QListView()
+    model = QRangeModel(NP_DOUBLE_ARRAY)
+    list_view.setModel(model)
+    tab_widget.addTab(list_view, "Numpy Double List")
+
+    table_view = QTableView()
+    model = QRangeModel(NP_INT_TABLE)
+    table_view.setModel(model)
+    tab_widget.addTab(table_view, "Numpy Int Table")
+
+    table_view = QTableView()
+    model = QRangeModel(NP_DOUBLE_TABLE)
+    table_view.setModel(model)
+    tab_widget.addTab(table_view, "Numpy Double Table")
+
+    window.setWindowTitle("QRangeModel")
+    window.show()
+    sys.exit(app.exec())
diff --git a/examples/widgets/itemviews/rangemodel/rangemodel.pyproject b/examples/widgets/itemviews/rangemodel/rangemodel.pyproject
new file mode 100644 (file)
index 0000000..cc7a74a
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py"]
+}
index 341a7f4ded75d21da7bd4211d5217872eede7fb3..db3abe34a97ebcb406afb2c64a4e6740a436b5ed 100644 (file)
@@ -15,8 +15,6 @@ from PySide6.QtGui import QAction, QIcon, QKeySequence
 from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow,
                                QMdiArea, QMessageBox, QTextEdit)
 
-import PySide6.QtExampleIcons  # noqa: F401
-
 
 class MdiChild(QTextEdit):
     sequence_number = 1
@@ -435,10 +433,6 @@ if __name__ == '__main__':
 
     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)
index 972ffbaa06c85ace554473ece01f2052c1a36c1c..1f59c15707cb6851bf0eec7769639c50a652d906 100644 (file)
@@ -34,7 +34,7 @@ COMPUTER_ICON = ":/qt-project.org/styles/commonstyle/images/computer-32.png"
 SYSTEMINFO = """<html><head/><body>
 <h3>Python</h3><p>{}</p>
 <h3>Qt Build</h3><p>{}</p>
-<h3>Operating System</h3><p>{}</p>
+<h3>Operating System</h3><p>"{}" / {}</p>
 <h3>Screens</h3>
 {}
 </body></html>"""
@@ -396,6 +396,7 @@ class WidgetGallery(QDialog):
         system_info = SYSTEMINFO.format(sys.version,
                                         QLibraryInfo.build(),
                                         QSysInfo.prettyProductName(),
+                                        QGuiApplication.platformName(),
                                         screen_info(self))
         self._systeminfo_textbrowser.setHtml(system_info)
 
index 4f778acbd1c4a093d1448c5e3f66d1af8c74a347..f02251849aee8a6b342258cc0c557431d24181e2 100644 (file)
@@ -112,11 +112,12 @@ class XbelTree(QTreeWidget):
         self._bookmark_icon.addPixmap(style.standardPixmap(QStyle.StandardPixmap.SP_FileIcon))
 
     def read(self, device):
-        ok, errorStr, errorLine, errorColumn = self._dom_document.setContent(device, True)
-        if not ok:
+        result = self._dom_document.setContent(device,
+                                               QDomDocument.ParseOption.UseNamespaceProcessing)
+        if not result:
             QMessageBox.information(self.window(), "DOM Bookmarks",
-                                    f"Parse error at line {errorLine}, "
-                                    f"column {errorColumn}:\n{errorStr}")
+                                    f"Parse error at line {result.errorLine}, "
+                                    f"column {result.errorColumn}:\n{result.errorMessage}")
             return False
 
         root = self._dom_document.documentElement()
index 7e795c4f5fcedd198d30fd37abbcd9d43efb8d03..a7d4d54431b6f8de5935333ae3a978e3be49f295 100644 (file)
@@ -1,5 +1,6 @@
 sphinx==7.4.7
 sphinx-design==0.6.0
+sphinx-collapse
 sphinx-copybutton==0.5.2
 sphinx-tags==0.4
 sphinx-toolbox==3.7.0
index d89da4f1b40c5fd1faeac9de0d3efd74529c6845..37b38805f76af2ecbbbe3f70674609cbc013580b 100644 (file)
@@ -20,10 +20,16 @@ macro(create_and_install_qt_javabindings)
         ${android_main_srcs}/QtService.java
     )
     # set android.jar from the sdk, for compiling the java files into .jar
-    set(sdk_jar_location "${ANDROID_SDK_ROOT}/platforms/android-${CMAKE_ANDROID_API}/android.jar")
-    file(GLOB sources_list LIST_DIRECTORIES true "${ANDROID_SDK_ROOT}/platforms/android-${CMAKE_ANDROID_API}/*")
+    # Use ANDROID_API_VERSION from environment if set, otherwise fall back to CMAKE_ANDROID_API
+    if(DEFINED ENV{ANDROID_API_VERSION})
+        set(ANDROID_SDK_API_LEVEL "$ENV{ANDROID_API_VERSION}")
+    else()
+        set(ANDROID_SDK_API_LEVEL "android-${CMAKE_ANDROID_API}")
+    endif()
+    set(sdk_jar_location "${ANDROID_SDK_ROOT}/platforms/${ANDROID_SDK_API_LEVEL}/android.jar")
+    file(GLOB sources_list LIST_DIRECTORIES true "${ANDROID_SDK_ROOT}/platforms/${ANDROID_SDK_API_LEVEL}/*")
     if (NOT EXISTS "${sdk_jar_location}")
-        message(FATAL_ERROR "Could not locate Android SDK jar for api '${CMAKE_ANDROID_API}' - ${sdk_jar_location}")
+        message(FATAL_ERROR "Could not locate Android SDK jar for api '${ANDROID_SDK_API_LEVEL}' - ${sdk_jar_location}")
     endif()
 
     # this variable is accessed by qt_internal_add_jar
index fafe0d67d40420e7d9086a2708ad26e2bba7982a..6da7b455eead36817ec567e8090bbfc467f91f6c 100644 (file)
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
+import os
 import sys
 # TODO: Remove this import when Python 3.11 is the minimum supported version
 if sys.version_info >= (3, 11):
@@ -48,19 +49,19 @@ def _parse_toml_content(content: str) -> dict:
     return result
 
 
-def _write_toml_content(data: dict) -> str:
+def _write_base_toml_content(data: dict) -> str:
     """
     Write minimal TOML content with project and tool.pyside6-project sections.
     """
     lines = []
 
-    if 'project' in data and data['project']:
+    if data.get('project'):
         lines.append('[project]')
         for key, value in sorted(data['project'].items()):
             if isinstance(value, str):
                 lines.append(f'{key} = "{value}"')
 
-    if 'tool' in data and 'pyside6-project' in data['tool']:
+    if data.get("tool") and data['tool'].get('pyside6-project'):
         lines.append('\n[tool.pyside6-project]')
         for key, value in sorted(data['tool']['pyside6-project'].items()):
             if isinstance(value, list):
@@ -115,7 +116,7 @@ def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult:
 
 def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]):
     """
-    Create or update a pyproject.toml file with the specified content.
+    Create or overwrite a pyproject.toml file with the specified content.
     """
     data = {
         "project": {"name": project_name},
@@ -124,13 +125,33 @@ def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files:
         }
     }
 
+    content = _write_base_toml_content(data)
     try:
-        content = _write_toml_content(data)
         pyproject_file.write_text(content, encoding='utf-8')
     except Exception as e:
         raise ValueError(f"Error writing TOML file: {str(e)}")
 
 
+def robust_relative_to_posix(target_path: Path, base_path: Path) -> str:
+    """
+    Calculates the relative path from base_path to target_path.
+    Uses Path.relative_to first, falls back to os.path.relpath if it fails.
+    Returns the result as a POSIX path string.
+    """
+    # Ensure both paths are absolute for reliable calculation, although in this specific code,
+    # project_folder and paths in output_files are expected to be resolved/absolute already.
+    abs_target = target_path.resolve() if not target_path.is_absolute() else target_path
+    abs_base = base_path.resolve() if not base_path.is_absolute() else base_path
+
+    try:
+        return abs_target.relative_to(abs_base).as_posix()
+    except ValueError:
+        # Fallback to os.path.relpath which is more robust for paths that are not direct subpaths.
+        relative_str = os.path.relpath(str(abs_target), str(abs_base))
+        # Convert back to Path temporarily to get POSIX format
+        return Path(relative_str).as_posix()
+
+
 def migrate_pyproject(pyproject_file: Path | str = None) -> int:
     """
     Migrate a project *.pyproject JSON file to the new pyproject.toml format.
@@ -170,7 +191,7 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int:
             project_name = project_files[0].stem
 
     # The project files that will be written to the pyproject.toml file
-    output_files = set()
+    output_files: set[Path] = set()
     for project_file in project_files:
         project_data = parse_pyproject_json(project_file)
         if project_data.errors:
@@ -185,39 +206,58 @@ def migrate_pyproject(pyproject_file: Path | str = None) -> int:
         project_name = project_folder.name
 
     pyproject_toml_file = project_folder / "pyproject.toml"
-    if pyproject_toml_file.exists():
-        already_existing_file = True
+
+    relative_files = sorted(
+        robust_relative_to_posix(p, project_folder) for p in output_files
+    )
+
+    if not (already_existing_file := pyproject_toml_file.exists()):
+        # Create new pyproject.toml file
+        data = {
+            "project": {"name": project_name},
+            "tool": {
+                "pyside6-project": {"files": relative_files}
+            }
+        }
+        updated_content = _write_base_toml_content(data)
+    else:
+        # For an already existing file, append our tool.pyside6-project section
+        # If the project section is missing, add it
         try:
             content = pyproject_toml_file.read_text(encoding='utf-8')
-            data = _parse_toml_content(content)
         except Exception as e:
-            raise ValueError(f"Error parsing TOML: {str(e)}")
-    else:
-        already_existing_file = False
-        data = {"project": {}, "tool": {"pyside6-project": {}}}
+            print(f"Error processing existing TOML file: {str(e)}", file=sys.stderr)
+            return 1
 
-    # Update project name if not present
-    if "name" not in data["project"]:
-        data["project"]["name"] = project_name
+        append_content = []
 
-    # Update files list
-    data["tool"]["pyside6-project"]["files"] = sorted(
-        p.relative_to(project_folder).as_posix() for p in output_files
-    )
+        if '[project]' not in content:
+            # Add project section if needed
+            append_content.append('\n[project]')
+            append_content.append(f'name = "{project_name}"')
+
+        if '[tool.pyside6-project]' not in content:
+            # Add tool.pyside6-project section
+            append_content.append('\n[tool.pyside6-project]')
+            items = [f'"{item}"' for item in relative_files]
+            append_content.append(f'files = [{", ".join(items)}]')
 
-    # Generate TOML content
-    toml_content = _write_toml_content(data)
+        if append_content:
+            updated_content = content.rstrip() + '\n' + '\n'.join(append_content)
+        else:
+            # No changes needed
+            print("pyproject.toml already contains [project] and [tool.pyside6-project] sections")
+            return 0
 
-    if already_existing_file:
         print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"")
         print("The file will be updated with the following content:")
-        print(toml_content)
+        print(updated_content)
         response = input("Proceed? [Y/n] ")
         if response.lower().strip() not in {"yes", "y"}:
             return 0
 
     try:
-        pyproject_toml_file.write_text(toml_content)
+        pyproject_toml_file.write_text(updated_content, encoding='utf-8')
     except Exception as e:
         print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr)
         return 1
index f1458e935475c19b07de2a237b7f628b5e5b1f4a..ffe43a7b9f1ee05dae684624061febceeb2efdf1 100644 (file)
@@ -1,5 +1,5 @@
 set(pyside_MAJOR_VERSION "6")
-set(pyside_MINOR_VERSION "9")
+set(pyside_MINOR_VERSION "10")
 set(pyside_MICRO_VERSION "2")
 set(pyside_PRE_RELEASE_VERSION_TYPE "")
 set(pyside_PRE_RELEASE_VERSION "")
index 9c238e9807b8958ed5b2cf1133e0c4016a1abe0b..6b73d70abcf7c69b39fea0f64123c266bb50a2db 100644 (file)
@@ -9,6 +9,8 @@ cmake_policy(VERSION 3.18)
 include(".cmake.conf")
 project(pysidebindings)
 
+set(QT_NO_PRIVATE_MODULE_WARNING ON)
+
 include(cmake/PySideSetup.cmake)
 
 get_rpath_base_token(base)
@@ -29,6 +31,19 @@ if(Qt${QT_MAJOR_VERSION}RemoteObjects_FOUND)
     add_subdirectory(libpysideremoteobjects)
 endif()
 
+# build-time export set for PySide6 full build
+install(EXPORT PySide6Targets
+        NAMESPACE PySide6::
+        DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6")
+
+# wheel export set
+if(NOT is_pyside6_superproject_build)
+    install(EXPORT PySide6WheelTargets
+            NAMESPACE PySide6::
+            DESTINATION "${LIB_INSTALL_DIR}/wheels/cmake/PySide6"
+            FILE PySide6Targets.cmake)
+endif()
+
 if(Qt${QT_MAJOR_VERSION}UiTools_FOUND)
     add_subdirectory(plugins/uitools)
     find_package(Qt6 COMPONENTS Designer)
@@ -45,5 +60,3 @@ if(BUILD_TESTS)
 endif()
 
 add_subdirectory(doc)
-
-add_subdirectory(qtexampleicons)
index e5b0b672e14c44a5df40d44b49a1f9437e7fcd61..c1934d9ca7aa43eac44438f2033ca07b41c3125e 100644 (file)
@@ -4,9 +4,6 @@
 project(pyside6)
 
 # Configure include based on platform
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/global.h.in"
-               "${CMAKE_CURRENT_BINARY_DIR}/pyside6_global.h" @ONLY)
-
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in"
                "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" @ONLY)
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/_config.py.in"
@@ -50,8 +47,24 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/generate_pyi.py"
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/deprecated.py"
                "${CMAKE_CURRENT_BINARY_DIR}/support/deprecated.py" COPYONLY)
 
+# Additional (non-Qt) modules implemented in PySide only
+foreach(mod IN LISTS PURE_PYTHON_MODULES)
+    set(src_dir "${CMAKE_CURRENT_SOURCE_DIR}/Qt${mod}")
+    set(dst_dir "${CMAKE_CURRENT_BINARY_DIR}/Qt${mod}")
+
+    if(EXISTS "${src_dir}")
+        file(GLOB_RECURSE module_files "${src_dir}/*")
+
+        foreach(f ${module_files})
+            file(RELATIVE_PATH relpath "${src_dir}" "${f}")
+            set(dst "${dst_dir}/${relpath}")
+            configure_file("${f}" "${dst}" COPYONLY)
+        endforeach()
+    endif()
+endforeach()
+
 # now compile all modules.
-file(READ "${CMAKE_CURRENT_BINARY_DIR}/pyside6_global.h" pyside6_global_contents)
+file(READ "${CMAKE_CURRENT_LIST_DIR}/pyside6_global.h" pyside6_global_contents)
 
 foreach(shortname IN LISTS all_module_shortnames)
     set(name "Qt${QT_MAJOR_VERSION}${shortname}")
@@ -105,8 +118,8 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/widgets_common.xml
         DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
 install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/datavisualization_common.xml
         DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyside6_global.h
-        DESTINATION include/${BINDING_NAME}${pyside6_SUFFIX})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/pyside6_global.h
+        DESTINATION ${BINDING_NAME}${pyside6_SUFFIX}/include)
 
 install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/QtAsyncio"
         DESTINATION "${PYTHON_SITE_PACKAGES}/${BINDING_NAME}${pyside6_SUFFIX}")
index 65f3ccbc3d1815b08c8a4ae5ea06f40c4af61546..6e2088454fc774c7a1dab3e8e3f8a689202bb9f5 100644 (file)
@@ -8,7 +8,7 @@ from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer,
 from . import futures
 from . import tasks
 
-from typing import Any, Callable
+from typing import Any, Callable, TypeVar
 
 import asyncio
 import collections.abc
@@ -26,6 +26,22 @@ __all__ = [
     "QAsyncioHandle", "QAsyncioTimerHandle",
 ]
 
+from typing import TYPE_CHECKING
+
+_T = TypeVar("_T")
+
+if TYPE_CHECKING:
+    try:
+        from typing import TypeVarTuple, Unpack
+    except ImportError:
+        from typing_extensions import TypeVarTuple, Unpack  # type: ignore
+
+    _Ts = TypeVarTuple("_Ts")
+    Context = contextvars.Context   # type: ignore
+else:
+    _Ts = None  # type: ignore
+    Context = contextvars.Context
+
 
 class QAsyncioExecutorWrapper(QObject):
     """
@@ -41,14 +57,13 @@ class QAsyncioExecutorWrapper(QObject):
     the executor thread, and then creates a zero-delay singleshot timer to push
     the actual callable for the executor into this new event loop.
     """
-
-    def __init__(self, func: Callable, *args: tuple) -> None:
+    def __init__(self, func: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None:
         super().__init__()
         self._loop: QEventLoop
         self._func = func
         self._args = args
-        self._result = None
-        self._exception = None
+        self._result: Any = None
+        self._exception: BaseException | None = None
 
     def _cb(self):
         try:
@@ -59,7 +74,7 @@ class QAsyncioExecutorWrapper(QObject):
             self._exception = e
         self._loop.exit()
 
-    def do(self):
+    def do(self) -> Any:
         # This creates a new event loop and dispatcher for the thread, if not
         # already created.
         self._loop = QEventLoop()
@@ -326,7 +341,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         if timeout is not None:
             deadline_timer = QDeadlineTimer(int(timeout * 1000))
         else:
-            deadline_timer = QDeadlineTimer(QDeadlineTimer.Forever)
+            deadline_timer = QDeadlineTimer(QDeadlineTimer.ForeverConstant.Forever)
 
         if self._default_executor is None:
             return
@@ -346,50 +361,50 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
 
     # Scheduling callbacks
 
-    def _call_soon_impl(self, callback: Callable, *args: Any,
-                        context: contextvars.Context | None = None,
+    def _call_soon_impl(self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts],
+                        context: Context | None = None,
                         is_threadsafe: bool | None = False) -> asyncio.Handle:
         return self._call_later_impl(0, callback, *args, context=context,
                                      is_threadsafe=is_threadsafe)
 
-    def call_soon(self, callback: Callable, *args: Any,
-                  context: contextvars.Context | None = None) -> asyncio.Handle:
+    def call_soon(self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts],
+                  context: Context | None = None) -> asyncio.Handle:
         return self._call_soon_impl(callback, *args, context=context, is_threadsafe=False)
 
-    def call_soon_threadsafe(self, callback: Callable, *args: Any,
-                             context: contextvars.Context | None = None) -> asyncio.Handle:
+    def call_soon_threadsafe(self, callback: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts],
+                             context: Context | None = None) -> asyncio.Handle:
         if self.is_closed():
             raise RuntimeError("Event loop is closed")
         if context is None:
             context = contextvars.copy_context()
         return self._call_soon_impl(callback, *args, context=context, is_threadsafe=True)
 
-    def _call_later_impl(self, delay: int | float, callback: Callable, *args: Any,
-                         context: contextvars.Context | None = None,
+    def _call_later_impl(self, delay: float, callback: Callable[[Unpack[_Ts]], object],
+                         *args: Unpack[_Ts], context: Context | None = None,
                          is_threadsafe: bool | None = False) -> asyncio.TimerHandle:
         if not isinstance(delay, (int, float)):
             raise TypeError("delay must be an int or float")
-        return self._call_at_impl(self.time() + delay, callback, *args, context=context,
-                                  is_threadsafe=is_threadsafe)
+        return self._call_at_impl(self.time() + delay, callback, *args,
+                                  context=context, is_threadsafe=is_threadsafe)
 
-    def call_later(self, delay: int | float, callback: Callable, *args: Any,
-                   context: contextvars.Context | None = None) -> asyncio.TimerHandle:
+    def call_later(self, delay: float, callback: Callable[[Unpack[_Ts]], object],
+                   *args: Unpack[_Ts], context: Context | None = None) -> asyncio.TimerHandle:
         return self._call_later_impl(delay, callback, *args, context=context, is_threadsafe=False)
 
-    def _call_at_impl(self, when: int | float, callback: Callable, *args: Any,
-                      context: contextvars.Context | None = None,
+    def _call_at_impl(self, when: float, callback: Callable[[Unpack[_Ts]], object],
+                      *args: Unpack[_Ts], context: Context | None = None,
                       is_threadsafe: bool | None = False) -> asyncio.TimerHandle:
         """ All call_at() and call_later() methods map to this method. """
         if not isinstance(when, (int, float)):
             raise TypeError("when must be an int or float")
         return QAsyncioTimerHandle(when, callback, args, self, context, is_threadsafe=is_threadsafe)
 
-    def call_at(self, when: int | float, callback: Callable, *args: Any,
-                context: contextvars.Context | None = None) -> asyncio.TimerHandle:
+    def call_at(self, when: float, callback: Callable[[Unpack[_Ts]], object],
+                *args: Unpack[_Ts], context: Context | None = None) -> asyncio.TimerHandle:
         return self._call_at_impl(when, callback, *args, context=context, is_threadsafe=False)
 
     def time(self) -> float:
-        return QDateTime.currentMSecsSinceEpoch() / 1000
+        return QDateTime.currentMSecsSinceEpoch() / 1000.0
 
     # Creating Futures and Tasks
 
@@ -560,9 +575,9 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
 
     # Executing code in thread or process pools
 
-    def run_in_executor(self,
-                        executor: concurrent.futures.ThreadPoolExecutor | None,
-                        func: Callable, *args: tuple) -> asyncio.futures.Future:
+    def run_in_executor(self, executor: concurrent.futures.ThreadPoolExecutor | None,
+                        func: Callable[[Unpack[_Ts]], _T],
+                        *args: Unpack[_Ts]) -> asyncio.Future[_T]:
         if self.is_closed():
             raise RuntimeError("Event loop is closed")
         if executor is None:
@@ -575,9 +590,7 @@ class QAsyncioEventLoop(asyncio.BaseEventLoop, QObject):
         # attaches a QEventLoop to the executor thread, and then pushes the
         # actual callable for the executor into this new event loop.
         wrapper = QAsyncioExecutorWrapper(func, *args)
-        return asyncio.futures.wrap_future(
-            executor.submit(wrapper.do), loop=self
-        )
+        return asyncio.futures.wrap_future(executor.submit(wrapper.do), loop=self)
 
     def set_default_executor(self,
                              executor: concurrent.futures.ThreadPoolExecutor | None) -> None:
@@ -649,13 +662,11 @@ class QAsyncioHandle():
                  loop: QAsyncioEventLoop, context: contextvars.Context | None,
                  is_threadsafe: bool | None = False) -> None:
         self._callback = callback
-        self._args = args
+        self._cb_args = args  # renamed from _args to avoid conflict with TimerHandle._args
         self._loop = loop
         self._context = context
         self._is_threadsafe = is_threadsafe
-
         self._timeout = 0
-
         self._state = QAsyncioHandle.HandleState.PENDING
         self._start()
 
@@ -685,9 +696,9 @@ class QAsyncioHandle():
         """
         if self._state == QAsyncioHandle.HandleState.PENDING:
             if self._context is not None:
-                self._context.run(self._callback, *self._args)
+                self._context.run(self._callback, *self._cb_args)
             else:
-                self._callback(*self._args)
+                self._callback(*self._cb_args)
             self._state = QAsyncioHandle.HandleState.DONE
 
     def cancel(self) -> None:
index deabf690d81a211089665e1aae8d54a38296f07d..c6cb3a10504ad3c1e71fa69f8c640ca16dbdc426 100644 (file)
@@ -6,7 +6,7 @@ from . import events
 from . import futures
 import traceback
 
-from typing import Any
+from typing import Any, Optional
 
 import asyncio
 import collections.abc
@@ -21,6 +21,10 @@ class QAsyncioTask(futures.QAsyncioFuture):
                  loop: "events.QAsyncioEventLoop | None" = None, name: str | None = None,
                  context: contextvars.Context | None = None) -> None:
         super().__init__(loop=loop, context=context)
+        self._source_traceback = None  # required for Python < 3.11
+
+        self._state: futures.QAsyncioFuture.FutureState = futures.QAsyncioFuture.FutureState.PENDING
+        self._exception: Optional[BaseException] = None
 
         self._coro = coro  # The coroutine for which this task was created.
         self._name = name if name else "QtTask"
@@ -40,12 +44,13 @@ class QAsyncioTask(futures.QAsyncioFuture):
         self._cancel_count = 0
         self._cancel_message: str | None = None
         # Store traceback in case of Exception. Useful when exception happens in coroutine
-        self._tb: str = None
+        self._tb: str | None = None
 
         # https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
         asyncio._register_task(self)  # type: ignore[arg-type]
 
     def __repr__(self) -> str:
+        state: str = "Unknown"
         if self._state == futures.QAsyncioFuture.FutureState.PENDING:
             state = "Pending"
         elif self._state == futures.QAsyncioFuture.FutureState.DONE_WITH_RESULT:
index d559f9d966aecfb40c0e3be510592c0b5ad34819..2f49b610e7ff53f9d83517ced00a3e195d46ec42 100644 (file)
@@ -15,13 +15,10 @@ set(QtCore_static_sources
     "${pyside6_SOURCE_DIR}/qiopipe.h"
 )
 
-if(ENABLE_WIN)
+if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
     set(SPECIFIC_OS_FILES
       ${QtCore_GEN_DIR}/qwineventnotifier_wrapper.cpp
     )
-else()
-    set(SPECIFIC_OS_FILES
-      ${QtCore_GEN_DIR}/qprocess_unixprocessparameters_wrapper.cpp)
 endif()
 
 set(QtCore_SRC
@@ -82,6 +79,7 @@ ${QtCore_GEN_DIR}/qfileselector_wrapper.cpp
 ${QtCore_GEN_DIR}/qfilesystemwatcher_wrapper.cpp
 ${QtCore_GEN_DIR}/qfutureinterfacebase_wrapper.cpp
 ${QtCore_GEN_DIR}/qgenericargument_wrapper.cpp
+${QtCore_GEN_DIR}/qrangemodel_wrapper.cpp
 ${QtCore_GEN_DIR}/qgenericreturnargument_wrapper.cpp
 ${QtCore_GEN_DIR}/qhashseed_wrapper.cpp
 ${QtCore_GEN_DIR}/qidentityproxymodel_wrapper.cpp
@@ -130,8 +128,6 @@ ${QtCore_GEN_DIR}/qpersistentmodelindex_wrapper.cpp
 ${QtCore_GEN_DIR}/qpluginloader_wrapper.cpp
 ${QtCore_GEN_DIR}/qpoint_wrapper.cpp
 ${QtCore_GEN_DIR}/qpointf_wrapper.cpp
-${QtCore_GEN_DIR}/qprocess_wrapper.cpp
-${QtCore_GEN_DIR}/qprocessenvironment_wrapper.cpp
 ${QtCore_GEN_DIR}/qpropertyanimation_wrapper.cpp
 ${QtCore_GEN_DIR}/qrandomgenerator64_wrapper.cpp
 ${QtCore_GEN_DIR}/qrandomgenerator_wrapper.cpp
@@ -236,6 +232,23 @@ else()
     list(APPEND QtCore_SRC ${QtCore_GEN_DIR}/qsharedmemory_wrapper.cpp)
 endif()
 
+if("process" IN_LIST QtCore_disabled_features)
+    list(APPEND QtCore_DROPPED_ENTRIES QProcess)
+    message(STATUS "Qt${QT_MAJOR_VERSION}Core: Dropping QProcess")
+else()
+    list(APPEND QtCore_SRC ${QtCore_GEN_DIR}/qprocess_wrapper.cpp)
+    if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows")
+        list(APPEND QtCore_SRC ${QtCore_GEN_DIR}/qprocess_unixprocessparameters_wrapper.cpp)
+    endif()
+endif()
+
+if("processenvironment" IN_LIST QtCore_disabled_features)
+    list(APPEND QtCore_DROPPED_ENTRIES QProcessEnvironment)
+    message(STATUS "Qt${QT_MAJOR_VERSION}Core: Dropping QProcessEnvironment")
+else()
+    list(APPEND QtCore_SRC ${QtCore_GEN_DIR}/qprocessenvironment_wrapper.cpp)
+endif()
+
 configure_file("${QtCore_SOURCE_DIR}/QtCore_global.post.h.in"
                "${QtCore_BINARY_DIR}/QtCore_global.post.h" @ONLY)
 
@@ -293,4 +306,4 @@ if (APPLE)
 endif()
 
 install(FILES ${pyside6_SOURCE_DIR}/qtcorehelper.h ${pyside6_SOURCE_DIR}/qiopipe.h
-        DESTINATION include/PySide6/QtCore/)
+        DESTINATION PySide6/include/QtCore/)
index c51d227471ffa82299f86193417d6a287f623939..a77a8ddeaf9531837516c76e7b8e8c5475054055 100644 (file)
@@ -5,7 +5,9 @@
 #include "qtcorehelper.h"
 #include "pysideqobject.h"
 
-#include "shiboken.h"
+#include "sbkpython.h"
+#include "sbkconverter.h"
+#include "sbkpep.h"
 #ifndef Py_LIMITED_API
 #  include <datetime.h>
 #endif
 #include <QtCore/QObject>
 #include <QtCore/QRegularExpression>
 #include <QtCore/QStack>
-#include <QtCore/QVariant>
 
-// Helpers for QVariant conversion
-
-QMetaType QVariant_resolveMetaType(PyTypeObject *type)
-{
-    if (!PyObject_TypeCheck(type, SbkObjectType_TypeF()))
-        return {};
-    const char *typeName = Shiboken::ObjectType::getOriginalName(type);
-    if (!typeName)
-        return {};
-    const bool valueType = '*' != typeName[qstrlen(typeName) - 1];
-    // Do not convert user type of value
-    if (valueType && Shiboken::ObjectType::isUserType(type))
-        return {};
-    QMetaType metaType = QMetaType::fromName(typeName);
-    if (metaType.isValid())
-        return metaType;
-    // Do not resolve types to value type
-    if (valueType)
-        return {};
-    // Find in base types. First check tp_bases, and only after check tp_base, because
-    // tp_base does not always point to the first base class, but rather to the first
-    // that has added any python fields or slots to its object layout.
-    // See https://mail.python.org/pipermail/python-list/2009-January/520733.html
-    if (type->tp_bases) {
-        const auto size = PyTuple_Size(type->tp_bases);
-        Py_ssize_t i = 0;
-        // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject;
-        // it needs to use always QGraphicsItem as a QVariant type for
-        // QGraphicsItem::itemChange() to work.
-        if (qstrcmp(typeName, "QGraphicsObject*") == 0 && size > 1) {
-            auto *firstBaseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0));
-            if (SbkObjectType_Check(firstBaseType)) {
-                const char *firstBaseTypeName = Shiboken::ObjectType::getOriginalName(firstBaseType);
-                if (firstBaseTypeName != nullptr && qstrcmp(firstBaseTypeName, "QObject*") == 0)
-                    ++i;
-            }
-        }
-        for ( ; i < size; ++i) {
-            auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
-            const QMetaType derived = QVariant_resolveMetaType(baseType);
-            if (derived.isValid())
-                return derived;
-        }
-    } else if (type->tp_base) {
-        return QVariant_resolveMetaType(type->tp_base);
-    }
-    return {};
-}
-
-QVariant QVariant_convertToValueList(PyObject *list)
-{
-    if (PySequence_Size(list) < 0) {
-        // clear the error if < 0 which means no length at all
-        PyErr_Clear();
-        return {};
-    }
-
-    Shiboken::AutoDecRef element(PySequence_GetItem(list, 0));
-
-    auto *type = reinterpret_cast<PyTypeObject *>(element.object());
-    QMetaType metaType = QVariant_resolveMetaType(type);
-    if (!metaType.isValid())
-        return {};
-
-    const QByteArray listTypeName = QByteArrayLiteral("QList<") + metaType.name() + '>';
-    metaType = QMetaType::fromName(listTypeName);
-    if (!metaType.isValid())
-        return {};
-
-    Shiboken::Conversions::SpecificConverter converter(listTypeName);
-    if (!converter) {
-        qWarning("Type converter for: %s not registered.", listTypeName.constData());
-        return {};
-    }
-
-    QVariant var(metaType);
-    converter.toCpp(list, &var);
-    return var;
-}
-
-bool QVariant_isStringList(PyObject *list)
-{
-    if (!PySequence_Check(list)) {
-        // If it is not a list or a derived list class
-        // we assume that will not be a String list neither.
-        return false;
-    }
-
-    if (PySequence_Size(list) < 0) {
-        // clear the error if < 0 which means no length at all
-        PyErr_Clear();
-        return false;
-    }
-
-    Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList"));
-    const Py_ssize_t size = PySequence_Size(fast.object());
-    for (Py_ssize_t i = 0; i < size; ++i) {
-        Shiboken::AutoDecRef item(PySequence_GetItem(fast.object(), i));
-        if (PyUnicode_Check(item) == 0)
-            return false;
-    }
-    return true;
-}
+#include <cstring>
 
 // Helpers for qAddPostRoutine
 
@@ -246,10 +145,7 @@ QString qObjectTr(PyTypeObject *type, const char *sourceText, const char *disamb
         auto *type = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
         if (type == sbkObjectType)
             continue;
-        const char *context = type->tp_name;
-        const char *dotpos = strrchr(context, '.');
-        if (dotpos != nullptr)
-            context = dotpos + 1;
+        const char *context = PepType_GetNameStr(type);
         result = QCoreApplication::translate(context, sourceText, disambiguation, n);
         if (result != oldResult)
             break;
index 11e84b2914816f747001a76109b419223e3d4a60..4c1867a1a3ece3cb561e97b56661ade9d9d353e8 100644 (file)
 
 QT_FORWARD_DECLARE_CLASS(QGenericArgument)
 QT_FORWARD_DECLARE_CLASS(QGenericReturnArgument)
-QT_FORWARD_DECLARE_CLASS(QMetaType)
 QT_FORWARD_DECLARE_CLASS(QObject)
 QT_FORWARD_DECLARE_CLASS(QRegularExpression)
-QT_FORWARD_DECLARE_CLASS(QVariant);
 
 QT_BEGIN_NAMESPACE
 namespace QtCoreHelper {
@@ -26,14 +24,6 @@ class QGenericReturnArgumentHolder;
 }
 QT_END_NAMESPACE
 
-// Helpers for QVariant conversion
-
-QMetaType QVariant_resolveMetaType(PyTypeObject *type);
-
-QVariant QVariant_convertToValueList(PyObject *list);
-
-bool QVariant_isStringList(PyObject *list);
-
 // Helpers for qAddPostRoutine
 namespace PySide {
 void globalPostRoutineCallback();
index d806b23e17ca698f5a9baeb99fa8b79e6495bdfa..3ff157e49f514a1d19106b35bfef1e31091ee0ff 100644 (file)
@@ -2,7 +2,8 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include <sbkpython.h>
-#include <shiboken.h>
+#include <sbkconverter.h>
+#include <sbkstaticstrings.h>
 #include <pysideweakref.h>
 
 #include <QtCore/QEasingCurve>
index 1544e6d448293e708a762da46411b8746a53fe05..3be80d31d653cf7007bf269700051d3fc96f83aa 100644 (file)
@@ -16,7 +16,9 @@
     <extra-includes>
         <include file-name="pysidemetatype.h" location="global"/>
         <include file-name="pysideutils.h" location="global"/> <!-- QString conversion -->
+        <include file-name="pysidevariantutils.h" location="global"/> <!-- QVariant conversion -->
         <include file-name="signalmanager.h" location="global"/>
+        <include file-name="sbkerrors.h" location="global"/>
         <!-- QtCoreHelper::QGenericReturnArgumentHolder -->
         <include file-name="qtcorehelper.h" location="local"/>
     </extra-includes>
   <primitive-type name="QVariant" target-lang-api-name="PyObject">
     <extra-includes>
         <include file-name="optional" location="global"/>
+        <include file-name="pysideqenum.h" location="global"/>
+        <include file-name="pysidevariantutils.h" location="global"/>
     </extra-includes>
     <conversion-rule>
         <native-to-target file="../glue/qtcore.cpp" snippet="return-qvariant"/>
             <add-conversion type="QByteArray" file="../glue/qtcore.cpp" snippet="conversion-qbytearray"/>
             <add-conversion type="PyFloat" check="PyFloat_CheckExact(%in)" file="../glue/qtcore.cpp" snippet="conversion-pyfloat"/>
             <add-conversion type="PyLong" check="PyLong_CheckExact(%in)"  file="../glue/qtcore.cpp" snippet="conversion-qlonglong"/>
+            <add-conversion type="SbkEnumType" check="Shiboken::Enum::check(%in)" file="../glue/qtcore.cpp" snippet="conversion-sbkobject"/>
             <add-conversion type="SbkObject" file="../glue/qtcore.cpp" snippet="conversion-sbkobject"/>
             <add-conversion type="PyDict" check="PyDict_CheckExact(%in)" file="../glue/qtcore.cpp" snippet="conversion-pydict"/>
             <add-conversion type="PyList" check="PyList_Check(%in)" file="../glue/qtcore.cpp" snippet="conversion-pylist"/>
   </object-type>
 
   <primitive-type name="QJsonObject">
+    <extra-includes>
+        <include file-name="pysidevariantutils.h" location="global"/>
+    </extra-includes>
     <conversion-rule>
         <native-to-target file="../glue/qtcore.cpp" snippet="return-qjsonobject"/>
         <target-to-native>
     <enum-type name="ConnectionType"/>
     <enum-type name="ContextMenuPolicy"/>
     <enum-type name="ContextMenuTrigger" since="6.8"/>
+    <enum-type name="ContrastPreference" since="6.10"/>
     <enum-type name="CoordinateSystem"/>
     <enum-type name="Corner"/>
     <enum-type name="CursorShape"/>
     <inject-code class="native" position="beginning" file="../glue/qtcore.cpp" snippet="qline-hash"/>
 
     <add-function signature="toTuple" return-type="PyObject*">
+        <modify-argument index="return" pyi-type="PyTuple[int, int, int, int]" />
         <inject-code class="target" position="beginning">
             <insert-template name="to_tuple">
                 <replace from="%TT_FORMAT" to="iiii"/>
     </add-function>
 
     <add-function signature="toTuple" return-type="PyObject*">
+        <modify-argument index="return" pyi-type="PyTuple[float, float, float, float]" />
         <inject-code class="target" position="beginning">
             <insert-template name="to_tuple">
                 <replace from="%TT_FORMAT" to="dddd"/>
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-2"/>
     </add-function>
     <!-- PYSIDE-1735: Qt::TimeSpec is no more compatible with int -->
-    <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,Qt::TimeSpec@spec@=Qt::LocalTime)">
+    <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,Qt::TimeSpec@spec@)">
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-3"/>
     </add-function>
+    <add-function signature="QDateTime(int@year@,int@month@,int@day@,int@h@,int@m@,int@s@,int@ms@,QTimeZone@spec@=QTimeZone::LocalTime)" since="6.5">
+        <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qdatetime-4"/>
+    </add-function>
     <add-function signature="__repr__" return-type="str">
         <inject-code class="target" position="beginning">
             <insert-template name="repr_code">
     <enum-type name="Filter" flags="Filters"/>
     <enum-type name="SortFlag" flags="SortFlags"/>
 
+    <declare-function signature="mkdir(QString)const" return-type="bool"/>
+    <declare-function signature="mkdir(QString,QFileDevice::Permissions)const" return-type="bool"/>
+    <modify-function signature="mkdir(QString,std::optional&lt;QFileDevice::Permissions&gt;)const" remove="all"/>
+
+    <declare-function signature="mkpath(QString)const" return-type="bool"/>
+    <declare-function signature="mkpath(QString,QFileDevice::Permissions)const" return-type="bool"/>
+    <modify-function signature="mkpath(QString,std::optional&lt;QFileDevice::Permissions&gt;)const" remove="all"/>
+
     <!-- PYSIDE-1499: Replace QString by pathlib.Path (qdir.h) -->
     <modify-function signature="QDir(const QString &amp;)">
       <modify-argument index="1" pyi-type="Optional[PyPathLike]">
     </add-function>
 
     <add-function signature="toTuple" return-type="PyObject*">
+        <modify-argument index="return" pyi-type="PyTuple[int, int]" />
         <inject-code class="target" position="beginning">
             <insert-template name="to_tuple">
                 <replace from="%TT_FORMAT" to="ii"/>
     </add-function>
 
     <add-function signature="toTuple" return-type="PyObject*">
+        <modify-argument index="return" pyi-type="PyTuple[float, float]" />
         <inject-code class="target" position="beginning">
             <insert-template name="to_tuple">
                 <replace from="%TT_FORMAT" to="dd"/>
     </add-function>
 
     <add-function signature="toTuple" return-type="PyObject*">
+        <modify-argument index="return" pyi-type="PyTuple[int, int]" />
         <inject-code class="target" position="beginning">
             <insert-template name="to_tuple">
                 <replace from="%TT_FORMAT" to="ii"/>
     </add-function>
 
     <add-function signature="toTuple" return-type="PyObject*">
+        <modify-argument index="return" pyi-type="PyTuple[float, float]" />
         <inject-code class="target" position="beginning">
             <insert-template name="to_tuple">
                 <replace from="%TT_FORMAT" to="dd"/>
     <modify-function signature="endResetModel()" allow-thread="yes"/>
   </object-type>
 
+  <object-type name="QRangeModel" since="6.10">
+    <extra-includes>
+      <include file-name="QtCore/qspan.h" location="global"/>
+      <include file-name="sbknumpycheck.h" location="global"/>
+      <include file-name="sbknumpyview.h" location="global"/>
+      <include file-name="pysidevariantutils.h" location="global"/>
+      <include file-name="vector" location="global"/>
+    </extra-includes>
+    <enum-type name="RowCategory"/>
+    <inject-code class="native" position="wrapper-declaration"
+                 file="../glue/qtcore.cpp" snippet="qrangemodel-wrapper"/>
+    <inject-code class="native" position="beginning"
+                 file="../glue/qtcore.cpp" snippet="qrangemodel-helper-functions"/>
+    <add-function signature="QRangeModel(PyArrayObject *@data@, QObject *@parent@ = nullptr)"
+                  overload-number="0">
+        <inject-documentation format="target" mode="append" file="../doc/qtcore.rst"
+                              snippet="qrangemodel-numpy-constructor"/>
+        <inject-code file="../glue/qtcore.cpp" snippet="qrangemodel-numpy-constructor"/>
+    </add-function>
+    <add-function signature="QRangeModel(PySequence@list@, QObject *@parent@ = nullptr)"
+                  overload-number="1">
+        <inject-documentation format="target" mode="append" file="../doc/qtcore.rst"
+                              snippet="qrangemodel-sequence-constructor"/>
+        <inject-code file="../glue/qtcore.cpp" snippet="qrangemodel-sequence-constructor"/>
+    </add-function>
+  </object-type>
+
   <value-type name="QItemSelection">
     <include file-name="QList" location="global"/>
     <!-- Expose operator==, != inherited from QList, which the parser does
       <include file-name="QStringList" location="global"/>
       <include file-name="QSize" location="global"/>
     </extra-includes>
+    <enum-type name="Direction" flags="Directions" since="6.10"/>
     <modify-function signature="setSourceModel(QAbstractItemModel*)">
       <modify-argument index="1">
         <reference-count action="set"/>
 
     <?if !windows?>
     <enum-type name="UnixProcessFlag" flags="UnixProcessFlags" since="6.6"/>
-    <value-type name="UnixProcessParameters" since="6.6"/>
+    <value-type name="UnixProcessParameters">
+      <configuration condition="QT_CONFIG(process)"/>
+    </value-type>
     <?endif?>
 
     <modify-function signature="waitForStarted(int)" allow-thread="yes"/>
     <enum-type name="ReadElementTextBehaviour"/>
   </object-type>
   <object-type name="QXmlStreamWriter">
+    <enum-type name="Error" since="6.10"/>
     <!-- Removed because it expect QString to be mutable -->
     <modify-function signature="QXmlStreamWriter(QString*)" remove="all"/>
   </object-type>
       <configuration condition="QT_CONFIG(future)"/>
       <enum-type name="State"/>
       <enum-type name="CancelMode" since="6.3"/>
+      <enum-type name="ContinuationType" since="6.10"/>
   </object-type>
   <value-type name="QFuture" generate="no">
       <include file-name="QtCore/qfuture.h" location="global"/>
     <add-function signature="__repr__" return-type="str">
         <inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qmetaobject-repr"/>
     </add-function>
+    <modify-function signature="className()const">
+        <modify-argument index="return" pyi-type="str"/>
+    </modify-function>
     <modify-function signature="indexOfClassInfo(const char *)const">
         <modify-argument index="1" pyi-type="str"/>
     </modify-function>
   </value-type>
 
   <value-type name="QMetaEnum">
+    <modify-function signature="value(int)const">
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtcore.cpp" snippet="qmetaenum_value"/>
+    </modify-function>
     <modify-function signature="keyToValue(const char*,bool*)const">
         <modify-argument index="2">
             <remove-argument />
         <modify-argument index="return">
             <replace-type modified-type="PyTuple"/>
         </modify-argument>
-        <inject-code class="target" position="beginning">
-            <insert-template name="fix_args,bool*"/>
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtcore.cpp" snippet="qmetaenum_keytovalue"/>
     </modify-function>
     <modify-function signature="keysToValue(const char*,bool*)const">
         <modify-argument index="2">
         <modify-argument index="return">
             <replace-type modified-type="PyTuple"/>
         </modify-argument>
-        <inject-code class="target" position="beginning">
-            <insert-template name="fix_args,bool*"/>
-        </inject-code>
+        <inject-code class="target" position="beginning"
+                     file="../glue/qtcore.cpp" snippet="qmetaenum_keystovalue"/>
     </modify-function>
     <!-- This isn't part of Qt public API -->
     <modify-function signature="enclosingMetaObject()const" remove="all"/>
index 5ee8dc67c08d384e11172cf6792363cd37399b0e..49e86d1217389b3591c5a9accd7a36857de83317 100644 (file)
@@ -51,4 +51,4 @@ create_pyside_module(NAME QtDBus
                      TYPESYSTEM_PATH QtDBus_SOURCE_DIR
                      SOURCES QtDBus_SRC)
 
-install(FILES ${pyside6_SOURCE_DIR}/qtdbushelper.h DESTINATION include/PySide6/QtDBus)
+install(FILES ${pyside6_SOURCE_DIR}/qtdbushelper.h DESTINATION PySide6/include/QtDBus)
index 1276b424f6fb6261dc043d356d2cb9ec64b46905..424799b225a7b64ebbc876599cb99b6582e5c6ec 100644 (file)
@@ -68,4 +68,4 @@ create_pyside_module(NAME QtDataVisualization
                      STATIC_SOURCES QtDataVisualization_src)
 
 install(FILES ${pyside6_SOURCE_DIR}/qtdatavisualization_helper.h
-        DESTINATION include/PySide6/QtDataVisualization)
+        DESTINATION PySide6/include/QtDataVisualization)
index e91532b87b228e7b26bf2c47c9208854638b3efc..570ed0f0b154e9cd4c2d209077bb95c1d3eb93d7 100644 (file)
@@ -67,4 +67,4 @@ create_pyside_module(NAME QtDesigner
                      STATIC_SOURCES QtDesigner_static_src
                      TYPESYSTEM_NAME ${QtDesigner_BINARY_DIR}/typesystem_designer.xml)
 
-install(FILES ${pyside6_SOURCE_DIR}/qpydesignerextensions.h DESTINATION include/PySide6/QtDesigner)
+install(FILES ${pyside6_SOURCE_DIR}/qpydesignerextensions.h DESTINATION PySide6/include/QtDesigner)
index 19e9742338732696d5bcf6dbe873ce099eaa8aa6..d93c038b36ac61efc193db1ff64d7af78cff8c2c 100644 (file)
@@ -6,8 +6,12 @@
 #include <QtCore/QCoreApplication>
 #include <QtCore/QVariant>
 
-#include <shiboken.h>
+#include <sbkpep.h>
+#include <autodecref.h>
+#include <basewrapper.h>
 #include <bindingmanager.h>
+#include <sbkconverter.h>
+#include <sbkstring.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -117,19 +121,20 @@ QWidget *PyDesignerCustomWidget::createWidget(QWidget *parent)
     PyTuple_SetItem(pyArgs, 0, pyParent); // tuple will keep pyParent reference
 
     // Call python constructor
-    auto result = reinterpret_cast<SbkObject *>(PyObject_CallObject(m_pyTypeObject, pyArgs));
-    if (!result) {
+    auto *obResult = PyObject_CallObject(m_pyTypeObject, pyArgs);
+    if (obResult == nullptr) {
         qWarning("Unable to create a Python custom widget of type \"%s\".", utf8Name());
         PyErr_Print();
         return nullptr;
     }
 
+    auto *result = reinterpret_cast<SbkObject *>(obResult);
     if (unknownParent) // if parent does not exist in python, transfer the ownership to cpp
         Shiboken::Object::releaseOwnership(result);
     else
-        Shiboken::Object::setParent(pyParent, reinterpret_cast<PyObject *>(result));
+        Shiboken::Object::setParent(pyParent, obResult);
 
-    return reinterpret_cast<QWidget *>(Shiboken::Object::cppPointer(result, Py_TYPE(result)));
+    return reinterpret_cast<QWidget *>(Shiboken::Object::cppPointer(result, Py_TYPE(obResult)));
 }
 
 void PyDesignerCustomWidget::initialize(QDesignerFormEditorInterface *core)
index 29a7b2544ee8f9d3166da091c495cc967281efc7..f416078780a01b322ff2b6b5b4349495bcd05c29 100644 (file)
@@ -96,4 +96,4 @@ create_pyside_module(NAME QtGraphs
                      DROPPED_ENTRIES QtGraphs_DROPPED_ENTRIES)
 
 install(FILES ${pyside6_SOURCE_DIR}/qtgraphs_helper.h
-        DESTINATION include/PySide6/QtGraphs)
+        DESTINATION PySide6/include/QtGraphs)
index 81485a706aea26ffeed544099fc11e6ccd22d1bc..ba0a8a486cfbeeb3630a24f4b6cf17c438e56c24 100644 (file)
@@ -23,6 +23,7 @@
      <enum-type name="ShadowQuality"/>
      <enum-type name="TransparencyTechnique" since="6.7"/>
      <enum-type name="CameraPreset" since="6.7"/>
+     <enum-type name="SliceCaptureType" since="6.10"/>
    </namespace-type>
 
    <value-type name="QGraphsLine" since="6.8">
@@ -54,6 +55,7 @@
   <object-type name="QAbstract3DSeries">
     <enum-type name="Mesh"/>
     <enum-type name="SeriesType"/>
+    <enum-type name="LightingMode" since="6.10"/>
   </object-type>
   <object-type name="QAbstractDataProxy">
     <enum-type name="DataType"/>
     </modify-function>
   </object-type>
   <object-type name="QPieModelMapper" since="6.8"/>
-  <object-type name="QPieSeries" since="6.8"/>
+  <object-type name="QPieSeries" since="6.8">
+    <enum-type name="LabelVisibility" since="6.10"/>
+  </object-type>
   <object-type name="QPieSlice" since="6.8">
       <enum-type name="LabelPosition"/>
   </object-type>
index e3207856e3c4dfdfa5ad605ad396a6712dd213c8..b9e842912b331fbe2c1313a624cf1e7e475cec3a 100644 (file)
@@ -19,6 +19,7 @@ set_property(SOURCE ${QtGui_SRC_UNITY_EXCLUDED_SRC}
 
 set(QtGui_SRC_RHI
 ${QtGui_GEN_DIR}/qrhi_wrapper.cpp
+${QtGui_GEN_DIR}/qrhiadapter_wrapper.cpp
 ${QtGui_GEN_DIR}/qrhibuffer_wrapper.cpp
 ${QtGui_GEN_DIR}/qrhicolorattachment_wrapper.cpp
 ${QtGui_GEN_DIR}/qrhicommandbuffer_wrapper.cpp
@@ -67,13 +68,13 @@ ${QtGui_GEN_DIR}/qshadercode_wrapper.cpp
 ${QtGui_GEN_DIR}/qshaderkey_wrapper.cpp
 )
 
-if (ENABLE_WIN)
+if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
     list(APPEND QtGui_SRC_RHI
          ${QtGui_GEN_DIR}/qrhid3d11initparams_wrapper.cpp
          ${QtGui_GEN_DIR}/qrhid3d11nativehandles_wrapper.cpp
          ${QtGui_GEN_DIR}/qrhid3d12initparams_wrapper.cpp
          ${QtGui_GEN_DIR}/qrhid3d12nativehandles_wrapper.cpp)
-elseif (ENABLE_MAC)
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
     list(APPEND QtGui_SRC_RHI
          ${QtGui_GEN_DIR}/qrhimetalinitparams_wrapper.cpp)
 endif()
@@ -84,6 +85,7 @@ ${QtGui_GEN_DIR}/qabstractfileiconprovider_wrapper.cpp
 ${QtGui_GEN_DIR}/qabstracttextdocumentlayout_paintcontext_wrapper.cpp
 ${QtGui_GEN_DIR}/qabstracttextdocumentlayout_selection_wrapper.cpp
 ${QtGui_GEN_DIR}/qabstracttextdocumentlayout_wrapper.cpp
+${QtGui_GEN_DIR}/qaccessibilityhints_wrapper.cpp
 ${QtGui_GEN_DIR}/qaccessible_wrapper.cpp
 ${QtGui_GEN_DIR}/qaccessibleactioninterface_wrapper.cpp
 ${QtGui_GEN_DIR}/qaccessibleannouncementevent_wrapper.cpp
@@ -294,7 +296,14 @@ get_property(QtGui_enabled_features TARGET Qt${QT_MAJOR_VERSION}::Gui
 if("xcb" IN_LIST QtGui_enabled_features)
     list(APPEND QtGui_SRC
          ${QtGui_GEN_DIR}/qnativeinterface_qx11application_wrapper.cpp)
-elseif(WIN32)
+endif()
+
+if("wayland" IN_LIST QtGui_enabled_features)
+    list(APPEND QtGui_SRC
+         ${QtGui_GEN_DIR}/qnativeinterface_qwaylandapplication_wrapper.cpp)
+endif()
+
+if(WIN32)
     list(APPEND QtGui_SRC
          ${QtGui_GEN_DIR}/qnativeinterface_qwindowsscreen_wrapper.cpp)
 endif()
@@ -357,4 +366,4 @@ create_pyside_module(NAME QtGui
 
 install(FILES ${pyside6_SOURCE_DIR}/qpytextobject.h
               ${pyside6_SOURCE_DIR}/qtguihelper.h
-        DESTINATION include/PySide6/QtGui/)
+        DESTINATION PySide6/include/QtGui/)
index 858989404660c4b038a768284cf4ce3791f34034..74f298cd8d9ad89aa3bf732267ac22bf962307a1 100644 (file)
@@ -14,7 +14,9 @@
     <?endif?>
     <?if unix !darwin?>
     <load-typesystem name="QtGui/typesystem_gui_x11.xml" generate="yes"/>
+    <load-typesystem name="QtGui/typesystem_gui_wayland.xml" generate="yes"/>
     <?endif?>
+    <load-typesystem name="QtGui/typesystem_gui_nativeinterface.xml" generate="yes"/>
     <load-typesystem name="QtGui/typesystem_gui_common.xml" generate="yes"/>
     <load-typesystem name="QtGui/typesystem_gui_rhi.xml" generate="yes"/>
 </typesystem>
index 5cf69940869e38f0c8c46f3bb31e42be5488c21d..ae298a8f62ab939591b96e90d98bb91befc2d3e2 100644 (file)
   <load-typesystem name="templates/gui_common.xml" generate="no"/>
 
   <rejection class="^Q.*$" argument-type="^QPlatform.*$"/>
+
+  <extra-includes>
+      <include file-name="QtGui/qquaternion.h" location="global"/> <!-- for qFuzzyCompare -->
+  </extra-includes>
+
   <function signature="qAlpha(uint)"/>
   <function signature="qBlue(uint)"/>
   <function signature="qGray(int,int,int)"/>
       </add-function>
   </value-type>
 
+  <object-type name="QAccessibilityHints" since="6.10"/>
   <object-type name="QAccessibleActionInterface"/>
   <object-type name="QAccessibleAttributesInterface" since="6.8"/>
   <object-type name="QAccessibleEditableTextInterface"/>
     <modify-function signature="QPolygon(int,const int*)" remove="all"/>
     <!-- ### A QList parameter, for no defined type, will generate wrong code. -->
     <modify-function signature="operator+=(QList&lt;QPoint&gt;)" remove="all"/>
-    <add-function signature="operator&lt;&lt;(QPoint)">
+    <add-function signature="operator&lt;&lt;(QPoint)" return-type="QPolygon">
         <inject-code file="../glue/qtgui.cpp" snippet="qpolygon-operatorlowerlower"/>
     </add-function>
-    <add-function signature="operator&lt;&lt;(QList&lt;QPoint&gt;)">
+    <add-function signature="operator&lt;&lt;(QList&lt;QPoint&gt;)" return-type="QPolygon">
         <inject-code file="../glue/qtgui.cpp" snippet="qpolygon-operatorlowerlower"/>
     </add-function>
     <!-- ### -->
     <extra-includes>
       <include file-name="QTransform" location="global"/>
     </extra-includes>
+    <!-- Expose operator==, != inherited from QList, which the parser does
+         not see due to the TMP expression of the return type. -->
+    <add-function signature="operator==(const QPolygonF&amp;)" return-type="bool"/>
+    <add-function signature="operator!=(const QPolygonF&amp;)" return-type="bool"/>
+
     <!-- ### A QList parameter, for no defined type, will generate wrong code. -->
     <modify-function signature="operator+=(QList&lt;QPointF&gt;)" remove="all"/>
     <!-- ### See bug 777 -->
-    <modify-function signature="operator&lt;&lt;(QList&lt;QPointF&gt;)" remove="all"/>
+    <add-function signature="operator&lt;&lt;(QPointF)" return-type="QPolygonF">
+        <inject-code file="../glue/qtgui.cpp" snippet="qpolygonf-operatorlowerlower"/>
+    </add-function>
+    <add-function signature="operator&lt;&lt;(QList&lt;QPointF&gt;)" return-type="QPolygonF">
+        <inject-code file="../glue/qtgui.cpp" snippet="qpolygonf-operatorlowerlower"/>
+    </add-function>
     <!-- ### -->
   </value-type>
   <value-type name="QIcon" >
         <parent index="this" action="add"/>
       </modify-argument>
     </modify-function>
-    <modify-function signature="invisibleRootItem()const">
-      <modify-argument index="return">
-        <parent index="this" action="add"/>
-      </modify-argument>
-    </modify-function>
-    <modify-function signature="item(int,int)const">
-      <modify-argument index="return">
-        <parent index="this" action="add"/>
-      </modify-argument>
-    </modify-function>
-    <modify-function signature="itemFromIndex(const QModelIndex&amp;)const">
-      <modify-argument index="return">
-        <parent index="this" action="add"/>
-      </modify-argument>
-    </modify-function>
-    <modify-function signature="itemPrototype()const">
-      <modify-argument index="return">
-        <parent index="this" action="add"/>
-      </modify-argument>
-    </modify-function>
 
     <modify-function signature="appendRow(const QList&lt;QStandardItem*&gt;&amp;)">
       <modify-argument index="1">
       </modify-argument>
     </modify-function>
 
-    <modify-function signature="findItems(const QString&amp;,QFlags&lt;Qt::MatchFlag&gt;,int)const">
-      <modify-argument index="return">
-        <parent index="this" action="add"/>
-      </modify-argument>
-    </modify-function>
-
     <modify-function signature="insertColumn(int,const QList&lt;QStandardItem*&gt;&amp;)">
       <modify-argument index="2">
         <parent index="this" action="add"/>
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix2x3">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix2x4">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix3x2">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix3x3">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix3x4">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix4x2">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix4x3">
         </insert-template>
       </inject-code>
     </add-function>
+    <add-function signature="__mgetitem__" return-type="PyObject*">
+        <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
+    </add-function>
   </value-type>
 
   <value-type name="QMatrix4x4">
     <modify-function signature="data()const" remove="all"/>
     <modify-function signature="constData()const" remove="all"/>
     <!-- ### -->
-    <modify-function signature="operator()(int,int)const" remove="all"/>
-    <modify-function signature="operator()(int,int)" remove="all"/>
     <add-function signature="__mgetitem__" return-type="PyObject*">
-       <inject-code file="../glue/qtgui.cpp" snippet="qmatrix4x4-mgetitem"/>
+      <inject-code file="../glue/qtgui.cpp" snippet="qmatrix-mgetitem"/>
     </add-function>
   </value-type>
 
       </modify-argument>
       <inject-code file="../glue/qtgui.cpp" snippet="qwindow-fromWinId"/>
     </modify-function>
+    <modify-function signature="setParent(QWindow*)">
+      <modify-argument index="1" pyi-type="Optional[QWindow]"/>
+    </modify-function>
     <modify-function signature="show()" allow-thread="yes"/>
     <modify-function signature="showFullScreen()" allow-thread="yes"/>
     <modify-function signature="showMaximized()" allow-thread="yes"/>
     <add-function signature="exec_()" return-type="int">
         <inject-code file="../glue/qtgui.cpp" snippet="qguiapplication-exec"/>
     </add-function>
-    <modify-function signature="modalWindow()">
-        <modify-argument index="return" pyi-type="Optional[PySide6.QtGui.QWindow]"/>
-    </modify-function>
     <add-function signature="nativeInterface()const" return-type="PyObject">
         <modify-argument index="return"> <!-- Suppress return value heuristics -->
             <define-ownership class="target" owner="default"/>
         <inject-code class="target" position="beginning" file="../glue/qtgui.cpp"
                      snippet="qguiapplication-nativeInterface"/>
     </add-function>
-    <modify-function signature="screenAt(const QPoint &amp;)">
-        <modify-argument index="return" pyi-type="Optional[PySide6.QtGui.QScreen]"/>
-    </modify-function>
     <modify-function signature="setOverrideCursor(const QCursor&amp;)">
         <modify-argument index="return" pyi-type="PyObject">
             <replace-type modified-type="QtGuiHelper::QOverrideCursorGuard*"/>
     </modify-function>
   </object-type>
 
-  <namespace-type name="QNativeInterface" private="yes" since="6.7">
-      <object-type name="QX11Application" private="yes" disable-wrapper="yes"
-                   force-abstract="yes">
-          <configuration condition="QT_CONFIG(xcb)"/>
-          <modify-function signature="display()const">
-              <modify-argument index="return">
-                  <replace-type modified-type="int"/>
-              </modify-argument>
-              <inject-code class="target" position="end" file="../glue/qtgui.cpp"
-                           snippet="qx11application-resource-ptr"/>
-          </modify-function>
-          <modify-function signature="connection()const">
-              <modify-argument index="return">
-                  <replace-type modified-type="int"/>
-              </modify-argument>
-              <inject-code class="target" position="end" file="../glue/qtgui.cpp"
-                           snippet="qx11application-resource-ptr"/>
-          </modify-function>
-      </object-type>
-      <object-type name="QWindowsScreen" private="yes" disable-wrapper="yes"
-                   force-abstract="yes">
-          <configuration condition="#ifdef Q_OS_WIN"/>
-      </object-type>
-  </namespace-type>
-
   <object-type name="QOpenGLContext">
     <enum-type name="OpenGLModuleType"/>
   </object-type>
diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_nativeinterface.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_nativeinterface.xml
new file mode 100644 (file)
index 0000000..ba83d0f
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+It is not possible to split this per platform since all type entries need to be
+known for the code snippets of QGuiApplication::nativeInterface() and similar.
+-->
+
+<typesystem package="PySide6.QtGui">
+    <namespace-type name="QNativeInterface" private="yes" since="6.7">
+        <object-type name="QWaylandApplication" private="yes" disable-wrapper="yes">
+            <configuration condition="QT_CONFIG(wayland)"/>
+            <modify-function signature="^(compositor|display|keyboard|pointer|seat|touch|lastInputSeat)\(\)const$">
+                <modify-argument index="return">
+                    <replace-type modified-type="int"/>
+                </modify-argument>
+                <inject-code class="target" position="end" file="../glue/qtgui.cpp"
+                             snippet="native-resource-ptr"/>
+            </modify-function>
+        </object-type>
+        <object-type name="QX11Application" private="yes" disable-wrapper="yes"
+                     force-abstract="yes">
+            <configuration condition="QT_CONFIG(xcb)"/>
+            <modify-function signature="display()const">
+                <modify-argument index="return">
+                    <replace-type modified-type="int"/>
+                </modify-argument>
+                <inject-code class="target" position="end" file="../glue/qtgui.cpp"
+                             snippet="native-resource-ptr"/>
+            </modify-function>
+            <modify-function signature="connection()const">
+                <modify-argument index="return">
+                    <replace-type modified-type="int"/>
+                </modify-argument>
+                <inject-code class="target" position="end" file="../glue/qtgui.cpp"
+                             snippet="native-resource-ptr"/>
+            </modify-function>
+        </object-type>
+        <object-type name="QWindowsScreen" private="yes" disable-wrapper="yes"
+                     force-abstract="yes">
+            <configuration condition="#ifdef Q_OS_WIN"/>
+        </object-type>
+    </namespace-type>
+</typesystem>
index 656f18ca4e26ceba7b90c83fd2c5a30bd6243dd7..c594a043f88296ff2457f60c9de690a21e8007d5 100644 (file)
@@ -4,6 +4,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 -->
 <typesystem package="PySide6.QtGui">
+  <object-type name="QRhiAdapter" since="6.10" private="yes"/>
   <value-type name="QRhiDepthStencilClearValue" since="6.6" private="yes"/>
   <value-type name="QRhiViewport" since="6.6" private="yes"/>
   <value-type name="QRhiScissor" since="6.6" private="yes"/>
       </add-function>
   </object-type>
   <value-type name="QRhiReadbackResult" since="6.6" private="yes"/>
-  <object-type name="QRhiResourceUpdateBatch" since="6.6" private="yes"/>
+  <object-type name="QRhiResourceUpdateBatch" since="6.6" private="yes">
+      <!-- Hack fixing uploadStaticBuffer(QRhiBuffer*,const void*) to work after adding the Q
+           ByteArray overloads in 6.10. The overloads:
+               uploadStaticBuffer(QRhiBuffer*,const void*)
+               uploadStaticBuffer(QRhiBuffer*,quint32,...)
+           cause an issue here since quint32 converts from const void*. -->
+      <modify-function signature="uploadStaticBuffer(QRhiBuffer*,const void*)" overload-number="0"/>
+  </object-type>
   <value-type name="QRhiDriverInfo" since="6.6" private="yes">
       <enum-type name="DeviceType"/>
   </value-type>
diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_wayland.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_wayland.xml
new file mode 100644 (file)
index 0000000..5b822d4
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+-->
+<typesystem package="PySide6.QtGui">
+    <custom-type name="wl_compositor"/>
+    <custom-type name="wl_display"/>
+    <custom-type name="wl_keyboard"/>
+    <custom-type name="wl_pointer"/>
+    <custom-type name="wl_seat"/>
+    <custom-type name="wl_touch"/>
+</typesystem>
index 8e6d3e3ce89624d8066cee253d2e286c500d0a9b..8d571642fe86035eb1dee20b6212957c1d971b3d 100644 (file)
@@ -31,8 +31,10 @@ ${QtMultimedia_GEN_DIR}/qmediaplayer_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qmediarecorder_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qmediatimerange_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qmediatimerange_interval_wrapper.cpp
+${QtMultimedia_GEN_DIR}/qplaybackoptions_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qscreencapture_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qsoundeffect_wrapper.cpp
+${QtMultimedia_GEN_DIR}/qtaudio_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qtvideo_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qvideoframe_paintoptions_wrapper.cpp
 ${QtMultimedia_GEN_DIR}/qvideoframe_wrapper.cpp
index 18082888bfb2fb292d838e519a99d077a5c3aa38..cbb392761d9bde2a6fdfca5474eb5b5247d672fd 100644 (file)
         <enum-type name="Error"/>
         <enum-type name="State"/>
         <enum-type name="VolumeScale"/>
-        <inject-code class="target" position="end"
-                     file="../glue/qtmultimedia.cpp"
-                     snippet="qtaudio-namespace-compatibility-alias"/>
+        <!-- Compatibility alias that calls QtAudio::convertVolume() -->
+        <add-function signature="convertVolume(float@volume@,QAudio::VolumeScale@from@,QAudio::VolumeScale@to@)"
+                      return-type="float">
+            <inject-code file="../glue/qtmultimedia.cpp" snippet="qaudio-convertvolume"/>
+        </add-function>
+    </namespace-type>
+
+    <namespace-type name="QtAudio">
+        <enum-type name="Error"/>
+        <enum-type name="State"/>
+        <enum-type name="VolumeScale"/>
+        <modify-field name="NoError" remove="true"/>
+        <modify-field name="OpenError" remove="true"/>
+        <modify-field name="IOError" remove="true"/>
+        <modify-field name="UnderrunError" remove="true"/>
+        <modify-field name="FatalError" remove="true"/>
+        <modify-field name="ActiveState" remove="true"/>
+        <modify-field name="SuspendedState" remove="true"/>
+        <modify-field name="StoppedState" remove="true"/>
+        <modify-field name="IdleState" remove="true"/>
+        <modify-field name="LinearVolumeScale" remove="true"/>
+        <modify-field name="CubicVolumeScale" remove="true"/>
+        <modify-field name="LogarithmicVolumeScale" remove="true"/>
+        <modify-field name="DecibelVolumeScale" remove="true"/>
     </namespace-type>
 
     <namespace-type name="QtVideo" since="6.7">
     </value-type>
     <object-type name="QMediaPlayer">
         <enum-type name="MediaStatus"/>
+        <enum-type name="PitchCompensationAvailability" since="6.10"/>
         <enum-type name="PlaybackState" since="6.1"/>
         <enum-type name="Error"/>
         <enum-type name="Loops" python-type="IntEnum" since="6.2.3"/>
         <value-type name="Interval"/>
     </value-type>
 
+    <value-type name="QPlaybackOptions" since="6.10">
+        <enum-type name="PlaybackIntent"/>
+    </value-type>
+
     <object-type name="QScreenCapture" since="6.5">
         <enum-type name="Error"/>
     </object-type>
index 2cab76b471c471fd65caa43c33f5a68a4c971ca0..d5c2e920682aec3c1ba3a7a05ac74adcff04acd0 100644 (file)
@@ -88,14 +88,10 @@ static PyMethodDef QtQml_VolatileBoolObject_methods[] = {
 static PyObject *
 QtQml_VolatileBoolObject_repr(QtQml_VolatileBoolObject *self)
 {
-    PyObject *s;
-
-    if (*self->flag)
-        s = PyBytes_FromFormat("%s(True)",
-                                Py_TYPE(self)->tp_name);
-    else
-        s = PyBytes_FromFormat("%s(False)",
-                                Py_TYPE(self)->tp_name);
+    const char *typeName = Py_TYPE(reinterpret_cast<PyObject *>(self))->tp_name;
+    PyObject *s = *self->flag
+        ? PyBytes_FromFormat("%s(True)", typeName)
+        : PyBytes_FromFormat("%s(False)", typeName);
     Py_XINCREF(s);
     return s;
 }
@@ -103,14 +99,10 @@ QtQml_VolatileBoolObject_repr(QtQml_VolatileBoolObject *self)
 static PyObject *
 QtQml_VolatileBoolObject_str(QtQml_VolatileBoolObject *self)
 {
-    PyObject *s;
-
-    if (*self->flag)
-        s = PyBytes_FromFormat("%s(True) -> %p",
-                                Py_TYPE(self)->tp_name, self->flag);
-    else
-        s = PyBytes_FromFormat("%s(False) -> %p",
-                                Py_TYPE(self)->tp_name, self->flag);
+    const char *typeName = Py_TYPE(reinterpret_cast<PyObject *>(self))->tp_name;
+    PyObject *s = *self->flag
+        ? PyBytes_FromFormat("%s(True) -> %p", typeName, self->flag)
+        : PyBytes_FromFormat("%s(False) -> %p", typeName, self->flag);
     Py_XINCREF(s);
     return s;
 }
@@ -150,13 +142,14 @@ static const char *VolatileBool_SignatureStrings[] = {
 
 void initQtQmlVolatileBool(PyObject *module)
 {
-    if (InitSignatureStrings(QtQml_VolatileBool_TypeF(), VolatileBool_SignatureStrings) < 0) {
+    auto *qmlVolatileBoolType = QtQml_VolatileBool_TypeF();
+    if (InitSignatureStrings(qmlVolatileBoolType, VolatileBool_SignatureStrings) < 0) {
         PyErr_Print();
         qWarning() << "Error initializing VolatileBool type.";
         return;
     }
 
-    Py_INCREF(QtQml_VolatileBool_TypeF());
-    PyModule_AddObject(module, PepType_GetNameStr(QtQml_VolatileBool_TypeF()),
-                       reinterpret_cast<PyObject *>(QtQml_VolatileBool_TypeF()));
+    auto *obQmlVolatileBoolType = reinterpret_cast<PyObject *>(qmlVolatileBoolType);
+    Py_INCREF(obQmlVolatileBoolType);
+    PepModule_AddType(module, qmlVolatileBoolType);
 }
index f7749b4e71b5a95b222829c243f73a1848ae7048..e92c06a4e586f54b800759969af88c3784e45159 100644 (file)
@@ -6,9 +6,12 @@
 #include <pyside.h>
 #include <pysideqmlregistertype.h>
 #include <pyside_p.h>
-#include <shiboken.h>
+#include <sbkpython.h>
+#include <sbkconverter.h>
+#include <sbkpep.h>
 
 #include <QtQuick/QQuickPaintedItem>
+#include <QtQuick/QQuickTextDocument>
 
 #if QT_CONFIG(opengl) || QT_CONFIG(opengles2) || QT_CONFIG(opengles3)
 #  include <QtQuick/QQuickFramebufferObject>
@@ -71,6 +74,7 @@ void PySide::initQuickSupport(PyObject *module)
     qRegisterMetaType<QQuickFramebufferObject*>("QQuickFramebufferObject*");
 #endif
     qRegisterMetaType<QQuickItem*>("QQuickItem*");
+    qRegisterMetaType<QQuickTextDocument*>("QQuickTextDocument*");
 
     Qml::setQuickRegisterItemFunction(quickRegisterType);
 }
index 70c3e6f695a0c5134df1bd97540d054d3ea6f335..451c191dd363fdcaed71ff382208229ecabae326 100644 (file)
@@ -20,6 +20,7 @@
     </extra-includes>
   </namespace-type>
 
+  <value-type name="QSqlDatabaseDefaultConnectionName" generate="no"/>
   <value-type name="QSqlDatabase">
     <extra-includes>
         <include file-name="QSqlQuery" location="global"/>
         <include file-name="QStringList" location="global"/>
         <include file-name="QSize" location="global"/>
     </extra-includes>
+    <modify-function signature="addDatabase(QString,QString)">
+        <modify-argument index="2">
+            <replace-default-expression with="QSqlDatabase::defaultConnectionName()"/>
+        </modify-argument>
+    </modify-function>
+    <modify-function signature="addDatabase(QSqlDriver*,QString)">
+        <modify-argument index="2">
+            <replace-default-expression with="QSqlDatabase::defaultConnectionName()"/>
+        </modify-argument>
+    </modify-function>
+    <modify-function signature="contains(QString)">
+        <modify-argument index="1">
+            <replace-default-expression with="QSqlDatabase::defaultConnectionName()"/>
+        </modify-argument>
+    </modify-function>
+    <modify-function signature="database(QString,bool)">
+        <modify-argument index="1">
+            <replace-default-expression with="QSqlDatabase::defaultConnectionName()"/>
+        </modify-argument>
+    </modify-function>
     <modify-function signature="exec(QString)const" allow-thread="yes"/>
     <add-function signature="exec_(QString @query@ = QString())const" return-type="QSqlQuery">
         <inject-code file="../glue/qtsql.cpp" snippet="qsqldatabase-exec"/>
index 9cfa176c842a6267bcb988205d291003fa273660..c8e98d96fcd76b0e4c860d1ed6a5f79c2fa31bf3 100644 (file)
@@ -13,7 +13,8 @@
         <include file-name="glue/plugins.h" location="local"/>
       </extra-includes>
       <inject-code class="native" position="beginning" file="../glue/qtuitools.cpp" snippet="uitools-loadui"/>
-      <inject-code file="../glue/qtuitools.cpp" snippet="quiloader"/>
+      <inject-code class="native" position="beginning"
+                   file="../glue/qtuitools.cpp" snippet="quiloader"/>
       <add-function signature="registerCustomWidget(PyObject*@customWidgetType@)" return-type="void">
          <inject-documentation format="target" mode="append" file="../doc/qtuitools.rst"
                                snippet="quiloader-registercustomwidget"/>
index 7afb9ef108791b0d45ab412e6be03f3ac12e1127..4d934438d23038cab928eb44f3031f27f048ff1e 100644 (file)
@@ -20,6 +20,8 @@ ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_filterrequest_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginedesktopmediarequest_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginedownloadrequest_wrapper.cpp
+${QtWebEngineCore_GEN_DIR}/qwebengineextensioninfo_wrapper.cpp
+${QtWebEngineCore_GEN_DIR}/qwebengineextensionmanager_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginefilesystemaccessrequest_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebenginefindtextresult_wrapper.cpp
 ${QtWebEngineCore_GEN_DIR}/qwebengineframe_wrapper.cpp
index 003c18deb0947e7479fde2acc0fe6edffa68afd5..3c15f4ee4d8e87b9a5903c1331175756668f781a 100644 (file)
@@ -52,6 +52,9 @@
     <enum-type name="SavePageFormat"/>
   </object-type>
 
+  <object-type name="QWebEngineExtensionInfo" since="6.10"/>
+  <object-type name="QWebEngineExtensionManager" since="6.10"/>
+
   <value-type name="QWebEngineFileSystemAccessRequest">
     <enum-type name="AccessFlag" flags="AccessFlags"/>
     <enum-type name="HandleType"/>
index 158c720c9f06806bd4a4d45ab94738b3e55302ee..a8ac8c2a219d23b3734e4ff14b674aa8189e85ea 100644 (file)
@@ -25,7 +25,8 @@ set(QtWebView_libraries pyside6
 set(QtWebView_deps QtGui)
 
 # for Windows and Linux, QtWebView depends on QtWebEngine to render content
-if ((WIN32 OR UNIX) AND NOT APPLE)
+# On Android, QtWebView uses the native webview backend and does not require QtWebEngine.
+if ((WIN32 OR UNIX) AND NOT APPLE AND NOT ANDROID)
     list(APPEND QtWebView_deps QtWebEngineCore QtWebEngineQuick)
 endif()
 
index f1b9e14daaa0c32ee582733ea554bc84b05b7bfc..8058c752d273235dcc7092d5fda1eb8d5aa9b086 100644 (file)
   <object-type name="QLayoutItem">
 
     <modify-function signature="widget()const">
-      <modify-argument index="return">
+      <modify-argument index="return" pyi-type="Optional[QWidget]">
         <define-ownership class="target" owner="default"/>
       </modify-argument>
     </modify-function>
     </modify-function>
 
     <modify-function signature="spacerItem()">
-      <modify-argument index="return">
+      <modify-argument index="return" pyi-type="Optional[QSpacerItem]">
         <define-ownership class="target" owner="default"/>
       </modify-argument>
     </modify-function>
index 089978b6d30f2aad135cb2294c44afc7a0a92240..93d3c1f563fa47a27b6c551b6cb0eee230ebd559 100644 (file)
@@ -20,7 +20,7 @@
         <enum-type name="ParseOption" flags="ParseOptions" since="6.5"/>
         <!-- will be replaced in inject code -->
 
-        <value-type name="ParseResult"/>
+        <value-type name="ParseResult" operator-bool="yes"/>
 
         <modify-function signature="setContent(const QByteArray&amp;,bool,QString*,int*,int*)">
             <modify-argument index="3">
             </modify-argument>
             <inject-code class="target" position="beginning" file="../glue/qtxml.cpp" snippet="qdomdocument-setcontent" />
         </modify-function>
+        <declare-function signature="setContent(const QByteArray&amp;@data@, QDomDocument::ParseOptions@options@=QDomDocument::ParseOption::Default)"
+                          return-type="QDomDocument::ParseResult" since="6.8" />
     </value-type>
 
     <value-type name="QDomDocumentFragment"/>
index 197eba963eafed6fa3af499690a18b6ce50b87d1..c1050f2c51bb62a379aaf86ff8b0fa9e754920a5 100644 (file)
@@ -64,9 +64,9 @@ def _setupQtDirectories():
         #              setting dictates. There is no longer a difference in path structure.
         global Shiboken
         from shiboken6 import Shiboken
-    except Exception:
+    except Exception as e:
         paths = ', '.join(sys.path)
-        print(f"PySide6/__init__.py: Unable to import Shiboken from {paths}",
+        print(f"PySide6/__init__.py: Unable to import Shiboken from {paths}: {e}",
               file=sys.stderr)
         raise
 
index b8d551e70e7de1bd9230794c091a603a6754f4d6..eb369ee7c7e1e839ea54bf9af19ed31e5baf90c2 100644 (file)
@@ -116,3 +116,12 @@ Example::
     logging.debug("Test debug message")
 
 // @snippet qmessagelogger
+
+// @snippet qrangemodel-numpy-constructor
+The function takes one-dimensional or two-dimensional numpy arrays of various
+integer or float types to populate an editable QRangeModel.
+// @snippet qrangemodel-numpy-constructor
+
+// @snippet qrangemodel-sequence-constructor
+The function takes a sequence of of data to populate a read-only QRangeModel.
+// @snippet qrangemodel-sequence-constructor
diff --git a/sources/pyside6/PySide6/global.h.in b/sources/pyside6/PySide6/global.h.in
deleted file mode 100644 (file)
index 9a1e001..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (C) 2020 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include <QtCore/qnamespace.h>
-
-#if @ENABLE_MAC@
-#  define Q_OS_MAC
-#endif
-#if @ENABLE_WIN@
-#  define Q_OS_WIN
-#endif
-#if @ENABLE_UNIX@
-#  define Q_OS_UNIX
-#endif
-
-// There are symbols in Qt that exist in Debug but
-// not in release
-#define QT_NO_DEBUG
-
-// Here are now all configured modules appended:
index 0a5019bf3bd284db4d005d8a7a71bf3a8228ce30..8e206109195133f982b22a9f0c05aac37c740f34 100644 (file)
@@ -33,7 +33,7 @@ QArgData qArgDataFromPyType(PyObject *t)
     const char *typeName{};
     if (PyType_Check(t)) {
         auto *pyType = reinterpret_cast<PyTypeObject *>(t);
-        typeName = pyType->tp_name;
+        typeName = PepType_GetFullyQualifiedNameStr(pyType);
         result.metaType = PySide::qMetaTypeFromPyType(pyType);
     } else if (PyUnicode_Check(t)) {
         typeName = Shiboken::String::toCString(t);
@@ -225,49 +225,6 @@ return %out;
 // @snippet conversion-qmetatype-pytypeobject
 
 // @snippet qvariant-conversion
-static QVariant QVariant_convertToVariantMap(PyObject *map)
-{
-    Py_ssize_t pos = 0;
-    Shiboken::AutoDecRef keys(PyDict_Keys(map));
-    if (!QVariant_isStringList(keys))
-        return {};
-    PyObject *key{};
-    PyObject *value{};
-    QMap<QString,QVariant> ret;
-    while (PyDict_Next(map, &pos, &key, &value)) {
-        QString cppKey = %CONVERTTOCPP[QString](key);
-        QVariant cppValue = %CONVERTTOCPP[QVariant](value);
-        ret.insert(cppKey, cppValue);
-    }
-    return QVariant(ret);
-}
-static QVariant QVariant_convertToVariantList(PyObject *list)
-{
-    if (QVariant_isStringList(list)) {
-        QList<QString > lst = %CONVERTTOCPP[QList<QString>](list);
-        return QVariant(QStringList(lst));
-    }
-    QVariant valueList = QVariant_convertToValueList(list);
-    if (valueList.isValid())
-        return valueList;
-
-    if (PySequence_Size(list) < 0) {
-        // clear the error if < 0 which means no length at all
-        PyErr_Clear();
-        return {};
-    }
-
-    QList<QVariant> lst;
-    Shiboken::AutoDecRef fast(PySequence_Fast(list, "Failed to convert QVariantList"));
-    const Py_ssize_t size = PySequence_Size(fast.object());
-    for (Py_ssize_t i = 0; i < size; ++i) {
-        Shiboken::AutoDecRef pyItem(PySequence_GetItem(fast.object(), i));
-        QVariant item = %CONVERTTOCPP[QVariant](pyItem);
-        lst.append(item);
-    }
-    return QVariant(lst);
-}
-
 using SpecificConverter = Shiboken::Conversions::SpecificConverter;
 
 static std::optional<SpecificConverter> converterForQtType(const char *typeNameC)
@@ -323,7 +280,7 @@ QList<QByteArray> version = QByteArray(qVersion()).split('.');
 PyObject *pyQtVersion = PyTuple_New(3);
 for (int i = 0; i < 3; ++i)
     PyTuple_SetItem(pyQtVersion, i, PyLong_FromLong(version[i].toInt()));
-PyModule_AddObject(module, "__version_info__", pyQtVersion);
+PepModule_Add(module, "__version_info__", pyQtVersion);
 PyModule_AddStringConstant(module, "__version__", qVersion());
 // @snippet qt-version
 
@@ -433,10 +390,7 @@ static PyObject *qtmsghandler = nullptr;
 static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
 {
     Shiboken::GilState state;
-    PyObject *excType{};
-    PyObject *excValue{};
-    PyObject *excTraceback{};
-    PyErr_Fetch(&excType, &excValue, &excTraceback);
+    Shiboken::Errors::Stash errorStash;
     Shiboken::AutoDecRef arglist(PyTuple_New(3));
     PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QtMsgType](type));
     PyTuple_SetItem(arglist, 1, %CONVERTTOPYTHON[QMessageLogContext &](ctx));
@@ -444,7 +398,6 @@ static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, co
     const char *data = array.constData();
     PyTuple_SetItem(arglist, 2, %CONVERTTOPYTHON[const char *](data));
     Shiboken::AutoDecRef ret(PyObject_CallObject(qtmsghandler, arglist));
-    PyErr_Restore(excType, excValue, excTraceback);
 }
 // @snippet qt-messagehandler
 
@@ -539,6 +492,12 @@ QTime time(%4, %5, %6, %7);
 Shiboken::Warnings::warnDeprecated("QDateTime", "QDateTime(..., Qt::TimeSpec spec)");
 // @snippet qdatetime-3
 
+// @snippet qdatetime-4
+QDate date(%1, %2, %3);
+QTime time(%4, %5, %6, %7);
+%0 = new %TYPE(date, time, QTimeZone(%8));
+// @snippet qdatetime-4
+
 // @snippet qdatetime-topython
 QDate date = %CPPSELF.date();
 QTime time = %CPPSELF.time();
@@ -670,9 +629,12 @@ if (PySlice_Check(_key) == 0)
                  "list indices must be integers or slices, not %.200s",
                  Py_TYPE(_key)->tp_name);
 
-Py_ssize_t start, stop, step, slicelength;
-if (PySlice_GetIndicesEx(_key, %CPPSELF.size(), &start, &stop, &step, &slicelength) < 0)
+Py_ssize_t start{};
+Py_ssize_t stop{};
+Py_ssize_t step{};
+if (PySlice_Unpack(_key, &start, &stop, &step) < 0)
     return nullptr;
+Py_ssize_t slicelength = PySlice_AdjustIndices(%CPPSELF.size(), &start, &stop, step);
 
 QByteArray ba;
 if (slicelength <= 0)
@@ -745,9 +707,12 @@ if (PySlice_Check(_key) == 0) {
     return -1;
 }
 
-Py_ssize_t start, stop, step, slicelength;
-if (PySlice_GetIndicesEx(_key, %CPPSELF.size(), &start, &stop, &step, &slicelength) < 0)
+Py_ssize_t start{};
+Py_ssize_t stop{};
+Py_ssize_t step{};
+if (PySlice_Unpack(_key, &start, &stop, &step) < 0)
     return -1;
+const Py_ssize_t slicelength = PySlice_AdjustIndices(%CPPSELF.size(), &start, &stop, step);
 
 // The parameter candidates are: bytes/str, bytearray, QByteArray itself.
 // Not supported are iterables containing ints between 0~255
@@ -1535,7 +1500,15 @@ double in = %CONVERTTOCPP[double](%in);
 
 // @snippet conversion-sbkobject
 // a class supported by QVariant?
-const QMetaType metaType = QVariant_resolveMetaType(Py_TYPE(%in));
+QMetaType metaType;
+if (Shiboken::Enum::check(%in)) {
+    const auto typeName = PySide::QEnum::getTypeName(Py_TYPE(%in));
+    if (!typeName.isEmpty())
+        metaType = QMetaType::fromName(typeName);
+}
+if (!metaType.isValid())
+  metaType = PySide::Variant::resolveMetaType(Py_TYPE(%in));
+
 bool ok = false;
 if (metaType.isValid()) {
     QVariant var(metaType);
@@ -1556,12 +1529,12 @@ if (!ok)
 // @snippet conversion-sbkobject
 
 // @snippet conversion-pydict
-QVariant ret = QVariant_convertToVariantMap(%in);
+QVariant ret = PySide::Variant::convertToVariantMap(%in);
 %out = ret.isValid() ? ret : QVariant::fromValue(PySide::PyObjectWrapper(%in));
 // @snippet conversion-pydict
 
 // @snippet conversion-pylist
-QVariant ret = QVariant_convertToVariantList(%in);
+QVariant ret = PySide::Variant::convertToVariantList(%in);
 %out = ret.isValid() ? ret : QVariant::fromValue(PySide::PyObjectWrapper(%in));
 // @snippet conversion-pylist
 
@@ -1571,7 +1544,7 @@ QVariant ret = QVariant_convertToVariantList(%in);
 // @snippet conversion-pyobject
 
 // @snippet conversion-qjsonobject-pydict
-QVariant dict = QVariant_convertToVariantMap(%in);
+QVariant dict = PySide::Variant::convertToVariantMap(%in);
 QJsonValue val = QJsonValue::fromVariant(dict);
 %out = val.toObject();
 // @snippet conversion-qjsonobject-pydict
@@ -1853,6 +1826,43 @@ if (Shiboken::Enum::check(%PYARG_2))
     cppArg1 = QVariant(int(Shiboken::Enum::getValue(%PYARG_2)));
 // @snippet qmetaproperty_write_enum
 
+// @snippet qmetaenum_value
+auto valueOpt = %CPPSELF.value64(%1);
+if (valueOpt.has_value()) {
+    const quint64 ullValue = valueOpt.value();
+    %PYARG_0 = PyLong_FromUnsignedLongLong(ullValue);
+} else {
+    const int lValue = %CPPSELF.%FUNCTION_NAME(%1);
+    %PYARG_0 = PyLong_FromLong(lValue);
+}
+// @snippet qmetaenum_value
+
+// @snippet qmetaenum_keytovalue
+PyObject *pyLongValue{};
+auto valueOpt = %CPPSELF.keyToValue64(%1);
+bool ok_ = valueOpt.has_value();
+if (ok_)
+    pyLongValue = PyLong_FromUnsignedLongLong(valueOpt.value());
+else
+    pyLongValue = PyLong_FromLong(%CPPSELF.%FUNCTION_NAME(%1, &ok_));
+%PYARG_0 = PyTuple_New(2);
+PyTuple_SetItem(%PYARG_0, 0, pyLongValue);
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[bool](ok_));
+// @snippet qmetaenum_keytovalue
+
+// @snippet qmetaenum_keystovalue
+PyObject *pyLongValue{};
+auto valueOpt = %CPPSELF.keysToValue64(%1);
+bool ok_ = valueOpt.has_value();
+if (ok_)
+    pyLongValue = PyLong_FromUnsignedLongLong(valueOpt.value());
+else
+    pyLongValue = PyLong_FromLong(%CPPSELF.%FUNCTION_NAME(%1, &ok_));
+%PYARG_0 = PyTuple_New(2);
+PyTuple_SetItem(%PYARG_0, 0, pyLongValue);
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[bool](ok_));
+// @snippet qmetaenum_keystovalue
+
 // @snippet qdatastream-read-bytes
 QByteArray data;
 data.resize(%2);
@@ -2172,7 +2182,9 @@ Q_IMPORT_PLUGIN(QDarwinCalendarPermissionPlugin)
 // @snippet qt-modifier
 PyObject *_inputDict = PyDict_New();
 // Note: The builtins line is no longer needed since Python 3.10. Undocumented!
-PyDict_SetItemString(_inputDict, "__builtins__", PyEval_GetBuiltins());
+Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+PyDict_SetItemString(_inputDict, "__builtins__", builtins.object());
+builtins.reset(nullptr);
 PyDict_SetItemString(_inputDict, "QtCore", module);
 PyDict_SetItemString(_inputDict, "Qt", reinterpret_cast<PyObject *>(pyType));
 // Explicitly not dereferencing the result.
@@ -2219,7 +2231,7 @@ if (%CPPSELF.next()) {
 // @snippet qdirlistingiterator-next
 
 // @snippet qdirlisting-direntry-repr
-QByteArray result = '<' + QByteArray(Py_TYPE(%PYSELF)->tp_name)
+QByteArray result = '<' + QByteArray(PepType_GetFullyQualifiedNameStr(Py_TYPE(%PYSELF)))
                     + " object at 0x"
                     + QByteArray::number(quintptr(%PYSELF), 16) + " (\""
                     + %CPPSELF.absoluteFilePath().toUtf8() + "\")>";
@@ -2243,3 +2255,252 @@ if (PySequence_Check(%PYARG_0) != 0 && PySequence_Size(%PYARG_0) == 2) {
 PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
 PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[qintptr](*result_out));
 // @snippet return-native-eventfilter
+
+
+// @snippet qrangemodel-wrapper
+// Import the template constructors
+using QRangeModel::QRangeModel;
+// @snippet qrangemodel-wrapper
+
+// @snippet qrangemodel-helper-functions
+template <class T>
+static inline QSpan<T> createSpan(void *vData, Py_ssize_t size)
+{
+    auto *data = reinterpret_cast<T *>(vData);
+    return QSpan<T>{data, data + size};
+}
+
+// Simple 2d table range for creating a QRangeModel
+// (potentially replaceable by a std::mdspan in C++ 23).
+template <class T>
+class TableRange
+{
+    struct TableData
+    {
+        T *data = nullptr;
+        qsizetype rowCount = -1;
+        qsizetype columCount = -1;
+    };
+
+public:
+    explicit TableRange(void *data, qsizetype rowCount, qsizetype columCount) :
+        m_data{reinterpret_cast<T *>(data), rowCount, columCount} {}
+
+    class Iterator
+    {
+    public:
+        using value_type = QSpan<T>;
+        using size_type = qsizetype;
+        using reference = value_type;
+        using pointer = value_type;
+        using difference_type = std::ptrdiff_t;
+        using iterator_category = std::random_access_iterator_tag;
+
+        explicit Iterator(const TableData &data, size_type row) noexcept:
+            m_data(data), m_row(row) {}
+
+        Iterator() = default;
+
+        constexpr Iterator &operator++() noexcept
+        {
+            Q_ASSERT(m_row < m_data.rowCount);
+            ++m_row;
+            return *this;
+        }
+
+        constexpr Iterator operator++(int) noexcept
+        {
+            Q_ASSERT(m_row < m_data.rowCount);
+            auto copy = *this;
+            ++m_row;
+            return copy;
+        }
+
+        constexpr Iterator &operator--() noexcept
+        {
+            Q_ASSERT(m_row > 0);
+            --m_row;
+            return *this;
+        }
+
+        constexpr Iterator operator--(int) noexcept
+        {
+            Q_ASSERT(m_row > 0);
+            auto copy = *this;
+            --m_row;
+            return copy;
+        }
+
+        Iterator &operator+=(difference_type i)
+        {
+            const auto row = m_row + i;
+            Q_ASSERT(row >= 0 && row <= m_data.rowCount);
+            m_row = row;
+            return *this;
+        }
+
+        Iterator &operator-=(difference_type i)
+        {
+            const auto row = m_row - i;
+            Q_ASSERT(row >= 0 && row <= m_data.rowCount);
+            m_row = row;
+            return *this;
+        }
+
+        Iterator operator+(difference_type i) const
+        {
+            const auto row = m_row + i;
+            Q_ASSERT(row >= 0 && row <= m_data.rowCount);
+            return {m_data, row};
+        }
+
+        Iterator operator-(difference_type i) const
+        {
+            const auto row = m_row - i;
+            Q_ASSERT(row >= 0 && row <= m_data.rowCount);
+            return {m_data, row};
+        }
+
+        difference_type operator-(const Iterator &it) const { return m_row - it.m_row; } // std::distance
+
+        reference operator*() const noexcept
+        {
+            auto *rowStart = m_data.data + m_row * m_data.columCount;
+            return {rowStart, rowStart + m_data.columCount};
+        }
+
+        [[nodiscard]] value_type operator[](difference_type i) const
+        {
+            auto *rowStart = m_data.data + (m_row + i) * m_data.columCount;
+            return {rowStart, rowStart + m_data.columCount};
+        }
+
+    private:
+        friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs) noexcept
+        {
+            Q_ASSERT(lhs.m_data.data != nullptr);
+            Q_ASSERT(lhs.m_data.data == rhs.m_data.data);
+            return lhs.m_row == rhs.m_row;
+        }
+
+        friend Qt::strong_ordering compareThreeWay(const Iterator &lhs,
+                                                   const Iterator &rhs) noexcept
+        {
+            Q_ASSERT(lhs.m_data.data != nullptr);
+            Q_ASSERT(lhs.m_data.data == rhs.m_data.data);
+            return Qt::compareThreeWay(lhs.m_row, rhs.m_row);
+        }
+
+        Q_DECLARE_STRONGLY_ORDERED(Iterator)
+
+        TableData m_data;
+        size_type m_row = 0;
+    };
+
+    [[nodiscard]] Iterator begin() const { return Iterator(m_data, 0); }
+    [[nodiscard]] Iterator end() const   { return Iterator(m_data, m_data.rowCount); }
+
+private:
+    TableData m_data;
+};
+
+template <class RangeModel> // QRangeModelWrapper
+static RangeModel *createRangeModel(PyObject *in, QObject *parent)
+{
+    auto view = Shiboken::Numpy::View::fromPyObject(in);
+    if (!view) {
+        PyErr_SetString(PyExc_TypeError, "Invalid parameter or missing numpy support.");
+        return nullptr;
+    }
+    switch (view.ndim) {
+    case 1: {
+        const auto size = view.dimensions[0];
+        switch (view.type) {
+        case Shiboken::Numpy::View::Int16:
+            return new RangeModel(createSpan<short>(view.data, size), parent);
+        case Shiboken::Numpy::View::Unsigned16:
+            return new RangeModel(createSpan<unsigned short>(view.data, size), parent);
+        case Shiboken::Numpy::View::Int:
+            return new RangeModel(createSpan<int>(view.data, size), parent);
+        case Shiboken::Numpy::View::Unsigned:
+            return new RangeModel(createSpan<unsigned>(view.data, size), parent);
+        case Shiboken::Numpy::View::Int64:
+            return new RangeModel(createSpan<int64_t>(view.data, size), parent);
+        case Shiboken::Numpy::View::Unsigned64:
+            return new RangeModel(createSpan<uint64_t>(view.data, size), parent);
+        case Shiboken::Numpy::View::Float:
+            return new RangeModel(createSpan<float>(view.data, size), parent);
+        case Shiboken::Numpy::View::Double:
+            return new RangeModel(createSpan<double>(view.data, size), parent);
+        default:
+            PyErr_SetString(PyExc_TypeError, "Unsupported data type for one-dimensional arrays.");
+            return nullptr;
+        }
+    }
+    break;
+
+    case 2: {
+        const auto rows = view.dimensions[0];
+        const auto columns = view.dimensions[1];
+        switch (view.type) {
+        case Shiboken::Numpy::View::Int16:
+            return new RangeModel(TableRange<short>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Unsigned16:
+            return new RangeModel(TableRange<unsigned short>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Int:
+            return new RangeModel(TableRange<int>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Unsigned:
+            return new RangeModel(TableRange<unsigned>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Int64:
+            return new RangeModel(TableRange<int64_t>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Unsigned64:
+            return new RangeModel(TableRange<uint64_t>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Float:
+            return new RangeModel(TableRange<float>(view.data, rows, columns), parent);
+        case Shiboken::Numpy::View::Double:
+            return new RangeModel(TableRange<double>(view.data, rows, columns), parent);
+        default:
+            PyErr_SetString(PyExc_TypeError, "Unsupported data type for two-dimensional arrays.");
+            return nullptr;
+        }
+    }
+    break;
+    default:
+        PyErr_SetString(PyExc_TypeError, "Only one and two-dimensional arrays are supported.");
+        return nullptr;
+    }
+    return nullptr;
+}
+
+static bool isVariantList(const QVariant &v)
+{
+    return v.typeId() == QMetaType::QVariantList;
+};
+// @snippet qrangemodel-helper-functions
+
+// @snippet qrangemodel-numpy-constructor
+auto *model = createRangeModel<%TYPE>(%PYARG_1, %2);
+if (model == nullptr)
+    return -1;
+%0 = model;
+// @snippet qrangemodel-numpy-constructor
+
+// @snippet qrangemodel-sequence-constructor
+const auto vlOptional = PySide::Variant::pyListToVariantList(%PYARG_1);
+if (!vlOptional.has_value()) {
+    PyErr_SetString(PyExc_TypeError, "Unable convert input sequence.");
+    return -1;
+}
+
+const QVariantList &vList = vlOptional.value();
+if (!vList.isEmpty() && std::all_of(vList.cbegin(),  vList.cend(), isVariantList)) {
+    // Empirical: Transform QVariantList<QVariant(List)>  -> QList<QVariantList> for a table
+    QList<QVariantList> variantTable;
+    variantTable.reserve(vList.size());
+    for (const auto &rowV : vList)
+        variantTable.append(rowV.value<QVariantList>());
+    %0 = new %TYPE(variantTable, %2);
+} else {
+    %0 = new %TYPE(vList, %2);
+}
+// @snippet qrangemodel-sequence-constructor
index 72d3d2b46ee48e2ca11c11c5fff2417c7bc94943..4b8e718a863c5e5e9401d1b5a941603b920a2806 100644 (file)
@@ -365,6 +365,11 @@ for (Py_ssize_t i = 0; i < count; ++i){
 %PYARG_0 = %CONVERTTOPYTHON[QPolygon *](%CPPSELF);
 // @snippet qpolygon-operatorlowerlower
 
+// @snippet qpolygonf-operatorlowerlower
+*%CPPSELF << %1;
+%PYARG_0 = %CONVERTTOPYTHON[QPolygonF *](%CPPSELF);
+// @snippet qpolygonf-operatorlowerlower
+
 // @snippet qpixmap
 %0 = new %TYPE(QPixmap::fromImage(%1));
 // @snippet qpixmap
@@ -853,7 +858,7 @@ for (Py_ssize_t i = 0; i < 16; ++i) {
 }
 // @snippet qmatrix4x4-copydatato
 
-// @snippet qmatrix4x4-mgetitem
+// @snippet qmatrix-mgetitem
 if (PySequence_Check(_key)) {
     Shiboken::AutoDecRef key(PySequence_Fast(_key, "Invalid matrix index."));
     if (PySequence_Size(key.object()) == 2) {
@@ -867,7 +872,7 @@ if (PySequence_Check(_key)) {
 }
 PyErr_SetString(PyExc_IndexError, "Invalid matrix index.");
 return 0;
-// @snippet qmatrix4x4-mgetitem
+// @snippet qmatrix-mgetitem
 
 // @snippet qguiapplication-init
 static void QGuiApplicationConstructor(PyObject *self, PyObject *pyargv, QGuiApplicationWrapper **cptr)
@@ -907,7 +912,13 @@ if (auto *x11App = %CPPSELF.nativeInterface<QNativeInterface::QX11Application>()
     hasNativeApp = true;
     %PYARG_0 = %CONVERTTOPYTHON[QNativeInterface::QX11Application*](x11App);
 }
-#endif
+#endif // xcb
+#if QT_CONFIG(wayland)
+if (auto *waylandApp = %CPPSELF.nativeInterface<QNativeInterface::QWaylandApplication>()) {
+    hasNativeApp = true;
+    %PYARG_0 = %CONVERTTOPYTHON[QNativeInterface::QWaylandApplication*](waylandApp);
+}
+#endif // wayland
 if (!hasNativeApp) {
     Py_INCREF(Py_None);
     %PYARG_0 = Py_None;
@@ -934,10 +945,11 @@ if (!hasNativeScreen) {
 }
 // @snippet qscreen-nativeInterface
 
-// @snippet qx11application-resource-ptr
+// Return 'int' from native interface's forward-declared structs like Display*
+// @snippet native-resource-ptr
  auto *resource = %CPPSELF.%FUNCTION_NAME();
 %PYARG_0 = PyLong_FromVoidPtr(resource);
-// @snippet qx11application-resource-ptr
+// @snippet native-resource-ptr
 
 // @snippet qwindow-fromWinId
 WId id = %1;
@@ -1055,7 +1067,7 @@ return %CONVERTTOPYTHON[QRect](cppResult);
 // @snippet qpainterstateguard-restore
 
 // @snippet qmatrix-repr-code
-QByteArray format(Py_TYPE(%PYSELF)->tp_name);
+QByteArray format(PepType_GetFullyQualifiedNameStr(Py_TYPE(%PYSELF)));
 format += QByteArrayLiteral("((");
 %MATRIX_TYPE data[%MATRIX_SIZE];
 %CPPSELF.copyDataTo(data);
index ac8434b97b1a2a414525a4a217fa7340c67b15f5..457621638cb18c4ca2bfc036e0355a63a8c7bb0c 100644 (file)
@@ -22,7 +22,7 @@ const auto size = %CPPSELF.byteCount();
 %PYARG_0 = Shiboken::Buffer::newObject(data, size);
 // @snippet qaudiobuffer-const-data
 
-// @snippet qtaudio-namespace-compatibility-alias
-Py_INCREF(pyType);
-PyModule_AddObject(module, "QtAudio", reinterpret_cast<PyObject *>(pyType));
-// @snippet qtaudio-namespace-compatibility-alias
+// @snippet qaudio-convertvolume
+const float result = QtAudio::convertVolume(%1, %2, %3);
+%PYARG_0 = %CONVERTTOPYTHON[float](result);
+// @snippet qaudio-convertvolume
index 6d6336a83ab3da208ff224e7f60a0d0df52ff644..b7140828bd39830c08d3b3b2b9f1ef226305b157 100644 (file)
@@ -15,7 +15,7 @@ if (!signature.isEmpty())
 
 if (emitter == nullptr || signature.isEmpty()) {
     QByteArray error = QByteArrayLiteral("Wrong parameter (")
-        + (%PYARG_1)->ob_type->tp_name
+        + PepType_GetFullyQualifiedNameStr(Py_TYPE(%PYARG_1))
         + QByteArrayLiteral(") passed, QSignalSpy requires a signal.");
     PyErr_SetString(PyExc_ValueError, error.constData());
     return -1;
index 3fe3554c4f8eb73a10ba393dc6413ab1cd8432ee..6eba9a94ffa01046eda60e6652433526d7e24eae 100644 (file)
@@ -7,7 +7,8 @@
  *          Frédéric <frederic.mantegazza at gbiloba.org>
  */
 
-#include <shiboken.h>
+#include <sbkpython.h>
+#include <sbkconverter.h>
 
 #include <QtUiTools/QUiLoader>
 #include <QtWidgets/QWidget>
diff --git a/sources/pyside6/PySide6/pyside6_global.h b/sources/pyside6/PySide6/pyside6_global.h
new file mode 100644 (file)
index 0000000..fe417b6
--- /dev/null
@@ -0,0 +1,10 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qnamespace.h>
+
+// There are symbols in Qt that exist in Debug but
+// not in release
+#define QT_NO_DEBUG
+
+// Here are now all configured modules appended:
index 5149f1a79794d0bb047e09998eecab43c66d4b11..b3bdd2b9a127b46ba649dcdfb1d6d10606884291 100644 (file)
 
     <template name="repr_code">
         QString format = QString::asprintf("%s(%REPR_FORMAT)",
-            Py_TYPE(%PYSELF)->tp_name, %REPR_ARGS);
+            PepType_GetFullyQualifiedNameStr(Py_TYPE(%PYSELF)), %REPR_ARGS);
         %PYARG_0 = Shiboken::String::fromCString(qPrintable(format));
     </template>
 
index 5cd12b6832d29aeb40b73a61c86f1652dcae1086..86791f4c7ae02de331d8205c94555d568b8de8ab 100644 (file)
@@ -210,27 +210,18 @@ macro(create_pyside_module)
     # comes as a default requirement for building PySide6. As such for
     # cross-compiling in linux, we use the clang compiler from the installed
     # libclang itself.
-    if(CMAKE_ANDROID_ARCH_LLVM_TRIPLE AND CMAKE_HOST_APPLE)
-        message(STATUS "Building for Android with arch ${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}")
-        list(APPEND shiboken_command "--clang-option=--target=${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}")
-
-        # CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX does not contain the ANDROID_PLATFORM i.e. it ends with
-        # the form 'aarch64-linux-android-'. Remove the last '-' and add the corresponding clang
-        # based on ANDROID_PLATFORM making it 'aarch64-linux-android26-clang++'
-
-        # Get the length of the string
-        string(LENGTH "${CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX}" _length)
-
-        # Subtract 1 from the length to get the characters till '-'
-        math(EXPR _last_index "${_length} - 1")
-
-        # Get the substring from the start to the character before the last one
-        string(SUBSTRING "${CMAKE_CXX_ANDROID_TOOLCHAIN_PREFIX}" 0 "${_last_index}"
-               SHIBOKEN_ANDROID_COMPILER_PREFIX)
+    if (CMAKE_CROSSCOMPILING)
+        list(APPEND shiboken_command "--platform=${CMAKE_SYSTEM_NAME}"
+                                     "--arch=${CMAKE_SYSTEM_PROCESSOR}"
+                                     "--compiler-path=${CMAKE_CXX_COMPILER}")
+    endif()
 
-        # use the compiler from the Android NDK
+    if(CMAKE_ANDROID_ARCH_LLVM_TRIPLE)
+        message(STATUS "Building for Android with arch ${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}")
+        # CMAKE_CXX_COMPILER is the generic clang++; for finding the include paths,
+        # it needs "--target".
         list(APPEND shiboken_command
-            "--compiler-path=${SHIBOKEN_ANDROID_COMPILER_PREFIX}${CMAKE_ANDROID_API}-clang++")
+             "--compiler-argument=--target=${CMAKE_ANDROID_ARCH_LLVM_TRIPLE}")
     endif()
 
     if(CMAKE_HOST_APPLE)
@@ -294,6 +285,24 @@ macro(create_pyside_module)
         set(ld_prefix_var_name "LD_LIBRARY_PATH")
     endif()
 
+    # Get the build type, default to RELEASE if not set
+    if(CMAKE_BUILD_TYPE)
+        string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type)
+    else()
+        set(_build_type "RELEASE")
+    endif()
+
+    # Try to get the location for the current build type
+    get_target_property(_shiboken_lib_location Shiboken6::libshiboken IMPORTED_LOCATION_${_build_type})
+
+    # Fallback to RELEASE if not found
+    if(NOT _shiboken_lib_location)
+        get_target_property(_shiboken_lib_location Shiboken6::libshiboken IMPORTED_LOCATION_RELEASE)
+    endif()
+
+    # Get the directory containing the library file, which is the lib directory
+    get_filename_component(SHIBOKEN_SHARED_LIBRARY_DIR "${_shiboken_lib_location}" DIRECTORY)
+
     set(ld_prefix_list "")
     list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpyside")
     list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpysideqml")
@@ -337,9 +346,11 @@ macro(create_pyside_module)
     # on the host machine (usually, unless you use some userspace qemu based mechanism).
     # TODO: Can we do something better here to still get pyi files?
     if(NOT (PYSIDE_IS_CROSS_BUILD OR DISABLE_PYI))
+        set(SHIBOKEN_PYTHON_MODULE_DIR "${PYTHON_SITE_PACKAGES}/shiboken6")
         set(generate_pyi_options ${module_NAME} --sys-path
             "${pysidebindings_BINARY_DIR}"
-            "${SHIBOKEN_PYTHON_MODULE_DIR}/..")     # use the layer above shiboken6
+            "${SHIBOKEN_PYTHON_MODULE_DIR}/.."
+            "${SHIBOKEN_PYTHON_MODULE_DIR}/../../..")     # use the layer above shiboken6
         if (QUIET_BUILD)
             list(APPEND generate_pyi_options "--quiet")
         endif()
@@ -367,7 +378,7 @@ macro(create_pyside_module)
 
 
     install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PySide6/${module_NAME}/pyside6_${lower_module_name}_python.h
-            DESTINATION include/PySide6${pyside6_SUFFIX}/${module_NAME}/)
+            DESTINATION PySide6${pyside6_SUFFIX}/include/${module_NAME}/)
     file(GLOB typesystem_files ${CMAKE_CURRENT_SOURCE_DIR}/typesystem_*.xml ${typesystem_path})
 
 #   Copy typesystem files and remove module names from the <load-typesystem> element
index a4641ee651616ef280ee7d4128095ac57d13605a..9891eab3fe9c56a2bbc6080b72014ab87a96dea6 100644 (file)
@@ -50,6 +50,7 @@ function(pyside_internal_find_host_shiboken_tools)
         list(APPEND "REQUIRED")
     endif()
 
+    set(SHIBOKEN6TOOLS_SKIP_FIND_DEPENDENCIES TRUE)
     find_package(
         Shiboken6Tools 6 CONFIG
         ${find_package_extra_args}
@@ -132,33 +133,17 @@ macro(collect_optional_modules)
     list(APPEND ALL_OPTIONAL_MODULES WebChannel WebEngineCore WebEngineWidgets
          WebEngineQuick WebSockets HttpServer)
     find_package(Qt${QT_MAJOR_VERSION}WebEngineQuick)
-    # for Windows and Linux, QtWebView depends on QtWebEngine to render content
-    if(Qt${QT_MAJOR_VERSION}WebEngineQuick_FOUND OR APPLE)
+    # For Windows and Linux, QtWebView depends on QtWebEngine to render content.
+    # On Android and Apple platforms, QtWebView uses the native webview backend and
+    # does not require QtWebEngine.
+    if(APPLE OR ANDROID)
+        list(APPEND ALL_OPTIONAL_MODULES WebView)
+    elseif(Qt${QT_MAJOR_VERSION}WebEngineQuick_FOUND)
         list(APPEND ALL_OPTIONAL_MODULES WebView)
     endif()
     list(APPEND ALL_OPTIONAL_MODULES 3DCore 3DRender 3DInput 3DLogic 3DAnimation 3DExtras)
 endmacro()
 
-macro(check_os)
-    set(ENABLE_UNIX "1")
-    set(ENABLE_MAC "0")
-    set(ENABLE_WIN "0")
-
-    # check if Android, if so, set ENABLE_UNIX=1
-    # this is needed to avoid including the wrapper specific to macOS when building for Android
-    # from a macOS host
-    if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android")
-        if(CMAKE_HOST_APPLE)
-            set(ENABLE_MAC "1")
-        elseif(CMAKE_HOST_WIN32)
-            set(ENABLE_WIN "1")
-            set(ENABLE_UNIX "0")
-        elseif(NOT CMAKE_HOST_UNIX)
-            message(FATAL_ERROR "OS not supported")
-        endif()
-    endif()
-endmacro()
-
 macro(use_protected_as_public_hack)
     # 2017-04-24 The protected hack can unfortunately not be disabled, because
     # Clang does produce linker errors when we disable the hack.
@@ -255,9 +240,10 @@ macro(collect_module_if_found shortname)
         # record the shortnames for the tests
         list(APPEND all_module_shortnames ${shortname})
         # Build Qt 5 compatibility variables
-        if(${QT_MAJOR_VERSION} GREATER_EQUAL 6 AND NOT "${shortname}" STREQUAL "OpenGLFunctions")
-            get_target_property(Qt6${shortname}_INCLUDE_DIRS Qt6::${shortname}
-                                INTERFACE_INCLUDE_DIRECTORIES)
+        get_target_property(Qt6${shortname}_INCLUDE_DIRS Qt6::${shortname}
+                            INTERFACE_INCLUDE_DIRECTORIES)
+        # Find QtGui private headers for exposing some QPA classes
+        if("${shortname}" STREQUAL "Gui")
             get_target_property(Qt6${shortname}_PRIVATE_INCLUDE_DIRS
                                 Qt6::${shortname}Private
                                 INTERFACE_INCLUDE_DIRECTORIES)
index 45a63a1a097f1eb682f1f7d90c64f0ed38d4e44e..72d6a1ccf6f44004cf797f20c4ba324d43b65f29 100644 (file)
@@ -52,6 +52,7 @@ set(BINDING_API_MINOR_VERSION "${pyside_MINOR_VERSION}")
 set(BINDING_API_MICRO_VERSION "${pyside_MICRO_VERSION}")
 set(BINDING_API_PRE_RELEASE_VERSION_TYPE "${pyside_PRE_RELEASE_VERSION_TYPE}")
 set(BINDING_API_PRE_RELEASE_VERSION "${pyside_PRE_RELEASE_VERSION}")
+set(pyside6_library_so_version "${SHIBOKEN_SO_VERSION}")
 
 # Detect if the Python interpreter is actually PyPy
 execute_process(
@@ -97,7 +98,8 @@ if(QFP_QT_HOST_PATH)
         endif()
     endif()
 endif()
-find_package(Qt6 REQUIRED COMPONENTS Core)
+# Find QtGui private headers for exposing some QPA classes
+find_package(Qt6 REQUIRED COMPONENTS Core CorePrivate Gui GuiPrivate)
 
 add_definitions(${Qt${QT_MAJOR_VERSION}Core_DEFINITIONS})
 
@@ -167,9 +169,12 @@ set (Qt${QT_MAJOR_VERSION}Widgets_FOUND "0")
 collect_essential_modules()
 collect_optional_modules()
 
+# Additional (non-Qt) modules implemented in PySide only
+set(PURE_PYTHON_MODULES Asyncio)
+
 # Modules to be built unless specified by -DMODULES on command line
 if(NOT MODULES)
-    set(MODULES "${ALL_ESSENTIAL_MODULES};${ALL_OPTIONAL_MODULES}")
+    set(MODULES "${ALL_ESSENTIAL_MODULES};${ALL_OPTIONAL_MODULES};${PURE_PYTHON_MODULES}")
     set(required_modules ${ALL_ESSENTIAL_MODULES})
     set(optional_modules ${ALL_OPTIONAL_MODULES})
 else()
@@ -180,6 +185,16 @@ list(REMOVE_ITEM MODULES ${SKIP_MODULES})
 list(REMOVE_ITEM required_modules ${SKIP_MODULES})
 list(REMOVE_ITEM optional_modules ${SKIP_MODULES})
 
+# Non-Qt modules must be removed before find_packages tries to locate them.
+foreach(m IN LISTS PURE_PYTHON_MODULES)
+    set(DISABLE_Qt${m} 1)
+    if("Qt${m}" IN_LIST MODULES OR "${m}" IN_LIST MODULES)
+        set(DISABLE_Qt${m} 0)
+    endif()
+    list(FILTER MODULES EXCLUDE REGEX "^(Qt)?${m}$")
+    list(FILTER required_modules EXCLUDE REGEX "^(Qt)?${m}$")
+endforeach()
+
 find_package(Qt6
     COMPONENTS ${required_modules}
     OPTIONAL_COMPONENTS ${optional_modules}
@@ -192,7 +207,7 @@ remove_skipped_modules()
 
 # Mark all non-collected modules as disabled. This is used for disabling tests
 # that depend on the disabled modules.
-foreach(m ${DISABLED_MODULES})
+foreach(m IN LISTS DISABLED_MODULES)
     set(DISABLE_Qt${m} 1)
 endforeach()
 
@@ -216,9 +231,6 @@ endif()
 
 # no more supported: include(${QT_USE_FILE})
 
-# Configure OS support
-check_os()
-
 # Define supported Qt Version
 set(SUPPORTED_QT_VERSION "${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
 
@@ -255,8 +267,11 @@ set(GENERATOR_EXTRA_FLAGS
 use_protected_as_public_hack()
 
 # Build with Address sanitizer enabled if requested. This may break things, so use at your own risk.
-if(SANITIZE_ADDRESS AND NOT MSVC)
+if(SANITIZE_ADDRESS)
     setup_sanitize_address()
 endif()
+if(SANITIZE_THREAD)
+    setup_sanitize_thread()
+endif()
 
 find_package(Qt6 COMPONENTS Designer)
index 8135c805676a680e90e217b8e8ed454fcfb635ae..62a7a430c0e864e7ff5c1edbdc105e2e30f252fd 100644 (file)
@@ -143,7 +143,7 @@ if (FULLDOCSBUILD)
     configure_file("pyside-config.qdocconf.in" "${config_docconf}" @ONLY)
 
     set(global_header "${pyside6_BINARY_DIR}/qdoc.h")
-    file(READ "${pyside6_BINARY_DIR}/pyside6_global.h" docHeaderContents)
+    file(READ "${CMAKE_CURRENT_LIST_DIR}/../PySide6/pyside6_global.h" docHeaderContents)
     file(WRITE ${global_header} "${docHeaderContents}")
 
     set(global_typesystem "${CMAKE_CURRENT_BINARY_DIR}/typesystem_doc.xml")
@@ -221,7 +221,6 @@ if(DOC_OUTPUT_FORMAT STREQUAL "html")
                     ${CMAKE_CURRENT_BINARY_DIR}/../../shiboken6/doc/html
                     ${CMAKE_CURRENT_BINARY_DIR}/html/shiboken6
             COMMENT "Copying Shiboken docs..."
-            DEPENDS "${DOC_DATA_DIR}/webxml/qtcore-index.webxml"
             VERBATIM)
 else()
     if(qhelpgenerator_binary)
@@ -233,7 +232,6 @@ else()
                 COMMAND ${python_executable} ${PATCH_QHP_SCRIPT} -p -v pyside6 ${QHP_FILE}
                 COMMAND "${qhelpgenerator_binary}" ${QHP_FILE}
                 COMMENT "Generating QCH from a QHP file..."
-                DEPENDS "${DOC_DATA_DIR}/webxml/qtcore-index.webxml"
                 VERBATIM
         )
     else()
@@ -249,6 +247,8 @@ set(CODE_SNIPPET_ROOT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/codesnippets"
 if (FULLDOCSBUILD)
 shiboken_get_tool_shell_wrapper(shiboken tool_wrapper)
 
+set(RHI_INCLUDE_DIR ${QT_INCLUDE_DIR}/QtGui/${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}/QtGui)
+
 add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/QtCore/index.rst"
                    COMMAND
                        ${tool_wrapper}
@@ -256,7 +256,7 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/Q
                        --generator-set=qtdoc
                        ${global_header}
                        --enable-pyside-extensions
-                       --include-paths="${QT_INCLUDE_DIR}${PATH_SEP}${pyside6_SOURCE_DIR}${PATH_SEP}${TS_ROOT}"
+                       --include-paths="${QT_INCLUDE_DIR}${PATH_SEP}${RHI_INCLUDE_DIR}${PATH_SEP}${pyside6_SOURCE_DIR}${PATH_SEP}${TS_ROOT}"
                        --api-version=${SUPPORTED_QT_VERSION}
                        --typesystem-paths="${QDOC_TYPESYSTEM_PATH}"
                        --library-source-dir=${QT_SRC_DIR}
@@ -268,7 +268,6 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/PySide6/Q
                        --additional-documentation=${CMAKE_CURRENT_BINARY_DIR}/${DOC_BASE_DIR}/additionaldocs.lst
                        --inheritance-file=${ENV_INHERITANCE_FILE}
                        ${global_typesystem}
-                   WORKING_DIRECTORY ${${module}_SOURCE_DIR}
                    COMMENT "Running generator to generate documentation...")
 endif()
 
index 0fdf2041aff804e99ea745f13f9efe42da63ee0a..d694f75f96ce445b65ecff5afd9e38af1adb30d6 100644 (file)
@@ -15,9 +15,9 @@ My tags: Android
     ../examples/example_multimedia_camera.rst
     ../examples/example_qml_editingmodel.rst
     ../examples/example_qml_usingmodel.rst
+    ../examples/example_quick_customitems_painteditem.rst
     ../examples/example_quick_models_objectlistmodel.rst
     ../examples/example_quick_models_stringlistmodel.rst
-    ../examples/example_quick_painteditem.rst
     ../examples/example_quickcontrols_contactslist.rst
     ../examples/example_quickcontrols_gallery.rst
     ../examples/example_widgets_widgets_digitalclock.rst
index 700647a7904578bca206107ad84773c3e3aa8847..8a580ea61be156f2e3bf2e115aa6bbf9268c9685 100644 (file)
@@ -4,7 +4,6 @@
 # A line enclosed in [] denotes a (relative) target directory
 
 [overviews]
-qt3d/webxml/qt3d-changes-qt6.webxml
 qt3d/webxml/qt3d-cpp.webxml
 qt3d/webxml/qt3d-overview.webxml
 qt3d/webxml/qt3d-qml.webxml
@@ -12,20 +11,14 @@ qt3d/webxml/qt3drender-framegraph.webxml
 qt3d/webxml/qt3drender-geometry.webxml
 qt3d/webxml/qt3drender-porting-to-rhi.webxml
 qt3d/webxml/qt3drender-protips.webxml
-qtbluetooth/webxml/qtbluetooth-changes-qt6.webxml
 qtbluetooth/webxml/qtbluetooth-le-overview.webxml
 qtbluetooth/webxml/qtbluetooth-overview.webxml
-qtcharts/webxml/qtcharts-changes-qt6.webxml
-qtcharts/webxml/qtcharts-overview.webxml
-qtconcurrent/webxml/concurrent-changes-qt6.webxml
 qtconcurrent/webxml/qtconcurrentfilter.webxml
 qtconcurrent/webxml/qtconcurrentmap.webxml
 qtconcurrent/webxml/qtconcurrentrun.webxml
 qtconcurrent/webxml/qtconcurrenttask.webxml
 qtcore/webxml/animation-overview.webxml
 qtcore/webxml/bindableproperties.webxml
-qtcore/webxml/cbor.webxml
-qtcore/webxml/containers.webxml
 qtcore/webxml/custom-types.webxml
 qtcore/webxml/datastreamformat.webxml
 qtcore/webxml/eventsandfilters.webxml
@@ -33,104 +26,134 @@ qtcore/webxml/implicit-sharing.webxml
 qtcore/webxml/io-functions.webxml
 qtcore/webxml/ipc.webxml
 qtcore/webxml/java-style-iterators.webxml
-qtcore/webxml/json.webxml
 qtcore/webxml/metaobjects.webxml
-qtcore/webxml/objecttrees.webxml
+qtcore/webxml/native-ipc-keys.webxml
 qtcore/webxml/object.webxml
+qtcore/webxml/objecttrees.webxml
 qtcore/webxml/permissions.webxml
-qtcore/webxml/plugins.webxml
 qtcore/webxml/properties.webxml
-qtcore/webxml/qabstracteventdispatcher-timerinfo.webxml
-qtcore/webxml/qadoptshareddatatag.webxml
-qtcore/webxml/qcborerror.webxml
-qtcore/webxml/qcborparsererror.webxml
-qtcore/webxml/qcborstreamreader-stringresult.webxml
-qtcore/webxml/qglobalstatic.webxml
-qtcore/webxml/qhashseed.webxml
-qtcore/webxml/qiterator.webxml
-qtcore/webxml/qjsonparseerror.webxml
-qtcore/webxml/qprocess-createprocessarguments.webxml
-qtcore/webxml/qstaticplugin.webxml
-qtcore/webxml/qtalgorithms.webxml
+qtcore/webxml/qt-add-android-permission.webxml
 qtcore/webxml/qtcborcommon.webxml
-qtcore/webxml/qtcore-changes-qt6.webxml
+qtcore/webxml/qtdarwinhelpers.webxml
+qtcore/webxml/qtdeprecationmarkers.webxml
+qtcore/webxml/qtenvironmentvariables.webxml
 qtcore/webxml/qtglobal.webxml
-qtcore/webxml/qtimezone-offsetdata.webxml
+qtcore/webxml/qtlogging.webxml
 qtcore/webxml/qtmath.webxml
 qtcore/webxml/qtplugin.webxml
+qtcore/webxml/qtresource.webxml
+qtcore/webxml/qtserialization.webxml
+qtcore/webxml/qtsystemdetection.webxml
+qtcore/webxml/qttranslation.webxml
+qtcore/webxml/qtversion.webxml
+qtcore/webxml/qtversionchecks.webxml
+qtcore/webxml/qtypeinfo.webxml
 qtcore/webxml/resources.webxml
-qtcore/webxml/shared.webxml
+qtcore/webxml/shared-memory.webxml
 qtcore/webxml/signalsandslots.webxml
 qtcore/webxml/timers.webxml
-qtdbus/webxml/dbus-changes-qt6.webxml
 qtdbus/webxml/qdbusdeclaringsignals.webxml
 qtdbus/webxml/qdbusdeclaringslots.webxml
 qtdbus/webxml/qdbustypesystem.webxml
 qtdbus/webxml/qdbusviewer.webxml
 qtdbus/webxml/qdbusxml2cpp.webxml
-qtdbus/webxml/qtdbus-cmake-qt-add-dbus-adaptor.webxml
-qtdbus/webxml/qtdbus-cmake-qt-add-dbus-interfaces.webxml
-qtdbus/webxml/qtdbus-cmake-qt-add-dbus-interface.webxml
-qtdbus/webxml/qtdbus-cmake-qt-generate-dbus-interface.webxml
+qtdbus/webxml/qtdbus-overview.webxml
 qtdbus/webxml/usingadaptors.webxml
-qtdesigner/webxml/qtdesigner-components.webxml
-qtdesigner/webxml/qtdesigner-manual.webxml
+qtdoc/webxml/accessible-qwidget.webxml
 qtdoc/webxml/accessible.webxml
+qtdoc/webxml/annotated.webxml
 qtdoc/webxml/appicon.webxml
-qtdoc/webxml/create-your-first-applications.webxml
-qtdoc/webxml/deployment.webxml
+qtdoc/webxml/bughowto.webxml
+qtdoc/webxml/classes.webxml
+qtdoc/webxml/classesandfunctions.webxml
 qtdoc/webxml/desktop-integration.webxml
-qtdoc/webxml/exceptionsafety.webxml
+qtdoc/webxml/embedded-linux.webxml
 qtdoc/webxml/explore-qt.webxml
+qtdoc/webxml/exportcontrols.webxml
+qtdoc/webxml/fdl.webxml
+qtdoc/webxml/functions.webxml
+qtdoc/webxml/get-and-install-qt-cli.webxml
 qtdoc/webxml/get-and-install-qt.webxml
+qtdoc/webxml/getting-sources-from-git.webxml
 qtdoc/webxml/gettingstarted.webxml
+qtdoc/webxml/gpl.webxml
 qtdoc/webxml/highdpi.webxml
-qtdoc/webxml/install-qt-design-studio.webxml
+qtdoc/webxml/i18n-plural-rules.webxml
+qtdoc/webxml/i18n-source-translation.webxml
+qtdoc/webxml/index.webxml
+qtdoc/webxml/inputs-linux-device.webxml
+qtdoc/webxml/ipc-overview.webxml
 qtdoc/webxml/known-issues.webxml
+qtdoc/webxml/lgpl.webxml
+qtdoc/webxml/linux-issues.webxml
+qtdoc/webxml/linux-requirements.webxml
+qtdoc/webxml/linux.webxml
+qtdoc/webxml/localization.webxml
+qtdoc/webxml/macos-issues.webxml
+qtdoc/webxml/macos.webxml
 qtdoc/webxml/mobiledevelopment.webxml
+qtdoc/webxml/modulechanges.webxml
+qtdoc/webxml/modules-cpp.webxml
+qtdoc/webxml/modules-qml.webxml
+qtdoc/webxml/namespaces.webxml
 qtdoc/webxml/overviews-main.webxml
-qtdoc/webxml/plugins-howto.webxml
-qtdoc/webxml/qmlapplications.webxml
-qtdoc/webxml/qml-codingconventions.webxml
-qtdoc/webxml/qmlfirststeps.webxml
-qtdoc/webxml/qml-glossary.webxml
+qtdoc/webxml/overviews.webxml
+qtdoc/webxml/porting-qt3d-to-qtquick3d.webxml
+qtdoc/webxml/porting-to-android.webxml
+qtdoc/webxml/portingguide.webxml
+qtdoc/webxml/qmltypes.webxml
+qtdoc/webxml/qmlvaluetypes.webxml
+qtdoc/webxml/qt-additional-modules.webxml
+qtdoc/webxml/qt-edu-for-designers.webxml
+qtdoc/webxml/qt-edu-for-developers.webxml
+qtdoc/webxml/qt-edu-mcu.webxml
+qtdoc/webxml/qt-edu-raspberry-pi.webxml
+qtdoc/webxml/qt-edu-resources.webxml
+qtdoc/webxml/qt-embedded-fonts.webxml
+qtdoc/webxml/qt-embedded-kmap2qmap.webxml
+qtdoc/webxml/qt-for-education.webxml
 qtdoc/webxml/qt-intro.webxml
-qtdoc/webxml/qtlanguages.webxml
-qtdoc/webxml/qtquick-debugging.webxml
-qtdoc/webxml/qtquick-performance.webxml
-qtdoc/webxml/qtquick-qml-runtime.webxml
-qtdoc/webxml/qtquick-usecase-animations.webxml
-qtdoc/webxml/qtquick-usecase-integratingjs.webxml
-qtdoc/webxml/qtquick-usecase-layouts.webxml
-qtdoc/webxml/qtquick-usecase-styling.webxml
-qtdoc/webxml/qtquick-usecase-text.webxml
-qtdoc/webxml/qtquick-usecase-userinput.webxml
-qtdoc/webxml/qtquick-usecase-visual.webxml
+qtdoc/webxml/qt-online-installation.webxml
 qtdoc/webxml/qt-releases.webxml
+qtdoc/webxml/qt-tools-utilities.webxml
+qtdoc/webxml/qtlanguages.webxml
+qtdoc/webxml/qtmodules.webxml
 qtdoc/webxml/qundo.webxml
 qtdoc/webxml/rcc.webxml
+qtdoc/webxml/reference-overview.webxml
 qtdoc/webxml/restoring-geometry.webxml
 qtdoc/webxml/scalability.webxml
 qtdoc/webxml/security.webxml
 qtdoc/webxml/session.webxml
 qtdoc/webxml/sharedlibrary.webxml
+qtdoc/webxml/signalsandslots-syntaxes.webxml
 qtdoc/webxml/solutions-for-application-development.webxml
-qtdoc/webxml/solutions-for-ui-design.webxml
+qtdoc/webxml/supported-platforms.webxml
 qtdoc/webxml/testing-and-debugging.webxml
-qtdoc/webxml/thread-basics.webxml
+qtdoc/webxml/threads-modules.webxml
+qtdoc/webxml/threads-qobject.webxml
+qtdoc/webxml/threads-reentrancy.webxml
+qtdoc/webxml/threads-synchronizing.webxml
+qtdoc/webxml/threads-technologies.webxml
 qtdoc/webxml/threads.webxml
 qtdoc/webxml/tools-for-qt-quick-uis.webxml
-qtdoc/webxml/tools-for-qt-widget-based-uis.webxml
 qtdoc/webxml/topics-app-development.webxml
 qtdoc/webxml/topics-core.webxml
 qtdoc/webxml/topics-data-io.webxml
 qtdoc/webxml/topics-graphics.webxml
+qtdoc/webxml/topics-graphics2d.webxml
 qtdoc/webxml/topics-network-connectivity.webxml
 qtdoc/webxml/topics-ui.webxml
+qtdoc/webxml/topics-vectorimageformats.webxml
+qtdoc/webxml/trademarks.webxml
 qtdoc/webxml/uic.webxml
 qtdoc/webxml/unicode.webxml
-qtdoc/webxml/untrusteddata.webxml
 qtdoc/webxml/wayland-and-qt.webxml
+qtdoc/webxml/wayland-requirements.webxml
+qtdoc/webxml/windows-graphics.webxml
+qtdoc/webxml/windows-issues.webxml
+qtdoc/webxml/windows.webxml
+qtdoc/webxml/xml-processing.webxml
 qtgraphs/webxml/qtgraphs-and-qtquick3d-integration-guide.webxml
 qtgraphs/webxml/qtgraphs-configure-options.webxml
 qtgraphs/webxml/qtgraphs-data-handling.webxml
@@ -143,18 +166,10 @@ qtgraphs/webxml/qtgraphs-overview-3d.webxml
 qtgraphs/webxml/qtgraphs-overview-theme.webxml
 qtgui/webxml/coordsys.webxml
 qtgui/webxml/dnd.webxml
-qtgui/webxml/gui-changes-qt6.webxml
 qtgui/webxml/paintsystem-devices.webxml
 qtgui/webxml/paintsystem-drawing.webxml
 qtgui/webxml/paintsystem-images.webxml
 qtgui/webxml/paintsystem.webxml
-qtgui/webxml/qabstracttextdocumentlayout-paintcontext.webxml
-qtgui/webxml/qabstracttextdocumentlayout-selection.webxml
-qtgui/webxml/qaccessible-state.webxml
-qtgui/webxml/qiconengine-scaledpixmapargument.webxml
-qtgui/webxml/qpageranges-range.webxml
-qtgui/webxml/qtextlayout-formatrange.webxml
-qtgui/webxml/qtextoption-tab.webxml
 qtgui/webxml/qtgui-overview.webxml
 qtgui/webxml/richtext-advanced-processing.webxml
 qtgui/webxml/richtext-common-tasks.webxml
@@ -163,65 +178,108 @@ qtgui/webxml/richtext-html-subset.webxml
 qtgui/webxml/richtext-layouts.webxml
 qtgui/webxml/richtext-structure.webxml
 qtgui/webxml/richtext.webxml
-qthelp/webxml/helpsystem.webxml
-qthelp/webxml/qhelplink.webxml
 qthelp/webxml/qthelp-framework.webxml
 qthelp/webxml/qthelpproject.webxml
-qthttpserver/webxml/qthttpserver-colorpalette-apibehavior-h.webxml
-qthttpserver/webxml/qthttpserver-colorpalette-colorpalette-pro.webxml
-qthttpserver/webxml/qthttpserver-colorpalette-main-cpp.webxml
-qthttpserver/webxml/qthttpserver-colorpalette-types-h.webxml
-qthttpserver/webxml/qthttpserver-colorpalette-utils-h.webxml
 qthttpserver/webxml/qthttpserver-logging.webxml
+qtlocation/webxml/location-maps-cpp.webxml
+qtlocation/webxml/location-maps-qml.webxml
 qtlocation/webxml/location-places-backend.webxml
 qtlocation/webxml/location-plugin-itemsoverlay.webxml
 qtlocation/webxml/location-plugin-osm.webxml
 qtlocation/webxml/qml-location5-maps.webxml
+qtlocation/webxml/qtlocation-cpp.webxml
 qtlocation/webxml/qtlocation-geoservices.webxml
 qtmultimedia/webxml/advanced-ffmpeg-configuration.webxml
 qtmultimedia/webxml/audiooverview.webxml
 qtmultimedia/webxml/cameraoverview.webxml
 qtmultimedia/webxml/multimediaoverview.webxml
-qtmultimedia/webxml/qmediatimerange-interval.webxml
 qtmultimedia/webxml/qt-add-ios-ffmpeg-libraries.webxml
 qtmultimedia/webxml/qtmultimedia-apple.webxml
-qtmultimedia/webxml/qtmultimedia-changes-qt6.webxml
+qtmultimedia/webxml/qtmultimedia-ffmpeg-stubs.webxml
 qtmultimedia/webxml/qtmultimedia-gstreamer.webxml
+qtmultimedia/webxml/qtmultimedia-linux.webxml
 qtmultimedia/webxml/qtmultimedia-modules.webxml
-qtmultimedia/webxml/qtmultimedia-wayland.webxml
+qtmultimedia/webxml/qtmultimedia-wasm.webxml
 qtmultimedia/webxml/qtmultimedia-windows.webxml
 qtmultimedia/webxml/videooverview.webxml
-qtnetworkauth/webxml/oauth-http-method-alternatives.webxml
-qtnetwork/webxml/network-changes-qt6.webxml
-qtnetwork/webxml/qdtlsclientverifier-generatorparameters.webxml
 qtnetwork/webxml/qtnetwork-programming.webxml
 qtnetwork/webxml/ssl.webxml
+qtnetworkauth/webxml/oauth-http-method-alternatives.webxml
+qtnetworkauth/webxml/qt-oauth2-browsersupport.webxml
+qtnetworkauth/webxml/qt-oauth2-overview.webxml
+qtnetworkauth/webxml/qtnetworkauth-security.webxml
 qtnfc/webxml/nfc-android.webxml
-qtnfc/webxml/qndeffilter-record.webxml
-qtnfc/webxml/qtnfc-changes-qt6.webxml
 qtnfc/webxml/qtnfc-features.webxml
 qtnfc/webxml/qtnfc-overview.webxml
 qtnfc/webxml/qtnfc-pcsc.webxml
-qtopengl/webxml/opengl-changes-qt6.webxml
-qtpositioning/webxml/positioning-cpp-qml.webxml
+qtpdf/webxml/qtpdf-platformnotes.webxml
+qtpositioning/webxml/location-positioning-qml.webxml
 qtpositioning/webxml/position-plugin-android.webxml
 qtpositioning/webxml/position-plugin-geoclue2.webxml
 qtpositioning/webxml/position-plugin-gypsy.webxml
 qtpositioning/webxml/position-plugin-nmea.webxml
+qtpositioning/webxml/positioning-cpp-qml.webxml
 qtpositioning/webxml/qtpositioning-android.webxml
-qtpositioning/webxml/qtpositioning-changes-qt6.webxml
 qtpositioning/webxml/qtpositioning-ios.webxml
 qtpositioning/webxml/qtpositioning-plugins.webxml
 qtprintsupport/webxml/pdf-licensing.webxml
-qtprintsupport/webxml/printsupport-changes-qt6.webxml
-qtqml/webxml/qml-changes-qt6.webxml
+qtqml/webxml/qml-singleton.webxml
 qtqml/webxml/qmldiskcache.webxml
+qtqml/webxml/qmllint-warnings-and-errors-access-singleton-via-object.webxml
+qtqml/webxml/qmllint-warnings-and-errors-alias-cycle.webxml
+qtqml/webxml/qmllint-warnings-and-errors-assignment-in-condition.webxml
+qtqml/webxml/qmllint-warnings-and-errors-attached-property-reuse.webxml
+qtqml/webxml/qmllint-warnings-and-errors-comma.webxml
+qtqml/webxml/qmllint-warnings-and-errors-confusing-minuses.webxml
+qtqml/webxml/qmllint-warnings-and-errors-confusing-pluses.webxml
+qtqml/webxml/qmllint-warnings-and-errors-deprecated.webxml
+qtqml/webxml/qmllint-warnings-and-errors-duplicate-enum-entries.webxml
+qtqml/webxml/qmllint-warnings-and-errors-duplicate-import.webxml
+qtqml/webxml/qmllint-warnings-and-errors-duplicate-inline-components.webxml
+qtqml/webxml/qmllint-warnings-and-errors-duplicate-property-binding.webxml
+qtqml/webxml/qmllint-warnings-and-errors-duplicated-name.webxml
+qtqml/webxml/qmllint-warnings-and-errors-enum-entry-matches-enum.webxml
+qtqml/webxml/qmllint-warnings-and-errors-import.webxml
+qtqml/webxml/qmllint-warnings-and-errors-incompatible-type.webxml
+qtqml/webxml/qmllint-warnings-and-errors-inheritance-cycle.webxml
+qtqml/webxml/qmllint-warnings-and-errors-invalid-lint-directive.webxml
+qtqml/webxml/qmllint-warnings-and-errors-literal-constructor.webxml
+qtqml/webxml/qmllint-warnings-and-errors-missing-enum-entry.webxml
+qtqml/webxml/qmllint-warnings-and-errors-missing-property.webxml
+qtqml/webxml/qmllint-warnings-and-errors-missing-type.webxml
+qtqml/webxml/qmllint-warnings-and-errors-multiline-strings.webxml
+qtqml/webxml/qmllint-warnings-and-errors-non-list-property.webxml
+qtqml/webxml/qmllint-warnings-and-errors-non-root-enum.webxml
+qtqml/webxml/qmllint-warnings-and-errors-quick-attached-property-type.webxml
+qtqml/webxml/qmllint-warnings-and-errors-read-only-property.webxml
+qtqml/webxml/qmllint-warnings-and-errors-recursion-depth-errors.webxml
+qtqml/webxml/qmllint-warnings-and-errors-redundant-optional-chaining.webxml
+qtqml/webxml/qmllint-warnings-and-errors-required.webxml
+qtqml/webxml/qmllint-warnings-and-errors-restricted-type.webxml
+qtqml/webxml/qmllint-warnings-and-errors-signal-handler-parameters.webxml
+qtqml/webxml/qmllint-warnings-and-errors-syntax-duplicate-ids.webxml
+qtqml/webxml/qmllint-warnings-and-errors-syntax-id-quotation.webxml
+qtqml/webxml/qmllint-warnings-and-errors-syntax.webxml
+qtqml/webxml/qmllint-warnings-and-errors-top-level-component.webxml
+qtqml/webxml/qmllint-warnings-and-errors-uncreatable-type.webxml
+qtqml/webxml/qmllint-warnings-and-errors-unqualified.webxml
+qtqml/webxml/qmllint-warnings-and-errors-unresolved-alias.webxml
+qtqml/webxml/qmllint-warnings-and-errors-unresolved-type.webxml
+qtqml/webxml/qmllint-warnings-and-errors-unterminated-case.webxml
+qtqml/webxml/qmllint-warnings-and-errors-unused-imports.webxml
+qtqml/webxml/qmllint-warnings-and-errors-use-proper-function.webxml
+qtqml/webxml/qmllint-warnings-and-errors-var-used-before-declaration.webxml
+qtqml/webxml/qmllint-warnings-and-errors-void.webxml
+qtqml/webxml/qmllint-warnings-and-errors-with.webxml
 qtqml/webxml/qmlreference.webxml
-qtqml/webxml/qml-singleton.webxml
-qtqml/webxml/qqmlcontext-propertypair.webxml
+qtqml/webxml/qqml-h.webxml
+qtqml/webxml/qqmlintegration-h.webxml
 qtqml/webxml/qt-add-qml-plugin.webxml
 qtqml/webxml/qt-import-qml-plugins.webxml
+qtqml/webxml/qt-target-qml-sources.webxml
+qtqml/webxml/qt6-modernize-qml-modules.webxml
 qtqml/webxml/qtjavascript.webxml
+qtqml/webxml/qtqml-cppclasses-topic.webxml
 qtqml/webxml/qtqml-documents-definetypes.webxml
 qtqml/webxml/qtqml-documents-networktransparency.webxml
 qtqml/webxml/qtqml-documents-scope.webxml
@@ -247,27 +305,103 @@ qtqml/webxml/qtqml-syntax-imports.webxml
 qtqml/webxml/qtqml-syntax-objectattributes.webxml
 qtqml/webxml/qtqml-syntax-propertybinding.webxml
 qtqml/webxml/qtqml-syntax-signals.webxml
+qtqml/webxml/qtqml-tool-qmlcachegen.webxml
+qtqml/webxml/qtqml-tooling-qml.webxml
+qtqml/webxml/qtqml-tooling-qmlformat.webxml
+qtqml/webxml/qtqml-tooling-qmlimportscanner.webxml
+qtqml/webxml/qtqml-tooling-qmllint.webxml
+qtqml/webxml/qtqml-tooling-qmlls.webxml
+qtqml/webxml/qtqml-tooling-qmlpreview.webxml
+qtqml/webxml/qtqml-tooling-qmlprofiler.webxml
+qtqml/webxml/qtqml-tooling-qmltyperegistrar.webxml
+qtqml/webxml/qtqml-tooling-svgtoqml.webxml
 qtqml/webxml/qtqml-typesystem-basictypes.webxml
+qtqml/webxml/qtqml-typesystem-enumerations.webxml
 qtqml/webxml/qtqml-typesystem-namespaces.webxml
 qtqml/webxml/qtqml-typesystem-objecttypes.webxml
 qtqml/webxml/qtqml-typesystem-references.webxml
 qtqml/webxml/qtqml-typesystem-sequencetypes.webxml
 qtqml/webxml/qtqml-typesystem-topic.webxml
 qtqml/webxml/qtqml-typesystem-valuetypes.webxml
-qtqml/webxml/qt-target-qml-sources.webxml
+qtqml/webxml/qtquick-debugging.webxml
+qtqml/webxml/qtquick-qml-runtime.webxml
+qtquick/webxml/qml-advtutorial.webxml
+qtquick/webxml/qml-codingconventions.webxml
+qtquick/webxml/qml-dynamicview-tutorial.webxml
+qtquick/webxml/qml-glossary.webxml
+qtquick/webxml/qml-tutorial.webxml
+qtquick/webxml/qml-tutorial1.webxml
+qtquick/webxml/qml-tutorial2.webxml
+qtquick/webxml/qml-tutorial3.webxml
+qtquick/webxml/qmlapplications.webxml
+qtquick/webxml/qmlfirststeps.webxml
+qtquick/webxml/qtquick-android-classes.webxml
+qtquick/webxml/qtquick-bestpractices.webxml
+qtquick/webxml/qtquick-codesamples.webxml
+qtquick/webxml/qtquick-convenience-topic.webxml
+qtquick/webxml/qtquick-cppextensionpoints.webxml
+qtquick/webxml/qtquick-effects-particles.webxml
+qtquick/webxml/qtquick-effects-sprites.webxml
+qtquick/webxml/qtquick-effects-topic.webxml
+qtquick/webxml/qtquick-effects-transformations.webxml
+qtquick/webxml/qtquick-for-android-fragments.webxml
+qtquick/webxml/qtquick-for-android.webxml
+qtquick/webxml/qtquick-how-tos.webxml
+qtquick/webxml/qtquick-input-focus.webxml
+qtquick/webxml/qtquick-input-mouseevents.webxml
+qtquick/webxml/qtquick-input-textinput.webxml
+qtquick/webxml/qtquick-input-topic.webxml
+qtquick/webxml/qtquick-modelviewsdata-cppmodels.webxml
+qtquick/webxml/qtquick-modelviewsdata-modelview.webxml
+qtquick/webxml/qtquick-modelviewsdata-sqlmodels.webxml
+qtquick/webxml/qtquick-modelviewsdata-topic.webxml
+qtquick/webxml/qtquick-particles-performance.webxml
+qtquick/webxml/qtquick-performance.webxml
+qtquick/webxml/qtquick-positioning-anchors.webxml
+qtquick/webxml/qtquick-positioning-layouts.webxml
+qtquick/webxml/qtquick-positioning-righttoleft.webxml
+qtquick/webxml/qtquick-positioning-topic.webxml
+qtquick/webxml/qtquick-statesanimations-animations.webxml
+qtquick/webxml/qtquick-statesanimations-behaviors.webxml
+qtquick/webxml/qtquick-statesanimations-states.webxml
+qtquick/webxml/qtquick-statesanimations-topic.webxml
+qtquick/webxml/qtquick-tools-and-utilities.webxml
+qtquick/webxml/qtquick-usecase-animations.webxml
+qtquick/webxml/qtquick-usecase-integratingjs.webxml
+qtquick/webxml/qtquick-usecase-layouts.webxml
+qtquick/webxml/qtquick-usecase-text.webxml
+qtquick/webxml/qtquick-usecase-userinput.webxml
+qtquick/webxml/qtquick-usecase-visual.webxml
+qtquick/webxml/qtquick-visualcanvas-adaptations-openvg.webxml
+qtquick/webxml/qtquick-visualcanvas-adaptations-software.webxml
+qtquick/webxml/qtquick-visualcanvas-adaptations.webxml
+qtquick/webxml/qtquick-visualcanvas-coordinates.webxml
+qtquick/webxml/qtquick-visualcanvas-scenegraph-renderer.webxml
+qtquick/webxml/qtquick-visualcanvas-scenegraph.webxml
+qtquick/webxml/qtquick-visualcanvas-topic.webxml
+qtquick/webxml/qtquick-visualcanvas-visualparent.webxml
+qtquick/webxml/qtquick-visualtypes-topic.webxml
+qtquick/webxml/qtquicklayouts-overview.webxml
+qtquick/webxml/qtquicklayouts-responsive.webxml
+qtquick/webxml/scalability.webxml
+qtquick3d/webxml/qt-add-lightprobe-images.webxml
+qtquick3d/webxml/qt-add-materials.webxml
+qtquick3d/webxml/qt-quick-3d-xr.webxml
 qtquick3d/webxml/qt3dxr-multiview.webxml
 qtquick3d/webxml/qt3dxr-pcvr.webxml
 qtquick3d/webxml/qt3dxr-quest-quick-start.webxml
 qtquick3d/webxml/qt3dxr-quick-start-guide-applevisionpro.webxml
 qtquick3d/webxml/qt3dxr-supported-openxr-extensions.webxml
 qtquick3d/webxml/qt3dxr-supported-platforms.webxml
-qtquick3d/webxml/qtaa-toc.webxml
 qtquick3d/webxml/qtquick3d-2d.webxml
 qtquick3d/webxml/qtquick3d-architecture.webxml
 qtquick3d/webxml/qtquick3d-custom.webxml
 qtquick3d/webxml/qtquick3d-lod.webxml
 qtquick3d/webxml/qtquick3d-requirements.webxml
-qtquick3d/webxml/qt-quick-3d-xr.webxml
+qtquick3d/webxml/qtquick3d-tool-balsam.webxml
+qtquick3d/webxml/qtquick3d-tool-instancer.webxml
+qtquick3d/webxml/qtquick3d-tool-materialeditor.webxml
+qtquick3d/webxml/qtquick3d-tool-shadergen.webxml
 qtquick3d/webxml/qtxr-locomotion.webxml
 qtquick3d/webxml/quick3d-asset-conditioning-3d-assets.webxml
 qtquick3d/webxml/quick3d-asset-conditioning-anti-aliasing.webxml
@@ -285,7 +419,6 @@ qtquick3d/webxml/quick3d-shadow-mapping.webxml
 qtquick3d/webxml/quick3d-vertex-skinning.webxml
 qtquickcontrols/webxml/qtquickcontrols-basic.webxml
 qtquickcontrols/webxml/qtquickcontrols-buttons.webxml
-qtquickcontrols/webxml/qtquickcontrols-changes-qt6.webxml
 qtquickcontrols/webxml/qtquickcontrols-configuration.webxml
 qtquickcontrols/webxml/qtquickcontrols-containers.webxml
 qtquickcontrols/webxml/qtquickcontrols-customize.webxml
@@ -311,64 +444,8 @@ qtquickcontrols/webxml/qtquickcontrols-popups.webxml
 qtquickcontrols/webxml/qtquickcontrols-separators.webxml
 qtquickcontrols/webxml/qtquickcontrols-styles.webxml
 qtquickcontrols/webxml/qtquickcontrols-universal.webxml
+qtquickcontrols/webxml/qtquickcontrols-versioning.webxml
 qtquickcontrols/webxml/qtquickcontrols-windows.webxml
-qtquick/webxml/qml-advtutorial.webxml
-qtquick/webxml/qml-dynamicview-tutorial.webxml
-qtquick/webxml/qml-tutorial1.webxml
-qtquick/webxml/qml-tutorial2.webxml
-qtquick/webxml/qml-tutorial3.webxml
-qtquick/webxml/qml-tutorial.webxml
-qtquick/webxml/qquickitem-itemchangedata.webxml
-qtquick/webxml/qsggeometry-attribute.webxml
-qtquick/webxml/qsggeometry-coloredpoint2d.webxml
-qtquick/webxml/qsggeometry-point2d.webxml
-qtquick/webxml/qsggeometry-texturedpoint2d.webxml
-qtquick/webxml/qsgmaterialshader-graphicspipelinestate.webxml
-qtquick/webxml/qsgmaterialtype.webxml
-qtquick/webxml/qtquick-android-classes.webxml
-qtquick/webxml/qtquick-bestpractices.webxml
-qtquick/webxml/qtquick-codesamples.webxml
-qtquick/webxml/qtquick-convenience-topic.webxml
-qtquick/webxml/qtquick-cppextensionpoints.webxml
-qtquick/webxml/qtquick-effects-particles.webxml
-qtquick/webxml/qtquick-effects-sprites.webxml
-qtquick/webxml/qtquick-effects-topic.webxml
-qtquick/webxml/qtquick-effects-transformations.webxml
-qtquick/webxml/qtquick-how-tos.webxml
-qtquick/webxml/qtquick-input-focus.webxml
-qtquick/webxml/qtquick-input-mouseevents.webxml
-qtquick/webxml/qtquick-input-textinput.webxml
-qtquick/webxml/qtquick-input-topic.webxml
-qtquick/webxml/qtquicklayouts-overview.webxml
-qtquick/webxml/qtquicklayouts-responsive.webxml
-qtquick/webxml/qtquick-modelviewsdata-cppmodels.webxml
-qtquick/webxml/qtquick-modelviewsdata-modelview.webxml
-qtquick/webxml/qtquick-modelviewsdata-topic.webxml
-qtquick/webxml/qtquick-particles-performance.webxml
-qtquick/webxml/qtquick-positioning-anchors.webxml
-qtquick/webxml/qtquick-positioning-layouts.webxml
-qtquick/webxml/qtquick-positioning-righttoleft.webxml
-qtquick/webxml/qtquick-positioning-topic.webxml
-qtquick/webxml/qtquick-statesanimations-animations.webxml
-qtquick/webxml/qtquick-statesanimations-behaviors.webxml
-qtquick/webxml/qtquick-statesanimations-states.webxml
-qtquick/webxml/qtquick-statesanimations-topic.webxml
-qtquick/webxml/qtquick-tools-and-utilities.webxml
-qtquick/webxml/qtquickview-android-class.webxml
-qtquick/webxml/qtquick-visualcanvas-adaptations-openvg.webxml
-qtquick/webxml/qtquick-visualcanvas-adaptations-software.webxml
-qtquick/webxml/qtquick-visualcanvas-adaptations.webxml
-qtquick/webxml/qtquick-visualcanvas-coordinates.webxml
-qtquick/webxml/qtquick-visualcanvas-scenegraph-renderer.webxml
-qtquick/webxml/qtquick-visualcanvas-scenegraph.webxml
-qtquick/webxml/qtquick-visualcanvas-topic.webxml
-qtquick/webxml/qtquick-visualcanvas-visualparent.webxml
-qtquick/webxml/qtquick-visualtypes-topic.webxml
-qtquick/webxml/quick-changes-qt6.webxml
-qtremoteobjects/webxml/qtremoteobjects-cmake-qt-add-repc-merged.webxml
-qtremoteobjects/webxml/qtremoteobjects-cmake-qt-add-repc-replicas.webxml
-qtremoteobjects/webxml/qtremoteobjects-cmake-qt-add-repc-sources.webxml
-qtremoteobjects/webxml/qtremoteobjects-cmake-qt-rep-from-headers.webxml
 qtremoteobjects/webxml/qtremoteobjects-compatibility.webxml
 qtremoteobjects/webxml/qtremoteobjects-custom-transport.webxml
 qtremoteobjects/webxml/qtremoteobjects-external-schemas.webxml
@@ -380,10 +457,7 @@ qtremoteobjects/webxml/qtremoteobjects-repc.webxml
 qtremoteobjects/webxml/qtremoteobjects-replica.webxml
 qtremoteobjects/webxml/qtremoteobjects-source.webxml
 qtremoteobjects/webxml/qtremoteobjects-troubleshooting.webxml
-qtremoteobjects/webxml/remoteobjects-changes-qt6.webxml
 qtscxml/webxml/qscxmlc.webxml
-qtscxml/webxml/qtscxml-changes-qt6.webxml
-qtscxml/webxml/qtscxml-cmake-qt-add-statecharts.webxml
 qtscxml/webxml/qtscxml-instantiating-state-machines.webxml
 qtscxml/webxml/qtscxml-overview.webxml
 qtscxml/webxml/qtscxml-scxml-compliance.webxml
@@ -392,8 +466,6 @@ qtsensors/webxml/creating-a-sensor-plugin.webxml
 qtsensors/webxml/determining-the-default-sensor-for-a-type.webxml
 qtsensors/webxml/dynamic-sensor-backend-registration.webxml
 qtsensors/webxml/genericbackend.webxml
-qtsensors/webxml/qoutputrange.webxml
-qtsensors/webxml/qtsensors-changes-qt6.webxml
 qtsensors/webxml/qtsensors-cpp.webxml
 qtsensors/webxml/qtsensors-tutorial.webxml
 qtsensors/webxml/senorfwbackend.webxml
@@ -407,9 +479,8 @@ qtserialbus/webxml/qtserialbus-systeccan-overview.webxml
 qtserialbus/webxml/qtserialbus-tinycan-overview.webxml
 qtserialbus/webxml/qtserialbus-vectorcan-overview.webxml
 qtserialbus/webxml/qtserialbus-virtualcan-overview.webxml
-qtserialport/webxml/qtserialport-changes-qt6.webxml
+qtspatialaudio/webxml/qtspatialaudio-modules.webxml
 qtspatialaudio/webxml/spatialaudiooverview.webxml
-qtsql/webxml/qtsql-changes-qt6.webxml
 qtsql/webxml/sql-connecting.webxml
 qtsql/webxml/sql-driver.webxml
 qtsql/webxml/sql-forms.webxml
@@ -418,27 +489,26 @@ qtsql/webxml/sql-presenting.webxml
 qtsql/webxml/sql-programming.webxml
 qtsql/webxml/sql-sqlstatements.webxml
 qtsql/webxml/sql-types.webxml
-qtsvg/webxml/qtsvg-changes-qt6.webxml
+qtstatemachine/webxml/qmlstatemachine-qml-guide.webxml
+qtstatemachine/webxml/qtstatemachine-cpp-guide.webxml
+qtstatemachine/webxml/qtstatemachine-overview.webxml
 qtsvg/webxml/svgextensions.webxml
 qtsvg/webxml/svgrendering.webxml
 qttestlib/webxml/qtest-overview.webxml
 qttestlib/webxml/qtest-tutorial.webxml
 qttestlib/webxml/qttest-best-practices-qdoc.webxml
 qttestlib/webxml/qttestlib-tutorial6.webxml
-qttestlib/webxml/testlib-changes-qt6.webxml
-qttexttospeech/webxml/qttexttospeech-changes-qt6.webxml
 qttexttospeech/webxml/qttexttospeech-engines.webxml
-qtwebchannel/webxml/qtwebchannel-changes-qt6.webxml
 qtwebchannel/webxml/qtwebchannel-javascript.webxml
-qtwebengine/webxml/qtwebengine-changes-qt6.webxml
+qtwebengine/webxml/qt-add-webengine-dictionary.webxml
 qtwebengine/webxml/qtwebengine-features.webxml
+qtwebengine/webxml/qtwebengine-modules.webxml
 qtwebengine/webxml/qtwebengine-overview.webxml
-qtwebengine/webxml/qtwebengine-platform-notes.webxml
+qtwebengine/webxml/qtwebengine-security.webxml
+qtwebengine/webxml/qtwebenginecoreglobal-h.webxml
 qtwebengine/webxml/qtwebenginewidgets-qtwebkitportingguide.webxml
-qtwebengine/webxml/qwebenginecookiestore-filterrequest.webxml
 qtwebsockets/webxml/echoclient.webxml
 qtwebsockets/webxml/echoserver.webxml
-qtwebsockets/webxml/qtwebsockets-changes-qt6.webxml
 qtwebsockets/webxml/qtwebsockets-testing.webxml
 qtwebsockets/webxml/websockets-overview.webxml
 qtwidgets/webxml/application-windows.webxml
@@ -452,9 +522,8 @@ qtwidgets/webxml/mainwindow.webxml
 qtwidgets/webxml/model-view-programming.webxml
 qtwidgets/webxml/modelview.webxml
 qtwidgets/webxml/qdrawutil-h.webxml
-qtwidgets/webxml/qformlayout-takerowresult.webxml
-qtwidgets/webxml/qtextedit-extraselection.webxml
-qtwidgets/webxml/qtilerules.webxml
+qtwidgets/webxml/qt-add-ui.webxml
+qtwidgets/webxml/qt-wrap-ui.webxml
 qtwidgets/webxml/qwidget-styling.webxml
 qtwidgets/webxml/style-reference.webxml
 qtwidgets/webxml/stylesheet-customizing.webxml
@@ -463,14 +532,13 @@ qtwidgets/webxml/stylesheet-reference.webxml
 qtwidgets/webxml/stylesheet-syntax.webxml
 qtwidgets/webxml/stylesheet.webxml
 qtwidgets/webxml/widget-classes.webxml
-qtwidgets/webxml/widgets-changes-qt6.webxml
+qtwidgets/webxml/widget-tutorials.webxml
+qtwidgets/webxml/widgets-getting-started.webxml
 qtwidgets/webxml/widgets-tutorial.webxml
-qtxml/webxml/xml-changes-qt6.webxml
-qtxml/webxml/xml-dom-tml.webxml
+qtxml/webxml/xml-dom.webxml
 qtxml/webxml/xml-namespaces.webxml
 qtxml/webxml/xml-processing.webxml
 qtxml/webxml/xml-streaming.webxml
-qtxml/webxml/xml-tools.webxml
 
 # WebXML <group> files (lists of classes grouped by function/curated)
 [groups]
index 3376516748e43645fc894aafe50b63daf9b574c2..a20143726a8fa0950aeab66dddc8c59d6ef0934f 100644 (file)
@@ -35,9 +35,6 @@ website.
   Prebuilt versions for each OS can be `downloaded here`_.
 * Check the `Supported Platforms of Qt`_
 
-.. _downloaded here: https://download.qt.io/development_releases/prebuilt/libclang/
-.. _`Supported Platforms of Qt` : https://doc.qt.io/qt-6/supported-platforms.html
-
 Guides per platform
 -------------------
 
@@ -487,7 +484,7 @@ The target executes several steps:
 #. ``sphinx`` is run to produce the documentation in HTML format.
 
 Re-running the command will not execute step 1 unless the file
-``qdoc-output/webxml/qtcore-index.webxml`` is removed from the build tree.
+``qdoc-output/qtcore/webxml/qtcore-index.webxml`` is removed from the build tree.
 Similarly, step 2 will not be executed unless the file ``base/PySide6/QtCore/index.rst``
 is removed.
 
@@ -570,3 +567,5 @@ A set of tools can be found under the ``tools/`` directory inside the ``pyside-s
 .. _`wiki page`: https://wiki.qt.io/Qt_for_Python_Missing_Bindings
 .. _BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/
 .. _`CMake Unity Build Mode` : https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html
+.. _downloaded here: https://download.qt.io/development_releases/prebuilt/libclang/
+.. _`Supported Platforms of Qt` : https://doc.qt.io/qt-6/supported-platforms.html
index 1e59006e20663a08738e8beec291279fce467c16..80d56f443ce1aa276f59985902efc6a267f8aff5 100644 (file)
@@ -41,12 +41,12 @@ Setting up CLANG
 
 If you don't have libclang already in your system, you can download from the Qt servers::
 
-    wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.5-based-linux-Rhel8.6-gcc10.3-x86_64.7z
+    wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_20.1.3-based-linux-Rhel8.8-gcc10.3-x86_64.7z
 
 Extract the files, and leave it in any desired path, and set the environment
 variable required::
 
-    7z x libclang-release_18.1.5-based-linux-Rhel8.6-gcc10.3-x86_64.7z
+    7z x libclang-release_20.1.3-based-linux-Rhel8.8-gcc10.3-x86_64.7z
     export LLVM_INSTALL_DIR=$PWD/libclang
 
 Getting the source
index 67a3d48ed0196c4eebf60558ace38408a8689e85..0e10d6cfeb950f659de7ace0aad24774ae6ad251 100644 (file)
@@ -45,12 +45,12 @@ Setting up CLANG
 
 If you don't have `libclang` already in your system, you can download from the Qt servers::
 
-    wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.5-based-macos-universal.7z
+    wget https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_20.1.3-based-macos-universal.7z
 
 Extract the files, and leave it in any desired path, and set the environment
 variable required::
 
-    7z x libclang-release_18.1.5-based-macos-universal.7z
+    7z x libclang-release_20.1.3-based-macos-universal.7z
     export LLVM_INSTALL_DIR=$PWD/libclang
 
 Getting PySide
index 737d045b345897e1954bd59d015ed280db095ba1..0732953db3ae01ebc946d32177f6df39c6ddd0ac 100644 (file)
@@ -55,7 +55,10 @@ Setting up CLANG
 
 libclang can be downloaded from the
 `Qt servers <https://download.qt.io/development_releases/prebuilt/libclang>`_.
-for example, ``libclang-release_18.1.5-based-windows-vs2019_64.7z``.
+for example, ``libclang-release_20.1.3-based-windows-vs2019_64.7z``.
+
+For ARM64 Windows, use:
+``libclang-release_20.1.3-based-windows-vs2022_arm64.7z``.
 
 Note that from version 12 onwards, the prebuilt Windows binaries from
 `LLVM <https://www.llvm.org>`_ no longer contain CMake configuration files; so
index 5af91019be9d3ab6c22ef6cd9e234600b342c17b..38c2c606c904b722685264b6bd28e512efb2c4c3 100644 (file)
@@ -33,7 +33,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.ifconfig',
     'sphinx.ext.graphviz', 'inheritance_diagram', 'pysideinclude',
     'sphinx.ext.viewcode',
     'sphinx_design', 'sphinx_copybutton', 'myst_parser', 'sphinx_tags',
-    'sphinx_toolbox.decorators', 'sphinx_reredirects']
+    'sphinx_toolbox.collapse', 'sphinx_toolbox.decorators', 'sphinx_reredirects']
 
 myst_enable_extensions = [
     "amsmath",
index 72e2b9cf447480b58423b649de8feb90ad792843..7d2064c73030fb4be9c270b4d3c0c4a93592aa71 100644 (file)
@@ -16,7 +16,7 @@ The final output is a `.apk` or a `.aab` file created within the project's sourc
 determines whether a `.apk` or a `.aab` is created.
 
 .. warning:: Currently, users are required to cross-compile Qt for Python to generate the wheels
-    required for `armeabi-v7a` and `x86` Andorid platforms. Instructions on cross-compiling
+    required for `armeabi-v7a` and `x86` Android platforms. Instructions on cross-compiling
     Qt for Python for Android can be found :ref:`here <cross_compile_android>`.
 
 .. note:: ``pyside6-android-deploy`` only works with a Unix (Linux or macOS) host at the moment.
index 59aa2f115a43df960eb4413da58cc5a09632931a..e65c3c9c8e473092ef5aa70078e17a63e98e9dde 100644 (file)
@@ -27,7 +27,13 @@ For example:
   $ flake8 --config pyside-setup/.flake8 your_file.py
   $ isort your_file.py
 
+Later on, the tool `tools/sync_examples.py` can be used to update the source
+files from Qt C++. Each difference produced by tool needs to checked with care,
+differences in `qmldir` files (`prefer` directive) might apply to C++ only.
 
+Also, be careful not to back-port old Qt Widgets Designer `.ui` files with
+unqualified enumerations. If a `.ui` file has additions, make sure to load and
+save it at least once.
 
 Add a new example
 -----------------
@@ -61,11 +67,17 @@ Port a Qt example
 - Note that our examples need to have unique names due to the doc build.
 - Verify that all slots are decorated using ``@Slot``.
 - Enumerations should be fully qualified (PYSIDE-1735).
+- Check the above by running the example with the environment variables:
+
+  .. code-block:: bash
+
+      export PYSIDE6_OPTION_PYTHON_ENUM=0x71
+      export QT_LOGGING_RULES=qt.pyside.libpyside.warning=true
+
 - Add a ``.pyproject`` file (verify later on that docs build).
-- Add a ``doc`` directory and descriptive ``.rst`` file,
+- Add a ``doc`` directory and descriptive ``.md`` or ``.rst`` file,
   and a screenshot if suitable (use ``optipng`` to reduce file size).
 - Add the ``"""Port of the ... example from Qt 6"""`` doc string.
-- Try to port variable and function names to snake case convention.
 - Remove C++ documentation from ``sources/pyside6/doc/additionaldocs.lst``.
 
 .. note:: Example screenshots in ``.png`` should be optimized by
index 53e912918130594456af3c4cb5a411462ee9bef3..24c8d7a174b209bc81b51167c0ed628f8af17aed 100644 (file)
@@ -44,24 +44,30 @@ shiboken/sphinx.
 
 A line in brackets denotes the output directory.
 
-The list can be created by the below script and some hand-editing. It will find
-almost all documents. Quite a number of them might be unreferenced, but there
-is no good way of filtering for this.
-Pages of examples that exist in Python should be removed.
+The list can be created by running the below script in the PySide6 build
+directory and some hand-editing. It will find almost all documents. Quite
+a number of them might be unreferenced, but there is no good way of filtering
+for this. Pages of examples that exist in Python should be removed.
 
 .. code-block:: bash
 
-    for F in *.webxml
-    do
-        echo "$F" | egrep '(-index|example|cmake|private-module|-changes-qt6|-module.web|-qmlmodule.web)' > /dev/null
-        if [ $? -ne 0 ]
-        then
-            if fgrep '<para>' "$F" > /dev/null # Exclude reference only
+    find_docs()
+    {
+        for F in $(find . -name "*.webxml")
+        do
+            echo "$F" | egrep '(-index|example|cmake|private-module|-changes-qt6|-module.web|-qmlmodule.web)' > /dev/null
+            if [ $? -ne 0 ]
             then
-                egrep "(<namespace|<class|<struct|<group|<union)" $F > /dev/null || echo $F
+                if fgrep '<para>' "$F" > /dev/null # Exclude reference only
+                then
+                    egrep "(<namespace|<class|<struct|<group|<union)" $F > /dev/null || echo $F
+                fi
             fi
-        fi
-    done
+        done
+    }
+
+    cd doc/qdoc-output
+    find_docs | cut -c3- | sort
 
 The overviews go into a directory named ``overviews``. There are also special
 pages containing lists of classes with brief, grouped by function. They mostly
index 1cccaad392420d74781acecd6094d395cd07d5f0..5d31d053ba09bd1a7ef4a03f7048c1a50145c4be 100644 (file)
@@ -40,6 +40,40 @@ pre-loaded. Assuming the library is found at
 
 Lately, this feature has been added to MVSC, too.
 
+Build with thread sanitizer
+===========================
+
+`Thread sanitizer <https://clang.llvm.org/docs/ThreadSanitizer.html>`_
+can be useful for detecting data races, etc, for example when experimenting
+with free threaded Python. It is similar to address sanitizer.
+
+For the build, the options ``--sanitize-thread`` and ``--disable-pyi`` should
+be passed to prevent it terminating due to false positives when generating the
+``.pyi`` files:
+
+.. code-block:: bash
+
+    python setup.py build [...] --sanitize-thread --disable-pyi
+
+Similar to address sanitizer, a library needs to be pre-loaded
+when running code:
+
+.. code-block:: bash
+
+    export LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/13/libtsan.so
+
+.. note:: Thread sanitizer maybe report false positives (data races
+          for  code that is protected by a ``QRecursiveMutex`` or a
+          ``std::recursive_mutex``).
+
+.. note:: When the error `Unexpected memory mapping` occurs, it helps to execute:
+
+          .. code-block:: bash
+
+              sudo sysctl vm.mmap_rnd_bits=28
+
+          See `Article on stackoverflow <https://stackoverflow.com/questions/77850769/fatal-threadsanitizer-unexpected-memory-mapping-when-running-on-linux-kernels>`_\.
+
 De-Virtualize the Python Files
 ==============================
 
index 296b455b2f503398f3d44961ec2290008b1516fc..88e3ab308a7be7010a349eca8dbb89d60b965bb3 100644 (file)
@@ -16,6 +16,7 @@ Development Topics
    add_module.rst
    add_port_example.rst
    add_tool.rst
+   pythonversions.md
    documentation.rst
    adapt_qt.rst
    extras.rst
diff --git a/sources/pyside6/doc/developer/pythonversions.md b/sources/pyside6/doc/developer/pythonversions.md
new file mode 100644 (file)
index 0000000..baef18d
--- /dev/null
@@ -0,0 +1,63 @@
+# Adapting to changes in supported Python versions
+
+## Relevant preprocessor defines
+
+- The version range is determined by `wheel_artifacts/pyproject.toml.base`.
+  This file also defines the version tag (`py_limited_api = "cp310"`).
+- `PY_VERSION_HEX` Python version (defined in CPython headers)
+- `Py_LIMITED_API` Limited API minimum version, defined in several CMake files
+- `PYPY_VERSION`  [PyPy](https://pypy.org/) version (defined in PyPy headers)
+
+## Removing outdated Python versions
+
+The removal of Python versions is tied to their lifetime
+(see [Wiki](https://wiki.qt.io/Qt_for_Python)).
+
+- Raise the `Py_LIMITED_API` definition in the CMake files.
+- Check the source code for preprocessor defines depending on
+  values `Py_LIMITED_API`, `PY_VERSION_HEX` and simplify or
+  remove conditions if possible.
+- Check the usages of `_PepRuntimeVersion()` for outdated versions
+- Run the tests and some examples. There might actually
+  some version checks in Python code that trigger
+  (see for example 
+   `sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/parser.py:70`).
+
+## Adapting to new Python versions
+
+New versions appear as branches in the `https://github.com/python/cpython.git`
+repository, are developed over the course of a year and released around fall.
+Change logs and information about deprecations are found in the directory
+`Doc/whatsnew`.
+
+It is recommended to build a release and a debug version of it and check
+whether PySide works with it from time to time.
+
+It is possible that some breakages occur that are fixed later in the
+development process, so, one should not prematurely submit fixes to PySide.
+
+A debug version of CPython can be build from a shadow build directory
+using:
+```
+<src_dir>/configure --prefix=<target_dir> --enable-shared --with-ensurepip=install \
+   -with-pydebug --with-trace-refs --with-valgrind \
+   "CFLAGS=-O0 -g -fno-inline -fno-omit-frame-pointer" CPPFLAGS=-O0 LDFLAGS=-O0
+make && make install
+```
+
+For a release build:
+
+```
+<src_dir>/configure --prefix=<target_dir> --enable-shared --with-ensurepip=install \
+   --enable-optimizations
+make && make install
+```
+
+Those binaries can then be used to create `venv`s and build PySide normally.
+
+Tests should always pass in the release build. The debug build might
+have some test failures; but it should not assert.
+
+It should also be checked whether PySide compiles when raising the Limited API
+minimum version to the new version (although the change can only be submitted
+much later).
index da2ccc60a72670059c74e85412766f8f89323f22..db72b61e0248cbb9311542f9b0c94a41c9bf0f40 100644 (file)
@@ -48,7 +48,7 @@ PySide6.QtCore.Property
                  freset: Optional[Callable] = None,
                  fdel: Optional[Callable] = None,
                  doc: str = '',
-                 notify: Optional[Callable] = None,
+                 notify: Optional[PySide6.QtCore.Signal] = None,
                  designable: bool = True,
                  scriptable: bool = True,
                  stored: bool = True, user: bool = False,
index 1027c1feae288f2aadf558ccf6a064a1d4f64838..1aa385008fa72ecc3d84fcac156c9cd6b52847df 100644 (file)
@@ -42,7 +42,7 @@ shader effects.
     * :ref:`Important-Concepts-In-Qt-Quick---User-Input`
     * :ref:`Important-Concepts-In-Qt-Quick---Positioning`
     * :ref:`Important-Concepts-in-Qt-Quick---States--Transitions-and-Animations`
-    * :ref:`Important-Concepts-In-Qt-Quick---Data---Models--Views-and-Data-Storage`
+    * :ref:`Important-Concepts-In-Qt-Quick---Data---Models--Views--and-Data-Storage`
     * :ref:`Important-Concepts-In-Qt-Quick---Graphical-Effects`
     * `MultiEffect <https://doc.qt.io/qt-6/qml-qtquick-effects-multieffect.html>`_
     * :ref:`Important-Concepts-In-Qt-Quick---Convenience-Types`
@@ -66,7 +66,7 @@ Articles and Guides
 
 Further information for writing QML applications:
 
-    * :ref:`QML-Applications` - Essential information for application development with QML and Qt Quick
+    * :ref:`Getting started with Qt Quick applications <Getting-started-with-Qt-Quick-applications>` - Essential information for application development with QML and Qt Quick
     * :mod:`Qt Qml <PySide6.QtQml>` - Documentation for the Qt QML module, which provides the QML engine and language infrastructure
     * :ref:`Qt Quick How-tos<Qt-Quick-How-tos>` - shows how to achieve specific tasks in Qt Quick
 
index 9abe6456a8b07728ba31ec4a4fd75a7637d44a49..9f4b97f8e53166e8173ea1f9622661d429aa9891 100644 (file)
@@ -282,6 +282,10 @@ Next steps
 Now that you have use both technologies, you can head to our
 :ref:`pyside6_examples` and :ref:`pyside6_tutorials` sections.
 
+.. tip:: **Visual Studio Code Users**: If you use VSCode, check out the
+   :ref:`vscode-ext` which provides project templates, debugging support,
+   build tasks, and other productivity features for PySide6 development.
+
 .. _faq-section:
 
 Frequently Asked Questions
index 0f6cd03f09b719688ac6a90075057f08a18de294..6e3057b3555c51b720e5868e239a58a27c13cc93 100644 (file)
@@ -163,6 +163,19 @@ PySide Utilities
         a tool to print out the metatype information in JSON to be used as
         input for ``qmltyperegistrar``.
 
+IDE Integration
+~~~~~~~~~~~~~~~
+
+.. grid:: 2
+    :gutter: 3 3 4 5
+
+    .. grid-item-card:: Qt Python VSCode Extension
+        :link: vscode-ext
+        :link-type: ref
+
+        Visual Studio Code extension for PySide6 development with project
+        templates, debugging, build tasks, and more.
+
 Deployment
 ~~~~~~~~~~
 
@@ -237,3 +250,4 @@ Qt Quick 3D
     pyside6-balsamui
     pyside6-qmlimportscanner
     pyside6-qsb
+    vscode-ext
diff --git a/sources/pyside6/doc/tools/vscode-ext.rst b/sources/pyside6/doc/tools/vscode-ext.rst
new file mode 100644 (file)
index 0000000..a0f6773
--- /dev/null
@@ -0,0 +1,73 @@
+.. _vscode-ext:
+
+Qt Python VSCode Extension
+**************************
+
+The `Qt Python extension`_ for Visual Studio Code is a comprehensive development tool
+that enhances your PySide6 development workflow with integrated debugging, project
+templates, and build tasks.
+
+Installation
+============
+
+Install the extension from the Visual Studio Code Marketplace:
+
+1. Open VSCode
+2. Go to the Extensions view
+3. Search for "Qt Python"
+4. Click Install on the extension published by **The Qt Company**
+
+Alternatively, install from the command line:
+
+.. code-block:: bash
+
+    code --install-extension TheQtCompany.qt-python
+
+Features
+========
+
+Project Creation
+----------------
+
+Create new PySide6 projects using templates:
+
+1. Open the Command Palette
+2. Type and select **Qt: Create a new Project or file**
+3. Choose from available templates:
+
+   * **Python QtQuick Application** - Creates a Qt Quick/QML project structure
+   * **Python QtWidgets Application** - Creates a Qt Widgets project structure
+
+PySide6 Installation
+--------------------
+
+Quickly install PySide6 in your current Python environment:
+
+1. Open the Command Palette
+2. Type and select **Qt-Python: Install PySide6**
+
+Build Tasks
+-----------
+
+The extension provides PySide6-specific tasks accessible via **Tasks: Run Task**:
+
+* **PySide: build** - Build your PySide6 project (compiles UI files, resources, etc.)
+* **PySide: run** - Run your PySide6 application
+* **PySide: clean** - Clean build artifacts
+* **PySide: deploy** - Deploy your application using pyside6-deploy
+
+Debugging
+---------
+
+The extension provides debugging capabilities for PySide6 applications with support
+for both Python and QML code. See :ref:`tutorial_qml_debugging` for detailed information
+on debugging Qt Quick applications with mixed Python/QML debugging.
+
+Learn More
+==========
+
+For detailed documentation, feature updates, and usage instructions, visit the
+`Qt Python extension marketplace page`_.
+
+.. _`Qt Python extension`: https://marketplace.visualstudio.com/items?itemName=TheQtCompany.qt-python
+.. _`Qt Python extension marketplace page`: https://marketplace.visualstudio.com/items?itemName=TheQtCompany.qt-python
index b6861f9207c42059468e58621c019037025c832e..2666f854feee1f14c882dcfd78db3180668bc410 100644 (file)
@@ -75,6 +75,11 @@ To use the generated file, add the following import at the top of your main Pyth
 
     import rc_icons
 
+.. note:: The tool uses `Zstandard` as a compression algorithm, which at its default
+          compression level (implementation-defined) may produce results that are
+          not usable on other platforms. To ensure the files are usable on all
+          platforms, the compression level should be reduced or `zlib` should
+          be chosen as compression algorithm (see ``pyside6-rcc --help``).
 
 Changes in the code
 ===================
index 32d32300ce8ffd85b7331f9a8113cca6adb6a3e6..6d17f03972082c26ebcfdde8b27deb4d46217109 100644 (file)
@@ -283,7 +283,7 @@ the custom widget should be visible in the widget box.
 For advanced usage, it is also possible to pass the function an implementation
 of the class QDesignerCustomWidgetInterface instead of the type to
 :meth:`addCustomWidget()<PySide6.QtDesigner.QPyDesignerCustomWidgetCollection.addCustomWidget>`.
-This is shown in taskmenuextension example, where a custom context menu
+This is shown in the :ref:`example_designer_taskmenuextension`, where a custom context menu
 is registered for the custom widget. The example is a port of the
 corresponding C++
 `Task Menu Extension Example <https://doc.qt.io/qt-6/qtdesigner-taskmenuextension-example.html>`_ .
@@ -291,6 +291,40 @@ corresponding C++
 .. _QDesignerCustomWidgetCollectionInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetcollectioninterface.html
 .. _QDesignerCustomWidgetInterface: https://doc.qt.io/qt-6/qdesignercustomwidgetinterface.html
 
+Writing Custom Widgets
+++++++++++++++++++++++
+
+For properties to become visible in *Qt Widgets Designer*, they need to be
+declared using :class:`PySide6.QtCore.Property`.
+
+Enums and flag types need to appear within the class and be decorated using
+:deco:`PySide6.QtCore.QEnum` or :deco:`PySide6.QtCore.QFlag`, respectively.
+This requires extracting a base class for them since otherwise, the enum type
+is not known when specifying :class:`PySide6.QtCore.Property`:
+
+.. code-block:: python
+
+     class CustomWidgetBase(QObject):
+         @QEnum
+         class TestEnum(Enum):
+             EnumValue0 = 0
+             EnumValue1 = 1
+
+
+     class CustomWidget(CustomWidgetBase):
+         def __init__(self, parent=None):
+             super().__init__(parent)
+             self._testEnum = CustomWidget.TestEnum.EnumValue1
+
+         def testEnum(self):
+             return self._testEnum
+
+         def setTestEnum(self, new_val):
+             self._testEnum = new_val
+
+         testEnum = Property(CustomWidgetBase.TestEnum, testEnum, setTestEnum)
+
+
 Troubleshooting the Qt Widgets Designer Plugin
 ++++++++++++++++++++++++++++++++++++++++++++++
 
index acffce40d9eedaf5e67595f229a0c379068649f0..87e012021f105e3262031d09322beeaa5bed2984 100644 (file)
@@ -5,17 +5,17 @@ Chapter 5 - Add a chart view
 =============================
 
 A table is nice to present data, but a chart is even better. For this, you
-need the QtCharts module that provides many types of plots and options to
+need the QtGraphs module that provides many types of plots and options to
 graphically represent data.
 
-The placeholder for a plot is a QChartView, and inside that Widget you can
-place a QChart. As a first step, try including only this without any data to
-plot.
+The relevant class for a plot is the GraphsView QML type, to which axes and
+data series can be added. As a first step, try including then without any data
+to plot.
 
 Make the following highlighted changes to :code:`main_widget.py` from the
-previous chapter to add a QChartView:
+previous chapter to add a chart:
 
 .. literalinclude:: datavisualize5/main_widget.py
    :linenos:
-   :lines: 3-
-   :emphasize-lines: 2-3,6,22-36,47-49
+   :lines: 5-
+   :emphasize-lines: 4,27-39,53
index ab5468cd67086acedd337f7d547dd33817e3ca26..9073e679a294c200c090bb6ba30d07c3ea137718 100644 (file)
@@ -4,7 +4,8 @@
 Chapter 3 - Create an empty QMainWindow
 ==========================================
 
-You can now think of presenting your data in a UI. A QMainWindow provides a
+You can now think of presenting your data in a UI. A
+class:`~PySide6.QtWidgets.QMainWindow` provides a
 convenient structure for GUI applications, such as a menu bar and status bar.
 The following image shows the layout that QMainWindow offers out-of-the box:
 
@@ -24,12 +25,13 @@ the resolution you currently have. In the following snippet, you will see how
 window size is defined based on available screen width (80%) and height (70%).
 
 .. note:: You can achieve a similar structure using other Qt elements like
-   QMenuBar, QWidget, and QStatusBar. Refer the QMainWindow layout for
+   class:`~PySide6.QtWidgets.QMenuBar`, class:`~PySide6.QtWidgets.QWidget`,
+   and class:`~PySide6.QtWidgets.QStatusBar`. Refer the QMainWindow layout for
    guidance.
 
 .. literalinclude:: datavisualize3/main_window.py
    :language: python
    :linenos:
-   :lines: 4-
+   :lines: 5-
 
 Try running the script to see what output you get with it.
index 5b7e5e735abb57962de39d9dd30600ffa1c15c3e..b3041349c4f2705e6e607c3007c4d81695d7f368 100644 (file)
@@ -8,21 +8,22 @@ Now that you have a QMainWindow, you can include a centralWidget to your
 interface. Usually, a QWidget is used to display data in most data-driven
 applications. Use a table view to display your data.
 
-The first step is to add a horizontal layout with just a QTableView. You
-can create a QTableView object and place it inside a QHBoxLayout. Once the
+The first step is to add a horizontal layout with just a
+class:`~PySide6.QtWidgets.QTableView`. You can create a QTableView object
+and place it inside a class:`~PySide6.QtWidgets.QHBoxLayout`. Once the
 QWidget is properly built, pass the object to the QMainWindow as its central
 widget.
 
 Remember that a QTableView needs a model to display information. In this case,
-you can use a QAbstractTableModel instance.
+you can use a class:`~PySide6.QtCore.QAbstractTableModel` instance.
 
 .. note:: You could also use the default item model that comes with a
-   QTableWidget instead. QTableWidget is a convenience class that reduces
-   your codebase considerably as you don't need to implement a data model.
-   However, it's less flexible than a QTableView, as QTableWidget cannot be
-   used with just any data. For more insight about Qt's model-view framework,
-   refer to the
-   `Model View Programming <https://doc.qt.io/qt-5/model-view-programming.html>`
+   class:`~PySide6.QtWidgets.QTableWidget` instead. QTableWidget is a
+   convenience class that reduces your codebase considerably as you don't need
+   to implement a data model. However, it's less flexible than a QTableView,
+   as QTableWidget cannot be used with just any data. For more insight about
+   Qt's model-view framework, refer to the
+   `Model View Programming <https://doc.qt.io/qt-6/model-view-programming.html>`
    documentation.
 
 Implementing the model for your QTableView, allows you to:
@@ -42,7 +43,7 @@ Here is a script that implements the CustomTableModel:
 .. literalinclude:: datavisualize4/table_model.py
    :language: python
    :linenos:
-   :lines: 3-
+   :lines: 5-
 
 Now, create a QWidget that has a QTableView, and connect it to your
 CustomTableModel.
@@ -50,8 +51,8 @@ CustomTableModel.
 .. literalinclude:: datavisualize4/main_widget.py
    :language: python
    :linenos:
-   :emphasize-lines: 12-17
-   :lines: 3-
+   :emphasize-lines: 12-12
+   :lines: 5-
 
 You also need minor changes to the :code:`main_window.py` and
 :code:`main.py` from chapter 3 to include the Widget inside the
@@ -62,11 +63,11 @@ In the following snippets you'll see those changes highlighted:
 .. literalinclude:: datavisualize4/main_window.py
    :language: python
    :linenos:
-   :lines: 3-
-   :emphasize-lines: 8,11
+   :lines: 5-
+   :emphasize-lines: 9
 
 .. literalinclude:: datavisualize4/main.py
    :language: python
    :linenos:
-   :lines: 3-
-   :emphasize-lines: 46-47
+   :lines: 5-
+   :emphasize-lines: 45-46
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/datavisualize3.pyproject
new file mode 100644 (file)
index 0000000..1bd31f9
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "main_window.py"]
+}
index 6ee8fa61b447e88a1830eb59233577dd3179dcf0..79a4afd36e2cd2f62f6da48fa8f0adcd2ce2ac78 100644 (file)
@@ -2,25 +2,22 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
 from PySide6.QtWidgets import QMainWindow
 
 
 class MainWindow(QMainWindow):
     def __init__(self):
-        QMainWindow.__init__(self)
+        super().__init__()
         self.setWindowTitle("Eartquakes information")
 
         # Menu
         self.menu = self.menuBar()
-        self.file_menu = self.menu.addMenu("File")
+        file_menu = self.menu.addMenu("File")
 
         # Exit QAction
-        exit_action = QAction("Exit", self)
-        exit_action.setShortcut(QKeySequence.Quit)
-        exit_action.triggered.connect(self.close)
-
-        self.file_menu.addAction(exit_action)
+        file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+                            "Exit", QKeySequence.StandardKey.Quit, self.close)
 
         # Status Bar
         self.status = self.statusBar()
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/datavisualize4.pyproject
new file mode 100644 (file)
index 0000000..f549697
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "main_widget.py", "main_window.py", "table_model.py"]
+}
index 85e24833f514c24440816546d5ba768f361d3c31..5d8e6ade3694894d588cccf5bd4df3bb6a1a3e83 100644 (file)
@@ -10,7 +10,7 @@ from table_model import CustomTableModel
 
 class Widget(QWidget):
     def __init__(self, data):
-        QWidget.__init__(self)
+        super().__init__()
 
         # Getting the Model
         self.model = CustomTableModel(data)
@@ -22,13 +22,13 @@ class Widget(QWidget):
         # QTableView Headers
         self.horizontal_header = self.table_view.horizontalHeader()
         self.vertical_header = self.table_view.verticalHeader()
-        self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents)
-        self.vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents)
+        self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
+        self.vertical_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
         self.horizontal_header.setStretchLastSection(True)
 
         # QWidget Layout
         self.main_layout = QHBoxLayout()
-        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+        size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
 
         # Left layout
         size.setHorizontalStretch(1)
index ded7fdf5cd0a1c3859bfacb9c7480c05f94d5b10..600af6503dcf1a5c4e166b7170cafd5ac493b8cd 100644 (file)
@@ -2,25 +2,22 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
 from PySide6.QtWidgets import QMainWindow
 
 
 class MainWindow(QMainWindow):
     def __init__(self, widget):
-        QMainWindow.__init__(self)
+        super().__init__()
         self.setWindowTitle("Eartquakes information")
         self.setCentralWidget(widget)
         # Menu
         self.menu = self.menuBar()
-        self.file_menu = self.menu.addMenu("File")
+        file_menu = self.menu.addMenu("File")
 
         # Exit QAction
-        exit_action = QAction("Exit", self)
-        exit_action.setShortcut(QKeySequence.Quit)
-        exit_action.triggered.connect(self.close)
-
-        self.file_menu.addAction(exit_action)
+        file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+                            "Exit", QKeySequence.StandardKey.Quit, self.close)
 
         # Status Bar
         self.status = self.statusBar()
index cc2ac12ab4837945e1d331ddfca97aae97edcffa..9a2871c2232382f613732c0f252ae71bdc489aee 100644 (file)
@@ -25,27 +25,27 @@ class CustomTableModel(QAbstractTableModel):
         return self.column_count
 
     def headerData(self, section, orientation, role):
-        if role != Qt.DisplayRole:
+        if role != Qt.ItemDataRole.DisplayRole:
             return None
-        if orientation == Qt.Horizontal:
+        if orientation == Qt.Orientation.Horizontal:
             return ("Date", "Magnitude")[section]
         else:
             return f"{section}"
 
-    def data(self, index, role=Qt.DisplayRole):
+    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
         column = index.column()
         row = index.row()
 
-        if role == Qt.DisplayRole:
+        if role == Qt.ItemDataRole.DisplayRole:
             if column == 0:
                 date = self.input_dates[row].toPython()
                 return str(date)[:-3]
             elif column == 1:
                 magnitude = self.input_magnitudes[row]
                 return f"{magnitude:.2f}"
-        elif role == Qt.BackgroundRole:
-            return QColor(Qt.white)
-        elif role == Qt.TextAlignmentRole:
-            return Qt.AlignRight
+        elif role == Qt.ItemDataRole.BackgroundRole:
+            return QColor(Qt.GlobalColor.white)
+        elif role == Qt.ItemDataRole.TextAlignmentRole:
+            return Qt.AlignmentFlag.AlignRight
 
         return None
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/datavisualize5.pyproject
new file mode 100644 (file)
index 0000000..f549697
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "main_widget.py", "main_window.py", "table_model.py"]
+}
index 77ea4e77674a1caa44b328a63a8ea9fb209ec3f0..fca09b059d63942b7f7a44fb53e1eb45cf2d31d0 100644 (file)
@@ -2,17 +2,17 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtGui import QPainter
 from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
                                QSizePolicy)
-from PySide6.QtCharts import QChart, QChartView
+from PySide6.QtQuickWidgets import QQuickWidget
+from PySide6.QtGraphs import QLineSeries, QDateTimeAxis, QValueAxis, QGraphsTheme
 
 from table_model import CustomTableModel
 
 
 class Widget(QWidget):
     def __init__(self, data):
-        QWidget.__init__(self)
+        super().__init__()
 
         # Getting the Model
         self.model = CustomTableModel(data)
@@ -24,21 +24,27 @@ class Widget(QWidget):
         # QTableView Headers
         self.horizontal_header = self.table_view.horizontalHeader()
         self.vertical_header = self.table_view.verticalHeader()
-        self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents)
-        self.vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents)
+        self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
+        self.vertical_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
         self.horizontal_header.setStretchLastSection(True)
 
-        # Creating QChart
-        self.chart = QChart()
-        self.chart.setAnimationOptions(QChart.AllAnimations)
-
-        # Creating QChartView
-        self.chart_view = QChartView(self.chart)
-        self.chart_view.setRenderHint(QPainter.Antialiasing)
+        # Create QGraphView via QML
+        self.series = QLineSeries()
+        self.axis_x = QDateTimeAxis()
+        self.axis_y = QValueAxis()
+        self.quick_widget = QQuickWidget(self)
+        self.quick_widget.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView)
+        self.theme = QGraphsTheme()
+        initial_properties = {"theme": self.theme,
+                              "axisX": self.axis_x,
+                              "axisY": self.axis_y,
+                              "seriesList": self.series}
+        self.quick_widget.setInitialProperties(initial_properties)
+        self.quick_widget.loadFromModule("QtGraphs", "GraphsView")
 
         # QWidget Layout
-        self.main_layout = QHBoxLayout()
-        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+        self.main_layout = QHBoxLayout(self)
+        size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
 
         # Left layout
         size.setHorizontalStretch(1)
@@ -47,8 +53,5 @@ class Widget(QWidget):
 
         # Right Layout
         size.setHorizontalStretch(4)
-        self.chart_view.setSizePolicy(size)
-        self.main_layout.addWidget(self.chart_view)
-
-        # Set the layout to the QWidget
-        self.setLayout(self.main_layout)
+        self.quick_widget.setSizePolicy(size)
+        self.main_layout.addWidget(self.quick_widget)
index ded7fdf5cd0a1c3859bfacb9c7480c05f94d5b10..600af6503dcf1a5c4e166b7170cafd5ac493b8cd 100644 (file)
@@ -2,25 +2,22 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
 from PySide6.QtWidgets import QMainWindow
 
 
 class MainWindow(QMainWindow):
     def __init__(self, widget):
-        QMainWindow.__init__(self)
+        super().__init__()
         self.setWindowTitle("Eartquakes information")
         self.setCentralWidget(widget)
         # Menu
         self.menu = self.menuBar()
-        self.file_menu = self.menu.addMenu("File")
+        file_menu = self.menu.addMenu("File")
 
         # Exit QAction
-        exit_action = QAction("Exit", self)
-        exit_action.setShortcut(QKeySequence.Quit)
-        exit_action.triggered.connect(self.close)
-
-        self.file_menu.addAction(exit_action)
+        file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+                            "Exit", QKeySequence.StandardKey.Quit, self.close)
 
         # Status Bar
         self.status = self.statusBar()
index cc2ac12ab4837945e1d331ddfca97aae97edcffa..9a2871c2232382f613732c0f252ae71bdc489aee 100644 (file)
@@ -25,27 +25,27 @@ class CustomTableModel(QAbstractTableModel):
         return self.column_count
 
     def headerData(self, section, orientation, role):
-        if role != Qt.DisplayRole:
+        if role != Qt.ItemDataRole.DisplayRole:
             return None
-        if orientation == Qt.Horizontal:
+        if orientation == Qt.Orientation.Horizontal:
             return ("Date", "Magnitude")[section]
         else:
             return f"{section}"
 
-    def data(self, index, role=Qt.DisplayRole):
+    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
         column = index.column()
         row = index.row()
 
-        if role == Qt.DisplayRole:
+        if role == Qt.ItemDataRole.DisplayRole:
             if column == 0:
                 date = self.input_dates[row].toPython()
                 return str(date)[:-3]
             elif column == 1:
                 magnitude = self.input_magnitudes[row]
                 return f"{magnitude:.2f}"
-        elif role == Qt.BackgroundRole:
-            return QColor(Qt.white)
-        elif role == Qt.TextAlignmentRole:
-            return Qt.AlignRight
+        elif role == Qt.ItemDataRole.BackgroundRole:
+            return QColor(Qt.GlobalColor.white)
+        elif role == Qt.ItemDataRole.TextAlignmentRole:
+            return Qt.AlignmentFlag.AlignRight
 
         return None
diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/datavisualize6.pyproject
new file mode 100644 (file)
index 0000000..f549697
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "files": ["main.py", "main_widget.py", "main_window.py", "table_model.py"]
+}
index f987689eab7b4bc9045f54b2326797d8f42ad021..336afacd8609a4cd2eff31ad0841051ee36b81e5 100644 (file)
@@ -2,18 +2,20 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtCore import QDateTime, Qt
-from PySide6.QtGui import QPainter
+from math import floor, ceil
+
+from PySide6.QtCore import QDateTime, QTime, QTimeZone
 from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
                                QSizePolicy)
-from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
+from PySide6.QtQuickWidgets import QQuickWidget
+from PySide6.QtGraphs import QLineSeries, QDateTimeAxis, QValueAxis, QGraphsTheme
 
 from table_model import CustomTableModel
 
 
 class Widget(QWidget):
     def __init__(self, data):
-        QWidget.__init__(self)
+        super().__init__()
 
         # Getting the Model
         self.model = CustomTableModel(data)
@@ -23,25 +25,29 @@ class Widget(QWidget):
         self.table_view.setModel(self.model)
 
         # QTableView Headers
-        resize = QHeaderView.ResizeToContents
+        resize = QHeaderView.ResizeMode.ResizeToContents
         self.horizontal_header = self.table_view.horizontalHeader()
         self.vertical_header = self.table_view.verticalHeader()
         self.horizontal_header.setSectionResizeMode(resize)
         self.vertical_header.setSectionResizeMode(resize)
         self.horizontal_header.setStretchLastSection(True)
 
-        # Creating QChart
-        self.chart = QChart()
-        self.chart.setAnimationOptions(QChart.AllAnimations)
-        self.add_series("Magnitude (Column 1)", [0, 1])
-
-        # Creating QChartView
-        self.chart_view = QChartView(self.chart)
-        self.chart_view.setRenderHint(QPainter.Antialiasing)
+        # Create QGraphView via QML
+        self.populate_series()
+        self.quick_widget = QQuickWidget(self)
+        self.quick_widget.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView)
+        self.theme = QGraphsTheme()
+        self.theme.setTheme(QGraphsTheme.Theme.BlueSeries)
+        initial_properties = {"theme": self.theme,
+                              "axisX": self.axis_x,
+                              "axisY": self.axis_y,
+                              "seriesList": self.series}
+        self.quick_widget.setInitialProperties(initial_properties)
+        self.quick_widget.loadFromModule("QtGraphs", "GraphsView")
 
         # QWidget Layout
-        self.main_layout = QHBoxLayout()
-        size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+        self.main_layout = QHBoxLayout(self)
+        size = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
 
         # Left layout
         size.setHorizontalStretch(1)
@@ -50,46 +56,51 @@ class Widget(QWidget):
 
         # Right Layout
         size.setHorizontalStretch(4)
-        self.chart_view.setSizePolicy(size)
-        self.main_layout.addWidget(self.chart_view)
+        self.quick_widget.setSizePolicy(size)
+        self.main_layout.addWidget(self.quick_widget)
 
-        # Set the layout to the QWidget
-        self.setLayout(self.main_layout)
+    def populate_series(self):
+        def seconds(qtime: QTime):
+            return qtime.minute() * 60 + qtime.second()
 
-    def add_series(self, name, columns):
-        # Create QLineSeries
         self.series = QLineSeries()
-        self.series.setName(name)
+        self.series.setName("Magnitude (Column 1)")
 
         # Filling QLineSeries
+        time_min = QDateTime(2100, 1, 1, 0, 0, 0)
+        time_max = QDateTime(1970, 1, 1, 0, 0, 0)
+        time_zone = QTimeZone(QTimeZone.Initialization.UTC)
+        y_min = 1e37
+        y_max = -1e37
+        date_fmt = "yyyy-MM-dd HH:mm:ss.zzz"
         for i in range(self.model.rowCount()):
-            # Getting the data
             t = self.model.index(i, 0).data()
-            date_fmt = "yyyy-MM-dd HH:mm:ss.zzz"
-
-            x = QDateTime().fromString(t, date_fmt).toSecsSinceEpoch()
+            time = QDateTime.fromString(t, date_fmt)
+            time.setTimeZone(time_zone)
             y = float(self.model.index(i, 1).data())
-
-            if x > 0 and y > 0:
-                self.series.append(x, y)
-
-        self.chart.addSeries(self.series)
+            if time.isValid() and y > 0:
+                if time > time_max:
+                    time_max = time
+                if time < time_min:
+                    time_min = time
+                if y > y_max:
+                    y_max = y
+                if y < y_min:
+                    y_min = y
+                self.series.append(time.toMSecsSinceEpoch(), y)
 
         # Setting X-axis
         self.axis_x = QDateTimeAxis()
-        self.axis_x.setTickCount(10)
-        self.axis_x.setFormat("dd.MM (h:mm)")
+        self.axis_x.setLabelFormat("dd.MM (h:mm)")
         self.axis_x.setTitleText("Date")
-        self.chart.addAxis(self.axis_x, Qt.AlignBottom)
-        self.series.attachAxis(self.axis_x)
+        self.axis_x.setMin(time_min.addSecs(-seconds(time_min.time())))
+        self.axis_x.setMax(time_max.addSecs(3600 - seconds(time_max.time())))
+        self.series.setAxisX(self.axis_x)
+
         # Setting Y-axis
         self.axis_y = QValueAxis()
-        self.axis_y.setTickCount(10)
         self.axis_y.setLabelFormat("%.2f")
         self.axis_y.setTitleText("Magnitude")
-        self.chart.addAxis(self.axis_y, Qt.AlignLeft)
-        self.series.attachAxis(self.axis_y)
-
-        # Getting the color from the QChart to use it on the QTableView
-        color_name = self.series.pen().color().name()
-        self.model.color = f"{color_name}"
+        self.axis_y.setMin(floor(y_min))
+        self.axis_y.setMax(ceil(y_max))
+        self.series.setAxisY(self.axis_y)
index f37268df8ec6156120ee12bc76e9b661d351b8ef..6a9eaea8eb476be3078cc47913e2e3e9a0fd2afc 100644 (file)
@@ -2,26 +2,22 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
 from __future__ import annotations
 
-from PySide6.QtGui import QAction, QKeySequence
+from PySide6.QtGui import QIcon, QKeySequence
 from PySide6.QtWidgets import QMainWindow
 
 
 class MainWindow(QMainWindow):
     def __init__(self, widget):
-        QMainWindow.__init__(self)
+        super().__init__()
         self.setWindowTitle("Eartquakes information")
 
         # Menu
         self.menu = self.menuBar()
-        self.file_menu = self.menu.addMenu("File")
+        file_menu = self.menu.addMenu("File")
 
         # Exit QAction
-        exit_action = QAction("Exit", self)
-        exit_action.setShortcut(QKeySequence.Quit)
-        exit_action.triggered.connect(self.close)
-
-        self.file_menu.addAction(exit_action)
-
+        file_menu.addAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit),
+                            "Exit", QKeySequence.StandardKey.Quit, self.close)
         # Status Bar
         self.status = self.statusBar()
         self.status.showMessage("Data loaded and plotted")
index 3201e5887058217a90d36f4c9466cb436b136168..9a2871c2232382f613732c0f252ae71bdc489aee 100644 (file)
@@ -9,7 +9,6 @@ from PySide6.QtGui import QColor
 class CustomTableModel(QAbstractTableModel):
     def __init__(self, data=None):
         QAbstractTableModel.__init__(self)
-        self.color = None
         self.load_data(data)
 
     def load_data(self, data):
@@ -26,27 +25,27 @@ class CustomTableModel(QAbstractTableModel):
         return self.column_count
 
     def headerData(self, section, orientation, role):
-        if role != Qt.DisplayRole:
+        if role != Qt.ItemDataRole.DisplayRole:
             return None
-        if orientation == Qt.Horizontal:
+        if orientation == Qt.Orientation.Horizontal:
             return ("Date", "Magnitude")[section]
         else:
             return f"{section}"
 
-    def data(self, index, role=Qt.DisplayRole):
+    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
         column = index.column()
         row = index.row()
 
-        if role == Qt.DisplayRole:
+        if role == Qt.ItemDataRole.DisplayRole:
             if column == 0:
                 date = self.input_dates[row].toPython()
                 return str(date)[:-3]
             elif column == 1:
                 magnitude = self.input_magnitudes[row]
                 return f"{magnitude:.2f}"
-        elif role == Qt.BackgroundRole:
-            return (QColor(Qt.white), QColor(self.color))[column]
-        elif role == Qt.TextAlignmentRole:
-            return Qt.AlignRight
+        elif role == Qt.ItemDataRole.BackgroundRole:
+            return QColor(Qt.GlobalColor.white)
+        elif role == Qt.ItemDataRole.TextAlignmentRole:
+            return Qt.AlignmentFlag.AlignRight
 
         return None
index bef134e5b695a422888e044b3c0758503440e0c0..4edde69c17c55b3afc1399af6ea3efa7c57d7e9f 100644 (file)
@@ -17,7 +17,8 @@ be done by filtering the data that follows the condition, "magnitude > 0", to
 avoid faulty data or unexpected behavior.
 
 The Date column provides data in UTC format (for example,
-2018-12-11T21:14:44.682Z), so you could easily map it to a QDateTime object
+2018-12-11T21:14:44.682Z), so you could easily map it to a
+class:`~PySide6.QtCore.QDateTime` object
 defining the structure of the string. Additionally, you can adapt the time
 based on the timezone you are in, using QTimeZone.
 
@@ -26,7 +27,7 @@ The following script filters and formats the CSV data as described earlier:
 .. literalinclude:: datavisualize2/main.py
    :language: python
    :linenos:
-   :lines: 3-
+   :lines: 5-
 
-Now that you have a tuple of QDateTime and float data, try improving the
+Now that you have a tuple of ``QDateTime`` and float data, try improving the
 output further. That's what you'll learn in the following chapters.
index e4374e8613f9d0ce99e31023496ede9e48d3a017..47d12a7c4d6ed9fa0a76cec7562cf314d52c7b8a 100644 (file)
@@ -1,23 +1,23 @@
 .. _tutorial_plot_datapoints:
 
 
-Chapter 6 - Plot the data in the ChartView
+Chapter 6 - Plot the data in the GraphsView
 ===========================================
 
-The last step of this tutorial is to plot the CSV data inside our QChart. For
-this, you need to go over our data and include the data on a QLineSeries.
+The last step of this tutorial is to plot the CSV data inside our GraphsView.
+For this, you need to go over our data and include the data on a QLineSeries.
 
 After adding the data to the series, you can modify the axis to properly
 display the QDateTime on the X-axis, and the magnitude values on the Y-axis.
 
 Here is the updated :code:`main_widget.py` that includes an additional
-function to plot data using a QLineSeries:
+function to plot data using a :class:`~PySide6.QtGraphs.QLineSeries`:
 
 .. literalinclude:: datavisualize6/main_widget.py
    :language: python
    :linenos:
-   :lines: 3-
-   :emphasize-lines: 33,56-91
+   :lines: 5-
+   :emphasize-lines: 31-42, 68-102
 
 Now, run the application to visualize the earthquake magnitudes
 data at different times.
index 8be0e1c2f0d16ec469bafa8e289c3830dae6a15c..d083a05ee6aa835b7db46399823f439282446b98 100644 (file)
@@ -21,7 +21,7 @@ The following python script, :code:`main.py`, demonstrates how to do it:
 .. literalinclude:: datavisualize1/main.py
    :language: python
    :linenos:
-   :lines: 3-
+   :lines: 5-
 
 The Python script uses the :code:`argparse` module to accept and parse input
 from the command line. It then uses the input, which in this case is the filename,
index bf84fecc74114fcfd52ad0738a46f1d9c992ec76..33578d16549da3e801288f6b8206784a61b11e98 100644 (file)
@@ -1,8 +1,10 @@
 .. _tutorial_qml_debugging:
 
+Mixed mode Debugging of PySide6 QML Applications
+************************************************
 
 Using Qt Creator's QML Debugger for a PySide6 QML Application
-*************************************************************
+=============================================================
 
 Besides the C++ debugger, *Qt Creator* provides a `QML debugger`_ which lets you
 inspect JavaScript code. It works by connecting to a socket server run by the
@@ -31,5 +33,28 @@ For instructions on how to use the QML debugger, see
 .. note:: The code should be removed or disabled when shipping the application
           as it poses a security risk.
 
+Using the Qt Python VSCode Extension
+====================================
+
+The `Qt Python extension`_ for Visual Studio Code provides an easier way to debug
+PySide6 QML applications with mixed-mode debugging support for both Python and QML.
+The extension comes with several preset launch configurations that enable seamless
+debugging without manual setup:
+
+- ``Qt: PySide: Launch`` - Launch and debug PySide6 applications
+- ``Qt: PySide: Launch with QML debugger`` - Launch PySide6 applications with QML debugging enabled
+- ``Qt: QML: Attach by port`` - Attach the QML debugger to a running application by port number
+
+With these configurations, you can set breakpoints in both your Python code and QML
+files, inspect variables, and step through code execution across the Python-QML boundary.
+For mixed Python and QML debugging, you can use a compound configuration that combines
+``Qt: PySide: Launch with QML debugger`` and ``Qt: QML: Attach by port`` to debug both
+layers simultaneously.
+
+For detailed instructions on how to debug PySide6 applications using the Qt Python
+extension, see `Debugging Qt for Python Applications in VSCode`_.
+
 .. _`QML debugger`: https://doc.qt.io/qtcreator/creator-debugging-qml.html
 .. _`Debugging a Qt Quick Example Application`: https://doc.qt.io/qtcreator/creator-qml-debugging-example.html
+.. _`Qt Python extension`: https://marketplace.visualstudio.com/items?itemName=TheQtCompany.qt-python
+.. _`Debugging Qt for Python Applications in VSCode`: https://doc-snapshots.qt.io/vscodeext-dev/vscodeext-how-debug-apps-python.html
diff --git a/sources/pyside6/doc/tutorials/drumpad/index.md b/sources/pyside6/doc/tutorials/drumpad/index.md
new file mode 100644 (file)
index 0000000..3e90c88
--- /dev/null
@@ -0,0 +1,215 @@
+(tutorial_qt_design_studio_integration)=
+
+# Qt Design Studio integration tutorial
+
+## Summary
+
+This tutorial provides a step-by-step guide for exporting a [Qt Design Studio] project for Python
+development and deployment. You will learn how to:
+
+ - Export a Qt Design Studio in order to get a project template for further Python development
+ - Implement custom QML elements using Python
+ - Successfully deploy the PySide6 application
+
+```{note}
+This tutorial is not focused on teaching how to use Qt Design Studio or QML, but rather how to
+integrate PySide6 with an existing Qt Design Studio project. If you want to learn how to use Qt
+Design Studio, check the [available tutorials][qt-design-studio-tutorials].
+```
+
+The project consists in a single "drumpad" screen that can be used to play different sound effects.
+The screen is composed of a responsive grid of buttons, each playing a different sound. In addition,
+a waveform display shows the audio amplitude over time using [Qt Multimedia] features.
+
+![Drumpad example screenshot](resources/drumpad.png)
+
+## Workflow overview
+
+Before starting the tutorial, we need to understand the Qt Design Studio project workflow first.
+
+1. **Create a QML project using Qt Design Studio**: Develop the application UI in a user
+friendly way. You can visually design components, screens and animations without writing QML code
+manually.
+
+2. **Export the project**: Create a Python project using the Qt Design Studio generator.
+
+3. **Develop logic**: Implement custom functionalities and application logic in Python, connecting
+it to the exported QML files. Define *backend* elements and signal communication with the UI.
+
+4. **Deploy**: Package the application into a standalone executable using the [pyside6-deploy] tool.
+This bundles all required dependencies, resources, and modules into a distributable format.
+
+## Qt Design Studio project set up
+
+The initial project source code is available for download at {ref}`example_tutorials_drumpad_initial_project`.
+This provides the starting point for the tutorial and includes a set of QML files, Qt Resource
+files, and other project files.
+
+![Qt Design Studio showing the main screen](resources/design_studio_main_screen.png)
+
+Qt Design Studio offers a Python project template generator. The option can be enabled in the
+`File` > `Export project` > `Enable Python Generator` setting.
+
+![Qt Design Studio Enable Python Generator setting](resources/design_studio_enable_python_generator.png)
+
+When the setting is enabled, Qt Design Studio will create a `Python` folder in the project directory,
+containing the `main.py` and `pyproject.toml` files as well as the `autogen` folder. The `autogen`
+folder contains the `settings.py` file, which is used to set up the project root path, the QML
+import paths and other Qt specific settings.
+
+## Python development
+
+The project contains three Python files that define QML elements located in the `Python/audio`
+folder. They belong to the `Audio` QML module. The QML code expects that they exist. Otherwise, the
+application can not be executed.
+
+The `AudioEngine` QML element is responsible for playing audio files. It uses the `QSoundEffect`
+class from the [Qt Multimedia] module to play the audio files. It also provides [Qt Signals] for
+communicating with the QML layer.
+
+<details>
+<summary class="prominent-summary">audio_engine.py</summary>
+
+```{literalinclude} ../../../../../../../../examples/tutorials/drumpad/final_project/Python/audio/audio_engine.py
+---
+language: python
+caption: audio_engine.py
+linenos: true
+---
+```
+</details>
+
+The `AudioFilesModel` QML element is responsible for managing the audio files. It fetches the
+available audio files from the `Sounds` folder and provides a `getModel()` method to return a list
+of files. It detects whether the application has been deployed because the compiled Qt resource
+files are used in this case.
+
+<details>
+<summary class="prominent-summary">audio_files_model.py</summary>
+
+```{literalinclude} ../../../../../../../../examples/tutorials/drumpad/final_project/Python/audio/audio_files_model.py
+---
+language: python
+caption: audio_files_model.py
+linenos: true
+---
+```
+</details>
+
+The `WaveformItem` QML element is responsible for displaying the audio waveform. It uses the
+`QAudioDecoder` and `QAudioFormat` classes from the [Qt Multimedia] module to decode the audio file
+and display the waveform. The graph is drawn using [QPainter].
+
+<details>
+<summary class="prominent-summary">waveform_item.py</summary>
+
+```{literalinclude} ../../../../../../../../examples/tutorials/drumpad/final_project/Python/audio/waveform_item.py
+---
+language: python
+caption: waveform_item.py
+linenos: true
+---
+```
+</details>
+
+## Running the application
+
+Navigate to the `Python/` directory of the project:
+
+```bash
+cd Python/
+```
+
+Then, build the project using:
+
+```bash
+pyside6-project build
+```
+
+This command will compile resources, UI files, QML files, and other necessary components.
+
+## Deployment
+
+In order to create a standalone executable of the application, we can use the [pyside6-deploy]
+command line tool. It will analyze the project source code, determine the required Qt modules and
+dependencies and bundle the code into a native executable.
+
+To deploy the application, execute the following command from the `Python/` directory:
+
+```bash
+pyside6-deploy --name Drumpad
+```
+
+This will create a standalone executable for the application in the project directory.
+
+```{important}
+Make sure to fulfil the [pyside6-deploy requirements] for your platform. Otherwise, the tool will
+not detect that the example code uses Qt Multimedia module. In that case, the produced
+executable will not work properly.
+```
+
+### Qt resource files
+
+Note that since the `main.py` file is contained in the `Python` folder, its references to the project
+QML files and other resources have to traverse one level up. When the project is deployed, this is
+an issue because of the way [Nuitka] works. After the deployment, the `main.py` entry point file
+is morphed into a native executable file, but its location in the project folder changes:
+
+Project structure before deployment:
+```
+├── Drumpad
+│   â”œâ”€â”€ AvailableSoundsComboBox.qml
+│   ...
+├── Python
+│   â”œâ”€â”€ main.py
+│   â”œâ”€â”€ pyproject.toml
+│   â””── autogen
+└── Sounds
+    â”œâ”€â”€ Clap.wav
+    â”œâ”€â”€ Kick Drum.wav
+    ...
+```
+
+Project structure after deployment:
+```
+├── main.exe  (OS dependent executable format)
+├── Drumpad
+│   â”œâ”€â”€ AvailableSoundsComboBox.qml
+│   ...
+└── Sounds
+    â”œâ”€â”€ Clap.wav
+    â”œâ”€â”€ Kick Drum.wav
+    ...
+```
+
+The relative location of the resources changes after the deployment. For example, before deploying
+the application, the path for accessing a sound from the `main.py` file would be:
+`../Sounds/Clap.wav`. After the deployment, the relative path is now: `Sounds/Clap.wav`.
+
+This issue is addressed by the [pyside6-deploy] tool thanks to the usage of
+[Qt resource files][Qt Resource System]. All the files listed in the `Drumpad.qrc` file are embedded
+in the executable and can be accessed by importing the `Python/autogen/resources.py` Python file.
+This way, the paths can be easily resolved properly after the deployment of the application.
+
+Qt Design Studio creates the `Python/autogen/settings.py` file which contains code that enables the
+usage of the compiled Qt resources when the application is deployed. This code can be modified on
+demand.
+
+## Conclusion
+
+In this tutorial, you learned how to integrate a user interface developed in [Qt Design Studio] with
+a Python *backend* using PySide6. We walked through the complete workflow, from exporting a QML
+project and implementing custom Python logic to packaging the application into a standalone
+executable using [pyside6-deploy].
+
+[Qt Design Studio]: https://www.qt.io/product/ui-design-tools/
+[Qt Quick]: https://doc.qt.io/qt-6/qtquick-index.html
+[qt-design-studio-tutorials]: https://doc.qt.io/qtdesignstudio/gstutorials.html
+[Qt Multimedia]: https://doc.qt.io/qt-6/qtmultimedia-index.html
+[Nuitka]: https://nuitka.net/
+[Qt Resource System]: https://doc.qt.io/qt-6/resources.html
+[pyside6-deploy]: https://doc.qt.io/qtforpython-6/deployment/deployment-pyside6-deploy.html
+[pyside6-deploy requirements]: https://doc.qt.io/qtforpython-6/deployment/deployment-pyside6-deploy.html#considerations
+[QML module]: https://doc.qt.io/qt-6/qtqml-modules-topic.html
+[Qt Signals]: https://doc.qt.io/qt-6/signalsandslots.html
+[QPainter]: https://doc.qt.io/qt-6/qpainter.html
diff --git a/sources/pyside6/doc/tutorials/drumpad/resources/design_studio_enable_python_generator.png b/sources/pyside6/doc/tutorials/drumpad/resources/design_studio_enable_python_generator.png
new file mode 100644 (file)
index 0000000..e377226
Binary files /dev/null and b/sources/pyside6/doc/tutorials/drumpad/resources/design_studio_enable_python_generator.png differ
diff --git a/sources/pyside6/doc/tutorials/drumpad/resources/design_studio_main_screen.png b/sources/pyside6/doc/tutorials/drumpad/resources/design_studio_main_screen.png
new file mode 100644 (file)
index 0000000..cfba693
Binary files /dev/null and b/sources/pyside6/doc/tutorials/drumpad/resources/design_studio_main_screen.png differ
diff --git a/sources/pyside6/doc/tutorials/drumpad/resources/drumpad.png b/sources/pyside6/doc/tutorials/drumpad/resources/drumpad.png
new file mode 100644 (file)
index 0000000..46b0142
Binary files /dev/null and b/sources/pyside6/doc/tutorials/drumpad/resources/drumpad.png differ
index 6f8b87d2db2a6f78fc3ec5005e5f984f678d107c..a5e4976e6c5e353ecf29811308da15f72378b30c 100644 (file)
@@ -352,6 +352,7 @@ code:
 <summary class="prominent-summary">main.py</summary>
 
 ```{literalinclude} ../../../../../../../../../examples/tutorials/finance_manager/part1/main.py
+---
 language: python
 caption: main.py
 linenos: true
index e9349f0c93d272e6c5f311cae8df34a77b37b1ea..a06c1b22413b4d5124c00f211b540ed2eec9ea54 100644 (file)
@@ -218,6 +218,33 @@ General Applications
     expenses/expenses.rst
     embedded/boot2qt.md
 
+Qt Creator
+----------
+
+* `Overview: Develop Qt for Python applications`_
+* `Tutorial: Qt Quick and Python`_
+* `Tutorial: Qt Widgets and Python`_
+* `Tutorial: Qt Widgets UI and Python`_
+
+Qt Design Studio
+----------------
+
+.. grid:: 1 3 3 3
+    :gutter: 2
+
+    .. grid-item-card:: Qt Design Studio integration tutorial
+        :class-item: cover-img
+        :link: tutorial_qt_design_studio_integration
+        :link-type: ref
+        :img-top: drumpad/resources/drumpad.png
+
+        Export a Qt Design Studio project to create a PySide6 application
+
+.. toctree::
+    :hidden:
+
+    drumpad/index.rst
+
 Qt Overviews
 ------------
 
@@ -241,3 +268,8 @@ Debug a PySide6 Application
 
     debugging/mixed_debugging.rst
     debugging/qml_debugging.rst
+
+.. _`Overview: Develop Qt for Python applications`: https://doc.qt.io/qtcreator/creator-python-development.html
+.. _`Tutorial: Qt Quick and Python`: https://doc.qt.io/qtcreator/creator-tutorial-python-application-qt-quick.html
+.. _`Tutorial: Qt Widgets and Python`: https://doc.qt.io/qtcreator/creator-tutorial-python-application-qt-widgets.html
+.. _`Tutorial: Qt Widgets UI and Python`: https://doc.qt.io/qtcreator/creator-tutorial-python-application-qt-widgets-ui.html
index 254d75f1e41915a20f42d13356789e8b740563cd..f52c0848f28fd4f89f8bcb4751059213d67ca1a3 100644 (file)
@@ -25,7 +25,7 @@ if __name__ == '__main__':
     # Set up the application window
     app = QGuiApplication(sys.argv)
     view = QQuickView()
-    view.setResizeMode(QQuickView.SizeRootObjectToView)
+    view.setResizeMode(QQuickView.ResizeMode.SizeRootObjectToView)
 
     # Expose the list to the Qml code
     my_model = QStringListModel()
@@ -38,7 +38,7 @@ if __name__ == '__main__':
     view.loadFromModule("App", "Main")
 
     # Show the window
-    if view.status() == QQuickView.Error:
+    if view.status() == QQuickView.Status.Error:
         sys.exit(-1)
     view.show()
 
diff --git a/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png b/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png
deleted file mode 100644 (file)
index 93968a5..0000000
Binary files a/sources/pyside6/doc/tutorials/qmlapp/newpyproject.png and /dev/null differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/newpyproject.webp b/sources/pyside6/doc/tutorials/qmlapp/newpyproject.webp
new file mode 100644 (file)
index 0000000..dea2e94
Binary files /dev/null and b/sources/pyside6/doc/tutorials/qmlapp/newpyproject.webp differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png
deleted file mode 100644 (file)
index c66d887..0000000
Binary files a/sources/pyside6/doc/tutorials/qmlapp/projectsmode.png and /dev/null differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/projectsmode.webp b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.webp
new file mode 100644 (file)
index 0000000..bbd2097
Binary files /dev/null and b/sources/pyside6/doc/tutorials/qmlapp/projectsmode.webp differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyproject.toml b/sources/pyside6/doc/tutorials/qmlapp/pyproject.toml
new file mode 100644 (file)
index 0000000..f88f386
--- /dev/null
@@ -0,0 +1,5 @@
+[project]
+name = "qml-application"
+
+[tool.pyside6-project]
+files = ["main.py", "App/Main.qml", "App/logo.png", "App/qmldir"]
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png
deleted file mode 100644 (file)
index 9832807..0000000
Binary files a/sources/pyside6/doc/tutorials/qmlapp/pyprojname.png and /dev/null differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojname.webp b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.webp
new file mode 100644 (file)
index 0000000..49db6f1
Binary files /dev/null and b/sources/pyside6/doc/tutorials/qmlapp/pyprojname.webp differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png
deleted file mode 100644 (file)
index e01e2eb..0000000
Binary files a/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.png and /dev/null differ
diff --git a/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.webp b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.webp
new file mode 100644 (file)
index 0000000..57c534e
Binary files /dev/null and b/sources/pyside6/doc/tutorials/qmlapp/pyprojxplor.webp differ
index 9dd3e4347a5c009c1862944e99bd77c6c2a93182..fac8ae1319d215a975454834f51c540d35a80543 100644 (file)
@@ -14,13 +14,12 @@ In this tutorial, you'll also learn how to provide data from Python
 as a QML initial property, which is then consumed by the ListView
 defined in the QML file.
 
-Before you begin, install the following prerequisites:
-
-* The `PySide6 <https://pypi.org/project/PySide6/>`_ Python packages.
-* *Qt Creator* from
-  `https://download.qt.io
-  <https://download.qt.io/snapshots/qtcreator/>`_.
+Before you begin, install *Qt Creator* from
+`https://download.qt.io <https://download.qt.io/snapshots/qtcreator/>`_.
 
+`Develop Qt for Python applications`_ describes how Python installations
+are handled by *Qt Creator*. By default, *Qt Creator* will prompt you
+to install PySide6 at the appropriate places.
 
 The following step-by-step instructions guide you through application
 development process using *Qt Creator*:
@@ -28,35 +27,37 @@ development process using *Qt Creator*:
 #. Open *Qt Creator* and select **File > New File or Project..** menu item
    to open following dialog:
 
-   .. image:: newpyproject.png
+   .. image:: newpyproject.webp
 
 #. Select **Qt for Python - Empty** from the list of application templates
    and select **Choose**.
 
-   .. image:: pyprojname.png
+   .. image:: pyprojname.webp
 
 #. Give a **Name** to your project, choose its location in the
    filesystem, and select **Finish** to create an empty ``main.py``
-   and ``main.pyproject``.
+   and ``pyproject.toml``.
 
-   .. image:: pyprojxplor.png
+   .. image:: pyprojxplor.webp
 
-   This should create a ``main.py`` and ```main.pyproject`` files
+   This should create a ``main.py`` and ```pyproject.toml`` files
    for the project.
 
 #. Download :download:`Main.qml<App/Main.qml>`, :download:`qmldir<App/qmldir>`
    and :download:`logo.png <App/logo.png>` and place them in a subdirectory
    named `App` in your project folder. This creates a basic QML module.
 
-#. Double-click on ``main.pyproject`` to open it in edit mode, and append
+#. Double-click on ``pyproject.toml``` to open it in edit mode, and append
    ``view.qml`` and ``logo.png`` to the **files** list. This is how your
    project file should look after this change:
 
    .. code::
 
-    {
-        "files": ["main.py", "App/Main.qml", "App/logo.png", "App/qmldir"]
-    }
+       [project]
+       name = "qml-application"
+
+       [tool.pyside6-project]
+       files = ["main.py", "App/Main.qml", "App/logo.png", "App/qmldir"]
 
 #. Now that you have the necessary bits for the application, import the
    Python modules in your ``main.py``, and download country data and
@@ -107,29 +108,19 @@ development process using *Qt Creator*:
 #. Your application is ready to be run now. Select **Projects** mode to
    choose the Python version to run it.
 
-   .. image:: projectsmode.png
+   .. image:: projectsmode.webp
 
 Run the application by using the ``CTRL+R`` keyboard shortcut to see if it
 looks like this:
 
 .. image:: qmlapplication.png
 
-You could also watch the following video tutorial for guidance to develop
-this application:
-
-.. raw:: html
-
-    <div style="position: relative; padding-bottom: 56.25%; height: 0;
-    overflow: hidden; max-width: 100%; height: auto;">
-        <iframe src="https://www.youtube.com/embed/JxfiUx60Mbg" frameborder="0"
-        allowfullscreen style="position: absolute; top: 0; left: 0;
-        width: 100%; height: 100%;">
-        </iframe>
-    </div>
-
 ********************
 Related information
 ********************
 
 * `QML Reference <https://doc.qt.io/qt-6/qmlreference.html>`_
+* `Develop Qt for Python applications <https://doc.qt.io/qtcreator/creator-python-development.html>`_
 * :doc:`../qmlintegration/qmlintegration`
+
+.. _`Develop Qt for Python applications`: https://doc.qt.io/qtcreator/creator-python-development.html
index 539f1f3299667660429bbf854fef4a344fa12bbb..b0050dd008b39fcd3fdf8b01ad4845c6e52676a0 100644 (file)
@@ -40,6 +40,7 @@ set(libpyside_HEADERS # installed below
     pysideslot_p.h
     pysidestaticstrings.h
     pysideutils.h
+    pysidevariantutils.h
     pysideweakref.h
     qobjectconnect.h
     signalmanager.h
@@ -59,6 +60,7 @@ set(libpyside_SRC
     pysidesignal.cpp
     pysideslot.cpp
     pysideproperty.cpp
+    pysidevariantutils.cpp
     pysideweakref.cpp
     pyside.cpp
     pyside_numpy.cpp
@@ -99,7 +101,7 @@ append_size_optimization_flags(pyside6)
 
 target_include_directories(pyside6 PUBLIC
   $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
-  $<INSTALL_INTERFACE:include/PySide6>
+  $<INSTALL_INTERFACE:PySide6/include>
 )
 
 target_compile_definitions(pyside6 PRIVATE -DQT_LEAN_HEADERS=1 -DQT_NO_KEYWORDS=1)
@@ -144,28 +146,29 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pyside6.pc.in"
 include(CMakePackageConfigHelpers)
 
 # Build-tree / super project package config file.
-set(PYSIDE_PYTHONPATH "${pysidebindings_BINARY_DIR}/PySide6")
-set(PYSIDE_TYPESYSTEMS "${pysidebindings_SOURCE_DIR}/PySide6/templates/")
-set(PYSIDE_GLUE "${pysidebindings_SOURCE_DIR}/PySide6/glue")
+set(PYSIDE_PYTHONPATH "${PYTHON_SITE_PACKAGES}/PySide6")
+set(PYSIDE_TYPESYSTEMS "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/typesystems")
+set(PYSIDE_GLUE "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/glue")
 
 configure_package_config_file(
     "${CMAKE_CURRENT_SOURCE_DIR}/PySide6Config-spec.cmake.in"
     "${CMAKE_CURRENT_BINARY_DIR}/PySide6Config${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake"
-     INSTALL_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}"
-     PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE
-     INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
+    INSTALL_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}"
+    PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE
+    INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
 )
 
-set(PYSIDE_PYTHONPATH "${PYTHON_SITE_PACKAGES}/PySide6")
-set(PYSIDE_TYPESYSTEMS "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/typesystems")
-set(PYSIDE_GLUE "${CMAKE_INSTALL_PREFIX}/share/PySide6${pyside6_SUFFIX}/glue")
+# Install-tree / wheel configuration
+set(PYSIDE_PYTHONPATH "")
+set(PYSIDE_TYPESYSTEMS "typesystems")
+set(PYSIDE_GLUE "glue")
+set(PYSIDE_SOVERSION "${pyside6_library_so_version}")
 
-# Install-tree / relocatable package config file.
 configure_package_config_file(
     "${CMAKE_CURRENT_SOURCE_DIR}/PySide6Config-spec.cmake.in"
     "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6Config${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake"
-     INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6"
-     PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE
+    INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6"
+    PATH_VARS PYSIDE_PYTHONPATH PYSIDE_TYPESYSTEMS PYSIDE_GLUE
 )
 
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6Config.cmake.in"
@@ -174,14 +177,24 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6ConfigVersion.cmake.in"
                "${CMAKE_CURRENT_BINARY_DIR}/PySide6ConfigVersion.cmake" @ONLY)
 
 install(FILES ${libpyside_HEADERS}
-        DESTINATION include/${BINDING_NAME}${pyside6_SUFFIX})
+        DESTINATION ${BINDING_NAME}${pyside6_SUFFIX}/include)
 
+# build-time installation
 install(TARGETS pyside6 EXPORT PySide6Targets
                         LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
                         ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
                         RUNTIME DESTINATION bin)
-install(EXPORT PySide6Targets NAMESPACE PySide6::
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6")
+
+# wheel installation
+set_target_properties(pyside6 PROPERTIES
+    VERSION ${PYSIDE_SOVERSION})
+
+if(NOT is_pyside6_superproject_build)
+    install(TARGETS pyside6 EXPORT PySide6WheelTargets
+                            LIBRARY DESTINATION "PySide6"
+                            ARCHIVE DESTINATION "PySide6"
+                            RUNTIME DESTINATION "PySide6")
+endif()
 
 install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pyside6${pyside6_SUFFIX}.pc"
         DESTINATION "${LIB_INSTALL_DIR}/pkgconfig")
index fd3de1e759c1479a0c9b38b216b20d66aef4779e..f9e9a4900e4dcd8277901e9e375373e168e96a81 100644 (file)
@@ -10,6 +10,10 @@ if (NOT TARGET PySide6::pyside6)
     include("${CMAKE_CURRENT_LIST_DIR}/PySide6Targets.cmake")
 endif()
 
+# set static variables
+set(PYSIDE_PYTHON_CONFIG_SUFFIX "@PYTHON_CONFIG_SUFFIX@")
+set(PYSIDE_SO_VERSION "@pyside6_library_so_version@")
+
 # Set relocatable variables.
 set_and_check(PYSIDE_PYTHONPATH "@PACKAGE_PYSIDE_PYTHONPATH@")
 set_and_check(PYSIDE_TYPESYSTEMS "@PACKAGE_PYSIDE_TYPESYSTEMS@")
index 89320977a915cd3eb3dbbe4c73dc1cf2daaae65b..2e1001be2f9f33166a928527923a3d9a40142f15 100644 (file)
@@ -168,12 +168,13 @@ void init(PyObject *module)
     PyTypeObject *type = SbkObjectType_TypeF();
     type->tp_setattro = SbkObjectType_meta_setattro;
 
-    if (InitSignatureStrings(PyClassProperty_TypeF(), PyClassProperty_SignatureStrings) < 0)
+    auto *classPropertyType = PyClassProperty_TypeF();
+    if (InitSignatureStrings(classPropertyType, PyClassProperty_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PyClassProperty_TypeF());
-    auto *classproptype = reinterpret_cast<PyObject *>(PyClassProperty_TypeF());
-    PyModule_AddObject(module, "PyClassProperty", classproptype);
+    auto *obClassPropertyType = reinterpret_cast<PyObject *>(classPropertyType);
+    Py_INCREF(obClassPropertyType);
+    PepModule_AddType(module, classPropertyType);
 }
 
 } // namespace PySide::ClassProperty
index a3b718d834fa78c986c68574497dfa4c309adb72..f92541d5d7a7b2caac5e8be368da0b5961b347d1 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <autodecref.h>
 #include <gilstate.h>
+#include <sbkpep.h>
 #include <sbkstaticstrings.h>
 #include <sbkstring.h>
 
 #include <private/qmetaobjectbuilder_p.h>
 
 #include <cstring>
+#include <limits>
 #include <vector>
 
 using namespace Qt::StringLiterals;
 
 using namespace PySide;
 
+// QMetaEnum can handle quint64 or int values. Check for big long values and force
+// them to quint64 (long=64bit/int=32bit on Linux vs long=32bit on Windows).
+// Note: underflows are currently not handled well.
+static QVariant longToEnumValue(PyObject *value)
+{
+    int overflow{};
+    const long longValue = PyLong_AsLongAndOverflow(value, &overflow);
+    return overflow != 0 || longValue > std::numeric_limits<int>::max()
+        ? QVariant(PyLong_AsUnsignedLongLong(value)) : QVariant(int(longValue));
+}
+
 // MetaObjectBuilder: Provides the QMetaObject's returned by
 // QObject::metaObject() for PySide6 objects. There are several
 // scenarios to consider:
@@ -63,8 +76,8 @@ public:
     int addProperty(const QByteArray &property, PyObject *data);
     void addInfo(const QByteArray &key, const  QByteArray &value);
     void addInfo(const QMap<QByteArray, QByteArray> &info);
-    void addEnumerator(const char *name, bool flag, bool scoped,
-                       const MetaObjectBuilder::EnumValues &entries);
+    QMetaEnumBuilder addEnumerator(const char *name, bool flag, bool scoped,
+                                   const MetaObjectBuilder::EnumValues &entries);
     void removeProperty(int index);
     const QMetaObject *update();
 
@@ -89,31 +102,21 @@ QMetaObjectBuilder *MetaObjectBuilderPrivate::ensureBuilder()
     return m_builder;
 }
 
-MetaObjectBuilder::MetaObjectBuilder(const char *className, const QMetaObject *metaObject) :
-    m_d(new MetaObjectBuilderPrivate)
+MetaObjectBuilder::MetaObjectBuilder(const QMetaObject *metaObject)
+    m_d(new MetaObjectBuilderPrivate)
 {
     m_d->m_baseObject = metaObject;
-    m_d->m_builder = new QMetaObjectBuilder();
-    m_d->m_builder->setClassName(className);
-    m_d->m_builder->setSuperClass(metaObject);
-    m_d->m_builder->setClassName(className);
 }
 
+// Parse the type in case of a Python class inheriting a Qt class.
 MetaObjectBuilder::MetaObjectBuilder(PyTypeObject *type, const QMetaObject *metaObject)
     : m_d(new MetaObjectBuilderPrivate)
 {
     m_d->m_baseObject = metaObject;
-    const char *className = type->tp_name;
-    if (const char *lastDot = strrchr(type->tp_name, '.'))
-        className = lastDot + 1;
-    // Different names indicate a Python class inheriting a Qt class.
-    // Parse the type.
-    if (strcmp(className, metaObject->className()) != 0) {
-        m_d->m_builder = new QMetaObjectBuilder();
-        m_d->m_builder->setClassName(className);
-        m_d->m_builder->setSuperClass(metaObject);
-        m_d->parsePythonType(type);
-    }
+    m_d->m_builder = new QMetaObjectBuilder();
+    m_d->m_builder->setClassName(PepType_GetNameStr(type));
+    m_d->m_builder->setSuperClass(metaObject);
+    m_d->parsePythonType(type);
 }
 
 MetaObjectBuilder::~MetaObjectBuilder()
@@ -303,9 +306,9 @@ QMetaPropertyBuilder
     auto *typeObject = Property::getTypeObject(property);
     if (typeObject != nullptr && PyType_Check(typeObject)) {
         auto *pyTypeObject = reinterpret_cast<PyTypeObject *>(typeObject);
-        if (qstrncmp(pyTypeObject->tp_name, "PySide", 6) != 0
+        if (qstrncmp(PepType_GetFullyQualifiedNameStr(pyTypeObject), "PySide", 6) != 0
             && PySide::isQObjectDerived(pyTypeObject, false)) {
-            const QByteArray pyType(pyTypeObject->tp_name);
+            const QByteArray pyType(PepType_GetFullyQualifiedNameStr(pyTypeObject));
             const auto metaType = QMetaType::fromName(pyType + '*');
             if (metaType.isValid()) {
                 return builder->addProperty(propertyName, pyType,
@@ -384,8 +387,9 @@ void MetaObjectBuilder::addEnumerator(const char *name, bool flag, bool scoped,
     m_d->addEnumerator(name, flag, scoped, entries);
 }
 
-void MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool scoped,
-                                             const MetaObjectBuilder::EnumValues &entries)
+QMetaEnumBuilder
+    MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool scoped,
+                                            const MetaObjectBuilder::EnumValues &entries)
 {
     auto *builder = ensureBuilder();
     int have_already = builder->indexOfEnumerator(name);
@@ -394,10 +398,15 @@ void MetaObjectBuilderPrivate::addEnumerator(const char *name, bool flag, bool s
     auto enumbuilder = builder->addEnumerator(name);
     enumbuilder.setIsFlag(flag);
     enumbuilder.setIsScoped(scoped);
+    for (const auto &item : entries) {
+        if (item.second.typeId() == QMetaType::ULongLong)
+            enumbuilder.addKey(item.first, item.second.toULongLong());
+        else
+            enumbuilder.addKey(item.first, item.second.toInt());
+    }
 
-    for (const auto &item : entries)
-        enumbuilder.addKey(item.first, item.second);
     m_dirty = true;
+    return enumbuilder;
 }
 
 void MetaObjectBuilderPrivate::removeProperty(int index)
@@ -676,16 +685,26 @@ void MetaObjectBuilderPrivate::parsePythonType(PyTypeObject *type)
         AutoDecRef items(PyMapping_Items(members));
         Py_ssize_t nr_items = PySequence_Length(items);
 
-        QList<std::pair<QByteArray, int> > entries;
+        MetaObjectBuilder::EnumValues entries;
+        entries.reserve(nr_items);
+        bool is64bit = false;
         for (Py_ssize_t idx = 0; idx < nr_items; ++idx) {
             AutoDecRef item(PySequence_GetItem(items, idx));
             AutoDecRef key(PySequence_GetItem(item, 0));
             AutoDecRef member(PySequence_GetItem(item, 1));
             AutoDecRef value(PyObject_GetAttr(member, Shiboken::PyName::value()));
             const auto *ckey = String::toCString(key);
-            auto ivalue = PyLong_AsSsize_t(value);
-            entries.push_back(std::make_pair(ckey, int(ivalue)));
+            QVariant valueV = longToEnumValue(value.object());
+            if (valueV.typeId() == QMetaType::ULongLong)
+                is64bit = true;
+            entries.append(std::make_pair(QByteArray(ckey), valueV));
         }
-        addEnumerator(name, isFlag, true, entries);
+        auto enumBuilder = addEnumerator(name, isFlag, true, entries);
+        QByteArray qualifiedName = ensureBuilder()->className() + "::"_ba + name;
+        auto *typeObject = reinterpret_cast<PyTypeObject *>(obEnumType);
+        auto metaType = is64bit
+            ? PySide::QEnum::createGenericEnum64MetaType(qualifiedName, typeObject)
+            : PySide::QEnum::createGenericEnumMetaType(qualifiedName, typeObject);
+        enumBuilder.setMetaType(metaType);
     }
 }
index fd5a5f55280379055ff91fc75f4cf3009cae801b..2b85dc6d8e0602b3f799d61a300a7ef802cee28b 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <QtCore/qmetaobject.h>
 #include <QtCore/qmetaobject.h>
+#include <QtCore/qvariant.h>
 
 #include <utility>
 
@@ -17,16 +18,17 @@ class MetaObjectBuilderPrivate;
 namespace PySide
 {
 
-class MetaObjectBuilder
+class PYSIDE_API MetaObjectBuilder
 {
     Q_DISABLE_COPY_MOVE(MetaObjectBuilder)
 public:
-    using EnumValue = std::pair<QByteArray, int>;
+    using EnumValue = std::pair<QByteArray, QVariant>; // Int/ULongLong
     using EnumValues = QList<EnumValue>;
 
-    MetaObjectBuilder(const char *className, const QMetaObject *metaObject);
-
-    MetaObjectBuilder(PyTypeObject *type, const QMetaObject *metaObject);
+    // Plain wrapped Qt types
+    explicit MetaObjectBuilder(const QMetaObject *metaObject);
+    // Types defined in Python which are parsed
+    explicit MetaObjectBuilder(PyTypeObject *type, const QMetaObject *metaObject);
     ~MetaObjectBuilder();
 
     int indexOfMethod(QMetaMethod::MethodType mtype, const QByteArray &signature) const;
@@ -44,7 +46,7 @@ public:
 
     const QMetaObject *update();
 
-    PYSIDE_API static QString formatMetaObject(const QMetaObject *metaObject);
+    static QString formatMetaObject(const QMetaObject *metaObject);
 
 private:
     MetaObjectBuilderPrivate *m_d;
index 1af286424c99bec5772e68730b9e9f45daee00f1..3d9b9c1bec3c7031dc5c35b337885d8f6ae25ba2 100644 (file)
@@ -17,6 +17,7 @@
 #include <QtCore/qcoreapplication.h>
 #include <QtCore/qhash.h>
 #include <QtCore/qpointer.h>
+#include <QtCore/qthread.h>
 
 namespace PySide
 {
@@ -314,6 +315,7 @@ public:
 
 public Q_SLOTS:
     void senderDestroyed(QObject *o);
+    void reparentOnQApp();
 };
 
 void SenderSignalDeletionTracker::senderDestroyed(QObject *o)
@@ -327,6 +329,12 @@ void SenderSignalDeletionTracker::senderDestroyed(QObject *o)
     }
 }
 
+void SenderSignalDeletionTracker::reparentOnQApp()
+{
+    if (auto *app = QCoreApplication::instance())
+        setParent(app);
+}
+
 static QPointer<SenderSignalDeletionTracker> senderSignalDeletionTracker;
 
 static void disconnectReceiver(PyObject *pythonSelf)
@@ -364,7 +372,15 @@ void registerSlotConnection(QObject *source, int signalIndex, PyObject *callback
     connectionHash.insert(connectionKey(source, signalIndex, callback), connection);
     if (senderSignalDeletionTracker.isNull()) {
         auto *app = QCoreApplication::instance();
-        senderSignalDeletionTracker = new SenderSignalDeletionTracker(app);
+        if (app == nullptr || QThread::currentThread() == app->thread()) {
+            senderSignalDeletionTracker = new SenderSignalDeletionTracker(app);
+        } else {
+            senderSignalDeletionTracker = new SenderSignalDeletionTracker(nullptr);
+            senderSignalDeletionTracker->moveToThread(app->thread());
+            senderSignalDeletionTracker->metaObject()->invokeMethod(senderSignalDeletionTracker,
+                                                                    "reparentOnQApp",
+                                                                    Qt::QueuedConnection);
+        }
         Py_AtExit(clearConnectionHash);
     }
 
index a60dd33192a4a4a3de6ee245d178af51439dbdc4..60659b2d6ef69055e86e076275e2b04a7f66659b 100644 (file)
@@ -8,12 +8,15 @@
 
 #include <autodecref.h>
 #include <sbkfeature_base.h>
+#include <sbkpep.h>
 #include <sbkstaticstrings.h>
 #include <sbkstring.h>
 #include <signature_p.h>
 
 #include <QtCore/qstringlist.h>
 
+#include <cstring>
+
 //////////////////////////////////////////////////////////////////////////////
 //
 // PYSIDE-1019: Support switchable extensions
@@ -123,7 +126,7 @@ static void ensureNewDictType()
     if (new_dict_type == nullptr) {
         new_dict_type = createDerivedDictType();
         if (new_dict_type == nullptr)
-            Py_FatalError("PySide6: Problem creating ChameleonDict");
+            Py_FatalError("libshiboken: Problem creating ChameleonDict");
     }
 }
 
@@ -271,19 +274,20 @@ static inline void SelectFeatureSetSubtype(PyTypeObject *type, int select_id)
      * This is the selector for one sublass. We need to call this for
      * every subclass until no more subclasses or reaching the wanted id.
      */
-    static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
+    static auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
     AutoDecRef tpDict(PepType_GetDict(type));
     if (Py_TYPE(tpDict.object()) == Py_TYPE(pyTypeType_tp_dict)) {
         // On first touch, we initialize the dynamic naming.
         // The dict type will be replaced after the first call.
         if (!replaceClassDict(type)) {
-            Py_FatalError("failed to replace class dict!");
+            Py_FatalError("libshiboken: failed to replace class dict!");
             return;
         }
     }
     if (!moveToFeatureSet(type, select_id)) {
         if (!createNewFeatureSet(type, select_id)) {
-            Py_FatalError("failed to create a new feature set!");
+            PyErr_Print();
+            Py_FatalError("libshiboken: failed to create a new feature set!");
             return;
         }
     }
@@ -296,13 +300,12 @@ static inline int getFeatureSelectId()
 {
     static auto *undef = PyLong_FromLong(-1);
     static auto *feature_dict = GetFeatureDict();
-    // these things are all borrowed
-    auto *globals = PyEval_GetGlobals();
-    if (globals == nullptr
-        || globals == cached_globals)
+
+    Shiboken::AutoDecRef globals(PepEval_GetFrameGlobals());
+    if (globals.isNull() || globals.object() == cached_globals)
         return last_select_id;
 
-    auto *modname = PyDict_GetItem(globals, PyMagicName::name());
+    auto *modname = PyDict_GetItem(globals.object(), PyMagicName::name());
     if (modname == nullptr)
         return last_select_id;
 
@@ -327,12 +330,12 @@ static inline void SelectFeatureSet(PyTypeObject *type)
      * Generated functions call this directly.
      * Shiboken will assign it via a public hook of `basewrapper.cpp`.
      */
-    static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
+    static auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
     AutoDecRef tpDict(PepType_GetDict(type));
     if (Py_TYPE(tpDict.object()) == Py_TYPE(pyTypeType_tp_dict)) {
         // We initialize the dynamic features by using our own dict type.
         if (!replaceClassDict(type)) {
-            Py_FatalError("failed to replace class dict!");
+            Py_FatalError("libshiboken: failed to replace class dict!");
             return;
         }
     }
@@ -445,9 +448,9 @@ static PyObject *methodWithNewName(PyTypeObject *type,
      * Create a method with a lower case name.
      */
     auto *obtype = reinterpret_cast<PyObject *>(type);
-    const auto len = strlen(new_name);
+    const auto len = std::strlen(new_name);
     auto *name = new char[len + 1];
-    strcpy(name, new_name);
+    std::strcpy(name, new_name);
     auto *new_meth = new PyMethodDef;
     new_meth->ml_name = name;
     new_meth->ml_meth = meth->ml_meth;
index 195c000dc1b56f85c12c90717b509709fecd78e7..1f55b30d790c0aeb4a40315e8a00e1b56b3807d4 100644 (file)
@@ -30,6 +30,8 @@
 #include <gilstate.h>
 #include <helper.h>
 #include <sbkconverter.h>
+#include <sbkerrors.h>
+#include <sbkpep.h>
 #include <sbkstring.h>
 #include <sbkstaticstrings.h>
 #include <sbkfeature_base.h>
 using namespace Qt::StringLiterals;
 
 static QStack<PySide::CleanupFunction> cleanupFunctionList;
-static void *qobjectNextAddr;
+
+// Used by QML (main thread), but needs to be protected against other
+// threads constructing QObject's.
+static void thread_local *qobjectNextAddr;
 
 QT_BEGIN_NAMESPACE
 extern bool qRegisterResourceData(int, const unsigned char *, const unsigned char *,
@@ -410,7 +415,8 @@ static void destructionVisitor(SbkObject *pyObj, void *data)
     auto *pyQApp = reinterpret_cast<SbkObject *>(realData[0]);
     auto *pyQObjectType = reinterpret_cast<PyTypeObject *>(realData[1]);
 
-    if (pyObj != pyQApp && PyObject_TypeCheck(pyObj, pyQObjectType)) {
+    auto *ob = reinterpret_cast<PyObject *>(pyObj);
+    if (pyObj != pyQApp && PyObject_TypeCheck(ob, pyQObjectType)) {
         if (Shiboken::Object::hasOwnership(pyObj) && Shiboken::Object::isValid(pyObj, false)) {
             Shiboken::Object::setValidCpp(pyObj, false);
 
@@ -452,15 +458,13 @@ std::size_t getSizeOfQObject(PyTypeObject *type)
     return retrieveTypeUserData(type)->cppObjSize;
 }
 
-void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::size_t cppObjSize)
+static void initDynamicMetaObjectHelper(PyTypeObject *type,
+                                        TypeUserData *userData)
 {
-    //create DynamicMetaObject based on python type
-    auto *userData = new TypeUserData(reinterpret_cast<PyTypeObject *>(type), base, cppObjSize);
-    userData->mo.update();
     Shiboken::ObjectType::setTypeUserData(type, userData, Shiboken::callCppDestructor<TypeUserData>);
 
-    //initialize staticQMetaObject property
-    void *metaObjectPtr = const_cast<QMetaObject *>(userData->mo.update());
+    // initialize staticQMetaObject property
+    const void *metaObjectPtr = userData->mo.update();
     static SbkConverter *converter = Shiboken::Conversions::getConverter("QMetaObject");
     if (!converter)
         return;
@@ -469,6 +473,11 @@ void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::siz
                      Shiboken::PyName::qtStaticMetaObject(), pyMetaObject);
 }
 
+void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::size_t cppObjSize)
+{
+    initDynamicMetaObjectHelper(type, new TypeUserData(base, cppObjSize));
+}
+
 TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj)
 {
     if (!SbkObjectType_Check(pyTypeObj))
@@ -520,7 +529,9 @@ void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject * /* kwds *
     // PYSIDE-1463: Don't change feature selection durin subtype initialization.
     // This behavior is observed with PySide 6.
     PySide::Feature::Enable(false);
-    initDynamicMetaObject(type, userData->mo.update(), userData->cppObjSize);
+    // create DynamicMetaObject based on python type
+    auto *subTypeData = new TypeUserData(type, userData->mo.update(), userData->cppObjSize);
+    initDynamicMetaObjectHelper(type, subTypeData);
     PySide::Feature::Enable(true);
 }
 
@@ -595,10 +606,7 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n
 
     // Search on metaobject (avoid internal attributes started with '__')
     if (!attr) {
-        PyObject *type{};
-        PyObject *value{};
-        PyObject *traceback{};
-        PyErr_Fetch(&type, &value, &traceback);     // This was omitted for a loong time.
+        Shiboken::Errors::Stash errorStash;
 
         int flags = currentSelectId(Py_TYPE(self));
         int snake_flag = flags & 0x01;
@@ -623,8 +631,10 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n
                     if (res) {
                         AutoDecRef elemName(PyObject_GetAttr(res, PySideMagicName::name()));
                         // Note: This comparison works because of interned strings.
-                        if (elemName == name)
+                        if (elemName == name) {
+                            errorStash.release();
                             return res;
+                        }
                         Py_DECREF(res);
                     }
                     PyErr_Clear();
@@ -655,6 +665,7 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n
                     } else if (auto *func = MetaFunction::newObject(cppSelf, i)) {
                         auto *result = reinterpret_cast<PyObject *>(func);
                         PyObject_SetAttr(self, name, result);
+                        errorStash.release();
                         return result;
                     }
                 }
@@ -663,17 +674,17 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n
                 auto *pySignal = reinterpret_cast<PyObject *>(
                     Signal::newObjectFromMethod(cppSelf, self, signalList));
                 PyObject_SetAttr(self, name, pySignal);
+                errorStash.release();
                 return pySignal;
             }
         }
-        PyErr_Restore(type, value, traceback);
     }
     return attr;
 }
 
 bool inherits(PyTypeObject *objType, const char *class_name)
 {
-    if (strcmp(objType->tp_name, class_name) == 0)
+    if (std::strcmp(PepType_GetFullyQualifiedNameStr(objType), class_name) == 0)
         return true;
 
     PyTypeObject *base = objType->tp_base;
@@ -1058,10 +1069,10 @@ QMetaType qMetaTypeFromPyType(PyTypeObject *pyType)
         return QMetaType(QMetaType::Int);
     if (Shiboken::ObjectType::checkType(pyType))
         return QMetaType::fromName(Shiboken::ObjectType::getOriginalName(pyType));
-    return QMetaType::fromName(pyType->tp_name);
+    return QMetaType::fromName(PepType_GetFullyQualifiedNameStr(pyType));
 }
 
-debugPyTypeObject::debugPyTypeObject(const PyTypeObject *o) noexcept
+debugPyTypeObject::debugPyTypeObject(PyTypeObject *o) noexcept
     : m_object(o)
 {
 }
@@ -1073,7 +1084,7 @@ QDebug operator<<(QDebug debug, const debugPyTypeObject &o)
     debug.nospace();
     debug << "PyTypeObject(";
     if (o.m_object)
-        debug << '"' << o.m_object->tp_name << '"';
+        debug << '"' << PepType_GetFullyQualifiedNameStr(o.m_object) << '"';
     else
         debug << '0';
     debug << ')';
@@ -1210,10 +1221,7 @@ QDebug operator<<(QDebug debug, const debugPyObject &o)
     return debug;
 }
 
-debugPyBuffer::debugPyBuffer(Py_buffer *b) noexcept : m_buffer(b)
-{
-}
-
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API >= 0x030B0000
 static void formatPy_ssizeArray(QDebug &debug, const char *name, const Py_ssize_t *array, int len)
 {
     debug << ", " << name << '=';
@@ -1227,6 +1235,10 @@ static void formatPy_ssizeArray(QDebug &debug, const char *name, const Py_ssize_
     }
 }
 
+debugPyBuffer::debugPyBuffer(Py_buffer *b) noexcept : m_buffer(b)
+{
+}
+
 PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b)
 {
     QDebugStateSaver saver(debug);
@@ -1252,5 +1264,6 @@ PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b)
     debug << ')';
     return debug;
 }
+#endif // !Py_LIMITED_API || >= 3.11
 
 } // namespace PySide
index eb45cb9a8810e31bb1b317e06134abaacfc4fc8b..65822e6ece97030c4f4dc7c8ed363aec513cd33a 100644 (file)
@@ -1,7 +1,7 @@
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=@CMAKE_INSTALL_PREFIX@
 libdir=@LIB_INSTALL_DIR@
-includedir=@CMAKE_INSTALL_PREFIX@/include/PySide6@pyside6_SUFFIX@
+includedir=@CMAKE_INSTALL_PREFIX@/PySide6@pyside6_SUFFIX@/include
 typesystemdir=@CMAKE_INSTALL_PREFIX@/share/PySide6@pyside6_SUFFIX@/typesystems
 gluedir=@CMAKE_INSTALL_PREFIX@/share/PySide6@pyside6_SUFFIX@/glue
 pythonpath=@PYTHON_SITE_PACKAGES@
index b13c1829a811735d92932dece376b7cb755238a6..7033239ac1624eddfccbb3820758530ad1381107 100644 (file)
@@ -14,8 +14,11 @@ namespace PySide
 // Struct associated with QObject's via Shiboken::Object::getTypeUserData()
 struct TypeUserData
 {
-    explicit TypeUserData(PyTypeObject* type,  const QMetaObject* metaobject, std::size_t size) :
+    explicit TypeUserData(PyTypeObject *type, const QMetaObject *metaobject, std::size_t size) :
         mo(type, metaobject), cppObjSize(size) {}
+    // Plain wrapped Qt types
+    explicit TypeUserData(const QMetaObject *metaobject, std::size_t size) :
+        mo(metaobject), cppObjSize(size) {}
 
     MetaObjectBuilder mo;
     std::size_t cppObjSize;
index 085e8980e5410964a5b7cf503fd28df230828124..429a9c60090f33b662738ecb99153b591611af0f 100644 (file)
@@ -116,8 +116,10 @@ void init(PyObject *module)
     if (InitSignatureStrings(PySideClassInfo_TypeF(), ClassInfo_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideClassInfo_TypeF());
-    PyModule_AddObject(module, "ClassInfo", reinterpret_cast<PyObject *>(PySideClassInfo_TypeF()));
+    auto *classInfoType = PySideClassInfo_TypeF();
+    auto *obClassInfoType  = reinterpret_cast<PyObject *>(classInfoType);
+    Py_INCREF(obClassInfoType);
+    PepModule_AddType(module, classInfoType);
 }
 
 bool checkType(PyObject *pyObj)
index 7a496c4b7e42f05fd992e8686ef99951610274dc..770146ef2e9e74ccd28f33ae7e38dad94b13db2b 100644 (file)
@@ -9,6 +9,7 @@
 #include <autodecref.h>
 #include <basewrapper.h>
 #include <sbkconverter.h>
+#include <sbkpep.h>
 #include <sbktypefactory.h>
 #include <signature.h>
 
@@ -85,8 +86,10 @@ void init(PyObject *module)
     if (InitSignatureStrings(PySideMetaFunction_TypeF(), MetaFunction_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideMetaFunction_TypeF());
-    PyModule_AddObject(module, "MetaFunction", reinterpret_cast<PyObject *>(PySideMetaFunction_TypeF()));
+    auto *metaFunctionType = PySideMetaFunction_TypeF();
+    auto *obMetaFunctionType = reinterpret_cast<PyObject *>(metaFunctionType);
+    Py_INCREF(obMetaFunctionType);
+    PepModule_AddType(module, metaFunctionType);
 }
 
 PySideMetaFunction *newObject(QObject *source, int methodIndex)
index 69d3470430db6394a8ba2a28a2350bab6b4bb9b5..c72bc381ab909048d3494cf729bf6200190ae679 100644 (file)
@@ -46,6 +46,7 @@ static PyObject *qProperty_fdel(PyObject *, void *);
 
 static PyMethodDef PySidePropertyMethods[] = {
     {"getter", reinterpret_cast<PyCFunction>(qPropertyGetter), METH_O, nullptr},
+    // "name@setter" handling
     {"setter", reinterpret_cast<PyCFunction>(qPropertySetter), METH_O,  nullptr},
     {"resetter", reinterpret_cast<PyCFunction>(qPropertyResetter), METH_O,  nullptr},
     {"deleter", reinterpret_cast<PyCFunction>(qPropertyDeleter), METH_O, nullptr},
@@ -114,7 +115,7 @@ PyObject *PySidePropertyPrivate::getValue(PyObject *source) const
 
 int PySidePropertyPrivate::setValue(PyObject *source, PyObject *value)
 {
-    if (fset && value) {
+    if (fset != nullptr && fset != Py_None && value != nullptr) {
         Shiboken::AutoDecRef args(PyTuple_New(2));
         PyTuple_SetItem(args, 0, source);
         PyTuple_SetItem(args, 1, value);
@@ -123,7 +124,7 @@ int PySidePropertyPrivate::setValue(PyObject *source, PyObject *value)
         Shiboken::AutoDecRef result(PyObject_CallObject(fset, args));
         return (result.isNull() ? -1 : 0);
     }
-    if (fdel) {
+    if (fdel != nullptr && fdel != Py_None) {
         Shiboken::AutoDecRef args(PyTuple_New(1));
         PyTuple_SetItem(args, 0, source);
         Py_INCREF(source);
@@ -136,7 +137,7 @@ int PySidePropertyPrivate::setValue(PyObject *source, PyObject *value)
 
 int PySidePropertyPrivate::reset(PyObject *source)
 {
-    if (freset) {
+    if (freset != nullptr && freset != Py_None) {
         Shiboken::AutoDecRef args(PyTuple_New(1));
         Py_INCREF(source);
         PyTuple_SetItem(args, 0, source);
@@ -486,7 +487,7 @@ static const char *Property_SignatureStrings[] = {
         "fset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
         "freset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
         "doc:str=None,"
-        "notify:typing.Optional[collections.abc.Callable[[],None]]=None,"
+        "notify:typing.Optional[PySide6.QtCore.Signal]=None,"
         "designable:bool=True,scriptable:bool=True,"
         "stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)",
     "PySide6.QtCore.Property.deleter(self,fdel:collections.abc.Callable[[typing.Any],None])->PySide6.QtCore.Property",
@@ -499,11 +500,13 @@ static const char *Property_SignatureStrings[] = {
 
 void init(PyObject *module)
 {
-    if (InitSignatureStrings(PySideProperty_TypeF(), Property_SignatureStrings) < 0)
+    auto *propertyType = PySideProperty_TypeF();
+    if (InitSignatureStrings(propertyType, Property_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideProperty_TypeF());
-    PyModule_AddObject(module, "Property", reinterpret_cast<PyObject *>(PySideProperty_TypeF()));
+    auto *obPropertyType = reinterpret_cast<PyObject *>(propertyType);
+    Py_INCREF(obPropertyType);
+    PepModule_AddType(module, propertyType);
 }
 
 bool checkType(PyObject *pyObj)
@@ -557,12 +560,12 @@ bool isReadable(const PySideProperty * /* self */)
 
 bool isWritable(const PySideProperty *self)
 {
-    return self->d->fset != nullptr;
+    return self->d->fset != nullptr && self->d->fset != Py_None;
 }
 
 bool hasReset(const PySideProperty *self)
 {
-    return self->d->freset != nullptr;
+    return self->d->freset != nullptr && self->d->freset != Py_None;
 }
 
 bool isDesignable(const PySideProperty *self)
index e922c2d245c52d0f1de1aa7425f818636553a5f7..166e7778cf12e8b40f3e79358d87cbc7300499c7 100644 (file)
@@ -3,12 +3,21 @@
 
 #include "pysideqenum.h"
 
+#include <pysidelogging_p.h>
+
 #include <autodecref.h>
+#include <sbkconverter.h>
 #include <sbkenum.h>
+#include <sbkpep.h>
 #include <sbkstaticstrings.h>
 #include <sbkstring.h>
 
+#include <QtCore/qmetatype.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qlist.h>
+
 #include <map>
+#include <cstring>
 
 ///////////////////////////////////////////////////////////////
 //
@@ -94,11 +103,65 @@ static bool is_module_code()
     if (ob_name.isNull())
         return false;
     const char *codename = Shiboken::String::toCString(ob_name);
-    return strcmp(codename, "<module>") == 0;
+    return std::strcmp(codename, "<module>") == 0;
 }
 
 } // extern "C"
 
+// Helper code for dynamically creating QMetaType's for @QEnum
+
+template <class UnderlyingInt>
+static void defaultCtr(const QtPrivate::QMetaTypeInterface *, void *addr)
+{
+    auto *i = reinterpret_cast<UnderlyingInt *>(addr);
+    *i = 0;
+}
+
+template <class UnderlyingInt>
+static void debugOp(const QtPrivate::QMetaTypeInterface *mti, QDebug &debug, const void *addr)
+{
+    const auto value = *reinterpret_cast<const UnderlyingInt *>(addr);
+    QDebugStateSaver saver(debug);
+    debug << mti->name << '(';
+    if constexpr (std::is_unsigned<UnderlyingInt>()) {
+        debug << Qt::showbase << Qt::hex;
+    } else {
+        if (value >= 0)
+            debug << Qt::showbase << Qt::hex;
+    }
+    debug << value << ')';
+}
+
+template <class UnderlyingInt>
+QMetaType createEnumMetaTypeHelper(const QByteArray &name)
+{
+    auto *mti = new QtPrivate::QMetaTypeInterface {
+        1,       // revision
+        ushort(std::alignment_of<UnderlyingInt>()),
+        sizeof(UnderlyingInt),
+        uint(QMetaType::fromType<UnderlyingInt>().flags() | QMetaType::IsEnumeration),
+        {},      // typeId
+        nullptr, // metaObjectFn
+        qstrdup(name.constData()),
+        defaultCtr<UnderlyingInt>,
+        nullptr, // copyCtr
+        nullptr, // moveCtr
+        nullptr, // dtor
+        QtPrivate::QEqualityOperatorForType<UnderlyingInt>::equals,
+        QtPrivate::QLessThanOperatorForType<UnderlyingInt>::lessThan,
+        debugOp<UnderlyingInt>,
+        nullptr, // dataStreamOut
+        nullptr, // dataStreamIn
+        nullptr  // legacyRegisterOp
+    };
+
+    QMetaType metaType(mti);
+
+    metaType.id(); // enforce registration
+    qCDebug(lcPySide, "libpyside: Registering @QEnum meta type \"%s\".", name.constData());
+    return metaType;
+}
+
 namespace PySide::QEnum {
 
 static std::map<int, PyObject *> enumCollector;
@@ -193,7 +256,125 @@ std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *containerType)
     return result;
 }
 
-} // namespace Shiboken::Enum
+QByteArray getTypeName(PyTypeObject *type)
+{
+    if (!Shiboken::Enum::checkType(type))
+        return {};
+
+    Shiboken::AutoDecRef qualName(PyObject_GetAttr(reinterpret_cast<PyObject *>(type),
+                                                   Shiboken::PyMagicName::qualname()));
+    QByteArray result = Shiboken::String::toCString(qualName.object());
+    result.replace(".", "::");
+
+    const auto metaType = QMetaType::fromName(result);
+    return metaType.isValid() && metaType.flags().testFlag(QMetaType::IsEnumeration)
+        ? result : QByteArray{};
+}
+
+using GenericEnumType = int;
+using GenericEnum64Type = unsigned long long;
+
+struct GenericEnumRegistry
+{
+    QList<PyTypeObject *> enumTypes;
+    QList<PyTypeObject *> enum64Types;
+};
+
+Q_GLOBAL_STATIC(GenericEnumRegistry, genericEnumTypeRegistry)
+
+} // namespace PySide::QEnum
+
+template <class IntType>
+static inline void genericEnumPythonToCppTpl(PyObject *pyIn, void *cppOut)
+{
+    const auto value = static_cast<IntType>(Shiboken::Enum::getValue(pyIn));
+    *reinterpret_cast<IntType *>(cppOut) = value;
+}
+
+template <class IntType>
+static inline PyObject *genericEnumCppToPythonTpl(PyTypeObject *pyType, const void *cppIn)
+{
+    const auto value = *reinterpret_cast<const IntType *>(cppIn);
+    return Shiboken::Enum::newItem(pyType, value);
+}
+
+extern "C"
+{
+
+// int
+static void genericEnumPythonToCpp(PyObject *pyIn, void *cppOut)
+{
+    genericEnumPythonToCppTpl<PySide::QEnum::GenericEnumType>(pyIn, cppOut);
+}
+
+static PythonToCppFunc isGenericEnumToCppConvertible(PyObject *pyIn)
+{
+
+    if (PySide::QEnum::genericEnumTypeRegistry()->enumTypes.contains(Py_TYPE(pyIn)))
+        return genericEnumPythonToCpp;
+    return {};
+}
+
+static PyObject *genericEnumCppToPython(PyTypeObject *pyType, const void *cppIn)
+{
+    return genericEnumCppToPythonTpl<PySide::QEnum::GenericEnumType>(pyType, cppIn);
+}
+
+// unsigned long long
+static void genericEnumPythonToCpp64(PyObject *pyIn, void *cppOut)
+{
+    genericEnumPythonToCppTpl<PySide::QEnum::GenericEnum64Type>(pyIn, cppOut);
+}
+
+static PythonToCppFunc isGenericEnumToCpp64Convertible(PyObject *pyIn)
+{
+
+    if (PySide::QEnum::genericEnumTypeRegistry()->enum64Types.contains(Py_TYPE(pyIn)))
+        return genericEnumPythonToCpp64;
+    return {};
+}
+
+static PyObject *genericEnumCpp64ToPython(PyTypeObject *pyType, const void *cppIn)
+{
+    return genericEnumCppToPythonTpl<PySide::QEnum::GenericEnum64Type>(pyType, cppIn);
+}
+
+} // extern "C"
+
+namespace PySide::QEnum
+{
+
+// int
+QMetaType createGenericEnumMetaType(const QByteArray &name, PyTypeObject *pyType)
+{
+    SbkConverter *converter = Shiboken::Conversions::createConverter(pyType,
+                                                                     genericEnumCppToPython);
+    Shiboken::Conversions::addPythonToCppValueConversion(converter,
+                                                         genericEnumPythonToCpp,
+                                                         isGenericEnumToCppConvertible);
+    Shiboken::Conversions::registerConverterName(converter, name.constData());
+    Shiboken::Enum::setTypeConverter(pyType, converter, nullptr);
+
+    genericEnumTypeRegistry->enumTypes.append(pyType);
+    return createEnumMetaTypeHelper<GenericEnumType>(name);
+}
+
+// "unsigned long long"
+QMetaType createGenericEnum64MetaType(const QByteArray &name, PyTypeObject *pyType)
+{
+    SbkConverter *converter = Shiboken::Conversions::createConverter(pyType,
+                                                                     genericEnumCpp64ToPython);
+    Shiboken::Conversions::addPythonToCppValueConversion(converter,
+                                                         genericEnumPythonToCpp64,
+                                                         isGenericEnumToCpp64Convertible);
+    Shiboken::Conversions::registerConverterName(converter, name.constData());
+    Shiboken::Enum::setTypeConverter(pyType, converter, nullptr);
+
+    genericEnumTypeRegistry()->enum64Types.append(pyType);
+    return createEnumMetaTypeHelper<GenericEnum64Type>(name);
+}
+
+} // namespace PySide::QEnum
 
 //
 ///////////////////////////////////////////////////////////////
index e97db07306e2790c430486fc63a9df79a6d3cd72..82e22e77353f874f014686813a3b429eb5218f23 100644 (file)
 
 #include <vector>
 
+#include <QtCore/qbytearray.h>
+
+QT_FORWARD_DECLARE_CLASS(QMetaType)
+
 namespace PySide::QEnum {
 
 // PYSIDE-957: Support the QEnum macro
@@ -18,6 +22,18 @@ PYSIDE_API int isFlag(PyObject *);
 PYSIDE_API std::vector<PyObject *> resolveDelayedQEnums(PyTypeObject *);
 PYSIDE_API void init();
 
+
+// PYSIDE-2840: For an enum registered in Qt, return the C++ name.
+// Ignore flags here; their underlying enums are of Python type flags anyways.
+PYSIDE_API QByteArray getTypeName(PyTypeObject *type);
+
+// Create a QMetaType for a decorated Python enum (int), enabling
+// modification of properties by Qt Widgets Designer.
+QMetaType createGenericEnumMetaType(const QByteArray &name, PyTypeObject *pyType);
+
+// Like createGenericEnumMetaType(), but for "unsigned long long".
+QMetaType createGenericEnum64MetaType(const QByteArray &name, PyTypeObject *pyType);
+
 } // namespace PySide::QEnum
 
 #endif
index f81c5039965253f8461b222aeed4c5a5c8ca89a1..41762b009bedc01ade6f2134a83556d2675ca901 100644 (file)
@@ -27,8 +27,10 @@ namespace PySide
 PYSIDE_API bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj,
                                  PyObject *kwds, bool allowErrors);
 
+/// Initialize the DynamicMetaObject helper for a wrapped Qt type (generated code)
 PYSIDE_API void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base,
                                       std::size_t cppObjSize);
+/// Initialize a Python-derived type
 PYSIDE_API void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject *kwds);
 
 /// Return the size in bytes of a type that inherits QObject.
index 93920dfeefbd5329ce02d4cee879e931d329f5a2..16a7c9c299671c9ac92588a8fdd741e0cd50cf14 100644 (file)
@@ -9,12 +9,14 @@
 #include "pysidestaticstrings.h"
 #include "qobjectconnect.h"
 #include "signalmanager.h"
+#include "pysideqenum.h"
 
 #include <autodecref.h>
 #include <helper.h>
 #include <pep384ext.h>
 #include <sbkconverter.h>
 #include <sbkenum.h>
+#include <sbkerrors.h>
 #include <sbkstaticstrings.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
@@ -97,7 +99,7 @@ static bool connection_Check(PyObject *o)
     static QByteArray typeName = QByteArrayLiteral("PySide")
         + QByteArray::number(QT_VERSION_MAJOR)
         + QByteArrayLiteral(".QtCore.QMetaObject.Connection");
-    return std::strcmp(o->ob_type->tp_name, typeName.constData()) == 0;
+    return std::strcmp(PepType_GetFullyQualifiedNameStr(Py_TYPE(o)), typeName.constData()) == 0;
 }
 
 static std::optional<QByteArrayList> parseArgumentNames(PyObject *argArguments)
@@ -438,17 +440,17 @@ static FunctionArgumentsResult extractFunctionArgumentsFromSlot(PyObject *slot)
         // Not retaining a reference inline with what PepFunction_GetName does.
         Py_DECREF(ret.functionName);
 
-        ret.objCode = reinterpret_cast<PepCodeObject *>(
-                    PyObject_GetAttr(ret.function, PySide::PySideMagicName::code()));
+        auto *obObjCode = PyObject_GetAttr(ret.function, PySide::PySideMagicName::code());
+        ret.objCode = reinterpret_cast<PepCodeObject *>(obObjCode);
         // Not retaining a reference inline with what PyFunction_GET_CODE does.
-        Py_XDECREF(ret.objCode);
+        Py_XDECREF(obObjCode);
 
         // Should not happen, but lets handle it gracefully, maybe Nuitka one day
         // makes these optional, or somebody defined a type named like it without
         // it being actually being that.
         if (ret.objCode == nullptr)
             ret.function = nullptr;
-    } else if (strcmp(Py_TYPE(slot)->tp_name, "compiled_function") == 0) {
+    } else if (std::strcmp(PepType_GetFullyQualifiedNameStr(Py_TYPE(slot)), "compiled_function") == 0) {
         ret.isMethod = false;
         ret.function = slot;
 
@@ -456,10 +458,10 @@ static FunctionArgumentsResult extractFunctionArgumentsFromSlot(PyObject *slot)
         // Not retaining a reference inline with what PepFunction_GetName does.
         Py_DECREF(ret.functionName);
 
-        ret.objCode = reinterpret_cast<PepCodeObject *>(
-                    PyObject_GetAttr(ret.function, PySide::PySideMagicName::code()));
+        auto *obObjCode = PyObject_GetAttr(ret.function, PySide::PySideMagicName::code());
+        ret.objCode = reinterpret_cast<PepCodeObject *>(obObjCode);
         // Not retaining a reference inline with what PyFunction_GET_CODE does.
-        Py_XDECREF(ret.objCode);
+        Py_XDECREF(obObjCode);
 
         // Should not happen, but lets handle it gracefully, maybe Nuitka one day
         // makes these optional, or somebody defined a type named like it without
@@ -539,7 +541,8 @@ static PyObject *signalInstanceConnect(PyObject *self, PyObject *args, PyObject
         return nullptr;
 
     Qt::ConnectionType connectionType = Qt::AutoConnection;
-    if (type != nullptr && qstrcmp(Py_TYPE(type)->tp_name, "ConnectionType") == 0) {
+    if (type != nullptr
+        && qstrcmp(PepType_GetFullyQualifiedNameStr(Py_TYPE(type)), "ConnectionType") == 0) {
         static SbkConverter *connectionTypeConv =
             Shiboken::Conversions::getConverter("Qt::ConnectionType");
         Q_ASSERT(connectionTypeConv);
@@ -666,13 +669,9 @@ static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key)
 static inline void warnDisconnectFailed(PyObject *aSlot, const QByteArray &signature)
 {
     if (PyErr_Occurred() != nullptr) { // avoid "%S" invoking str() when an error is set.
-        PyObject *exc{};
-        PyObject *inst{};
-        PyObject *tb{};
-        PyErr_Fetch(&exc, &inst, &tb);
+        Shiboken::Errors::Stash errorStash;
         PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%s) from signal \"%s\".",
                          Py_TYPE(aSlot)->tp_name, signature.constData());
-        PyErr_Restore(exc, inst, tb);
     } else {
         PyErr_WarnFormat(PyExc_RuntimeWarning, 0, "Failed to disconnect (%S) from signal \"%s\".",
                          aSlot, signature.constData());
@@ -908,23 +907,26 @@ static const char *SignalInstance_SignatureStrings[] = {
 
 void init(PyObject *module)
 {
-    if (InitSignatureStrings(PySideMetaSignal_TypeF(), MetaSignal_SignatureStrings) < 0)
+    auto *metaSignalType = PySideMetaSignal_TypeF();
+    if (InitSignatureStrings(metaSignalType, MetaSignal_SignatureStrings) < 0)
         return;
-    Py_INCREF(PySideMetaSignal_TypeF());
-    auto *obMetaSignal_Type = reinterpret_cast<PyObject *>(PySideMetaSignal_TypeF());
-    PyModule_AddObject(module, "MetaSignal", obMetaSignal_Type);
+    auto *obMetaSignalType = reinterpret_cast<PyObject *>(metaSignalType);
+    Py_INCREF(obMetaSignalType);
+    PepModule_AddType(module, metaSignalType);
 
-    if (InitSignatureStrings(PySideSignal_TypeF(), Signal_SignatureStrings) < 0)
+    auto *signalType = PySideSignal_TypeF();
+    if (InitSignatureStrings(signalType, Signal_SignatureStrings) < 0)
         return;
-    Py_INCREF(PySideSignal_TypeF());
-    auto *obSignal_Type = reinterpret_cast<PyObject *>(PySideSignal_TypeF());
-    PyModule_AddObject(module, "Signal", obSignal_Type);
+    auto *obSignalType = reinterpret_cast<PyObject *>(signalType);
+    Py_INCREF(obSignalType);
+    PepModule_AddType(module, signalType);
 
-    if (InitSignatureStrings(PySideSignalInstance_TypeF(), SignalInstance_SignatureStrings) < 0)
+    auto *signalInstanceType = PySideSignalInstance_TypeF();
+    if (InitSignatureStrings(signalInstanceType, SignalInstance_SignatureStrings) < 0)
         return;
-    Py_INCREF(PySideSignalInstance_TypeF());
-    auto *obSignalInstance_Type = reinterpret_cast<PyObject *>(PySideSignalInstance_TypeF());
-    PyModule_AddObject(module, "SignalInstance", obSignalInstance_Type);
+    auto *obSignalInstanceType = reinterpret_cast<PyObject *>(signalInstanceType);
+    Py_INCREF(obSignalInstanceType);
+    PepModule_AddType(module, signalInstanceType);
 }
 
 bool checkType(PyObject *pyObj)
@@ -988,23 +990,6 @@ void updateSourceObject(PyObject *source)
         return;
 }
 
-// PYSIDE-2840: For an enum registered in Qt, return the C++ name.
-// Ignore flags here; their underlying enums are of Python type flags anyways.
-static QByteArray getQtEnumTypeName(PyTypeObject *type)
-{
-    if (!Shiboken::Enum::checkType(type))
-        return {};
-
-    Shiboken::AutoDecRef qualName(PyObject_GetAttr(reinterpret_cast<PyObject *>(type),
-                                                   Shiboken::PyMagicName::qualname()));
-    QByteArray result = Shiboken::String::toCString(qualName.object());
-    result.replace(".", "::");
-
-    const auto metaType = QMetaType::fromName(result);
-    return metaType.isValid() && metaType.flags().testFlag(QMetaType::IsEnumeration)
-        ? result : QByteArray{};
-}
-
 QByteArray getTypeName(PyObject *obType)
 {
     if (PyType_Check(obType)) {
@@ -1024,7 +1009,7 @@ QByteArray getTypeName(PyObject *obType)
             return QByteArrayLiteral("QVariantList");
         if (type == &PyDict_Type)
             return QByteArrayLiteral("QVariantMap");
-        QByteArray enumName = getQtEnumTypeName(type);
+        QByteArray enumName = PySide::QEnum::getTypeName(type);
         return enumName.isEmpty() ? "PyObject"_ba : enumName;
     }
     if (obType == Py_None) // Must be checked before as Shiboken::String::check accepts Py_None
@@ -1278,7 +1263,7 @@ QByteArray getCallbackSignature(QMetaMethod signal, QObject *receiver,
             prefix += '(';
             for (int i = 0; i < mo->methodCount(); i++) {
                 QMetaMethod me = mo->method(i);
-                if ((strncmp(me.methodSignature(), prefix, prefix.size()) == 0) &&
+                if ((std::strncmp(me.methodSignature(), prefix, prefix.size()) == 0) &&
                     QMetaObject::checkConnectArgs(signal, me.methodSignature())) {
                     numArgs = me.parameterTypes().size() + useSelf;
                     break;
@@ -1298,7 +1283,7 @@ QByteArray getCallbackSignature(QMetaMethod signal, QObject *receiver,
             prefix += '(';
             for (int i = 0, count = mo->methodCount(); i < count; ++i) {
                 QMetaMethod me = mo->method(i);
-                if ((strncmp(me.methodSignature(), prefix, prefix.size()) == 0) &&
+                if ((std::strncmp(me.methodSignature(), prefix, prefix.size()) == 0) &&
                     QMetaObject::checkConnectArgs(signal, me)) {
                     numArgs = me.parameterTypes().size() + useSelf;
                     break;
index bc034c724a87d278a3d249cc74eee0e87cddb144..beb8a670be2e11afd62c1318b96c94db4043eead 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <autodecref.h>
 #include <basewrapper.h>
+#include <sbkpep.h>
 #include <sbkstaticstrings.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
@@ -171,17 +172,19 @@ DataList *dataListFromCapsule(PyObject *capsule)
 }
 
 static const char *Slot_SignatureStrings[] = {
-    "PySide6.QtCore.Slot(self,*types:type,name:str=nullptr,result:type=nullptr)",
+    "PySide6.QtCore.Slot(self,*types:typing.Union[type,str],name:str=nullptr,result:typing.Union[type,str]=nullptr)",
     "PySide6.QtCore.Slot.__call__(self,function:collections.abc.Callable[...,typing.Any])->typing.Any",
     nullptr}; // Sentinel
 
 void init(PyObject *module)
 {
-    if (InitSignatureStrings(PySideSlot_TypeF(), Slot_SignatureStrings) < 0)
+    auto *slotType = PySideSlot_TypeF();
+    if (InitSignatureStrings(slotType, Slot_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideSlot_TypeF());
-    PyModule_AddObject(module, "Slot", reinterpret_cast<PyObject *>(PySideSlot_TypeF()));
+    auto *obSlotType = reinterpret_cast<PyObject *>(slotType);
+    Py_INCREF(obSlotType);
+    PepModule_AddType(module, slotType);
 }
 
 } // namespace PySide::Slot
index 579e7f74c5f6d327f21026908e17427a1050e68e..db2f705e91ac748b78d6096ab3f7e5197a29c30e 100644 (file)
@@ -41,9 +41,9 @@ PYSIDE_API bool isCompiledMethod(PyObject *callback);
 
 struct debugPyTypeObject
 {
-    PYSIDE_API explicit debugPyTypeObject(const PyTypeObject *o) noexcept;
+    PYSIDE_API explicit debugPyTypeObject(PyTypeObject *o) noexcept;
 
-    const PyTypeObject *m_object;
+    PyTypeObject *m_object;
 };
 
 PYSIDE_API QDebug operator<<(QDebug debug, const debugPyTypeObject &o);
@@ -57,6 +57,7 @@ struct debugPyObject
 
 PYSIDE_API QDebug operator<<(QDebug debug, const debugPyObject &o);
 
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API >= 0x030B0000
 struct debugPyBuffer
 {
     PYSIDE_API explicit debugPyBuffer(Py_buffer *b) noexcept;
@@ -65,6 +66,7 @@ struct debugPyBuffer
 };
 
 PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b);
+#endif // !Py_LIMITED_API || >= 3.11
 
 } //namespace PySide
 
diff --git a/sources/pyside6/libpyside/pysidevariantutils.cpp b/sources/pyside6/libpyside/pysidevariantutils.cpp
new file mode 100644 (file)
index 0000000..5aaa363
--- /dev/null
@@ -0,0 +1,219 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidevariantutils.h"
+#include "pysideutils.h"
+
+#include <QtCore/qvariantmap.h>
+
+#include <autodecref.h>
+#include <sbkconverter.h>
+#include <sbkpep.h>
+#include <basewrapper.h>
+
+using namespace Qt::StringLiterals;
+
+static const char qVariantTypeName[] = "QVariant";
+
+static void warnConverter(const char *name)
+{
+    qWarning("Type converter for: %s not registered.", name);
+}
+
+// Helper converting each item of a non-empty list using the "QVariant" converter
+static std::optional<QVariantList> pyListToVariantListHelper(PyObject *list, Py_ssize_t size)
+{
+    Q_ASSERT(size > 0);
+    QVariantList result;
+    result.reserve(size);
+    Shiboken::Conversions::SpecificConverter converter(qVariantTypeName);
+    if (!converter) {
+        warnConverter(qVariantTypeName);
+        return std::nullopt;
+    }
+    for (Py_ssize_t i = 0; i < size; ++i) {
+        Shiboken::AutoDecRef pyItem(PySequence_GetItem(list, i));
+        QVariant item;
+        converter.toCpp(pyItem.object(), &item);
+        result.append(item);
+    }
+    return result;
+}
+
+// Helper checking for a sequence of Unicode objects
+static bool isStringList(PyObject *list)
+{
+    const Py_ssize_t size = PySequence_Size(list);
+    if (size == 0)
+        return false;
+    for (Py_ssize_t i = 0; i < size; ++i) {
+        Shiboken::AutoDecRef item(PySequence_GetItem(list, i));
+        if (PyUnicode_Check(item) == 0)
+            return false;
+    }
+    return true;
+}
+
+// Helper to convert to a QStringList
+static std::optional<QStringList> listToStringList(PyObject *list)
+{
+    static const char listType[] = "QList<QString>";
+    Shiboken::Conversions::SpecificConverter converter(listType);
+    if (!converter) {
+        warnConverter(listType);
+        return std::nullopt;
+    }
+    QStringList result;
+    converter.toCpp(list, &result);
+    return result;
+}
+
+// Helper to convert a non-empty, homogenous list using the converter of the first item
+static QVariant convertToValueList(PyObject *list)
+{
+    Q_ASSERT(PySequence_Size(list) >= 0);
+
+    Shiboken::AutoDecRef element(PySequence_GetItem(list, 0));
+
+    auto *type = reinterpret_cast<PyTypeObject *>(element.object());
+    QMetaType metaType = PySide::Variant::resolveMetaType(type);
+    if (!metaType.isValid())
+        return {};
+
+    const QByteArray listTypeName = QByteArrayLiteral("QList<") + metaType.name() + '>';
+    metaType = QMetaType::fromName(listTypeName);
+    if (!metaType.isValid())
+        return {};
+
+    Shiboken::Conversions::SpecificConverter converter(listTypeName);
+    if (!converter) {
+        warnConverter(listTypeName.constData());
+        return {};
+    }
+
+    QVariant var(metaType);
+    converter.toCpp(list, &var);
+    return var;
+}
+
+namespace PySide::Variant
+{
+
+QMetaType resolveMetaType(PyTypeObject *type)
+{
+    if (!PyObject_TypeCheck(reinterpret_cast<PyObject *>(type), SbkObjectType_TypeF()))
+        return {};
+    const char *typeName = Shiboken::ObjectType::getOriginalName(type);
+    if (!typeName)
+        return {};
+    const bool valueType = '*' != typeName[qstrlen(typeName) - 1];
+    // Do not convert user type of value
+    if (valueType && Shiboken::ObjectType::isUserType(type))
+        return {};
+    QMetaType metaType = QMetaType::fromName(typeName);
+    if (metaType.isValid())
+        return metaType;
+    // Do not resolve types to value type
+    if (valueType)
+        return {};
+    // Find in base types. First check tp_bases, and only after check tp_base, because
+    // tp_base does not always point to the first base class, but rather to the first
+    // that has added any python fields or slots to its object layout.
+    // See https://mail.python.org/pipermail/python-list/2009-January/520733.html
+    if (type->tp_bases) {
+        const auto size = PyTuple_Size(type->tp_bases);
+        Py_ssize_t i = 0;
+        // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject;
+        // it needs to use always QGraphicsItem as a QVariant type for
+        // QGraphicsItem::itemChange() to work.
+        if (qstrcmp(typeName, "QGraphicsObject*") == 0 && size > 1) {
+            auto *firstBaseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0));
+            if (SbkObjectType_Check(firstBaseType)) {
+                const char *firstBaseTypeName = Shiboken::ObjectType::getOriginalName(firstBaseType);
+                if (firstBaseTypeName != nullptr && qstrcmp(firstBaseTypeName, "QObject*") == 0)
+                    ++i;
+            }
+        }
+        for ( ; i < size; ++i) {
+            auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
+            const QMetaType derived = resolveMetaType(baseType);
+            if (derived.isValid())
+                return derived;
+        }
+        return {};
+    }
+    if (type->tp_base != nullptr)
+        return resolveMetaType(type->tp_base);
+    return {};
+}
+
+std::optional<QVariantList> pyListToVariantList(PyObject *list)
+{
+    if (list == nullptr || PySequence_Check(list) == 0)
+        return std::nullopt;
+    const auto size = PySequence_Size(list);
+    if (size < 0) { // Some infinite (I/O read) thing? - bail out
+        PyErr_Clear();
+        return std::nullopt;
+    }
+    if (size == 0)
+        return QVariantList{};
+    return pyListToVariantListHelper(list, size);
+}
+
+QVariant convertToVariantList(PyObject *list)
+{
+    const auto size = PySequence_Size(list);
+    if (size < 0) { // Some infinite (I/O read) thing? - bail out
+        PyErr_Clear();
+        return {};
+    }
+    if (size == 0)
+        return QVariantList{};
+
+    if (isStringList(list)) {
+        auto stringListO = listToStringList(list);
+        if (stringListO.has_value())
+            return {stringListO.value()};
+    }
+
+    if (QVariant valueList = convertToValueList(list); valueList.isValid())
+        return valueList;
+
+    if (auto vlO = pyListToVariantListHelper(list, size); vlO.has_value())
+        return vlO.value();
+
+    return {};
+}
+
+QVariant convertToVariantMap(PyObject *map)
+{
+    if (map == nullptr || PyDict_Check(map) == 0)
+        return {};
+
+    QVariantMap result;
+    if (PyDict_Size(map) == 0)
+        return result;
+
+    Py_ssize_t pos = 0;
+    Shiboken::AutoDecRef keys(PyDict_Keys(map));
+    if (!isStringList(keys))
+        return {};
+
+    Shiboken::Conversions::SpecificConverter converter(qVariantTypeName);
+    if (!converter) {
+        warnConverter(qVariantTypeName);
+        return {};
+    }
+
+    PyObject *key{};
+    PyObject *value{};
+    while (PyDict_Next(map, &pos, &key, &value)) {
+        QVariant cppValue;
+        converter.toCpp(value, &cppValue);
+        result.insert(PySide::pyUnicodeToQString(key), cppValue);
+    }
+    return result;
+}
+
+} // namespace PySide::Variant
diff --git a/sources/pyside6/libpyside/pysidevariantutils.h b/sources/pyside6/libpyside/pysidevariantutils.h
new file mode 100644 (file)
index 0000000..b53f7ce
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDEVARIANTUTILS_H
+#define PYSIDEVARIANTUTILS_H
+
+#include <sbkpython.h>
+
+#include <pysidemacros.h>
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qvariantlist.h>
+
+#include <optional>
+
+namespace PySide::Variant
+{
+
+/// Return a QMetaType for a PyTypeObject for purposes of
+/// converting to a QVariant.
+PYSIDE_API QMetaType resolveMetaType(PyTypeObject *type);
+
+/// Convert a heterogenous Python list to a QVariantList by converting each
+/// item using the QVariant converter.
+PYSIDE_API std::optional<QVariantList> pyListToVariantList(PyObject *list);
+
+/// Converts a list to a QVariant following the PySide semantics:
+/// - A list of strings is returned as QVariant<QStringList>
+/// - A list of convertible values is returned as QVariant<QList<Value>>
+/// - Remaining types are returned as QVariant(QVariantList)
+PYSIDE_API QVariant convertToVariantList(PyObject *list);
+
+/// Converts a map to a QVariantMap (string keys and QVariant values)
+PYSIDE_API QVariant convertToVariantMap(PyObject *map);
+} // namespace PySide::Variant
+
+#endif // PYSIDEVARIANTUTILS_H
index 3c862b3aaadd89853dee86133ec28fa4edc3a200..d953c6842ad233bb6c58ce21fe9ff1c0a0789076 100644 (file)
@@ -10,6 +10,7 @@
 #include "signalmanager.h"
 
 #include <sbkstring.h>
+#include <sbkpep.h>
 #include <sbkstaticstrings.h>
 #include "basewrapper.h"
 #include "autodecref.h"
@@ -211,8 +212,10 @@ QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *sign
     }
 
     QMetaObject::Connection connection{};
+    const bool connectByIndex = !receiver.forceDynamicSlot
+                                && receiver.receiver != nullptr && receiver.slotIndex != -1;
     Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify()
-    if (!receiver.forceDynamicSlot && receiver.receiver != nullptr && receiver.slotIndex != -1) {
+    if (connectByIndex) {
         connection = QMetaObject::connect(source, signalIndex,
                                           receiver.receiver, receiver.slotIndex, type);
     } else {
@@ -233,7 +236,8 @@ QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *sign
     if (!connection)
         return {};
 
-    registerSlotConnection(source, signalIndex, callback, connection);
+    if (!connectByIndex)
+        registerSlotConnection(source, signalIndex, callback, connection);
 
     static_cast<FriendlyQObject *>(source)->connectNotify(signalMethod);
     return connection;
index a7bbcdf4ae64620b17c6c64ddd96d20d26387fd6..d8fd41188bac09eada339b76ca1f73a6363bec2b 100644 (file)
@@ -15,6 +15,7 @@
 #include <bindingmanager.h>
 #include <gilstate.h>
 #include <sbkconverter.h>
+#include <sbkpep.h>
 #include <sbkstring.h>
 #include <sbkstaticstrings.h>
 #include <sbkerrors.h>
@@ -37,7 +38,11 @@ using namespace Qt::StringLiterals;
 #error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value!
 #endif
 
-static PyObject *metaObjectAttr = nullptr;
+PyObject *metaObjectAttr()
+{
+    static PyObject *const s = Shiboken::String::createStaticString("__METAOBJECT__");
+    return s;
+}
 
 static int pyObjectWrapperMetaTypeId = QMetaType::UnknownType;
 
@@ -251,7 +256,7 @@ PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj)
     debug << '<';
     if (PyObject *ob = myObj) {
         const auto refs = Py_REFCNT(ob);
-        debug << Py_TYPE(ob)->tp_name << " at " << ob;
+        debug << PepType_GetFullyQualifiedNameStr(Py_TYPE(ob)) << " at " << ob;
         if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT
             debug << ", immortal";
         else
@@ -308,9 +313,6 @@ void SignalManager::init()
     Shiboken::Conversions::registerConverterName(converter, "object");
     Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper");
     Shiboken::Conversions::registerConverterName(converter, "PySide::PyObjectWrapper");
-
-    if (!metaObjectAttr)
-        metaObjectAttr = Shiboken::String::fromCString("__METAOBJECT__");
 }
 
 void SignalManager::setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler)
@@ -352,6 +354,18 @@ void SignalManager::handleMetaCallError()
     Py_SetRecursionLimit(reclimit);
 }
 
+const char *metaObjectCallName(QMetaObject::Call call)
+{
+    static const char *names[] = {
+        "InvokeMetaMethod", "ReadProperty", "WriteProperty", "ResetProperty",
+        "CreateInstance", "IndexOfMethod", "RegisterPropertyMetaType",
+        "RegisterMethodArgumentMetaType", "BindableProperty", "CustomCall",
+        "ConstructInPlace"};
+    constexpr size_t count = sizeof(names)/sizeof(names[0]);
+    static_assert(QMetaObject::ConstructInPlace == count - 1);
+    return call >= 0 && call < count ? names[call] : "<unknown>";
+}
+
 // Handler for QMetaObject::ReadProperty/WriteProperty/ResetProperty:
 int SignalManagerPrivate::qtPropertyMetacall(QObject *object,
                                              QMetaObject::Call call,
@@ -384,25 +398,20 @@ int SignalManagerPrivate::qtPropertyMetacall(QObject *object,
     if (PyErr_Occurred()) {
         // PYSIDE-2160: An unknown type was reported. Indicated by StopIteration.
         if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
-            PyObject *excType{};
-            PyObject *excValue{};
-            PyObject *excTraceback{};
-            PyErr_Fetch(&excType, &excValue, &excTraceback);
+            Shiboken::Errors::Stash errorStash;
             bool ign = call == QMetaObject::WriteProperty;
             PyErr_WarnFormat(PyExc_RuntimeWarning, 0,
                 ign ? "Unknown property type '%s' of QObject '%s' used in fset"
                     : "Unknown property type '%s' of QObject '%s' used in fget with %R",
-                pp->d->typeName.constData(), metaObject->className(), excValue);
+                pp->d->typeName.constData(), metaObject->className(), errorStash.getException());
             if (PyErr_Occurred())
                 Shiboken::Errors::storeErrorOrPrint();
-            Py_DECREF(excType);
-            Py_DECREF(excValue);
-            Py_XDECREF(excTraceback);
+            errorStash.release();
             return result;
         }
 
         qWarning().noquote().nospace()
-            << "An error occurred executing the property metacall " << call
+            << "An error occurred executing the property metacall " << metaObjectCallName(call)
             << " on property \"" << mp.name() << "\" of " << object;
         handleMetaCallError(object, &result);
     }
@@ -519,7 +528,7 @@ static int callPythonMetaMethodHelper(const QByteArrayList &paramTypes,
     }
 
     QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter;
-    if (isNonVoidReturn(returnType)) {
+    if (args[0] != nullptr && isNonVoidReturn(returnType)) {
         retConverter.reset(new Shiboken::Conversions::SpecificConverter(returnType));
         if (!retConverter->isValid())
             return CallResult::CallReturnValueError;
@@ -612,13 +621,13 @@ static MetaObjectBuilder *metaBuilderFromDict(PyObject *dict)
     // no GIL.
     // Note that "SignalManager::registerMetaMethodGetIndex" has write actions
     // that might involve the interpreter, but in that context the GIL is held.
-    if (!dict || !PyDict_Contains(dict, metaObjectAttr))
+    if (!dict || !PyDict_Contains(dict, metaObjectAttr()))
         return nullptr;
 
     // PYSIDE-813: The above assumption is not true in debug mode:
     // PyDict_GetItem would touch PyThreadState_GET and the global error state.
     // PyDict_GetItemWithError instead can work without GIL.
-    PyObject *pyBuilder = PyDict_GetItemWithError(dict, metaObjectAttr);
+    PyObject *pyBuilder = PyDict_GetItemWithError(dict, metaObjectAttr());
     return reinterpret_cast<MetaObjectBuilder *>(PyCapsule_GetPointer(pyBuilder, nullptr));
 }
 
@@ -692,7 +701,7 @@ static int addMetaMethod(QObject *source, const QByteArray &signature,
     if (dmo == nullptr) {
         dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject);
         PyObject *pyDmo = PyCapsule_New(dmo, nullptr, destroyMetaObject);
-        PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo);
+        PyObject_SetAttr(pySelf, metaObjectAttr(), pyDmo);
         Py_DECREF(pyDmo);
     }
 
@@ -742,6 +751,12 @@ int SignalManager::registerMetaMethodGetIndexBA(QObject* source, const QByteArra
 
 const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self)
 {
+#ifdef Py_GIL_DISABLED
+    // PYSIDE-2221: When working with disable-gil, it seems to be necessary
+    //              to hold the GIL. Maybe that is harmless here (check later).
+    // Thanks to Sam Gross who fixed most errors by pointing this out.
+    Shiboken::GilState gil;
+#endif
     // PYSIDE-803: Avoid the GIL in SignalManager::retrieveMetaObject
     // This function had the GIL. We do not use the GIL unless we have to.
     // metaBuilderFromDict accesses a Python dict, but in that context there
index 77a405fbe52a99637cdf0244f65aae2783685e63..1430334c81a536a4d66e9c44acba9a2baea5cc1e 100644 (file)
@@ -45,7 +45,6 @@ add_library(PySide6::pyside6qml ALIAS pyside6qml)
 
 target_include_directories(pyside6qml PUBLIC
   $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
-  $<INSTALL_INTERFACE:include/PySide6Qml>
 )
 
 target_compile_definitions(pyside6qml PRIVATE -DQT_LEAN_HEADERS=1 -DQT_NO_KEYWORDS=1)
@@ -84,34 +83,21 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_T
 
 qfp_strip_library("pyside6qml")
 
-# Install-tree / relocatable package config file.
-configure_package_config_file(
-    "${CMAKE_CURRENT_SOURCE_DIR}/PySide6QmlConfig-spec.cmake.in"
-    "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6QmlConfig${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake"
-     INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml"
-)
-
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6QmlConfig.cmake.in"
-               "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfig.cmake" @ONLY)
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/PySide6QmlConfigVersion.cmake.in"
-               "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfigVersion.cmake" @ONLY)
-
 install(FILES ${libpysideqml_HEADERS}
-        DESTINATION include/${BINDING_NAME}${pyside6qml_SUFFIX})
+        DESTINATION ${BINDING_NAME}${pyside6qml_SUFFIX}/include)
 
-install(TARGETS pyside6qml EXPORT PySide6QmlTargets
+#built-time installation
+install(TARGETS pyside6qml EXPORT PySide6Targets
                            LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
                            ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
                            RUNTIME DESTINATION bin)
-install(EXPORT PySide6QmlTargets NAMESPACE PySide6Qml::
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml")
 
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfig.cmake"
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml")
-
-install(FILES
-        "${CMAKE_CURRENT_BINARY_DIR}/install/PySide6QmlConfig${SHIBOKEN_PYTHON_CONFIG_SUFFIX}.cmake"
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml")
-
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PySide6QmlConfigVersion.cmake"
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/PySide6Qml")
+# install-tree or relocatable package installation
+if(NOT is_pyside6_superproject_build)
+    set_target_properties(pyside6qml PROPERTIES
+                        VERSION ${PYSIDE_SO_VERSION})
+    install(TARGETS pyside6qml EXPORT PySide6WheelTargets
+                            LIBRARY DESTINATION "PySide6"
+                            ARCHIVE DESTINATION "PySide6"
+                            RUNTIME DESTINATION "PySide6")
+endif()
diff --git a/sources/pyside6/libpysideqml/PySide6QmlConfig-spec.cmake.in b/sources/pyside6/libpysideqml/PySide6QmlConfig-spec.cmake.in
deleted file mode 100644 (file)
index 36eb412..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-@PACKAGE_INIT@
-
-# Import targets only when using an installed PySide6 config file (so not during a regular
-# PySide6 build, or during a super project build).
-if (NOT TARGET PySide6::pyside6qml)
-    include("${CMAKE_CURRENT_LIST_DIR}/PySide6QmlTargets.cmake")
-endif()
diff --git a/sources/pyside6/libpysideqml/PySide6QmlConfig.cmake.in b/sources/pyside6/libpysideqml/PySide6QmlConfig.cmake.in
deleted file mode 100644 (file)
index dab0a6b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-if (NOT PYTHON_CONFIG_SUFFIX)
-    message(STATUS "PySide6QmlConfig: Using default python: @SHIBOKEN_PYTHON_CONFIG_SUFFIX@")
-    SET(PYTHON_CONFIG_SUFFIX @SHIBOKEN_PYTHON_CONFIG_SUFFIX@)
-endif()
-include(${CMAKE_CURRENT_LIST_DIR}/PySide6QmlConfig${PYTHON_CONFIG_SUFFIX}.cmake)
diff --git a/sources/pyside6/libpysideqml/PySide6QmlConfigVersion.cmake.in b/sources/pyside6/libpysideqml/PySide6QmlConfigVersion.cmake.in
deleted file mode 100644 (file)
index f5073ce..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-set(PACKAGE_VERSION @BINDING_API_VERSION@)
-
-if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" )
-   set(PACKAGE_VERSION_COMPATIBLE FALSE)
-else("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" )
-   set(PACKAGE_VERSION_COMPATIBLE TRUE)
-   if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}")
-      set(PACKAGE_VERSION_EXACT TRUE)
-   endif( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}")
-endif("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" )
index 41d7dee97bfb2ff9f4908b05865186a761353261..e4e9c16dd0fb8900736bd3bf436564fba35037eb 100644 (file)
@@ -13,6 +13,7 @@
 #include <autodecref.h>
 #include <gilstate.h>
 #include <sbkconverter.h>
+#include <sbkpep.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
 #include <signature.h>
@@ -59,7 +60,7 @@ static PyTypeObject *createPySideQmlAttachedType()
         PySide::ClassDecorator::Methods<PySideQmlAttachedPrivate>::typeSlots();
 
     PyType_Spec PySideQmlAttachedType_spec = {
-        "2:PySide6.QtCore.qmlAttached",
+        "2:PySide6.QtCore.QmlAttached",
         sizeof(PySideClassDecorator),
         0,
         Py_TPFLAGS_DEFAULT,
@@ -107,7 +108,7 @@ static QObject *attachedFactoryHelper(PyTypeObject *attachingType, QObject *o)
 
     if (PyType_IsSubtype(pyResult->ob_type, qObjectType()) == 0) {
         qWarning("QmlAttached: Attached objects must inherit QObject, got %s.",
-                 pyResult->ob_type->tp_name);
+                 PepType_GetFullyQualifiedNameStr(Py_TYPE(pyResult)));
         return nullptr;
     }
 
@@ -166,12 +167,13 @@ void initQmlAttached(PyObject *module)
     std::fill(attachingTypes, attachingTypes + MAX_ATTACHING_TYPES, nullptr);
     AttachedFactoryInitializer<MAX_ATTACHING_TYPES - 1>::init();
 
-    if (InitSignatureStrings(PySideQmlAttached_TypeF(), qmlAttached_SignatureStrings) < 0)
+    auto *qmlAttachedType = PySideQmlAttached_TypeF();
+    if (InitSignatureStrings(qmlAttachedType, qmlAttached_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideQmlAttached_TypeF());
-    PyModule_AddObject(module, "QmlAttached",
-                       reinterpret_cast<PyObject *>(PySideQmlAttached_TypeF()));
+    auto *obQmlAttachedType = reinterpret_cast<PyObject *>(qmlAttachedType);
+    Py_INCREF(obQmlAttachedType);
+    PepModule_AddType(module, qmlAttachedType);
 }
 
 PySide::Qml::QmlExtensionInfo qmlAttachedInfo(PyTypeObject *t,
@@ -181,7 +183,7 @@ PySide::Qml::QmlExtensionInfo qmlAttachedInfo(PyTypeObject *t,
     if (!info || info->attachedType == nullptr)
         return result;
 
-    const auto *name = reinterpret_cast<PyTypeObject *>(t)->tp_name;
+    const auto *name = PepType_GetFullyQualifiedNameStr(reinterpret_cast<PyTypeObject *>(t));
     if (nextAttachingType >= MAX_ATTACHING_TYPES) {
         qWarning("Unable to initialize attached type \"%s\": "
                  "The limit %d of  attached types has been reached.",
@@ -208,7 +210,8 @@ QObject *qmlAttachedPropertiesObject(PyObject *typeObject, QObject *obj, bool cr
     auto *end = attachingTypes + nextAttachingType;
     auto *typePtr = std::find(attachingTypes, end, type);
     if (typePtr == end) {
-        qWarning("%s: Attaching type \"%s\" not found.", __FUNCTION__, type->tp_name);
+        qWarning("%s: Attaching type \"%s\" not found.", __FUNCTION__,
+                 PepType_GetFullyQualifiedNameStr(type));
         return nullptr;
     }
 
index e2a96b60ce9007d680d2ba690cd6d7a1bd57db24..f26fb9f89c59a750f4f4824551957caedccfd645 100644 (file)
@@ -11,6 +11,7 @@
 #include <autodecref.h>
 #include <gilstate.h>
 #include <sbkconverter.h>
+#include <sbkpep.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
 #include <signature.h>
@@ -55,7 +56,7 @@ static PyTypeObject *createPySideQmlExtendedType()
         PySide::ClassDecorator::Methods<PySideQmlExtendedPrivate>::typeSlots();
 
     PyType_Spec PySideQmlExtendedType_spec = {
-        "2:PySide6.QtCore.qmlExtended",
+        "2:PySide6.QtCore.QmlExtended",
         sizeof(PySideClassDecorator),
         0,
         Py_TPFLAGS_DEFAULT,
@@ -95,7 +96,8 @@ static QObject *extensionFactory(QObject *o)
     auto *pyObjType = Py_TYPE(pyObj);
     const auto info = qmlTypeInfo(reinterpret_cast<PyObject *>(pyObjType));
     if (!info || info->extensionType == nullptr) {
-        qWarning("QmlExtended: Cannot find extension of %s.", pyObjType->tp_name);
+        qWarning("QmlExtended: Cannot find extension of %s.",
+                 PepType_GetFullyQualifiedNameStr(pyObjType));
         return nullptr;
     }
 
@@ -110,7 +112,7 @@ static QObject *extensionFactory(QObject *o)
 
     if (PyType_IsSubtype(pyResult->ob_type, qObjectType()) == 0) {
         qWarning("QmlExtended: Extension objects must inherit QObject, got %s.",
-                 pyResult->ob_type->tp_name);
+                 PepType_GetFullyQualifiedNameStr(pyResult->ob_type));
         return nullptr;
     }
 
@@ -121,12 +123,13 @@ static QObject *extensionFactory(QObject *o)
 
 void initQmlExtended(PyObject *module)
 {
-    if (InitSignatureStrings(PySideQmlExtended_TypeF(), qmlExtended_SignatureStrings) < 0)
+    auto *qmlExtendedType = PySideQmlExtended_TypeF();
+    if (InitSignatureStrings(qmlExtendedType, qmlExtended_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideQmlExtended_TypeF());
-    PyModule_AddObject(module, "QmlExtended",
-                       reinterpret_cast<PyObject *>(PySideQmlExtended_TypeF()));
+    auto *obQmlExtendedType = reinterpret_cast<PyObject *>(qmlExtendedType);
+    Py_INCREF(obQmlExtendedType);
+    PepModule_AddType(module, qmlExtendedType);
 }
 
 PySide::Qml::QmlExtensionInfo qmlExtendedInfo(PyObject *t,
@@ -139,7 +142,7 @@ PySide::Qml::QmlExtensionInfo qmlExtendedInfo(PyObject *t,
             result.factory = extensionFactory;
         } else {
             qWarning("Unable to retrieve meta object for %s",
-                     reinterpret_cast<PyTypeObject *>(t)->tp_name);
+                     PepType_GetFullyQualifiedNameStr(reinterpret_cast<PyTypeObject *>(t)));
         }
     }
     return result;
index ef8d7fdf0a67ba2b42295d2000cb65c9f29e4975..e56eb1b2ad64d0ef6274a7cac2e6c377a6caccd5 100644 (file)
@@ -57,7 +57,7 @@ static PyTypeObject *createPySideQmlForeignType()
         PySide::ClassDecorator::Methods<PySideQmlForeignPrivate>::typeSlots();
 
     PyType_Spec PySideQmlForeignType_spec = {
-        "2:PySide6.QtCore.qmlForeign",
+        "2:PySide6.QtCore.QmlForeign",
         sizeof(PySideClassDecorator),
         0,
         Py_TPFLAGS_DEFAULT,
@@ -83,12 +83,13 @@ namespace PySide::Qml {
 
 void initQmlForeign(PyObject *module)
 {
-    if (InitSignatureStrings(PySideQmlForeign_TypeF(), qmlForeign_SignatureStrings) < 0)
+    auto *foreignType = PySideQmlForeign_TypeF();
+    if (InitSignatureStrings(foreignType, qmlForeign_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideQmlForeign_TypeF());
-    PyModule_AddObject(module, "QmlForeign",
-                       reinterpret_cast<PyObject *>(PySideQmlForeign_TypeF()));
+    auto *obForeignType = reinterpret_cast<PyObject *>(foreignType);
+    Py_INCREF(obForeignType);
+    PepModule_AddType(module, foreignType);
 }
 
 } // namespace PySide::Qml
index 5011fd613a74f7e1aa42b143db29f4c316fad7f5..a48a3d4de013a5738db4548949b8f655612ad851 100644 (file)
@@ -9,6 +9,7 @@
 #include <pep384ext.h>
 #include <sbkconverter.h>
 #include <signature.h>
+#include <sbkpep.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
 
@@ -306,9 +307,10 @@ void initQtQmlListProperty(PyObject *module)
     // Register QQmlListProperty metatype for use in QML
     qRegisterMetaType<QQmlListProperty<QObject>>();
 
-    Py_INCREF(reinterpret_cast<PyObject *>(PropertyList_TypeF()));
-    PyModule_AddObject(module, PepType_GetNameStr(PropertyList_TypeF()),
-                       reinterpret_cast<PyObject *>(PropertyList_TypeF()));
+    auto *propertyListType = PropertyList_TypeF();
+    auto *obPropertyListType = reinterpret_cast<PyObject *>(propertyListType);
+    Py_INCREF(obPropertyListType);
+    PepModule_AddType(module, propertyListType);
 }
 
 } // namespace PySide::Qml
index a3d2664c4eef0f9484a0fce91d1d54799f60ac80..4e0afa3b28a1d285d298faffc8468fdd2e7d6889 100644 (file)
@@ -5,6 +5,7 @@
 
 #include <sbkpython.h>
 #include <sbkstring.h>
+#include <sbkerrors.h>
 #include <autodecref.h>
 
 // Remove deprecated MACRO of copysign for MSVC #86286
@@ -40,17 +41,17 @@ std::optional<int> qmlMetaCallErrorHandler(QObject *object)
     if (engine->currentStackFrame == nullptr)
         return {};
 
-    PyObject *errType{};
-    PyObject *errValue{};
-    PyObject *errTraceback{};
-    PyErr_Fetch(&errType, &errValue, &errTraceback);
+    Shiboken::Errors::Stash errorStash;
+    PyObject *errValue = errorStash.getException();
     // PYSIDE-464: The error is only valid before PyErr_Restore,
     // PYSIDE-464: therefore we take local copies.
     Shiboken::AutoDecRef objStr(PyObject_Str(errValue));
     const QString errString = QString::fromUtf8(Shiboken::String::toCString(objStr));
-    const bool isSyntaxError = errType == PyExc_SyntaxError;
-    const bool isTypeError = errType == PyExc_TypeError;
-    PyErr_Restore(errType, errValue, errTraceback);
+    const bool isSyntaxError = errValue != nullptr
+                               && PyErr_GivenExceptionMatches(errValue, PyExc_SyntaxError);
+    const bool isTypeError = errValue != nullptr
+                             && PyErr_GivenExceptionMatches(errValue, PyExc_TypeError);
+    errorStash.restore();
 
     PyErr_Print();    // Note: PyErr_Print clears the error.
 
index a0c05b384b710a77387d0f6714a833d2b2ae975c..3178f433909ddd2b7f3633dd763a526741e32cf6 100644 (file)
@@ -41,7 +41,7 @@ PyTypeObject *createPySideQmlNamedElementType(void)
         PySide::ClassDecorator::Methods<PySideQmlNamedElementPrivate>::typeSlots();
 
     PyType_Spec PySideQmlNamedElementType_spec = {
-        "2:PySide6.QtCore.qmlNamedElement",
+        "2:PySide6.QtCore.QmlNamedElement",
         sizeof(PySideClassDecorator),
         0,
         Py_TPFLAGS_DEFAULT,
@@ -65,10 +65,11 @@ static const char *qmlNamedElement_SignatureStrings[] = {
 
 void initQmlNamedElement(PyObject *module)
 {
-    if (InitSignatureStrings(PySideQmlNamedElement_TypeF(), qmlNamedElement_SignatureStrings) < 0)
+    auto *qmlNamedElementType = PySideQmlNamedElement_TypeF();
+    if (InitSignatureStrings(qmlNamedElementType, qmlNamedElement_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideQmlNamedElement_TypeF());
-    PyModule_AddObject(module, "QmlNamedElement",
-                       reinterpret_cast<PyObject *>(PySideQmlNamedElement_TypeF()));
+    auto *obQmlNamedElementType = reinterpret_cast<PyObject *>(qmlNamedElementType);
+    Py_INCREF(obQmlNamedElementType);
+    PepModule_AddType(module, qmlNamedElementType);
 }
index b64a72f8ef5565b6904dd02aa19ddf622b5044df..d654813147eb9dcbc237036eafb911ab4fd99d67 100644 (file)
@@ -14,6 +14,7 @@
 // shiboken
 #include <autodecref.h>
 #include <sbkconverter.h>
+#include <sbkpep.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
 
@@ -98,8 +99,11 @@ static inline bool isQmlParserStatus(const QMetaObject *o)
 
 static QByteArray getGlobalString(const char *name)
 {
-    PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name);
+    Shiboken::AutoDecRef globals(PepEval_GetFrameGlobals());
+    if (globals.isNull())
+        return {};
 
+    PyObject *globalVar = PyDict_GetItemString(globals, name);
     if (globalVar == nullptr || PyUnicode_Check(globalVar) == 0)
         return {};
 
@@ -109,8 +113,11 @@ static QByteArray getGlobalString(const char *name)
 
 static int getGlobalInt(const char *name)
 {
-    PyObject *globalVar = PyDict_GetItemString(PyEval_GetGlobals(), name);
+    Shiboken::AutoDecRef globals(PepEval_GetFrameGlobals());
+    if (globals.isNull())
+        return -1;
 
+    PyObject *globalVar = PyDict_GetItemString(globals, name);
     if (globalVar == nullptr || PyLong_Check(globalVar) == 0)
         return -1;
 
@@ -208,7 +215,7 @@ static int qmlRegisterType(PyObject *pyObj,
     // there's no way to unregister a QML type.
     Py_INCREF(pyObj);
 
-    const QByteArray typeName(pyObjType->tp_name);
+    const QByteArray typeName(PepType_GetFullyQualifiedNameStr(pyObjType));
     QByteArray ptrType = typeName + '*';
     QByteArray listType = QByteArrayLiteral("QQmlListProperty<") + typeName + '>';
     const auto typeId = QMetaType(new QQmlMetaTypeInterface(ptrType));
@@ -651,8 +658,10 @@ static std::optional<SingletonQObjectCreation>
     Shiboken::AutoDecRef tpDict(PepType_GetDict(pyObjType));
     auto *create = PyDict_GetItemString(tpDict.object(), "create");
     // Method decorated by "@staticmethod"
-    if (create == nullptr || std::strcmp(Py_TYPE(create)->tp_name, "staticmethod") != 0)
+    if (create == nullptr
+        || std::strcmp(PepType_GetFullyQualifiedNameStr(Py_TYPE(create)), "staticmethod") != 0) {
         return std::nullopt;
+    }
     // 3.10: "__wrapped__"
     Shiboken::AutoDecRef function(PyObject_GetAttrString(create, "__func__"));
     if (function.isNull()) {
index d1d56efacd82ff8aa392bbf62f2c7bf0e17a1e56..97ebf2206e92e2efbc3cd0dfece1455f132e6971 100644 (file)
@@ -6,6 +6,8 @@
 #include <QtCore/qdebug.h>
 #include <QtCore/qhash.h>
 
+#include <sbkpep.h>
+
 #include <algorithm>
 
 namespace PySide::Qml {
@@ -43,11 +45,11 @@ QDebug operator<<(QDebug d, const QmlTypeInfo &i)
     d.nospace();
     d << "QmlTypeInfo(" << i.flags;
     if (i.foreignType)
-        d << ", foreignType=" << i.foreignType->tp_name;
+        d << ", foreignType=" << PepType_GetFullyQualifiedNameStr(i.foreignType);
     if (i.attachedType)
-        d << ", attachedType=" << i.attachedType->tp_name;
+        d << ", attachedType=" << PepType_GetFullyQualifiedNameStr(i.attachedType);
     if (i.extensionType)
-        d << ", extensionType=" << i.extensionType->tp_name;
+        d << ", extensionType=" << PepType_GetFullyQualifiedNameStr(i.extensionType);
     d << ')';
     return d;
 }
index 348d53d58021b6c5ae776938fde7c695c2315759..2397fa61c21258c8ddc66870e73d9953f5a7339e 100644 (file)
@@ -72,7 +72,7 @@ PyTypeObject *createPySideQmlUncreatableType(void)
         PySide::ClassDecorator::Methods<PySideQmlUncreatablePrivate>::typeSlots();
 
     PyType_Spec PySideQmlUncreatableType_spec = {
-        "2:PySide6.QtCore.qmlUncreatable",
+        "2:PySide6.QtCore.QmlUncreatable",
         sizeof(PySideClassDecorator),
         0,
         Py_TPFLAGS_DEFAULT,
@@ -96,12 +96,13 @@ static const char *qmlUncreatable_SignatureStrings[] = {
 
 void initQmlUncreatable(PyObject *module)
 {
-    if (InitSignatureStrings(PySideQmlUncreatable_TypeF(), qmlUncreatable_SignatureStrings) < 0)
+    auto *qmlUncreatableType = PySideQmlUncreatable_TypeF();
+    if (InitSignatureStrings(qmlUncreatableType, qmlUncreatable_SignatureStrings) < 0)
         return;
 
-    Py_INCREF(PySideQmlUncreatable_TypeF());
-    PyModule_AddObject(module, "QmlUncreatable",
-                       reinterpret_cast<PyObject *>(PySideQmlUncreatable_TypeF()));
+    auto *obQmlUncreatableType = reinterpret_cast<PyObject *>(qmlUncreatableType);
+    Py_INCREF(obQmlUncreatableType);
+    PepModule_AddType(module, qmlUncreatableType);
 }
 
 void setUncreatableClassInfo(PyTypeObject *type, const QByteArray &reason)
index 4669fb677075086121475d9aa6518290c2e03427..caaadc241d95033a36028329123dc7a3f203ea6b 100644 (file)
@@ -80,7 +80,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_T
 #
 
 install(FILES ${libpysideremoteobjects_HEADERS}
-        DESTINATION include/${BINDING_NAME}${pyside6remoteobjects_SUFFIX})
+        DESTINATION ${BINDING_NAME}${pyside6remoteobjects_SUFFIX}/include)
 
 install(TARGETS pyside6remoteobjects EXPORT PySide6RemoteObjectsTargets
                            LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
index d5a5454f048c1db6dbf3cab72ba7f10999d70e68..aad440daf6987d69820e4ea51c19139d38b27d00 100644 (file)
@@ -3,6 +3,9 @@
 
 #include "pysidecapsulemethod_p.h"
 
+#include <string.h>
+#include <sbkpep.h>
+
 extern "C"
 {
 
index 941e38c6e162d0379966a08eed545e2be70fd70f..8f685713c3ed14cf17bfab11f2c9c87973109dff 100644 (file)
@@ -10,6 +10,7 @@
 #include "pysiderephandler_p.h"
 
 #include <basewrapper.h>
+#include <sbkpep.h>
 #include <sbkconverter.h>
 #include <sbkstring.h>
 
@@ -25,6 +26,9 @@
 #include <QtRemoteObjects/qremoteobjectpendingcall.h>
 #include <QtRemoteObjects/qremoteobjectreplica.h>
 
+#include <cstring>
+#include <cctype>
+
 using namespace Shiboken;
 
 class FriendlyReplica : public QRemoteObjectReplica
@@ -167,7 +171,7 @@ struct SourceDefs
                 auto name = callData->name.sliced(4);
                 auto index = metaObject->indexOfProperty(name.constData());
                 if (index < 0) {
-                    name[0] = tolower(name[0]);  // Try lower case
+                    name[0] = std::tolower(name[0]);  // Try lower case
                     index = metaObject->indexOfProperty(name.constData());
                 }
                 // It is possible a .rep names a Slot "push" or "pushSomething" that
@@ -232,7 +236,7 @@ struct ReplicaDefs
             PyObject *name = nullptr;
             static PyTypeObject *nodeType = Shiboken::Conversions::getPythonTypeObject("QRemoteObjectNode");
             if (!PyArg_UnpackTuple(args, "Replica.__init__", 2, 3, &node, &constructorType, &name) ||
-                !PySide::inherits(Py_TYPE(node), nodeType->tp_name)) {
+                !PySide::inherits(Py_TYPE(node), PepType_GetFullyQualifiedNameStr(nodeType))) {
                 PyErr_SetString(PyExc_TypeError,
                                 "Replicas can be initialized with no arguments or by node.acquire only");
                 return -1;
@@ -378,7 +382,7 @@ PyTypeObject *createDynamicClassImpl(QMetaObject *meta)
 
     auto fullTypeName = QByteArray{T::getTypePrefix()} + meta->className();
     PyType_Spec spec = {
-        fullTypeName.constData(),
+        qstrdup(fullTypeName.constData()),
         0,
         0,
         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
@@ -478,9 +482,9 @@ PyTypeObject *createDynamicClassImpl(QMetaObject *meta)
 PyTypeObject *createDynamicClass(QMetaObject *meta, PyObject *properties_capsule)
 {
     bool isSource;
-    if (strncmp(meta->superClass()->className(), "QObject", 7) == 0) {
+    if (std::strncmp(meta->superClass()->className(), "QObject", 7) == 0) {
         isSource = true;
-    } else if (strncmp(meta->superClass()->className(), "QRemoteObjectReplica", 20) == 0) {
+    } else if (std::strncmp(meta->superClass()->className(), "QRemoteObjectReplica", 20) == 0) {
         isSource = false;
     } else {
         PyErr_SetString(PyExc_RuntimeError,
index 1e8bc32796fba56687649dace4799b85a83615d2..b1b675108b64c757dc74c28867c3a32085b19e8a 100644 (file)
@@ -4,6 +4,7 @@
 #include "pysidedynamiccommon_p.h"
 #include "pysidedynamicenum_p.h"
 
+#include <sbkpep.h>
 #include <sbkstring.h>
 
 #include <QtCore/qmetaobject.h>
@@ -96,7 +97,7 @@ int create_managed_py_enums(PyObject *self, QMetaObject *meta)
     if (PyObject_SetAttrString(self, "_enum_data", enum_data) < 0) {
         PyErr_Print();
         qWarning() << "Failed to set _enum_data attribute on type"
-                   << reinterpret_cast<PyTypeObject *>(self)->tp_name;
+                   << PepType_GetFullyQualifiedNameStr(reinterpret_cast<PyTypeObject *>(self));
         return -1;
     }
     Py_DECREF(enum_data);
index abfeaa037e741ca160097410a327518e95d05107..5dfdb448456679023d7d0eb3319c2e55ee0734d2 100644 (file)
@@ -74,7 +74,7 @@ struct PodDefs
     static PyObject *tp_repr(PyObject *self)
     {
         auto *type = Py_TYPE(self);
-        std::string repr(type->tp_name);
+        std::string repr(PepType_GetFullyQualifiedNameStr(type));
         repr += "(";
         for (Py_ssize_t i = 0; i < PyTuple_Size(self); ++i) {
             if (i > 0)
@@ -203,8 +203,9 @@ PyTypeObject *createPodType(QMetaObject *meta)
             return nullptr;
         }
         auto *pyType = Conversions::getPythonTypeObject(metaType.name());
-        Py_INCREF(pyType);
-        PyTuple_SetItem(pyParamTypes, i, reinterpret_cast<PyObject *>(pyType));
+        auto *obPyType = reinterpret_cast<PyObject *>(pyType);
+        Py_INCREF(obPyType);
+        PyTuple_SetItem(pyParamTypes, i, obPyType);
     }
 
     auto *type = reinterpret_cast<PyTypeObject *>(obType);
@@ -229,10 +230,8 @@ PyTypeObject *createPodType(QMetaObject *meta)
                                               PyCapsule_GetPointer(capsule, "PropertyCapsule"));
                                       });
         auto *capsulePropObject = make_capsule_property(&method, capsule);
-        if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), metaProperty.name(),
-                                   capsulePropObject) < 0) {
+        if (PyObject_SetAttrString(obType, metaProperty.name(), capsulePropObject) < 0)
             return nullptr;
-        }
 
         Py_DECREF(capsulePropObject);
     }
@@ -242,11 +241,12 @@ PyTypeObject *createPodType(QMetaObject *meta)
     // to the type's attributes. So we need to decrease the ref count on the type
     // after calling createConverter.
     auto *converter = Shiboken::Conversions::createConverter(type, cppToPython_POD_Tuple);
-    Py_DECREF(type);
+    Py_DECREF(obType);
     if (set_cleanup_capsule_attr_for_pointer(type, "_converter_capsule", converter) < 0)
         return nullptr;
     Shiboken::Conversions::registerConverterName(converter, meta->className());
-    Shiboken::Conversions::registerConverterName(converter, type->tp_name);
+    Shiboken::Conversions::registerConverterName(converter,
+                                                 PepType_GetFullyQualifiedNameStr(type));
     Shiboken::Conversions::addPythonToCppValueConversion(converter, pythonToCpp_Tuple_POD,
                                                          is_Tuple_PythonToCpp_POD_Convertible);
 
index bfe085456405a42497a8bae03697aed601766f40..5804add37a0cf3c5f10872973d44e5394ae9f6bb 100644 (file)
@@ -7,6 +7,7 @@
 #include "pysidedynamiccommon_p.h"
 
 #include <pep384ext.h>
+#include <sbkerrors.h>
 #include <sbkstring.h>
 #include <sbktypefactory.h>
 #include <signature.h>
@@ -367,22 +368,16 @@ bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue)
     static PyObject *pyLocals = PyDict_New();
 
     // Create the Python expression to evaluate
-    std::string code = std::string(pyType->tp_name) + '('
+    std::string code = std::string(PepType_GetFullyQualifiedNameStr(pyType)) + '('
                        + defaultValue.toUtf8().constData() + ')';
     PyObject *pyResult = PyRun_String(code.c_str(), Py_eval_input, pyLocals, pyLocals);
 
     if (!pyResult) {
-        PyObject *ptype = nullptr;
-        PyObject *pvalue = nullptr;
-        PyObject *ptraceback = nullptr;
-        PyErr_Fetch(&ptype, &pvalue, &ptraceback);
-        PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
+        Shiboken::Errors::Stash errorStash;
         PyErr_Format(PyExc_TypeError,
                      "Failed to generate default value. Error: %s. Problematic code: %s",
-                     Shiboken::String::toCString(PyObject_Str(pvalue)), code.c_str());
-        Py_XDECREF(ptype);
-        Py_XDECREF(pvalue);
-        Py_XDECREF(ptraceback);
+                     Shiboken::String::toCString(PyObject_Str(errorStash.getException())), code.c_str());
+        errorStash.release();
         Py_DECREF(pyLocals);
         return false;
     }
@@ -446,8 +441,10 @@ void init(PyObject *module)
     qRegisterMetaType<QRemoteObjectPendingCall>();
     qRegisterMetaType<QRemoteObjectPendingCallWatcher>();
 
-    Py_INCREF(PySideRepFile_TypeF());
-    PyModule_AddObject(module, "RepFile", reinterpret_cast<PyObject *>(PySideRepFile_TypeF()));
+    auto *repType = PySideRepFile_TypeF();
+    auto *obRepType = reinterpret_cast<PyObject *>(repType);
+    Py_INCREF(obRepType);
+    PepModule_AddType(module, repType);
 
     // Add a test helper to verify type reference counting
     static PyMethodDef get_capsule_count_def = {
@@ -457,7 +454,7 @@ void init(PyObject *module)
         "Returns the current count of PyCapsule objects"   // docstring
     };
 
-    PyModule_AddObject(module, "getCapsuleCount", PyCFunction_New(&get_capsule_count_def, nullptr));
+    PepModule_Add(module, "getCapsuleCount", PyCFunction_New(&get_capsule_count_def, nullptr));
 }
 
 } // namespace PySide::RemoteObjects
index b5a00dd422f40d73a78af2fae81b8cc2c3b81e36..f886c7f36f685232a33edffdeaf268c3524b40b9 100644 (file)
@@ -22,7 +22,10 @@ target_sources(PySidePlugin PRIVATE
 
 target_compile_definitions(PySidePlugin PRIVATE -DQT_NO_KEYWORDS=1)
 
-if(PYTHON_LIMITED_API)
+# For Windows we use the limited API by default
+# See ShibokenHelpers.cmake::shiboken_check_if_limited_api() which is called always
+# with default FORCE_LIMITED_API set to TRUE for building libshiboken
+if(FORCE_LIMITED_API OR WIN32)
     target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03090000")
 endif()
 
index 07faa5b02ad3b5de8f010ed784d0f9a993658397..d13539859f9b42d851bd335db1a0277e569dcccb 100644 (file)
@@ -57,6 +57,7 @@ static QString pyStr(PyObject *o)
 static QString pyErrorMessage()
 {
     QString result = "<error information not available>"_L1;
+#if (defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030C0000) || (!defined(Py_LIMITED_API) && PY_VERSION_HEX < 0x030C0000)
     PyObject *ptype = {};
     PyObject *pvalue = {};
     PyObject *ptraceback = {};
@@ -64,6 +65,12 @@ static QString pyErrorMessage()
     if (pvalue != nullptr)
         result = pyStr(pvalue);
     PyErr_Restore(ptype, pvalue, ptraceback);
+#else // <3.11
+    if (PyObject *pvalue = PyErr_GetRaisedException()) {
+        result = pyStr(pvalue);
+        Py_DECREF(pvalue);
+    }
+#endif
     return result;
 }
 
index 06d0ae9007f81100f4913a0c0f28ac404ff92e2a..f111109f14244f62d584235ec06ec5761676fa02 100644 (file)
@@ -18,7 +18,7 @@ add_library(uiplugin STATIC ${ui_plugin_src})
 if(CMAKE_HOST_UNIX AND NOT CYGWIN)
     add_definitions(-fPIC)
 endif()
-add_definitions(-DQT_STATICPLUGIN)
+add_compile_definitions(QT_STATICPLUGIN)
 
 set_property(TARGET pyside6 PROPERTY CXX_STANDARD 17)
 
index 19e285c3acbafd8a4b06eca544fa5441172e7be3..361c02fa5b2dee6cc3bdcaddc4b0f4027499113e 100644 (file)
@@ -9,12 +9,14 @@
 #include <basewrapper.h>
 #include <bindingmanager.h>
 #include <sbkconverter.h>
+#include <sbkpep.h>
+
 
 // Part of the static plugin linked to the QtUiLoader Python module,
 // allowing it to create a custom widget written in Python.
 PyCustomWidget::PyCustomWidget(PyObject *objectType) :
     m_pyObject(objectType),
-    m_name(QString::fromUtf8(reinterpret_cast<PyTypeObject *>(objectType)->tp_name))
+    m_name(QString::fromUtf8(PepType_GetFullyQualifiedNameStr(reinterpret_cast<PyTypeObject *>(objectType))))
 {
 }
 
@@ -88,20 +90,21 @@ QWidget *PyCustomWidget::createWidget(QWidget *parent)
     PyTuple_SetItem(pyArgs.object(), 0, pyParent); // tuple will keep pyParent reference
 
     // Call python constructor
-    auto *result = reinterpret_cast<SbkObject *>(PyObject_CallObject(m_pyObject, pyArgs));
-    if (result == nullptr) {
+    auto *obResult = PyObject_CallObject(m_pyObject, pyArgs);
+    if (obResult == nullptr) {
         qWarning("Unable to create a Python custom widget of type \"%s\".",
                  qPrintable(m_name));
         PyErr_Print();
         return nullptr;
     }
 
+    auto *result = reinterpret_cast<SbkObject *>(obResult);
     if (unknownParent) // if parent does not exist in python, transfer the ownership to cpp
         Shiboken::Object::releaseOwnership(result);
     else
-        Shiboken::Object::setParent(pyParent, reinterpret_cast<PyObject *>(result));
+        Shiboken::Object::setParent(pyParent, obResult);
 
-    return reinterpret_cast<QWidget *>(Shiboken::Object::cppPointer(result, Py_TYPE(result)));
+    return reinterpret_cast<QWidget *>(Shiboken::Object::cppPointer(result, Py_TYPE(obResult)));
 }
 
 void PyCustomWidget::initialize(QDesignerFormEditorInterface *)
diff --git a/sources/pyside6/qtexampleicons/CMakeLists.txt b/sources/pyside6/qtexampleicons/CMakeLists.txt
deleted file mode 100644 (file)
index c6446c4..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) 2023 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-cmake_minimum_required(VERSION 3.18)
-cmake_policy(VERSION 3.18)
-
-project(QtExampleIcons)
-
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
-
-set(CMAKE_AUTORCC ON)
-
-set(CMAKE_AUTOMOC ON)
-
-find_package(Qt6 COMPONENTS ExampleIconsPrivate)
-
-add_library(QtExampleIcons MODULE module.c)
-
-# See libshiboken/CMakeLists.txt
-if(PYTHON_LIMITED_API)
-    target_compile_definitions(QtExampleIcons PRIVATE "-DPy_LIMITED_API=0x03090000")
-endif()
-
-if(CMAKE_BUILD_TYPE STREQUAL "Debug")
-    if(PYTHON_WITH_DEBUG)
-        target_compile_definitions(QtExampleIcons PRIVATE "-DPy_DEBUG")
-    endif()
-    if (PYTHON_WITH_COUNT_ALLOCS)
-        target_compile_definitions(QtExampleIcons PRIVATE "-DCOUNT_ALLOCS")
-    endif()
-elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
-    target_compile_definitions(QtExampleIcons PRIVATE "-DNDEBUG")
-endif()
-
-target_include_directories(QtExampleIcons PRIVATE ${SHIBOKEN_PYTHON_INCLUDE_DIRS})
-
-get_property(SHIBOKEN_PYTHON_LIBRARIES GLOBAL PROPERTY shiboken_python_libraries)
-
-target_link_libraries(QtExampleIcons PRIVATE
-                      Qt::ExampleIconsPrivate
-                      ${SHIBOKEN_PYTHON_LIBRARIES})
-
-set_target_properties(QtExampleIcons PROPERTIES
-                      PREFIX ""
-                      OUTPUT_NAME "QtExampleIcons${SHIBOKEN_PYTHON_EXTENSION_SUFFIX}"
-                      LIBRARY_OUTPUT_DIRECTORY "${pyside6_BINARY_DIR}")
-
-if(WIN32)
-    set_property(TARGET QtExampleIcons PROPERTY SUFFIX ".pyd")
-endif()
-
-install(TARGETS QtExampleIcons LIBRARY DESTINATION "${PYTHON_SITE_PACKAGES}/PySide6")
diff --git a/sources/pyside6/qtexampleicons/module.c b/sources/pyside6/qtexampleicons/module.c
deleted file mode 100644 (file)
index b728daf..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2023 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include <Python.h>
-
-#if defined _WIN32
-#  define MODULE_EXPORT __declspec(dllexport)
-#else
-#  define MODULE_EXPORT __attribute__ ((visibility("default")))
-#endif
-
-static PyMethodDef QtExampleIconsMethods[] = {
-    {NULL, NULL, 0, NULL}
-};
-
-static struct PyModuleDef moduleDef = {
-    /* m_base     */ PyModuleDef_HEAD_INIT,
-    /* m_name     */ "QtExampleIcons",
-    /* m_doc      */ NULL,
-    /* m_size     */ -1,
-    /* m_methods  */ QtExampleIconsMethods,
-    /* m_reload   */ NULL,
-    /* m_traverse */ NULL,
-    /* m_clear    */ NULL,
-    /* m_free     */ NULL
-};
-
-MODULE_EXPORT PyObject *PyInit_QtExampleIcons(void)
-{
-    return PyModule_Create(&moduleDef);
-}
-
-int main(int argc, char *argv[])
-{
-#ifndef PYPY_VERSION
-    Py_Initialize();
-#endif
-    PyInit_QtExampleIcons();
-    return 0;
-}
index 539e1aea8081ec274712b0619f05ffaa8cf35e87..40df2c68220cbc838e47d49a3c737ecc277d9f8a 100644 (file)
@@ -48,6 +48,10 @@ endmacro()
 if (NOT DISABLE_QtCore AND NOT DISABLE_QtGui AND NOT DISABLE_QtWidgets)
     add_subdirectory(pysidetest)
 endif()
+if (NOT DISABLE_QtAsyncio)
+    add_subdirectory(QtAsyncio)
+endif()
+
 add_subdirectory(registry)
 add_subdirectory(signals)
 add_subdirectory(support)
@@ -68,6 +72,6 @@ foreach(shortname IN LISTS all_module_shortnames)
 endforeach()
 
 #platform specific
-if (ENABLE_MAC)
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
     add_subdirectory(mac)
 endif ()
index 935e0d90aa944097b1528fda54223bb1aa51e40a..221b74b64fef1d32ffa46ef4f870f61ffafdc012 100644 (file)
@@ -1,2 +1,11 @@
+PYSIDE_TEST(bug_2790.py)
+PYSIDE_TEST(bug_2799.py)
 PYSIDE_TEST(qasyncio_test.py)
+PYSIDE_TEST(qasyncio_test_cancel_task.py)
+PYSIDE_TEST(qasyncio_test_cancel_taskgroup.py)
 PYSIDE_TEST(qasyncio_test_chain.py)
+PYSIDE_TEST(qasyncio_test_executor.py)
+PYSIDE_TEST(qasyncio_test_queues.py)
+PYSIDE_TEST(qasyncio_test_threadsafe.py)
+PYSIDE_TEST(qasyncio_test_time.py)
+PYSIDE_TEST(qasyncio_test_uncancel.py)
index 9fd152b15358c77c6186a2099eea181ef2b25823..5bf91931e7f7a80b0f01ce36961a2035bf2a603a 100644 (file)
@@ -4,9 +4,16 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
-import asyncio
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 import PySide6.QtAsyncio as QtAsyncio
 
 
@@ -29,6 +36,7 @@ class QAsyncioTestCaseBug2790(unittest.TestCase):
             except TimeoutError:
                 outputs.append("Timeout")
 
+    @unittest.skipIf(sys.version_info < (3, 11), "requires asyncio.timeout")
     def test_timeout(self):
         # The Qt event loop (and thus QtAsyncio) does not guarantee that events
         # will be processed in the order they were posted, so there is two
index eb86c7d4bd0e44d4900f3186c1205d2363eaea4f..c277bd5b0b2158bace04f8046f10d0b13f7e305e 100644 (file)
@@ -4,10 +4,16 @@ from __future__ import annotations
 
 """Test cases for QtAsyncio"""
 
-import unittest
-import asyncio
+import os
 import sys
+import unittest
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 import PySide6.QtAsyncio as QtAsyncio
 
 
@@ -22,7 +28,7 @@ class QAsyncioTestCaseBug2799(unittest.TestCase):
             raise RuntimeError()
 
     def test_exception_group(self):
-        with self.assertRaises(ExceptionGroup):
+        with self.assertRaises(ExceptionGroup):  # noqa: F821
             QtAsyncio.run(self.main(), keep_running=False)
 
 
index 80c5107acd7b5f8d3240511625ad9c8e716679cb..cf3f77113d0541b0d8ae78624f5dc9f9b5129ccb 100644 (file)
@@ -4,9 +4,16 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
-import asyncio
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
 
 
index c2f56d01aaf7042586e0cccd306132ae7ecb2b6d..ab28beb8f7f3fde8e32c782a6c93234a2865b4a7 100644 (file)
@@ -4,9 +4,16 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
-import asyncio
+import os
+import sys
 import unittest
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 import PySide6.QtAsyncio as QtAsyncio
 
 
index f3724f0101a09256cd46a0f9b7afe233a7075493..178701dc25bc3de09b0b3ff708dcfde0f747aea1 100644 (file)
@@ -4,10 +4,16 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
-import asyncio
-import unittest
+import os
 import sys
+import unittest
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 import PySide6.QtAsyncio as QtAsyncio
 
 
index a8e5bb1983f860a89465474f758bbe466f88bf1c..8b41a133af00db496f9a340a530c30bffac03c15 100644 (file)
@@ -4,11 +4,18 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
 import asyncio
 import random
 import time
-
 from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
 
 
index 641d374df04603f97b29019de4e250b6de71facc..f9812a771e7831ddd22ca6d332340317eaf1aca9 100644 (file)
@@ -4,11 +4,18 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
-import asyncio
 
 from concurrent.futures import ThreadPoolExecutor
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 from PySide6.QtCore import QThread
 from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
 
index 2df39ee02c798f0f51c18869056dd13f040a19a5..f75480d9d10cfe82cb2af35173cfc491d8d0d2dd 100644 (file)
@@ -4,11 +4,18 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
 import asyncio
 import random
 import time
-
 from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
 
 
index 4893543498536f388577da139e71ac1d285a4c7d..a3bcd0c27442b9ae3d3453aa649041298b2aeb5e 100644 (file)
@@ -4,14 +4,22 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
 import asyncio
 import threading
 import time
-
 from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
 
 
+@unittest.skip("Temporarily disabled: hangs indefinitely on Python < 3.11")
 class QAsyncioTestCaseThreadsafe(unittest.TestCase):
 
     def setUp(self) -> None:
index 66f0433dfd439ac9050cfbc223d80907ab81b607..e95e457b6a4e23ae7f8cfdddeecb5a29a7e63fb3 100644 (file)
@@ -4,10 +4,17 @@ from __future__ import annotations
 
 '''Test cases for QtAsyncio'''
 
+import os
+import sys
 import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
 import asyncio
 import datetime
-
 from PySide6.QtAsyncio import QAsyncioEventLoopPolicy
 
 
index 0366228452acdc9a225cb171f3ba85b580308169..fd75a1fc711543071fd9a4f200c7e8727e14a458 100644 (file)
@@ -4,12 +4,20 @@ from __future__ import annotations
 
 """Test cases for QtAsyncio"""
 
+import os
+import sys
 import unittest
-import asyncio
 
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+import asyncio
 import PySide6.QtAsyncio as QtAsyncio
 
 
+@unittest.skipIf(sys.version_info < (3, 11), "requires Task.uncancel() (Python 3.11+)")
 class QAsyncioTestCaseUncancel(unittest.TestCase):
     """ https://superfastpython.com/asyncio-cancel-task-cancellation """
 
index f4c9ded362a85d2752d553bae9e4d180182268d9..28db96160860c8ca44d1f11a42e718189147a478 100644 (file)
@@ -73,11 +73,13 @@ PYSIDE_TEST(qdate_test.py)
 PYSIDE_TEST(qdir_test.py)
 PYSIDE_TEST(qeasingcurve_test.py)
 PYSIDE_TEST(qenum_test.py)
+PYSIDE_TEST(qenum_designer_test.py)
 PYSIDE_TEST(qevent_test.py)
 PYSIDE_TEST(qfileinfo_test.py)
 PYSIDE_TEST(qfile_test.py)
 PYSIDE_TEST(qfileread_test.py)
 PYSIDE_TEST(qflags_test.py)
+PYSIDE_TEST(qrangemodel_test.py)
 PYSIDE_TEST(qinstallmsghandler_test.py)
 PYSIDE_TEST(qiodevice_buffered_read_test.py)
 PYSIDE_TEST(qiopipe_test.py)
index 8b211a979f9ee72eb52bead2b1783be4e3ec5e94..64b001e6de63f0208ead3d9ba282048a67315480 100644 (file)
@@ -12,8 +12,8 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(False)
 
-from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF
-from PySide6.QtCore import Qt, QSize, QSizeF, QRect, QRectF, QDir, QPoint, QPointF
+from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF, QTimeZone
+from PySide6.QtCore import QSize, QSizeF, QRect, QRectF, QDir, QPoint, QPointF
 try:
     from PySide6.QtCore import QUuid
     HAVE_Q = True
@@ -45,7 +45,7 @@ class QTimeDeepCopy(DeepCopyHelper, unittest.TestCase):
 
 class QDateTimeDeepCopy(DeepCopyHelper, unittest.TestCase):
     def setUp(self):
-        self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, Qt.TimeSpec.LocalTime)
+        self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, QTimeZone(QTimeZone.LocalTime))
 
 
 class QSizeDeepCopy(DeepCopyHelper, unittest.TestCase):
diff --git a/sources/pyside6/tests/QtCore/qenum_designer_test.py b/sources/pyside6/tests/QtCore/qenum_designer_test.py
new file mode 100644 (file)
index 0000000..2a8f352
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/python
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test cases for QEnum and QFlags within Qt Widgets Designer'''
+
+import os
+import sys
+import unittest
+
+from enum import Enum, Flag
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QObject, Property, QEnum, QFlag, Qt
+
+
+class CustomWidgetBase(QObject):
+    @QEnum
+    class TestEnum(Enum):
+        EnumValue0 = 0
+        EnumValue1 = 1
+        EnumValue2 = 2
+        EnumValue3 = 3
+
+    @QFlag
+    class TestFlag(Flag):
+        FlagValue0 = 1
+        FlagValue1 = 2
+        FlagValue2 = 4
+        FlagValue3 = 8
+
+    @QFlag
+    class BigTestFlag(Flag):
+        BigFlagValue0 = 0x100000000  # >32bit
+        BigFlagValue1 = 0x200000000
+        BigFlagValue2 = 0x400000000
+        BigFlagValue3 = 0x800000000
+
+
+class CustomWidget(CustomWidgetBase):
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._testEnum = CustomWidget.TestEnum.EnumValue1
+        self._testFlag = (CustomWidget.TestFlag.FlagValue0
+                          | CustomWidget.TestFlag.FlagValue1)
+        self._bigTestFlag = CustomWidget.BigTestFlag.BigFlagValue1
+        self._orientation = Qt.Orientation.Horizontal
+
+    def orientation(self):
+        return self._orientation
+
+    def setOrientation(self, new_val):
+        self._orientation = new_val
+
+    def testEnum(self):
+        return self._testEnum
+
+    def setTestEnum(self, new_val):
+        self._testEnum = new_val
+
+    def getTestFlag(self):
+        return self._testFlag
+
+    def setTestFlag(self, new_val):
+        self._testFlag = new_val
+
+    def getBigTestFlag(self):
+        return self._bigTestFlag
+
+    def setBigTestFlag(self, new_val):
+        self._bigTestFlag = new_val
+
+    # Qt C++ enum
+    orientation = Property(Qt.Orientation, orientation, setOrientation)
+    # Decorated Python enums
+    testEnum = Property(CustomWidgetBase.TestEnum, testEnum, setTestEnum)
+    testFlag = Property(CustomWidgetBase.TestFlag, getTestFlag, setTestFlag)
+    bigTestFlag = Property(CustomWidgetBase.BigTestFlag,
+                           getBigTestFlag, setBigTestFlag)
+
+
+class TestDesignerEnum(unittest.TestCase):
+    """PYSIDE-2840: Test whether a custom widget with decorated enum/flag properties
+       allows for modifying the values from C++."""
+
+    def setUp(self):
+        super().setUp()
+        self._cw = CustomWidget()
+
+    def tearDown(self):
+        super().tearDown()
+        self._cw = None
+
+    def testEnum(self):
+        # Emulate Qt Widgets Designer setting a property
+        cw = self._cw
+        cw.setProperty("testEnum", 3)
+        self.assertEqual(cw.testEnum, CustomWidget.TestEnum.EnumValue3)
+        # Emulate uic generated code
+        cw.setProperty("testEnum", CustomWidgetBase.TestEnum.EnumValue2)
+        self.assertEqual(cw.testEnum, CustomWidget.TestEnum.EnumValue2)
+
+        # Emulate Qt Widgets Designer setting a property
+        cw.setProperty("testFlag", 12)
+        self.assertEqual(cw.testFlag, (CustomWidget.TestFlag.FlagValue2
+                                       | CustomWidget.TestFlag.FlagValue3))
+        # Emulate uic generated code
+        cw.setProperty("testFlag", CustomWidgetBase.TestFlag.FlagValue1)
+        self.assertEqual(cw.testFlag, CustomWidget.TestFlag.FlagValue1)
+
+        # Emulate Qt Widgets Designer setting a property (note though
+        # it does not support it).
+        self.assertEqual(cw.bigTestFlag, CustomWidget.BigTestFlag.BigFlagValue1)
+        ok = cw.setProperty("bigTestFlag", CustomWidgetBase.BigTestFlag.BigFlagValue2)
+        self.assertTrue(ok)
+        self.assertEqual(cw.bigTestFlag, CustomWidget.BigTestFlag.BigFlagValue2)
+
+    def testMetaProperty(self):
+        mo = self._cw.metaObject()
+        index = mo.indexOfProperty("orientation")
+        self.assertTrue(index != -1)
+        self.assertTrue(mo.property(index).isEnumType())
+        index = mo.indexOfProperty("testEnum")
+        self.assertTrue(index != -1)
+        self.assertTrue(mo.property(index).isEnumType())
+        index = mo.indexOfProperty("testFlag")
+        self.assertTrue(index != -1)
+        self.assertTrue(mo.property(index).isEnumType())
+
+
+if __name__ == '__main__':
+    unittest.main()
index 58115295f7d99115861ceb6d91ab74ea6058e87e..edf22dfa7470fc0f85b81746fadf5c9cdbf55f12 100644 (file)
@@ -135,6 +135,9 @@ class SomeClass(QObject):
         A = 1
         B = 2
         C = 3
+        D = 0x100000000  # >32bit
+        E = 0x200000000
+        F = 0x400000000
 
     class InnerClass(QObject):
 
@@ -186,9 +189,39 @@ class TestQEnumMacro(unittest.TestCase):
     def testIsRegistered(self):
         mo = SomeClass.staticMetaObject
         self.assertEqual(mo.enumeratorCount(), 2)
-        self.assertEqual(mo.enumerator(0).name(), "OtherEnum")
-        self.assertEqual(mo.enumerator(0).scope(), "SomeClass")
-        self.assertEqual(mo.enumerator(1).name(), "SomeEnum")
+
+        # 64 bit / IntEnum
+        other_metaenum = mo.enumerator(0)
+        self.assertEqual(other_metaenum.scope(), "SomeClass")
+        self.assertEqual(other_metaenum.name(), "OtherEnum")
+        self.assertTrue(other_metaenum.is64Bit())
+        key_count = other_metaenum.keyCount()
+        self.assertEqual(key_count, 6)
+        self.assertEqual(other_metaenum.value(key_count - 1), SomeClass.OtherEnum.F)
+        # Test lookup
+        v, ok = other_metaenum.keyToValue("F")
+        self.assertTrue(ok)
+        self.assertEqual(v, SomeClass.OtherEnum.F)
+        v, ok = other_metaenum.keysToValue("E")
+        self.assertTrue(ok)
+        self.assertEqual(v, SomeClass.OtherEnum.E)
+
+        # 32 bit / Enum
+        some_metaenum = mo.enumerator(1)
+        self.assertEqual(some_metaenum.scope(), "SomeClass")
+        self.assertEqual(some_metaenum.name(), "SomeEnum")
+        self.assertFalse(some_metaenum.is64Bit())
+        key_count = some_metaenum.keyCount()
+        self.assertEqual(key_count, 3)
+        self.assertEqual(some_metaenum.value(key_count - 1), SomeClass.SomeEnum.C.value)
+        # Test lookup
+        v, ok = some_metaenum.keyToValue("C")
+        self.assertTrue(ok)
+        self.assertEqual(v, SomeClass.SomeEnum.C.value)
+        v, ok = some_metaenum.keysToValue("C")
+        self.assertTrue(ok)
+        self.assertEqual(v, SomeClass.SomeEnum.C.value)
+
         moi = SomeClass.InnerClass.staticMetaObject
         self.assertEqual(moi.enumerator(0).name(), "InnerEnum")
         # Question: Should that scope not better be  "SomeClass.InnerClass"?
index a011f7240a9936541d63b5a8002c415eb9141a91..2ab5032dbed24e8cad057cac13e150be4d89434f 100644 (file)
@@ -114,14 +114,15 @@ class TestQObjectEventFilterPython(UsesQApplication):
     def testInstallEventFilterRefCountAfterDelete(self):
         '''Bug 910 - installEventFilter() increments reference count on target object
         http://bugs.pyside.org/show_bug.cgi?id=910'''
+        expected_ref_count = 1 if sys.version_info >= (3, 14) else 2
         obj = QObject()
         filt = QObject()
 
-        self.assertEqual(sys.getrefcount(obj), 2)
-        self.assertEqual(sys.getrefcount(filt), 2)
+        self.assertEqual(sys.getrefcount(obj), expected_ref_count)
+        self.assertEqual(sys.getrefcount(filt), expected_ref_count)
         obj.installEventFilter(filt)
-        self.assertEqual(sys.getrefcount(obj), 2)
-        self.assertEqual(sys.getrefcount(filt), 2)
+        self.assertEqual(sys.getrefcount(obj), expected_ref_count)
+        self.assertEqual(sys.getrefcount(filt), expected_ref_count)
 
         wref = weakref.ref(obj)
         del obj
@@ -133,14 +134,15 @@ class TestQObjectEventFilterPython(UsesQApplication):
         obj = QObject()
         filt = QObject()
 
-        self.assertEqual(sys.getrefcount(obj), 2)
-        self.assertEqual(sys.getrefcount(filt), 2)
+        expected_ref_count = 1 if sys.version_info >= (3, 14) else 2
+        self.assertEqual(sys.getrefcount(obj), expected_ref_count)
+        self.assertEqual(sys.getrefcount(filt), expected_ref_count)
         obj.installEventFilter(filt)
-        self.assertEqual(sys.getrefcount(obj), 2)
-        self.assertEqual(sys.getrefcount(filt), 2)
+        self.assertEqual(sys.getrefcount(obj), expected_ref_count)
+        self.assertEqual(sys.getrefcount(filt), expected_ref_count)
         obj.removeEventFilter(filt)
-        self.assertEqual(sys.getrefcount(obj), 2)
-        self.assertEqual(sys.getrefcount(filt), 2)
+        self.assertEqual(sys.getrefcount(obj), expected_ref_count)
+        self.assertEqual(sys.getrefcount(filt), expected_ref_count)
 
         wref = weakref.ref(obj)
         del obj
index ffca0d1770436631c51b13d3274af3b16cfc4716..932f7864a6a4c4be98bb2dc497320f1529297a48 100644 (file)
@@ -61,7 +61,8 @@ class ParentRefCountCase(unittest.TestCase):
     def testConstructor(self):
         # QObject(QObject) refcount changes
         child = QObject(self.parent)
-        self.assertEqual(sys.getrefcount(child), 3)
+        expected_ref_count = 2 if sys.version_info >= (3, 14) else 3
+        self.assertEqual(sys.getrefcount(child), expected_ref_count)
 
 
 class ParentCase(unittest.TestCase):
index 80387ec7782e50bf5c92f927f00c66ab8bfe6992..e0a8044fe5e1ce9185c6d7ee0ed12a37491573bd 100644 (file)
@@ -9,6 +9,7 @@ import sys
 import unittest
 
 from pathlib import Path
+from typing import NamedTuple
 sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(False)
@@ -16,6 +17,9 @@ init_test_paths(False)
 from PySide6.QtCore import QObject, Property, Signal
 
 
+Point = NamedTuple("Point", [("x", float), ("y", float)])
+
+
 class MyObjectWithNotifyProperty(QObject):
     def __init__(self, parent=None):
         QObject.__init__(self, parent)
@@ -52,6 +56,23 @@ class MyObjectWithOtherClassProperty(QObject):
     otherclass = Property(OtherClass, fget=_get_otherclass, fset=_set_otherclass)
 
 
+class TestVariantPropertyObject(QObject):
+    """Helper for testing QVariant conversion in properties and signals
+       (PYSIDE-3206, PYSIDE-3244). It uses a property of list type that
+       can passed a QVariant list with various element types."""
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self._property = None
+
+    def set_property(self, v):
+        self._property = v
+
+    def get_property(self):
+        return self._property
+
+    testProperty = Property(list, fget=get_property, fset=set_property)
+
+
 class PropertyWithNotify(unittest.TestCase):
     def called(self):
         self.called_ = True
@@ -84,5 +105,33 @@ class QObjectWithOtherClassPropertyTest(unittest.TestCase):
             self.assertTrue(type(pv) is OtherClass)
 
 
+class VariantPropertyTest(unittest.TestCase):
+    """Test QVariant conversion in properties and signals (PYSIDE-3256,
+       PYSIDE-3244, PYSIDE-3206 [open]). It uses a property of list type
+       that is passed a QVariantList with various element types when
+       using QObject.setProperty()."""
+
+    def testIt(self):
+        to = TestVariantPropertyObject()
+        idx = to.metaObject().indexOfProperty("testProperty")
+        self.assertTrue(idx != -1)
+
+        # List
+        to.setProperty("testProperty", [[1, 2]])
+        self.assertEqual(type(to.get_property()[0]), list)
+
+        # Dict
+        to.setProperty("testProperty", [{"key": 42}])
+        self.assertEqual(type(to.get_property()[0]), dict)
+
+        # Tuple (PYSIDE-3256)
+        to.setProperty("testProperty", [(1, 2)])
+        self.assertEqual(type(to.get_property()[0]), tuple)
+
+        # Named Tuple (PYSIDE-3244)
+        to.setProperty("testProperty", [Point(1, 2)])
+        self.assertEqual(type(to.get_property()[0]), Point)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/sources/pyside6/tests/QtCore/qrangemodel_test.py b/sources/pyside6/tests/QtCore/qrangemodel_test.py
new file mode 100644 (file)
index 0000000..b9319bd
--- /dev/null
@@ -0,0 +1,56 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QRangeModel
+
+
+try:
+    import numpy as np
+    HAVE_NUMPY = True
+except ModuleNotFoundError:
+    HAVE_NUMPY = False
+
+
+class QRangeModelTest(unittest.TestCase):
+
+    def test_pylist(self):
+        test_list = [1, 2, 3]
+        model = QRangeModel(test_list)
+        self.assertEqual(model.rowCount(), 3)
+        self.assertEqual(model.data(model.createIndex(2, 0)), 3)
+
+    def test_pytable(self):
+        test_table = [[1, 2], [3, 4]]
+        model = QRangeModel(test_table)
+        self.assertEqual(model.rowCount(), 2)
+        self.assertEqual(model.columnCount(), 2)
+        self.assertEqual(model.data(model.createIndex(1, 1)), 4)
+
+    @unittest.skipUnless(HAVE_NUMPY, "requires numpy")
+    def test_numpy_list(self):
+        test_array = np.array([1, 2, 3])
+        model = QRangeModel(test_array)
+        self.assertEqual(model.rowCount(), 3)
+        self.assertEqual(model.data(model.createIndex(2, 0)), 3)
+
+    @unittest.skipUnless(HAVE_NUMPY, "requires numpy")
+    def test_numpy_table(self):
+        test_table = np.array([[1, 2], [3, 4]])
+        model = QRangeModel(test_table)
+        self.assertEqual(model.rowCount(), 2)
+        self.assertEqual(model.columnCount(), 2)
+        self.assertEqual(model.data(model.createIndex(1, 1)), 4)
+
+
+if __name__ == '__main__':
+    unittest.main()
index 084b69f87e720b91bd1cf8174ebb1ed4ad6c7dec..52a426d3157ea520cbfc97ba485053e4ddffe396 100644 (file)
@@ -13,8 +13,8 @@ init_test_paths(False)
 
 # for 'self.original'
 import PySide6  # noqa
-from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF
-from PySide6.QtCore import Qt, QSize, QSizeF, QRect, QRectF, QPoint, QPointF
+from PySide6.QtCore import QByteArray, QDate, QDateTime, QTime, QLine, QLineF, QTimeZone
+from PySide6.QtCore import QSize, QSizeF, QRect, QRectF, QPoint, QPointF
 try:
     from PySide6.QtCore import QUuid
     HAVE_Q = True
@@ -46,7 +46,7 @@ class QTimeReprCopy(ReprCopyHelper, unittest.TestCase):
 
 class QDateTimeReprCopy(ReprCopyHelper, unittest.TestCase):
     def setUp(self):
-        self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, Qt.TimeSpec.LocalTime)
+        self.original = QDateTime(2010, 5, 18, 10, 24, 45, 223, QTimeZone(QTimeZone.LocalTime))
 
 
 class QSizeReprCopy(ReprCopyHelper, unittest.TestCase):
index ffa47cd532014f8e8f5ffed253700c50c6583aa0..36bbc6b4f85b096bb50b81f000140fb142074a67 100644 (file)
@@ -16,6 +16,9 @@ from PySide6.QtCore import QT_TR_NOOP, QT_TR_NOOP_UTF8
 from PySide6.QtCore import QT_TRANSLATE_NOOP, QT_TRANSLATE_NOOP3, QT_TRANSLATE_NOOP_UTF8
 
 
+REF_COUNT_DELTA = 0 if sys.version_info >= (3, 14) else 1  # 3.14 borrows references
+
+
 class QtTrNoopTest(unittest.TestCase):
 
     def setUp(self):
@@ -31,35 +34,35 @@ class QtTrNoopTest(unittest.TestCase):
         refcnt = sys.getrefcount(self.txt)
         result = QT_TR_NOOP(self.txt)
         self.assertEqual(result, self.txt)
-        self.assertEqual(sys.getrefcount(result), refcnt + 1)
+        self.assertEqual(sys.getrefcount(result), refcnt + REF_COUNT_DELTA)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testQtTrNoopUtf8(self):
         refcnt = sys.getrefcount(self.txt)
         result = QT_TR_NOOP_UTF8(self.txt)
         self.assertEqual(result, self.txt)
-        self.assertEqual(sys.getrefcount(result), refcnt + 1)
+        self.assertEqual(sys.getrefcount(result), refcnt + REF_COUNT_DELTA)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testQtTranslateNoop(self):
         refcnt = sys.getrefcount(self.txt)
         result = QT_TRANSLATE_NOOP(None, self.txt)
         self.assertEqual(result, self.txt)
-        self.assertEqual(sys.getrefcount(result), refcnt + 1)
+        self.assertEqual(sys.getrefcount(result), refcnt + REF_COUNT_DELTA)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testQtTranslateNoopUtf8(self):
         refcnt = sys.getrefcount(self.txt)
         result = QT_TRANSLATE_NOOP_UTF8(self.txt)
         self.assertEqual(result, self.txt)
-        self.assertEqual(sys.getrefcount(result), refcnt + 1)
+        self.assertEqual(sys.getrefcount(result), refcnt + REF_COUNT_DELTA)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testQtTranslateNoop3(self):
         refcnt = sys.getrefcount(self.txt)
         result = QT_TRANSLATE_NOOP3(None, self.txt, None)
         self.assertEqual(result, self.txt)
-        self.assertEqual(sys.getrefcount(result), refcnt + 1)
+        self.assertEqual(sys.getrefcount(result), refcnt + REF_COUNT_DELTA)
 
 
 if __name__ == '__main__':
index bf2d2105a0adaa6417224c87ec281c5b81e315c0..e618fd7b54732cee5bc3301ea74e334878b60ae9 100644 (file)
@@ -1,6 +1,6 @@
 # Copyright (C) 2023 The Qt Company Ltd.
 # SPDX-License-Identifier: BSD-3-Clause
 
-if(ENABLE_UNIX)
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
     PYSIDE_TEST(test_dbus.py)
 endif()
index fb0df14260337923a7839e375829ae546303f1d0..c8ac4c4577fe9519e12f7ecc8a07566763ebd6dc 100644 (file)
@@ -19,6 +19,7 @@ PYSIDE_TEST(bug_PYSIDE-344.py)
 PYSIDE_TEST(deepcopy_test.py)
 PYSIDE_TEST(event_filter_test.py)
 PYSIDE_TEST(float_to_int_implicit_conversion_test.py)
+PYSIDE_TEST(nativeinterface_test.py)
 PYSIDE_TEST(qbrush_test.py)
 PYSIDE_TEST(qcolor_test.py)
 PYSIDE_TEST(qcolor_reduce_test.py)
diff --git a/sources/pyside6/tests/QtGui/nativeinterface_test.py b/sources/pyside6/tests/QtGui/nativeinterface_test.py
new file mode 100644 (file)
index 0000000..6822544
--- /dev/null
@@ -0,0 +1,32 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths  # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtGui import QGuiApplication, QNativeInterface  # noqa
+from helper.usesqapplication import UsesQApplication  # noqa: E402
+
+
+class TestNativeInterface(UsesQApplication):
+
+    @unittest.skipUnless(sys.platform == "linux", "Linux only")
+    def testLinuxNativeApplication(self):
+        app = qApp  # noqa: F821
+        native_app = app.nativeInterface()
+        if native_app:
+            if issubclass(type(native_app), QNativeInterface.QX11Application):
+                self.assertTrue(native_app.display() != 0)
+            elif issubclass(type(native_app), QNativeInterface.QWaylandApplication):
+                self.assertTrue(native_app.display() != 0)
+                self.assertTrue(native_app.seat() is not None)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/sources/pyside6/tests/QtGui/qmatrix_test.py b/sources/pyside6/tests/QtGui/qmatrix_test.py
new file mode 100644 (file)
index 0000000..64a97d6
--- /dev/null
@@ -0,0 +1,68 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtGui import (
+    QMatrix2x2,
+    QMatrix2x3,
+    QMatrix2x4,
+    QMatrix3x2,
+    QMatrix3x3,
+    QMatrix3x4,
+    QMatrix4x2,
+    QMatrix4x3,
+    QMatrix4x4,
+)
+
+
+# Helper function to create sequential data for a matrix
+def sequential_values(rows: int, cols: int) -> list[float]:
+    return [float(i + 1) for i in range(rows * cols)]
+
+
+class TestQMatrixIndexing(unittest.TestCase):
+
+    def setUp(self):
+        # Matrix types
+        self.matrices = [
+            (QMatrix2x2(sequential_values(2, 2)), 2, 2),
+            (QMatrix2x3(sequential_values(2, 3)), 3, 2),
+            (QMatrix2x4(sequential_values(2, 4)), 4, 2),
+            (QMatrix3x2(sequential_values(3, 2)), 2, 3),
+            (QMatrix3x3(sequential_values(3, 3)), 3, 3),
+            (QMatrix3x4(sequential_values(3, 4)), 4, 3),
+            (QMatrix4x2(sequential_values(4, 2)), 2, 4),
+            (QMatrix4x3(sequential_values(4, 3)), 3, 4),
+            (QMatrix4x4(sequential_values(4, 4)), 4, 4),
+        ]
+
+    def test_getitem(self):
+        """Test [row, col] indexing for all matrix types."""
+        for m, rows, cols in self.matrices:
+            v = 1.0
+            for x in range(rows):
+                for y in range(cols):
+                    self.assertEqual(m[x, y], v)
+                    v += 1.0
+
+    def test_callable_operator(self):
+        """Test operator()(row, col) for all QMatrix types."""
+        for m, rows, cols in self.matrices:
+            v = 1.0
+            for x in range(rows):
+                for y in range(cols):
+                    self.assertEqual(m(x, y), v)
+                    v += 1.0
+
+
+if __name__ == "__main__":
+    unittest.main()
index ea5a73db896c5cf5abd29b30b1d027c94efaff88..85325f1145519d54051b0e7fd06b672d6f62cd6f 100644 (file)
@@ -38,6 +38,18 @@ class QPolygonFNotIterableTest(unittest.TestCase):
         p << QPoint(10, 20) << QPoint(20, 30) << [QPoint(20, 30), QPoint(40, 50)]
         self.assertEqual(len(p), 4)
 
+    def testPolygonComparassion(self):
+        points = [QPointF(10, 20), QPointF(20, 30), QPointF(20, 30), QPointF(40, 50)]
+
+        p = QPolygonF(points)
+        other = QPolygonF(points)
+        self.assertEqual(p, other)
+
+        points = [QPointF(10, 10), QPointF(20, 20), QPointF(30, 30), QPointF(40, 40)]
+        polygon = QPolygonF(points)
+        self.assertNotEqual(p, polygon)
+        self.assertNotEqual(other, polygon)
+
 
 if __name__ == '__main__':
     unittest.main()
index 683b5825618a3d1801d252b90576a96561a33fd7..21de4a7502630cbe441280246d42594eee15c3df 100644 (file)
@@ -74,11 +74,12 @@ class QStandardItemModelRef(UsesQApplication):
 
         my_i = model.item(0, 0)
         # ref(my_i) + parent_ref + items list ref
-        self.assertEqual(sys.getrefcount(my_i), 4)
+        base_ref_count = 2 if sys.version_info >= (3, 14) else 3
+        self.assertEqual(sys.getrefcount(my_i), base_ref_count + 1)
 
         model.clear()
         # ref(my_i)
-        self.assertEqual(sys.getrefcount(my_i), 3)
+        self.assertEqual(sys.getrefcount(my_i), base_ref_count)
 
 
 if __name__ == '__main__':
index b7f346b2af6a4579bf4472bdf92b0fd850beb244..ceec27d2b0eaa585876295e48597f30dd57e9bab 100644 (file)
@@ -17,6 +17,9 @@ from PySide6.QtCore import (QCoreApplication, QObject, QParallelAnimationGroup,
 from PySide6.QtStateMachine import QEventTransition, QFinalState, QState, QStateMachine
 
 
+REF_COUNT_DELTA = 2 if sys.version_info >= (3, 14) else 1
+
+
 def addStates(transition):
     sx = QState()
     sy = QState()
@@ -73,7 +76,8 @@ class QAbstractTransitionTest(unittest.TestCase):
         transition.setTargetState(state1)
 
         self.assertEqual(transition.targetState(), state1)
-        self.assertEqual(sys.getrefcount(transition.targetState()), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetState()),
+                         refcount1 + REF_COUNT_DELTA)
 
         state2 = QState()
         refcount2 = sys.getrefcount(state2)
@@ -81,7 +85,8 @@ class QAbstractTransitionTest(unittest.TestCase):
         transition.setTargetState(state2)
 
         self.assertEqual(transition.targetState(), state2)
-        self.assertEqual(sys.getrefcount(transition.targetState()), refcount2 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetState()),
+                         refcount2 + REF_COUNT_DELTA)
         self.assertEqual(sys.getrefcount(state1), refcount1)
 
         del transition
@@ -101,8 +106,10 @@ class QAbstractTransitionTest(unittest.TestCase):
 
         self.assertEqual(transition.targetStates(), states)
         self.assertEqual(transition.targetState(), state1)
-        self.assertEqual(sys.getrefcount(transition.targetStates()[0]), refcount1 + 1)
-        self.assertEqual(sys.getrefcount(transition.targetStates()[1]), refcount2 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetStates()[0]),
+                         refcount1 + REF_COUNT_DELTA)
+        self.assertEqual(sys.getrefcount(transition.targetStates()[1]),
+                         refcount2 + REF_COUNT_DELTA)
 
         del states
         del transition
@@ -119,7 +126,8 @@ class QAbstractTransitionTest(unittest.TestCase):
         transition.setTargetState(state0)
 
         self.assertEqual(transition.targetState(), state0)
-        self.assertEqual(sys.getrefcount(transition.targetState()), refcount0 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetState()),
+                         refcount0 + REF_COUNT_DELTA)
 
         state1 = QState()
         state2 = QState()
@@ -132,8 +140,10 @@ class QAbstractTransitionTest(unittest.TestCase):
         self.assertEqual(sys.getrefcount(state0), refcount0)
         self.assertEqual(transition.targetStates(), states)
         self.assertEqual(transition.targetState(), state1)
-        self.assertEqual(sys.getrefcount(transition.targetStates()[0]), refcount1 + 1)
-        self.assertEqual(sys.getrefcount(transition.targetStates()[1]), refcount2 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetStates()[0]),
+                         refcount1 + REF_COUNT_DELTA)
+        self.assertEqual(sys.getrefcount(transition.targetStates()[1]),
+                         refcount2 + REF_COUNT_DELTA)
 
         del states
         del transition
@@ -154,8 +164,10 @@ class QAbstractTransitionTest(unittest.TestCase):
 
         self.assertEqual(transition.targetStates(), states)
         self.assertEqual(transition.targetState(), state1)
-        self.assertEqual(sys.getrefcount(transition.targetStates()[0]), refcount1 + 1)
-        self.assertEqual(sys.getrefcount(transition.targetStates()[1]), refcount2 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetStates()[0]),
+                         refcount1 + REF_COUNT_DELTA)
+        self.assertEqual(sys.getrefcount(transition.targetStates()[1]),
+                         refcount2 + REF_COUNT_DELTA)
 
         state3 = QState()
         refcount3 = sys.getrefcount(state3)
@@ -163,7 +175,8 @@ class QAbstractTransitionTest(unittest.TestCase):
         transition.setTargetState(state3)
 
         self.assertEqual(transition.targetState(), state3)
-        self.assertEqual(sys.getrefcount(transition.targetState()), refcount3 + 1)
+        self.assertEqual(sys.getrefcount(transition.targetState()),
+                         refcount3 + REF_COUNT_DELTA)
 
         del states
 
index 4d9bf472dec1d7b53fd492e30c08495267e1a09e..544732cc2401a3b176c8545e50b48e70c36e43a5 100644 (file)
@@ -16,6 +16,9 @@ from PySide6.QtCore import QBuffer
 from PySide6.QtSvg import QSvgGenerator
 
 
+REF_COUNT_DELTA = 2 if sys.version_info >= (3, 14) else 1
+
+
 class QSvgGeneratorTest(unittest.TestCase):
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
@@ -27,7 +30,8 @@ class QSvgGeneratorTest(unittest.TestCase):
         generator.setOutputDevice(iodevice1)
 
         self.assertEqual(generator.outputDevice(), iodevice1)
-        self.assertEqual(sys.getrefcount(generator.outputDevice()), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(generator.outputDevice()),
+                         refcount1 + REF_COUNT_DELTA)
 
         iodevice2 = QBuffer()
         refcount2 = sys.getrefcount(iodevice2)
@@ -35,7 +39,8 @@ class QSvgGeneratorTest(unittest.TestCase):
         generator.setOutputDevice(iodevice2)
 
         self.assertEqual(generator.outputDevice(), iodevice2)
-        self.assertEqual(sys.getrefcount(generator.outputDevice()), refcount2 + 1)
+        self.assertEqual(sys.getrefcount(generator.outputDevice()),
+                         refcount2 + REF_COUNT_DELTA)
         self.assertEqual(sys.getrefcount(iodevice1), refcount1)
 
         del generator
index e1a1df8f6bccf068e7ffd6d2af2b1c7b6e3c49cb..1be2936a24d99286fffb8116a9e9d67e60736081 100644 (file)
@@ -26,7 +26,8 @@ class TestDestruction(UsesQApplication):
         fileName = QFile(file)
         loader = QUiLoader()
         main_win = loader.load(fileName)
-        self.assertEqual(sys.getrefcount(main_win), 2)
+        expected_ref_count = 1 if sys.version_info >= (3, 14) else 2
+        self.assertEqual(sys.getrefcount(main_win), expected_ref_count)
         fileName.close()
 
         tw = QTabWidget(main_win)
index 61356c17365ed13b1968048e6dbde98ccfcaccd7..7ede387ebe96ca9a075c9c0cb2621188b78cfb36 100644 (file)
@@ -13,7 +13,7 @@ init_test_paths(False)
 
 from helper.usesqapplication import UsesQApplication
 
-from PySide6.QtCore import QCoreApplication, QTimer
+from PySide6.QtCore import QTimer
 from PySide6.QtGui import QPainter
 from PySide6.QtWidgets import QWidget
 
@@ -33,8 +33,6 @@ class TestQPainter(UsesQApplication):
     def testFontInfo(self):
         w = MyWidget()
         w.show()
-        while not w.windowHandle().isExposed():
-            QCoreApplication.processEvents()
         self.app.exec()
         self.assertTrue(w._info)
 
index 3e6ceb7b073ab5aa20d2d67ee26b05242c1c0764..60d168b63aeb27c813962fa3c7e9f5a49e828d8f 100644 (file)
@@ -33,14 +33,15 @@ class TestUserDataRefCount(UsesQApplication):
         cursor = QTextCursor(doc)
         cursor.insertText("PySide Rocks")
         ud = TestUserData({"Life": 42})
-        self.assertEqual(sys.getrefcount(ud), 2)
+        base_ref_count = sys.getrefcount(ud)
         cursor.block().setUserData(ud)
-        self.assertEqual(sys.getrefcount(ud), 3)
+        self.assertEqual(sys.getrefcount(ud), base_ref_count + 1)
         ud2 = cursor.block().userData()
-        self.assertEqual(sys.getrefcount(ud), 4)
+        self.assertEqual(sys.getrefcount(ud), base_ref_count + 2)
         self.udata = weakref.ref(ud, None)
         del ud, ud2
-        self.assertEqual(sys.getrefcount(self.udata()), 2)
+        delta = 1 if sys.version_info >= (3, 14) else 0
+        self.assertEqual(sys.getrefcount(self.udata()), base_ref_count + delta)
 
 
 if __name__ == '__main__':
index f93770aa03d719064099c4888b78cb6b18a02399..e9b962279986796bb12aa34e56f70f8f2f6341f1 100644 (file)
@@ -54,6 +54,8 @@ class KeepReferenceTest(UsesQApplication):
         refcount1 = sys.getrefcount(model1)
         view1 = QTableView()
         view1.setModel(model1)
+        if sys.version_info >= (3, 14):
+            refcount1 += 1
         self.assertEqual(sys.getrefcount(view1.model()), refcount1 + 1)
 
         view2 = QTableView()
@@ -69,9 +71,10 @@ class KeepReferenceTest(UsesQApplication):
         '''Tests reference count of model object referred by deceased view object.'''
         model = TestModel()
         refcount1 = sys.getrefcount(model)
+        delta = 2 if sys.version_info >= (3, 14) else 1
         view = QTableView()
         view.setModel(model)
-        self.assertEqual(sys.getrefcount(view.model()), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(view.model()), refcount1 + delta)
 
         del view
         self.assertEqual(sys.getrefcount(model), refcount1)
index 5fdb64d01401575239c5067d8893bcc56d334373..fbf0beeb64f36ce1a83edbaef57eeea80335c2dd 100644 (file)
@@ -127,6 +127,7 @@ class QAccessibleTest(UsesQApplication):
         QAccessible.installFactory(accessible_factory)
         window = Window()  # noqa: F841
 
+    @unittest.skipUnless(QAccessible.isActive(), "Accessibility is not active")
     def testLineEdits(self):
         window = Window()
         window.show()
index e9b0440c29895c0958623e718576233c3d0d940b..e123146585747a836ffa5b87e6914092ab0fadde 100644 (file)
@@ -12,7 +12,7 @@ from init_paths import init_test_paths
 init_test_paths(False)
 
 from helper.usesqapplication import UsesQApplication
-from PySide6.QtCore import QCoreApplication, QTimer
+from PySide6.QtCore import QTimer
 from PySide6.QtGui import QPicture, QPainter
 from PySide6.QtWidgets import QWidget
 
@@ -43,8 +43,6 @@ class QPictureTest(UsesQApplication):
         w = MyWidget(picture2)
 
         w.show()
-        while not w.windowHandle().isExposed():
-            QCoreApplication.processEvents()
         self.app.exec()
 
 
index 28c189e1689945dc0fa47418dfae0116e49fd01c..c1ff43b55eb761f82a54e74e604a058750cb486a 100644 (file)
@@ -12,6 +12,7 @@ from init_paths import init_test_paths
 init_test_paths(False)
 
 from PySide6.QtWidgets import QWidget, QMainWindow
+from PySide6.QtGui import QGuiApplication
 from helper.usesqapplication import UsesQApplication
 
 
@@ -47,10 +48,10 @@ class QWidgetVisible(UsesQApplication):
         widget.setVisible(True)
         self.assertTrue(widget.isVisible())
         self.assertTrue(widget.winId() != 0)
-        # skip this test on macOS since no native events are received
-        if sys.platform == 'darwin':
+        # skip this test on macOS/Wayland since no native events are received
+        if sys.platform == 'darwin' or QGuiApplication.platformName() == 'wayland':
             return
-        for i in range(10):
+        for i in range(100):
             if widget.nativeEventCount > 0:
                 break
             self.app.processEvents()
index 67a39e129a00c23dbd553babdb365316a01b4248..65cb8ec871515e5c29423f85503b56907a9b1cf1 100644 (file)
@@ -51,7 +51,8 @@ class ReferenceCount(UsesQApplication):
         self.wrp = weakref.ref(pol, pol_del)
 
         # refcount need be 3 because one ref for QGraphicsScene, and one to rect obj
-        self.assertEqual(sys.getrefcount(pol), 3)
+        expected_ref_count = 2 if sys.version_info >= (3, 14) else 3
+        self.assertEqual(sys.getrefcount(pol), expected_ref_count)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testReferenceCount(self):
@@ -66,7 +67,8 @@ class ReferenceCount(UsesQApplication):
         self.wrr = weakref.ref(rect, rect_del)
 
         # refcount need be 3 because one ref for QGraphicsScene, and one to rect obj
-        self.assertEqual(sys.getrefcount(rect), 3)
+        expected_ref_count = 2 if sys.version_info >= (3, 14) else 3
+        self.assertEqual(sys.getrefcount(rect), expected_ref_count)
 
         del rect
         # not destroyed because one ref continue in QGraphicsScene
index 8fe4f6e175e5813dd9b91d708ee34a25764e02c0..b321b1bdf69c67a465d5b57a4420556ff8442cc0 100644 (file)
@@ -44,18 +44,20 @@ class QDomDocumentTest(unittest.TestCase):
 
     def testQDomDocumentSetContentWithBadXmlData(self):
         '''Sets invalid xml as the QDomDocument contents.'''
-        ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.badXmlData, True)
-        self.assertFalse(ok)
-        self.assertEqual(errorStr, 'Opening and ending tag mismatch.')
-        self.assertEqual(errorLine, 4)
+        parseResult = self.dom.setContent(self.badXmlData,
+                                          QDomDocument.ParseOption.UseNamespaceProcessing)
+        self.assertFalse(parseResult)
+        self.assertEqual(parseResult.errorMessage, 'Opening and ending tag mismatch.')
+        self.assertEqual(parseResult.errorLine, 4)
 
     def testQDomDocumentSetContentWithGoodXmlData(self):
         '''Sets valid xml as the QDomDocument contents.'''
-        ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.goodXmlData, True)
-        self.assertTrue(ok)
-        self.assertEqual(errorStr, '')
-        self.assertEqual(errorLine, 0)
-        self.assertEqual(errorColumn, 0)
+        parseResult = self.dom.setContent(self.goodXmlData,
+                                          QDomDocument.ParseOption.UseNamespaceProcessing)
+        self.assertTrue(parseResult)
+        self.assertEqual(parseResult.errorMessage, '')
+        self.assertEqual(parseResult.errorLine, 0)
+        self.assertEqual(parseResult.errorColumn, 0)
 
     def testQDomDocumentData(self):
         '''Checks the QDomDocument elements for the valid xml contents.'''
@@ -66,7 +68,8 @@ class QDomDocumentTest(unittest.TestCase):
             self.assertTrue(element.hasAttribute(attribute))
             self.assertEqual(element.attribute(attribute), value)
 
-        ok, errorStr, errorLine, errorColumn = self.dom.setContent(self.goodXmlData, True)
+        parseResult = self.dom.setContent(self.goodXmlData,  # noqa F:841
+                                          QDomDocument.ParseOption.UseNamespaceProcessing)
         root = self.dom.documentElement()
         self.assertEqual(root.tagName(), 'typesystem')
         checkAttribute(root, 'package', 'PySide6.QtXml')
index 4a7e2e1d1de4a811e39269b18c59e2b797b8701f..8b4de5d8e91f51630be317eb77b45aec14551f11 100644 (file)
@@ -156,7 +156,6 @@ PYSIDE_TEST(new_inherited_functions_test.py)
 PYSIDE_TEST(notify_id.py)
 PYSIDE_TEST(properties_test.py)
 PYSIDE_TEST(property_python_test.py)
-PYSIDE_TEST(snake_case_sub.py)
 PYSIDE_TEST(snake_case_test.py)
 PYSIDE_TEST(true_property_test.py)
 PYSIDE_TEST(qapp_like_a_macro_test.py)
index ab20cbab456c00f1ab8a1acc308121f3a0956635..a60b4d1887960214951f4e80fca13d696253a8f0 100644 (file)
@@ -20,8 +20,8 @@ import dis
 class ListConnectionTest(unittest.TestCase):
 
     def testEnumVisibility(self):
-        self.assertEqual(Enum1.Option1, 1)
-        self.assertEqual(Enum1.Option2, 2)
+        self.assertEqual(Enum1.Option1.value, 1)
+        self.assertEqual(Enum1.Option2.value, 2)
         self.assertEqual(TestObjectWithoutNamespace.Enum2.Option3, 3)
         self.assertEqual(TestObjectWithoutNamespace.Enum2.Option4, 4)
 
index 83d80efca709eeff6452a8ac59dc3325ecd13f0e..9d836cfece925926cabb1e1bb7858c532e4ad1fc 100644 (file)
@@ -69,8 +69,9 @@ class MypyCorrectnessTest(unittest.TestCase):
     def testMypy(self):
         self.assertTrue(HAVE_MYPY)
         insert_version = ["--python-version", "3.11"] if sys.version_info[:2] < (3, 11) else []
+        exclusion = ["--exclude", "QtAsyncio"]
         cmd = ([sys.executable, "-m", "mypy", "--pretty", "--cache-dir", self.cache_dir]
-               + insert_version + [self.pyside_dir])
+               + exclusion + insert_version + [self.pyside_dir])
         time_pre = time.time()
         ret = subprocess.run(cmd, capture_output=True)
         time_post = time.time()
index 00d7aad1b2efa760412203e243f394f3759ce4d8..4452afe72e5c8da922bf073d401386715bbcb9c1 100644 (file)
@@ -76,6 +76,40 @@ class TestDerivedObject(QStringListModel):
                      notify=valueChanged)
 
 
+class SpecialProperties(QObject):
+    _value = 1
+
+    def __init__(self):
+        super().__init__()
+        self._readWriteInt = 2
+        self._readWriteDecoratedInt = 3
+
+    def readOnlyInt(self):  # Class variable properties
+        return 4
+
+    def readWriteInt(self):
+        return self._readWriteInt
+
+    def setReadWriteInt(self, v):
+        self._readWriteInt = v
+
+    @Property(int)  # Property decorators
+    def readOnlyDecoratedInt(self):
+        return 5
+
+    @Property(int)
+    def readWriteDecoratedInt(self):
+        return self._readWriteDecoratedInt
+
+    @readWriteDecoratedInt.setter
+    def readWriteDecoratedInt(self, v):
+        self._readWriteDecoratedInt = v
+
+    constantValue = Property(int, lambda self: self._value, constant=True)
+    readOnlyInt = Property(int, readOnlyInt)
+    readWriteInt = Property(int, readWriteInt, fset=setReadWriteInt)
+
+
 class PropertyTest(unittest.TestCase):
 
     def test1Object(self):
@@ -104,6 +138,49 @@ class PropertyTest(unittest.TestCase):
         self.assertEqual(testObject.setter_called, 1)
         self.assertEqual(testObject.getter_called, 2)
 
+    def testSpecialProperties(self):
+        """PYSIDE-924, PYSIDE-3227, constant, read-only."""
+        testObject = SpecialProperties()
+        mo = testObject.metaObject()
+
+        i = mo.indexOfProperty("constantValue")
+        self.assertTrue(i != -1)
+        metaProperty = mo.property(i)
+        self.assertTrue(metaProperty.isConstant())
+        self.assertEqual(testObject.constantValue, 1)
+
+        i = mo.indexOfProperty("readWriteInt")
+        self.assertTrue(i != -1)
+        metaProperty = mo.property(i)
+        self.assertTrue(metaProperty.isWritable())
+        self.assertEqual(testObject.readWriteInt, 2)
+        testObject.readWriteInt = 42
+        self.assertEqual(testObject.readWriteInt, 42)
+
+        i = mo.indexOfProperty("readWriteDecoratedInt")
+        self.assertTrue(i != -1)
+        metaProperty = mo.property(i)
+        self.assertTrue(metaProperty.isWritable())
+        self.assertEqual(testObject.readWriteDecoratedInt, 3)
+        testObject.readWriteDecoratedInt = 42
+        self.assertEqual(testObject.readWriteDecoratedInt, 42)
+
+        i = mo.indexOfProperty("readOnlyInt")
+        self.assertTrue(i != -1)
+        metaProperty = mo.property(i)
+        self.assertFalse(metaProperty.isWritable())
+        self.assertEqual(testObject.readOnlyInt, 4)
+        with self.assertRaises(AttributeError):
+            testObject.readOnlyInt = 42
+
+        i = mo.indexOfProperty("readOnlyDecoratedInt")
+        self.assertTrue(i != -1)
+        metaProperty = mo.property(i)
+        self.assertFalse(metaProperty.isWritable())
+        self.assertEqual(testObject.readOnlyDecoratedInt, 5)
+        with self.assertRaises(AttributeError):
+            testObject.readOnlyDecoratedInt = 42
+
 
 if __name__ == '__main__':
     unittest.main()
index 83b25b9789f92e569ce66df1f600e998befd7958..fee3b0109f3ef388c755cb9c7cc85a92841cd24c 100644 (file)
@@ -64,6 +64,9 @@ class QVariantTest(UsesQApplication):
         # check toInt() conversion for IntEnum
         self.assertEqual(PyTestQVariantEnum.getNumberFromQVarEnum(Qt.GestureType.TapGesture), 1)
 
+        # Test if enum still an enum on C++ land
+        self.assertTrue(TestQVariantEnum.isQtOrientationEnum(Qt.Orientation.Vertical))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/sources/pyside6/tests/pysidetest/snake_case_imported.py b/sources/pyside6/tests/pysidetest/snake_case_imported.py
new file mode 100644 (file)
index 0000000..c79966e
--- /dev/null
@@ -0,0 +1,25 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths  # noqa: E402
+init_test_paths(False)
+
+from __feature__ import snake_case  # noqa
+
+"""
+PYSIDE-3250: Tests that snake_case works when used in several files
+"""
+
+from PySide6.QtWidgets import QWidget  # noqa: E402
+
+
+def test():
+    print(__name__)
+    widget = QWidget()
+    return widget.size_hint()
diff --git a/sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py b/sources/pyside6/tests/pysidetest/snake_case_imported_no_snake_case.py
new file mode 100644 (file)
index 0000000..a5ce146
--- /dev/null
@@ -0,0 +1,23 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths  # noqa: E402
+init_test_paths(False)
+
+"""
+PYSIDE-2029: Tests that snake_case is isolated from imported modules
+"""
+
+from PySide6.QtWidgets import QWidget  # noqa: E402
+
+
+def test_no_snake_case():
+    print(__name__)
+    widget = QWidget()
+    return widget.sizeHint()
diff --git a/sources/pyside6/tests/pysidetest/snake_case_sub.py b/sources/pyside6/tests/pysidetest/snake_case_sub.py
deleted file mode 100644 (file)
index e423542..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-import os
-import sys
-
-from pathlib import Path
-sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths  # noqa: E402
-init_test_paths(False)
-
-"""
-PYSIDE-2029: Tests that snake_case is isolated from imported modules
-"""
-
-from PySide6.QtWidgets import QWidget  # noqa: E402
-
-
-def test_no_snake_case():
-    print(__name__)
-    widget = QWidget()
-    check = widget.sizeHint  # noqa
index bdcd996c4535bfe5e35a63b4deb8f0360668f56c..4667e584a519228d53581c395895996ad8b351db 100644 (file)
@@ -21,18 +21,26 @@ if not is_pypy:
     from __feature__ import snake_case  # noqa
 from helper.usesqapplication import UsesQApplication
 
-import snake_case_sub
+import snake_case_imported
+import snake_case_imported_no_snake_case
 
 
 @unittest.skipIf(is_pypy, "__feature__ cannot yet be used with PyPy")
 class SnakeCaseNoPropagateTest(UsesQApplication):
 
-    def testSnakeCase(self):
-        # this worked
+    def testSnakeCaseImport(self):
+        """PYSIDE-3250: Test that snake case works when using it in imported modules."""
         widget = QWidget()
-        check = widget.size_hint  # noqa
+        r1 = widget.size_hint()
+        r2 = snake_case_imported.test()
+        self.assertEqual(r1, r2)
 
-        snake_case_sub.test_no_snake_case()
+    def testSnakeCaseImportNoSnakeCase(self):
+        """PYSIDE-2029: Tests that snake_case is isolated from imported modules."""
+        widget = QWidget()
+        r1 = widget.size_hint()
+        r2 = snake_case_imported_no_snake_case.test_no_snake_case()
+        self.assertEqual(r1, r2)
 
 
 if __name__ == '__main__':
index 7135e422ada2e7d4805ed51fdeb3a7b75e90a913..043073e832ce12bbb0b800992a2364dcf1d0be7d 100644 (file)
@@ -27,3 +27,8 @@ bool TestQVariantEnum::isEnumChanneled() const
 {
     return this->channelingEnum(this->getRValEnum());
 }
+
+bool TestQVariantEnum::isQtOrientationEnum(QVariant var)
+{
+    return (var.typeId() == QMetaType::fromType<Qt::Orientation>().id());
+}
index 4b729e3dd6a908239cc3f981205fcd2462996b0d..d2923174322c41a7093fd7d2c6a67e0196cf0c85 100644 (file)
@@ -18,6 +18,8 @@ public:
     virtual QVariant getRValEnum() const;
     virtual bool channelingEnum(QVariant rvalEnum) const;
     virtual ~TestQVariantEnum() = default;
+    static bool isQtOrientationEnum(QVariant var);
+
 private:
     QVariant m_enum;
 };
index 29ef312b34fd3e4fc504de4a1e93cadb475e7348..c94a2735e7e1834a5b1ffcba32ef42e178bd482d 100644 (file)
@@ -137,6 +137,20 @@ class TestDisconnect(unittest.TestCase):
         obj.signalWithDefaultValue.emit()
         self.assertTrue(self.called)
 
+    def testMultipleConnections(self):
+        """PYSIDE-3190: Signal.disconnect() should use the QMetaObject
+           code to disconnect, disconnecting multiple connections."""
+        s = Sender()
+        r = Receiver()
+        s.bar.connect(r.receiver)
+        s.bar.connect(r.receiver)
+        s.bar.emit()
+        print(r.called)
+        self.assertEqual(r.called, 2)
+        s.bar.disconnect(r.receiver)
+        s.bar.emit()
+        self.assertEqual(r.called, 2)
+
 
 if __name__ == '__main__':
     unittest.main()
index 0e8ad2c0bc2d78c4972a8f4cb01ec136325bea14..12746698330417f1df511a761c2da2e2fe4785b9 100644 (file)
@@ -14,9 +14,11 @@ sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
 from init_paths import init_test_paths
 init_test_paths(False)
 
-from PySide6.QtCore import QCoreApplication, QObject, QTimer, Signal
+from PySide6.QtCore import QCoreApplication, QObject, QTimer, Signal, Slot
 from helper.usesqapplication import UsesQApplication
 
+import samenamesender
+
 
 class ExtQTimer(QTimer):
     def __init__(self):
@@ -27,6 +29,30 @@ class Sender(QObject):
     foo = Signal()
 
 
+class SameNameSender(samenamesender.SameNameSender):
+    ''' Test sender class for SameNameSenderTest (PYSIDE-3201).'''
+    signal3 = Signal()
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+        self.signal1.connect(self.slot2)
+        self.signal2.connect(self.slot2)
+        self.signal3.connect(self.slot3)
+        self.slot3_invoked = False
+
+    @Slot()
+    def slot1(self):
+        pass
+
+    @Slot()
+    def slot2(self):
+        pass
+
+    @Slot()
+    def slot3(self):
+        self.slot3_invoked = True
+
+
 class Receiver(QObject):
     def __init__(self):
         super().__init__()
@@ -38,6 +64,15 @@ class Receiver(QObject):
             QCoreApplication.instance().exit()
 
 
+class ResultReceiver(QObject):
+    def __init__(self):
+        super().__init__()
+
+    @Slot(result=int)
+    def slotWithResult(self):
+        return 3
+
+
 class ObjectSenderTest(unittest.TestCase):
     '''Test case for QObject.sender() method.'''
 
@@ -114,5 +149,26 @@ class ObjectSenderWithQAppCheckOnReceiverTest(UsesQApplication):
         self.assertEqual(sender, recv.the_sender)
 
 
+class SameNameSenderTest(UsesQApplication):
+    '''PYSIDE-3201: Test whether the meta object system is confused by identical
+       class names.'''
+    def test(self):
+        sender = SameNameSender()
+        sender.signal1.emit()
+        sender.signal2.emit()
+        sender.signal3.emit()
+        self.assertTrue(sender.slot3_invoked)
+
+
+class ResultSlotTest(UsesQApplication):
+    '''PYSIDE-3266: Test that a slot declaring a result type does not cause crashes
+       in signal connections due to args[0] == 0.'''
+    def test(self):
+        sender = Sender()
+        recv = ResultReceiver()
+        sender.foo.connect(recv.slotWithResult)
+        sender.foo.emit()
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/sources/pyside6/tests/signals/samenamesender.py b/sources/pyside6/tests/signals/samenamesender.py
new file mode 100644 (file)
index 0000000..269be99
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+from PySide6.QtCore import Signal, QObject
+
+
+class SameNameSender(QObject):
+    ''' Base class for the Test sender class of SameNameSenderTest (PYSIDE-3201).'''
+    signal1 = Signal()
+    signal2 = Signal()
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
index 648b07a50832bd126391e0d6d818ec687632635f..a4d41275e4db9b82b52aff56cf28441bdfd98132 100644 (file)
@@ -330,11 +330,6 @@ class TestPySide6DeployQml(DeployTestBase):
         expected_modules = {"Core", "Gui", "Qml", "Quick", "Network", "OpenGL", "QmlModels",
                             "QmlWorkerScript", "QmlMeta"}
 
-        # Exclude OpenGL if the platform is Windows and architecture is aarch64
-        # https://bugreports.qt.io/browse/QTBUG-126030
-        if sys.platform == "win32" and platform.machine() == "ARM64":
-            expected_modules.remove("OpenGL")
-
         if sys.platform != "win32":
             expected_modules.add("DBus")
         obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
@@ -395,6 +390,7 @@ class TestPySide6DeployWebEngine(DeployTestBase):
             "DownloadView.qml",
             "FindBar.qml",
             "FullScreenNotification.qml",
+            "WebAuthDialog.qml"
         ]
         data_files_cmd = " ".join(
             [
@@ -460,7 +456,7 @@ class TestPySide6DeployWebEngine(DeployTestBase):
 
         # Exclude specific modules if the platform is Windows and architecture is aarch64
         if sys.platform == "win32" and platform.machine() == "ARM64":
-            excluded_modules = {"OpenGL", "WebEngineCore", "Positioning", "WebChannelQuick",
+            excluded_modules = {"WebEngineCore", "Positioning", "WebChannelQuick",
                                 "WebChannel"}
             expected_modules.difference_update(excluded_modules)
 
index 1ceb0ac0b12e7418da902cc8b8e0667a5151043e..b6f16e69853a2ea3402dd46fe71af7e40c49836a 100644 (file)
@@ -2,4 +2,4 @@
 name = "subproject"
 
 [tool.pyside6-project]
-files = ["subproject_button.py"]
+files = ["subproject_button.py", "../main.py"]
index be8aa949f90c01472d16b040bff231c5e261251a..c0adb0c762562b45a5ef5608021ea3c22bd6bd07 100644 (file)
@@ -15,8 +15,9 @@ target-version = ["py38"]
 
 # Another comment
 
-[tool.pyside6-project]
-files = ["main.py", "zzz.py"]
 [build-system]
 requires = ["setuptools >=42"]
 build-backend = "setuptools.build_meta"
+
+[tool.pyside6-project]
+files = ["main.py", "zzz.py"]
index d663952519c6e1c9ebcc60e2d5d7dc337246ec0c..cbacc4e487c70b4e8d69d8b1f501a49b802a9ca9 100644 (file)
@@ -1,5 +1,7 @@
 # Copyright (C) 2024 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
+
 import contextlib
 import difflib
 import importlib
index 248b66f5f5c95ec8fa90794a08eded8fa7fdc3df..7bd76c7cdedd2a3955f02ec194f4cec08e512047 100644 (file)
@@ -1,5 +1,5 @@
 set(shiboken_MAJOR_VERSION "6")
-set(shiboken_MINOR_VERSION "9")
+set(shiboken_MINOR_VERSION "10")
 set(shiboken_MICRO_VERSION "2")
 set(shiboken_PRE_RELEASE_VERSION_TYPE "")
 set(shiboken_PRE_RELEASE_VERSION "")
index a9c61fdd35b2975176e774b657d8c73256fe0ed0..9807e1ea6d5bcb48a7921cf3ee78a889ce685657 100644 (file)
@@ -453,12 +453,29 @@ FileModelItem AbstractMetaBuilderPrivate::buildDom(QByteArrayList arguments,
                                                    unsigned clangFlags)
 {
     clang::Builder builder;
+    clang::setHeuristicOptions(arguments);
     builder.setForceProcessSystemIncludes(TypeDatabase::instance()->forceProcessSystemIncludes());
     if (addCompilerSupportArguments) {
         if (level == LanguageLevel::Default)
             level = clang::emulatedCompilerLanguageLevel();
         arguments.prepend(QByteArrayLiteral("-std=")
                           + clang::languageLevelOption(level));
+        // Add target for qsystemdetection.h to set the right Q_OS_ definitions
+        if (clang::isCrossCompilation() && !clang::hasTargetOption(arguments)) {
+            const auto triplet = clang::targetTripletForPlatform(clang::platform(),
+                                                                 clang::architecture(),
+                                                                 clang::compiler(),
+                                                                 clang::platformVersion());
+            if (triplet.isEmpty()) {
+                qCWarning(lcShiboken,
+                          "Unable to determine a cross compilation target triplet (%d/%d/%d).",
+                          int(clang::platform()), int(clang::architecture()), int(clang::compiler()));
+            } else {
+                arguments.prepend("--target="_ba + triplet);
+                const auto msg = "Setting clang target: "_L1 + QLatin1StringView(triplet);
+                ReportHandler::addGeneralMessage(msg);
+            }
+        }
     }
     FileModelItem result = clang::parse(arguments, addCompilerSupportArguments,
                                         level, clangFlags, builder)
@@ -514,13 +531,22 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom,
     ReportHandler::startProgress("Generated enum model ("
                                  + QByteArray::number(enums.size()) + ").");
     for (const EnumModelItem &item : enums) {
-        auto metaEnum = traverseEnum(item, nullptr, QSet<QString>());
+        auto metaEnum = traverseEnum(item, nullptr);
         if (metaEnum.has_value()) {
             if (metaEnum->typeEntry()->generateCode())
                 m_globalEnums << metaEnum.value();
         }
     }
 
+    const auto &globalTypeDefs = dom->typeDefs();
+    for (const auto &typeDef : globalTypeDefs) {
+        if (typeDef->underlyingTypeCategory() == TypeCategory::Enum) {
+            const auto metaEnum = traverseTypedefedEnum(dom, typeDef, {});
+            if (metaEnum.has_value())
+                m_globalEnums.append(metaEnum.value());
+        }
+    }
+
     const auto &namespaceTypeValues = dom->namespaces();
     ReportHandler::startProgress("Generated namespace model ("
                                  + QByteArray::number(namespaceTypeValues.size()) + ").");
@@ -748,7 +774,7 @@ AbstractMetaClassPtr
     AbstractMetaBuilderPrivate::traverseNamespace(const FileModelItem &dom,
                                                   const NamespaceModelItem &namespaceItem)
 {
-    QString namespaceName = currentScope()->qualifiedName().join(u"::"_s);
+    QString namespaceName = currentScope()->qualifiedNameString();
     if (!namespaceName.isEmpty())
         namespaceName.append(u"::"_s);
     namespaceName.append(namespaceItem->name());
@@ -792,7 +818,7 @@ AbstractMetaClassPtr
         m_itemToClass.insert(namespaceItem.get(), metaClass);
     }
 
-    traverseEnums(namespaceItem, metaClass, namespaceItem->enumsDeclarations());
+    traverseEnums(namespaceItem, metaClass);
 
     pushScope(namespaceItem);
 
@@ -810,11 +836,20 @@ AbstractMetaClassPtr
     // specific typedefs to be used as classes.
     const TypeDefList typeDefs = namespaceItem->typeDefs();
     for (const TypeDefModelItem &typeDef : typeDefs) {
-        const auto cls = traverseTypeDef(dom, typeDef, metaClass);
-        if (cls) {
-            metaClass->addInnerClass(cls);
-            cls->setEnclosingClass(metaClass);
-            addAbstractMetaClass(cls, typeDef.get());
+        switch (typeDef->underlyingTypeCategory()) {
+        case TypeCategory::Enum: {
+            const auto metaEnum = traverseTypedefedEnum(dom, typeDef, metaClass);
+            if (metaEnum.has_value())
+                metaClass->addEnum(metaEnum.value());
+        }
+            break;
+        default:
+            if (const auto cls = traverseTypeDef(dom, typeDef, metaClass)) {
+                metaClass->addInnerClass(cls);
+                cls->setEnclosingClass(metaClass);
+                addAbstractMetaClass(cls, typeDef.get());
+            }
+            break;
         }
     }
 
@@ -839,16 +874,15 @@ AbstractMetaClassPtr
 
 std::optional<AbstractMetaEnum>
     AbstractMetaBuilderPrivate::traverseEnum(const EnumModelItem &enumItem,
-                                             const AbstractMetaClassPtr &enclosing,
-                                             const QSet<QString> &enumsDeclarations)
+                                             const AbstractMetaClassPtr &enclosing)
 {
-    QString qualifiedName = enumItem->qualifiedName().join(u"::"_s);
+    QString qualifiedName = enumItem->qualifiedNameString();
 
     TypeEntryPtr typeEntry;
-    const auto enclosingTypeEntry = enclosing ? enclosing->typeEntry() : TypeEntryCPtr{};
     if (enumItem->accessPolicy() == Access::Private) {
+        Q_ASSERT(enclosing);
         typeEntry = std::make_shared<EnumTypeEntry>(enumItem->qualifiedName().constLast(),
-                                                    QVersionNumber(0, 0), enclosingTypeEntry);
+                                                    QVersionNumber(0, 0), enclosing->typeEntry());
         TypeDatabase::instance()->addType(typeEntry);
     } else if (enumItem->enumKind() != AnonymousEnum) {
         typeEntry = TypeDatabase::instance()->findType(qualifiedName);
@@ -864,12 +898,17 @@ std::optional<AbstractMetaEnum>
                 break;
         }
     }
+    return createMetaEnum(enumItem, qualifiedName, typeEntry, enclosing);
+}
 
-    QString enumName = enumItem->name();
-
-    QString className;
-    if (enclosingTypeEntry)
-        className = enclosingTypeEntry->qualifiedCppName();
+std::optional<AbstractMetaEnum>
+    AbstractMetaBuilderPrivate::createMetaEnum(const EnumModelItem &enumItem,
+                                               const QString &qualifiedName,
+                                               const TypeEntryPtr &typeEntry,
+                                               const AbstractMetaClassPtr &enclosing)
+{
+    const QString enumName = enumItem->name();
+    const QString className = enclosing ? enclosing->typeEntry()->qualifiedCppName() : QString{};
 
     QString rejectReason;
     if (TypeDatabase::instance()->isEnumRejected(className, enumName, &rejectReason)) {
@@ -905,10 +944,6 @@ std::optional<AbstractMetaEnum>
     metaEnum.setDeprecated(enumItem->isDeprecated());
     metaEnum.setUnderlyingType(enumItem->underlyingType());
     metaEnum.setSigned(enumItem->isSigned());
-    if (enumsDeclarations.contains(qualifiedName)
-        || enumsDeclarations.contains(enumName)) {
-        metaEnum.setHasQEnumsDeclaration(true);
-    }
 
     auto enumTypeEntry = std::static_pointer_cast<EnumTypeEntry>(typeEntry);
     metaEnum.setTypeEntry(enumTypeEntry);
@@ -955,6 +990,49 @@ std::optional<AbstractMetaEnum>
     return metaEnum;
 }
 
+// Add typedef'ed enumerations ("Using MyEnum=SomeNamespace::MyEnum") for which
+// a type entry exists.
+std::optional<AbstractMetaEnum>
+    AbstractMetaBuilderPrivate::traverseTypedefedEnum(const FileModelItem &dom,
+                                                      const TypeDefModelItem &typeDefItem,
+                                                      const AbstractMetaClassPtr &enclosing)
+{
+    if (enclosing && typeDefItem->accessPolicy() != Access::Public)
+        return std::nullopt; // Only for global/public enums typedef'ed into classes/namespaces
+    auto modelItem = CodeModel::findItem(typeDefItem->type().qualifiedName(), dom);
+    if (!modelItem || modelItem->kind() != _CodeModelItem::Kind_Enum)
+        return std::nullopt;
+    auto enumItem = std::static_pointer_cast<_EnumModelItem>(modelItem);
+    if (enumItem->accessPolicy() != Access::Public)
+        return std::nullopt;
+    // Name in class
+    QString qualifiedName = enclosing
+        ? enclosing->qualifiedCppName() + "::"_L1 + typeDefItem->name() : typeDefItem->name();
+    auto targetTypeEntry = TypeDatabase::instance()->findType(qualifiedName);
+    if (!targetTypeEntry || !targetTypeEntry->isEnum() || !targetTypeEntry->generateCode())
+        return std::nullopt;
+    auto targetEnumTypeEntry = std::static_pointer_cast<EnumTypeEntry>(targetTypeEntry);
+    auto sourceTypeEntry = TypeDatabase::instance()->findType(enumItem->qualifiedNameString());
+    if (!sourceTypeEntry || !sourceTypeEntry->isEnum())
+        return std::nullopt;
+
+    auto sourceEnumTypeEntry = std::static_pointer_cast<EnumTypeEntry>(sourceTypeEntry);
+    if (sourceEnumTypeEntry == targetEnumTypeEntry) // Reject "typedef Enum1 { V1 } Enum1;"
+        return std::nullopt;
+
+    const QString message = "Enum \""_L1 + qualifiedName + "\" is an alias to \""_L1
+                            + enumItem->qualifiedNameString() + "\"."_L1;
+    ReportHandler::addGeneralMessage(message);
+    auto result = createMetaEnum(enumItem, qualifiedName, targetTypeEntry, enclosing);
+    if (result.has_value()) {
+        targetEnumTypeEntry->setAliasMode(EnumTypeEntry::AliasTarget);
+        targetEnumTypeEntry->setAliasTypeEntry(sourceEnumTypeEntry);
+        sourceEnumTypeEntry->setAliasMode(EnumTypeEntry::AliasSource);
+        sourceEnumTypeEntry->setAliasTypeEntry(targetEnumTypeEntry);
+    }
+    return result;
+}
+
 AbstractMetaClassPtr
     AbstractMetaBuilderPrivate::traverseTypeDef(const FileModelItem &dom,
                                                 const TypeDefModelItem &typeDef,
@@ -1160,7 +1238,7 @@ AbstractMetaClassPtr AbstractMetaBuilderPrivate::traverseClass(const FileModelIt
 
     parseQ_Properties(metaClass, classItem->propertyDeclarations());
 
-    traverseEnums(classItem, metaClass, classItem->enumsDeclarations());
+    traverseEnums(classItem, metaClass);
 
     // Inner classes
     {
@@ -1180,10 +1258,20 @@ AbstractMetaClassPtr AbstractMetaBuilderPrivate::traverseClass(const FileModelIt
     // specific typedefs to be used as classes.
     const TypeDefList typeDefs = classItem->typeDefs();
     for (const TypeDefModelItem &typeDef : typeDefs) {
-        const auto cls = traverseTypeDef(dom, typeDef, metaClass);
-        if (cls) {
-            cls->setEnclosingClass(metaClass);
-            addAbstractMetaClass(cls, typeDef.get());
+        if (typeDef->accessPolicy() != Access::Private) {
+            switch (typeDef->underlyingTypeCategory()) {
+            case TypeCategory::Enum: {
+                const auto metaEnum = traverseTypedefedEnum(dom, typeDef, metaClass);
+                if (metaEnum.has_value())
+                    metaClass->addEnum(metaEnum.value());
+            }
+                break;
+            default:
+                if (const auto cls = traverseTypeDef(dom, typeDef, metaClass)) {
+                    cls->setEnclosingClass(metaClass);
+                    addAbstractMetaClass(cls, typeDef.get());
+                }
+            }
         }
     }
 
@@ -1631,13 +1719,11 @@ bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &me
 }
 
 void AbstractMetaBuilderPrivate::traverseEnums(const ScopeModelItem &scopeItem,
-                                               const AbstractMetaClassPtr &metaClass,
-                                               const QStringList &enumsDeclarations)
+                                               const AbstractMetaClassPtr &metaClass)
 {
     const EnumList &enums = scopeItem->enums();
-    const QSet<QString> enumsDeclarationSet(enumsDeclarations.cbegin(), enumsDeclarations.cend());
     for (const EnumModelItem &enumItem : enums) {
-        auto metaEnum = traverseEnum(enumItem, metaClass, enumsDeclarationSet);
+        auto metaEnum = traverseEnum(enumItem, metaClass);
         if (metaEnum.has_value()) {
             metaClass->addEnum(metaEnum.value());
         }
@@ -1696,10 +1782,7 @@ AbstractMetaFunctionPtr
 
     const auto &args = addedFunc->arguments();
 
-    qsizetype argCount = args.size();
-    // Check "foo(void)"
-    if (argCount == 1 && args.constFirst().typeInfo.isVoid())
-        argCount = 0;
+    const qsizetype argCount = args.size();
     for (qsizetype i = 0; i < argCount; ++i) {
         const AddedFunction::Argument &arg = args.at(i);
         auto type = translateType(arg.typeInfo, metaClass, {}, errorMessage);
@@ -2771,6 +2854,16 @@ std::optional<AbstractMetaType>
 
     TypeEntryCList types = findTypeEntries(qualifiedName, name, flags,
                                            currentClass, d, errorMessageIn);
+    if (types.isEmpty() && !typeInfo.instantiations().isEmpty()) {
+        // Allow for specifying template specializations as primitive types
+        // with converters ('std::optional<int>' or similar).
+        auto pt = TypeDatabase::instance()->findPrimitiveType(typeInfo.qualifiedInstantationName());
+        if (pt) {
+            types.append(pt);
+            typeInfo.clearInstantiations();
+        }
+    }
+
     if (!flags.testFlag(AbstractMetaBuilder::TemplateArgument)) {
         // Avoid clashes between QByteArray and enum value QMetaType::QByteArray
         // unless we are looking for template arguments.
index 45dd21e0260787b52801083d5bc5e7db4b7d7ce8..0a09d578b17dd89aeb73fc045873faafb18ef453 100644 (file)
@@ -94,10 +94,14 @@ public:
     AbstractMetaClassPtr traverseNamespace(const FileModelItem &dom,
                                          const NamespaceModelItem &item);
     std::optional<AbstractMetaEnum> traverseEnum(const EnumModelItem &item,
-                                                 const AbstractMetaClassPtr &enclosing,
-                                                 const QSet<QString> &enumsDeclarations);
-    void traverseEnums(const ScopeModelItem &item, const AbstractMetaClassPtr &parent,
-                       const QStringList &enumsDeclarations);
+                                                 const AbstractMetaClassPtr &enclosing);
+    void traverseEnums(const ScopeModelItem &item, const AbstractMetaClassPtr &parent);
+    std::optional<AbstractMetaEnum>
+        createMetaEnum(const EnumModelItem &enumItem, const QString &qualifiedName,
+                       const TypeEntryPtr &typeEntry, const AbstractMetaClassPtr &enclosing);
+    std::optional<AbstractMetaEnum>
+        traverseTypedefedEnum(const FileModelItem &dom, const TypeDefModelItem &typeDefItem,
+                              const AbstractMetaClassPtr &enclosing);
     AbstractMetaFunctionList classFunctionList(const ScopeModelItem &scopeItem,
                                                AbstractMetaClass::Attributes *constructorAttributes,
                                                const AbstractMetaClassPtr &currentClass);
index 486d3d39c34538a05e8d6caf803911614aed5e6f..3f2714ffcc78716052abd663c30194e939e6bdb6 100644 (file)
@@ -96,8 +96,7 @@ void AbstractMetaEnumValue::setDocumentation(const Documentation &doc)
 class AbstractMetaEnumData : public QSharedData
 {
 public:
-    AbstractMetaEnumData()  : m_deprecated(false),
-        m_hasQenumsDeclaration(false), m_signed(true)
+    AbstractMetaEnumData()  : m_deprecated(false), m_signed(true)
     {
     }
 
@@ -113,7 +112,6 @@ public:
     EnumKind m_enumKind = CEnum;
     Access m_access = Access::Public;
     uint m_deprecated : 1;
-    uint m_hasQenumsDeclaration : 1;
     uint m_signed : 1;
 };
 
@@ -298,17 +296,6 @@ bool AbstractMetaEnum::isAnonymous() const
     return d->m_enumKind == AnonymousEnum;
 }
 
-bool AbstractMetaEnum::hasQEnumsDeclaration() const
-{
-    return d->m_hasQenumsDeclaration;
-}
-
-void AbstractMetaEnum::setHasQEnumsDeclaration(bool on)
-{
-    if (d->m_hasQenumsDeclaration != on)
-        d->m_hasQenumsDeclaration = on;
-}
-
 EnumTypeEntryCPtr AbstractMetaEnum::typeEntry() const
 {
     return d->m_typeEntry;
index cfaa9b9bb16f92097a9d61dd05abdcc735858256..c7edb74f6d432ddad11116e3159121ad33b9f386 100644 (file)
@@ -96,10 +96,6 @@ public:
 
     bool isAnonymous() const;
 
-    // Has the enum been declared inside a Q_ENUMS() macro in its enclosing class?
-    bool hasQEnumsDeclaration() const;
-    void setHasQEnumsDeclaration(bool on);
-
     EnumTypeEntryCPtr typeEntry() const;
     void setTypeEntry(const EnumTypeEntryCPtr &entry);
 
index e88f354bbd17df81a06681480f7f961d1f6f934a..107d89d519e36a62083518abac0de295d4068129 100644 (file)
@@ -172,7 +172,7 @@ bool AbstractMetaClass::isPolymorphic() const
 AbstractMetaFunctionCList AbstractMetaClass::queryFunctionsByName(const QString &name) const
 {
     AbstractMetaFunctionCList returned;
-    for (const auto &function : d->m_functions) {
+    for (const auto &function : std::as_const(d->m_functions)) {
         if (function->name() == name)
             returned.append(function);
     }
@@ -434,7 +434,7 @@ bool AbstractMetaClass::hasSignal(const AbstractMetaFunction *other) const
     if (!other->isSignal())
         return false;
 
-    for (const auto &f : d->m_functions) {
+    for (const auto &f : std::as_const(d->m_functions)) {
         if (f->isSignal() && f->compareTo(other) & AbstractMetaFunction::EqualName)
             return other->modifiedName() == f->modifiedName();
     }
@@ -1395,7 +1395,7 @@ void AbstractMetaClass::addEnum(const AbstractMetaEnum &e)
 std::optional<AbstractMetaEnum>
     AbstractMetaClass::findEnum(const QString &enumName) const
 {
-    for (const auto &e : d->m_enums) {
+    for (const auto &e : std::as_const(d->m_enums)) {
         if (e.name() == enumName)
             return e;
     }
@@ -1421,7 +1421,7 @@ std::optional<AbstractMetaEnumValue>
 
 void AbstractMetaClass::getEnumsToBeGenerated(AbstractMetaEnumList *enumList) const
 {
-    for (const AbstractMetaEnum &metaEnum : d->m_enums) {
+    for (const AbstractMetaEnum &metaEnum : std::as_const(d->m_enums)) {
         if (!metaEnum.isPrivate() && metaEnum.typeEntry()->generateCode())
             enumList->append(metaEnum);
     }
@@ -1496,7 +1496,7 @@ void AbstractMetaClassPrivate::addUsingConstructors(const AbstractMetaClassPtr &
         return;
     }
 
-    for (const auto &superClass : m_baseClasses) {
+    for (const auto &superClass : std::as_const(m_baseClasses)) {
         // Find any "using base-constructor" directives
         if (isUsingMember(superClass, superClass->name(), Access::Protected)) {
             // Add to derived class with parameter lists.
@@ -1538,7 +1538,7 @@ void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass, bool avo
             nonRemovedFuncs.append(f);
     }
 
-    for (const auto &superClassC : d->m_baseClasses) {
+    for (const auto &superClassC : std::as_const(d->m_baseClasses)) {
         for (const auto &pof : superClassC->userAddedPythonOverrides()) {
             auto *clonedPof = pof->copy();
             clonedPof->setOwnerClass(klass);
@@ -1941,7 +1941,7 @@ void AbstractMetaClass::format(QDebug &debug) const
         debug << " [deleted move assignment]";
     if (!d->m_baseClasses.isEmpty()) {
         debug << ", inherits ";
-        for (const auto &b : d->m_baseClasses)
+        for (const auto &b : std::as_const(d->m_baseClasses))
             debug << " \"" << b->name() << '"';
     }
 
index ee8009cfee6a1964718c1823e30d6e3c677bd716..649296066c185f176e289cb667e377fb61ae7382 100644 (file)
@@ -165,10 +165,12 @@ AddedFunction::AddedFunctionPtr
     }
 
     const auto paramString = signature.mid(openParenPos + 1, closingParenPos - openParenPos - 1);
-    const auto params = AddedFunctionParser::splitParameters(paramString, errorMessage);
+    auto params = AddedFunctionParser::splitParameters(paramString, errorMessage);
     if (params.isEmpty() && !errorMessage->isEmpty())
         return {};
-    for (const auto &p : params) {
+    if (params.size() == 1 && params.constFirst().type == "void"_L1)
+        params.clear(); // "void foo(void)" -> ""void foo()"
+    for (const auto &p : std::as_const(params)) {
         TypeInfo type = p.type == u"..."
             ? TypeInfo::varArgsType() : TypeParser::parse(p.type, errorMessage);
         if (!errorMessage->isEmpty()) {
index 38dedfd148fd078214cc4c7f7a7b1aa29532c03b..1c398313b371fa7653754f475e28fe384a4c02f7 100644 (file)
@@ -370,7 +370,6 @@ bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags)
 
     bool addCompilerSupportArguments = true;
     if (clangOptionsSize > 0) {
-        clang::setTargetTriple(m_clangOptions);
         qsizetype i = 0;
         if (m_clangOptions.at(i) == u"-") {
             ++i;
@@ -614,8 +613,8 @@ ApiExtractorPrivate::addInstantiatedContainersAndSmartPointers(InstantiationColl
     if (type.hasTemplateChildren()) {
         const auto piece = isContainer ? "container"_L1 : "smart pointer"_L1;
         QString warning =
-            QString::fromLatin1("Skipping instantiation of %1 '%2' because it has template"
-                                " arguments.").arg(piece, type.originalTypeDescription());
+            "Skipping instantiation of %1 '%2' because it has template"
+            " arguments."_L1.arg(piece, type.originalTypeDescription());
         if (!contextName.isEmpty())
             warning.append(" Calling context: "_L1 + contextName);
 
index 5188262de68a921a7a89455d734f3d846343df2f..cc2924287bed2c96b505183e7c582c06ae590012 100644 (file)
@@ -524,6 +524,27 @@ void BuilderPrivate::addTemplateInstantiations(const CXType &type,
         typeName->remove(pos.first, pos.second - pos.first);
 }
 
+static TypeCategory typeCategoryFromClang(CXTypeKind k)
+{
+    switch (k) {
+    case CXType_Void:
+        return TypeCategory::Void;
+    case CXType_Enum:
+        return TypeCategory::Enum;
+    case CXType_Pointer:
+    case CXType_BlockPointer:
+        return TypeCategory::Pointer;
+    case CXType_FunctionNoProto:
+    case CXType_FunctionProto:
+        return TypeCategory::Function;
+    default:
+        break;
+    }
+    if (k >= CXType_FirstBuiltin && k <= CXType_LastBuiltin)
+        return TypeCategory::Builtin;
+    return TypeCategory::Other;
+}
+
 TypeInfo BuilderPrivate::createTypeInfoUncached(const CXType &type,
                                                 bool *cacheable) const
 {
@@ -533,6 +554,7 @@ TypeInfo BuilderPrivate::createTypeInfoUncached(const CXType &type,
         if (argCount >= 0) {
             TypeInfo result = createTypeInfoUncached(clang_getResultType(pointeeType),
                                                      cacheable);
+            result.setTypeCategory(TypeCategory::Pointer);
             result.setFunctionPointer(true);
             for (int a = 0; a < argCount; ++a)
                 result.addArgument(createTypeInfoUncached(clang_getArgType(pointeeType, unsigned(a)),
@@ -542,6 +564,7 @@ TypeInfo BuilderPrivate::createTypeInfoUncached(const CXType &type,
     }
 
     TypeInfo typeInfo;
+    typeInfo.setTypeCategory(typeCategoryFromClang(clang_getCanonicalType(type).kind));
 
     CXType nestedType = type;
     for (; isArrayType(nestedType.kind); nestedType = clang_getArrayElementType(nestedType)) {
@@ -623,6 +646,7 @@ void BuilderPrivate::addTypeDef(const CXCursor &cursor, const CXType &cxType)
     setFileName(cursor, item.get());
     item->setType(createTypeInfo(cxType));
     item->setScope(m_scope);
+    item->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor)));
     m_scopeStack.back()->addTypeDef(item);
 }
 
@@ -827,6 +851,7 @@ BuilderPrivate::SpecialSystemHeader
     }
 
     switch (clang::platform()) {
+    case Platform::Linux:
     case Platform::Unix:
         if (fileName == u"/usr/include/stdlib.h"
             || baseName == u"types.h"
@@ -836,6 +861,7 @@ BuilderPrivate::SpecialSystemHeader
         }
         break;
     case Platform::macOS:
+    case Platform::iOS:
         // Parse the following system headers to get the correct typdefs for types like
         // int32_t, which are used in the macOS implementation of OpenGL framework.
         // They are installed under /Applications/Xcode.app/Contents/Developer/Platforms...
index 747937ede8b9a6e897420d18f12972d0f9614a61..4c63c3e88687e6fa483544a47c9f786c3e67a243 100644 (file)
@@ -249,6 +249,11 @@ static CXTranslationUnit createTranslationUnit(CXIndex index,
                                      // https://github.com/darlinghq/darling/issues/204
 #endif
         "-Wno-constant-logical-operand",
+#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 64 // Clang 21
+        // QTBUG-141204: Suppress "character-conversion" warnings in Qt: qchar.h:... warning: implicit
+        // conversion from 'const char16_t' to 'char32_t' may change the meaning of the represented code unit.
+        "-Wno-character-conversion",
+#endif
         "-x",
         "c++" // Treat .h as C++, not C
     };
@@ -287,7 +292,12 @@ static void setupTarget(CXTranslationUnit translationUnit)
     {
         QTextStream str(&message);
         str << "CLANG v" << CINDEX_VERSION_MAJOR << '.' << CINDEX_VERSION_MINOR
-            << " targeting \"" << targetTriple() << "\", " << pointerSize() << "bit.";
+            << " targeting \"" << targetTriple() << "\"/"
+            << clang::compilerTripletValue(clang::compiler())
+            << ", " << pointerSize() << "bit";
+        if (clang::isCrossCompilation())
+            str << ", (cross build)";
+        str << '.';
     }
     qCInfo(lcShiboken, "%s", qPrintable(message));
     ReportHandler::addGeneralMessage(message + u'\n');
index 4d93a084f9d2d7af43f82afdcf030f47c6ee84cc..23e1f0ae9c96303f2dd4e0be9c8922d8584d6c84 100644 (file)
 #include <QtCore/qdir.h>
 #include <QtCore/qfile.h>
 #include <QtCore/qfileinfo.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qoperatingsystemversion.h>
 #include <QtCore/qprocess.h>
 #include <QtCore/qstandardpaths.h>
 #include <QtCore/qstringlist.h>
-#include <QtCore/qversionnumber.h>
 
 #include <clang-c/Index.h>
 
 #include <algorithm>
+#include <cstring>
 #include <iterator>
+#include <string_view>
 
 using namespace Qt::StringLiterals;
 
 namespace clang {
 
+// The command line options set
+enum OptionSetFlag : unsigned
+{
+    CompilerOption = 0x1,
+    CompilerPathOption = 0x2,
+    PlatformOption = 0x4,
+    PlatformVersionOption = 0x8,
+    ArchitectureOption = 0x10
+};
+
+Q_DECLARE_FLAGS(OptionsSet, OptionSetFlag)
+Q_DECLARE_OPERATORS_FOR_FLAGS(OptionsSet)
+
+static OptionsSet setOptions;
+
 QVersionNumber libClangVersion()
 {
     return QVersionNumber(CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR);
 }
 
-static Compiler _compiler =
+static Compiler hostCompiler()
+{
 #if defined (Q_CC_CLANG)
-    Compiler::Clang;
+    return Compiler::Clang;
 #elif defined (Q_CC_MSVC)
-    Compiler::Msvc;
+    return Compiler::Msvc;
 #else
-    Compiler::Gpp;
+    return Compiler::Gpp;
 #endif
+}
+
+static Compiler _compiler = hostCompiler();
 
 Compiler compiler() { return _compiler; }
 
-bool setCompiler(const QString &name)
+// CMAKE_CXX_COMPILER_ID or triplet name
+bool parseCompiler(QStringView name, Compiler *c)
 {
     bool result = true;
-    if (name == u"msvc")
-        _compiler = Compiler::Msvc;
-    else if (name == u"g++")
-        _compiler = Compiler::Gpp;
-    else if (name == u"clang")
-        _compiler = Compiler::Clang;
+    *c = hostCompiler();
+    if (name.compare("msvc"_L1, Qt::CaseInsensitive) == 0)
+        *c = Compiler::Msvc;
+    else if (name.compare("g++"_L1, Qt::CaseInsensitive) == 0 || name.compare("gnu"_L1, Qt::CaseInsensitive) == 0)
+        *c = Compiler::Gpp;
+    else if (name.compare("clang"_L1, Qt::CaseInsensitive) == 0)
+        *c = Compiler::Clang;
     else
         result = false;
     return result;
 }
 
+bool setCompiler(const QString &name)
+{
+    setOptions.setFlag(CompilerOption);
+    return parseCompiler(name, &_compiler);
+}
+
 QString _compilerPath; // Pre-defined compiler path (from command line)
+QStringList _compilerArguments; // Arguments
 
 static unsigned _pointerSize = QT_POINTER_SIZE * 8;
 static QString _targetTriple;
@@ -69,31 +100,249 @@ const QString &compilerPath()
 
 void setCompilerPath(const QString &name)
 {
+    setOptions.setFlag(CompilerPathOption);
     _compilerPath = name;
 }
 
-static Platform _platform =
+void addCompilerArgument(const QString &arg)
+{
+    _compilerArguments.append(arg);
+}
+
+static Platform hostPlatform()
+{
 #if defined (Q_OS_DARWIN)
-    Platform::macOS;
+    return Platform::macOS;
 #elif defined (Q_OS_WIN)
-    Platform::Windows;
+    return Platform::Windows;
+#elif defined (Q_OS_LINUX)
+    return Platform::Linux;
 #else
-    Platform::Unix;
+    return Platform::Unix;
 #endif
+}
+
+static Platform _platform = hostPlatform();
 
 Platform platform() { return _platform; }
 
-bool setPlatform(const QString &name)
+// from CMAKE_SYSTEM_NAME / legacy lower case name or target triplet
+static bool parsePlatform(QStringView name, Platform *p)
 {
+    *p = hostPlatform();
     bool result = true;
-    if (name == u"windows")
-        _platform = Platform::Windows;
-    else if (name == u"darwin")
-        _platform = Platform::macOS;
-    else if (name == u"unix")
-        _platform = Platform::Unix;
-    else
+    if (name.compare("unix"_L1, Qt::CaseInsensitive) == 0) {
+        *p = Platform::Unix;
+    } else if (name.compare("linux"_L1, Qt::CaseInsensitive) == 0) {
+        *p = Platform::Linux;
+    } else if (name.compare("windows"_L1, Qt::CaseInsensitive) == 0) {
+        *p = Platform::Windows;
+    } else if (name.compare("darwin"_L1, Qt::CaseInsensitive) == 0
+               || name.compare("macosx"_L1, Qt::CaseInsensitive) == 0) {
+        *p = Platform::macOS;
+    } else if (name.startsWith("android"_L1, Qt::CaseInsensitive)) {
+        *p = Platform::Android; // "androideabi"
+    } else if (name.compare("ios"_L1, Qt::CaseInsensitive) == 0) {
+        *p = Platform::iOS;
+    } else {
         result = false;
+    }
+    return result;
+}
+
+bool setPlatform(const QString &name)
+{
+    setOptions.setFlag(PlatformOption);
+    return parsePlatform(name, &_platform);
+}
+
+static QVersionNumber hostPlatformVersion()
+{
+    auto ov = QOperatingSystemVersion::current();
+    return ov.type() != QOperatingSystemVersionBase::Unknown ? ov.version() : QVersionNumber{};
+}
+
+// Version is not initialized from host since it is optional and the host version
+// should not interfere with cross build targets
+static QVersionNumber _platformVersion;
+
+QVersionNumber platformVersion()
+{
+    return _platformVersion;
+}
+
+bool setPlatformVersion(const QString &name)
+{
+    auto v = QVersionNumber::fromString(name);
+    setOptions.setFlag(PlatformVersionOption);
+    const bool result = !v.isNull();
+    if (result)
+        _platformVersion = v;
+    return result;
+}
+
+static Architecture hostArchitecture()
+{
+    // src/corelib/global/archdetect.cpp, "Qt 6.9.2 (x86_64-little_endian-lp64..."
+    std::string_view build = QLibraryInfo::build();
+    auto startPos = build.find('(');
+    auto dashPos = build.find('-');
+    if (startPos != std::string_view::npos && dashPos != std::string_view::npos) {
+        ++startPos;
+        build = build.substr(startPos, dashPos - startPos);
+        if (build == "x86_64")
+            return Architecture::X64;
+        if (build == "i386")
+            return Architecture::X86;
+        if (build == "arm64")
+            return Architecture::Arm64;
+        if (build == "arm")
+            return Architecture::Arm32;
+    }
+    return Architecture::Other;
+}
+
+// from CMAKE_SYSTEM_PROCESSOR or target triplet
+static Architecture parseArchitecture(QStringView a)
+{
+    if (a == "AMD64"_L1 || a == "IA64"_L1 // Windows
+        || a == "x86_64"_L1)
+        return Architecture::X64;
+    if (a.compare("x86"_L1, Qt::CaseInsensitive) == 0
+        || a.compare("i386"_L1, Qt::CaseInsensitive) == 0
+        || a.compare("i486"_L1, Qt::CaseInsensitive) == 0
+        || a.compare("i586"_L1, Qt::CaseInsensitive) == 0
+        || a.compare("i686"_L1, Qt::CaseInsensitive) == 0) {
+        return Architecture::X86;
+    }
+    if (a.startsWith("armv7"_L1, Qt::CaseInsensitive))
+        return Architecture::Arm32;
+    if (a.startsWith("arm"_L1, Qt::CaseInsensitive)
+        || a.startsWith("aarch64"_L1, Qt::CaseInsensitive)) {
+        return Architecture::Arm64;
+    }
+    return Architecture::Other;
+}
+
+static Architecture _architecture = hostArchitecture();
+
+Architecture architecture()
+{
+    return _architecture;
+}
+
+bool setArchitecture(const QString &name)
+{
+    setOptions.setFlag(ArchitectureOption);
+    auto newArchitecture = parseArchitecture(name);
+    const bool result = newArchitecture != Architecture::Other;
+    if (result)
+        _architecture = newArchitecture;
+    return result;
+}
+
+// Parsing triplets
+static inline bool isVersionChar(QChar c)
+{
+    return c.isDigit() || c == u'.';
+}
+
+// "macosx15.0" -> "macosx"
+QStringView stripTrailingVersion(QStringView s)
+{
+    while (!s.isEmpty() && isVersionChar(s.at(s.size() - 1)))
+        s.chop(1);
+    return s;
+}
+
+bool parseTriplet(QStringView name, Architecture *a, Platform *p, Compiler *c,
+                  QVersionNumber *version)
+{
+    *a = hostArchitecture();
+    *p = hostPlatform();
+    *c = hostCompiler();
+    *version = hostPlatformVersion();
+    auto values = name.split(u'-');
+    if (values.size() < 2)
+        return false;
+    *a = parseArchitecture(values.constFirst());
+    if (*a == Architecture::Other)
+        return false;
+    // Try a trailing compiler?
+    Compiler comp{};
+    if (parseCompiler(stripTrailingVersion(values.constLast()), &comp)) {
+        *c = comp;
+        values.removeLast();
+    }
+    const QStringView &fullPlatform = values.constLast();
+    QStringView platformName = stripTrailingVersion(fullPlatform);
+    if (platformName.size() < fullPlatform.size()) {
+        if (auto vn = QVersionNumber::fromString(fullPlatform.sliced(platformName.size())); !vn.isNull())
+            *version = vn;
+    }
+    return parsePlatform(platformName, p);
+}
+
+const char *compilerTripletValue(Compiler c)
+{
+    switch (c) {
+    case Compiler::Clang:
+        return "clang";
+    case Compiler::Msvc:
+        return "msvc";
+    case Compiler::Gpp:
+        break;
+    }
+    return "gnu";
+}
+
+QByteArray targetTripletForPlatform(Platform p, Architecture a, Compiler c,
+                                    const QVersionNumber &platformVersion)
+{
+    QByteArray result;
+    if (p == Platform::Unix || a == Architecture::Other)
+        return result; // too unspecific
+
+    switch (a) {
+    case Architecture::Other:
+        break;
+    case Architecture::X64:
+        result += "x86_64";
+        break;
+    case Architecture::X86:
+        result += "i586";
+        break;
+    case Architecture::Arm32:
+        result += "armv7a";
+        break;
+    case Architecture::Arm64:
+        result += p == Platform::Android ? "aarch64" : "arm64";
+        break;
+    }
+
+    result += '-';
+
+    const QByteArray platformVersionB = platformVersion.isNull()
+        ? QByteArray{} : platformVersion.toString().toUtf8();
+    switch (p) {
+    case Platform::Unix:
+        break;
+    case Platform::Linux:
+        result += "unknown-linux"_ba + platformVersionB + '-' + compilerTripletValue(c);
+        break;
+    case Platform::Windows:
+        result += "pc-windows"_ba + platformVersionB + '-' + compilerTripletValue(c);
+        break;
+    case Platform::macOS:
+        result += "apple-macosx"_ba + platformVersionB;
+        break;
+    case Platform::Android:
+        result += "unknown-linux-android"_ba + platformVersionB;
+        break;
+    case Platform::iOS:
+        result += "apple-ios"_ba + platformVersionB;
+        break;
+    }
     return result;
 }
 
@@ -179,10 +428,12 @@ static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths)
 // /usr/local/include
 // /System/Library/Frameworks (framework directory)
 // End of search list.
-static HeaderPaths gppInternalIncludePaths(const QString &compiler)
+static HeaderPaths gppInternalIncludePaths(const QString &compiler,
+                                           const QStringList &args)
 {
     HeaderPaths result;
     QStringList arguments{u"-E"_s, u"-x"_s, u"c++"_s, u"-"_s, u"-v"_s};
+    arguments.append(args);
     QByteArray stdOut;
     QByteArray stdErr;
     if (!runProcess(compiler, arguments, &stdOut, &stdErr))
@@ -213,7 +464,8 @@ static HeaderPaths gppInternalIncludePaths(const QString &compiler)
     QString message;
     {
         QTextStream str(&message);
-        str << "gppInternalIncludePaths:\n    compiler: " << compiler  << '\n';
+        str << "gppInternalIncludePaths:\n    compiler: " << compiler
+            << arguments.join(u' ') << '\n';
         for (const auto &h : result)
             str << "    " << h.path << '\n';
         if (ReportHandler::isDebug(ReportHandler::MediumDebug))
@@ -239,16 +491,10 @@ QByteArrayList detectVulkan()
 
 // For MSVC, we set the MS compatibility version and let Clang figure out its own
 // options and include paths.
-// For the others, we pass "-nostdinc" since libclang tries to add it's own system
-// include paths, which together with the clang compiler paths causes some clash
-// which causes std types not being found and construct -I/-F options from the
-// include paths of the host compiler.
-
-static QByteArray noStandardIncludeOption() { return QByteArrayLiteral("-nostdinc"); }
 
 // The clang builtin includes directory is used to find the definitions for
 // intrinsic functions and builtin types. It is necessary to use the clang
-// includes to prevent redefinition errors. The default toolchain includes
+// includes to prevent rqedefinition errors. The default toolchain includes
 // should be picked up automatically by clang without specifying
 // them implicitly.
 
@@ -399,8 +645,8 @@ QByteArrayList emulatedCompilerOptions(LanguageLevel level)
             appendClangBuiltinIncludes(&headerPaths);
         break;
     case Compiler::Clang:
-        headerPaths.append(gppInternalIncludePaths(compilerFromCMake(u"clang++"_s)));
-        result.append(noStandardIncludeOption());
+        headerPaths.append(gppInternalIncludePaths(compilerFromCMake(u"clang++"_s),
+                                                   _compilerArguments));
         break;
     case Compiler::Gpp:
         if (needsClangBuiltinIncludes())
@@ -408,7 +654,8 @@ QByteArrayList emulatedCompilerOptions(LanguageLevel level)
 
         // Append the c++ include paths since Clang is unable to find
         // <type_traits> etc (g++ 11.3).
-        const HeaderPaths gppPaths = gppInternalIncludePaths(compilerFromCMake(u"g++"_s));
+        const HeaderPaths gppPaths = gppInternalIncludePaths(compilerFromCMake(u"g++"_s),
+                                                             _compilerArguments);
         for (const HeaderPath &h : gppPaths) {
             if (h.path.contains("c++") || h.path.contains("sysroot"))
                 headerPaths.append(h);
@@ -416,6 +663,17 @@ QByteArrayList emulatedCompilerOptions(LanguageLevel level)
         break;
     }
 
+    // For Android cross-compilation, prevent clang from including host system headers that
+    // conflict with NDK headers.
+    // This addresses the mbstate_t typedef redefinition error in COIN for RHEL 9.4
+    const auto hostPlatform = clang::hostPlatform();
+    if (_platform == Platform::Android
+        && (hostPlatform == Platform::Unix || hostPlatform == Platform::Linux)
+        && (_compiler == Compiler::Clang || _compiler == Compiler::Gpp)) {
+        result.append("-nostdinc");
+        result.append("-nostdinc++");
+    }
+
     std::transform(headerPaths.cbegin(), headerPaths.cend(),
                    std::back_inserter(result), HeaderPath::includeOption);
     return result;
@@ -453,7 +711,7 @@ const char *languageLevelOption(LanguageLevel l)
 LanguageLevel languageLevelFromOption(const char *o)
 {
     for (const LanguageLevelMapping &m : languageLevelMapping) {
-        if (!strcmp(m.option, o))
+        if (!std::strcmp(m.option, o))
             return m.level;
     }
     return LanguageLevel::Default;
@@ -479,13 +737,65 @@ void setTargetTriple(const QString &t)
     _targetTriple = t;
 }
 
-void setTargetTriple(const QStringList &clangOptions)
+bool isCrossCompilation()
 {
-    static constexpr auto targetOption = "--target="_L1;
-    auto targetOptionPred = [](const QString &o) { return o.startsWith(targetOption); };
-    const auto it = std::find_if(clangOptions.cbegin(), clangOptions.cend(), targetOptionPred);
-    if (it != clangOptions.cend())
-        _targetTriple = it->sliced(targetOption.size());
+    return platform() != hostPlatform() || architecture() != hostArchitecture()
+           || compiler() != hostCompiler();
+}
+
+static const char targetOptionC[] = "--target=";
+
+static inline bool isTargetOption(const QByteArray &o)
+{
+    return o.startsWith(targetOptionC);
+}
+
+static bool isTargetArchOption(const QByteArray &o)
+{
+    return isTargetOption(o)
+           || o.startsWith("-march=") || o.startsWith("-meabi");
+}
+
+bool hasTargetOption(const QByteArrayList &clangOptions)
+{
+    return std::any_of(clangOptions.cbegin(), clangOptions.cend(),
+                       isTargetArchOption);
+}
+
+void setHeuristicOptions(const QByteArrayList &clangOptions)
+{
+    // Figure out compiler type from the binary set
+    if (!setOptions.testFlag(CompilerOption) && setOptions.testFlag(CompilerPathOption)) {
+        const QString name = QFileInfo(_compilerPath).baseName().toLower();
+        if (name.contains("clang"_L1))
+            _compiler = Compiler::Clang;
+        else if (name.contains("cl"_L1))
+            _compiler = Compiler::Msvc;
+        else if (name.contains("gcc"_L1) || name.contains("g++"_L1))
+            _compiler = Compiler::Gpp;
+    }
+
+    // Figure out platform/arch from "--target" triplet
+    if (!setOptions.testFlag(PlatformOption) && !setOptions.testFlag(ArchitectureOption)) {
+        auto it = std::find_if(clangOptions.cbegin(), clangOptions.cend(), isTargetOption);
+        if (it != clangOptions.cend()) {
+            const QString triplet = QLatin1StringView(it->sliced(qstrlen(targetOptionC)));
+            Architecture arch{};
+            Platform platform{};
+            Compiler comp{};
+            QVersionNumber platformVersion;
+            if (parseTriplet(triplet, &arch, &platform, &comp, &platformVersion)) {
+                if (!setOptions.testFlag(ArchitectureOption))
+                    _architecture = arch;
+                if (!setOptions.testFlag(PlatformOption))
+                    _platform = platform;
+                if (!setOptions.testFlag(PlatformVersionOption))
+                    _platformVersion = platformVersion;
+            } else {
+                qCWarning(lcShiboken, "Unable to parse triplet \"%s\".", qPrintable(triplet));
+            }
+        }
+    }
 }
 
 } // namespace clang
index 0e12ca13732ddbff435d8f6c31278631d3be778e..84395d28f1bda3927404f6905416a116ef4aa105 100644 (file)
@@ -5,8 +5,8 @@
 #define COMPILERSUPPORT_H
 
 #include <QtCore/qbytearraylist.h>
+#include <QtCore/qversionnumber.h>
 
-QT_FORWARD_DECLARE_CLASS(QVersionNumber)
 QT_FORWARD_DECLARE_CLASS(QString)
 
 enum class LanguageLevel {
@@ -26,8 +26,19 @@ enum class Compiler {
 
 enum class Platform {
     Unix,
+    Linux,
     Windows,
-    macOS
+    macOS,
+    Android,
+    iOS
+};
+
+enum class Architecture {
+    Other,
+    X64,
+    X86,
+    Arm64,
+    Arm32
 };
 
 namespace clang {
@@ -48,17 +59,41 @@ QString compilerFromCMake();
 
 const QString &compilerPath();
 void setCompilerPath(const QString &name);
+void addCompilerArgument(const QString &arg);
 
 Platform platform();
 bool setPlatform(const QString &name);
 
+QVersionNumber platformVersion();
+bool setPlatformVersion(const QString &name);
+
+QByteArray targetTripletForPlatform(Platform p, Architecture a, Compiler c,
+                                    const QVersionNumber &platformVersion = {});
+const char *compilerTripletValue(Compiler c);
+
+Architecture architecture();
+bool setArchitecture(const QString &name);
+
 unsigned pointerSize(); // (bit)
 void setPointerSize(unsigned ps); // Set by parser
 
 QString targetTriple();
-void setTargetTriple(const QStringList &clangOptions); // Set from cmd line before parsing
 void setTargetTriple(const QString &t); // Updated by clang parser while parsing
 
+bool isCrossCompilation();
+
+// Are there any options specifying a target
+bool hasTargetOption(const QByteArrayList &clangOptions);
+
+// Unless the platform/architecture/compiler options were set, try to find
+// values based on a --target option in clangOptions and the compiler path.
+void setHeuristicOptions(const QByteArrayList &clangOptions);
+
+// Parse a triplet "x86_64-unknown-linux-gnu" (for testing). Note the
+// compiler might not be present and defaults to host
+bool parseTriplet(QStringView name, Architecture *a, Platform *p, Compiler *c,
+                  QVersionNumber *version);
+
 } // namespace clang
 
 #endif // COMPILERSUPPORT_H
index 1e721b5899710a600b153fe526d78476c3f429e1..ccc7b29d2e0068d3ed1b2ea4db7c92574916227e 100644 (file)
@@ -88,7 +88,7 @@ enum class WebXmlCodeTag
 
 static WebXmlCodeTag tag(QStringView name)
 {
-    if (name == u"class" || name == u"namespace")
+    if (name == "class"_L1 || name == "struct"_L1 || name == "namespace"_L1)
         return WebXmlCodeTag::Class;
     if (name == u"enum")
         return WebXmlCodeTag::Enum;
index 6b9f0ae2ec16c421d5e671d8efe8423808c26da6..88079a63054091a40715566ef78b0c45c9c04b9f 100644 (file)
@@ -158,24 +158,7 @@ bool ConditionalStreamReader::conditionMatches() const
 
 void ConditionalStreamReader::setConditions(const QStringList &newConditions)
 {
-    m_conditions = newConditions + platformConditions();
-}
-
-QStringList ConditionalStreamReader::platformConditions()
-{
-    QStringList result;
-#if defined (Q_OS_UNIX)
-    result << "unix"_L1;
-#endif
-
-#if defined (Q_OS_LINUX)
-    result << "linux"_L1;
-#elif defined (Q_OS_MACOS)
-    result << "darwin"_L1;
-#elif defined (Q_OS_WINDOWS)
-    result << "windows"_L1;
-#endif
-    return result;
+    m_conditions = newConditions;
 }
 
 ConditionalStreamReader::ExtendedToken ConditionalStreamReader::readNextInternal()
index 36c4752a432fa4a9e3bcf6515501047bada2a512..d9af5dc1ee772101bb8256a13ca7e506c4370c6b 100644 (file)
@@ -69,8 +69,6 @@ public:
     const QStringList &conditions() const { return m_conditions; }
     void setConditions(const QStringList &newConditions);
 
-    static QStringList platformConditions();
-
 private:
     enum class PiTokens { None, If, Endif, EntityDefinition };
 
@@ -82,7 +80,7 @@ private:
 
     QXmlStreamReader m_reader;
     ProxyEntityResolver *m_proxyEntityResolver = nullptr;
-    QStringList m_conditions = ConditionalStreamReader::platformConditions();
+    QStringList m_conditions;
 };
 
 QDebug operator<<(QDebug dbg, const QXmlStreamAttributes &a);
index 4cfd1b974436032b2d16d9ec009bc040653ca8c3..b022cd6924117c6f6134faa98a5f185218d3fa2a 100644 (file)
@@ -5,6 +5,7 @@
 #include "containertypeentry.h"
 #include "customtypenentry.h"
 #include "primitivetypeentry.h"
+#include "smartpointertypeentry.h"
 #include "valuetypeentry.h"
 
 #include <QtCore/qdebug.h>
@@ -94,6 +95,11 @@ QString TargetToNativeConversion::sourceTypeName() const
 }
 
 QString TargetToNativeConversion::sourceTypeCheck() const
+{
+    return m_sourceTypeCheck;
+}
+
+QString TargetToNativeConversion::sourceTypeCheckFallback() const
 {
     if (!m_sourceTypeCheck.isEmpty())
         return m_sourceTypeCheck;
@@ -108,6 +114,10 @@ QString TargetToNativeConversion::sourceTypeCheck() const
         }
     }
 
+    if (m_sourceTypeName == "Py_None"_L1 || m_sourceTypeName == "PyNone"_L1)
+        return "%in == Py_None"_L1;
+    if (m_sourceTypeName == "SbkObject"_L1)
+        return "Shiboken::Object::checkType(%in)"_L1;
     return {};
 }
 
@@ -139,6 +149,8 @@ CustomConversionPtr CustomConversion::getCustomConversion(const TypeEntryCPtr &t
         return std::static_pointer_cast<const ContainerTypeEntry>(type)->customConversion();
     if (type->isValue())
         return std::static_pointer_cast<const ValueTypeEntry>(type)->customConversion();
+    if (type->isSmartPointer())
+        return std::static_pointer_cast<const SmartPointerTypeEntry>(type)->customConversion();
     return {};
 }
 
index a751788464714f2cd360defe9281215bca5bf82b..9125d67057bc2aff09cdf2f607b290094fc0d592 100644 (file)
@@ -25,7 +25,10 @@ public:
     void setSourceType(const TypeEntryCPtr &sourceType);
     bool isCustomType() const;
     QString sourceTypeName() const;
+    // Check as specified in the type system
     QString sourceTypeCheck() const;
+    // Check with fallback bases on sourceType
+    QString sourceTypeCheckFallback() const;
     QString conversion() const;
     void setConversion(const QString &conversion);
 
index 580d8f969a21ba55deb9f94c92d1df42dec765cb..a623529ca35ff98bd8a2c4cab47268b9ed5ef590 100644 (file)
@@ -29,6 +29,7 @@ public:
 
     bool equals(const Documentation &rhs) const;
 
+    bool hasDetailed() const { return !m_detailed.isEmpty(); }
     const QString &detailed() const { return m_detailed; }
     void setDetailed(const QString &detailed);
 
index 3360d7db5c5ffd5b16e3ac05e2c4a0d256e643ea..633ab3183215e770397e713385324d9ce5b28342 100644 (file)
@@ -13,6 +13,12 @@ class EnumTypeEntryPrivate;
 class EnumTypeEntry : public ConfigurableTypeEntry
 {
 public:
+    enum AliasMode : unsigned char {
+        NoAlias,
+        AliasSource, // Source of a C++ alias "using ThatEnum = ThisEnum";
+        AliasTarget // Target of a C++ alias "using ThisEnum = ThatEnum";
+    };
+
     explicit EnumTypeEntry(const QString &entryName,
                            const QVersionNumber &vr,
                            const TypeEntryCPtr &parent);
@@ -40,6 +46,12 @@ public:
     QString docFile() const;
     void setDocFile(const QString &df);
 
+    AliasMode aliasMode() const;
+    void setAliasMode(AliasMode am);
+
+    EnumTypeEntryCPtr aliasTypeEntry() const;
+    void setAliasTypeEntry(const EnumTypeEntryCPtr &entry);
+
     TypeEntry *clone() const override;
 #ifndef QT_NO_DEBUG_STREAM
     void formatDebug(QDebug &d) const override;
index 2aa7a549748a7876b0f1b486646225e914411e1f..dbbfc3490aecd968a0a73b86c2bf84e8897a3f14 100644 (file)
@@ -12,6 +12,8 @@
 
 #include <cstdio>
 
+using namespace Qt::StringLiterals;
+
 bool FileOut::m_dryRun = false;
 bool FileOut::m_diff = false;
 
@@ -175,8 +177,8 @@ FileOut::State FileOut::done()
     if (!FileOut::m_dryRun) {
         QDir dir(info.absolutePath());
         if (!dir.mkpath(dir.absolutePath())) {
-            const QString message = QString::fromLatin1("Unable to create directory '%1'")
-                                        .arg(QDir::toNativeSeparators(dir.absolutePath()));
+            const QString message = "Unable to create directory '%1'"_L1
+                                    .arg(QDir::toNativeSeparators(dir.absolutePath()));
             throw Exception(message);
         }
 
index 5647cccc5fb964cf9d86b375245a67c3653a0dc3..6932b04dbd6d2543a1351c1c5a4763e62e43a750 100644 (file)
@@ -22,6 +22,7 @@
 #include <QtCore/qfile.h>
 #include <QtCore/qlibraryinfo.h>
 #include <QtCore/qstringlist.h>
+#include <QtCore/qsysinfo.h>
 #include <QtCore/qxmlstream.h>
 
 #include <algorithm>
@@ -171,7 +172,7 @@ static void msgFormatEnumType(Stream &str,
 {
     switch (enumItem->enumKind()) {
     case CEnum:
-        str << "Enum '" << enumItem->qualifiedName().join(u"::"_s) << '\'';
+        str << "Enum '" << enumItem->qualifiedNameString() << '\'';
         break;
     case AnonymousEnum: {
         const EnumeratorList &values = enumItem->enumerators();
@@ -194,7 +195,7 @@ static void msgFormatEnumType(Stream &str,
     }
         break;
     case EnumClass:
-        str << "Scoped enum '" << enumItem->qualifiedName().join(u"::"_s) << '\'';
+        str << "Scoped enum '" << enumItem->qualifiedNameString() << '\'';
         break;
     }
     if (!className.isEmpty())
@@ -715,13 +716,13 @@ QString msgXpathDocModificationError(const DocModificationList& mods,
 
 QString msgCannotOpenForReading(const QFile &f)
 {
-    return QString::fromLatin1("Failed to open file '%1' for reading: %2")
+    return "Failed to open file '%1' for reading: %2"_L1
            .arg(QDir::toNativeSeparators(f.fileName()), f.errorString());
 }
 
 QString msgCannotOpenForWriting(const QFile &f)
 {
-    return QString::fromLatin1("Failed to open file '%1' for writing: %2")
+    return "Failed to open file '%1' for writing: %2"_L1
            .arg(QDir::toNativeSeparators(f.fileName()), f.errorString());
 }
 
@@ -1021,7 +1022,7 @@ QString msgUnknownArrayPointerConversion(const QString &s)
 QString msgMissingProjectFileMarker(const QString &name, const QByteArray &startMarker)
 {
     return u"First line of project file \""_s + QDir::toNativeSeparators(name)
-        + u"\" must be the string \""_s + QString::fromLatin1(startMarker) + u"\"."_s;
+        + u"\" must be the string \""_s + QLatin1StringView(startMarker) + u"\"."_s;
 }
 
 QString msgInvalidLanguageLevel(const QString &l)
@@ -1101,6 +1102,7 @@ QString msgRemoveRedundantOverload(const AbstractMetaFunctionCPtr &func,
 QString msgCommandLineArguments(const QStringList &argv)
 {
     QString result = "Host platform: "_L1 + QLatin1StringView(QLibraryInfo::build())
+                     + "\nHost OS      : "_L1 + QSysInfo::prettyProductName()
                      + "\nCommand line:\n   "_L1;
     for (const QString &arg : argv) {
         result.append(u' ');
index a5994bd7504241e2544d4c378196c0842bb5490b..8a56c9eee4b4a41d0efc3b85306e63893baa363d 100644 (file)
@@ -160,6 +160,11 @@ QStringList _CodeModelItem::qualifiedName() const
     return q;
 }
 
+QString _CodeModelItem::qualifiedNameString() const
+{
+    return qualifiedName().join("::"_L1);
+}
+
 QString _CodeModelItem::name() const
 {
     return m_name;
@@ -453,11 +458,6 @@ FunctionModelItem _ScopeModelItem::declaredFunction(const FunctionModelItem &ite
 
 _ScopeModelItem::~_ScopeModelItem() = default;
 
-void _ScopeModelItem::addEnumsDeclaration(const QString &enumsDeclaration)
-{
-    m_enumsDeclarations << enumsDeclaration;
-}
-
 void _ScopeModelItem::addClass(const ClassModelItem &item)
 {
     m_classes.append(item);
@@ -520,15 +520,13 @@ void _ScopeModelItem::appendScope(const _ScopeModelItem &other)
     m_templateTypeAliases += other.m_templateTypeAliases;
     m_variables += other.m_variables;
     m_functions += other.m_functions;
-    m_enumsDeclarations += other.m_enumsDeclarations;
 }
 
 bool _ScopeModelItem::isEmpty() const
 {
     return m_classes.isEmpty() && m_enums.isEmpty()
         && m_typeDefs.isEmpty() && m_templateTypeAliases.isEmpty()
-        && m_variables.isEmpty() && m_functions.isEmpty()
-        && m_enumsDeclarations.isEmpty();
+        && m_variables.isEmpty() && m_functions.isEmpty();
 }
 
 /* This function removes MSVC export declarations of non-type template
@@ -1208,11 +1206,26 @@ void _TypeDefModelItem::setType(const TypeInfo &type)
     m_type = type;
 }
 
+TypeCategory _TypeDefModelItem::underlyingTypeCategory() const
+{
+    return m_type.typeCategory();
+}
+
+Access _TypeDefModelItem::accessPolicy() const
+{
+    return m_accessPolicy;
+}
+
+void _TypeDefModelItem::setAccessPolicy(Access accessPolicy)
+{
+    m_accessPolicy = accessPolicy;
+}
+
 #ifndef QT_NO_DEBUG_STREAM
 void _TypeDefModelItem::formatDebug(QDebug &d) const
 {
     _CodeModelItem::formatDebug(d);
-    d << ", type=" << m_type;
+    d << ", " << m_accessPolicy << ", type=" << m_type;
 }
 #endif // !QT_NO_DEBUG_STREAM
 
index fb46fab82f4f1d85b2ae10d6dfc7a999b7160835..77082efa5779ade7c1fe139e44c290c2789cc36b 100644 (file)
@@ -120,6 +120,7 @@ public:
     int kind() const;
 
     QStringList qualifiedName() const;
+    QString qualifiedNameString() const;
 
     QString name() const;
     void setName(const QString &name);
@@ -211,9 +212,6 @@ public:
     TemplateTypeAliasModelItem findTemplateTypeAlias(QAnyStringView name) const;
     VariableModelItem findVariable(QAnyStringView name) const;
 
-    void addEnumsDeclaration(const QString &enumsDeclaration);
-    QStringList enumsDeclarations() const { return m_enumsDeclarations; }
-
     FunctionModelItem declaredFunction(const FunctionModelItem &item);
 
     bool isEmpty() const;
@@ -250,9 +248,6 @@ private:
     TemplateTypeAliasList m_templateTypeAliases;
     VariableList m_variables;
     FunctionList m_functions;
-
-private:
-    QStringList m_enumsDeclarations;
 };
 
 class _ClassModelItem: public _ScopeModelItem
@@ -564,11 +559,17 @@ public:
     TypeInfo type() const;
     void setType(const TypeInfo &type);
 
+    TypeCategory underlyingTypeCategory() const;
+
+    Access accessPolicy() const;
+    void setAccessPolicy(Access accessPolicy);
+
 #ifndef QT_NO_DEBUG_STREAM
     void formatDebug(QDebug &d) const override;
 #endif
 
 private:
+    Access m_accessPolicy = Access::Public;
     TypeInfo m_type;
 };
 
index e5c429bd0d58b63f4998c8b0c04be57dddaa958a..c3bb10e5f2c918ab959f4109c506f9e2438e3109 100644 (file)
@@ -58,4 +58,14 @@ enum class FunctionAttribute {
 Q_DECLARE_FLAGS(FunctionAttributes, FunctionAttribute)
 Q_DECLARE_OPERATORS_FOR_FLAGS(FunctionAttributes)
 
+// C++ type category for TypeInfo, reflecting clang's CXTypeKind
+enum class TypeCategory : unsigned char {
+    Other,
+    Builtin,
+    Enum,
+    Pointer,
+    Function,
+    Void
+};
+
 #endif // CODEMODEL_ENUMS_H
index 93627e6d57783663c164a4a10e6878e778d9a97b..c530cafeaae06751451d60ffc8314722f16dab9d 100644 (file)
@@ -25,7 +25,6 @@ class TypeInfoData : public QSharedData
 public:
     TypeInfoData();
 
-    bool isVoid() const;
     bool equals(const TypeInfoData &other) const;
     bool isStdType() const;
     void simplifyStdType();
@@ -48,6 +47,7 @@ public:
     };
 
     ReferenceType m_referenceType = NoReference;
+    TypeCategory m_category = TypeCategory::Other;
 };
 
 TypeInfoData::TypeInfoData() : flags(0)
@@ -143,18 +143,21 @@ void  TypeInfo::addName(const QString &n)
     d->m_qualifiedName.append(n);
 }
 
-bool TypeInfoData::isVoid() const
+bool TypeInfo::isVoid() const
 {
-    return m_indirections.isEmpty() && m_referenceType == NoReference
-        && m_arguments.isEmpty() && m_arrayElements.isEmpty()
-        && m_instantiations.isEmpty()
-        && m_qualifiedName.size() == 1
-        && m_qualifiedName.constFirst() == u"void";
+    return d->m_category == TypeCategory::Void;
 }
 
-bool TypeInfo::isVoid() const
+TypeCategory TypeInfo::typeCategory() const
+{
+    return d->m_category;
+
+}
+
+void TypeInfo::setTypeCategory(TypeCategory c)
 {
-    return d->isVoid();
+    if (d->m_category != c)
+        d->m_category = c;
 }
 
 bool TypeInfo::isConstant() const
@@ -457,6 +460,7 @@ bool TypeInfoData::equals(const TypeInfoData &other) const
 
     return flags == other.flags
            && m_qualifiedName == other.m_qualifiedName
+           && m_category == other.m_category
            && (!m_functionPointer || m_arguments == other.m_arguments)
            && m_instantiations == other.m_instantiations;
 }
@@ -584,6 +588,23 @@ void TypeInfo::formatDebug(QDebug &debug) const
         debug << ", [const]";
     if (d->m_volatile)
         debug << ", [volatile]";
+    switch (d->m_category) {
+    case TypeCategory::Other:
+    case TypeCategory::Void:
+        break;
+    case TypeCategory::Builtin:
+        debug << ", [builtin]";
+        break;
+    case TypeCategory::Enum:
+        debug << ", [enum]";
+        break;
+    case TypeCategory::Pointer:
+        debug << ", [pointer]";
+        break;
+    case TypeCategory::Function:
+        debug << ", [function";
+        break;
+    }
     if (!d->m_indirections.isEmpty()) {
         debug << ", indirections=";
         for (auto i : d->m_indirections)
index 6f75b57375afbf05c439fc86799d253f107f1dc2..092fbb724ce5c738e47b8a85603e44b0537670fc 100644 (file)
@@ -48,6 +48,9 @@ public:
 
     bool isVoid() const;
 
+    TypeCategory typeCategory() const;
+    void setTypeCategory(TypeCategory c);
+
     bool isConstant() const;
     void setConstant(bool is);
 
index 22727282e5facfce8de27f7a1a013205df493254..639947726b5307dddc822de504545f153164d64f 100644 (file)
@@ -194,7 +194,7 @@ return %out;
      uR"(PyObject *%out = PyList_New(Py_ssize_t(%in.size()));
 Py_ssize_t idx = 0;
 for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ++it, ++idx) {
-    const auto &cppItem = *it;
+    ShibokenContainerStdVectorValueType<%INTYPE_0>::Type cppItem = *it;
     PyList_SetItem(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
 }
 return %out;)"_s},
index c7361b87a1416d683aad2ab3a207be739e8a9411..0d982b26022617c43f0e8a490a64cf4d347b2f2d 100644 (file)
@@ -30,6 +30,8 @@
 
 #include <algorithm>
 #include <iterator>
+#include <optional>
+#include <utility>
 
 using namespace Qt::StringLiterals;
 
@@ -245,23 +247,35 @@ QtDocParser::FunctionDocumentationOpt
     return std::nullopt;
 }
 
-// Extract the <brief> section from a WebXML (class) documentation and remove it
-// from the source.
-static QString extractBrief(QString *value)
+// Extract the <brief>/detailed sections from a WebXML (class) documentation (from <description>)
+static std::pair<QString, QString> extractBrief(QString value)
 {
-    const auto briefStart = value->indexOf(briefStartElement);
-    if (briefStart < 0)
-        return {};
-    const auto briefEnd = value->indexOf(briefEndElement,
-                                         briefStart + briefStartElement.size());
-    if (briefEnd < briefStart)
-        return {};
-    const auto briefLength = briefEnd + briefEndElement.size() - briefStart;
-    QString briefValue = value->mid(briefStart, briefLength);
-    briefValue.insert(briefValue.size() - briefEndElement.size(),
-                      u"<rst> More_...</rst>"_s);
-    value->remove(briefStart, briefLength);
-    return briefValue;
+    std::pair<QString, QString> result;
+    const auto briefStart = value.indexOf(briefStartElement);
+    if (briefStart > 0) {
+        const auto briefEnd = value.indexOf(briefEndElement,
+                                            briefStart + briefStartElement.size());
+        if (briefEnd > briefStart) {
+            const auto briefLength = briefEnd + briefEndElement.size() - briefStart;
+            if (briefLength > briefStartElement.size() + briefEndElement.size())
+                result.first = value.sliced(briefStart, briefLength);
+            value.remove(briefStart, briefLength);
+            // Remove any space/newlines between the <brief/> element and its
+            // surrounding XML elements.
+            auto lastElement = value.lastIndexOf(u'>', briefStart);
+            if (lastElement != -1) {
+                ++lastElement;
+                const auto nextElement = value.indexOf(u'<', briefStart);
+                if (nextElement > lastElement)
+                    value.remove(lastElement, nextElement - lastElement);
+            }
+        }
+    }
+
+    if (value != "<description></description>"_L1)
+        result.second = value;
+
+    return result;
 }
 
 // Apply the documentation parsed from WebXML to a AbstractMetaFunction and complete argument
@@ -408,13 +422,12 @@ QString QtDocParser::fillDocumentation(const AbstractMetaClassPtr &metaClass)
         qCWarning(lcShibokenDoc, "%s",
                   qPrintable(msgCannotFindDocumentation(sourceFileName, "class", className, {})));
     }
-    const QString brief = extractBrief(&docString);
+    const auto descriptionPair = extractBrief(docString);
 
     Documentation doc;
     doc.setSourceFile(sourceFileName);
-    if (!brief.isEmpty())
-        doc.setValue(brief, DocumentationType::Brief);
-    doc.setValue(docString);
+    doc.setValue(descriptionPair.first, DocumentationType::Brief);
+    doc.setValue(descriptionPair.second, DocumentationType::Detailed);
     metaClass->setDocumentation(doc);
 
     //Functions Documentation
@@ -484,6 +497,23 @@ static QString qmlReferenceLink(const QFileInfo &qmlModuleFi)
         + u'/' + qmlModuleFi.baseName() + ".html"_L1;
 }
 
+// Find a webxml file containing QML types. Note: These files are empty;
+// we need to point to the web docs.
+static std::optional<QFileInfo> qmlModuleFile(const QString &dirPath,
+                                              const QString &lowerModuleName)
+{
+    static constexpr auto postFix = "-qmlmodule.webxml"_L1;
+    const QFileInfo moduleFile(dirPath + u'/' + lowerModuleName + postFix);
+    if (moduleFile.exists())
+        return moduleFile;
+    // Some file names are irregular, fall back to using a filter
+    const QFileInfoList qmlModuleFiles =
+        QDir(dirPath).entryInfoList({u'*' + postFix}, QDir::Files);
+    if (!qmlModuleFiles.isEmpty())
+        return qmlModuleFiles.constFirst();
+    return std::nullopt;
+}
+
 ModuleDocumentation QtDocParser::retrieveModuleDocumentation(const QString &name)
 {
     // TODO: This method of acquiring the module name supposes that the target language uses
@@ -518,11 +548,8 @@ ModuleDocumentation QtDocParser::retrieveModuleDocumentation(const QString &name
     ModuleDocumentation result{Documentation{docString, {}, sourceFile}, {}};
 
     // If a QML module info file exists, insert a link to the Qt docs.
-    // Use a filter as some file names are irregular.
     // Note: These files are empty; we need to point to the web docs.
-    const QFileInfoList qmlModuleFiles =
-        QDir(dirPath).entryInfoList({"*-qmlmodule.webxml"_L1}, QDir::Files);
-    if (!qmlModuleFiles.isEmpty())
-        result.qmlTypesUrl = qmlReferenceLink(qmlModuleFiles.constFirst());
+    if (const auto qmlModuleFileO = qmlModuleFile(dirPath, lowerModuleName))
+        result.qmlTypesUrl = qmlReferenceLink(qmlModuleFileO.value());
     return result;
 }
index 95ea2ce447593f38e1414708e83acd87fc3a9b91..e24dadb94799fb1f6136d727e8f7305438256ef6 100644 (file)
@@ -206,11 +206,13 @@ void ReportHandler::addGeneralMessage(const QString &message)
     generalMessages.append(message);
 }
 
+static const char generalLogFile[] = "mjb_shiboken.log";
+
 void ReportHandler::writeGeneralLogFile(const QString &directory)
 {
     if (generalMessages.isEmpty())
         return;
-    QFile file(directory + "/mjb_shiboken.log"_L1);
+    QFile file(directory + u'/' + QLatin1StringView(generalLogFile));
     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
         qWarning(lcShiboken, "%s", qPrintable(msgCannotOpenForWriting(file)));
         return;
@@ -220,3 +222,13 @@ void ReportHandler::writeGeneralLogFile(const QString &directory)
         file.putChar('\n');
     }
 }
+
+void ReportHandler::dumpGeneralLogFile()
+{
+    std::fprintf(stdout, "\n--- %s  ---\n", generalLogFile);
+    for (const auto &m : std::as_const(generalMessages)) {
+        std::fputs(m.toUtf8().constData(), stdout);
+        std::fputc('\n', stdout);
+    }
+    std::fprintf(stdout, "--- End of %s ---\n\n", generalLogFile);
+}
index 9444901903d226e6765238780afc1ae489a3ed48..3562ab43a25d618c795d1fc5a2a1410f7c2eb509 100644 (file)
@@ -41,6 +41,7 @@ public:
 
     static void addGeneralMessage(const QString &message);
     static void writeGeneralLogFile(const QString &directory);
+    static void dumpGeneralLogFile();
 
 private:
     static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
index d704210f796231eb218097d928cd47dc598354c5..7b67647b9e8266111f8d11a310c39317cbe00250 100644 (file)
@@ -5,6 +5,7 @@
 #define SMARTPOINTERTYPEENTRY_H
 
 #include "complextypeentry.h"
+#include "customconversion_typedefs.h"
 
 class SmartPointerTypeEntryPrivate;
 
@@ -51,6 +52,10 @@ public:
 
     QString getTargetName(const AbstractMetaType &metaType) const;
 
+    bool hasCustomConversion() const;
+    void setCustomConversion(const CustomConversionPtr &customConversion);
+    CustomConversionPtr customConversion() const;
+
 #ifndef QT_NO_DEBUG_STREAM
     void formatDebug(QDebug &d) const override;
 #endif
index 3c09d380024091b1c97507c1bb2cfd59d985aa14..bb771ed1fba792f4c45a13569e14d06593247d1a 100644 (file)
@@ -3,7 +3,7 @@
 <WebXML>
     <document>
         <class name="A">
-            <description>oi
+            <description><para>before brief</para>
                 <brief>Brief description</brief>
                 <para>Paragraph number 1</para>
                 <para>Paragraph number 2</para>
index a95b71507ff6140b8c6b9931310c7ac88f2aaf5e..989b05cd9f7a7ad0f96a058149b93ece471a8dfa 100644 (file)
@@ -7,9 +7,13 @@
 #include <abstractmetalang.h>
 #include <complextypeentry.h>
 #include <typesystemtypeentry.h>
+#include <clangparser/compilersupport.h>
 
+#include <QtCore/qversionnumber.h>
 #include <QtTest/qtest.h>
 
+using namespace Qt::StringLiterals;
+
 void TestExtraInclude::testClassExtraInclude()
 {
     const char cppCode[] = "struct A {};\n";
@@ -61,4 +65,97 @@ void TestExtraInclude::testGlobalExtraIncludes()
     QCOMPARE(includes.constLast().name(), u"header2.h");
 }
 
+void TestExtraInclude::testParseTriplet_data()
+{
+    QTest::addColumn<QString>("triplet");
+    QTest::addColumn<bool>("expectedOk");
+    QTest::addColumn<Architecture>("expectedArchitecture");
+    QTest::addColumn<Platform>("expectedPlatform");
+    QTest::addColumn<bool>("expectedCompilerPresent");
+    QTest::addColumn<Compiler>("expectedCompiler");
+    QTest::addColumn<bool>("expectedPlatformVersionPresent");
+    QTest::addColumn<QVersionNumber>("expectedPlatformVersion");
+    QTest::addColumn<QByteArray>("expectedConverted"); // test back-conversion
+
+    QTest::newRow("Invalid")
+        << QString("Invalid"_L1)
+        << false << Architecture::X64 << Platform::Linux << false << Compiler::Gpp
+        << false << QVersionNumber{} << QByteArray{};
+
+    QTest::newRow("Linux")
+        << QString("x86_64-unknown-linux-gnu"_L1)
+        << true << Architecture::X64 << Platform::Linux << true << Compiler::Gpp
+        << false << QVersionNumber{}
+        << "x86_64-unknown-linux-gnu"_ba;
+
+    QTest::newRow("WindowsArm")
+        << QString("aarch64-pc-windows-msvc19.39.0"_L1)
+        << true << Architecture::Arm64 << Platform::Windows << true << Compiler::Msvc
+        << false << QVersionNumber{}
+        << "arm64-pc-windows-msvc"_ba;
+
+    QTest::newRow("Apple")
+        << QString("arm64-apple-macosx15.0.0"_L1)
+        << true << Architecture::Arm64 << Platform::macOS << false << Compiler::Gpp
+        << true << QVersionNumber{15, 0, 0}
+        << "arm64-apple-macosx15.0.0"_ba;
+
+    QTest::newRow("AndroidArm32")
+        << QString("armv7a-none-linux-android5.1"_L1)
+        << true << Architecture::Arm32 << Platform::Android << false << Compiler::Gpp
+        << true << QVersionNumber{5, 1}
+        << "armv7a-unknown-linux-android5.1"_ba;
+
+    QTest::newRow("AndroidArm64")
+        << QString("aarch64-none-linux-androideabi27.1"_L1)
+        << true << Architecture::Arm64 << Platform::Android << false << Compiler::Gpp
+        << true << QVersionNumber{27, 1}
+        << "aarch64-unknown-linux-android27.1"_ba;
+
+    QTest::newRow("iOS")
+        << QString("arm64-apple-ios"_L1)
+        << true << Architecture::Arm64 << Platform::iOS << false << Compiler::Gpp
+        << false << QVersionNumber{}
+        << "arm64-apple-ios"_ba;
+}
+
+void TestExtraInclude::testParseTriplet()
+{
+    QFETCH(QString, triplet);
+    QFETCH(bool, expectedOk);
+    QFETCH(Architecture, expectedArchitecture);
+    QFETCH(Platform, expectedPlatform);
+    QFETCH(bool, expectedCompilerPresent);
+    QFETCH(Compiler, expectedCompiler);
+    QFETCH(bool, expectedPlatformVersionPresent);
+    QFETCH(QVersionNumber, expectedPlatformVersion);
+    QFETCH(QByteArray, expectedConverted);
+
+    Architecture actualArchitecture{};
+    Platform actualPlatform{};
+    Compiler actualCompiler{};
+    QVersionNumber actualPlatformVersion;
+
+    const bool ok = clang::parseTriplet(triplet, &actualArchitecture, &actualPlatform,
+                                        &actualCompiler, &actualPlatformVersion);
+    QCOMPARE(ok, expectedOk);
+    if (ok) {
+        QCOMPARE(actualArchitecture, expectedArchitecture);
+        QCOMPARE(actualPlatform, expectedPlatform);
+        if (expectedPlatformVersionPresent) {
+            QCOMPARE(actualPlatformVersion.isNull(), expectedPlatformVersion.isNull());
+            QCOMPARE(actualPlatformVersion, expectedPlatformVersion);
+        } else {
+            actualPlatformVersion = QVersionNumber{}; // clear host version
+        }
+        if (expectedCompilerPresent)
+            QCOMPARE(expectedCompiler, actualCompiler);
+        if (expectedOk) {
+            auto actualConverted = clang::targetTripletForPlatform(actualPlatform, actualArchitecture,
+                                                                   actualCompiler, actualPlatformVersion);
+            QCOMPARE(actualConverted, expectedConverted);
+        }
+    }
+}
+
 QTEST_APPLESS_MAIN(TestExtraInclude)
index ae1a18686bdfc53667c436e862e5bc0ba06f373a..8a2493aa23deb5b24e90740cf85cb569f2350228 100644 (file)
@@ -12,6 +12,8 @@ class TestExtraInclude : public QObject
     private slots:
         void testClassExtraInclude();
         void testGlobalExtraIncludes();
+        void testParseTriplet_data();
+        void testParseTriplet();
 };
 
 #endif
index c2fc3b2127cfba5e9a9972fce30bd75055f43219..9e59ebde662832750ecfc6df6e0e804d0b46e973 100644 (file)
@@ -28,7 +28,7 @@ R"(<typesystem package="Foo">
     </value-type>
     <value-type name='A'>
     <modify-documentation xpath='description/brief'>&lt;brief>Modified Brief&lt;/brief></modify-documentation>
-    <modify-documentation xpath='description/para[3]'>&lt;para>Some changed contents here&lt;/para></modify-documentation>
+    <modify-documentation xpath='description/para[4]'>&lt;para>Some changed contents here&lt;/para></modify-documentation>
     </value-type>
 </typesystem>
 )";
@@ -66,8 +66,7 @@ R"(<typesystem package="Foo">
 
 const char expectedDoc[] =
 R"(<?xml version="1.0"?>
-<description>oi
-<para>Paragraph number 1</para>
+<description><para>before brief</para><para>Paragraph number 1</para>
 <para>Paragraph number 2</para>
 <para>Some changed contents here</para>
 </description>
index 8f79b4a7d08e22ef9ee560e5671ade5bb86afbcb..40501c35074bbd77693f59cec54a00dd68d0c338 100644 (file)
@@ -49,7 +49,7 @@ namespace TestUtil
         }
         QByteArrayList arguments;
         arguments.append(QFile::encodeName(tempSource.fileName()));
-        tempSource.write(cppCode, qint64(strlen(cppCode)));
+        tempSource.write(cppCode, qint64(qstrlen(cppCode)));
         tempSource.close();
 
         auto builder = std::make_unique<AbstractMetaBuilder>();
index 5cebd850a889af7a803b847c773f60229c303359..2e1f345cf1af0d5d49e96d4329e4fde2599b1ec6 100644 (file)
@@ -6,6 +6,7 @@
 #include "addedfunction.h"
 #include "messages.h"
 #include "typesystemparser_p.h"
+#include "clangparser/compilersupport.h"
 #include "complextypeentry.h"
 #include "constantvaluetypeentry.h"
 #include "containertypeentry.h"
@@ -433,10 +434,32 @@ void TypeDatabase::addRequiredTargetImport(const QString& moduleName)
         d->m_requiredTargetImports << moduleName;
 }
 
+static QStringList platformKeywords()
+{
+    static constexpr auto unixKeyword = "unix"_L1;
+    static constexpr auto linuxKeyword = "linux"_L1;
+    switch (clang::platform()) {
+    case Platform::Unix:
+        return {unixKeyword};
+    case Platform::Linux:
+        return {unixKeyword, linuxKeyword};
+    case Platform::Windows:
+        return {"windows"_L1};
+    case Platform::macOS:
+        return {unixKeyword, "darwin"_L1};
+    case Platform::Android:
+        return {unixKeyword, linuxKeyword, "android"_L1};
+    case Platform::iOS:
+        return {unixKeyword, "ios"_L1};
+    }
+    return {};
+}
+
 QStringList TypeDatabase::typesystemKeywords() const
 {
-    QStringList result = d->m_typesystemKeywords;
-    for (const auto &d : d->m_dropTypeEntries)
+    QStringList result = d->m_typesystemKeywords + platformKeywords();
+
+    for (const auto &d : std::as_const(d->m_dropTypeEntries))
         result.append("no_"_L1 + d);
 
     switch (clang::emulatedCompilerLanguageLevel()) {
index 0820150f894dc4cba7f5f6f59de180127d6c2e19..c02ec0c93129efc2cc31d6b126db82018e87cab6 100644 (file)
@@ -1225,7 +1225,9 @@ public:
     FlagsTypeEntryPtr m_flags;
     QString m_cppType;
     QString m_docFile;
+    std::weak_ptr<const EnumTypeEntry> m_aliasTypeEntry;
     TypeSystem::PythonEnumType m_pythonEnumType = TypeSystem::PythonEnumType::Unspecified;
+    EnumTypeEntry::AliasMode m_aliasMode = EnumTypeEntry::AliasMode::NoAlias;
 };
 
 EnumTypeEntry::EnumTypeEntry(const QString &entryName,
@@ -1330,6 +1332,30 @@ void EnumTypeEntry::setDocFile(const QString &df)
     d->m_docFile = df;
 }
 
+EnumTypeEntry::AliasMode EnumTypeEntry::aliasMode() const
+{
+    S_D(const EnumTypeEntry);
+    return d->m_aliasMode;
+}
+
+void EnumTypeEntry::setAliasMode(AliasMode am)
+{
+    S_D(EnumTypeEntry);
+    d->m_aliasMode = am;
+}
+
+EnumTypeEntryCPtr EnumTypeEntry::aliasTypeEntry() const
+{
+    S_D(const EnumTypeEntry);
+    return d->m_aliasTypeEntry.lock();
+}
+
+void EnumTypeEntry::setAliasTypeEntry(const EnumTypeEntryCPtr &entry)
+{
+    S_D(EnumTypeEntry);
+    d->m_aliasTypeEntry = entry;
+}
+
 TypeEntry *EnumTypeEntry::clone() const
 {
     S_D(const EnumTypeEntry);
@@ -2154,6 +2180,7 @@ public:
     QString m_resetMethod;
     SmartPointerTypeEntry::Instantiations m_instantiations;
     TypeEntryCList m_excludedInstantiations;
+    CustomConversionPtr m_customConversion;
     TypeSystem::SmartPointerType m_smartPointerType;
 };
 
@@ -2306,6 +2333,24 @@ QString SmartPointerTypeEntry::getTargetName(const AbstractMetaType &metaType) c
     return fixSmartPointerName(name);
 }
 
+bool SmartPointerTypeEntry::hasCustomConversion() const
+{
+    S_D(const SmartPointerTypeEntry);
+    return bool(d->m_customConversion);
+}
+
+void SmartPointerTypeEntry::setCustomConversion(const CustomConversionPtr &customConversion)
+{
+    S_D(SmartPointerTypeEntry);
+    d->m_customConversion = customConversion;
+}
+
+CustomConversionPtr SmartPointerTypeEntry::customConversion() const
+{
+    S_D(const SmartPointerTypeEntry);
+    return d->m_customConversion;
+}
+
 // ----------------- NamespaceTypeEntry
 class NamespaceTypeEntryPrivate : public ComplexTypeEntryPrivate
 {
index c0c3da1f64f7b11ce5ea38ee6f42c250d2ac29f4..2b876efc4e3ff6f2a8caaa6857541ffa3e6cd91d 100644 (file)
@@ -36,6 +36,7 @@ enum CodeSnipPosition {
     CodeSnipPositionEnd,
     CodeSnipPositionDeclaration,
     CodeSnipPositionPyOverride,
+    CodeSnipPositionWrapperDeclaration,
     CodeSnipPositionAny
 };
 
index 1d747419f5484973b42625918b66bfc6b9412fd5..e3a825e9ccb1877f52db189c8d2d0fa3d4be72f5 100644 (file)
@@ -341,7 +341,8 @@ ENUM_LOOKUP_BEGIN(TypeSystem::CodeSnipPosition, Qt::CaseInsensitive,
         {u"beginning", TypeSystem::CodeSnipPositionBeginning},
         {u"end", TypeSystem::CodeSnipPositionEnd},
         {u"declaration", TypeSystem::CodeSnipPositionDeclaration},
-        {u"override", TypeSystem::CodeSnipPositionPyOverride}
+        {u"override", TypeSystem::CodeSnipPositionPyOverride},
+        {u"wrapper-declaration", TypeSystem::CodeSnipPositionWrapperDeclaration}
     };
 ENUM_LOOKUP_LINEAR_SEARCH
 
@@ -1178,10 +1179,10 @@ bool TypeSystemParser::importFileElement(const QXmlStreamAttributes &atts)
         }
     }
     if (!foundFromOk || !foundToOk) {
-        QString fromError = QString::fromLatin1("Could not find quote-after-line='%1' in file '%2'.")
-                                                .arg(quoteFrom.toString(), fileName);
-        QString toError = QString::fromLatin1("Could not find quote-before-line='%1' in file '%2'.")
-                                              .arg(quoteTo.toString(), fileName);
+        QString fromError = "Could not find quote-after-line='%1' in file '%2'."_L1
+                            .arg(quoteFrom.toString(), fileName);
+        QString toError = "Could not find quote-before-line='%1' in file '%2'."_L1
+                          .arg(quoteTo.toString(), fileName);
 
         if (!foundToOk)
             m_error = toError;
@@ -2249,7 +2250,10 @@ TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStrea
 
     if (m_defaultPackage.isEmpty()) { // Extending default, see addBuiltInContainerTypes()
         auto moduleEntry = std::const_pointer_cast<TypeSystemTypeEntry>(m_context->db->defaultTypeSystemType());
-        Q_ASSERT(moduleEntry);
+        if (!moduleEntry) {
+            m_error = "No type system entry found (\"package\" attribute missing?)."_L1;
+            return {};
+        }
         m_defaultPackage = moduleEntry->name();
         return moduleEntry;
     }
@@ -2347,9 +2351,10 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &,
     if (topElement != StackElement::ModifyArgument
         && topElement != StackElement::ValueTypeEntry
         && topElement != StackElement::PrimitiveTypeEntry
-        && topElement != StackElement::ContainerTypeEntry) {
+        && topElement != StackElement::ContainerTypeEntry
+        && topElement != StackElement::SmartPointerTypeEntry) {
         m_error = u"Conversion rules can only be specified for argument modification, "
-                   "value-type, primitive-type or container-type conversion."_s;
+                   "value-type, primitive-type, or container-type or smartpointer-type conversion."_s;
         return false;
     }
 
@@ -2414,6 +2419,9 @@ bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &,
         std::static_pointer_cast<ContainerTypeEntry>(top->entry)->setCustomConversion(customConversion);
     else if (top->entry->isValue())
         std::static_pointer_cast<ValueTypeEntry>(top->entry)->setCustomConversion(customConversion);
+    else if (top->entry->isSmartPointer())
+        std::static_pointer_cast<SmartPointerTypeEntry>(top->entry)->setCustomConversion(customConversion);
+
     customConversionsForReview.append(customConversion);
     return true;
 }
@@ -2479,7 +2487,7 @@ static bool parseIndex(const QString &index, int *result, QString *errorMessage)
     bool ok = false;
     *result = index.toInt(&ok);
     if (!ok)
-        *errorMessage = QString::fromLatin1("Cannot convert '%1' to integer").arg(index);
+        *errorMessage = "Cannot convert '%1' to integer"_L1.arg(index);
     return ok;
 }
 
@@ -2691,8 +2699,8 @@ bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &,
         || topElement == StackElement::Root
         || topElement ==  StackElement::ContainerTypeEntry;
     if (!validParent) {
-        m_error = QString::fromLatin1("Add/Declare function requires a complex/container type or a root tag as parent"
-                                      ", was=%1").arg(tagFromElement(topElement));
+        m_error = "Add/Declare function requires a complex/container type or a root tag as parent, was=%1"_L1
+                  + tagFromElement(topElement);
         return false;
     }
 
@@ -2823,8 +2831,8 @@ bool TypeSystemParser::parseProperty(const ConditionalStreamReader &, StackEleme
                                      QXmlStreamAttributes *attributes)
 {
     if (!isComplexTypeEntry(topElement)) {
-        m_error = QString::fromLatin1("Add property requires a complex type as parent"
-                                      ", was=%1").arg(tagFromElement(topElement));
+        m_error = "Add property requires a complex type as parent, was=%1"_L1
+                  + tagFromElement(topElement);
         return false;
     }
 
@@ -2922,8 +2930,8 @@ bool TypeSystemParser::parseModifyFunction(const ConditionalStreamReader &reader
         || topElement == StackElement::TypedefTypeEntry
         || topElement == StackElement::FunctionTypeEntry;
     if (!validParent) {
-        m_error = QString::fromLatin1("Modify function requires complex type as parent"
-                                      ", was=%1").arg(tagFromElement(topElement));
+        m_error = "Modify function requires complex type as parent, was=%1"_L1
+                  + tagFromElement(topElement);
         return false;
     }
 
@@ -3590,6 +3598,8 @@ bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, Stack
         switch (element) {
         case StackElement::Root:
             top->entry = parseRootElement(reader, versionRange.since, &attributes);
+            if (!top->entry)
+                return false;
             break;
         case StackElement::LoadTypesystem:
             if (!loadTypesystem(reader, &attributes))
index bcaf31e89ee1821fd6c65a27e54b65e8edb29014..0e993fadb1df696229a2822d76700e538bb11dc1 100644 (file)
@@ -33,9 +33,13 @@ macro(set_debug_build)
 endmacro()
 
 macro(setup_sanitize_address)
-    # Currently this does not check that the clang / gcc version used supports Address sanitizer,
-    # so once again, use at your own risk.
-    add_compile_options("-fsanitize=address" "-g" "-fno-omit-frame-pointer")
+    # Currently this does not check that the clang / gcc / MSVC version used supports Address
+    # sanitizer, so once again, use at your own risk.
+    if(MSVC)
+        add_compile_options("/fsanitize=address")
+    else()
+        add_compile_options("-fsanitize=address" "-g" "-fno-omit-frame-pointer")
+    endif()
     # We need to add the sanitize address option to all linked executables / shared libraries
     # so that proper sanitizer symbols are linked in.
     #
@@ -44,7 +48,21 @@ macro(setup_sanitize_address)
     # sanitizer will tell you what environment variable needs to be exported. For example:
     # export DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/Toolchains/
     #   ./XcodeDefault.xctoolchain/usr/lib/clang/8.1.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib
-    set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_STANDARD_LIBRARIES} -fsanitize=address")
+    if(MSVC)
+        set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_STANDARD_LIBRARIES} /fsanitize=address")
+    else()
+        set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_STANDARD_LIBRARIES} -fsanitize=address")
+    endif()
+endmacro()
+
+macro(setup_sanitize_thread)
+    if(MSVC)
+        set(sanitize_thread_option "/fsanitize=thread")
+    else()
+        set(sanitize_thread_option "-fsanitize=thread")
+    endif()
+    add_compile_options("${sanitize_thread_option}")
+    set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_STANDARD_LIBRARIES} ${sanitize_thread_option}")
 endmacro()
 
 macro(set_cmake_cxx_flags)
@@ -347,7 +365,7 @@ macro(shiboken_find_required_python)
             Python
             ${_shiboken_find_python_version_args}
             REQUIRED
-            COMPONENTS Interpreter Development
+            COMPONENTS Interpreter Development.Module
         )
     endif()
 
@@ -869,28 +887,9 @@ endfunction()
 
 # Get path to libclang.dll/libclang.so depending on the platform
 macro(find_libclang)
-    if(CMAKE_HOST_WIN32)
-        set(libclang_directory_suffix "bin")
-        set(libclang_suffix ".dll")
-    else()
-        set(libclang_directory_suffix "lib")
-        if(CMAKE_HOST_APPLE)
-            set(libclang_suffix ".dylib")
-        else()
-            set(libclang_suffix ".so")
-        endif()
-    endif()
-
-    set(libclang_lib_dir "")
-    if(DEFINED ENV{LLVM_INSTALL_DIR})
-        set(libclang_lib_dir "$ENV{LLVM_INSTALL_DIR}/${libclang_directory_suffix}")
-    elseif(DEFINED ENV{CLANG_INSTALL_DIR})
-        set(libclang_lib_dir "$ENV{CLANG_INSTALL_DIR}/${libclang_directory_suffix}")
-    else()
-        message(WARNING
-            "Couldn't find libclang${libclang_suffix} "
-            "You will likely need to add it manually to PATH to ensure the build succeeds.")
-    endif()
+    find_package(Clang CONFIG REQUIRED)
+    get_target_property(libclang_location libclang LOCATION)
+    get_filename_component(libclang_lib_dir "${libclang_location}" DIRECTORY)
 endmacro()
 
 # Allow setting a shiboken debug level from the the build system or from the environment
index 45e04c9c73579086c389800f8da3ed0a120d0fe3..eb3c0e1f0042535e5b96fd2943833fcaf58c63dd 100644 (file)
@@ -10,30 +10,18 @@ endif()
 include(CMakePackageConfigHelpers)
 
 # Build-tree / super project package config file.
-set(SHIBOKEN_PYTHON_MODULE_DIR "${shiboken6_BINARY_DIR}/shibokenmodule")
-set(SHIBOKEN_SHARED_LIBRARY_DIR "${shiboken6_BINARY_DIR}/libshiboken")
-
 configure_package_config_file(
     "${CMAKE_CURRENT_SOURCE_DIR}/Shiboken6Config-spec.cmake.in"
     "${CMAKE_CURRENT_BINARY_DIR}/Shiboken6Config${PYTHON_CONFIG_SUFFIX}.cmake"
      INSTALL_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}"
-     PATH_VARS SHIBOKEN_PYTHON_MODULE_DIR SHIBOKEN_SHARED_LIBRARY_DIR
      INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
 )
 
 # Install-tree / relocatable package config file.
-set(SHIBOKEN_PYTHON_MODULE_DIR "${PYTHON_SITE_PACKAGES}/shiboken6")
-if (WIN32)
-    set(SHIBOKEN_SHARED_LIBRARY_DIR "${BIN_INSTALL_DIR}")
-else()
-    set(SHIBOKEN_SHARED_LIBRARY_DIR "${LIB_INSTALL_DIR}")
-endif()
-
 configure_package_config_file(
     "${CMAKE_CURRENT_SOURCE_DIR}/Shiboken6Config-spec.cmake.in"
     "${CMAKE_CURRENT_BINARY_DIR}/install/Shiboken6Config${PYTHON_CONFIG_SUFFIX}.cmake"
      INSTALL_DESTINATION "${LIB_INSTALL_DIR}/cmake/Shiboken6"
-     PATH_VARS SHIBOKEN_PYTHON_MODULE_DIR SHIBOKEN_SHARED_LIBRARY_DIR
 )
 
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Shiboken6Config.cmake.in"
index 233404bc6b39686993fe741c322b3bab34dc23a6..57ad645a48219503397272f959affe171f3d049c 100644 (file)
@@ -35,7 +35,4 @@ set(SHIBOKEN_PYTHON_CONFIG_SUFFIX "@PYTHON_CONFIG_SUFFIX@")
 set(SHIBOKEN_SO_VERSION "@shiboken6_library_so_version@")
 set(SHIBOKEN_BUILD_TYPE "@SHIBOKEN_BUILD_TYPE@")
 
-set_and_check(SHIBOKEN_PYTHON_MODULE_DIR "@PACKAGE_SHIBOKEN_PYTHON_MODULE_DIR@")
-set_and_check(SHIBOKEN_SHARED_LIBRARY_DIR "@PACKAGE_SHIBOKEN_SHARED_LIBRARY_DIR@")
-
 message(STATUS "libshiboken built for @SHIBOKEN_BUILD_TYPE@")
index 438b5c651411c24583236428bbc81af3d8ae79b0..fdc8e6dc47eea22de3cf5a683cc051a89e805ae3 100644 (file)
@@ -2,6 +2,29 @@
 
 cmake_minimum_required(VERSION 3.18)
 
+include(CMakeFindDependencyMacro)
+if(NOT CMAKE_CROSSCOMPILING)
+    find_dependency(Python COMPONENTS Interpreter Development.Module)
+
+    if(NOT SHIBOKEN6TOOLS_SKIP_FIND_DEPENDENCIES)
+        # Dynamically determine Python_SITELIB using Python itself
+        execute_process(
+            COMMAND ${Python_EXECUTABLE} -c
+            "import site; print(next(p for p in site.getsitepackages() if 'site-packages' in p))"
+            OUTPUT_VARIABLE Python_SITELIB
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+        )
+        list(APPEND CMAKE_PREFIX_PATH
+            "${Python_SITELIB}/shiboken6/lib/cmake"
+            "${Python_SITELIB}/PySide6/lib/cmake"
+        )
+        find_dependency(Shiboken6 REQUIRED)
+        find_dependency(PySide6 REQUIRED)
+    endif()
+endif()
+
 if(NOT TARGET Shiboken6::shiboken6)
     include("${CMAKE_CURRENT_LIST_DIR}/Shiboken6ToolsTargets.cmake")
 endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/Shiboken6ToolsMacros.cmake")
diff --git a/sources/shiboken6/data/Shiboken6ToolsMacros.cmake b/sources/shiboken6/data/Shiboken6ToolsMacros.cmake
new file mode 100644 (file)
index 0000000..b570e73
--- /dev/null
@@ -0,0 +1,187 @@
+# Function to configure a binding project
+function(shiboken_generator_create_binding)
+    set(options FORCE_LIMITED_API)
+    set(one_value_args
+        EXTENSION_TARGET
+        TYPESYSTEM_FILE
+        LIBRARY_TARGET)
+    set(multi_value_args
+        GENERATED_SOURCES
+        HEADERS
+        QT_MODULES
+        SHIBOKEN_EXTRA_OPTIONS)
+
+    cmake_parse_arguments(PARSE_ARGV 0 arg
+        "${options}"
+        "${one_value_args}"
+        "${multi_value_args}")
+
+    # Validate required arguments
+    foreach(req EXTENSION_TARGET GENERATED_SOURCES HEADERS TYPESYSTEM_FILE LIBRARY_TARGET)
+        if(NOT DEFINED arg_${req})
+            message(FATAL_ERROR "shiboken_generator_create_binding: ${req} is required")
+        endif()
+    endforeach()
+
+    get_target_property(shiboken_include_dirs Shiboken6::libshiboken INTERFACE_INCLUDE_DIRECTORIES)
+
+    # Get Shiboken path based on build type
+    if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+        get_target_property(shiboken_path Shiboken6::shiboken6 IMPORTED_LOCATION_DEBUG)
+    else()
+        get_target_property(shiboken_path Shiboken6::shiboken6 IMPORTED_LOCATION_RELEASE)
+    endif()
+
+    # Basic shiboken options
+    set(shiboken_options
+        --generator-set=shiboken
+        --enable-parent-ctor-heuristic
+        --enable-return-value-heuristic
+        --use-isnull-as-nb_nonzero
+        --avoid-protected-hack
+        -I${CMAKE_CURRENT_SOURCE_DIR}
+        -T${CMAKE_CURRENT_SOURCE_DIR}
+        --output-directory=${CMAKE_CURRENT_BINARY_DIR})
+
+    # Add extra options if specified
+    if(arg_SHIBOKEN_EXTRA_OPTIONS)
+        list(APPEND shiboken_options ${arg_SHIBOKEN_EXTRA_OPTIONS})
+    endif()
+
+    # Add Qt/PySide specific configurations only if Qt modules are specified
+    if(arg_QT_MODULES)
+        # Get Qt include directories
+        set(qt_include_dirs "")
+        foreach(module ${arg_QT_MODULES})
+            get_property(module_includes TARGET Qt6::${module} PROPERTY
+                INTERFACE_INCLUDE_DIRECTORIES)
+            list(APPEND qt_include_dirs ${module_includes})
+
+            # Check each module for framework on macOS
+            if(APPLE)
+                get_target_property(is_framework Qt6::${module} FRAMEWORK)
+                if(is_framework)
+                    get_target_property(lib_location Qt6::${module} LOCATION)
+                    get_filename_component(lib_dir "${lib_location}" DIRECTORY)
+                    get_filename_component(framework_dir "${lib_dir}/../" ABSOLUTE)
+                    list(APPEND shiboken_options "--framework-include-paths=${framework_dir}")
+                endif()
+            endif()
+
+            # Add include paths to shiboken options
+            foreach(include_dir ${module_includes})
+                list(APPEND shiboken_options "-I${include_dir}")
+            endforeach()
+        endforeach()
+
+        get_target_property(pyside_include_dir PySide6::pyside6 INTERFACE_INCLUDE_DIRECTORIES)
+
+        # Add PySide typesystems path
+        list(APPEND shiboken_options "-T${PYSIDE_TYPESYSTEMS}")
+
+        # Enable PySide extensions
+        list(APPEND shiboken_options "--enable-pyside-extensions")
+    endif()
+
+    # Generate binding sources
+    add_custom_command(
+        OUTPUT ${arg_GENERATED_SOURCES}
+        COMMAND "${shiboken_path}"
+        ${shiboken_options} ${arg_HEADERS} "${arg_TYPESYSTEM_FILE}"
+        DEPENDS ${arg_HEADERS} ${arg_TYPESYSTEM_FILE}
+        IMPLICIT_DEPENDS CXX ${arg_HEADERS}
+        WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+        COMMENT "Generating bindings for ${arg_EXTENSION_TARGET}"
+    )
+
+    # Create binding library
+    add_library(${arg_EXTENSION_TARGET} MODULE ${arg_GENERATED_SOURCES})
+
+    # set limited API
+    if(arg_FORCE_LIMITED_API OR FORCE_LIMITED_API)
+        target_compile_definitions(${arg_EXTENSION_TARGET} PRIVATE -DPy_LIMITED_API=0x03090000)
+    endif()
+
+
+    # Configure include paths
+    target_include_directories(
+        ${arg_EXTENSION_TARGET} PRIVATE
+        ${SHIBOKEN_PYTHON_INCLUDE_DIRS}
+        ${shiboken_include_dirs}
+        ${CMAKE_CURRENT_SOURCE_DIR}
+    )
+
+    # Link with Python, Shiboken and C++ library
+    target_link_libraries(
+        ${arg_EXTENSION_TARGET} PRIVATE
+        Shiboken6::libshiboken
+        ${arg_LIBRARY_TARGET}
+    )
+
+    if(arg_QT_MODULES)
+        # Add Qt and PySide includes
+        target_include_directories(
+            ${arg_EXTENSION_TARGET} PRIVATE ${qt_include_dirs}
+        )
+        target_include_directories(
+            ${arg_EXTENSION_TARGET} PRIVATE ${pyside_include_dir}
+        )
+
+        # Add PySide Qt module-specific includes and link libraries
+        foreach(module ${arg_QT_MODULES})
+            target_include_directories(
+                ${arg_EXTENSION_TARGET} PRIVATE "${pyside_include_dir}/Qt${module}"
+            )
+            target_link_libraries(
+                ${arg_EXTENSION_TARGET} PRIVATE Qt6::${module}
+            )
+        endforeach()
+
+        # Link base PySide6 library
+        target_link_libraries(
+            ${arg_EXTENSION_TARGET} PRIVATE PySide6::pyside6
+        )
+
+        # Link PySide6 QML library if Qml module is used
+        if("Qml" IN_LIST arg_QT_MODULES)
+            target_link_libraries(
+                ${arg_EXTENSION_TARGET} PRIVATE PySide6::pyside6qml
+            )
+        endif()
+    endif()
+
+    # Configure target properties
+    set_target_properties(
+        ${arg_EXTENSION_TARGET} PROPERTIES
+        PREFIX ""
+        OUTPUT_NAME "${arg_EXTENSION_TARGET}${SHIBOKEN_PYTHON_EXTENSION_SUFFIX}"
+    )
+
+    # Platform specific settings
+    if(WIN32)
+        # Add Python libraries only on Windows
+        get_property(SHIBOKEN_PYTHON_LIBRARIES GLOBAL PROPERTY shiboken_python_libraries)
+
+        target_link_libraries(
+            ${arg_EXTENSION_TARGET} PRIVATE "${SHIBOKEN_PYTHON_LIBRARIES}"
+        )
+
+        # Set Windows-specific suffix
+        if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+            set_property(
+                TARGET ${arg_EXTENSION_TARGET} PROPERTY SUFFIX "_d.pyd"
+            )
+        else()
+            set_property(
+                TARGET ${arg_EXTENSION_TARGET} PROPERTY SUFFIX ".pyd"
+            )
+        endif()
+    endif()
+
+    if(APPLE)
+        set_target_properties(
+            ${arg_EXTENSION_TARGET} PROPERTIES
+            LINK_FLAGS "-undefined dynamic_lookup"
+        )
+    endif()
+endfunction()
index a82d23168958f42e85d3359ce07881e0a32d07a9..917b706a3defb871d3ece50b07109c7e0a537440 100644 (file)
@@ -1,7 +1,7 @@
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=@CMAKE_INSTALL_PREFIX@
 libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@
-includedir=@CMAKE_INSTALL_PREFIX@/include/shiboken6
+includedir=@CMAKE_INSTALL_PREFIX@/shiboken6/include
 python_interpreter=@Python_EXECUTABLE@
 python_include_dir=@Python_INCLUDE_DIRS@
 
index 31df11b812ed73d6d125514bf55d2d6478c125d8..ce6a0612ac4fc7ebff5e65c808642c2d446d112a 100644 (file)
@@ -202,14 +202,37 @@ Options
     When '-' is passed as the first option in the list, none of the options
     built into shiboken will be added, allowing for a complete replacement.
 
+.. _compiler-option:
+
 ``--compiler=<type>``
     Emulated compiler type (g++, msvc, clang)
 
+.. _compiler-path-option:
+
 ``--compiler-path=<file>``
     Path to the compiler for determining builtin include paths
 
-``--platform=<file>``
-    Emulated platform (windows, darwin, unix)
+.. _compiler-argument-option:
+
+``compiler-argument=<argument>``
+    Add an argument for the compiler for determining builtin include paths
+
+.. _platform-option:
+
+``--platform=<name>``
+    Emulated platform (``android``, ``darwin``, ``ios``, ``linux``, ``unix``, ``windows``).
+    ``CMAKE_SYSTEM_NAME`` may be used.
+
+.. _platform-version-option:
+
+``--platform-version=<version>``
+    Platform version
+
+.. _arch-option:
+
+``--arch=<name>``
+    Emulated architecture (``x86_64``, ``arm64``, ``i586``).
+    ``CMAKE_SYSTEM_PROCESSOR`` may be used.
 
 .. _include-paths:
 
@@ -390,3 +413,131 @@ becomes
 .. code-block:: ini
 
      VALUE-ARGUMENT = VALUE
+
+
+.. _cross-compilation:
+
+Cross Compilation
+=================
+
+Shiboken uses **libclang** to parse the headers of the library to be exposed.
+When compiling for another platform, the clang parser should ideally use the
+target of the platform.
+
+Simple bindings may already work when the parser uses the default host platform
+target. But for bigger projects like Qt, it is important that macros like
+``QT_POINTER_SIZE`` and the platform defines ``Q_OS_XXX`` are set correctly
+when parsing files like ``qsystemdetection.h`` or ``qprocessordetection.h``.
+Some Qt API might be excluded depending on platform and there might be subtle
+differences depending on word size.
+
+For platform and architecture, the relevant command line options are
+:ref:`platform-option` and :ref:`arch-option`. They take common platform names
+and architectures as used in target triplets and can be set to the values of
+the CMake variables ``CMAKE_SYSTEM_NAME`` and ``CMAKE_SYSTEM_PROCESSOR``,
+respectively. If the specified platform is different from the host, Shiboken
+will pass a target triplet based on them to the clang parser.
+
+Optionally, the version of the platform can be specified using the
+:ref:`platform-version-option`. This is useful when the clang parser defaults
+to a too-old version.
+
+If this results in a wrong or too generic triplet, it is also possible to
+directly pass a target triplet in the Clang options specified by
+:ref:`clang_option`. In this case, Shiboken will not pass a target triplet and
+try to derive the platform/architecture from this triplet.
+
+When using the ``Clang`` and ``GNU`` compilers for cross-compiling, the
+:ref:`compiler-path-option` option should be specified since Shiboken may need
+to run the compiler to determine system include paths. For most cases, passing
+the value of the CMake variable ``CMAKE_CXX_COMPILER`` should work. If the
+compiler is in the path, it should suffice to pass the compiler type to
+:ref:`compiler-option` (value of ``CMAKE_CXX_COMPILER_ID``).
+
+It is possible (for example, when targeting Android) that ``CMAKE_CXX_COMPILER``
+is a generic compiler that also needs a ``--target=`` or similar option to
+locate the correct system include paths. In this case (shiboken failing due to
+not finding some system headers), the :ref:`compiler-argument-option` can be
+passed to specify the target.
+
+Typically, a ``CMakeLists.txt`` files will then look like:
+
+.. code-block:: cmake
+
+    if (CMAKE_CROSSCOMPILING)
+        list(APPEND shiboken_command "--platform=${CMAKE_SYSTEM_NAME}"
+                                     "--arch=${CMAKE_SYSTEM_PROCESSOR}"
+                                     "--compiler-path=${CMAKE_CXX_COMPILER}")
+    endif()
+
+When passing the target triplet:
+
+.. code-block:: cmake
+
+    if (CMAKE_CROSSCOMPILING)
+        list(APPEND shiboken_command "--clang-option=--target=aarch64-none-linux-android"
+                                     "--compiler-path=${CMAKE_CXX_COMPILER}")
+    endif()
+
+***********
+CMake Usage
+***********
+
+The ``Shiboken6Tools`` CMake package provides an easy way to invoke the
+Shiboken generator from CMake to create Python bindings for C++ libraries. It
+is contained in the ``shiboken6_generator`` wheel. This is achieved using the
+``shiboken_generator_create_binding`` CMake function. This function automates
+the process of generating binding sources and building the Python extension
+module.
+
+Function Signature
+==================
+
+.. code-block:: cmake
+
+    shiboken_generator_create_binding(
+        TARGET_NAME <name>
+        GENERATED_SOURCES <generated_sources>
+        HEADERS <headers>
+        TYPESYSTEM_FILE <typesystem_file>
+        CPP_LIBRARY <cpp_library>
+        [QT_MODULES <qt_modules>]
+        [EXTRA_OPTIONS <extra_options>]
+        [FORCE_LIMITED_API]
+    )
+
+Arguments
+*********
+
+* ``TARGET_NAME``: Name of the Python extension module target to create.
+* ``GENERATED_SOURCES``: List of C++ source files generated by Shiboken.
+* ``HEADERS``: List of C++ header files to parse.
+* ``TYPESYSTEM_FILE``: Path to the typesystem XML file.
+* ``CPP_LIBRARY``: C++ library to link against.
+* ``QT_MODULES`` (optional): List of Qt modules required for the binding.
+* ``EXTRA_OPTIONS`` (optional): Additional command line options for Shiboken.
+* ``FORCE_LIMITED_API`` (optional): Use the Limited API for the generated extension module.
+
+Usage Example
+*************
+
+.. code-block:: cmake
+
+    shiboken_generator_create_binding(
+        TARGET_NAME MyBinding
+        GENERATED_SOURCES ${generated_sources}
+        HEADERS ${wrapped_header}
+        TYPESYSTEM_FILE ${typesystem_file}
+        CPP_LIBRARY ${my_library}
+        QT_MODULES Core Gui Widgets
+        EXTRA_OPTIONS --some-extra-option
+        FORCE_LIMITED_API
+    )
+
+This macro will generate the binding sources, build the Python module, and link it with the specified
+libraries and include paths.
+
+For complete usage examples, see:
+
+* `SampleBinding Example <https://doc.qt.io/qtforpython-6/examples/example_samplebinding_samplebinding.html>`_
+* `WidgetBinding Example <https://doc.qt.io/qtforpython-6/examples/example_widgetbinding_widgetbinding.html>`_
index 90387ec1a2379862f94aae63e8db3543c845804d..4e54446c35358c0bc975a68020479da42b86d683 100644 (file)
@@ -192,8 +192,8 @@ holds the reference(s). It defaults to the function signature.
 For instance, in a model/view relation, a view receiving a model
 as argument for a **setModel()** method should increment the model's reference
 counting, since the model should be kept alive as long as the view lives.
-Remember that our hypothetical view cannot become a :ref:`parent` of the
-model, since the said model could be used by other views as well.
+Remember that our hypothetical view cannot become a :ref:`parent <ownership-parent>`
+of the model, since the said model could be used by other views as well.
 
 .. _parent-on-arguments:
 
index 0e047f38fc57ef380e74c5a7a2c210cb489913a9..d8b740d764e9264ed167dd684bb05bfc7a89aa2b 100644 (file)
@@ -29,80 +29,83 @@ into the **C++ Wrapper** or the **Python Wrapper** (see
 The ``position`` attribute specifies the location of the custom code in the
 function.
 
-
-+---------------+------+-----------+--------------------------------------------------------------+
-|Parent Tag     |Class |Position   |Meaning                                                       |
-+===============+======+===========+==============================================================+
-|value-type,    |native|beginning  |Write to the beginning of a class wrapper ``.cpp`` file, right|
-|object-type    |      |           |after the ``#include`` clauses. A common use would be to write|
-|               |      |           |prototypes for custom functions whose definitions are put on a|
-|               |      |           |``native/end`` code injection.                                |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |Write to the end of a class wrapper ``.cpp`` file. Could be   |
-|               |      |           |used to write custom/helper functions definitions for         |
-|               |      |           |prototypes declared on ``native/beginning``.                  |
-|               +------+-----------+--------------------------------------------------------------+
-|               |target|beginning  |Put custom code on the beginning of the wrapper initializer   |
-|               |      |           |function (``init_CLASS(PyObject *module)``). This could be    |
-|               |      |           |used to manipulate the ``PyCLASS_Type`` structure before      |
-|               |      |           |registering it on Python.                                     |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |Write the given custom code at the end of the class wrapper   |
-|               |      |           |initializer function (``init_CLASS(PyObject *module)``). The  |
-|               |      |           |code here will be executed after all the wrapped class        |
-|               |      |           |components have been initialized.                             |
-+---------------+------+-----------+--------------------------------------------------------------+
-|modify-function|native|beginning  |Code here is put on the virtual method override of a C++      |
-|               |      |           |wrapper class (the one responsible for passing C++ calls to a |
-|               |      |           |Python override, if there is any), right after the C++        |
-|               |      |           |arguments have been converted but before the Python call.     |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |This code injection is put in a virtual method override on the|
-|               |      |           |C++ wrapper class, after the call to Python and before        |
-|               |      |           |dereferencing the Python method and tuple of arguments.       |
-|               +------+-----------+--------------------------------------------------------------+
-|               |target|beginning  |This code is injected on the Python method wrapper            |
-|               |      |           |(``PyCLASS_METHOD(...)``), right after the decisor have found |
-|               |      |           |which signature to call and also after the conversion of the  |
-|               |      |           |arguments to be used, but before the actual call.             |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |This code is injected on the Python method wrapper            |
-|               |      |           |(``PyCLASS_METHOD(...)``), right after the C++ method call,   |
-|               |      |           |but still inside the scope created by the overload for each   |
-|               |      |           |signature.                                                    |
-|               +------+-----------+--------------------------------------------------------------+
-|               |shell |declaration|Used only for virtual functions. This code is injected at the |
-|               |      |           |top.                                                          |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |override   |Used only for virtual functions. The code is injected before  |
-|               |      |           |the code calling the Python override.                         |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |beginning  |Used only for virtual functions. The code is injected when the|
-|               |      |           |function does not has a Python implementation, then the code  |
-|               |      |           |is inserted before c++ call                                   |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |Same as above, but the code is inserted after c++ call        |
-+---------------+------+-----------+--------------------------------------------------------------+
-|typesystem     |native|beginning  |Write code to the beginning of the module ``.cpp`` file, right|
-|               |      |           |after the ``#include`` clauses. This position has a similar   |
-|               |      |           |purpose as the ``native/beginning`` position on a wrapper     |
-|               |      |           |class ``.cpp`` file, namely write function prototypes, but not|
-|               |      |           |restricted to this use.                                       |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |Write code to the end of the module ``.cpp`` file. Usually    |
-|               |      |           |implementations for function prototypes inserted at the       |
-|               |      |           |beginning of the file with a ``native/beginning`` code        |
-|               |      |           |injection.                                                    |
-|               +------+-----------+--------------------------------------------------------------+
-|               |target|beginning  |Insert code at the start of the module initialization function|
-|               |      |           |(``initMODULENAME()``), before the calling ``Py_InitModule``. |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |end        |Insert code at the end of the module initialization function  |
-|               |      |           |(``initMODULENAME()``), but before the checking that emits a  |
-|               |      |           |fatal error in case of problems importing the module.         |
-|               |      +-----------+--------------------------------------------------------------+
-|               |      |declaration|Insert code into module header.                               |
-+---------------+------+-----------+--------------------------------------------------------------+
++---------------+------+---------------------+--------------------------------------------------------------+
+|Parent Tag     |Class |Position             |Meaning                                                       |
++===============+======+=====================+==============================================================+
+|value-type,    |native|beginning            |Write to the beginning of a class wrapper ``.cpp`` file, right|
+|object-type    |      |                     |after the ``#include`` clauses. A common use would be to write|
+|               |      |                     |prototypes for custom functions whose definitions are put on a|
+|               |      |                     |``native/end`` code injection.                                |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |Write to the end of a class wrapper ``.cpp`` file. Could be   |
+|               |      |                     |used to write custom/helper functions definitions for         |
+|               |      |                     |prototypes declared on ``native/beginning``.                  |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      | wrapper-declaration |Write into the declaration of the wrapper class, right after  |
+|               |      |                     |the ``public:`` keyword. This can be used for importing base  |
+|               |      |                     |class members via ``using``.                                  |
+|               +------+---------------------+--------------------------------------------------------------+
+|               |target|beginning            |Put custom code on the beginning of the wrapper initializer   |
+|               |      |                     |function (``init_CLASS(PyObject *module)``). This could be    |
+|               |      |                     |used to manipulate the ``PyCLASS_Type`` structure before      |
+|               |      |                     |registering it on Python.                                     |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |Write the given custom code at the end of the class wrapper   |
+|               |      |                     |initializer function (``init_CLASS(PyObject *module)``). The  |
+|               |      |                     |code here will be executed after all the wrapped class        |
+|               |      |                     |components have been initialized.                             |
++---------------+------+---------------------+--------------------------------------------------------------+
+|modify-function|native|beginning            |Code here is put on the virtual method override of a C++      |
+|               |      |                     |wrapper class (the one responsible for passing C++ calls to a |
+|               |      |                     |Python override, if there is any), right after the C++        |
+|               |      |                     |arguments have been converted but before the Python call.     |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |This code injection is put in a virtual method override on the|
+|               |      |                     |C++ wrapper class, after the call to Python and before        |
+|               |      |                     |dereferencing the Python method and tuple of arguments.       |
+|               +------+---------------------+--------------------------------------------------------------+
+|               |target|beginning            |This code is injected on the Python method wrapper            |
+|               |      |                     |(``PyCLASS_METHOD(...)``), right after the decisor have found |
+|               |      |                     |which signature to call and also after the conversion of the  |
+|               |      |                     |arguments to be used, but before the actual call.             |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |This code is injected on the Python method wrapper            |
+|               |      |                     |(``PyCLASS_METHOD(...)``), right after the C++ method call,   |
+|               |      |                     |but still inside the scope created by the overload for each   |
+|               |      |                     |signature.                                                    |
+|               +------+---------------------+--------------------------------------------------------------+
+|               |shell |declaration          |Used only for virtual functions. This code is injected at the |
+|               |      |                     |top.                                                          |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |override             |Used only for virtual functions. The code is injected before  |
+|               |      |                     |the code calling the Python override.                         |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |beginning            |Used only for virtual functions. The code is injected when the|
+|               |      |                     |function does not has a Python implementation, then the code  |
+|               |      |                     |is inserted before c++ call                                   |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |Same as above, but the code is inserted after c++ call        |
++---------------+------+---------------------+--------------------------------------------------------------+
+|typesystem     |native|beginning            |Write code to the beginning of the module ``.cpp`` file, right|
+|               |      |                     |after the ``#include`` clauses. This position has a similar   |
+|               |      |                     |purpose as the ``native/beginning`` position on a wrapper     |
+|               |      |                     |class ``.cpp`` file, namely write function prototypes, but not|
+|               |      |                     |restricted to this use.                                       |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |Write code to the end of the module ``.cpp`` file. Usually    |
+|               |      |                     |implementations for function prototypes inserted at the       |
+|               |      |                     |beginning of the file with a ``native/beginning`` code        |
+|               |      |                     |injection.                                                    |
+|               +------+---------------------+--------------------------------------------------------------+
+|               |target|beginning            |Insert code at the start of the module initialization function|
+|               |      |                     |(``initMODULENAME()``), before the calling ``Py_InitModule``. |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |end                  |Insert code at the end of the module initialization function  |
+|               |      |                     |(``initMODULENAME()``), but before the checking that emits a  |
+|               |      |                     |fatal error in case of problems importing the module.         |
+|               |      +---------------------+--------------------------------------------------------------+
+|               |      |declaration          |Insert code into module header.                               |
++---------------+------+---------------------+--------------------------------------------------------------+
 
 
 Anatomy of Code Injection
@@ -335,7 +338,7 @@ Code injections to the class Python initialization function.
             return;
 
         Py_INCREF(&PyInjectCode_Type);
-        PyModule_AddObject(module, "InjectCode",
+        PyModule_Add(module, "InjectCode",
             ((PyObject*)&PyInjectCode_Type));
 
         // INJECT-CODE: <value/object-type><inject-code class="target" position="end">
index cee45bfb37b25160e0d7d4deddf2ef636d484683..f6ce18345d8c6e8a95585415b965458f42e4cfc5 100644 (file)
@@ -12,9 +12,13 @@ The **conversion-rule** tag specifies how a **primitive-type**, a **container-ty
 or a **value-type** may be converted to and from the native C++ language types to the
 target language types (see also :ref:`user-defined-type-conversion`).
 
-It is a child of the :ref:`container-type`, :ref:`primitive-type` or
-:ref:`value-type` and may contain :ref:`native-to-target` or
-:ref:`native-to-target` child nodes.
+It may be a child of the :ref:`container-type` and :ref:`primitive-type` nodes,
+where conversions have to be provided for both directions using the
+:ref:`native-to-target` and :ref:`target-to-native` child nodes.
+
+It may also appear as a child of :ref:`value-type` or :ref:`smart-pointer-type`
+where additional conversions from other target language types can be provided
+using the :ref:`target-to-native` child node.
 
 .. code-block:: xml
 
@@ -70,8 +74,9 @@ an input value an does what's needed to convert it to the output value.
     </conversion-rule>
 
 Use the replace node to modify the template code.
-Notice that the generator must provide type system variables for the input
-and output values and types, namely **%in**, **%out**, **%INTYPE** and
+Notice that the generator provides type system variables for the input
+and output values and types (see :ref:`converter_variables_and_functions`).
+The most important ones are **%in**, **%out**, **%INTYPE** and
 **%OUTTYPE**. In the case of container types, **%INTYPE** refers to the
 full container type (e.g. **"list<int>"**) and **%INTYPE_0**, **%INTYPE_1**,
 **%INTYPE_#**, should be replaced by the types used in the container template
index c03d203b99d2f9b0cbc070f206925591ef46ccbc..7fe4df39276cab6d3301536e1e7d54a119a75167 100644 (file)
@@ -208,6 +208,9 @@ can be generated for them. Instead, an instance of the viewed class should
 be instantiated and passed to functions using the view class
 for argument types.
 
+It is also possible to specify template specializations
+like "std::optional<int>" as primitive types with converters.
+
 See :ref:`predefined_templates` for built-in templates for standard type
 conversion rules.
 
@@ -344,6 +347,23 @@ production of ABI compatible bindings.
 The **flags-revision** attribute has the same purposes of **revision** attribute but
 is used for the QFlag related to this enum.
 
+An enum can also be a C++ type alias:
+
+.. code-block:: c++
+
+    enum Option { Value1 = 0; }
+
+    class SomeClass {
+        public:
+        using OptionAlias = Option;
+    };
+
+In this case, when specifying `<enum-type name="OptionAlias"...>` in
+`SomeClass`, an enumeration `OptionAlias` will be generated into the class. The
+values of `OptionAlias` and `Option` can be used interchangeably. This feature
+is specifically intended for renaming enumerations by deprecating; it works for
+at most one alias.
+
 .. _reject-enum-value:
 
 reject-enum-value
@@ -726,7 +746,8 @@ The ``smart pointer`` type node indicates that the given class is a smart pointe
 and requires inserting calls to **getter** to access the pointeee.
 Currently, the usage is limited to function return values.
 **ref-count-method** specifies the name of the method used to do reference counting.
-It is a child of the :ref:`typesystem_details` node or other type nodes.
+It is a child of the :ref:`typesystem_details` node or other type nodes
+and may contain :ref:`conversion-rule` nodes.
 
 The *optional* attribute **instantiations** specifies for which instantiations
 of the smart pointer wrappers will be generated (comma-separated list).
index 6dfd1f80178070353ba006a15f1385f697322926..179c97ad42e01bd271d18b0c0239f14c8c3fefea 100644 (file)
@@ -218,14 +218,14 @@ conversion code (see :ref:`converter_variables_and_functions`).
 
   .. code-block:: c++
 
-      long a = PyLong_AS_LONG(%PYARG_1);
+      long a = PyLong_AsLong(%PYARG_1);
 
 
   is equivalent of
 
   .. code-block:: c++
 
-      long a = PyLong_AS_LONG(PyTuple_GetItem(%PYTHON_ARGUMENTS, 0));
+      long a = PyLong_AsLong(PyTuple_GetItem(%PYTHON_ARGUMENTS, 0));
 
 
   The generator tries to be smart with attributions, but it will work for the
index 454e9cf7f3ac186fd1133f4a2da73691b914091d..997468f02ad89db5983dd182f6e9ab21fafc3985 100644 (file)
@@ -69,6 +69,18 @@ install(EXPORT "${package_name}Targets"
         NAMESPACE "Shiboken6::"
         DESTINATION ${LIB_INSTALL_DIR}/cmake/${package_name})
 
+# Add wheel specific installation
+if(NOT is_pyside6_superproject_build)
+    install(TARGETS shiboken6
+            EXPORT "${package_name}WheelTargets"
+            DESTINATION "shiboken6_generator")
+
+    install(EXPORT "${package_name}WheelTargets"
+            NAMESPACE "Shiboken6::"
+            DESTINATION "${LIB_INSTALL_DIR}/wheels/cmake/${package_name}"
+            FILE "${package_name}Targets.cmake")
+endif()
+
 set(shiboken_generator_package_name "shiboken6_generator")
 
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/_config.py.in"
@@ -101,6 +113,11 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_git_shiboken_generator_version.py"
 
 include(CMakePackageConfigHelpers)
 
+# Copy macros file to build tree
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../data/${package_name}Macros.cmake"
+               "${CMAKE_CURRENT_BINARY_DIR}/${package_name}Macros.cmake"
+               COPYONLY)
+
 # Single build-tree and install-tree Config file. There's no need for separate ones because we
 # don't specify any PATH_VARS, so the relative path of PACKAGE_PREFIX_DIR does not really matter.
 configure_package_config_file(
@@ -115,8 +132,9 @@ write_basic_package_version_file(
     ARCH_INDEPENDENT
 )
 
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${package_name}Config.cmake"
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/${package_name}")
-
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${package_name}ConfigVersion.cmake"
-        DESTINATION "${LIB_INSTALL_DIR}/cmake/${package_name}")
+# Install the config files
+install(FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/${package_name}Config.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/${package_name}ConfigVersion.cmake"
+    "${CMAKE_CURRENT_BINARY_DIR}/${package_name}Macros.cmake"
+    DESTINATION "${LIB_INSTALL_DIR}/cmake/${package_name}")
index 808234a7c3b026540b92f25d438580f23d42c066..5b04e73828c1b517e91de0393557b9a7dae69f29 100644 (file)
@@ -370,7 +370,7 @@ QString Generator::getFullTypeName(const AbstractMetaType &type)
         typeName = getFullTypeNameWithoutModifiers(type);
     else
         typeName = getFullTypeName(type.typeEntry());
-    return typeName + QString::fromLatin1("*").repeated(type.indirections());
+    return typeName + QString(type.indirections(), u'*');
 }
 
 QString Generator::getFullTypeName(const AbstractMetaClassCPtr &metaClass)
index 80047d7ce7d83096dbd5ee57d23a4425d2d10a33..3fd5383de5a9b1b1c2edcd9c2d97eabc408725a4 100644 (file)
@@ -19,6 +19,7 @@
 #include <QtCore/qdir.h>
 #include <QtCore/qfile.h>
 #include <QtCore/qlibrary.h>
+#include <QtCore/qversionnumber.h>
 #include <QtCore/qscopeguard.h>
 #include <QtCore/qvariant.h>
 
@@ -85,11 +86,19 @@ OptionDescriptions CommonOptionsParser::optionDescriptions()
         {u"documentation-only"_s,
          u"Do not generates any code, just the documentation"_s},
         {u"compiler=<type>"_s,
-         u"Emulated compiler type (g++, msvc, clang)"_s},
+         u"Emulated compiler type (g++/gnu, msvc, clang). CMAKE_CXX_COMPILER_ID may be used."_s},
         {u"platform=<name>"_s,
-         u"Emulated platform (windows, darwin, unix)"_s},
+         u"Emulated platform (android, darwin, ios, linux, unix, windows)."
+          " CMAKE_SYSTEM_NAME may be used."_s},
+        {u"platform-version=<version>"_s,
+         u"Platform version"_s},
+        {u"arch=<name>"_s,
+         u"Emulated architecture (x86_64, arm64, i586)."
+          " CMAKE_SYSTEM_PROCESSOR may be used."_s},
         {u"compiler-path=<file>"_s,
          u"Path to the compiler for determining builtin include paths"_s},
+        {u"compiler-argument=<argument>"_s,
+         u"Add an argument for the compiler for determining builtin include paths"_s},
         {u"generator-set=<\"generator module\">"_s,
          u"generator-set to be used. e.g. qtdoc"_s},
         {u"diff"_s, u"Print a diff of wrapper files"_s},
@@ -185,17 +194,40 @@ bool CommonOptionsParser::handleOption(const QString &key, const QString &value,
         return true;
     }
     if (key == u"compiler") {
-        if (!clang::setCompiler(value))
-            throw Exception(u"Invalid value \""_s + value + u"\" passed to --compiler"_s);
+        if (!clang::setCompiler(value)) {
+            qCWarning(lcShiboken, "Invalid compiler \"%s\" passed to --compiler, defaulting to host.",
+                      qPrintable(value));
+        }
         return true;
     }
     if (key == u"compiler-path") {
         clang::setCompilerPath(value);
         return true;
     }
+    if (key == u"compiler-argument") {
+        clang::addCompilerArgument(value);
+        return true;
+    }
+
     if (key == u"platform") {
-        if (!clang::setPlatform(value))
-            throw Exception(u"Invalid value \""_s + value + u"\" passed to --platform"_s);
+        if (!clang::setPlatform(value)) {
+            qCWarning(lcShiboken, "Invalid value \"%s\" passed to --platform, defaulting to host.",
+                      qPrintable(value));
+        }
+        return true;
+    }
+
+    if (key == u"platform-version") {
+        if (!clang::setPlatformVersion(value))
+            throw Exception("Invalid value "_L1 + value + " passed to --platform-version."_L1);
+        return true;
+    }
+
+    if (key == u"arch") {
+        if (!clang::setArchitecture(value)) {
+            qCWarning(lcShiboken, "Invalid architecture \"%s\" passed to --arch  defaulting to host.",
+                      qPrintable(value));
+        }
         return true;
     }
 
@@ -436,5 +468,7 @@ int wmain(int argc, wchar_t *argv[])
         std::cerr << appName << " error: " << e.what() << '\n';
         ex = EXIT_FAILURE;
     }
+    if (ex != 0 && qEnvironmentVariableIsSet("COIN_UNIQUE_JOB_ID"))
+        ReportHandler::dumpGeneralLogFile();
     return ex;
 }
index 7cd28be139d53fa0f6a28c8ab6b8db3ac1c386bd..3875c53fb864dfe56dd7853ddace95c88862e7aa 100644 (file)
@@ -492,6 +492,40 @@ void QtDocGenerator::generateClassRecursion(TextStream &s, const QString &target
     }
 }
 
+void QtDocGenerator::writeDetailedDescription(TextStream &s,
+                                              const AbstractMetaClassCPtr &metaClass,
+                                              const QString &scope,
+                                              QtXmlToSphinxImages *parsedImages) const
+{
+    auto documentation = metaClass->documentation();
+    writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, metaClass,
+                             parsedImages);
+    if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, metaClass,
+                                  parsedImages))
+        writeFormattedDetailedText(s, documentation, scope, parsedImages);
+    writeInjectDocumentation(s, TypeSystem::DocModificationAppend, metaClass,
+                             parsedImages);
+}
+
+enum ClassDescriptionMode
+{
+    NoDescription,
+    BriefOnly,
+    DetailedOnly,
+    BriefAndDetailed,
+    BriefAndDetailedSections,
+};
+
+static ClassDescriptionMode classDescriptionMode(const Documentation &doc)
+{
+    if (!doc.hasDetailed())
+        return doc.hasBrief() ? BriefOnly : NoDescription;
+    if (!doc.hasBrief())
+        return DetailedOnly;
+    return doc.detailed().contains("<section"_L1)
+        ? BriefAndDetailedSections : BriefAndDetailed;
+}
+
 void QtDocGenerator::doGenerateClass(TextStream &s, const QString &targetDir,
                                      const AbstractMetaClassCPtr &metaClass)
 {
@@ -506,8 +540,33 @@ void QtDocGenerator::doGenerateClass(TextStream &s, const QString &targetDir,
     QtXmlToSphinxImages parsedImages;
     auto documentation = metaClass->documentation();
     const QString scope = classScope(metaClass);
-    if (documentation.hasBrief())
+
+    const auto descriptionMode = classDescriptionMode(documentation);
+    switch (descriptionMode) {
+    case NoDescription:
+    case DetailedOnly:
+        break;
+    case BriefOnly:
         writeFormattedBriefText(s, documentation, scope, &parsedImages);
+        break;
+    case BriefAndDetailed: {
+        // A "collapse" sphinx directive can be used for brief/expanding to details
+        // for descriptions consisting of a paragraph sequence.
+        writeFormattedBriefText(s, documentation, scope, &parsedImages);
+        s << "\n\n.. collapse:: Details\n\n";
+        Indentation detailIndent(s);
+        writeDetailedDescription(s, metaClass, scope, &parsedImages);
+    }
+        break;
+    case BriefAndDetailedSections: {
+        // If the the description has nested <section>'s (which break collapse::), we
+        // use a 'more' label for the detailed text to be written further down.
+        QString brief = documentation.brief();
+        brief.insert(brief.lastIndexOf(u'<'), "<rst> More_...</rst>"_L1);
+        writeFormattedText(s, brief, documentation.format(), scope, &parsedImages);
+    }
+        break;
+    }
 
     if (!metaClass->baseClasses().isEmpty()) {
         if (m_options.inheritanceDiagram) {
@@ -545,13 +604,17 @@ void QtDocGenerator::doGenerateClass(TextStream &s, const QString &targetDir,
          "    translation, you can also let us know by creating a ticket on\n"
          "    https:/bugreports.qt.io/projects/PYSIDE\n\n";
 
-    s << '\n' << headline("Detailed Description") << ".. _More:\n";
-
-    writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, metaClass,
-                             &parsedImages);
-    if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, metaClass, &parsedImages))
-        writeFormattedDetailedText(s, documentation, scope, &parsedImages);
-    writeInjectDocumentation(s, TypeSystem::DocModificationAppend, metaClass, &parsedImages);
+    switch (descriptionMode) {
+    case DetailedOnly:
+    case BriefAndDetailedSections:
+        s << '\n' << headline("Detailed Description");
+        if (descriptionMode == BriefAndDetailedSections)
+            s << ".. _More:\n";
+        writeDetailedDescription(s, metaClass, scope, &parsedImages);
+        break;
+    default:
+        break;
+    }
 
     writeEnums(s, metaClass->enums(), scope, &parsedImages);
 
@@ -901,8 +964,8 @@ QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type,
             strType.remove(u"QHash"_s);
             strType.remove(u"QMap"_s);
             QStringList types = strType.split(u',');
-            strType = QString::fromLatin1("Dictionary with keys of type %1 and values of type %2.")
-                                         .arg(types[0], types[1]);
+            strType = "Dictionary with keys of type %1 and values of type %2."_L1
+                      .arg(types[0], types[1]);
         }
         return strType;
     }
index 8937814a3d080a16deb9d23297c137492623feca..ac5e22f34b21d45b76f428a5ee8a3e1da45fba7f 100644 (file)
@@ -69,6 +69,9 @@ private:
                                 QList<GeneratorContext> *contexts);
     void doGenerateClass(TextStream &ts, const QString &targetDir,
                          const AbstractMetaClassCPtr &metaClass);
+    void writeDetailedDescription(TextStream &s,
+                                  const AbstractMetaClassCPtr &metaClass, const QString &scope,
+                                  QtXmlToSphinxImages *parsedImages) const;
     void writeEnums(TextStream &s, const AbstractMetaEnumList &enums,
                     const QString &scope, QtXmlToSphinxImages *images) const;
 
index bddf2f51177e90afe142a718fe35714ab1335e03..3ffe80f3416b30af7c2acb65ef8a04b613fe8b17 100644 (file)
@@ -18,6 +18,8 @@
 #include <QtCore/qregularexpression.h>
 #include <QtCore/qxmlstream.h>
 
+#include <cstring>
+
 using namespace Qt::StringLiterals;
 
 QDebug operator<<(QDebug debug, const QtXmlToSphinxImage &i)
@@ -198,9 +200,9 @@ enum class WebXmlTag {
     heading, brief, para, italic, bold, see_also, snippet, dots, codeline,
     table, header, row, item, argument, teletype, link, inlineimage, image,
     list, term, raw, underline, superscript, code, badcode, legalese,
-    rst, section, quotefile,
+    rst, section, quotefile, target, keyword, page, group,
     // ignored tags
-    generatedlist, tableofcontents, quotefromfile, skipto, target, page, group,
+    generatedlist, tableofcontents, quotefromfile, skipto,
     // useless tags
     description, definition, printuntil, relation,
     // Doxygen tags
@@ -251,6 +253,7 @@ static const WebXmlTagHash &webXmlTagHash()
         {u"quotefromfile", WebXmlTag::quotefromfile},
         {u"skipto", WebXmlTag::skipto},
         {u"target", WebXmlTag::target},
+        {u"keyword", WebXmlTag::keyword},
         {u"page", WebXmlTag::page},
         {u"group", WebXmlTag::group},
         {u"description", WebXmlTag::description},
@@ -401,6 +404,7 @@ void QtXmlToSphinx::callHandler(WebXmlTag t, QXmlStreamReader &r)
         handleIgnoredTag(r);
         break;
     case WebXmlTag::target:
+    case WebXmlTag::keyword:
         handleTargetTag(r);
         break;
     case WebXmlTag::page:
@@ -688,15 +692,13 @@ QString QtXmlToSphinx::readSnippet(const QString &location, const QString &ident
 void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
 {
     static int headingSize = 0;
-    static char type;
-    static char types[] = { '-', '^' };
+    static char type{};
+    static constexpr const char types[] = R"(#*=-^")";
     QXmlStreamReader::TokenType token = reader.tokenType();
     if (token == QXmlStreamReader::StartElement) {
-        uint typeIdx = reader.attributes().value(u"level"_s).toUInt();
-        if (typeIdx >= sizeof(types))
-            type = types[sizeof(types)-1];
-        else
-            type = types[typeIdx];
+        // Levels are 1..n. We start at #2 since <page> already uses '#' (1) for the title.
+        const auto typeIdx = std::size_t(reader.attributes().value(u"level"_s).toUInt()); // level 1..n
+        type = types[std::min(typeIdx, std::strlen(types) - 1)];
     } else if (token == QXmlStreamReader::EndElement) {
         m_output << disableIndent << Pad(type, headingSize) << "\n\n"
             << enableIndent;
@@ -1305,7 +1307,7 @@ void QtXmlToSphinx::handlePageTag(QXmlStreamReader &reader)
        ? writeEscapedRstText(m_output, title)
        : writeEscapedRstText(m_output, fullTitle);
 
-    m_output << '\n' << Pad('*', size) << "\n\n"
+    m_output << '\n' << Pad('#', size) << "\n\n"
         << enableIndent;
 }
 
@@ -1315,7 +1317,7 @@ void QtXmlToSphinx::handleTargetTag(QXmlStreamReader &reader)
         return;
     const auto  name = reader.attributes().value("name");
     if (!name.isEmpty())
-        m_output << rstLabel(name.toString());
+        m_output << disableIndent << rstLabel(name.toString()) << enableIndent;
 }
 
 void QtXmlToSphinx::handleIgnoredTag(QXmlStreamReader&)
index 654b502103625f442d2b4a10dba0059f072023eb..36f34de5d787243726eac2db55f83b76cc03486c 100644 (file)
@@ -88,6 +88,23 @@ TextStream &operator<<(TextStream &str, const sbkUnusedVariableCast &c)
     return str;
 }
 
+struct retrieveWrapper
+{
+    explicit retrieveWrapper(const AbstractMetaClassCPtr &klass,
+                             QAnyStringView instanceName = "this")
+        : m_klass(klass), m_instanceName(instanceName) {}
+
+    const AbstractMetaClassCPtr m_klass;
+    const QAnyStringView m_instanceName;
+};
+
+TextStream &operator<<(TextStream &str, const retrieveWrapper &rw)
+{
+    str << "Shiboken::BindingManager::instance().retrieveWrapper(" << rw.m_instanceName
+        << ", " << CppGenerator::cpythonTypeName(rw.m_klass) << ')';
+    return str;
+}
+
 struct pyTypeGetSlot
 {
     explicit pyTypeGetSlot(QAnyStringView funcType, QAnyStringView typeObject,
@@ -336,15 +353,6 @@ static QString compilerOptionOptimize()
     return result;
 }
 
-QString CppGenerator::chopType(QString s)
-{
-    if (s.endsWith(u"_Type"))
-        s.chop(5);
-    else if (s.endsWith(u"_TypeF()"))
-        s.chop(8);
-    return s;
-}
-
 static bool isStdSetterName(const QString &setterName, const QString &propertyName)
 {
    return setterName.size() == propertyName.size() + 3
@@ -474,10 +482,17 @@ static QString BuildEnumFlagInfo(const AbstractMetaEnum &cppEnum)
 }
 
 static void writePyGetSetDefEntry(TextStream &s, const QString &name,
-                                  const QString &getFunc, const QString &setFunc)
+                                  const QString &getFunc, const QString &setFunc, const QString &doc={})
 {
-    s << "{const_cast<char *>(\"" << mangleName(name) << "\"), " << getFunc << ", "
-        << (setFunc.isEmpty() ? NULL_PTR : setFunc) << ", nullptr, nullptr},\n";
+    s << "{\"" << mangleName(name) << "\", " << getFunc << ", "
+        << (setFunc.isEmpty() ? NULL_PTR : setFunc) << ", ";
+
+    if (doc.isEmpty())
+        s << "nullptr";
+    else
+        s << "\"" << doc << "\"";
+
+    s << ", nullptr},\n";
 }
 
 static bool generateRichComparison(const GeneratorContext &c)
@@ -511,7 +526,7 @@ void CppGenerator::generateIncludes(TextStream &s, const GeneratorContext &class
                                "type_traits"}; // enum/underlying type
     // headers
     s << "// default includes\n";
-    s << "#include <shiboken.h>\n";
+    s << "#include <shiboken.h>\n#include <sbkpep.h>\n#include <sbkpepbuffer.h>\n";
     if (wrapperDiagnostics()) {
         s << "#include <helper.h>\n";
         cppIncludes << "iostream";
@@ -702,6 +717,8 @@ void CppGenerator::generateClass(TextStream &s,
 
     s  << '\n';
 
+    writeClassTypeFunction(s, classContext.metaClass());
+
     // class inject-code native/beginning
     if (!typeEntry->codeSnips().isEmpty()) {
         writeClassCodeSnips(s, typeEntry->codeSnips(),
@@ -812,7 +829,7 @@ void CppGenerator::generateClass(TextStream &s,
     const QString methodsDefinitions = md.toString();
     const QString singleMethodDefinitions = smd.toString();
 
-    const QString className = chopType(cpythonTypeName(metaClass));
+    const QString className = cpythonBaseName(metaClass);
 
     // Write single method definitions
     s << singleMethodDefinitions;
@@ -913,8 +930,9 @@ void CppGenerator::generateClass(TextStream &s,
                 const QString setter = canGenerateSetter
                     ? cpythonSetterFunctionName(metaField) : QString();
                 const auto names = metaField.definitionNames();
+                const QString doc = metaField.type().pythonSignature();
                 for (const auto &name : names)
-                    writePyGetSetDefEntry(s, name, getter, setter);
+                    writePyGetSetDefEntry(s, name, getter, setter, doc);
             }
         }
 
@@ -978,7 +996,7 @@ void CppGenerator::writeCacheResetNative(TextStream &s, const GeneratorContext &
 {
     s << "void " << classContext.wrapperName()
         << "::resetPyMethodCache()\n{\n" << indent
-        << "std::fill_n(m_PyMethodCache, sizeof(m_PyMethodCache) / sizeof(m_PyMethodCache[0]), false);\n"
+        << "std::fill(m_PyMethodCache.begin(), m_PyMethodCache.end(), nullptr);\n"
         << outdent << "}\n\n";
 }
 
@@ -1014,9 +1032,8 @@ void CppGenerator::writeDestructorNative(TextStream &s,
     if (wrapperDiagnostics())
         s << R"(std::cerr << __FUNCTION__ << ' ' << this << '\n';)" << '\n';
     // kill pyobject
-    s << R"(SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(this);
-Shiboken::Object::destroy(wrapper, this);
-)" << outdent << "}\n";
+    s << "auto *wrapper = " << retrieveWrapper(classContext.metaClass())
+      << ";\nShiboken::Object::destroy(wrapper, this);\n" << outdent << "}\n";
 }
 
 // Return type for error messages when getting invalid types from virtual
@@ -1062,8 +1079,8 @@ QString CppGenerator::getVirtualFunctionReturnTypeName(const AbstractMetaFunctio
     if (func->type().isPrimitive())
         return u'"' + func->type().name() + u'"';
 
-    return u"Shiboken::SbkType< "_s
-        + typeEntry->qualifiedCppName() + u" >()->tp_name"_s;
+    return u"PepType_GetFullyQualifiedNameStr(Shiboken::SbkType< "_s
+        + typeEntry->qualifiedCppName() + u" >())"_s;
 }
 
 void CppGenerator::writeVirtualMethodCppCall(TextStream &s,
@@ -1296,7 +1313,8 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s,
     const QString funcName = func->isOperatorOverload()
         ? pythonOperatorFunctionName(func) : func->definitionNames().constFirst();
 
-    QString className = wrapperName(func->ownerClass());
+    auto owner = func->ownerClass();
+    QString className = wrapperName(owner);
     const Options options = Generator::SkipDefaultValues | Generator::OriginalTypeDescription;
     s << functionSignature(func, className, {}, options)
       << "\n{\n" << indent;
@@ -1338,7 +1356,8 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s,
     s << "static PyObject *nameCache[2] = {};\n"
       << "Shiboken::GilState gil(false);\n"
       << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR << "(Sbk_GetPyOverride("
-      << "this, gil, funcName, &m_PyMethodCache[" << cacheIndex << "], nameCache));\n"
+      << "this, " << CppGenerator::cpythonTypeName(owner) << ", gil, funcName, m_PyMethodCache["
+      << cacheIndex << "], nameCache));\n"
       << "if (pyOverride.isNull()) {\n" << indent;
     writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType,
                               returnStatement.statement, false, true);
@@ -1352,7 +1371,6 @@ void CppGenerator::writeVirtualMethodNative(TextStream &s,
     if (!func->isVoid())
         s << "return ";
 
-    auto owner = func->ownerClass();
     const auto &reusedFuncs = getReusedOverridenFunctions(owner);
     auto rit = reusedFuncs.constFind(func);
     const bool canReuse = rit != reusedFuncs.cend();
@@ -1436,7 +1454,7 @@ void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s,
 
     if (!snips.isEmpty()) {
         if (func->injectedCodeUsesPySelf())
-            s << "PyObject *pySelf = BindingManager::instance().retrieveWrapper(this);\n";
+            s << "PyObject *pySelf = " << retrieveWrapper(func->ownerClass()) << ";\n";
 
         const AbstractMetaArgument *lastArg = func->arguments().isEmpty()
                                             ? nullptr : &func->arguments().constLast();
@@ -1656,10 +1674,11 @@ void CppGenerator::writeMetaCast(TextStream &s,
     const QString qualifiedCppName = classContext.metaClass()->qualifiedCppName();
     s << "void *" << wrapperClassName << "::qt_metacast(const char *_clname)\n{\n"
         << indent << "if (_clname == nullptr)\n" << indent << "return {};\n" << outdent
-        << "SbkObject *pySelf = Shiboken::BindingManager::instance().retrieveWrapper(this);\n"
-        << "if (pySelf != nullptr && PySide::inherits(Py_TYPE(pySelf), _clname))\n"
+        << "if (SbkObject *pySelf = Shiboken::BindingManager::instance().retrieveWrapper(this)) {\n" << indent
+        << "auto *obSelf = reinterpret_cast<PyObject *>(pySelf);\n"
+        << "if (PySide::inherits(Py_TYPE(obSelf), _clname))\n"
         << indent << "return static_cast<void *>(const_cast< "
-        << wrapperClassName << " *>(this));\n" << outdent
+        << wrapperClassName << " *>(this));\n" << outdent << outdent << "}\n"
         << "return " << qualifiedCppName << "::qt_metacast(_clname);\n"
         << outdent << "}\n\n";
 }
@@ -1717,6 +1736,17 @@ void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMeta
     writePythonToCppFunction(s, c.toString(), enumConverterPythonType, typeName);
 
     QString pyTypeCheck = u"PyObject_TypeCheck(pyIn, "_s + enumPythonType + u')';
+    switch (metaEnum.typeEntry()->aliasMode()) {
+    case EnumTypeEntry::NoAlias:
+        break;
+    case EnumTypeEntry::AliasSource:
+    case EnumTypeEntry::AliasTarget: {
+        const QString &aliasSourceType = cpythonTypeNameExt(metaEnum.typeEntry()->aliasTypeEntry());
+        pyTypeCheck += "\n    || PyObject_TypeCheck(pyIn, "_L1 + aliasSourceType + u')';
+    }
+        break;
+    }
+
     writeIsPythonConvertibleToCppFunction(s, enumConverterPythonType, typeName, pyTypeCheck);
 
     c.clear();
@@ -1724,9 +1754,9 @@ void CppGenerator::writeEnumConverterFunctions(TextStream &s, const AbstractMeta
     c << "using IntType = std::underlying_type_t<" << cppTypeName << ">;\n"
          "const auto castCppIn = IntType(*reinterpret_cast<const "
         << cppTypeName << " *>(cppIn));\n" << "return "
-        << "Shiboken::Enum::newItem(" << enumPythonType << ", castCppIn);\n";
+        << "Shiboken::Enum::newItem(pyType, castCppIn);\n";
     s << '\n';
-    writeCppToPythonFunction(s, c.toString(), typeName, enumConverterPythonType);
+    writeCppToPythonFunction(s, c.toString(), typeName, enumConverterPythonType, true);
     s << '\n';
 
     auto flags = enumType->flags();
@@ -1753,7 +1783,7 @@ static void writePointerToPythonConverter(TextStream &c,
                                           const QString &typeName,
                                           const QString &cpythonType)
 {
-    c << "auto *pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(cppIn));\n"
+    c << "auto *pyOut = reinterpret_cast<PyObject *>(" << retrieveWrapper(metaClass, "cppIn") << ");\n"
         << "if (pyOut) {\n" << indent
         << "Py_INCREF(pyOut);\nreturn pyOut;\n" << outdent
         << "}\n";
@@ -2101,6 +2131,24 @@ void CppGenerator::writeCustomConverterRegister(TextStream &s,
     }
 }
 
+void CppGenerator::writeTemplateCustomConverterRegister(TextStream &s,
+                                                        const AbstractMetaType &type,
+                                                        QString converter)
+{
+    auto customConversion = CustomConversion::getCustomConversion(type.typeEntry());
+    if (!customConversion || customConversion->targetToNativeConversions().isEmpty())
+        return;
+    if (converter.isEmpty())
+        converter = converterVar;
+    const QString typeName = fixedCppTypeName(type);
+    for (const auto &conv : customConversion->targetToNativeConversions()) {
+        const QString &sourceTypeName = conv.sourceTypeName();
+        QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName);
+        QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName);
+        writeAddPythonToCppConversion(s, converter, toCpp, isConv);
+    }
+}
+
 void CppGenerator::writeContainerConverterFunctions(TextStream &s,
                                                     const AbstractMetaType &containerType) const
 {
@@ -2315,12 +2363,8 @@ void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &ov
     // Python owns it and C++ wrapper is false.
     if (shouldGenerateCppWrapper(overloadData.referenceFunction()->ownerClass()))
         s << "Shiboken::Object::setHasCppWrapper(sbkSelf, true);\n";
-    // Need to check if a wrapper for same pointer is already registered
-    // Caused by bug PYSIDE-217, where deleted objects' wrappers are not released
-    s << "if (Shiboken::BindingManager::instance().hasWrapper(cptr)) {\n" << indent
-        << "Shiboken::BindingManager::instance().releaseWrapper("
-           "Shiboken::BindingManager::instance().retrieveWrapper(cptr));\n" << outdent
-        << "}\nShiboken::BindingManager::instance().registerWrapper(sbkSelf, cptr);\n";
+
+    s << "Shiboken::BindingManager::instance().registerWrapper(sbkSelf, cptr);\n";
 
     // Create metaObject and register signal/slot
     if (needsMetaObject) {
@@ -3389,15 +3433,17 @@ QString CppGenerator::convertibleToCppFunctionName(const TargetToNativeConversio
 }
 
 void CppGenerator::writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName,
-                                            const QString &targetTypeName) const
+                                            const QString &targetTypeName, bool withType) const
 {
 
     QString prettyCode = code;
     const QString funcName = cppToPythonFunctionName(sourceTypeName, targetTypeName);
     processCodeSnip(prettyCode, funcName);
 
-    s << "static PyObject *" << funcName
-        << "(const void *cppIn)\n{\n" << indent << prettyCode
+    s << "static PyObject *" << funcName <<'(';
+    if (withType)
+        s << "PyTypeObject *pyType, ";
+    s << "const void *cppIn)\n{\n" << indent << prettyCode
         << ensureEndl << outdent << "}\n";
 }
 
@@ -3502,9 +3548,16 @@ void CppGenerator::writeIsPythonConvertibleToCppFunction(TextStream &s,
         if (!condition.contains(u"pyIn"))
             s << sbkUnusedVariableCast("pyIn");
     }
-    s << "if (" << condition << ")\n" << indent
-        << "return " << pythonToCppFuncName << ";\n" << outdent
-        << "return {};\n" << outdent << "}\n";
+
+    const bool useBrace = condition.contains(u'\n');
+    s << "if (" << condition << ')';
+    if (useBrace)
+        s<< " {";
+    s << '\n' << indent
+      << "return " << pythonToCppFuncName << ";\n" << outdent;
+    if (useBrace)
+        s<< "}\n";
+    s << "return {};\n" << outdent << "}\n";
 }
 
 void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
@@ -3562,14 +3615,7 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
     writePythonToCppFunction(s, code, sourceTypeName, targetTypeName);
 
     // Python to C++ convertible check function.
-    QString typeCheck = toNative.sourceTypeCheck();
-    if (typeCheck.isEmpty()) {
-        QString pyTypeName = toNative.sourceTypeName();
-        if (pyTypeName == u"Py_None" || pyTypeName == u"PyNone")
-            typeCheck = u"%in == Py_None"_s;
-        else if (pyTypeName == u"SbkObject")
-            typeCheck = u"Shiboken::Object::checkType(%in)"_s;
-    }
+    QString typeCheck = toNative.sourceTypeCheckFallback();
     if (typeCheck.isEmpty()) {
         if (!toNative.sourceType() || toNative.sourceType()->isPrimitive()) {
             QString m;
@@ -3580,33 +3626,47 @@ void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
         typeCheck = u"PyObject_TypeCheck(%in, "_s
                     + cpythonTypeNameExt(toNative.sourceType()) + u')';
     }
-    typeCheck.replace(u"%in"_s, u"pyIn"_s);
-    processCodeSnip(typeCheck, targetType->qualifiedCppName());
+    processTypeCheckCodeSnip(typeCheck, targetType->qualifiedCppName());
     writeIsPythonConvertibleToCppFunction(s, sourceTypeName, targetTypeName, typeCheck);
 }
 
-void CppGenerator::writePythonToCppConversionFunctions(TextStream &s, const AbstractMetaType &containerType) const
+void CppGenerator::writePythonToCppConversionFunctions(TextStream &s,
+                                                       const AbstractMetaType &templateType) const
 {
-    Q_ASSERT(containerType.typeEntry()->isContainer());
-    const auto cte = std::static_pointer_cast<const ContainerTypeEntry>(containerType.typeEntry());
-    const auto customConversion = cte->customConversion();
-    for (const auto &conv : customConversion->targetToNativeConversions())
-        writePythonToCppConversionFunction(s, containerType, conv);
+    const auto customConversion = CustomConversion::getCustomConversion(templateType.typeEntry());
+    if (customConversion) {
+        const auto &conversions = customConversion->targetToNativeConversions();
+        for (const auto &conv : conversions)
+            writePythonToCppConversionFunction(s, templateType, conv);
+    }
 }
 
 void CppGenerator::writePythonToCppConversionFunction(TextStream &s,
-                                                      const AbstractMetaType &containerType,
+                                                      const AbstractMetaType &templateType,
                                                       const TargetToNativeConversion &conv) const
 {
+    // Python to C++ convertible check function.
+    QString typeName = fixedCppTypeName(templateType);
+    // Check fallback is too broad for containers that need elements of same type
+    QString typeCheck = templateType.isContainer()
+        ? conv.sourceTypeCheck() : conv.sourceTypeCheckFallback();
+    if (typeCheck.isEmpty()) {
+        typeCheck = cpythonCheckFunction(templateType);
+        if (typeCheck.isEmpty())
+            typeCheck = u"false"_s;
+        else
+            typeCheck = typeCheck + u"pyIn)"_s;
+    }
+
     // Python to C++ conversion function.
-    QString cppTypeName = getFullTypeNameWithoutModifiers(containerType);
+    QString cppTypeName = getFullTypeNameWithoutModifiers(templateType);
     QString code = conv.conversion();
     const QString line = u"auto &cppOutRef = *reinterpret_cast<"_s
         + cppTypeName + u" *>(cppOut);"_s;
     CodeSnipAbstract::prependCode(&code, line);
-    for (qsizetype i = 0; i < containerType.instantiations().size(); ++i) {
-        const AbstractMetaType &type = containerType.instantiations().at(i);
-        QString typeName = getFullTypeName(type);
+    for (qsizetype i = 0; i < templateType.instantiations().size(); ++i) {
+        const AbstractMetaType &type = templateType.instantiations().at(i);
+        QString instTypeName = getFullTypeName(type);
         // Containers of opaque containers are not handled here.
         const auto generatorArg = GeneratorArgument::fromMetaType(type);
         if (generatorArg.indirections > 0 && !type.generateOpaqueContainer()) {
@@ -3620,23 +3680,19 @@ void CppGenerator::writePythonToCppConversionFunction(TextStream &s,
                 rightCode.replace(varName, u'*' + varName);
                 code.replace(pos, code.size() - pos, rightCode);
             }
-            typeName.append(u" *"_s);
+            instTypeName.append(" *"_L1);
         }
-        code.replace(u"%OUTTYPE_"_s + QString::number(i), typeName);
+        const QString var = "%OUTTYPE_"_L1 + QString::number(i);
+        code.replace(var, instTypeName);
+        typeCheck.replace(var, instTypeName);
     }
     code.replace(u"%OUTTYPE"_s, cppTypeName);
     code.replace(u"%in"_s, u"pyIn"_s);
     code.replace(u"%out"_s, u"cppOutRef"_s);
-    QString typeName = fixedCppTypeName(containerType);
     const QString &sourceTypeName = conv.sourceTypeName();
     writePythonToCppFunction(s, code, sourceTypeName, typeName);
 
-    // Python to C++ convertible check function.
-    QString typeCheck = cpythonCheckFunction(containerType);
-    if (typeCheck.isEmpty())
-        typeCheck = u"false"_s;
-    else
-        typeCheck = typeCheck + u"pyIn)"_s;
+    processTypeCheckCodeSnip(typeCheck, typeName); // needs %OUTTYPE_[n]
     writeIsPythonConvertibleToCppFunction(s, sourceTypeName, typeName, typeCheck);
     s << '\n';
 }
@@ -4423,8 +4479,7 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s,
         s << '&' << targetTypeName << "_Type";
     }
 
-    const QString typeName = fixedCppTypeName(type);
-    s << ", " << cppToPythonFunctionName(typeName, targetTypeName) << ");\n";
+    s << ", " << cppToPythonFunctionName(fixedCppTypeName(type), targetTypeName) << ");\n";
 
     s << registerConverterName(cppSignature, converter);
     if (usePySideExtensions() && cppSignature.startsWith("const "_L1)
@@ -4433,12 +4488,7 @@ QString CppGenerator::writeContainerConverterInitialization(TextStream &s,
         s << registerConverterName(underlyingType, converter);
     }
 
-    for (const auto &conv : typeEntry->customConversion()->targetToNativeConversions()) {
-        const QString &sourceTypeName = conv.sourceTypeName();
-        QString toCpp = pythonToCppFunctionName(sourceTypeName, typeName);
-        QString isConv = convertibleToCppFunctionName(sourceTypeName, typeName);
-        writeAddPythonToCppConversion(s, converter, toCpp, isConv);
-    }
+    writeTemplateCustomConverterRegister(s, type, converter);
 
     auto typedefItPair = api.typedefTargetToName().equal_range(type.cppSignature());
     if (typedefItPair.first != typedefItPair.second) {
@@ -4570,6 +4620,17 @@ static QString docString(const AbstractMetaClassCPtr &metaClass)
     return it != docModifs.cend() ? it->code().trimmed() : QString{};
 }
 
+void CppGenerator::writeClassTypeFunction(TextStream &s,
+                                          const AbstractMetaClassCPtr &metaClass)
+{
+    const QString className = cpythonBaseName(metaClass);
+    const QString typePtr = u"_"_s + className + u"_Type"_s;
+    s << openExternC << "static PyTypeObject *" << typePtr << " = nullptr;\n"
+        << "static PyTypeObject *" << className << "_TypeF(void)\n"
+        << "{\n" << indent << "return " << typePtr << ";\n" << outdent << "}\n"
+        << closeExternC;
+}
+
 void CppGenerator::writeClassDefinition(TextStream &s,
                                         const AbstractMetaClassCPtr &metaClass,
                                         const GeneratorContext &classContext)
@@ -4578,7 +4639,7 @@ void CppGenerator::writeClassDefinition(TextStream &s,
     QString tp_dealloc;
     QString tp_hash;
     QString tp_call;
-    const QString className = chopType(cpythonTypeName(metaClass));
+    const QString className = cpythonBaseName(metaClass);
 
     bool onlyPrivCtor = !metaClass->hasNonPrivateConstructor();
 
@@ -4664,8 +4725,8 @@ void CppGenerator::writeClassDefinition(TextStream &s,
         s << '\n';
     }
 
-    s << "// Class Definition -----------------------------------------------\n"
-         "extern \"C\" {\n";
+    s << "\n// Class Definition -----------------------------------------------\n"
+          << openExternC;
 
     if (hasHashFunction(metaClass))
         tp_hash = u'&' + cpythonBaseName(metaClass) + u"_HashFunc"_s;
@@ -4674,12 +4735,7 @@ void CppGenerator::writeClassDefinition(TextStream &s,
     if (callOp && !callOp->isModifiedRemoved())
         tp_call = u'&' + cpythonFunctionName(callOp);
 
-    const QString typePtr = u"_"_s + className
-        + u"_Type"_s;
-    s << "static PyTypeObject *" << typePtr << " = nullptr;\n"
-        << "static PyTypeObject *" << className << "_TypeF(void)\n"
-        << "{\n" << indent << "return " << typePtr << ";\n" << outdent
-        << "}\n\nstatic PyType_Slot " << className << "_slots[] = {\n" << indent
+    s << "\nstatic PyType_Slot " << className << "_slots[] = {\n" << indent
         << "{Py_tp_base,        nullptr}, // inserted by introduceWrapperType\n"
         << pyTypeSlotEntry("Py_tp_dealloc", tp_dealloc)
       << pyTypeSlotEntry("Py_tp_repr", m_tpFuncs.value(REPR_FUNCTION))
@@ -4938,7 +4994,7 @@ QString CppGenerator::writeCopyFunction(TextStream &s,
                                         const GeneratorContext &context)
 {
     const auto &metaClass = context.metaClass();
-    const QString className = chopType(cpythonTypeName(metaClass));
+    const QString className = cpythonBaseName(metaClass);
     const QString funcName = className + u"__copy__"_s;
 
     // PYSIDE-3135 replace _Self by Self when the minimum Python version is 3.11
@@ -5018,27 +5074,7 @@ void CppGenerator::writeGetterFunction(TextStream &s,
         cppField = u"cppOut_local"_s;
     }
 
-    s << "PyObject *pyOut = {};\n";
     if (newWrapperSameObject) {
-        // Special case colocated field with same address (first field in a struct)
-        s << "if (reinterpret_cast<void *>("
-            << cppField << ") == reinterpret_cast<void *>("
-            << CPP_SELF_VAR << ")) {\n" << indent
-            << "pyOut = reinterpret_cast<PyObject *>(Shiboken::Object::findColocatedChild("
-            << "reinterpret_cast<SbkObject *>(self), "
-            << cpythonTypeNameExt(fieldType) << "));\n"
-            << "if (pyOut != nullptr) {\n" << indent
-            << "Py_IncRef(pyOut);\n"
-            << "return pyOut;\n"
-            << outdent << "}\n";
-        // Check if field wrapper has already been created.
-        s << outdent << "} else if (Shiboken::BindingManager::instance().hasWrapper("
-            << cppField << ")) {" << "\n" << indent
-            << "pyOut = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper("
-            << cppField << "));" << "\n"
-            << "Py_IncRef(pyOut);" << "\n"
-            << "return pyOut;" << "\n"
-            << outdent << "}\n";
         // Create and register new wrapper. We force a pointer conversion also
         // for wrapped value types so that they refer to the struct member,
         // avoiding any trouble copying them. Add a parent relationship to
@@ -5047,15 +5083,23 @@ void CppGenerator::writeGetterFunction(TextStream &s,
         // unsolved issues when using temporary Python lists of structs
         // which can cause elements to be reported deleted in expressions like
         // "foo.list_of_structs[2].field".
-        s << "pyOut = "
-            << "Shiboken::Object::newObject(" << cpythonTypeNameExt(fieldType)
-            << ", " << cppField << ", false, true);\n"
-            << "Shiboken::Object::setParent(self, pyOut)";
+        s << "PyObject *pyOut = {};\n"
+            << "auto *fieldTypeObject = " << cpythonTypeNameExt(fieldType) << ";\n"
+            << "if (auto *sbkOut = Shiboken::BindingManager::instance().retrieveWrapper("
+            << cppField << ", fieldTypeObject)) {\n" << indent
+            << "pyOut = reinterpret_cast<PyObject *>(sbkOut);\n"
+            << "Py_INCREF(pyOut);\n" << outdent << "} else {\n" << indent
+            << "pyOut = Shiboken::Object::newObject(fieldTypeObject, "
+            << cppField << ", false, true);\n"
+            << "Shiboken::Object::setParent(self, pyOut);\n"
+            << outdent << "}\n"
+            << "return pyOut;\n";
     } else {
-        s << "pyOut = ";
+        s << "return ";
         writeToPythonConversion(s, fieldType, metaField.enclosingClass(), cppField);
+        s << ";\n";
     }
-    s << ";\nreturn pyOut;\n" << outdent << "}\n";
+    s << outdent << "}\n";
 }
 
 // Write a getter for QPropertySpec
@@ -5448,14 +5492,14 @@ void CppGenerator::writeSignatureInfo(TextStream &s, const OverloadData &overloa
     }
 }
 
-void CppGenerator::writeEnumsInitialization(TextStream &s, AbstractMetaEnumList &enums)
+void CppGenerator::writeEnumsInitialization(TextStream &s, const AbstractMetaEnumList &enums)
 {
     if (enums.isEmpty())
         return;
     bool preambleWritten = false;
     bool etypeUsed = false;
 
-    for (const AbstractMetaEnum &cppEnum : std::as_const(enums)) {
+    for (const AbstractMetaEnum &cppEnum : enums) {
         if (cppEnum.isPrivate())
             continue;
         if (!preambleWritten) {
@@ -5471,6 +5515,14 @@ void CppGenerator::writeEnumsInitialization(TextStream &s, AbstractMetaEnumList
         s << sbkUnusedVariableCast("EType");
 }
 
+void CppGenerator::writeEnumsInitFunc(TextStream &s, const QString &funcName,
+                                      const AbstractMetaEnumList &enums)
+{
+     s << "static void " << funcName << "(PyObject *module)\n{\n" << indent;
+     writeEnumsInitialization(s, enums);
+     s << outdent << "}\n\n";
+}
+
 static qsizetype maxLineLength(const QStringList &list)
 {
     qsizetype result = 0;
@@ -5583,7 +5635,7 @@ bool CppGenerator::writeEnumInitialization(TextStream &s, const AbstractMetaEnum
                     << indent << (isSigned ? "PyLong_FromLongLong" : "PyLong_FromUnsignedLongLong")
                     << "(" << pyValue << "));\n" << outdent;
             } else {
-                s << "PyModule_AddObject(module, \"" << mangledName << "\",\n" << indent
+                s << "PepModule_Add(module, \"" << mangledName << "\",\n" << indent
                     << (isSigned ? "PyLong_FromLongLong" : "PyLong_FromUnsignedLongLong") << "("
                     << pyValue << "));\n" << outdent;
             }
@@ -5812,7 +5864,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
     AbstractMetaClassCPtr enc = metaClass->targetLangEnclosingClass();
     QString enclosingObjectVariable = enc ? u"enclosingClass"_s : u"module"_s;
 
-    QString pyTypeName = cpythonTypeName(metaClass);
+    QString pyTypePrefix = cpythonBaseName(metaClass);
     QString initFunctionName = getInitFunctionName(classContext);
 
     // PYSIDE-510: Create a signatures string for the introspection feature.
@@ -5827,7 +5879,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
         << "return " << globalTypeVarExpr << ";\n\n" << outdent;
 
     // Multiple inheritance
-    QString pyTypeBasesVariable = chopType(pyTypeName) + u"_Type_bases"_s;
+    QString pyTypeBasesVariable = pyTypePrefix + u"_Type_bases"_s;
     const QStringList pyBases = pyBaseTypes(metaClass);
     s << "Shiboken::AutoDecRef " << pyTypeBasesVariable << "(PyTuple_Pack("
         << pyBases.size() << ",\n" << indent;
@@ -5839,8 +5891,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
     s << "));\n\n" << outdent;
 
     // Create type and insert it in the module or enclosing class.
-    const QString typePtr = u"_"_s + chopType(pyTypeName)
-        + u"_Type"_s;
+    const QString typePtr = u"_"_s + pyTypePrefix + u"_Type"_s;
 
     s << typePtr << " = Shiboken::ObjectType::introduceWrapperType(\n" << indent;
     // 1:enclosingObject
@@ -5861,7 +5912,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
 
     s << "\",\n";
     // 4:typeSpec
-    s << '&' << chopType(pyTypeName) << "_spec,\n";
+    s << '&' << pyTypePrefix << "_spec,\n";
 
     // 5:cppObjDtor
     QString dtorClassName = destructorClassName(metaClass, classContext);
@@ -5887,7 +5938,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
     else
         s << wrapperFlags.join(" | ");
 
-    s << outdent << ");\nauto *pyType = " << pyTypeName << "; // references "
+    s << outdent << ");\nauto *pyType = " << typePtr << "; // references "
         << typePtr << "\n"
         << outdent << "#if PYSIDE6_COMOPT_COMPRESS == 0\n" << indent
         << "InitSignatureStrings(pyType, " << initFunctionName << "_SignatureStrings);\n"
@@ -5898,7 +5949,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
 
     if (usePySideExtensions() && !classContext.forSmartPointer())
         s << "SbkObjectType_SetPropertyStrings(pyType, "
-                    << chopType(pyTypeName) << "_PropertyStrings);\n";
+            << pyTypePrefix << "_PropertyStrings);\n";
     s << globalTypeVarExpr << " = pyType;\n\n";
 
     // Register conversions for the type.
@@ -5944,7 +5995,7 @@ void CppGenerator::writeClassRegister(TextStream &s,
 
     if (!classContext.forSmartPointer() && !classEnums.isEmpty())
         s << "// Pass the ..._EnumFlagInfo to the class.\n"
-            << "SbkObjectType_SetEnumFlagInfo(pyType, " << chopType(pyTypeName)
+            << "SbkObjectType_SetEnumFlagInfo(pyType, " << pyTypePrefix
             << "_EnumFlagInfo);\n\n";
     writeEnumsInitialization(s, classEnums);
 
@@ -5987,8 +6038,8 @@ void CppGenerator::writeStaticFieldInitialization(TextStream &s,
     if (parts.size() < 4) {
         s << "\nPyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(metaClass)
             << "(PyObject *module)\n{\n" << indent
-            << "auto *obType = PyObject_GetAttrString(module, \"" << metaClass->name() << "\");\n"
-            << "auto *type = reinterpret_cast<PyTypeObject *>(obType);\n"
+            << "Shiboken::AutoDecRef obType(PyObject_GetAttrString(module, \"" << metaClass->name() << "\"));\n"
+            << "auto *type = reinterpret_cast<PyTypeObject *>(obType.object());\n"
             << "Shiboken::AutoDecRef dict(PepType_GetDict(type));\n";
     } else {
         s << "\nPyTypeObject *" << getSimpleClassStaticFieldsInitFunctionName(metaClass)
@@ -6364,12 +6415,11 @@ void CppGenerator::writeInitFuncCall(TextStream &callStr,
             : "module"_L1;
         callStr << functionName << '(' << enclosing << ");\n";
     } else if (hasParent) {
-        const QString &enclosingName = enclosingEntry->name();
-        const auto parts = QStringView{enclosingName}.split(u"::", Qt::SkipEmptyParts);
-        const QString namePathPrefix = enclosingEntry->name().replace("::"_L1, "."_L1);
+        const QString &enclosingName = enclosingEntry->targetLangName();
+        const auto parts = QStringView{enclosingName}.split(u".", Qt::SkipEmptyParts);
         callStr << "Shiboken::Module::AddTypeCreationFunction("
             << "module, \"" << parts[0] << "\", "
-            << functionName << ", \"" << namePathPrefix << '.' << pythonName << "\");\n";
+            << functionName << ", \"" << enclosingName << '.' << pythonName << "\");\n";
     } else {
         callStr << "Shiboken::Module::AddTypeCreationFunction("
             << "module, \"" << pythonName << "\", "
@@ -6385,11 +6435,42 @@ static void writeSubModuleHandling(TextStream &s, const QString &moduleName,
         << subModuleOf << "\"));\n"
         << "if (parentModule.isNull())\n" << indent
         << "return nullptr;\n" << outdent
-        << "if (PyModule_AddObject(parentModule.object(), \"" << moduleName
+        << "if (PepModule_Add(parentModule.object(), \"" << moduleName
         << "\", module) < 0)\n"
         << indent << "return nullptr;\n" << outdent << outdent << "}\n";
 }
 
+static QString writeModuleDef(TextStream &s, const QString &moduleName,
+                              const QString &execFunc)
+{
+    QString moduleDef = moduleName + "ModuleDef"_L1;
+    s << "static PyModuleDef_Slot " << moduleName << R"(ModuleSlots[] = {
+    {Py_mod_exec, reinterpret_cast<void *>()" << execFunc << R"()},
+#if !defined(PYPY_VERSION) && ((!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030C0000) || (defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030C0000))
+    {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+#endif
+#ifdef Py_GIL_DISABLED
+    {Py_mod_gil, Py_MOD_GIL_USED},
+#endif
+    {0, nullptr}
+};
+
+static struct PyModuleDef )" << moduleDef << R"( = {
+    /* m_base     */ PyModuleDef_HEAD_INIT,
+    /* m_name     */ ")" << moduleName << R"(",
+    /* m_doc      */ nullptr,
+    /* m_size     */ 0,
+    /* m_methods  */ )" << moduleName << R"(Methods,
+    /* m_slots    */ )" << moduleName << R"(ModuleSlots,
+    /* m_traverse */ nullptr,
+    /* m_clear    */ nullptr,
+    /* m_free     */ nullptr
+};
+
+)";
+    return moduleDef;
+}
+
 bool CppGenerator::finishGeneration()
 {
     //Generate CPython wrapper file
@@ -6489,8 +6570,9 @@ bool CppGenerator::finishGeneration()
 
     // write license comment
     s << licenseComment() << R"(
-#include <sbkpython.h>
+#include <sbkpep.h>
 #include <shiboken.h>
+#include <sbkbindingutils.h>
 #include <algorithm>
 #include <signature.h>
 )";
@@ -6544,8 +6626,6 @@ bool CppGenerator::finishGeneration()
        << "Shiboken::Module::TypeInitStruct *" << cppApiVariableName() << " = nullptr;\n"
        << "// Backwards compatible structure with identical indexing.\n"
        << "PyTypeObject **" << cppApiVariableNameOld() << " = nullptr;\n"
-       << "// Current module's PyObject pointer.\n"
-       << "PyObject *" << pythonModuleObjectName() << " = nullptr;\n"
        << "// Current module's converter array.\n"
        << "SbkConverter **" << convertersVariableName() << " = nullptr;\n\n";
 
@@ -6572,7 +6652,7 @@ bool CppGenerator::finishGeneration()
     s << "// Global functions "
         << "------------------------------------------------------------\n"
         << s_globalFunctionImpl.toString() << '\n'
-        << "static PyMethodDef " << moduleName() << "_methods[] = {\n" << indent
+        << "static PyMethodDef " << moduleName() << "Methods[] = {\n" << indent
         << s_globalFunctionDef.toString()
         << METHOD_DEF_SENTINEL << outdent << "};\n\n"
         << "// Classes initialization functions "
@@ -6632,7 +6712,7 @@ bool CppGenerator::finishGeneration()
         s << '\n';
     }
 
-    QHash<AbstractMetaType, OpaqueContainerData> opaqueContainers;
+    OpaqueContainerTypeHash opaqueContainers;
     const auto &containers = api().instantiatedContainers();
     QSet<AbstractMetaType> valueConverters;
     if (!containers.isEmpty()) {
@@ -6650,100 +6730,95 @@ bool CppGenerator::finishGeneration()
         s << '\n';
     }
 
-    s << "static struct PyModuleDef moduledef = {\n"
-        << "    /* m_base     */ PyModuleDef_HEAD_INIT,\n"
-        << "    /* m_name     */ \"" << moduleName() << "\",\n"
-        << "    /* m_doc      */ nullptr,\n"
-        << "    /* m_size     */ -1,\n"
-        << "    /* m_methods  */ " << moduleName() << "_methods,\n"
-        << "    /* m_reload   */ nullptr,\n"
-        << "    /* m_traverse */ nullptr,\n"
-        << "    /* m_clear    */ nullptr,\n"
-        << "    /* m_free     */ nullptr\n};\n\n";
+    const QString &modName =  moduleName();
 
     // PYSIDE-510: Create a signatures string for the introspection feature.
     writeSignatureStrings(s, signatureStream.toString(), moduleName(), "global functions");
 
     writeInitInheritance(s);
 
-    // Write module init function
-    const QString globalModuleVar = pythonModuleObjectName();
-    s << "extern \"C\" LIBSHIBOKEN_EXPORT PyObject *PyInit_"
-        << moduleName() << "()\n{\n" << indent;
-    // Guard against repeated invocation
-    s << "if (" << globalModuleVar << " != nullptr)\n"
-        << indent << "return " << globalModuleVar << ";\n" << outdent;
+    const QString convInitFunc = "initConverters_"_L1 + modName;
+    writeConverterInitFunc(s, convInitFunc, typeConversions, extendedConverters);
+    const QString containerConvInitFunc = "initContainerConverters_"_L1 + modName;
+    writeContainerConverterInitFunc(s, containerConvInitFunc, opaqueContainers);
+    QString opaqueContainerRegisterFunc;
+    if (!opaqueContainers.isEmpty()) {
+        opaqueContainerRegisterFunc = "registerOpaqueContainers_"_L1 + modName;
+        writeOpaqueContainerConverterRegisterFunc(s, opaqueContainerRegisterFunc,
+                                                  opaqueContainers);
+    }
+    QString enumRegisterFunc;
+    QString qtEnumRegisterMetaTypeFunc;
+    if (!globalEnums.isEmpty()) {
+        enumRegisterFunc = "registerEnums_"_L1 + modName;
+        writeEnumsInitFunc(s, enumRegisterFunc, globalEnums);
+        if (usePySideExtensions()) {
+            qtEnumRegisterMetaTypeFunc = "registerEnumMetaTypes_"_L1 + modName;
+            writeQtEnumRegisterMetaTypeFunction(s, qtEnumRegisterMetaTypeFunc, globalEnums);
+        }
+    }
 
-    // module inject-code target/beginning
-    writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning,
-                         TypeSystem::TargetLangCode);
+    const QString execFunc = "exec_"_L1 + modName;
+    writeModuleExecFunction(s, execFunc, opaqueContainerRegisterFunc, enumRegisterFunc,
+                            s_classPythonDefines.toString(), classesWithStaticFields);
 
-    for (const QString &requiredModule : requiredModules) {
-        s << "{\n" << indent
-             << "Shiboken::AutoDecRef requiredModule(Shiboken::Module::import(\"" << requiredModule << "\"));\n"
-             << "if (requiredModule.isNull())\n" << indent
-             << "return nullptr;\n" << outdent
-             << cppApiVariableName(requiredModule)
-             << " = Shiboken::Module::getTypes(requiredModule);\n"
-             << convertersVariableName(requiredModule)
-             << " = Shiboken::Module::getTypeConverters(requiredModule);\n" << outdent
-             << "}\n\n";
-    }
-
-    int maxTypeIndex = getMaxTypeIndex() + api().instantiatedSmartPointers().size();
-    if (maxTypeIndex) {
-        s << "// Create an array of wrapper types/names for the current module.\n"
-            << "static Shiboken::Module::TypeInitStruct cppApi[] = {\n" << indent;
-
-        // Windows did not like an array of QString.
-        QStringList typeNames;
-        for (int idx = 0; idx < maxTypeIndex; ++idx)
-            typeNames.append("+++ unknown entry #"_L1 + QString::number(idx)
-                             + " in "_L1 + moduleName());
-
-        collectFullTypeNamesArray(typeNames);
-
-        for (const auto &typeName : typeNames)
-            s << "{nullptr, \"" << typeName << "\"},\n";
-
-        s << "{nullptr, nullptr}\n" << outdent << "};\n"
-            << "// The new global structure consisting of (type, name) pairs.\n"
-            << cppApiVariableName() << " = cppApi;\n";
-        if (usePySideExtensions())
-            s << "QT_WARNING_PUSH\nQT_WARNING_DISABLE_DEPRECATED\n";
-        s << "// The backward compatible alias with upper case indexes.\n"
-            << cppApiVariableNameOld() << " = reinterpret_cast<PyTypeObject **>(cppApi);\n";
-        if (usePySideExtensions())
-            s << "QT_WARNING_POP\n";
-        s << '\n';
-    }
+    const QString moduleDef = writeModuleDef(s, modName, execFunc);
 
-    s << "// Create an array of primitive type converters for the current module.\n"
-        << "static SbkConverter *sbkConverters[SBK_" << moduleName()
-        << "_CONVERTERS_IDX_COUNT" << "];\n"
-        << convertersVariableName() << " = sbkConverters;\n\n"
-        << "PyObject *module = Shiboken::Module::create(\""  << moduleName()
-        << "\", &moduledef);\n\n"
-        << "// Make module available from global scope\n"
-        << globalModuleVar << " = module;\n\n";
+    writeModuleInitFunction(s, moduleDef, execFunc, convInitFunc, containerConvInitFunc, qtEnumRegisterMetaTypeFunc);
 
-    const QString subModuleOf = typeDb->defaultTypeSystemType()->subModuleOf();
-    if (!subModuleOf.isEmpty())
-        writeSubModuleHandling(s,  moduleName(), subModuleOf);
+    file.done();
+    return true;
+}
 
-    s << "// Initialize classes in the type system\n"
-        << s_classPythonDefines.toString();
+void CppGenerator::writeConverterInitFunc(TextStream &s,
+                                          const QString &funcName,
+                                          const QList<CustomConversionPtr> &typeConversions,
+                                          const ExtendedConverterData &extendedConverters)
+{
+    s << "static void " << funcName << "()\n{\n" << indent;
 
     if (!typeConversions.isEmpty()) {
-        s << '\n';
+        s << "// Type conversions.\n";
         for (const auto &conversion : typeConversions) {
             writePrimitiveConverterInitialization(s, conversion);
             s << '\n';
         }
     }
 
-    if (!containers.isEmpty()) {
+    if (!extendedConverters.isEmpty()) {
         s << '\n';
+        for (auto it = extendedConverters.cbegin(), end = extendedConverters.cend(); it != end; ++it) {
+            writeExtendedConverterInitialization(s, it.key(), it.value());
+            s << '\n';
+        }
+    }
+
+    const PrimitiveTypeEntryCList &primitiveTypeList = primitiveTypes();
+    if (!primitiveTypeList.isEmpty()) {
+        s << "// Register primitive types converters.\n";
+        for (const auto &pte : primitiveTypeList) {
+            if (!pte->generateCode() || !isCppPrimitive(pte))
+                continue;
+            if (!pte->referencesType())
+                continue;
+            TypeEntryCPtr referencedType = basicReferencedTypeEntry(pte);
+            s << registerConverterName(pte->qualifiedCppName(), converterObject(referencedType),
+                                       registerConverterName::Alias
+                                       | registerConverterName::PartiallyQualifiedAliases) << '\n';
+        }
+    }
+
+    s << outdent << "}\n\n";
+}
+
+void CppGenerator::writeContainerConverterInitFunc(TextStream &s,
+                                                   const QString &funcName,
+                                                   const OpaqueContainerTypeHash &opaqueContainers) const
+{
+    s << "static void " << funcName << "()\n{\n" << indent;
+
+    const auto &containers = api().instantiatedContainers();
+    if (!containers.isEmpty()) {
         for (const AbstractMetaType &container : containers) {
             const QString converterObj = writeContainerConverterInitialization(s, container, api());
             const auto it = opaqueContainers.constFind(container);
@@ -6756,49 +6831,157 @@ bool CppGenerator::finishGeneration()
         }
     }
 
-    if (!opaqueContainers.isEmpty()) {
-        s << "\n// Opaque container type registration\n"
-            << "PyObject *ob_type{};\n";
-        if (usePySideExtensions()) {
-            const bool hasQVariantConversion =
-                std::any_of(opaqueContainers.cbegin(), opaqueContainers.cend(),
-                            [](const OpaqueContainerData &d) { return d.hasQVariantConversion; });
-            if (hasQVariantConversion) {
-                const char qVariantConverterVar[] = "qVariantConverter";
-                s << "auto *" << qVariantConverterVar
-                  << " = Shiboken::Conversions::getConverter(\"QVariant\");\n"
-                  << "Q_ASSERT(" << qVariantConverterVar << " != nullptr);\n";
-            }
+    s << outdent << "}\n\n";
+}
+
+void CppGenerator::writeOpaqueContainerConverterRegisterFunc(TextStream &s, const QString &funcName,
+                                                             const OpaqueContainerTypeHash &opaqueContainers)
+{
+    s << "static void " << funcName << "(PyObject *module)\n{\n" << indent
+        << "PyTypeObject *pyType{};\n";
+    if (usePySideExtensions()) {
+        const bool hasQVariantConversion =
+            std::any_of(opaqueContainers.cbegin(), opaqueContainers.cend(),
+                        [](const OpaqueContainerData &d) { return d.hasQVariantConversion; });
+        if (hasQVariantConversion) {
+            const char qVariantConverterVar[] = "qVariantConverter";
+            s << "auto *" << qVariantConverterVar
+                << " = Shiboken::Conversions::getConverter(\"QVariant\");\n"
+                << "Q_ASSERT(" << qVariantConverterVar << " != nullptr);\n";
         }
-        for (const auto &d : opaqueContainers)
-            s << d.registrationCode;
-        s << '\n';
     }
+    for (const auto &d : opaqueContainers)
+        s << d.registrationCode;
+    s << outdent << "}\n\n";
+}
 
-    if (!extendedConverters.isEmpty()) {
-        s << '\n';
-        for (ExtendedConverterData::const_iterator it = extendedConverters.cbegin(), end = extendedConverters.cend(); it != end; ++it) {
-            writeExtendedConverterInitialization(s, it.key(), it.value());
-            s << '\n';
+void CppGenerator::writeModuleInitFunction(TextStream &s, const QString &moduleDef,
+                                           const QString &execFunc, const QString &convInitFunc,
+                                           const QString &containerConvInitFunc,
+                                           const QString &qtEnumRegisterMetaTypeFunc)
+{
+     s << "extern \"C\" LIBSHIBOKEN_EXPORT PyObject *PyInit_"
+         << moduleName() << "()\n{\n" << indent
+         << "Shiboken::init();\n\n";
+
+     // Static initialization: Create converter/type arrays and retrieve arrays
+     // of the required modules for initializing the converters.
+     const int maxTypeIndex = getMaxTypeIndex() + api().instantiatedSmartPointers().size();
+     if (maxTypeIndex) {
+         s << "// Create an array of wrapper types/names for the current module.\n"
+             << "static Shiboken::Module::TypeInitStruct cppApi[] = {\n" << indent;
+
+         // Windows did not like an array of QString.
+         QStringList typeNames;
+         for (int idx = 0; idx < maxTypeIndex; ++idx)
+             typeNames.append("+++ unknown entry #"_L1 + QString::number(idx)
+                              + " in "_L1 + moduleName());
+
+         collectFullTypeNamesArray(typeNames);
+
+         for (const auto &typeName : typeNames)
+             s << "{nullptr, \"" << typeName << "\"},\n";
+
+         s << "{nullptr, nullptr}\n" << outdent << "};\n"
+             << "// The new global structure consisting of (type, name) pairs.\n"
+             << cppApiVariableName() << " = cppApi;\n";
+         if (usePySideExtensions())
+             s << "QT_WARNING_PUSH\nQT_WARNING_DISABLE_DEPRECATED\n";
+         s << "// The backward compatible alias with upper case indexes.\n"
+             << cppApiVariableNameOld() << " = reinterpret_cast<PyTypeObject **>(cppApi);\n";
+         if (usePySideExtensions())
+             s << "QT_WARNING_POP\n";
+         s << '\n';
+     }
+
+     s << "// Create an array of primitive type converters for the current module.\n"
+         << "static SbkConverter *sbkConverters[SBK_" << moduleName()
+         << "_CONVERTERS_IDX_COUNT" << "];\n"
+         << convertersVariableName() << " = sbkConverters;\n\n";
+
+     const TypeDatabase *typeDb = TypeDatabase::instance();
+     const CodeSnipList snips = typeDb->defaultTypeSystemType()->codeSnips();
+
+     writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning,
+                          TypeSystem::TargetLangCode);
+
+     const QStringList &requiredModules = typeDb->requiredTargetImports();
+     for (const QString &requiredModule : requiredModules) {
+         s << "{\n" << indent
+              << "Shiboken::AutoDecRef requiredModule(Shiboken::Module::import(\"" << requiredModule << "\"));\n"
+              << "if (requiredModule.isNull())\n" << indent
+              << "return nullptr;\n" << outdent
+              << cppApiVariableName(requiredModule)
+              << " = Shiboken::Module::getTypes(requiredModule);\n"
+              << convertersVariableName(requiredModule)
+              << " = Shiboken::Module::getTypeConverters(requiredModule);\n" << outdent
+              << "}\n\n";
+     }
+
+     s << convInitFunc << "();\n" << containerConvInitFunc << "();\n";
+     if (!qtEnumRegisterMetaTypeFunc.isEmpty())
+         s << qtEnumRegisterMetaTypeFunc << "();\n";
+     s << '\n';
+
+     // As of 8/25, Nuitka does not support multi-phase initialization. Fall back
+     s << "PyObject *module = nullptr;\n"
+         << "if (Shiboken::isCompiled()) {\n" << indent
+         << moduleDef << ".m_size = -1;\n"
+         << moduleDef << ".m_slots = nullptr;\n"
+         << "module = Shiboken::Module::createOnly(\""  << moduleName()
+         << "\", &" << moduleDef << ");\n"
+         << "if (module == nullptr)\n" << indent << "return nullptr;\n" << outdent
+         << "#ifdef Py_GIL_DISABLED\n"
+         << "PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);\n"
+         << "#endif\n"
+         << "if (" << execFunc << "(module) != 0)\n" << indent << "return nullptr;\n" << outdent
+         << outdent << "} else {\n" << indent;
+      // Multi-phase initialization (exec() will be called by CPython).
+      s  << "module = PyModuleDef_Init(&" << moduleDef << ");\n" << outdent << "}\n"
+         << "return module;\n" << outdent << "}\n\n";
+}
+
+void CppGenerator::writeQtEnumRegisterMetaTypeFunction(TextStream &s,
+                                                       const QString &name,
+                                                       const AbstractMetaEnumList &globalEnums)
+{
+    s << "static void " << name << "()\n{\n" << indent;
+    for (const AbstractMetaEnum &metaEnum : globalEnums) {
+        if (!metaEnum.isAnonymous()) {
+            ConfigurableScope configScope(s, metaEnum.typeEntry());
+            s << "qRegisterMetaType< " << Generator::getFullTypeName(metaEnum.typeEntry())
+              << " >(\"" << metaEnum.name() << "\");\n";
         }
     }
+    s << outdent << "}\n\n";
+}
 
-    writeEnumsInitialization(s, globalEnums);
+void CppGenerator::writeModuleExecFunction(TextStream &s, const QString &name,
+                                           const QString &opaqueContainerRegisterFunc,
+                                           const QString &enumRegisterFunc,
+                                           const QString &classPythonDefines,
+                                           const AbstractMetaClassCList &classesWithStaticFields)
+{
+    // Code to run in an module instance of a subinterpreter (Py_mod_exec)
+    s << "extern \"C\" {\nstatic int " << name << "(PyObject *module)\n{\n" << indent
+        << "Shiboken::Module::exec(module);\n\n";
 
-    s << "// Register primitive types converters.\n";
-    const PrimitiveTypeEntryCList &primitiveTypeList = primitiveTypes();
-    for (const auto &pte : primitiveTypeList) {
-        if (!pte->generateCode() || !isCppPrimitive(pte))
-            continue;
-        if (!pte->referencesType())
-            continue;
-        TypeEntryCPtr referencedType = basicReferencedTypeEntry(pte);
-        s << registerConverterName(pte->qualifiedCppName(), converterObject(referencedType),
-                                   registerConverterName::Alias
-                                   | registerConverterName::PartiallyQualifiedAliases);
-    }
+    // module inject-code target/beginning
+    const TypeDatabase *typeDb = TypeDatabase::instance();
+    const CodeSnipList snips = typeDb->defaultTypeSystemType()->codeSnips();
+
+    const QString subModuleOf = typeDb->defaultTypeSystemType()->subModuleOf();
+    if (!subModuleOf.isEmpty())
+        writeSubModuleHandling(s,  moduleName(), subModuleOf);
 
+    s << "// Initialize classes in the type system\n" << classPythonDefines << '\n';
+    if (!opaqueContainerRegisterFunc.isEmpty())
+        s << opaqueContainerRegisterFunc << "(module);\n";
+    if (!enumRegisterFunc.isEmpty())
+        s << enumRegisterFunc << "(module);\n";
     s << '\n';
+
+    const int maxTypeIndex = getMaxTypeIndex() + api().instantiatedSmartPointers().size();
     if (maxTypeIndex)
         s << "Shiboken::Module::registerTypes(module, " << cppApiVariableName() << ");\n";
     s << "Shiboken::Module::registerTypeConverters(module, " << convertersVariableName() << ");\n";
@@ -6816,7 +6999,7 @@ bool CppGenerator::finishGeneration()
     s << '\n' << initInheritanceFunction << "();\n"
         << "\nif (" << shibokenErrorsOccurred << ") {\n" << indent
         << "PyErr_Print();\n"
-        << "Py_FatalError(\"can't initialize module " << moduleName() << "\");\n"
+        << "Py_FatalError(\"shiboken: can't initialize module " << moduleName() << "\");\n"
         << outdent << "}\n";
 
     // module inject-code target/end
@@ -6825,29 +7008,17 @@ bool CppGenerator::finishGeneration()
     // module inject-code native/end
     writeModuleCodeSnips(s, snips, TypeSystem::CodeSnipPositionEnd, TypeSystem::NativeCode);
 
-    if (usePySideExtensions()) {
-        for (const AbstractMetaEnum &metaEnum : std::as_const(globalEnums))
-            if (!metaEnum.isAnonymous()) {
-                ConfigurableScope configScope(s, metaEnum.typeEntry());
-                s << "qRegisterMetaType< " << getFullTypeName(metaEnum.typeEntry())
-                  << " >(\"" << metaEnum.name() << "\");\n";
-            }
-
-        // cleanup staticMetaObject attribute
+    if (usePySideExtensions()) // cleanup staticMetaObject attribute
         s << "PySide::registerCleanupFunction(cleanTypesAttributes);\n\n";
-    }
 
     // finish the rest of get_signature() initialization.
     s << outdent << "#if PYSIDE6_COMOPT_COMPRESS == 0\n" << indent
         << "FinishSignatureInitialization(module, " << moduleName() << "_SignatureStrings);\n"
         << outdent << "#else\n" << indent
         << "if (FinishSignatureInitBytes(module, " << moduleName() << "_SignatureBytes, "
-        << moduleName() << "_SignatureByteSize) < 0)\n" << indent << "return {};\n" << outdent
+        << moduleName() << "_SignatureByteSize) < 0)\n" << indent << "return -1;\n" << outdent
         << outdent << "#endif\n" << indent
-        << "\nreturn module;\n" << outdent << "}\n";
-
-    file.done();
-    return true;
+        << "\nreturn 0;\n" << outdent << "}\n} // extern \"C\"\n\n";
 }
 
 static ArgumentOwner getArgumentOwner(const AbstractMetaFunctionCPtr &func, int argIndex)
index 0ae86dd37e79752391a4e5e3c12e52afefe0aa22..2510fa980635c04e649806508250596f6fe754cf 100644 (file)
@@ -114,6 +114,9 @@ private:
     static void writeCustomConverterRegister(TextStream &s,
                                              const CustomConversionPtr &customConversion,
                                              const QString &converterVar);
+    static void writeTemplateCustomConverterRegister(TextStream &s,
+                                                     const AbstractMetaType &type,
+                                                     QString converter = {});
 
     void writeContainerConverterFunctions(TextStream &s,
                                           const AbstractMetaType &containerType) const;
@@ -127,6 +130,7 @@ private:
         QString registrationCode;
         bool hasQVariantConversion = false;
     };
+    using OpaqueContainerTypeHash = QHash<AbstractMetaType, OpaqueContainerData>;
 
     OpaqueContainerData
         writeOpaqueContainerConverterFunctions(TextStream &s,
@@ -331,7 +335,7 @@ private:
 
     /// Writes a C++ to Python conversion function.
     void writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName,
-                                  const QString &targetTypeName = {}) const;
+                                  const QString &targetTypeName = {}, bool withType = false) const;
     void writeCppToPythonFunction(TextStream &s, const CustomConversionPtr &customConversion) const;
     void writeCppToPythonFunction(TextStream &s, const AbstractMetaType &containerType) const;
     /// Main target type name of a container (for naming the functions).
@@ -361,12 +365,13 @@ private:
                                              const TargetToNativeConversion &toNative,
                                              const TypeEntryCPtr &targetType) const;
 
-    /// Writes a pair of Python to C++ conversion and check functions for instantiated container types.
+    /// Writes a pair of Python to C++ conversion and check functions for instantiated
+    /// template (smart pointer/container types).
     void writePythonToCppConversionFunctions(TextStream &s,
-                                             const AbstractMetaType &containerType) const;
+                                             const AbstractMetaType &templateType) const;
 
     void writePythonToCppConversionFunction(TextStream &s,
-                                            const AbstractMetaType &containerType,
+                                            const AbstractMetaType &templateType,
                                             const TargetToNativeConversion &conv) const;
 
     static void writeAddPythonToCppConversion(TextStream &s, const QString &converterVar,
@@ -419,6 +424,8 @@ private:
     void writeClassDefinition(TextStream &s,
                               const AbstractMetaClassCPtr &metaClass,
                               const GeneratorContext &classContext);
+    static void writeClassTypeFunction(TextStream &s,
+                                       const AbstractMetaClassCPtr &metaClass);
     QByteArrayList methodDefinitionParameters(const OverloadData &overloadData) const;
     QList<PyMethodDefEntry> methodDefinitionEntries(const OverloadData &overloadData) const;
 
@@ -474,7 +481,9 @@ private:
     void writeRichCompareFunction(TextStream &s, TextStream &t, const GeneratorContext &context) const;
     void writeSmartPointerRichCompareFunction(TextStream &s, const GeneratorContext &context) const;
 
-    static void writeEnumsInitialization(TextStream &s, AbstractMetaEnumList &enums);
+    static void writeEnumsInitialization(TextStream &s, const AbstractMetaEnumList &enums);
+    static void writeEnumsInitFunc(TextStream &s, const QString &funcName,
+                                   const AbstractMetaEnumList &enums);
     static bool writeEnumInitialization(TextStream &s, const AbstractMetaEnum &metaEnum);
 
     static void writeSignalInitialization(TextStream &s, const AbstractMetaClassCPtr &metaClass);
@@ -501,6 +510,25 @@ private:
                                                      const TypeEntryCPtr &externalType,
                                                      const AbstractMetaClassCList &conversions);
 
+    void writeModuleInitFunction(TextStream &s, const QString &moduleDef,
+                                 const QString &execFunc, const QString &convInitFunc,
+                                 const QString &containerConvInitFunc,
+                                 const QString &qtEnumRegisterMetaTypeFunc);
+    void writeModuleExecFunction(TextStream &s, const QString &name,
+                                 const QString &opaqueContainerRegisterFunc,
+                                 const QString &enumRegisterFunc,
+                                 const QString &classPythonDefines,
+                                 const AbstractMetaClassCList &classesWithStaticFields);
+    static void writeConverterInitFunc(TextStream &s, const QString &funcName,
+                                       const QList<CustomConversionPtr> &typeConversions,
+                                       const ExtendedConverterData &extendedConverters);
+    void writeContainerConverterInitFunc(TextStream &s, const QString &funcName,
+                                         const OpaqueContainerTypeHash &opaqueContainers) const;
+    static void writeOpaqueContainerConverterRegisterFunc(TextStream &s, const QString &funcName,
+                                                          const OpaqueContainerTypeHash &opaqueContainers);
+    static void writeQtEnumRegisterMetaTypeFunction(TextStream &s, const QString &name,
+                                                    const AbstractMetaEnumList &globalEnums);
+
     void writeParentChildManagement(TextStream &s, const AbstractMetaFunctionCPtr &func,
                                     bool usesPyArgs,
                                     bool userHeuristicForReturn) const;
@@ -564,7 +592,6 @@ private:
     { return boolCast(metaClass).has_value(); }
 
     void clearTpFuncs();
-    static QString chopType(QString s);
 
     static QString typeInitStructHelper(const TypeEntryCPtr &te, const QString &varName);
 
index 8ff47315f9e83c3aa427192f05afac35df473c6c..826ad89af13e4274844a68c89b9612940adb72e6 100644 (file)
@@ -285,9 +285,9 @@ CppGenerator::OpaqueContainerData
                             result.pythonToConverterFunctionName);
 
     TextStream registrationStr(&result.registrationCode, TextStream::Language::Cpp);
-    registrationStr << "ob_type = reinterpret_cast<PyObject *>("
-        << typeFName << "());\nPy_XINCREF(ob_type);\nPyModule_AddObject(module, \""
-        << result.name << "\", ob_type);\n";
+    registrationStr << "pyType = " << typeFName << "();\n"
+        << "Py_XINCREF(reinterpret_cast<PyObject *>(pyType));\n"
+        << "PepModule_AddType(module, pyType);\n";
 
     if (!result.hasQVariantConversion)
         return result;
index fec67659c66010181c2084611112e8aec53874c3..c87888c21cfa8b76ddf6c19574b34f7dab55247a 100644 (file)
@@ -182,7 +182,7 @@ void CppGenerator::generateSmartPointerClass(TextStream &s,
     const QString &methodsDefinitions = md.toString();
     const QString &singleMethodDefinitions = smd.toString();
 
-    const QString className = chopType(cpythonTypeName(typeEntry));
+    const QString className = cpythonBaseName(typeEntry);
 
     // Write single method definitions
     s << singleMethodDefinitions;
@@ -210,6 +210,7 @@ void CppGenerator::generateSmartPointerClass(TextStream &s,
     writeTpTraverseFunction(s, metaClass);
     writeTpClearFunction(s, metaClass);
 
+    writeClassTypeFunction(s, metaClass);
     writeClassDefinition(s, metaClass, classContext);
 
     s << '\n';
@@ -231,13 +232,16 @@ void CppGenerator::generateSmartPointerClass(TextStream &s,
 void CppGenerator::writeSmartPointerConverterFunctions(TextStream &s,
                                                        const AbstractMetaType &smartPointerType) const
 {
+    auto smartPointerTypeEntry =
+        std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry());
+
+    if (smartPointerTypeEntry->hasCustomConversion())
+        writePythonToCppConversionFunctions(s, smartPointerType);
+
     const auto baseClasses = findSmartPointeeBaseClasses(api(), smartPointerType);
     if (baseClasses.isEmpty())
         return;
 
-    auto smartPointerTypeEntry =
-        std::static_pointer_cast<const SmartPointerTypeEntry>(smartPointerType.typeEntry());
-
     // TODO: Missing conversion to smart pointer pointer type:
 
     s << "// Register smartpointer conversion for all derived classes\n";
@@ -289,6 +293,8 @@ void CppGenerator::writeSmartPointerConverterInitialization(TextStream &s,
         writeAddPythonToCppConversion(s, targetConverter, toCpp, isConv);
     };
 
+    writeTemplateCustomConverterRegister(s, type);
+
     const auto classes = findSmartPointeeBaseClasses(api(), type);
     if (classes.isEmpty())
         return;
index 011a4a3eaa5ad615d2ccb4bff7c73088bd2bcf75..fb0df17fd149205b46a36352549e775a116be190 100644 (file)
@@ -33,6 +33,7 @@ constexpr auto CPP_ARG0 = QLatin1StringView("cppArg0");
 extern const char *const METHOD_DEF_SENTINEL;
 extern const char *const PYTHON_TO_CPPCONVERSION_STRUCT;
 extern const char *const openTargetExternC;
+extern const char *const openExternC;
 extern const char *const closeExternC;
 extern const char *const richCompareComment;
 
index b6491afeb41a78356e41b36715674ab3963fdfd3..6ffeef9a312f84fc6b942f5a5391fbd8d8e0f48e 100644 (file)
@@ -185,6 +185,7 @@ void HeaderGenerator::writeWrapperClass(TextStream &s,
         for( const auto &includeGroup : includeGroups)
             s << includeGroup;
     }
+    s << "#include <sbkpython.h>\n\n#include <array>\n";
 
     s << "namespace Shiboken { struct AutoDecRef; class GilState; }\n\n";
 
@@ -240,6 +241,10 @@ void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s,
       << " : public " << metaClass->qualifiedCppName()
       << "\n{\npublic:\n" << indent;
 
+    writeClassCodeSnips(s, metaClass->typeEntry()->codeSnips(),
+                        TypeSystem::CodeSnipPositionWrapperDeclaration,
+                        TypeSystem::NativeCode, classContext);
+
     writeProtectedEnums(s, classContext);
     writeSpecialFunctions(s, wrapperName, classContext);
 
@@ -284,9 +289,10 @@ void *qt_metacast(const char *_clname) override;
     }
 
     if (needsMethodCache) {
-        s << "mutable bool m_PyMethodCache[" << maxOverrides << "] = {false";
+        s << "mutable std::array<PyObject *, " << maxOverrides
+            << "> m_PyMethodCache = {nullptr";
         for (int i = 1; i < maxOverrides; ++i)
-            s << ", false";
+            s << ", nullptr";
         s << "};\n";
     }
 
@@ -722,17 +728,25 @@ HeaderGenerator::IndexValues HeaderGenerator::collectConverterIndexes() const
 }
 
 // PYSIDE-2404: Write the enums in unchanged case for reuse in type imports.
-//              For conpatibility, we create them in uppercase, too and with
+//              For compatibility, we create them in uppercase, too and with
 //              doubled index for emulating the former type-only case.
 //
 // FIXME: Remove in PySide 7. (See the note in `parser.py`)
-//
-static IndexValue typeIndexUpper(struct IndexValue const &ti)
+
+static IndexValue indexUpper(IndexValue ti) // converter indexes (old macro compatibility)
 {
     QString modi = ti.name.toUpper();
     if (modi == ti.name)
-        modi = u"// "_s + modi;
-    return {modi, ti.value * 2, ti.comment};
+        modi.prepend("// "_L1);
+    ti.name = modi;
+    return ti;
+}
+
+static IndexValue typeIndexUpper(const IndexValue &ti) // type indexes (PYSIDE-2404)
+{
+    IndexValue result = indexUpper(ti);
+    result.value *= 2;
+    return result;
 }
 
 bool HeaderGenerator::finishGeneration()
@@ -782,7 +796,7 @@ bool HeaderGenerator::finishGeneration()
     const auto converterIndexes = collectConverterIndexes();
     macrosStream << "// Converter indices\nenum [[deprecated]] : int {\n";
     for (const auto &ci : converterIndexes)
-        macrosStream << typeIndexUpper(ci);
+        macrosStream << indexUpper(ci);
     macrosStream << "};\n\n";
 
     macrosStream << "// Converter indices\nenum : int {\n";
@@ -800,7 +814,7 @@ bool HeaderGenerator::finishGeneration()
     TextStream privateTypeFunctions(&privateParameters.typeFunctions, TextStream::Language::Cpp);
 
     for (const AbstractMetaEnum &cppEnum : api().globalEnums()) {
-        if (!cppEnum.isAnonymous()) {
+        if (!cppEnum.isAnonymous() && cppEnum.typeEntry()->aliasMode() != EnumTypeEntry::AliasSource) {
             const auto te = cppEnum.typeEntry();
             if (te->hasConfigCondition())
                 parameters.conditionalIncludes[te->configCondition()].append(te->include());
@@ -832,8 +846,10 @@ bool HeaderGenerator::finishGeneration()
 
         ConfigurableScope configScope(typeFunctionsStr, classType);
         for (const AbstractMetaEnum &cppEnum : metaClass->enums()) {
-            if (cppEnum.isAnonymous() || cppEnum.isPrivate())
+            if (cppEnum.isAnonymous() || cppEnum.isPrivate()
+                || cppEnum.typeEntry()->aliasMode() == EnumTypeEntry::AliasSource) {
                 continue;
+            }
             if (const auto inc = cppEnum.typeEntry()->include(); inc != classInclude)
                 par.includes.insert(inc);
             writeProtectedEnumSurrogate(protEnumsSurrogates, cppEnum);
index def95e3fb3295fc0553d79718392cfdf37e5b60d..c4f0d4ca4f223db238003af9718d7f3c7d3e49ba 100644 (file)
@@ -88,6 +88,7 @@ const char *const openTargetExternC =  R"(
 
 extern "C" {
 )";
+const char *const openExternC = "extern \"C\" {\n";
 const char *const closeExternC =  "} // extern \"C\"\n\n";
 const char *const richCompareComment =
     "// PYSIDE-74: By default, we redirect to object's tp_richcompare (which is `==`, `!=`).\n";
@@ -633,13 +634,6 @@ bool ShibokenGenerator::shouldRejectNullPointerArgument(const AbstractMetaFuncti
     return false;
 }
 
-QString ShibokenGenerator::cpythonBaseName(const AbstractMetaType &type)
-{
-    if (type.isCString())
-        return u"PyString"_s;
-    return cpythonBaseName(type.typeEntry());
-}
-
 QString ShibokenGenerator::cpythonBaseName(const AbstractMetaClassCPtr &metaClass)
 {
     return cpythonBaseName(metaClass->typeEntry());
@@ -663,25 +657,10 @@ QString ShibokenGenerator::containerCpythonBaseName(const ContainerTypeEntryCPtr
     return cPySequenceT;
 }
 
-QString ShibokenGenerator::cpythonBaseName(const TypeEntryCPtr &type)
-{
-    QString baseName;
-    if (type->isWrapperType() || type->isNamespace()) { // && type->referenceType() == NoReference) {
-        baseName = u"Sbk_"_s + type->name();
-    } else if (type->isPrimitive()) {
-        const auto ptype = basicReferencedTypeEntry(type);
-        baseName = ptype->hasTargetLangApiType()
-                   ? ptype->targetLangApiName() : pythonPrimitiveTypeName(ptype->name());
-    } else if (type->isEnum()) {
-        baseName = cpythonEnumName(std::static_pointer_cast<const EnumTypeEntry>(type));
-    } else if (type->isFlags()) {
-        baseName = cpythonFlagsName(std::static_pointer_cast<const FlagsTypeEntry>(type));
-    } else if (type->isContainer()) {
-        const auto ctype = std::static_pointer_cast<const ContainerTypeEntry>(type);
-        baseName = containerCpythonBaseName(ctype);
-    } else {
-        baseName = cPyObjectT;
-    }
+QString ShibokenGenerator::cpythonBaseName(const ComplexTypeEntryCPtr &type)
+{
+    Q_ASSERT(type->isWrapperType() || type->isNamespace());
+    QString baseName = u"Sbk_"_s + type->name();
     return baseName.replace(u"::"_s, u"_"_s);
 }
 
@@ -690,7 +669,7 @@ QString ShibokenGenerator::cpythonTypeName(const AbstractMetaClassCPtr &metaClas
     return cpythonTypeName(metaClass->typeEntry());
 }
 
-QString ShibokenGenerator::cpythonTypeName(const TypeEntryCPtr &type)
+QString ShibokenGenerator::cpythonTypeName(const ComplexTypeEntryCPtr &type)
 {
     return cpythonBaseName(type) + u"_TypeF()"_s;
 }
@@ -1021,8 +1000,9 @@ QString ShibokenGenerator::cpythonIsConvertibleFunction(const TypeEntryCPtr &typ
         result += u"("_s + cpythonTypeNameExt(type) + u", "_s;
         return result;
     }
-    return QString::fromLatin1("Shiboken::Conversions::isPythonToCppConvertible(%1, ")
-              .arg(converterObject(type));
+
+    return "Shiboken::Conversions::isPythonToCppConvertible("_L1
+           + converterObject(type) + ", "_L1;
 }
 
 QString ShibokenGenerator::cpythonIsConvertibleFunction(const AbstractMetaType &metaType)
@@ -1386,6 +1366,12 @@ void ShibokenGenerator::processClassCodeSnip(QString &code, const GeneratorConte
     processCodeSnip(code, context.effectiveClassName());
 }
 
+void ShibokenGenerator::processTypeCheckCodeSnip(QString &code, const QString &context) const
+{
+    code.replace("%in"_L1, "pyIn"_L1);
+    processCodeSnip(code, context);
+}
+
 void ShibokenGenerator::processCodeSnip(QString &code) const
 {
     // replace "toPython" converters
@@ -2303,7 +2289,7 @@ static AbstractMetaFunctionCList filterFunctions(const OverloadRemovalRules &rem
         if (const auto index = types.indexOf(rule.type); index != -1) {
             for (const auto &redundantType : rule.redundantTypes) {
                 if (const auto index2 = types.indexOf(redundantType); index2 != -1) {
-                    auto redundant = overloads.at(index2);
+                    const auto &redundant = overloads.at(index2);
                     if (!result.contains(redundant)) { // nested long->int->short rule?
                         ReportHandler::addGeneralMessage(msgRemoveRedundantOverload(redundant, rule.type));
                         result.append(redundant);
@@ -2751,10 +2737,7 @@ QString ShibokenGenerator::pythonModuleObjectName(const QString &moduleName)
 
 QString ShibokenGenerator::convertersVariableName(const QString &moduleName)
 {
-    QString result = cppApiVariableNameOld(moduleName);
-    result.chop(1);
-    result.append(u"Converters"_s);
-    return result;
+    return "Sbk"_L1 + moduleCppPrefix(moduleName) + "TypeConverters"_L1;
 }
 
 static QString processInstantiationsVariableName(const AbstractMetaType &type)
index a019e02bedf76eb977ecbdb44c784aa33ce83f65..67722b9748be9c52b39265c473217613aeb073f0 100644 (file)
@@ -99,6 +99,10 @@ public:
     static QString minimalConstructorExpression(const ApiExtractorResult &api,
                                                 const TypeEntryCPtr &type);
 
+    /// Return the name of the _TypeF() function generated to get the PyTypeObject
+    static QString cpythonTypeName(const AbstractMetaClassCPtr &metaClass);
+    static QString cpythonTypeName(const ComplexTypeEntryCPtr &type);
+
 protected:
     bool doSetup() override;
 
@@ -158,6 +162,8 @@ protected:
     void processCodeSnip(QString &code) const;
     void processCodeSnip(QString &code, const QString &context) const;
     void processClassCodeSnip(QString &code, const GeneratorContext &context) const;
+    /// Replaces variables in a custom conversion type check snippet
+    void processTypeCheckCodeSnip(QString &code, const QString &context) const;
 
     /**
      *   Verifies if any of the function's code injections makes a call
@@ -259,12 +265,10 @@ protected:
     static QString converterObject(const AbstractMetaType &type) ;
     static QString converterObject(const TypeEntryCPtr &type);
 
+    /// Return a name prefixed by Sbk_ which can be used for naming variables in the code
     static QString cpythonBaseName(const AbstractMetaClassCPtr &metaClass);
-    static QString cpythonBaseName(const TypeEntryCPtr &type);
+    static QString cpythonBaseName(const ComplexTypeEntryCPtr &type);
     static QString containerCpythonBaseName(const ContainerTypeEntryCPtr &ctype);
-    static QString cpythonBaseName(const AbstractMetaType &type);
-    static QString cpythonTypeName(const AbstractMetaClassCPtr &metaClass);
-    static QString cpythonTypeName(const TypeEntryCPtr &type);
     static QString cpythonTypeNameExtSet(const TypeEntryCPtr &type);
     static QString cpythonTypeNameExtSet(const AbstractMetaType &type);
     static QString cpythonTypeNameExt(const TypeEntryCPtr &type);
index 9bf165194c57e4b12e03ddc3266947f246554783..85f94331c1422cd4bde1a8e2b1fe3570e5bb0972 100644 (file)
@@ -75,9 +75,11 @@ sbkcppstring.cpp sbkcppstring.h sbkcpptonumpy.h
 sbkenum.cpp sbkenum.h
 sbkerrors.cpp sbkerrors.h
 sbkfeature_base.cpp sbkfeature_base.h
-sbkmodule.cpp sbkmodule.h
+sbkmodule.cpp sbkmodule.h sbkmodule_p.h
 sbknumpy.cpp sbknumpycheck.h
 sbknumpyview.h
+sbkpep.h
+sbkpepbuffer.h
 sbkpython.h
 sbksmartpointer.cpp sbksmartpointer.h
 sbkstaticstrings.cpp sbkstaticstrings.h sbkstaticstrings_p.h
@@ -109,7 +111,7 @@ add_library(Shiboken6::libshiboken ALIAS libshiboken)
 target_include_directories(libshiboken PUBLIC
   $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
   $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
-  $<INSTALL_INTERFACE:include/shiboken6>
+  $<INSTALL_INTERFACE:shiboken6/include>
 )
 
 if (NOT "${NUMPY_INCLUDE_DIR}" STREQUAL "")
@@ -170,6 +172,7 @@ install(FILES
         sbkerrors.h
         sbkfeature_base.h
         sbkmodule.h
+        sbkmodule_p.h
         sbknumpycheck.h
         sbknumpyview.h
         sbkstring.h
@@ -181,6 +184,8 @@ install(FILES
         shibokenmacros.h
         threadstatesaver.h
         shibokenbuffer.h
+        sbkpep.h
+        sbkpepbuffer.h
         sbkpython.h
         sbkwindows.h
         pep384impl.h
@@ -192,10 +197,27 @@ install(FILES
         signature.h
         signature_p.h
 
-        DESTINATION include/shiboken6${shiboken6_SUFFIX})
+        DESTINATION shiboken6${shiboken6_SUFFIX}/include)
 install(TARGETS libshiboken EXPORT Shiboken6Targets
                             LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
                             ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
                             RUNTIME DESTINATION bin)
 install(EXPORT Shiboken6Targets NAMESPACE Shiboken6::
         DESTINATION ${LIB_INSTALL_DIR}/cmake/Shiboken6)
+
+# wheel specific installation
+if(NOT is_pyside6_superproject_build)
+
+    set_target_properties(libshiboken PROPERTIES
+        VERSION ${libshiboken_SOVERSION})
+
+    install(TARGETS libshiboken EXPORT Shiboken6WheelTargets
+            LIBRARY DESTINATION "shiboken6"
+            ARCHIVE DESTINATION "shiboken6"
+            RUNTIME DESTINATION "shiboken6")
+
+    install(EXPORT Shiboken6WheelTargets
+            NAMESPACE Shiboken6::
+            DESTINATION "${LIB_INSTALL_DIR}/wheels/cmake/Shiboken6"
+            FILE Shiboken6Targets.cmake)
+endif()
index 4bae93b079d18f5a20e4deb8cbdcf900651a692d..eb9d47c7f17a06f63005eb0f1f38ef31db0049de 100644 (file)
@@ -21,6 +21,7 @@
 #include "voidptr.h"
 
 #include <algorithm>
+#include <cctype>
 #include <cstddef>
 #include <cstring>
 #include <iostream>
@@ -92,7 +93,7 @@ DestructorEntries getDestructorEntries(SbkObject *o)
 {
     DestructorEntries result;
     void **cptrs = o->d->cptr;
-    walkThroughBases(Py_TYPE(o), [&result, cptrs](PyTypeObject *node) {
+    walkThroughBases(Shiboken::pyType(o), [&result, cptrs](PyTypeObject *node) {
         auto *sotp = PepType_SOTP(node);
         auto index = result.size();
         result.push_back(DestructorEntry{sotp->cpp_dtor,
@@ -415,12 +416,8 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete)
         }
     }
 
-    PyObject *error_type{};
-    PyObject *error_value{};
-    PyObject *error_traceback{};
-
     /* Save the current exception, if any. */
-    PyErr_Fetch(&error_type, &error_value, &error_traceback);
+    Shiboken::Errors::Stash errorStash;
 
     if (canDelete) {
         if (sotp->is_multicpp) {
@@ -441,7 +438,7 @@ static void SbkDeallocWrapperCommon(PyObject *pyObj, bool canDelete)
     }
 
     /* Restore the saved exception. */
-    PyErr_Restore(error_type, error_value, error_traceback);
+    errorStash.restore();
 
     if (needTypeDecref)
         Py_DECREF(pyType);
@@ -454,7 +451,7 @@ static inline PyObject *_Sbk_NewVarObject(PyTypeObject *type)
 {
     // PYSIDE-1970: Support __slots__, implemented by PyVarObject
     auto const baseSize = sizeof(SbkObject);
-    auto varCount = Py_SIZE(type);
+    auto varCount = Py_SIZE(reinterpret_cast<PyObject *>(type));
     auto *self = PyObject_GC_NewVar(PyObject, type, varCount);
     if (varCount)
         std::memset(reinterpret_cast<char *>(self) + baseSize, 0, varCount * sizeof(void *));
@@ -539,9 +536,10 @@ PyObject *MakeQAppWrapper(PyTypeObject *type)
 
     // monitoring the last application state
     PyObject *qApp_curr = type != nullptr ? _Sbk_NewVarObject(type) : Py_None;
-    static PyObject *builtins = PyEval_GetBuiltins();
-    if (PyDict_SetItem(builtins, Shiboken::PyName::qApp(), qApp_curr) < 0)
+    Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+    if (PyDict_SetItem(builtins.object(), Shiboken::PyName::qApp(), qApp_curr) < 0)
         return nullptr;
+    builtins.reset(nullptr);
     qApp_last = qApp_curr;
     // Note: This Py_INCREF would normally be wrong because the qApp
     // object already has a reference from PyObject_GC_New. But this is
@@ -731,7 +729,8 @@ PyObject *FallbackRichCompare(PyObject *self, PyObject *other, int op)
 bool SbkObjectType_Check(PyTypeObject *type)
 {
     static auto *meta = SbkObjectType_TypeF();
-    return Py_TYPE(type) == meta || PyType_IsSubtype(Py_TYPE(type), meta);
+    auto *obType = reinterpret_cast<PyObject *>(type);
+    return Py_TYPE(obType) == meta || PyType_IsSubtype(Py_TYPE(obType), meta);
 }
 
 // Global functions from folding.
@@ -763,28 +762,89 @@ PyObject *Sbk_ReturnFromPython_Self(PyObject *self)
     return self;
 }
 
-// The virtual function call
-PyObject *Sbk_GetPyOverride(const void *voidThis, Shiboken::GilState &gil, const char *funcName,
-                            bool *resultCache, PyObject **nameCache)
-{
-    PyObject *pyOverride{};
-    if (!*resultCache) {
-        gil.acquire();
-        pyOverride = Shiboken::BindingManager::instance().getOverride(voidThis, nameCache, funcName);
-        if (pyOverride == nullptr) {
-            *resultCache = true;
-            gil.release();
-        } else if (Shiboken::Errors::occurred() != nullptr) {
-            // Give up.
-            Py_XDECREF(pyOverride);
-            pyOverride = nullptr;
-        }
+} //extern "C"
+
+// Determine name of a Python override of a virtual method according to features
+// and populate name cache.
+static PyObject *overrideMethodName(PyObject *pySelf, const char *methodName,
+                                    PyObject **nameCache)
+{
+    // PYSIDE-1626: Touch the type to initiate switching early.
+    auto *obType = Py_TYPE(pySelf);
+    SbkObjectType_UpdateFeature(obType);
+
+    const int flag = currentSelectId(obType);
+    const int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0;
+    const bool is_snake = flag & 0x01;
+    PyObject *pyMethodName = nameCache[is_snake];  // borrowed
+    if (pyMethodName == nullptr) {
+        if (propFlag)
+            methodName += 2; // skip the propFlag and ':'
+        pyMethodName = Shiboken::String::getSnakeCaseName(methodName, is_snake);
+        nameCache[is_snake] = pyMethodName;
     }
-    return pyOverride;
+    return pyMethodName;
 }
 
-} //extern "C"
+// The virtual function call
+PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject,
+                            Shiboken::GilState &gil, const char *funcName,
+                            PyObject *&resultCache, PyObject **nameCache)
+{
+    if (Py_IsInitialized() == 0 || resultCache == Py_None)
+        return nullptr; // Bail out, execute C++ call (wrappers may outlive Python).
+
+    auto &bindingManager = Shiboken::BindingManager::instance();
+    SbkObject *wrapper = bindingManager.retrieveWrapper(voidThis, typeObject);
+    // The refcount can be 0 if the object is dieing and someone called
+    // a virtual method from the destructor
+    if (wrapper == nullptr)
+        return nullptr;
+    auto *pySelf = reinterpret_cast<PyObject *>(wrapper);
+    if (Py_REFCNT(pySelf) == 0)
+        return nullptr;
+
+    gil.acquire();
+    if (resultCache == Py_None) { // PYSIDE 3246, some other thread may have determined the override
+        gil.release();
+        return nullptr;
+    }
+
+    if (resultCache != nullptr) // recreate the callable from function/self
+        return PepExt_Type_CallDescrGet(resultCache, pySelf, nullptr);
+
+    PyObject *pyMethodName = overrideMethodName(pySelf, funcName, nameCache);
+    auto *wrapper_dict = SbkObject_GetDict_NoRef(pySelf);
+
+    // Note: This special case was implemented for duck-punching, which happens
+    // in the instance dict. It does not work with properties.
+    // This is not cached to avoid leaking. FIXME PYSIDE 7: Remove (PYSIDE-2916)?
+    if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) {
+        Py_INCREF(method);
+        return method;
+    }
+
+    auto *pyOverride = Shiboken::BindingManager::getOverride(wrapper, pyMethodName);
+    if (pyOverride == nullptr) {
+        resultCache = Py_None;
+        Py_INCREF(resultCache);
+        gil.release();
+        return nullptr; // No override, execute C++ call
+    }
+
+    if (Shiboken::Errors::occurred() != nullptr) {
+        // Give up.
+        Py_XDECREF(pyOverride);
+        resultCache = Py_None;
+        Py_INCREF(resultCache);
+        gil.release();
+        return nullptr; // // Give up.
+    }
 
+    resultCache = pyOverride;
+    // recreate the callable from function/self
+    return PepExt_Type_CallDescrGet(resultCache, pySelf, nullptr);
+}
 
 namespace
 {
@@ -815,7 +875,7 @@ void _initMainThreadId(); // helper.cpp
 static std::string msgFailedToInitializeType(const char *description)
 {
     std::ostringstream stream;
-    stream << "[libshiboken] Failed to initialize " << description;
+    stream << "libshiboken: Failed to initialize " << description;
     if (auto *error = PepErr_GetRaisedException()) {
         if (auto *str = PyObject_Str(error))
             stream << ": " << Shiboken::String::toCString(str);
@@ -830,7 +890,7 @@ namespace Conversions { void init(); }
 void init()
 {
     static bool shibokenAlreadInitialised = false;
-    if (shibokenAlreadInitialised)
+    if (shibokenAlreadInitialised) // Leave guard in place until fully ported to multi phase init
         return;
 
     _initMainThreadId();
@@ -857,17 +917,18 @@ void init()
 // PYSIDE-1735: Initialize the whole Shiboken startup.
 void initShibokenSupport(PyObject *module)
 {
-    Py_INCREF(SbkObject_TypeF());
-    PyModule_AddObject(module, "Object", reinterpret_cast<PyObject *>(SbkObject_TypeF()));
+    auto *type = SbkObject_TypeF();
+    auto *obType = reinterpret_cast<PyObject *>(type);
+    Py_INCREF(obType);
+    PepModule_AddType(module, type);
 
     // PYSIDE-1735: When the initialization was moved into Shiboken import, this
     //              Py_INCREF became necessary. No idea why.
     Py_INCREF(module);
     init_shibokensupport_module();
 
-    auto *type = SbkObject_TypeF();
     if (InitSignatureStrings(type, SbkObject_SignatureStrings) < 0)
-        Py_FatalError("Error in initShibokenSupport");
+        Py_FatalError("libshiboken: Error in initShibokenSupport");
 }
 
 // setErrorAboutWrongArguments now gets overload info from the signature module.
@@ -1088,7 +1149,7 @@ introduceWrapperType(PyObject *enclosingObject,
 
     // PyModule_AddObject steals type's reference.
     Py_INCREF(ob_type);
-    if (PyModule_AddObject(enclosingObject, typeName, ob_type) != 0) {
+    if (PepModule_AddType(enclosingObject, type) != 0) {
         std::cerr << "Warning: " << __FUNCTION__ << " returns nullptr for "
             << typeName << '/' << originalName << " due to PyModule_AddObject(enclosingObject="
             << enclosingObject << ", ob_type=" << ob_type << ") failing\n";
@@ -1239,8 +1300,7 @@ void callCppDestructors(SbkObject *pyObj)
         DestroyQApplication();
         return;
     }
-    PyTypeObject *type = Py_TYPE(pyObj);
-    auto *sotp = PepType_SOTP(type);
+    auto *sotp = PepType_SOTP(Shiboken::pyType(pyObj));
     if (sotp->is_multicpp) {
         callDestructor(getDestructorEntries(pyObj));
     } else {
@@ -1297,7 +1357,8 @@ void getOwnership(PyObject *pyObj)
 void releaseOwnership(SbkObject *self)
 {
     // skip if the ownership have already moved to c++
-    auto *selfType = Py_TYPE(self);
+    auto *ob  = reinterpret_cast<PyObject *>(self);
+    auto *selfType = Py_TYPE(ob);
     if (!self->d->hasOwnership || Shiboken::Conversions::pythonTypeIsValueType(PepType_SOTP(selfType)->converter))
         return;
 
@@ -1306,7 +1367,7 @@ void releaseOwnership(SbkObject *self)
 
     // If We have control over object life
     if (self->d->containsCppWrapper)
-        Py_INCREF(reinterpret_cast<PyObject *>(self)); // keep the python object alive until the wrapper destructor call
+        Py_INCREF(ob); // keep the python object alive until the wrapper destructor call
     else
         invalidate(self); // If I do not know when this object will die We need to invalidate this to avoid use after
 }
@@ -1399,7 +1460,7 @@ void makeValid(SbkObject *self)
 
 void *cppPointer(SbkObject *pyObj, PyTypeObject *desiredType)
 {
-    PyTypeObject *pyType = Py_TYPE(pyObj);
+    PyTypeObject *pyType = Shiboken::pyType(pyObj);
     auto *sotp = PepType_SOTP(pyType);
     int idx = 0;
     if (sotp->is_multicpp)
@@ -1411,7 +1472,7 @@ void *cppPointer(SbkObject *pyObj, PyTypeObject *desiredType)
 
 std::vector<void *> cppPointers(SbkObject *pyObj)
 {
-    int n = getNumberOfCppBaseClasses(Py_TYPE(pyObj));
+    int n = getNumberOfCppBaseClasses(Shiboken::pyType(pyObj));
     std::vector<void *> ptrs(n);
     for (int i = 0; i < n; ++i)
         ptrs[i] = pyObj->d->cptr[i];
@@ -1421,7 +1482,7 @@ std::vector<void *> cppPointers(SbkObject *pyObj)
 
 bool setCppPointer(SbkObject *sbkObj, PyTypeObject *desiredType, void *cptr)
 {
-    PyTypeObject *type = Py_TYPE(sbkObj);
+    PyTypeObject *type = Shiboken::pyType(sbkObj);
     int idx = 0;
     if (PepType_SOTP(type)->is_multicpp)
         idx = getTypeIndexOnHierarchy(type, desiredType);
@@ -1439,11 +1500,12 @@ bool setCppPointer(SbkObject *sbkObj, PyTypeObject *desiredType, void *cptr)
 
 bool isValid(PyObject *pyObj)
 {
-    if (!pyObj || pyObj == Py_None
-        || PyType_Check(pyObj) != 0
-        || Py_TYPE(Py_TYPE(pyObj)) != SbkObjectType_TypeF()) {
+    if (pyObj == nullptr || pyObj == Py_None || PyType_Check(pyObj) != 0)
+        return true;
+
+    PyTypeObject *type = Py_TYPE(pyObj);
+    if (Py_TYPE(reinterpret_cast<PyObject *>(type)) != SbkObjectType_TypeF())
         return true;
-    }
 
     auto *priv = reinterpret_cast<SbkObject *>(pyObj)->d;
 
@@ -1468,17 +1530,18 @@ bool isValid(SbkObject *pyObj, bool throwPyError)
         return false;
 
     SbkObjectPrivate *priv = pyObj->d;
-    if (!priv->cppObjectCreated && isUserType(reinterpret_cast<PyObject *>(pyObj))) {
+    auto *ob = reinterpret_cast<PyObject *>(pyObj);
+    if (!priv->cppObjectCreated && isUserType(ob)) {
         if (throwPyError)
             PyErr_Format(PyExc_RuntimeError, "Base constructor of the object (%s) not called.",
-                         Py_TYPE(pyObj)->tp_name);
+                         Py_TYPE(ob)->tp_name);
         return false;
     }
 
     if (!priv->validCppObject) {
         if (throwPyError)
             PyErr_Format(PyExc_RuntimeError, "Internal C++ object (%s) already deleted.",
-                         (Py_TYPE(pyObj))->tp_name);
+                         (Py_TYPE(ob))->tp_name);
         return false;
     }
 
@@ -1498,7 +1561,7 @@ SbkObject *findColocatedChild(SbkObject *wrapper,
                               const PyTypeObject *instanceType)
 {
     // Degenerate case, wrapper is the correct wrapper.
-    if (reinterpret_cast<const void *>(Py_TYPE(wrapper)) == reinterpret_cast<const void *>(instanceType))
+    if (reinterpret_cast<const void *>(Shiboken::pyType(wrapper)) == reinterpret_cast<const void *>(instanceType))
         return wrapper;
 
     if (!(wrapper->d && wrapper->d->cptr))
@@ -1514,7 +1577,8 @@ SbkObject *findColocatedChild(SbkObject *wrapper,
         if (!(child->d && child->d->cptr))
             continue;
         if (child->d->cptr[0] == wrapper->d->cptr[0]) {
-            return reinterpret_cast<const void *>(Py_TYPE(child)) == reinterpret_cast<const void *>(instanceType)
+            auto *childType = Shiboken::pyType(child);
+            return reinterpret_cast<const void *>(childType) == reinterpret_cast<const void *>(instanceType)
                 ? child : findColocatedChild(child, instanceType);
         }
     }
@@ -1582,42 +1646,16 @@ PyObject *newObjectWithHeuristics(PyTypeObject *instanceType,
 
 PyObject *newObjectForType(PyTypeObject *instanceType, void *cptr, bool hasOwnership)
 {
-    bool shouldCreate = true;
-    bool shouldRegister = true;
-    SbkObject *self = nullptr;
-
     auto &bindingManager = BindingManager::instance();
-    // Some logic to ensure that colocated child field does not overwrite the parent
-    if (SbkObject *existingWrapper = bindingManager.retrieveWrapper(cptr)) {
-        self = findColocatedChild(existingWrapper, instanceType);
-        if (self) {
-            // Wrapper already registered for cptr.
-            // This should not ideally happen, binding code should know when a wrapper
-            // already exists and retrieve it instead.
-            shouldRegister = shouldCreate = false;
-        } else if (hasOwnership &&
-                  (!(Shiboken::Object::hasCppWrapper(existingWrapper) ||
-                     Shiboken::Object::hasOwnership(existingWrapper)))) {
-            // Old wrapper is likely junk, since we have ownership and it doesn't.
-            bindingManager.releaseWrapper(existingWrapper);
-        } else {
-            // Old wrapper may be junk caused by some bug in identifying object deletion
-            // but it may not be junk when a colocated field is accessed for an
-            // object which was not created by python (returned from c++ factory function).
-            // Hence we cannot release the wrapper confidently so we do not register.
-            shouldRegister = false;
-        }
-    }
-
-    if (shouldCreate) {
+    SbkObject *self = bindingManager.retrieveWrapper(cptr, instanceType);
+    if (self != nullptr) {
+        Py_IncRef(reinterpret_cast<PyObject *>(self));
+    } else {
         self = reinterpret_cast<SbkObject *>(SbkObject_tp_new(instanceType, nullptr, nullptr));
         self->d->cptr[0] = cptr;
         self->d->hasOwnership = hasOwnership;
         self->d->validCppObject = 1;
-        if (shouldRegister)
-            bindingManager.registerWrapper(self, cptr);
-    } else {
-        Py_IncRef(reinterpret_cast<PyObject *>(self));
+        bindingManager.registerWrapper(self, cptr);
     }
     return reinterpret_cast<PyObject *>(self);
 }
@@ -1735,8 +1773,14 @@ void setParent(PyObject *parent, PyObject *child)
             parent_->d->parentInfo = new ParentInfo;
 
         // do not re-add a child
-        if (child_->d->parentInfo && (child_->d->parentInfo->parent == parent_))
+        if (child_->d->parentInfo && (child_->d->parentInfo->parent == parent_)) {
+            if (Shiboken::pyVerbose()) {
+                std::cerr << "Warning: Attempt to re-add child "
+                          << child << '/' << Py_TYPE(child)->tp_name << " to parent "
+                          << parent << '/' << Py_TYPE(parent)->tp_name << '\n';
+            }
             return;
+        }
     }
 
     ParentInfo *pInfo = child_->d->parentInfo;
@@ -1759,7 +1803,7 @@ void setParent(PyObject *parent, PyObject *child)
         parent_->d->parentInfo->children.insert(child_);
 
         // Add Parent ref
-        Py_INCREF(child_);
+        Py_INCREF(child);
 
         // Remove ownership
         child_->d->hasOwnership = false;
@@ -1795,7 +1839,7 @@ void deallocData(SbkObject *self, bool cleanup)
 
 void setTypeUserData(SbkObject *wrapper, void *userData, DeleteUserDataFunc d_func)
 {
-    auto *type = Py_TYPE(wrapper);
+    auto *type = Shiboken::pyType(wrapper);
     auto *sotp = PepType_SOTP(type);
     if (sotp->user_data)
         sotp->d_func(sotp->user_data);
@@ -1806,7 +1850,7 @@ void setTypeUserData(SbkObject *wrapper, void *userData, DeleteUserDataFunc d_fu
 
 void *getTypeUserData(SbkObject *wrapper)
 {
-    auto *type = Py_TYPE(wrapper);
+    auto *type = Shiboken::pyType(wrapper);
     return PepType_SOTP(type)->user_data;
 }
 
@@ -1879,14 +1923,15 @@ void clearReferences(SbkObject *self)
 
 static std::vector<PyTypeObject *> getBases(SbkObject *self)
 {
-    return ObjectType::isUserType(Py_TYPE(self))
-        ? getCppBaseClasses(Py_TYPE(self))
-        : std::vector<PyTypeObject *>(1, Py_TYPE(self));
+    auto *type = Shiboken::pyType(self);
+    return ObjectType::isUserType(type)
+        ? getCppBaseClasses(type)
+        : std::vector<PyTypeObject *>(1, type);
 }
 
 static bool isValueType(SbkObject *self)
 {
-    return PepType_SOTP(Py_TYPE(self))->type_behaviour == BEHAVIOUR_VALUETYPE;
+    return PepType_SOTP(Shiboken::pyType(self))->type_behaviour == BEHAVIOUR_VALUETYPE;
 }
 
 void _debugFormat(std::ostream &s, SbkObject *self)
@@ -1929,6 +1974,7 @@ std::string info(SbkObject *self)
 {
     std::ostringstream s;
 
+    s << "id................ " << self << '\n';
     if (self->d && self->d->cptr) {
         const std::vector<PyTypeObject *> bases = getBases(self);
 
@@ -1946,12 +1992,12 @@ std::string info(SbkObject *self)
          "validCppObject.... " << self->d->validCppObject << "\n"
          "wasCreatedByPython " << self->d->cppObjectCreated << "\n"
          "value......        " << isValueType(self) << "\n"
-         "reference count... " << reinterpret_cast<PyObject *>(self)->ob_refcnt << '\n';
+         "reference count... " << Py_REFCNT(reinterpret_cast<PyObject *>(self)) << '\n';
 
     if (self->d->parentInfo && self->d->parentInfo->parent) {
         s << "parent............ ";
         Shiboken::AutoDecRef parent(PyObject_Str(reinterpret_cast<PyObject *>(self->d->parentInfo->parent)));
-        s << String::toCString(parent) << "\n";
+        s << String::toCString(parent) << '\n';
     }
 
     if (self->d->parentInfo && !self->d->parentInfo->children.empty()) {
index 425328ae5e1ef279acd4cdd050d56fe05b796f13..426298bcf420deb2c6fe837162e24163d1161318 100644 (file)
@@ -120,11 +120,12 @@ LIBSHIBOKEN_API bool SbkObjectType_Check(PyTypeObject *type);
 LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_None();
 LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Result(PyObject *pyResult);
 LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Self(PyObject *self);
-LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, Shiboken::GilState &gil,
-                                            const char *funcName, bool *resultCache,
-                                            PyObject **nameCache);
 } // extern "C"
 
+LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, PyTypeObject *typeObject,
+                                            Shiboken::GilState &gil, const char *funcName,
+                                            PyObject *&resultCache, PyObject **nameCache);
+
 namespace Shiboken
 {
 
index fb9140793a365ec79f50b5a8801d45a24e92d915..e8744ad2dc4a887dc506278a54c5a5c60fef1d27 100644 (file)
@@ -162,6 +162,11 @@ void deallocData(SbkObject *self, bool doCleanup);
 void _debugFormat(std::ostream &str, SbkObject *self);
 } // namespace Object
 
+inline PyTypeObject *pyType(SbkObject *sbo)
+{
+    return Py_TYPE(reinterpret_cast<PyObject *>(sbo));
+}
+
 } // namespace Shiboken
 
 #endif
index a5f7dff683536da5944a841d133a6b24a8d51fb1..32eb697d5732df71e79ae493d19dce8a7001e771 100644 (file)
@@ -9,6 +9,7 @@
 #include "helper.h"
 #include "sbkfeature_base.h"
 #include "sbkmodule.h"
+#include "sbkpep.h"
 #include "sbkstaticstrings.h"
 #include "sbkstring.h"
 
@@ -47,7 +48,9 @@ struct std::hash<GraphNode> {
 namespace Shiboken
 {
 
-using WrapperMap = std::unordered_map<const void *, SbkObject *>;
+// Mapping of C++ address to wrapper. We use a multimap to allow for co-located
+// objects, which happens for example for the first field of a struct.
+using WrapperMap = std::unordered_multimap<const void *, SbkObject *>;
 
 template <class NodeType>
 class BaseGraph
@@ -174,6 +177,9 @@ struct BindingManager::BindingManagerPrivate {
     Graph classHierarchy;
     DestructorEntries deleteInMainThread;
 
+    WrapperMap::const_iterator findSbkObject(const void *cptr, SbkObject *wrapper) const;
+    WrapperMap::const_iterator findByType(const void *cptr, PyTypeObject *desiredType) const;
+
     bool releaseWrapper(void *cptr, SbkObject *wrapper, const int *bases = nullptr);
     bool releaseWrapperHelper(void *cptr, SbkObject *wrapper);
 
@@ -181,14 +187,43 @@ struct BindingManager::BindingManagerPrivate {
     void assignWrapperHelper(SbkObject *wrapper, const void *cptr);
 };
 
-inline bool BindingManager::BindingManagerPrivate::releaseWrapperHelper(void *cptr, SbkObject *wrapper)
+// Find wrapper map entry by Python instance
+WrapperMap::const_iterator
+    BindingManager::BindingManagerPrivate::findSbkObject(const void *cptr,
+                                                         SbkObject *wrapper) const
+{
+    const auto end = wrapperMapper.cend();
+    auto it = wrapperMapper.find(cptr);
+    for (; it != end && it->first == cptr; ++it) {
+        if (it->second == wrapper)
+            return it;
+    }
+    return end;
+}
+
+// Find wrapper map entry by Python type
+WrapperMap::const_iterator
+    BindingManager::BindingManagerPrivate::findByType(const void *cptr,
+                                                      PyTypeObject *desiredType) const
+{
+    const auto end = wrapperMapper.cend();
+    auto it = wrapperMapper.find(cptr);
+    for (; it != end && it->first == cptr; ++it) {
+        auto *foundType = Py_TYPE(reinterpret_cast<PyObject *>(it->second));
+        if (foundType == desiredType || PyType_IsSubtype(foundType, desiredType) != 0)
+            return it;
+    }
+    return end;
+}
+
+bool BindingManager::BindingManagerPrivate::releaseWrapperHelper(void *cptr, SbkObject *wrapper)
 {
     // The wrapper argument is checked to ensure that the correct wrapper is released.
     // Returns true if the correct wrapper is found and released.
     // If wrapper argument is NULL, no such check is performed.
-    auto iter = wrapperMapper.find(cptr);
-    if (iter != wrapperMapper.end() && (wrapper == nullptr || iter->second == wrapper)) {
-        wrapperMapper.erase(iter);
+    const auto it = wrapper != nullptr ? findSbkObject(cptr, wrapper) : wrapperMapper.find(cptr);
+    if (it != wrapperMapper.cend()) {
+        wrapperMapper.erase(it);
         return true;
     }
     return false;
@@ -211,8 +246,8 @@ bool BindingManager::BindingManagerPrivate::releaseWrapper(void *cptr, SbkObject
 inline void BindingManager::BindingManagerPrivate::assignWrapperHelper(SbkObject *wrapper,
                                                                        const void *cptr)
 {
-    auto iter = wrapperMapper.find(cptr);
-    if (iter == wrapperMapper.end())
+    const auto it = findSbkObject(cptr, wrapper);
+    if (it == wrapperMapper.cend())
         wrapperMapper.insert(std::make_pair(cptr, wrapper));
 }
 
@@ -264,15 +299,21 @@ BindingManager &BindingManager::instance() {
     return singleton;
 }
 
-bool BindingManager::hasWrapper(const void *cptr)
+bool BindingManager::hasWrapper(const void *cptr) const
 {
     std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock);
     return m_d->wrapperMapper.find(cptr) != m_d->wrapperMapper.end();
 }
 
+bool BindingManager::hasWrapper(const void *cptr, PyTypeObject *typeObject) const
+{
+    std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock);
+    return m_d->findByType(cptr, typeObject) != m_d->wrapperMapper.cend();
+}
+
 void BindingManager::registerWrapper(SbkObject *pyObj, void *cptr)
 {
-    auto *instanceType = Py_TYPE(pyObj);
+    auto *instanceType = Shiboken::pyType(pyObj);
     auto *d = PepType_SOTP(instanceType);
 
     if (!d)
@@ -285,9 +326,9 @@ void BindingManager::registerWrapper(SbkObject *pyObj, void *cptr)
 
 void BindingManager::releaseWrapper(SbkObject *sbkObj)
 {
-    auto *sbkType = Py_TYPE(sbkObj);
+    auto *sbkType = Shiboken::pyType(sbkObj);
     auto *d = PepType_SOTP(sbkType);
-    int numBases = ((d && d->is_multicpp) ? getNumberOfCppBaseClasses(Py_TYPE(sbkObj)) : 1);
+    int numBases = ((d && d->is_multicpp) ? getNumberOfCppBaseClasses(sbkType) : 1);
 
     void **cptrs = sbkObj->d->cptr;
     const int *mi_offsets = d != nullptr ? d->mi_offsets : nullptr;
@@ -310,7 +351,7 @@ void BindingManager::addToDeletionInMainThread(const DestructorEntry &e)
     m_d->deleteInMainThread.push_back(e);
 }
 
-SbkObject *BindingManager::retrieveWrapper(const void *cptr)
+SbkObject *BindingManager::retrieveWrapper(const void *cptr) const
 {
     std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock);
     auto iter = m_d->wrapperMapper.find(cptr);
@@ -319,96 +360,60 @@ SbkObject *BindingManager::retrieveWrapper(const void *cptr)
     return iter->second;
 }
 
-PyObject *BindingManager::getOverride(const void *cptr,
-                                      PyObject *nameCache[],
-                                      const char *methodName)
+SbkObject *BindingManager::retrieveWrapper(const void *cptr, PyTypeObject *typeObject) const
 {
-    SbkObject *wrapper = retrieveWrapper(cptr);
-    // The refcount can be 0 if the object is dieing and someone called
-    // a virtual method from the destructor
-    if (!wrapper || Py_REFCNT(reinterpret_cast<const PyObject *>(wrapper)) == 0)
-        return nullptr;
-
-    // PYSIDE-1626: Touch the type to initiate switching early.
-    SbkObjectType_UpdateFeature(Py_TYPE(wrapper));
-
-    int flag = currentSelectId(Py_TYPE(wrapper));
-    int propFlag = isdigit(methodName[0]) ? methodName[0] - '0' : 0;
-    bool is_snake = flag & 0x01;
-    PyObject *pyMethodName = nameCache[is_snake];  // borrowed
-    if (pyMethodName == nullptr) {
-        if (propFlag)
-            methodName += 2;    // skip the propFlag and ':'
-        pyMethodName = Shiboken::String::getSnakeCaseName(methodName, is_snake);
-        nameCache[is_snake] = pyMethodName;
-    }
+    std::lock_guard<std::recursive_mutex> guard(m_d->wrapperMapLock);
+    const auto it = m_d->findByType(cptr, typeObject);
+    return it != m_d->wrapperMapper.cend() ? it->second : nullptr;
+}
 
+PyObject *BindingManager::getOverride(SbkObject *wrapper, PyObject *pyMethodName)
+{
     auto *obWrapper = reinterpret_cast<PyObject *>(wrapper);
-    auto *wrapper_dict = SbkObject_GetDict_NoRef(obWrapper);
-    if (PyObject *method = PyDict_GetItem(wrapper_dict, pyMethodName)) {
-        // Note: This special case was implemented for duck-punching, which happens
-        // in the instance dict. It does not work with properties.
-        Py_INCREF(method);
-        return method;
-    }
 
-    PyObject *method = PyObject_GetAttr(obWrapper, pyMethodName);
+    Shiboken::AutoDecRef method(PyObject_GetAttr(obWrapper, pyMethodName));
+    if (method.isNull())
+        return nullptr;
 
     PyObject *function = nullptr;
 
     // PYSIDE-1523: PyMethod_Check is not accepting compiled methods, we do this rather
     // crude check for them.
-    if (method) {
-        // PYSIDE-535: This macro is redefined in a compatible way in pep384
-        if (PyMethod_Check(method)) {
-            if (PyMethod_GET_SELF(method) == obWrapper) {
-                function = PyMethod_GET_FUNCTION(method);
-            } else {
-                Py_DECREF(method);
-                method = nullptr;
-            }
-        } else if (isCompiledMethod(method)) {
-            PyObject *im_self = PyObject_GetAttr(method, PyName::im_self());
-            // Not retaining a reference inline with what PyMethod_GET_SELF does.
-            Py_DECREF(im_self);
-
-            if (im_self == obWrapper) {
-                function = PyObject_GetAttr(method, PyName::im_func());
-                // Not retaining a reference inline with what PyMethod_GET_FUNCTION does.
-                Py_DECREF(function);
-            } else {
-                Py_DECREF(method);
-                method = nullptr;
-            }
-        } else {
-            Py_DECREF(method);
-            method = nullptr;
-        }
+    // PYSIDE-535: This macro is redefined in a compatible way in pep384
+    if (PyMethod_Check(method) != 0) {
+        if (PyMethod_Self(method) != obWrapper)
+            return nullptr;
+        function = PyMethod_Function(method);
+    } else if (isCompiledMethod(method)) {
+        Shiboken::AutoDecRef im_self(PyObject_GetAttr(method, PyName::im_self()));
+        // Not retaining a reference inline with what PyMethod_GET_SELF does.
+        if (im_self.object() != obWrapper)
+            return nullptr;
+        function = PyObject_GetAttr(method, PyName::im_func());
+        // Not retaining a reference inline with what PyMethod_GET_FUNCTION does.
+        Py_DECREF(function);
+    } else {
+        return nullptr;
     }
 
-    if (method != nullptr) {
-        PyObject *mro = Py_TYPE(wrapper)->tp_mro;
-
-        bool defaultFound = false;
-        // The first class in the mro (index 0) is the class being checked and it should not be tested.
-        // The last class in the mro (size - 1) is the base Python object class which should not be tested also.
-        for (Py_ssize_t idx = 1, size = PyTuple_Size(mro); idx < size - 1; ++idx) {
-            auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
-            AutoDecRef parentDict(PepType_GetDict(parent));
-            if (parentDict) {
-                if (PyObject *defaultMethod = PyDict_GetItem(parentDict.object(), pyMethodName)) {
-                    defaultFound = true;
-                    if (function != defaultMethod)
-                        return method;
-                }
+    PyObject *mro = Py_TYPE(obWrapper)->tp_mro;
+    bool defaultFound = false;
+    // The first class in the mro (index 0) is the class being checked and it should not be tested.
+    // The last class in the mro (size - 1) is the base Python object class which should not be tested also.
+    for (Py_ssize_t idx = 1, size = PyTuple_Size(mro); idx < size - 1; ++idx) {
+        auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
+        AutoDecRef parentDict(PepType_GetDict(parent));
+        if (parentDict) {
+            if (PyObject *defaultMethod = PyDict_GetItem(parentDict.object(), pyMethodName)) {
+                defaultFound = true;
+                if (function != defaultMethod)
+                    return function;
             }
         }
-        // PYSIDE-2255: If no default method was found, use the method.
-        if (!defaultFound)
-            return method;
-        Py_DECREF(method);
     }
-
+    // PYSIDE-2255: If no default method was found, use the method.
+    if (!defaultFound)
+        return function;
     return nullptr;
 }
 
@@ -448,7 +453,7 @@ void BindingManager::visitAllPyObjects(ObjectVisitor visitor, void *data)
 {
     WrapperMap copy = m_d->wrapperMapper;
     for (const auto &p : copy) {
-        if (hasWrapper(p.first))
+        if (m_d->findSbkObject(p.first, p.second) != m_d->wrapperMapper.cend())
             visitor(p.second, data);
     }
 }
@@ -465,11 +470,11 @@ void BindingManager::dumpWrapperMap()
         << "WrapperMap size: " << wrapperMap.size() << " Types: "
         << m_d->classHierarchy.nodeSet().size() << '\n';
     for (auto it : wrapperMap) {
-        const SbkObject *sbkObj = it.second;
+        auto *ob = reinterpret_cast<PyObject *>(it.second);
         std::cerr << "key: " << it.first << ", value: "
-            << static_cast<const void *>(sbkObj) << " ("
-            << (Py_TYPE(sbkObj))->tp_name << ", refcnt: "
-            << Py_REFCNT(reinterpret_cast<const PyObject *>(sbkObj)) << ")\n";
+            << static_cast<const void *>(ob) << " ("
+            << PepType_GetFullyQualifiedNameStr(Py_TYPE(ob)) << ", refcnt: "
+            << Py_REFCNT(ob) << ")\n";
     }
     std::cerr << "-------------------------------\n";
 }
@@ -501,7 +506,7 @@ static bool _callInheritedInit(PyObject *self, PyObject *args, PyObject *kwds,
     /* No need to check the last one: it's gonna be skipped anyway.  */
     for ( ; idx + 1 < n; ++idx) {
         auto *lookType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
-        if (className == lookType->tp_name)
+        if (className == PepType_GetFullyQualifiedNameStr(lookType))
             break;
     }
     // We are now at the first non-Python class `QObject`.
index 300783e7a4a8bf331386dbaf276494e55aa961e8..e2a4ac8ea22a8d4b41df5dfa99f64a664f9b54b1 100644 (file)
@@ -33,7 +33,8 @@ public:
 
     static BindingManager &instance();
 
-    bool hasWrapper(const void *cptr);
+    bool hasWrapper(const void *cptr, PyTypeObject *typeObject) const;
+    bool hasWrapper(const void *cptr) const;
 
     void registerWrapper(SbkObject *pyObj, void *cptr);
     void releaseWrapper(SbkObject *wrapper);
@@ -41,8 +42,9 @@ public:
     void runDeletionInMainThread();
     void addToDeletionInMainThread(const DestructorEntry &);
 
-    SbkObject *retrieveWrapper(const void *cptr);
-    PyObject *getOverride(const void *cptr, PyObject *nameCache[], const char *methodName);
+    SbkObject *retrieveWrapper(const void *cptr, PyTypeObject *typeObject) const;
+    SbkObject *retrieveWrapper(const void *cptr) const;
+    static PyObject *getOverride(SbkObject *wrapper, PyObject *pyMethodName);
 
     void addClassInheritance(Module::TypeInitStruct *parent, Module::TypeInitStruct *child);
     /// Try to find the correct type of cptr via type discovery knowing that it's at least
index 4ccf970e5501399999d9ab237ee35937ff057f9d..c7f7648ba6d1a5b60d81e92e72512cb322fef448 100644 (file)
@@ -9,10 +9,12 @@
  *
  */
 
-#ifdef Py_LIMITED_API
+#include "bufferprocs_py37.h"
+#include "sbkpep.h"
 
-#include "sbkpython.h"
-/* Buffer C-API for Python 3.0 */
+#if defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030B0000
+
+// Buffer C-API for Python 3.0 (copy of cpython/Objects/abstract.c:426)
 
 int
 PyObject_GetBuffer(PyObject *obj, Pep_buffer *view, int flags)
@@ -28,320 +30,10 @@ PyObject_GetBuffer(PyObject *obj, Pep_buffer *view, int flags)
     return (*pb->bf_getbuffer)(obj, view, flags);
 }
 
-static int
-_IsFortranContiguous(const Pep_buffer *view)
-{
-    Py_ssize_t sd, dim;
-    int i;
-
-    /* 1) len = product(shape) * itemsize
-       2) itemsize > 0
-       3) len = 0 <==> exists i: shape[i] = 0 */
-    if (view->len == 0) return 1;
-    if (view->strides == NULL) {  /* C-contiguous by definition */
-        /* Trivially F-contiguous */
-        if (view->ndim <= 1) return 1;
-
-        /* ndim > 1 implies shape != NULL */
-        assert(view->shape != NULL);
-
-        /* Effectively 1-d */
-        sd = 0;
-        for (i=0; i<view->ndim; i++) {
-            if (view->shape[i] > 1) sd += 1;
-        }
-        return sd <= 1;
-    }
-
-    /* strides != NULL implies both of these */
-    assert(view->ndim > 0);
-    assert(view->shape != NULL);
-
-    sd = view->itemsize;
-    for (i=0; i<view->ndim; i++) {
-        dim = view->shape[i];
-        if (dim > 1 && view->strides[i] != sd) {
-            return 0;
-        }
-        sd *= dim;
-    }
-    return 1;
-}
-
-static int
-_IsCContiguous(const Pep_buffer *view)
-{
-    Py_ssize_t sd, dim;
-    int i;
-
-    /* 1) len = product(shape) * itemsize
-       2) itemsize > 0
-       3) len = 0 <==> exists i: shape[i] = 0 */
-    if (view->len == 0) return 1;
-    if (view->strides == NULL) return 1; /* C-contiguous by definition */
-
-    /* strides != NULL implies both of these */
-    assert(view->ndim > 0);
-    assert(view->shape != NULL);
-
-    sd = view->itemsize;
-    for (i=view->ndim-1; i>=0; i--) {
-        dim = view->shape[i];
-        if (dim > 1 && view->strides[i] != sd) {
-            return 0;
-        }
-        sd *= dim;
-    }
-    return 1;
-}
-
-int
-PyBuffer_IsContiguous(const Pep_buffer *view, char order)
-{
-
-    if (view->suboffsets != NULL) return 0;
-
-    if (order == 'C')
-        return _IsCContiguous(view);
-    else if (order == 'F')
-        return _IsFortranContiguous(view);
-    else if (order == 'A')
-        return (_IsCContiguous(view) || _IsFortranContiguous(view));
-    return 0;
-}
-
-
-void *
-PyBuffer_GetPointer(Pep_buffer *view, Py_ssize_t *indices)
-{
-    int i;
-    auto pointer = reinterpret_cast<char *>(view->buf);
-    for (i = 0; i < view->ndim; i++) {
-        pointer += view->strides[i]*indices[i];
-        if ((view->suboffsets != NULL) && (view->suboffsets[i] >= 0)) {
-            pointer = *reinterpret_cast<char **>(pointer) + view->suboffsets[i];
-        }
-    }
-    return pointer;
-}
-
-
-void
-_Py_add_one_to_index_F(int nd, Py_ssize_t *index, const Py_ssize_t *shape)
-{
-    int k;
-
-    for (k=0; k<nd; k++) {
-        if (index[k] < shape[k]-1) {
-            index[k]++;
-            break;
-        }
-        else {
-            index[k] = 0;
-        }
-    }
-}
-
-void
-_Py_add_one_to_index_C(int nd, Py_ssize_t *index, const Py_ssize_t *shape)
-{
-    int k;
-
-    for (k=nd-1; k>=0; k--) {
-        if (index[k] < shape[k]-1) {
-            index[k]++;
-            break;
-        }
-        else {
-            index[k] = 0;
-        }
-    }
-}
-
-int
-PyBuffer_FromContiguous(Pep_buffer *view, void *buf, Py_ssize_t len, char fort)
-{
-    int k;
-    void (*addone)(int, Py_ssize_t *, const Py_ssize_t *);
-    Py_ssize_t *indices, elements;
-    char *src, *ptr;
-
-    if (len > view->len) {
-        len = view->len;
-    }
-
-    if (PyBuffer_IsContiguous(view, fort)) {
-        /* simplest copy is all that is needed */
-        memcpy(view->buf, buf, len);
-        return 0;
-    }
-
-    /* Otherwise a more elaborate scheme is needed */
-
-    /* view->ndim <= 64 */
-    indices = (Py_ssize_t *)PyMem_Malloc(sizeof(Py_ssize_t)*(view->ndim));
-    if (indices == NULL) {
-        PyErr_NoMemory();
-        return -1;
-    }
-    for (k=0; k<view->ndim; k++) {
-        indices[k] = 0;
-    }
-
-    if (fort == 'F') {
-        addone = _Py_add_one_to_index_F;
-    }
-    else {
-        addone = _Py_add_one_to_index_C;
-    }
-    src = (char *)buf; // patched by CT
-    /* XXX : This is not going to be the fastest code in the world
-             several optimizations are possible.
-     */
-    elements = len / view->itemsize;
-    while (elements--) {
-        ptr = (char *)PyBuffer_GetPointer(view, indices); // patched by CT
-        memcpy(ptr, src, view->itemsize);
-        src += view->itemsize;
-        addone(view->ndim, indices, view->shape);
-    }
-
-    PyMem_Free(indices);
-    return 0;
-}
-
-int PyObject_CopyData(PyObject *dest, PyObject *src)
-{
-    Pep_buffer view_dest, view_src;
-    int k;
-    Py_ssize_t *indices, elements;
-    char *dptr, *sptr;
-
-    if (!PyObject_CheckBuffer(dest) ||
-        !PyObject_CheckBuffer(src)) {
-        PyErr_SetString(PyExc_TypeError,
-                        "both destination and source must be "\
-                        "bytes-like objects");
-        return -1;
-    }
-
-    if (PyObject_GetBuffer(dest, &view_dest, PyBUF_FULL) != 0) return -1;
-    if (PyObject_GetBuffer(src, &view_src, PyBUF_FULL_RO) != 0) {
-        PyBuffer_Release(&view_dest);
-        return -1;
-    }
-
-    if (view_dest.len < view_src.len) {
-        PyErr_SetString(PyExc_BufferError,
-                        "destination is too small to receive data from source");
-        PyBuffer_Release(&view_dest);
-        PyBuffer_Release(&view_src);
-        return -1;
-    }
-
-    if ((PyBuffer_IsContiguous(&view_dest, 'C') &&
-         PyBuffer_IsContiguous(&view_src, 'C')) ||
-        (PyBuffer_IsContiguous(&view_dest, 'F') &&
-         PyBuffer_IsContiguous(&view_src, 'F'))) {
-        /* simplest copy is all that is needed */
-        memcpy(view_dest.buf, view_src.buf, view_src.len);
-        PyBuffer_Release(&view_dest);
-        PyBuffer_Release(&view_src);
-        return 0;
-    }
-
-    /* Otherwise a more elaborate copy scheme is needed */
-
-    /* XXX(nnorwitz): need to check for overflow! */
-    indices = (Py_ssize_t *)PyMem_Malloc(sizeof(Py_ssize_t)*view_src.ndim);
-    if (indices == NULL) {
-        PyErr_NoMemory();
-        PyBuffer_Release(&view_dest);
-        PyBuffer_Release(&view_src);
-        return -1;
-    }
-    for (k=0; k<view_src.ndim;k++) {
-        indices[k] = 0;
-    }
-    elements = 1;
-    for (k=0; k<view_src.ndim; k++) {
-        /* XXX(nnorwitz): can this overflow? */
-        elements *= view_src.shape[k];
-    }
-    while (elements--) {
-        _Py_add_one_to_index_C(view_src.ndim, indices, view_src.shape);
-        dptr = (char *)PyBuffer_GetPointer(&view_dest, indices); // patched by CT
-        sptr = (char *)PyBuffer_GetPointer(&view_src, indices); // patched by CT
-        memcpy(dptr, sptr, view_src.itemsize);
-    }
-    PyMem_Free(indices);
-    PyBuffer_Release(&view_dest);
-    PyBuffer_Release(&view_src);
-    return 0;
-}
-
-void
-PyBuffer_FillContiguousStrides(int nd, Py_ssize_t *shape,
-                               Py_ssize_t *strides, int itemsize,
-                               char fort)
-{
-    int k;
-    Py_ssize_t sd;
-
-    sd = itemsize;
-    if (fort == 'F') {
-        for (k=0; k<nd; k++) {
-            strides[k] = sd;
-            sd *= shape[k];
-        }
-    }
-    else {
-        for (k=nd-1; k>=0; k--) {
-            strides[k] = sd;
-            sd *= shape[k];
-        }
-    }
-    return;
-}
-
-int
-PyBuffer_FillInfo(Pep_buffer *view, PyObject *obj, void *buf, Py_ssize_t len,
-                  int readonly, int flags)
-{
-    if (view == NULL) {
-        PyErr_SetString(PyExc_BufferError,
-            "PyBuffer_FillInfo: view==NULL argument is obsolete");
-        return -1;
-    }
-
-    if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) &&
-        (readonly == 1)) {
-        PyErr_SetString(PyExc_BufferError,
-                        "Object is not writable.");
-        return -1;
-    }
-
-    view->obj = obj;
-    if (obj)
-        Py_INCREF(obj);
-    view->buf = buf;
-    view->len = len;
-    view->readonly = readonly;
-    view->itemsize = 1;
-    view->format = NULL;
-    if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
-        view->format = (char *)"B"; // patched by CT
-    view->ndim = 1;
-    view->shape = NULL;
-    if ((flags & PyBUF_ND) == PyBUF_ND)
-        view->shape = &(view->len);
-    view->strides = NULL;
-    if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES)
-        view->strides = &(view->itemsize);
-    view->suboffsets = NULL;
-    view->internal = NULL;
-    return 0;
-}
+// Omitted functions: _IsFortranContiguous(), _IsCContiguous(), PyBuffer_IsContiguous(),
+// PyBuffer_GetPointer(),// _Py_add_one_to_index_F(), _Py_add_one_to_index_C(),
+// PyBuffer_FromContiguous(), PyObject_CopyData(), PyBuffer_FillContiguousStrides(),
+// PyBuffer_FillInfo()
 
 void
 PyBuffer_Release(Pep_buffer *view)
@@ -357,4 +49,4 @@ PyBuffer_Release(Pep_buffer *view)
     Py_DECREF(obj);
 }
 
-#endif // Py_LIMITED_API
+#endif // Py_LIMITED_API && < 3.11
index e16194e509877b607dfcfbdcc6ef2034bab8e7cd..f381369e017eb4a16fc44b6b6a518dec2d034b2f 100644 (file)
@@ -50,9 +50,18 @@ PSF LICENSE AGREEMENT FOR PYTHON 3.7.0
 #ifndef BUFFER_REENABLE_H
 #define BUFFER_REENABLE_H
 
-/* buffer interface */
-// This has been renamed to Pep_buffer and will be used.
-typedef struct bufferinfo {
+#include "sbkpython.h"
+#include "shibokenmacros.h"
+
+#ifdef Py_LIMITED_API
+
+// The buffer interface has been added to limited API in 3.11, (abstract.h, PYSIDE-1960,
+// except some internal structs).
+
+#  if Py_LIMITED_API < 0x030B0000
+
+struct Pep_buffer
+{
     void *buf;
     PyObject *obj;        /* owned reference */
     Py_ssize_t len;
@@ -65,10 +74,33 @@ typedef struct bufferinfo {
     Py_ssize_t *strides;
     Py_ssize_t *suboffsets;
     void *internal;
-} Pep_buffer;
+};
 
 using getbufferproc =int (*)(PyObject *, Pep_buffer *, int);
 using releasebufferproc = void (*)(PyObject *, Pep_buffer *);
+using Py_buffer = Pep_buffer;
+
+#  else //  < 3.11
+
+using Pep_buffer = Py_buffer;
+
+#  endif // >= 3.11
+
+// The structs below are not part of the limited API.
+struct PepBufferProcs
+{
+    getbufferproc bf_getbuffer;
+    releasebufferproc bf_releasebuffer;
+};
+
+struct PepBufferType
+{
+    PyVarObject ob_base;
+    void *skip[17];
+    PepBufferProcs *tp_as_buffer;
+};
+
+#  if Py_LIMITED_API < 0x030B0000
 
 /* Maximum number of dimensions */
 #define PyBUF_MAX_NDIM 64
@@ -106,4 +138,26 @@ using releasebufferproc = void (*)(PyObject *, Pep_buffer *);
 LIBSHIBOKEN_API PyObject *PyMemoryView_FromBuffer(Pep_buffer *info);
 #define Py_buffer Pep_buffer
 
+#define PyObject_CheckBuffer(obj) \
+    ((PepType_AS_BUFFER(Py_TYPE(obj)) != NULL) &&  \
+     (PepType_AS_BUFFER(Py_TYPE(obj))->bf_getbuffer != NULL))
+
+LIBSHIBOKEN_API int PyObject_GetBuffer(PyObject *ob, Pep_buffer *view, int flags);
+LIBSHIBOKEN_API void PyBuffer_Release(Pep_buffer *view);
+
+#  endif // << 3.11
+
+#  define PepType_AS_BUFFER(type)   \
+reinterpret_cast<PepBufferType *>(type)->tp_as_buffer
+
+using PyBufferProcs = PepBufferProcs;
+
+#else // Py_LIMITED_API
+
+using PepBufferProcs = PyBufferProcs;
+
+#  define PepType_AS_BUFFER(type)             ((type)->tp_as_buffer)
+
+#endif // !Py_LIMITED_API
+
 #endif // BUFFER_REENABLE_H
index 92f70f1549f2f90a5511c2f160ada22742583b4e..016ba12528c1f06cd3dd8ad8d9bf891a5705b88c 100644 (file)
@@ -2,10 +2,12 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "helper.h"
+#include "sbkpepbuffer.h"
 #include "basewrapper_p.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "pep384impl.h"
+#include "bufferprocs_py37.h"
 
 #include <algorithm>
 #include <optional>
@@ -46,14 +48,14 @@ static bool verbose = false;
 
 static void formatTypeTuple(PyObject *t, const char *what, std::ostream &str);
 
-static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool verbose)
+static void formatPyTypeObject(PyTypeObject *obj, std::ostream &str, bool verbose)
 {
     if (obj == nullptr) {
         str << '0';
         return;
     }
 
-    str << '"' << obj->tp_name << '"';
+    str << '"' << PepType_GetFullyQualifiedNameStr(obj) << '"';
     if (verbose) {
         bool immutableType = false;
         str << ", 0x" << std::hex << obj->tp_flags << std::dec;
@@ -110,7 +112,7 @@ static void formatPyTypeObject(const PyTypeObject *obj, std::ostream &str, bool
             if (!immutableType) {
                 auto *underlying = reinterpret_cast<const PyObject *>(obj)->ob_type;
                 if (underlying != nullptr && underlying != obj) {
-                    str << ", underlying=\"" << underlying->tp_name << '"';
+                    str << ", underlying=\"" << PepType_GetFullyQualifiedNameStr(underlying) << '"';
                 }
             }
         }
@@ -126,10 +128,12 @@ static void formatTypeTuple(PyObject *t, const char *what, std::ostream &str)
             if (i != 0)
                 str << ", ";
             Shiboken::AutoDecRef item(PyTuple_GetItem(t, i));
-            if (item.isNull())
+            if (item.isNull()) {
                 str << '0'; // Observed with non-ready types
-            else
-                str << '"' << reinterpret_cast<PyTypeObject *>(item.object())->tp_name << '"';
+            } else {
+                str << '"' << PepType_GetFullyQualifiedNameStr(reinterpret_cast<PyTypeObject *>(item.object()))
+                    << '"';
+            }
         }
         str << '}';
     }
@@ -179,9 +183,13 @@ static void formatPyDict(PyObject *obj, std::ostream &str)
     Py_ssize_t pos = 0;
     str << '{';
     while (PyDict_Next(obj, &pos, &key, &value) != 0) {
-        if (pos)
+        if (pos > 1)
             str << ", ";
-        str << Shiboken::debugPyObject(key) << '=' << Shiboken::debugPyObject(value);
+        if (PyUnicode_Check(key))
+            str << '"' << Shiboken::String::toCString(key) << '"';
+        else
+            str << Shiboken::debugPyObject(key);
+        str << ": " << Shiboken::debugPyObject(value);
     }
     str << '}';
 }
@@ -356,6 +364,8 @@ static void formatPyObjectHelper(PyObject *obj, std::ostream &str)
         formatPyFunction(obj, str);
     else if (PyMethod_Check(obj) != 0)
         formatPyMethod(obj, str);
+    else if (PyModule_Check(obj) != 0)
+        str << "Module \"" << PyModule_GetName(obj) << '"';
     else if (PepCode_Check(obj) != 0)
         formatPyCodeObject(obj, str);
     else if (PySequence_Check(obj))
@@ -386,7 +396,7 @@ debugSbkObject::debugSbkObject(SbkObject *o) : m_object(o)
 {
 }
 
-debugPyTypeObject::debugPyTypeObject(const PyTypeObject *o) : m_object(o)
+debugPyTypeObject::debugPyTypeObject(PyTypeObject *o) : m_object(o)
 {
 }
 
@@ -492,10 +502,16 @@ bool listToArgcArgv(PyObject *argList, int *argcIn, char ***argvIn, const char *
         auto *argv = new char *[1];
         *argvIn = argv;
         *argcIn = 1;
-        if (PyObject *appName = PyDict_GetItem(PyEval_GetGlobals(), Shiboken::PyMagicName::file()))
-            argv[0] = strDup(Shiboken::String::toCString(appName));
-        else
-            argv[0] = strDup(defaultAppName ? defaultAppName : "PySideApplication");
+
+        const char *appNameC = nullptr;
+        Shiboken::AutoDecRef globals(PepEval_GetFrameGlobals());
+        if (!globals.isNull())  {
+            if (PyObject *appName = PyDict_GetItem(globals, Shiboken::PyMagicName::file()))
+                appNameC = Shiboken::String::toCString(appName);
+        }
+        if (appNameC == nullptr)
+            appNameC = defaultAppName ? defaultAppName : "PySideApplication";
+        argv[0] = strDup(appNameC);
         return true;
     }
 
index ccd80b2dbf9afe8bbe10aa54c9adace6b1b1ca60..a0a09e6ab72e955a66e66d6b56405dfe8564bcb2 100644 (file)
@@ -96,17 +96,12 @@ struct LIBSHIBOKEN_API debugSbkObject
 
 struct LIBSHIBOKEN_API debugPyTypeObject
 {
-    explicit debugPyTypeObject(const PyTypeObject *o);
+    explicit debugPyTypeObject(PyTypeObject *o);
 
-    const PyTypeObject *m_object;
+    PyTypeObject *m_object;
 };
 
-struct LIBSHIBOKEN_API debugPyBuffer
-{
-    explicit debugPyBuffer(const Py_buffer &b);
-
-    const Py_buffer &m_buffer;
-};
+struct debugPyBuffer;
 
 struct debugPyArrayObject
 {
index bd7a4c51aa9c781a6acaa2a8dd454d91feec8b5e..09a6a6e8c092e3799ec3b9c00b386256b3ed700b 100644 (file)
@@ -3,7 +3,7 @@
 
 #define PEP384_INTERN
 
-#include "sbkpython.h"
+#include "pep384impl.h"
 #include "autodecref.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
@@ -120,7 +120,7 @@ check_PyTypeObject_valid()
     Shiboken::AutoDecRef tpDict(PepType_GetDict(check));
     auto *checkDict = tpDict.object();
     if (false
-        || strcmp(probe_tp_name, check->tp_name) != 0
+        || std::strcmp(probe_tp_name, check->tp_name) != 0
         || probe_tp_basicsize       != check->tp_basicsize
         || probe_tp_dealloc         != check->tp_dealloc
         || probe_tp_repr            != check->tp_repr
@@ -148,7 +148,7 @@ check_PyTypeObject_valid()
         || probe_tp_bases           != typetype->tp_bases
         || probe_tp_mro             != typetype->tp_mro
         || Py_TPFLAGS_DEFAULT       != (check->tp_flags & Py_TPFLAGS_DEFAULT))
-        Py_FatalError("The structure of type objects has changed!");
+        Py_FatalError("libshiboken: The structure of type objects has changed!");
     Py_DECREF(checkObj);
     Py_DECREF(probe_tp_base_obj);
     Py_DECREF(w);
@@ -407,6 +407,8 @@ const char *_PepUnicode_AsString(PyObject *str)
             Py_FatalError("Error in " AT);
     }
     PyObject *bytesStr = PyUnicode_AsEncodedString(str, "utf8", nullptr);
+    if (bytesStr == nullptr)
+        Py_FatalError("Error in " AT);
     PyObject *entry = PyDict_GetItemWithError(cstring_dict, bytesStr);
     if (entry == nullptr) {
         int e = PyDict_SetItem(cstring_dict, bytesStr, bytesStr);
@@ -464,7 +466,7 @@ Pep_GetVerboseFlag()
 
 // Support for pyerrors.h
 
-#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000
+#ifdef PEP_OLD_ERR_API
 // Emulate PyErr_GetRaisedException() using the deprecated PyErr_Fetch()/PyErr_Store()
 PyObject *PepErr_GetRaisedException()
 {
@@ -549,7 +551,7 @@ static PyTypeObject *dt_getCheck(const char *name)
     PyObject *op = PyObject_GetAttrString(PyDateTimeAPI->module, name);
     if (op == nullptr) {
         fprintf(stderr, "datetime.%s not found\n", name);
-        Py_FatalError("aborting");
+        Py_FatalError("libshiboken: error initializing DateTime support, aborting");
     }
     return reinterpret_cast<PyTypeObject *>(op);
 }
@@ -563,10 +565,10 @@ init_DateTime(void)
     if (!initialized) {
         PyDateTimeAPI = (datetime_struc *)malloc(sizeof(datetime_struc));
         if (PyDateTimeAPI == nullptr)
-            Py_FatalError("PyDateTimeAPI malloc error, aborting");
+            Py_FatalError("libshiboken: PyDateTimeAPI malloc error, aborting");
         PyDateTimeAPI->module = PyImport_ImportModule("datetime");
         if (PyDateTimeAPI->module == nullptr)
-            Py_FatalError("datetime module not found, aborting");
+            Py_FatalError("libshiboken: datetime module not found, aborting");
         PyDateTimeAPI->DateType     = dt_getCheck("date");
         PyDateTimeAPI->DateTimeType = dt_getCheck("datetime");
         PyDateTimeAPI->TimeType     = dt_getCheck("time");
@@ -781,6 +783,22 @@ PepType_GetNameStr(PyTypeObject *type)
     return nodots != nullptr ? nodots + 1 : ret;
 }
 
+const char *PepType_GetFullyQualifiedNameStr(PyTypeObject *type)
+{
+#if 0
+    // Should look like the below code for Limited API >= 3.13, however, it crashes for heap types
+    //  since PyType_GetFullyQualifiedName() calls type_qualname() at Objects/typeobject.c:1402
+    // which expects a PyHeapTypeObject (struct _heaptypeobject).
+    Shiboken::AutoDecRef name(PyType_GetFullyQualifiedName(type));
+    Py_ssize_t size{};
+    const char *result = PyUnicode_AsUTF8AndSize(name.object(), &size);
+    assert(result != nullptr && size > 0);
+    return result;
+#else
+    return type->tp_name;
+#endif
+}
+
 // PYSIDE-2264: Find the _functools or functools module and retrieve the
 //              partial function. This can be tampered with, check carefully.
 PyObject *
@@ -798,10 +816,10 @@ Pep_GetPartialFunction(void)
         functools = PyImport_ImportModule("functools");
     }
     if (!functools)
-        Py_FatalError("functools cannot be found");
+        Py_FatalError("libshiboken: functools cannot be found");
     result = PyObject_GetAttrString(functools, "partial");
     if (!result || !PyCallable_Check(result))
-        Py_FatalError("partial not found or not a function");
+        Py_FatalError("libshiboken: partial not found or not a function");
     initialized = true;
     return result;
 }
@@ -822,10 +840,14 @@ PepRun_GetResult(const char *command)
      * Evaluate a string and return the variable `result`
      */
     PyObject *d = PyDict_New();
-    if (d == nullptr
-        || PyDict_SetItem(d, Shiboken::PyMagicName::builtins(), PyEval_GetBuiltins()) < 0) {
+    if (d == nullptr)
         return nullptr;
-    }
+
+    Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+    if (PyDict_SetItem(d, Shiboken::PyMagicName::builtins(), PyEval_GetBuiltins()) < 0)
+        return nullptr;
+    builtins.reset(nullptr);
+
     PyObject *v = PyRun_String(command, Py_file_input, d, d);
     PyObject *res = v ? PyDict_GetItem(d, Shiboken::PyName::result()) : nullptr;
     Py_XDECREF(v);
@@ -1137,6 +1159,61 @@ void *PepType_GetSlot(PyTypeObject *type, int aSlot)
     return nullptr;
 }
 
+PyObject *PepEval_GetFrameGlobals()
+{
+    // PyEval_GetFrameGlobals() (added to stable ABI in 3.13) returns a new reference
+    // as opposed to deprecated PyEval_GetGlobals() which returns a borrowed reference
+#if !defined(PYPY_VERSION) && ((!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030D0000) || (defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030D0000))
+    return PyEval_GetFrameGlobals();
+#else
+    PyObject *result = PyEval_GetGlobals();
+    Py_XINCREF(result);
+    return result;
+#endif
+}
+
+PyObject *PepEval_GetFrameBuiltins()
+{
+    // PepEval_GetFrameBuiltins() (added to stable ABI in 3.13) returns a new reference
+    // as opposed to deprecated PyEval_GetBuiltins() which returns a borrowed reference
+#if !defined(PYPY_VERSION) && ((!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030D0000) || (defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030D0000))
+    return PyEval_GetFrameBuiltins();
+#else
+    PyObject *result = PyEval_GetBuiltins();
+    Py_XINCREF(result);
+    return result;
+#endif
+}
+
+int PepModule_AddType(PyObject *module, PyTypeObject *type)
+{
+    // PyModule_AddType (added to stable ABI in 3.10) is the replacement for
+    // PyModule_AddObject() (deprecated in 3.13) for adding types to a module.
+#if !defined(PYPY_VERSION) && ((!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030A0000) || (defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030A0000))
+    return PyModule_AddType(module, type);
+#else
+    auto *ob = reinterpret_cast<PyObject *>(type);
+    int result = PyModule_AddObject(module, PepType_GetNameStr(type), ob);
+    if (result != 0)
+        Py_XDECREF(ob);
+    return result;
+#endif
+}
+
+int PepModule_Add(PyObject *module, const char *name, PyObject *value)
+{
+    // PyModule_Add (added to stable ABI in 3.13) is the replacement for PyModule_AddObject()
+    // (deprecated in 3.13).
+#if !defined(PYPY_VERSION) && ((!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030D0000) || (defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030D0000))
+    return PyModule_Add(module, name, value);
+#else
+    int result = PyModule_AddObject(module, name, value);
+    if (result != 0)
+        Py_XDECREF(value);
+    return result;
+#endif
+}
+
 /***************************************************************************
  *
  * PYSIDE-535: The enum/flag error
index 4c4e1b47ea18a709d361afea6d9150432837511b..96284b93bf85db6b3dac6860ce31e87846e59dc6 100644 (file)
@@ -4,22 +4,12 @@
 #ifndef PEP384IMPL_H
 #define PEP384IMPL_H
 
+#include "sbkpython.h"
 #include "shibokenmacros.h"
 
 extern "C"
 {
 
-/*****************************************************************************
- *
- * RESOLVED: memoryobject.h
- *
- */
-
-// Extracted into bufferprocs27.h
-#ifdef Py_LIMITED_API
-#include "bufferprocs_py37.h"
-#endif
-
 /*****************************************************************************
  *
  * RESOLVED: object.h
@@ -170,8 +160,14 @@ struct SbkQFlagsTypePrivate;
 /*****************************************************************************/
 
 // functions used everywhere
+
+/// (convenience) Return the unqualified type name
 LIBSHIBOKEN_API const char *PepType_GetNameStr(PyTypeObject *type);
 
+/// (convenience) Return the fully qualified type name(PepType_GetFullyQualifiedNameStr())
+/// as C-string
+LIBSHIBOKEN_API const char *PepType_GetFullyQualifiedNameStr(PyTypeObject *type);
+
 LIBSHIBOKEN_API PyObject *Pep_GetPartialFunction(void);
 
 /*****************************************************************************
@@ -190,15 +186,20 @@ LIBSHIBOKEN_API int Pep_GetFlag(const char *name);
 LIBSHIBOKEN_API int Pep_GetVerboseFlag(void);
 #endif
 
+#if (defined(Py_LIMITED_API) && Py_LIMITED_API < 0x030C0000) || PY_VERSION_HEX < 0x030C0000
+#  define PEP_OLD_ERR_API
+#endif
+
 // pyerrors.h
-#if defined(Py_LIMITED_API) || PY_VERSION_HEX < 0x030C0000
+#ifdef PEP_OLD_ERR_API
 LIBSHIBOKEN_API PyObject *PepErr_GetRaisedException();
 LIBSHIBOKEN_API PyObject *PepException_GetArgs(PyObject *ex);
 LIBSHIBOKEN_API void PepException_SetArgs(PyObject *ex, PyObject *args);
 #else
-#  define PepErr_GetRaisedException PyErr_GetRaisedException
-#  define PepException_GetArgs PyException_GetArgs
-#  define PepException_SetArgs PyException_SetArgs
+inline PyObject *PepErr_GetRaisedException() { return PyErr_GetRaisedException(); }
+inline PyObject *PepException_GetArgs(PyObject *ex) { return PyException_GetArgs(ex); }
+inline void PepException_SetArgs(PyObject *ex, PyObject *args)
+{ PyException_SetArgs(ex, args); }
 #endif
 
 /*****************************************************************************
@@ -290,48 +291,6 @@ using PyCFunctionObject = struct _pycfunc;
 LIBSHIBOKEN_API PyObject *PyRun_String(const char *, int, PyObject *, PyObject *);
 #endif
 
-/*****************************************************************************
- *
- * RESOLVED: abstract.h
- *
- */
-#ifdef Py_LIMITED_API
-
-// This definition breaks the limited API a little, because it re-enables the
-// buffer functions.
-// But this is no problem as we check it's validity for every version.
-
-// PYSIDE-1960 The buffer interface is since Python 3.11 part of the stable
-// API and we do not need to check the compatibility by hand anymore.
-
-typedef struct {
-     getbufferproc bf_getbuffer;
-     releasebufferproc bf_releasebuffer;
-} PyBufferProcs;
-
-typedef struct _Pepbuffertype {
-    PyVarObject ob_base;
-    void *skip[17];
-    PyBufferProcs *tp_as_buffer;
-} PepBufferType;
-
-#define PepType_AS_BUFFER(type)   \
-    reinterpret_cast<PepBufferType *>(type)->tp_as_buffer
-
-#define PyObject_CheckBuffer(obj) \
-    ((PepType_AS_BUFFER(Py_TYPE(obj)) != NULL) &&  \
-     (PepType_AS_BUFFER(Py_TYPE(obj))->bf_getbuffer != NULL))
-
-LIBSHIBOKEN_API int PyObject_GetBuffer(PyObject *ob, Pep_buffer *view, int flags);
-LIBSHIBOKEN_API void PyBuffer_Release(Pep_buffer *view);
-
-#else
-
-#define Pep_buffer                          Py_buffer
-#define PepType_AS_BUFFER(type)             ((type)->tp_as_buffer)
-
-#endif /* Py_LIMITED_API */
-
 /*****************************************************************************
  *
  * RESOLVED: funcobject.h
@@ -549,6 +508,18 @@ LIBSHIBOKEN_API int PepType_SetDict(PyTypeObject *type, PyObject *dict);
 
 LIBSHIBOKEN_API void *PepType_GetSlot(PyTypeObject *type, int aSlot);
 
+// Runtime support for Python 3.13 stable ABI
+
+// Return dictionary of the global variables in the current execution frame
+LIBSHIBOKEN_API PyObject *PepEval_GetFrameGlobals();
+
+// Return a dictionary of the builtins in the current execution frame
+LIBSHIBOKEN_API PyObject *PepEval_GetFrameBuiltins();
+
+LIBSHIBOKEN_API int PepModule_AddType(PyObject *module, PyTypeObject *type);
+
+LIBSHIBOKEN_API int PepModule_Add(PyObject *module, const char *name, PyObject *value);
+
 /*****************************************************************************
  *
  * Module Initialization
index e01c4fc1b88e6bbed56ff034801dc23c48b9989c..d61551b3b7c5344f76eca1c7556a28342d0c42d7 100644 (file)
@@ -4,6 +4,9 @@
 #include "sbkbindingutils.h"
 
 #include "autodecref.h"
+#include "sbkstring.h"
+#include "sbkpep.h"
+#include "sbkstaticstrings_p.h"
 
 #include <algorithm>
 
@@ -73,4 +76,26 @@ bool parseConstructorKeywordArguments(PyObject *kwds,
     return true;
 }
 
+static bool isCompiledHelper()
+{
+    Shiboken::AutoDecRef globals(PepEval_GetFrameGlobals());
+    if (globals.isNull())
+        return false;
+
+    if (PyDict_GetItem(globals.object(), PyMagicName::compiled()) != nullptr)
+        return true;
+    globals.reset(nullptr);
+
+    // __compiled__ may not be set in initialization phases, check builtins
+    static PyObject *nuitkaDir = Shiboken::String::createStaticString("__nuitka_binary_exe");
+    Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+    return !builtins.isNull() && PyDict_GetItem(builtins.object(), nuitkaDir) != nullptr;
+}
+
+bool isCompiled()
+{
+    static const bool result = isCompiledHelper();
+    return result;
+}
+
 } // namespace Shiboken
index 4ed833dfa4d0d5f91464217acd4f644630fa8a0a..310fe342689f2ea89743f76d7d7037d168936590 100644 (file)
@@ -35,6 +35,9 @@ LIBSHIBOKEN_API bool
                                      const ArgumentNameIndexMapping *mapping, size_t size,
                                      Shiboken::AutoDecRef &errInfo, PyObject **pyArgs);
 
+/// Returns whether we are running in compiled mode (Nuitka).
+LIBSHIBOKEN_API bool isCompiled();
+
 } // namespace Shiboken
 
 #endif // SBK_BINDINGUTILS
index 7de1f03e69fd589d16ad1225522ca2920e914a06..52eb419a730ebc1553a29e2528209e7a8c218c96 100644 (file)
@@ -2,9 +2,25 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "sbkcontainer.h"
+#include "sbkpep.h"
 #include "sbkstaticstrings.h"
 #include "autodecref.h"
 
+// The functionality was moved to a source to reduce size
+// and remove PepType_GetSlot() usage from the public header.
+ShibokenContainer *ShibokenSequenceContainerPrivateBase::allocContainer(PyTypeObject *subtype)
+{
+    allocfunc allocFunc = reinterpret_cast<allocfunc>(PepType_GetSlot(subtype, Py_tp_alloc));
+    return reinterpret_cast<ShibokenContainer *>(allocFunc(subtype, 0));
+}
+
+void ShibokenSequenceContainerPrivateBase::freeSelf(PyObject *pySelf)
+{
+    auto freeFunc = reinterpret_cast<freefunc>(PepType_GetSlot(Py_TYPE(pySelf)->tp_base,
+                                                               Py_tp_free));
+    freeFunc(pySelf);
+}
+
 namespace Shiboken
 {
 bool isOpaqueContainer(PyObject *o)
index 8ad5aadc6fa3864c00be36d6fb91ba819470af82..083f994e409f3607fbd0518a61aba3703941ece8 100644 (file)
@@ -13,6 +13,8 @@
 #include <optional>
 #include <utility>
 
+// Opaque container helpers
+
 extern "C"
 {
 struct LIBSHIBOKEN_API ShibokenContainer
@@ -48,8 +50,35 @@ public:
     enum { value = sizeof(test<T>(nullptr)) == sizeof(YesType) };
 };
 
+// PYSIDE-3259 Handling of the std::vector<bool> optimization for providing
+// a pointer for the SbkConverter. Use const-ref for the standard case to
+// avoid copies and instantiate a bool in case of std::vector<bool>.
+template <typename T>
+struct ShibokenContainerStdVectorValueType
+{
+    using Type = const T &;
+};
+
+template <>
+struct ShibokenContainerStdVectorValueType<bool>
+{
+    using Type = bool;
+};
+
+class ShibokenSequenceContainerPrivateBase
+{
+public:
+    static constexpr const char *msgModifyConstContainer =
+        "Attempt to modify a constant container.";
+
+protected:
+    LIBSHIBOKEN_API static ShibokenContainer *allocContainer(PyTypeObject *subtype);
+    LIBSHIBOKEN_API static void freeSelf(PyObject *pySelf);
+};
+
+// Helper for sequence type containers
 template <class SequenceContainer>
-class ShibokenSequenceContainerPrivate // Helper for sequence type containers
+class ShibokenSequenceContainerPrivate : public ShibokenSequenceContainerPrivateBase
 {
 public:
     using value_type = typename SequenceContainer::value_type;
@@ -58,13 +87,10 @@ public:
     SequenceContainer *m_list{};
     bool m_ownsList = false;
     bool m_const = false;
-    static constexpr const char *msgModifyConstContainer =
-        "Attempt to modify a constant container.";
 
     static PyObject *tpNew(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */)
     {
-        allocfunc allocFunc = reinterpret_cast<allocfunc>(PepType_GetSlot(subtype, Py_tp_alloc));
-        auto *me = reinterpret_cast<ShibokenContainer *>(allocFunc(subtype, 0));
+        auto *me = allocContainer(subtype);
         auto *d = new ShibokenSequenceContainerPrivate;
         d->m_list = new SequenceContainer;
         d->m_ownsList = true;
@@ -91,9 +117,7 @@ public:
         if (d->m_ownsList)
             delete d->m_list;
         delete d;
-        auto freeFunc = reinterpret_cast<freefunc>(PepType_GetSlot(Py_TYPE(pySelf)->tp_base,
-                                                                   Py_tp_free));
-        freeFunc(self);
+        freeSelf(pySelf);
     }
 
     static Py_ssize_t sqLen(PyObject *self)
index b8634002cf76808ffc4b78c7b34979280e237409..9853e369d11ec1533b57d6e10a3c35447c45d9fd 100644 (file)
@@ -4,11 +4,12 @@
 #include "sbkconverter.h"
 #include "sbkconverter_p.h"
 #include "sbkarrayconverter_p.h"
-#include "sbkmodule.h"
+#include "sbkmodule_p.h"
 #include "basewrapper_p.h"
 #include "bindingmanager.h"
 #include "autodecref.h"
 #include "helper.h"
+#include "sbkpep.h"
 #include "voidptr.h"
 
 #include <string>
@@ -84,9 +85,9 @@ static void dumpPyTypeObject(std::ostream &str, PyTypeObject *t)
         str << "<None>";
         return;
     }
-    str << '"' << t->tp_name << '"';
+    str << '"' << PepType_GetFullyQualifiedNameStr(t) << '"';
     if (t->tp_base != nullptr && t->tp_base != &PyBaseObject_Type)
-        str << '(' << t->tp_base->tp_name << ')';
+        str << '(' << PepType_GetFullyQualifiedNameStr(t->tp_base) << ')';
 }
 
 static void dumpSbkConverter(std::ostream &str, const SbkConverter *c)
@@ -183,10 +184,11 @@ SbkConverter *createConverterObject(PyTypeObject *type,
     auto *converter = new SbkConverter;
     converter->pythonType = type;
     // PYSIDE-595: All types are heaptypes now, so provide reference.
-    Py_XINCREF(type);
+    Py_XINCREF(reinterpret_cast<PyObject *>(type));
 
     converter->pointerToPython = pointerToPythonFunc;
     converter->copyToPython = copyToPythonFunc;
+    converter->copyToPythonWithType = nullptr;
 
     if (toCppPointerCheckFunc && toCppPointerConvFunc)
         converter->toCppPointerConversion = std::make_pair(toCppPointerCheckFunc, toCppPointerConvFunc);
@@ -214,6 +216,13 @@ SbkConverter *createConverter(PyTypeObject *type, CppToPythonFunc toPythonFunc)
     return createConverterObject(type, nullptr, nullptr, nullptr, toPythonFunc);
 }
 
+SbkConverter *createConverter(PyTypeObject *type, CppToPythonWithTypeFunc toPythonFunc)
+{
+    auto *result = createConverterObject(type, nullptr, nullptr, nullptr, nullptr);
+    result->copyToPythonWithType = toPythonFunc;
+    return result;
+}
+
 void deleteConverter(SbkConverter *converter)
 {
     if (converter) {
@@ -293,8 +302,8 @@ PyObject *referenceToPython(const SbkConverter *converter, const void *cppIn)
 {
     assert(cppIn);
 
-    auto *pyOut = reinterpret_cast<PyObject *>(BindingManager::instance().retrieveWrapper(cppIn));
-    if (pyOut) {
+    if (auto *sbkOut = BindingManager::instance().retrieveWrapper(cppIn, converter->pythonType)) {
+        auto *pyOut = reinterpret_cast<PyObject *>(sbkOut);
         Py_INCREF(pyOut);
         return pyOut;
     }
@@ -310,12 +319,13 @@ static inline PyObject *CopyCppToPython(const SbkConverter *converter, const voi
 {
     if (!cppIn)
         Py_RETURN_NONE;
-    if (!converter->copyToPython) {
-        warning(PyExc_RuntimeWarning, 0, "CopyCppToPython(): SbkConverter::copyToPython is null for \"%s\".",
-                converter->pythonType->tp_name);
-        Py_RETURN_NONE;
-    }
-    return converter->copyToPython(cppIn);
+    if (converter->copyToPythonWithType != nullptr)
+        return converter->copyToPythonWithType(converter->pythonType, cppIn);
+    if (converter->copyToPython != nullptr)
+        return converter->copyToPython(cppIn);
+    warning(PyExc_RuntimeWarning, 0, "CopyCppToPython(): SbkConverter::copyToPython is null for \"%s\".",
+            converter->pythonType->tp_name);
+    Py_RETURN_NONE;
 }
 
 PyObject *copyToPython(PyTypeObject *type, const void *cppIn)
@@ -451,9 +461,13 @@ void nonePythonToCppNullPtr(PyObject *, void *cppOut)
 void *cppPointer(PyTypeObject *desiredType, SbkObject *pyIn)
 {
     assert(pyIn);
-    if (!ObjectType::checkType(desiredType))
+    if (!ObjectType::checkType(desiredType)) {
+        std::cerr << __FUNCTION__ << ": Conversion to non SbkObject type "
+                  << PepType_GetFullyQualifiedNameStr(desiredType)
+                  << " requested, falling back to pass-through.\n";
         return pyIn;
-    auto *inType = Py_TYPE(pyIn);
+    }
+    auto *inType = Shiboken::pyType(pyIn);
     if (ObjectType::hasCast(inType))
         return ObjectType::cast(inType, pyIn, desiredType);
     return Object::cppPointer(pyIn, desiredType);
@@ -485,8 +499,14 @@ static void _pythonToCppCopy(const SbkConverter *converter, PyObject *pyIn, void
     assert(pyIn);
     assert(cppOut);
     PythonToCppFunc toCpp = IsPythonToCppConvertible(converter, pyIn);
-    if (toCpp)
+    if (toCpp) {
         toCpp(pyIn, cppOut);
+    } else {
+        std::cerr << __FUNCTION__ << ": Cannot copy-convert " << pyIn;
+        if (pyIn)
+            std::cerr << " (" << PepType_GetFullyQualifiedNameStr(Py_TYPE(pyIn)) << ')';
+        std::cerr << " to C++.\n";
+    }
 }
 
 void pythonToCppCopy(PyTypeObject *type, PyObject *pyIn, void *cppOut)
@@ -575,7 +595,7 @@ SbkConverter *getConverter(const char *typeNameC)
         return it->second;
     // PYSIDE-2404: Did not find the name. Load the lazy classes
     //              which have this name and try again.
-    Shiboken::Module::loadLazyClassesWithName(getRealTypeName(typeName).c_str());
+    Shiboken::Module::loadLazyClassesWithNameStd(getRealTypeName(typeName));
     it = converters.find(typeName);
     if (it != converters.end())
         return it->second;
@@ -856,18 +876,23 @@ PyTypeObject *getPythonTypeObject(const char *typeName)
     return getPythonTypeObject(getConverter(typeName));
 }
 
+static bool hasCopyToPythonFunc(const SbkConverter *converter)
+{
+    return converter->copyToPython != nullptr || converter->copyToPythonWithType != nullptr;
+}
+
 bool pythonTypeIsValueType(const SbkConverter *converter)
 {
     // Unlikely to happen but for multi-inheritance SbkObjs
     // the converter is not defined, hence we need a default return.
     if (!converter)
         return false;
-    return converter->pointerToPython && converter->copyToPython;
+    return converter->pointerToPython && hasCopyToPythonFunc(converter);
 }
 
 bool pythonTypeIsObjectType(const SbkConverter *converter)
 {
-    return converter->pointerToPython && !converter->copyToPython;
+    return converter->pointerToPython && !hasCopyToPythonFunc(converter);
 }
 
 bool pythonTypeIsWrapperType(const SbkConverter *converter)
@@ -881,7 +906,7 @@ SpecificConverter::SpecificConverter(const char *typeName)
     m_converter = getConverter(typeName);
     if (!m_converter)
         return;
-    const auto len = strlen(typeName);
+    const auto len = std::strlen(typeName);
     char lastChar = typeName[len -1];
     if (lastChar == '&') {
         m_type = ReferenceConversion;
index a050844f207e9ef6849a1d4591b541ee4f9647c2..e774fa01587cdcd9eaed81f339f5ab1b7dc225eb 100644 (file)
@@ -45,6 +45,11 @@ struct SbkArrayConverter;
  */
 using CppToPythonFunc = PyObject *(*)(const void *);
 
+/** Same as CppToPythonFunc, but additionally receives the 'PyTypeObject *'.
+  * This is handy for some converters, namely enumeration converters or
+  * dynamic user-defined converters that invoke the type object. */
+using CppToPythonWithTypeFunc = PyObject *(*)(PyTypeObject *, const void *);
+
 /**
  *  This function converts a Python object to a C++ value, it may be
  *  a pointer, value, class, container or primitive type, passed via
@@ -126,6 +131,7 @@ LIBSHIBOKEN_API SbkConverter *createConverter(PyTypeObject *type,
  *  \returns            A new type converter.
  */
 LIBSHIBOKEN_API SbkConverter *createConverter(PyTypeObject *type, CppToPythonFunc toPythonFunc);
+LIBSHIBOKEN_API SbkConverter *createConverter(PyTypeObject *type, CppToPythonWithTypeFunc toPythonFunc);
 
 LIBSHIBOKEN_API void deleteConverter(SbkConverter *converter);
 
index b4ef51d44ef586f544d3ef923d002bc7fd46e76c..974b0e4b286550718e6fa2ad3cf111c3df05a051 100644 (file)
@@ -50,6 +50,10 @@ struct SbkConverter
      *  wrapper assigned for it.
      */
     CppToPythonFunc copyToPython;
+    /** Same as copyToPython, but additionally receives the 'PyTypeObject *'.
+      * Both functions are checked.
+      * FIXME PYSIDE 7: Add PyTypeObject parameter to CppToPythonFunc? */
+    CppToPythonWithTypeFunc copyToPythonWithType;
     /**
      *  This is a special case of a Python to C++ conversion. It returns
      *  the underlying C++ pointer of a Python wrapper passed as parameter
@@ -366,7 +370,7 @@ struct Primitive<bool> : OnePrimitive<bool>
     }
     static void toCpp(PyObject *pyIn, void *cppOut)
     {
-        *reinterpret_cast<bool *>(cppOut) = PyLong_AS_LONG(pyIn) != 0;
+        *reinterpret_cast<bool *>(cppOut) = PyLong_AsLong(pyIn) != 0;
     }
 };
 
index 1aec93f17f247e63bdcaf32c8513fcce20c13269..53f0de4271ab40896cfe8ff41fa86fb7ca0f7575 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "sbkcppstring.h"
 #include "autodecref.h"
+#include "sbkpep.h"
 
 namespace Shiboken::String
 {
index 212fcec861f4f0da1fa55a1a754f591687609f20..0013eb55252cf22563c95937e610c7703c827e4c 100644 (file)
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "sbkenum.h"
+#include "sbkpep.h"
 #include "sbkstring.h"
 #include "helper.h"
 #include "sbkstaticstrings.h"
@@ -67,7 +68,7 @@ PyTypeObject *getPyEnumMeta()
             return reinterpret_cast<PyTypeObject *>(PyEnumMeta);
         }
     }
-    Py_FatalError("Python module 'enum' not found");
+    Py_FatalError("libshiboken: Python module 'enum' not found");
     return nullptr;
 }
 
@@ -77,7 +78,7 @@ void init_enum()
     if (isInitialized)
         return;
     if (!(isInitialized || _init_enum()))
-        Py_FatalError("could not init enum");
+        Py_FatalError("libshiboken: could not init enum");
 
     // PYSIDE-1735: Determine whether we should use the old or the new enum implementation.
     static PyObject *option = PySys_GetObject("pyside6_option_python_enum");
@@ -224,7 +225,7 @@ bool checkType(PyTypeObject *pyTypeObj)
     init_enum();
 
     static PyTypeObject *meta = getPyEnumMeta();
-    return Py_TYPE(pyTypeObj) == meta;
+    return Py_TYPE(reinterpret_cast<PyObject *>(pyTypeObj)) == meta;
 }
 
 PyObject *getEnumItemFromValue(PyTypeObject *enumType, EnumValueType itemValue)
@@ -282,14 +283,14 @@ void setTypeConverter(PyTypeObject *type, SbkConverter *converter,
 
 static void setModuleAndQualnameOnType(PyObject *type, const char *fullName)
 {
-    const char *colon = strchr(fullName, ':');
+    const char *colon = std::strchr(fullName, ':');
     assert(colon);
     int package_level = atoi(fullName);
     const char *mod = colon + 1;
 
     const char *qual = mod;
     for (int idx = package_level; idx > 0; --idx) {
-        const char *dot = strchr(qual, '.');
+        const char *dot = std::strchr(qual, '.');
         if (!dot)
             break;
         qual = dot + 1;
@@ -306,7 +307,7 @@ static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
                                          const char *fullName,
                                          PyObject *pyEnumItems)
 {
-    const char *dot = strrchr(fullName, '.');
+    const char *dot = std::strrchr(fullName, '.');
     AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : fullName));
 
     static PyObject *enumName = String::createStaticString("IntEnum");
@@ -473,7 +474,7 @@ PyTypeObject *createPythonEnum(const char *fullName, PyObject *pyEnumItems,
         return nullptr;
     }
 
-    const char *dot = strrchr(fullName, '.');
+    const char *dot = std::strrchr(fullName, '.');
     AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : fullName));
     AutoDecRef callArgs(Py_BuildValue("(OO)", name.object(), pyEnumItems));
     auto *newType = PyObject_Call(PyEnumType, callArgs, callDict);
index 8dc3c639cb877e2a3b587e1760e3acd1d4b57501..33bcf69f9ce52d5ec503be2b604633158cb258a7 100644 (file)
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "sbkerrors.h"
+#include "sbkpep.h"
 #include "sbkstring.h"
 #include "helper.h"
 #include "gilstate.h"
@@ -129,13 +130,53 @@ static bool prependToExceptionMessage(PyObject *exc, const char *context)
     return true;
 }
 
-struct ErrorStore {
-    PyObject *type;
-    PyObject *exc;
-    PyObject *traceback;
+struct ErrorStore
+{
+    operator bool() const { return exc != nullptr; }
+
+    PyObject *exc = nullptr;
+#ifdef PEP_OLD_ERR_API
+    PyObject *traceback = nullptr;
+    PyObject *type = nullptr;
+#endif
 };
 
-static thread_local ErrorStore savedError{};
+static void fetchError(ErrorStore &s)
+{
+#ifdef PEP_OLD_ERR_API
+    PyErr_Fetch(&s.type, &s.exc, &s.traceback);
+#else
+    s.exc = PyErr_GetRaisedException();
+#endif
+}
+
+static void restoreError(ErrorStore &s)
+{
+#ifdef PEP_OLD_ERR_API
+    PyErr_Restore(s.type, s.exc, s.traceback);
+    s.type = s.exc = s.traceback = nullptr;
+#else
+    if (s.exc) {
+        PyErr_SetRaisedException(s.exc);
+        s.exc = nullptr;
+    } else {
+        PyErr_Clear();
+    }
+#endif
+}
+
+static void releaseError(ErrorStore &s)
+{
+    Py_XDECREF(s.exc);
+    s.exc = nullptr;
+#ifdef PEP_OLD_ERR_API
+    Py_XDECREF(s.type);
+    Py_XDECREF(s.traceback);
+    s.type = s.traceback = nullptr;
+#endif
+}
+
+static thread_local ErrorStore savedError;
 
 static bool hasPythonContext()
 {
@@ -148,7 +189,7 @@ void storeErrorOrPrint()
     // Therefore, we handle the error when we are error checking, anyway.
     // But we do that only when we know that an error handler can pick it up.
     if (hasPythonContext())
-        PyErr_Fetch(&savedError.type, &savedError.exc, &savedError.traceback);
+        fetchError(savedError);
     else
         PyErr_Print();
 }
@@ -158,7 +199,7 @@ void storeErrorOrPrint()
 static void storeErrorOrPrintWithContext(const char *context)
 {
     if (hasPythonContext()) {
-        PyErr_Fetch(&savedError.type, &savedError.exc, &savedError.traceback);
+        fetchError(savedError);
         prependToExceptionMessage(savedError.exc, context);
     } else {
         std::fputs(context, stderr);
@@ -175,13 +216,42 @@ void storePythonOverrideErrorOrPrint(const char *className, const char *funcName
 
 PyObject *occurred()
 {
-    if (savedError.type) {
-        PyErr_Restore(savedError.type, savedError.exc, savedError.traceback);
-        savedError.type = nullptr;
-    }
+    if (savedError)
+        restoreError(savedError);
     return PyErr_Occurred();
 }
 
+Stash::Stash() : m_store(std::make_unique<ErrorStore>())
+{
+    fetchError(*m_store);
+}
+
+Stash::~Stash()
+{
+    restore();
+}
+
+PyObject *Stash::getException() const
+{
+    return m_store ? m_store->exc : nullptr;
+}
+
+void Stash::restore()
+{
+    if (m_store) {
+        restoreError(*m_store);
+        m_store.reset();
+    }
+}
+
+void Stash::release()
+{
+    if (m_store) {
+        releaseError(*m_store);
+        m_store.reset();
+    }
+}
+
 } // namespace Errors
 
 namespace Warnings
index d7247ded4a54096c46e23a822182377c5f40e9fc..58576dc7b0d110c421f5696fcbf35ac5c2386f2b 100644 (file)
@@ -7,6 +7,8 @@
 #include "sbkpython.h"
 #include "shibokenmacros.h"
 
+#include <memory>
+
 /// Craving for C++20 and std::source_location::current()
 #if defined(_MSC_VER)
 #  define SBK_FUNC_INFO     __FUNCSIG__
@@ -35,6 +37,32 @@ public:
 namespace Errors
 {
 
+struct ErrorStore;
+
+/// Temporarily stash an error set in Python
+class Stash
+{
+public:
+    Stash(const Stash &) = delete;
+    Stash &operator=(const Stash &) = delete;
+    Stash(Stash &&) = delete;
+    Stash &operator=(Stash &&) = delete;
+
+    LIBSHIBOKEN_API Stash();
+    LIBSHIBOKEN_API ~Stash();
+
+    LIBSHIBOKEN_API operator bool() const { return getException() != nullptr; }
+    [[nodiscard]] LIBSHIBOKEN_API PyObject *getException() const;
+
+    /// Restore the stored error
+    LIBSHIBOKEN_API void restore();
+    /// Discard the stored error
+    LIBSHIBOKEN_API void release();
+
+private:
+    std::unique_ptr<ErrorStore> m_store;
+};
+
 LIBSHIBOKEN_API void setIndexOutOfBounds(Py_ssize_t value, Py_ssize_t minValue,
                                          Py_ssize_t maxValue);
 LIBSHIBOKEN_API void setInstantiateAbstractClass(const char *name);
index b778367a1684a1b26741352c1837d7e18dd8fb75..0b79ff3be769779d4baeabda35f0284382a47289 100644 (file)
@@ -6,6 +6,7 @@
 #include "autodecref.h"
 #include "pep384ext.h"
 #include "sbkenum.h"
+#include "sbkerrors.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
 #include "gilstate.h"
 
 #include <cctype>
+#include <iostream>
+#include <string_view>
+#include <vector>
 
 using namespace Shiboken;
 
+using StringViewVector = std::vector<std::string_view>;
+
+static StringViewVector splitStringView(std::string_view s, char delimiter)
+{
+    StringViewVector result;
+    const auto size = s.size();
+    for (std::string_view::size_type pos = 0; pos < size; ) {
+        auto nextPos = s.find(delimiter, pos);
+        if (nextPos == std::string_view::npos)
+            nextPos = size;
+        result.push_back(s.substr(pos, nextPos - pos));
+        pos = nextPos + 1;
+    }
+    return result;
+}
+
 extern "C"
 {
 
@@ -60,8 +80,8 @@ SelectableFeatureHook initSelectableFeature(SelectableFeatureHook func)
 void disassembleFrame(const char *marker)
 {
     Shiboken::GilState gil;
-    PyObject *error_type, *error_value, *error_traceback;
-    PyErr_Fetch(&error_type, &error_value, &error_traceback);
+
+    Shiboken::Errors::Stash errorStash;
     static PyObject *dismodule = PyImport_ImportModule("dis");
     static PyObject *disco = PyObject_GetAttrString(dismodule, "disco");
     static PyObject *const _f_lasti = Shiboken::String::createStaticString("f_lasti");
@@ -84,12 +104,11 @@ void disassembleFrame(const char *marker)
         fprintf(stdout, "%s END line=%ld %s\n\n", marker, line, fname);
     }
 #if PY_VERSION_HEX >= 0x030C0000 && !Py_LIMITED_API
-    if (error_type)
-        PyErr_DisplayException(error_value);
+    if (auto *exc = errorStash.getException())
+        PyErr_DisplayException(exc);
 #endif
     static PyObject *stdout_file = PySys_GetObject("stdout");
     ignore.reset(PyObject_CallMethod(stdout_file, "flush", nullptr));
-    PyErr_Restore(error_type, error_value, error_traceback);
 }
 
 // OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
@@ -133,7 +152,6 @@ static int const CALL_METHOD = 161;
 static bool currentOpcode_Is_CallMethNoArgs()
 {
     static const auto number = _PepRuntimeVersion();
-    static PyObject *flags = PySys_GetObject("flags");
     // We look into the currently active operation if we are going to call
     // a method with zero arguments.
     auto *frame = PyEval_GetFrame();
@@ -200,27 +218,40 @@ static bool currentOpcode_Is_CallMethNoArgs()
     return opcode2 == CALL_OpCode(number) && oparg2 == 0;
 }
 
+static inline PyObject *PyUnicode_fromStringView(std::string_view s)
+{
+    return PyUnicode_FromStringAndSize(s.data(), s.size());
+}
+
+static bool populateEnumDicts(PyObject *typeDict, PyObject *dict, const char *enumFlagInfo)
+{
+    StringViewVector parts = splitStringView(enumFlagInfo, ':');
+    if (parts.size() < 2)
+        return false;
+    AutoDecRef name(PyUnicode_fromStringView(parts[0]));
+    AutoDecRef typeName(PyUnicode_fromStringView(parts[1]));
+    if (parts.size() >= 3) {
+        AutoDecRef key(PyUnicode_fromStringView(parts[2]));
+        auto *value = name.object();
+        if (PyDict_SetItem(dict, key, value) != 0)
+            return false;
+    }
+    if (PyDict_SetItem(typeDict, name, typeName) != 0)
+        return false;
+    return true;
+}
+
 void initEnumFlagsDict(PyTypeObject *type)
 {
     // We create a dict for all flag enums that holds the original C++ name
     // and a dict that gives every enum/flag type name.
-    static PyObject *const split = Shiboken::String::createStaticString("split");
-    static PyObject *const colon = Shiboken::String::createStaticString(":");
-    auto sotp = PepType_SOTP(type);
+    auto *sotp = PepType_SOTP(type);
     auto **enumFlagInfo = sotp->enumFlagInfo;
     auto *dict = PyDict_New();
     auto *typeDict = PyDict_New();
     for (; *enumFlagInfo; ++enumFlagInfo) {
-        AutoDecRef line(PyUnicode_FromString(*enumFlagInfo));
-        AutoDecRef parts(PyObject_CallMethodObjArgs(line, split, colon, nullptr));
-        auto *name = PyList_GetItem(parts, 0);
-        if (PyList_Size(parts) == 3) {
-            auto *key = PyList_GetItem(parts, 2);
-            auto *value = name;
-            PyDict_SetItem(dict, key, value);
-        }
-        auto *typeName = PyList_GetItem(parts, 1);
-        PyDict_SetItem(typeDict, name, typeName);
+        if (!populateEnumDicts(typeDict, dict, *enumFlagInfo))
+            std::cerr << __FUNCTION__ << ": Invalid enum \"" << *enumFlagInfo << '\n';
     }
     sotp->enumFlagsDict = dict;
     sotp->enumTypeDict = typeDict;
@@ -366,15 +397,11 @@ PyObject *mangled_type_getattro(PyTypeObject *type, PyObject *name)
     }
 
     if (!ret && name != ignAttr1 && name != ignAttr2) {
-        PyObject *error_type{}, *error_value{}, *error_traceback{};
-        PyErr_Fetch(&error_type, &error_value, &error_traceback);
+        Shiboken::Errors::Stash errorsStash;
         ret = lookupUnqualifiedOrOldEnum(type, name);
         if (ret) {
-            Py_DECREF(error_type);
-            Py_XDECREF(error_value);
-            Py_XDECREF(error_traceback);
-        } else {
-            PyErr_Restore(error_type, error_value, error_traceback);
+            errorsStash.release();
+            return ret;
         }
     }
     return ret;
index 865978a14dd97a193efcebd07844e26580d729b9..c2d2009802f33d1a04f5b84d1bc18a840e4771e1 100644 (file)
@@ -8,6 +8,7 @@
 #include "sbkstring.h"
 #include "sbkcppstring.h"
 #include "sbkconverter_p.h"
+#include "sbkpep.h"
 
 #include <unordered_map>
 #include <unordered_set>
@@ -116,7 +117,7 @@ static void incarnateSubtypes(PyObject *module,
     }
 }
 
-static PyTypeObject *incarnateType(PyObject *module, const char *name,
+static PyTypeObject *incarnateType(PyObject *module, const std::string &name,
                                    NameToTypeFunctionMap &nameToFunc)
 {
     // - locate the name and retrieve the generating function
@@ -139,9 +140,8 @@ static PyTypeObject *incarnateType(PyObject *module, const char *name,
     initSelectableFeature(saveFeature);
 
     // - assign this object to the name in the module
-    auto *res = reinterpret_cast<PyObject *>(type);
-    Py_INCREF(res);
-    PyModule_AddObject(module, name, res);   // steals reference
+    Py_INCREF(reinterpret_cast<PyObject *>(type));
+    PepModule_AddType(module, type);   // steals reference
     // - remove the entry, if not by something cleared.
     if (!nameToFunc.empty())
         nameToFunc.erase(funcIter);
@@ -152,7 +152,7 @@ static PyTypeObject *incarnateType(PyObject *module, const char *name,
 // PYSIDE-2404: Make sure that the mentioned classes really exist.
 // Used in `Pyside::typeName`. Because the result will be cached by
 // the creation of the type(s), this is efficient.
-void loadLazyClassesWithName(const char *name)
+void loadLazyClassesWithNameStd(const std::string &name)
 {
     for (auto const & tableIter : moduleToFuncs) {
         auto nameToFunc = tableIter.second;
@@ -165,6 +165,11 @@ void loadLazyClassesWithName(const char *name)
     }
 }
 
+void loadLazyClassesWithName(const char *name)
+{
+    loadLazyClassesWithNameStd(std::string(name));
+}
+
 // PYSIDE-2404: Completely load all not yet loaded classes.
 //              This is needed to resolve a star import.
 // PYSIDE-2898: Use a name list to pick the toplevel types.
@@ -257,13 +262,6 @@ static PyMethodDef module_methods[] = {
     {nullptr, nullptr, 0, nullptr}
 };
 
-// Python 3.8 - 3.12
-static int const LOAD_CONST_312 = 100;
-static int const IMPORT_NAME_312 = 108;
-// Python 3.13
-static int const LOAD_CONST_313 = 83;
-static int const IMPORT_NAME_313 = 75;
-
 // OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
 // egrep '( LOAD_CONST | IMPORT_NAME )' opcode_ids.h
 
@@ -339,9 +337,7 @@ static bool isImportStar(PyObject *module)
 }
 
 // PYSIDE-2404: These modules produce ambiguous names which we cannot handle, yet.
-static std::unordered_set<std::string> dontLazyLoad{
-    "testbinding"
-};
+static std::unordered_set<std::string> dontLazyLoad;
 
 static const std::unordered_set<std::string> knownModules{
     "shiboken6.Shiboken",
@@ -423,18 +419,19 @@ void AddTypeCreationFunction(PyObject *module,
 }
 
 void AddTypeCreationFunction(PyObject *module,
-                             const char *containerName,
+                             const char *enclosingName,
                              TypeCreationFunction func,
-                             const char *namePath)
+                             const char *subTypeNamePath)
 {
     // - locate the module in the moduleTofuncs mapping
     auto tableIter = moduleToFuncs.find(module);
     assert(tableIter != moduleToFuncs.end());
     // - Assign the name/generating function tcStruct.
     auto &nameToFunc = tableIter->second;
-    auto nit = nameToFunc.find(containerName);
+    auto nit = nameToFunc.find(enclosingName);
 
     // - insert namePath into the subtype vector of the main type.
+    std::string namePath(subTypeNamePath);
     nit->second.subtypeNames.emplace_back(namePath);
     // - insert it also as its own entry.
     nit = nameToFunc.find(namePath);
@@ -490,21 +487,40 @@ static PyMethodDef lazy_methods[] = {
     {nullptr, nullptr, 0, nullptr}
 };
 
-PyObject *create(const char * /* modName */, void *moduleData)
+PyObject *createOnly(const char * /* moduleName */, PyModuleDef *moduleData)
+
+{
+    Shiboken::init();
+    auto *module = PyModule_Create(moduleData);
+    if (module == nullptr) {
+        PyErr_Print();
+        return nullptr;
+    }
+#ifdef Py_GIL_DISABLED
+    PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED);
+#endif
+    return module;
+}
+
+PyObject *create(const char *moduleName, PyModuleDef *moduleData)
+{
+    auto *module = createOnly(moduleName, moduleData);
+    if (module != nullptr)
+        exec(module);
+    return module;
+}
+
+void exec(PyObject *module)
 {
     static auto *sysModules = PyImport_GetModuleDict();
-    static auto *builtins = PyEval_GetBuiltins();
     static auto *partial = Pep_GetPartialFunction();
     static bool lazy_init{};
 
-    Shiboken::init();
-    auto *module = PyModule_Create(reinterpret_cast<PyModuleDef *>(moduleData));
-
     // Setup of a dir function for "missing" classes.
     auto *moduleDirTemplate = PyCFunction_NewEx(module_methods, nullptr, nullptr);
     // Turn this function into a bound object, so we have access to the module.
     auto *moduleDir = PyObject_CallFunctionObjArgs(partial, moduleDirTemplate, module, nullptr);
-    PyModule_AddObject(module, module_methods->ml_name, moduleDir);  // steals reference
+    PepModule_Add(module, module_methods->ml_name, moduleDir);  // steals reference
     // Insert an initial empty table for the module.
     NameToTypeFunctionMap empty;
     moduleToFuncs.insert(std::make_pair(module, empty));
@@ -518,10 +534,11 @@ PyObject *create(const char * /* modName */, void *moduleData)
         origModuleGetattro = PyModule_Type.tp_getattro;
         PyModule_Type.tp_getattro = PyModule_lazyGetAttro;
         // Add the lazy import redirection, keeping a reference.
-        origImportFunc = PyDict_GetItemString(builtins, "__import__");
+        Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+        origImportFunc = PyDict_GetItemString(builtins.object(), "__import__");
         Py_INCREF(origImportFunc);
         AutoDecRef func(PyCFunction_NewEx(lazy_methods, nullptr, nullptr));
-        PyDict_SetItemString(builtins, "__import__", func);
+        PyDict_SetItemString(builtins.object(), "__import__", func);
         lazy_init = true;
     }
     // PYSIDE-2404: Nuitka inserts some additional code in standalone mode
@@ -529,11 +546,10 @@ PyObject *create(const char * /* modName */, void *moduleData)
     //              that gets imported before the running import can call
     //              `_PyImport_FixupExtensionObject` which does the insertion
     //              into `sys.modules`. This can cause a race condition.
-    // Insert the module early into the module dict to prevend recursion.
+    // Insert the module early into the module dict to prevent recursion.
     PyDict_SetItemString(sysModules, PyModule_GetName(module), module);
     // Clear the non-existing name cache because we have a new module.
     Shiboken::Conversions::clearNegativeLazyCache();
-    return module;
 }
 
 void registerTypes(PyObject *module, TypeInitStruct *types)
@@ -577,7 +593,7 @@ bool replaceModuleDict(PyObject *module, PyObject *modClass, PyObject *dict)
     auto *modict = PyModule_GetDict(module);
     auto *modIntern = reinterpret_cast<StartOf_PyModuleObject *>(module);
     if (modict != modIntern->md_dict)
-        Py_FatalError("The layout of modules is incompatible");
+        Py_FatalError("libshiboken: The layout of modules is incompatible");
     auto *hold = modIntern->md_dict;
     modIntern->md_dict = dict;
     Py_INCREF(dict);
index ac77604b839f7ed51dac515a165575dcb75f3fbc..c095a985433829a277562ab001542478fba2a8b9 100644 (file)
@@ -37,12 +37,19 @@ LIBSHIBOKEN_API void resolveLazyClasses(PyObject *module);
 LIBSHIBOKEN_API PyObject *import(const char *moduleName);
 
 /**
- *  Creates a new Python module named \p moduleName using the information passed in \p moduleData.
- *  In fact, \p moduleData expects a "PyMethodDef *" object, but that's for Python 2. A "void*"
- *  was preferred to make this work with future Python 3 support.
+ *  Creates a new Python module named \p moduleName using the information passed in \p moduleData
+ *  and calls exec() on it.
  *  \returns a newly created module.
  */
-LIBSHIBOKEN_API PyObject *create(const char *moduleName, void *moduleData);
+[[deprecated]] LIBSHIBOKEN_API PyObject *create(const char *moduleName, PyModuleDef *moduleData);
+
+/// Creates a new Python module named \p moduleName using the information passed in \p moduleData.
+/// exec() is not called (Support for Nuitka).
+/// \returns a newly created module.
+LIBSHIBOKEN_API PyObject *createOnly(const char *moduleName, PyModuleDef *moduleData);
+
+/// Executes a module (multi-phase initialization helper)
+LIBSHIBOKEN_API void exec(PyObject *module);
 
 using TypeCreationFunction = PyTypeObject *(*)(PyObject *module);
 
@@ -52,9 +59,9 @@ LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module,
                                              TypeCreationFunction func);
 
 LIBSHIBOKEN_API void AddTypeCreationFunction(PyObject *module,
-                                             const char *name,
+                                             const char *enclosingName,
                                              TypeCreationFunction func,
-                                             const char *containerName);
+                                             const char *subTypeNamePath);
 
 /**
  *  Registers the list of types created by \p module.
diff --git a/sources/shiboken6/libshiboken/sbkmodule_p.h b/sources/shiboken6/libshiboken/sbkmodule_p.h
new file mode 100644 (file)
index 0000000..a9cfa8f
--- /dev/null
@@ -0,0 +1,16 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef SBK_MODULE_P_H
+#define SBK_MODULE_P_H
+
+#include <string>
+
+namespace Shiboken::Module {
+
+/// PYSIDE-2404: Make sure that mentioned classes really exist.
+void loadLazyClassesWithNameStd(const std::string &name);
+
+} // namespace Shiboken::Module
+
+#endif // SBK_MODULE_H
diff --git a/sources/shiboken6/libshiboken/sbkpep.h b/sources/shiboken6/libshiboken/sbkpep.h
new file mode 100644 (file)
index 0000000..5d07fbf
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef SBKPEP_H
+#define SBKPEP_H
+
+#include "sbkversion.h"
+#include "sbkpython.h"
+#include "pep384impl.h"
+
+#endif // SBKPEP_H
diff --git a/sources/shiboken6/libshiboken/sbkpepbuffer.h b/sources/shiboken6/libshiboken/sbkpepbuffer.h
new file mode 100644 (file)
index 0000000..f92647c
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef SBKPEPBUFFER_H
+#define SBKPEPBUFFER_H
+
+#include "bufferprocs_py37.h"
+
+// FIXME: Move back to sbktypefactory.h once Py_LIMITED_API >= 3.11
+extern "C"
+{
+LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec,
+                                                     PyObject *bases,
+                                                     PyTypeObject *meta,
+                                                     int dictoffset,
+                                                     int weaklistoffset,
+                                                     PyBufferProcs *bufferprocs);
+} // extern "C"
+
+// FIXME: Move back to helper.h once Py_LIMITED_API >= 3.11
+namespace Shiboken
+{
+struct LIBSHIBOKEN_API debugPyBuffer
+{
+    explicit debugPyBuffer(const Py_buffer &b);
+
+    const Py_buffer &m_buffer;
+};
+
+} // namespace Shiboken
+
+#endif // SBKBUFFER_H
index 4914bec4d20a2386138455ab030b88148ab82764..b001d2735ede2f26d3335cca7ac7364754bf72bb 100644 (file)
@@ -4,8 +4,6 @@
 #ifndef SBKPYTHON_H
 #define SBKPYTHON_H
 
-#include "sbkversion.h"
-
 // PYSIDE-2701: This definition is needed for all Python formats with "#".
 #define PY_SSIZE_T_CLEAN
 
@@ -21,10 +19,6 @@ extern "C" {
 }
 
 #  include <structmember.h>
-// Now we have the usual variables from Python.h .
-#  include "shibokenmacros.h"
-// "pep384impl.h" may nowhere be included but in this file.
-#  include "pep384impl.h"
 #  pragma pop_macro("slots")
 
 #else
@@ -34,10 +28,6 @@ extern "C" {
 }
 
 #  include <structmember.h>
-// Now we have the usual variables from Python.h .
-#  include "shibokenmacros.h"
-// "pep384impl.h" may nowhere be included but in this file.
-#  include "pep384impl.h"
 #endif
 
 // In Python 3, Py_TPFLAGS_DEFAULT contains Py_TPFLAGS_HAVE_VERSION_TAG,
index ee28f7db88f1c2be71f677f4ea19f4d0109b47f0..b8cb84c750acb77967cebb167630d8ce5642ccb3 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "sbksmartpointer.h"
 #include "sbkstring.h"
+#include "sbkpep.h"
 #include "autodecref.h"
 
 #include <unordered_set>
index 023de0ea49ee63a3a32973e43e51a1a78dcb175f..7d3890c32814058850c227b30ef27c7c9a23d3f1 100644 (file)
@@ -69,6 +69,7 @@ STATIC_STRING_IMPL(rrshift, "__rrshift__")
 STATIC_STRING_IMPL(base, "__base__")
 STATIC_STRING_IMPL(bases, "__bases__")
 STATIC_STRING_IMPL(builtins, "__builtins__")
+STATIC_STRING_IMPL(compiled, "__compiled__");
 STATIC_STRING_IMPL(dictoffset, "__dictoffset__")
 STATIC_STRING_IMPL(func, "__func__")
 STATIC_STRING_IMPL(func_kind, "__func_kind__")
index 2a337bf7e10d7ce54c51e7472b579aa92f20fc5c..8813f4ae0c5311aaf0c3e02d544ab2547df306ee 100644 (file)
@@ -23,7 +23,7 @@ namespace PyMagicName
 PyObject *base();
 PyObject *bases();
 PyObject *builtins();
-PyObject *code();
+PyObject *compiled();
 PyObject *dictoffset();
 PyObject *func_kind();
 PyObject *iter();
index d2570f48e7d8e78ef7bdeff5cdfe00071335605e..5ef6ebb79fc5b7f42b9392d31b4f5374e4301ac1 100644 (file)
@@ -2,10 +2,14 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "sbkstring.h"
+#include "sbkpep.h"
 #include "sbkenum.h"
 #include "sbkstaticstrings_p.h"
 #include "autodecref.h"
 
+#include <cstring>
+#include <cctype>
+
 namespace Shiboken::String
 {
 
@@ -27,7 +31,7 @@ static PyObject *initPathLike()
     if (osmodule == nullptr
         || (PathLike = PyObject_GetAttrString(osmodule, "PathLike")) == nullptr) {
         PyErr_Print();
-        Py_FatalError("cannot import os.PathLike");
+        Py_FatalError("libshiboken: cannot import os.PathLike");
     }
     return PathLike;
 }
@@ -209,20 +213,20 @@ PyObject *getSnakeCaseName(const char *name, bool lower)
      * unchanged since that are the special OpenGL functions.
      */
     if (!lower
-        || strlen(name) < 3
-        || (name[0] == 'g' && name[1] == 'l' && isupper(name[2])))
+        || std::strlen(name) < 3
+        || (name[0] == 'g' && name[1] == 'l' && std::isupper(name[2])))
         return createStaticString(name);
 
     char new_name[200 + 1] = {};
     const char *p = name;
     char *q = new_name;
     for (; *p && q - new_name < 200; ++p, ++q) {
-        if (isupper(*p)) {
-            if (p != name && isupper(*(p - 1)))
+        if (std::isupper(*p)) {
+            if (p != name && std::isupper(*(p - 1)))
                 return createStaticString(name);
             *q = '_';
             ++q;
-            *q = tolower(*p);
+            *q = std::tolower(*p);
         }
         else {
             *q = *p;
index f8d8b910448769f79cfc60b9e4eb44929f6975c7..1a5796c8ff300ba93e70b29ca1a43fb58dbd398d 100644 (file)
@@ -2,7 +2,15 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "sbktypefactory.h"
-#include "shiboken.h"
+#include "autodecref.h"
+#include "sbkpep.h"
+#include "sbkpepbuffer.h"
+#include "sbkstring.h"
+#include "sbkstaticstrings.h"
+
+#include <cstring>
+#include <iostream>
+#include <utility>
 
 extern "C"
 {
@@ -64,10 +72,18 @@ static PyObject *_PyType_FromSpecWithBasesHack(PyType_Spec *spec,
         }
 
         for (Py_ssize_t idx = 0, n = PyTuple_Size(bases); idx < n; ++idx) {
-            PyTypeObject *base = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(bases, idx));
-            PyTypeObject *meta = Py_TYPE(base);
+            PyObject *obBase = PyTuple_GetItem(bases, idx);
+            auto *base = reinterpret_cast<PyTypeObject *>(obBase);
+            PyTypeObject *meta = Py_TYPE(obBase);
             if (meta->tp_new != PyType_Type.tp_new) {
                 // make sure there is no second meta class
+                if (keepMeta != nullptr) {
+                    std::cerr << "Warning: " << __FUNCTION__
+                        << ": multiple meta classes found for " << spec->name << " at "
+                        << idx << ": " << PepType_GetFullyQualifiedNameStr(base)
+                        << " in addition to "
+                        << PepType_GetFullyQualifiedNameStr(keepMeta) << '\n';
+                }
                 assert(keepMeta == nullptr);
                 keepMeta = meta;
                 keepNew = meta->tp_new;
@@ -111,7 +127,7 @@ PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec,
     //    __name__     : "subclass"
 
     PyType_Spec new_spec = *spec;
-    const char *colon = strchr(spec->name, ':');
+    const char *colon = std::strchr(spec->name, ':');
     assert(colon);
     int package_level = atoi(spec->name);
     const char *mod = new_spec.name = colon + 1;
@@ -122,7 +138,7 @@ PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec,
 
     const char *qual = mod;
     for (int idx = package_level; idx > 0; --idx) {
-        const char *dot = strchr(qual, '.');
+        const char *dot = std::strchr(qual, '.');
         if (!dot)
             break;
         qual = dot + 1;
@@ -134,11 +150,10 @@ PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec,
     auto *type = reinterpret_cast<PyTypeObject *>(obType);
 
     if (meta) {
-        PyTypeObject *hold = Py_TYPE(type);
-        obType->ob_type = meta;
-        Py_INCREF(Py_TYPE(type));
+        PyTypeObject *hold = std::exchange(obType->ob_type, meta);
+        Py_INCREF(reinterpret_cast<PyObject *>(Py_TYPE(obType)));
         if (hold->tp_flags & Py_TPFLAGS_HEAPTYPE)
-            Py_DECREF(hold);
+            Py_DECREF(reinterpret_cast<PyObject *>(hold));
     }
 
     if (dictoffset)
@@ -296,7 +311,7 @@ _PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
     }
 
     /* Set the type name and qualname */
-    s = strrchr(const_cast<char *>(spec->name), '.');
+    s = std::strrchr(const_cast<char *>(spec->name), '.');
     if (s == nullptr)
         s = (char*)spec->name;
     else
@@ -364,7 +379,7 @@ _PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
         if (slot->slot == Py_tp_doc) {
             const char *old_doc = reinterpret_cast<char *>(slot->pfunc);
             //_PyType_DocWithoutSignature(type->tp_name, slot->pfunc);
-            size_t len = strlen(old_doc)+1;
+            size_t len = std::strlen(old_doc)+1;
             char *tp_doc = reinterpret_cast<char *>(PyObject_MALLOC(len));
             if (tp_doc == nullptr) {
                 type->tp_doc = nullptr;
index 81cb32d41bd3403da1ddfba36cee7fdba37f6e4d..ad3d3c269b73cb427cc9a0c43f2229e7fe718458 100644 (file)
@@ -4,7 +4,8 @@
 #ifndef SBKTYPEFACTORY_H
 #define SBKTYPEFACTORY_H
 
-#include "sbkpython.h"
+#include "sbkpepbuffer.h"
+#include "shibokenmacros.h"
 
 extern "C"
 {
@@ -14,12 +15,6 @@ LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpec(PyType_Spec *);
 LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpecWithMeta(PyType_Spec *, PyTypeObject *);
 LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpecWithBases(PyType_Spec *, PyObject *);
 LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpecBasesMeta(PyType_Spec *, PyObject *, PyTypeObject *);
-LIBSHIBOKEN_API PyTypeObject *SbkType_FromSpec_BMDWB(PyType_Spec *spec,
-                                                     PyObject *bases,
-                                                     PyTypeObject *meta,
-                                                     int dictoffset,
-                                                     int weaklistoffset,
-                                                     PyBufferProcs *bufferprocs);
 
 } //extern "C"
 
index d046138952efc497d2fa65b8077405fd1d3ca3b3..f701506a7b6f9ffa5208012986423db71de8ed26 100644 (file)
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 
 #include "shibokenbuffer.h"
+#include "sbkpepbuffer.h"
 #include <cstdlib>
 #include <cstring>
 
index c3dee51adeab435b89b75af09e1ceaa2668e6cf3..593ac50451c86a522e2710aae3e3789db23cd6ea 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "basewrapper.h"
 #include "autodecref.h"
+#include "sbkpep.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
@@ -26,6 +27,7 @@
 #include <structmember.h>
 
 #include <algorithm>
+#include <cstring>
 
 using namespace Shiboken;
 
@@ -68,7 +70,7 @@ PyObject *GetClassOrModOf(PyObject *ob)
         return _get_class_of_descr(ob);
     if (Py_TYPE(ob) == &PyWrapperDescr_Type)
         return _get_class_of_descr(ob);
-    Py_FatalError("unexpected type in GetClassOrModOf");
+    Py_FatalError("libshiboken: unexpected type in GetClassOrModOf");
     return nullptr;
 }
 
@@ -89,7 +91,7 @@ PyObject *GetTypeKey(PyObject *ob)
     }
     AutoDecRef class_name(PyObject_GetAttr(ob, PyMagicName::qualname()));
     if (class_name.isNull()) {
-        Py_FatalError("Signature: missing class name in GetTypeKey");
+        Py_FatalError("libshiboken: missing class name in GetTypeKey");
         return nullptr;
     }
     return Py_BuildValue("(OO)", module_name.object(), class_name.object());
@@ -302,32 +304,35 @@ static PyObject *feature_import(PyObject * /* self */, PyObject *args, PyObject
         return ret;
     // feature_import did not handle it, so call the normal import.
     Py_DECREF(ret);
-    static PyObject *builtins = PyEval_GetBuiltins();
-    PyObject *origImportFunc = PyDict_GetItemString(builtins, "__orig_import__");
+    Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+    PyObject *origImportFunc = PyDict_GetItemString(builtins.object(), "__orig_import__");
     if (origImportFunc == nullptr) {
-        Py_FatalError("builtins has no \"__orig_import__\" function");
+        Py_FatalError("libshiboken: builtins has no \"__orig_import__\" function");
     }
-    // PYSIDE-3054: Instead of just calling the original import, we temporarily
-    //              reset the whole import function to the previous version.
-    //              This prevents unforeseen recursions like in settrace.
-    PyObject *featureImportFunc = PyDict_GetItemString(builtins, "__import__");
-    Py_INCREF(origImportFunc);
-    Py_INCREF(featureImportFunc);
-    PyDict_SetItemString(builtins, "__import__", origImportFunc);
     ret = PyObject_Call(origImportFunc, args, kwds);
     if (ret) {
+        // PYSIDE-3054: Instead of just calling the original import, we temporarily
+        //              reset the whole import function to the previous version.
+        //              This prevents unforeseen recursions like in settrace.
+        PyObject *featureImportFunc = PyDict_GetItemString(builtins.object(), "__import__");
+        Py_INCREF(origImportFunc);
+        Py_INCREF(featureImportFunc);
+        PyDict_SetItemString(builtins.object(), "__import__", origImportFunc);
+
         // PYSIDE-2029: Intercept after the import to search for PySide usage.
         PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func,
                                                       ret, nullptr);
         Py_XDECREF(post);
+
+        PyDict_SetItemString(builtins.object(), "__import__", featureImportFunc);
+        Py_DECREF(origImportFunc);
+        Py_DECREF(featureImportFunc);
+
         if (post == nullptr) {
             Py_DECREF(ret);
             ret = nullptr;
         }
     }
-    PyDict_SetItemString(builtins, "__import__", featureImportFunc);
-    Py_DECREF(origImportFunc);
-    Py_DECREF(featureImportFunc);
     return ret;
 }
 
@@ -535,7 +540,7 @@ static int _finishSignaturesCommon(PyObject *module)
     // the shiboken module (or a test module).
     [[maybe_unused]] const char *name = PyModule_GetName(module);
     if (pyside_globals->finish_import_func == nullptr) {
-        assert(strncmp(name, "PySide6.", 8) != 0);
+        assert(std::strncmp(name, "PySide6.", 8) != 0);
         return 0;
     }
     // Call a Python function which has to finish something as well.
@@ -685,8 +690,8 @@ static PyObject *adjustFuncName(const char *func_name)
     static PyObject *ns = PyModule_GetDict(mapping);
 
     char _path[200 + 1] = {};
-    const char *_name = strrchr(func_name, '.');
-    strncat(_path, func_name, _name - func_name);
+    const char *_name = std::strrchr(func_name, '.');
+    std::strncat(_path, func_name, _name - func_name);
     ++_name;
 
     // This is a very cheap call into `mapping.py`.
@@ -768,7 +773,7 @@ void SetError_Argument(PyObject *args, const char *func_name, PyObject *info)
     AutoDecRef new_func_name(adjustFuncName(func_name));
     if (new_func_name.isNull()) {
         PyErr_Print();
-        Py_FatalError("seterror_argument failed to call update_mapping");
+        Py_FatalError("libshiboken: seterror_argument failed to call update_mapping");
     }
     if (info == nullptr)
         info = Py_None;
@@ -776,13 +781,13 @@ void SetError_Argument(PyObject *args, const char *func_name, PyObject *info)
                                                 args, new_func_name.object(), info, nullptr));
     if (res.isNull()) {
         PyErr_Print();
-        Py_FatalError("seterror_argument did not receive a result");
+        Py_FatalError("libshiboken: seterror_argument did not receive a result");
     }
     PyObject *err{};
     PyObject *msg{};
     if (!PyArg_UnpackTuple(res, func_name, 2, 2, &err, &msg)) {
         PyErr_Print();
-        Py_FatalError("unexpected failure in seterror_argument");
+        Py_FatalError("libshiboken: unexpected failure in seterror_argument");
     }
     PyErr_SetObject(err, msg);
 }
index 3ce9e21e379a23b1cab6939722ac555612b1fd4e..b9ce2944ff8712c3c1f73abaf8a8fd8c5fbabf87 100644 (file)
 //             Shiboken.ObjectType and Shiboken.EnumMeta have new getsets, instead.
 
 #include "autodecref.h"
+#include "sbkpep.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
 
 #include "signature_p.h"
 
+#include <cstring>
+
 using namespace Shiboken;
 
 extern "C" {
@@ -79,7 +82,7 @@ PyObject *pyside_md_get___signature__(PyObject *ob_md, PyObject *modifier)
     if (func.object() == Py_None)
         Py_RETURN_NONE;
     if (func.isNull())
-        Py_FatalError("missing mapping in MethodDescriptor");
+        Py_FatalError("libshiboken: missing mapping in MethodDescriptor");
     return pyside_cf_get___signature__(func, modifier);
 }
 
@@ -123,11 +126,11 @@ static PyObject *handle_doc(PyObject *ob, PyObject *old_descr)
     bool isModule = PyModule_Check(ob_type_mod.object());
     const char *name = isModule
         ? PyModule_GetName(ob_type_mod.object())
-        : reinterpret_cast<PyTypeObject *>(ob_type_mod.object())->tp_name;
+        : PepType_GetFullyQualifiedNameStr(reinterpret_cast<PyTypeObject *>(ob_type_mod.object()));
     PyObject *res{};
 
     if (handle_doc_in_progress || name == nullptr
-        || (isModule && strncmp(name, "PySide6.", 8) != 0)) {
+        || (isModule && std::strncmp(name, "PySide6.", 8) != 0)) {
         res = PyObject_CallMethodObjArgs(old_descr, PyMagicName::get(), ob, nullptr);
     } else {
         handle_doc_in_progress++;
index 0a08309cc01431e7c7f4e1b358ca4a756faf457f..b62708041387fd28d53e53d3994ff9fb4872b153 100644 (file)
@@ -9,6 +9,7 @@
 //
 
 #include "autodecref.h"
+#include "sbkpep.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
@@ -16,6 +17,8 @@
 
 #include "signature_p.h"
 
+#include <cstring>
+
 using namespace Shiboken;
 
 extern "C" {
@@ -54,9 +57,12 @@ static safe_globals_struc *init_phase_1()
         AutoDecRef bytes(PyBytes_FromStringAndSize(bytes_cast, sizeof(PySide_SignatureLoader)));
         if (bytes.isNull())
             break;
+
+        AutoDecRef builtins;
 #if defined(Py_LIMITED_API) || defined(SHIBOKEN_NO_EMBEDDING_PYC)
-        PyObject *builtins = PyEval_GetBuiltins();
-        PyObject *compile = PyDict_GetItem(builtins, PyName::compile());
+        builtins.reset(PepEval_GetFrameBuiltins());
+        PyObject *compile = PyDict_GetItem(builtins.object(), PyName::compile());
+        builtins.reset(nullptr);
         if (compile == nullptr)
             break;
         AutoDecRef code_obj(PyObject_CallFunction(compile, "Oss",
@@ -72,8 +78,10 @@ static safe_globals_struc *init_phase_1()
             break;
         // Initialize the module
         PyObject *mdict = PyModule_GetDict(p->helper_module);
-        if (PyDict_SetItem(mdict, PyMagicName::builtins(), PyEval_GetBuiltins()) < 0)
+        builtins.reset(PepEval_GetFrameBuiltins());
+        if (PyDict_SetItem(mdict, PyMagicName::builtins(), builtins.object()) < 0)
             break;
+        builtins.reset(nullptr);
 
         /*********************************************************************
          *
@@ -123,7 +131,7 @@ static safe_globals_struc *init_phase_1()
     } while (false);
 
     PyErr_Print();
-    Py_FatalError("could not initialize part 1");
+    Py_FatalError("libshiboken/signature: could not initialize part 1");
     return nullptr;
 }
 
@@ -139,8 +147,9 @@ static int init_phase_2(safe_globals_struc *p, PyMethodDef *methods)
             Py_DECREF(v);
         }
         // The first entry is __feature_import__, add documentation.
-        PyObject *builtins = PyEval_GetBuiltins();
-        PyObject *imp_func = PyDict_GetItemString(builtins, "__import__");
+        Shiboken::AutoDecRef builtins(PepEval_GetFrameBuiltins());
+        PyObject *imp_func = PyDict_GetItemString(builtins.object(), "__import__");
+        builtins.reset(nullptr);
         PyObject *imp_doc = PyObject_GetAttrString(imp_func, "__doc__");
         signature_methods[0].ml_doc = String::toCString(imp_doc);
 
@@ -200,7 +209,7 @@ static int init_phase_2(safe_globals_struc *p, PyMethodDef *methods)
     } while (0);
 
     PyErr_Print();
-    Py_FatalError("could not initialize part 2");
+    Py_FatalError("libshiboken/signature: could not initialize part 2");
     return -1;
 }
 
@@ -247,7 +256,7 @@ void init_shibokensupport_module(void)
 #ifndef _WIN32
         // We enable the stack trace in CI, only.
         const char *testEnv = getenv("QTEST_ENVIRONMENT");
-        if (testEnv && strstr(testEnv, "ci"))
+        if (testEnv && std::strstr(testEnv, "ci"))
             signal(SIGSEGV, handler);   // install our handler
 #endif // _WIN32
 
index 3ecddbf0125bc2cbb4d7f097ea49c2c07bc37651..0b9c5cba0f4021a7c9a9794e4dccd4124113ca4b 100644 (file)
@@ -11,6 +11,7 @@
 //
 
 #include "autodecref.h"
+#include "sbkpep.h"
 #include "sbkstring.h"
 #include "sbkstaticstrings.h"
 #include "sbkstaticstrings_p.h"
@@ -32,7 +33,7 @@ static int _fixup_getset(PyTypeObject *type, const char *name, PyGetSetDef *new_
     PyGetSetDef *gsp = type->tp_getset;
     if (gsp != nullptr) {
         for (; gsp->name != nullptr; gsp++) {
-            if (strcmp(gsp->name, name) == 0) {
+            if (std::strcmp(gsp->name, name) == 0) {
                 new_gsp->set = gsp->set;
                 new_gsp->doc = gsp->doc;
                 new_gsp->closure = gsp->closure;
@@ -43,7 +44,7 @@ static int _fixup_getset(PyTypeObject *type, const char *name, PyGetSetDef *new_
     PyMemberDef *md = type->tp_members;
     if (md != nullptr)
         for (; md->name != nullptr; md++)
-            if (strcmp(md->name, name) == 0)
+            if (std::strcmp(md->name, name) == 0)
                 return 1;
     return 0;
 }
@@ -64,7 +65,7 @@ int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp, PyObject **doc_descr)
         PyObject *have_descr = PyDict_GetItemString(dict, gsp->name);
         if (have_descr != nullptr) {
             Py_INCREF(have_descr);
-            if (strcmp(gsp->name, "__doc__") == 0)
+            if (std::strcmp(gsp->name, "__doc__") == 0)
                 *doc_descr = have_descr;
             else
                 assert(false);
@@ -93,7 +94,7 @@ static PyObject *get_funcname(PyObject *ob)
     PyObject *func_name = PyObject_GetAttr(func, PyMagicName::name());
     Py_DECREF(func);
     if (func_name == nullptr)
-        Py_FatalError("unexpected name problem in compute_name_key");
+        Py_FatalError("libshiboken: unexpected name problem in compute_name_key");
     return func_name;
 }
 
@@ -321,7 +322,7 @@ int _build_func_to_type(PyObject *obtype)
 
     // PYSIDE-2404: Get the original dict for late initialization.
     //              The dict might have been switched before signature init.
-    static const auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
+    static auto *pyTypeType_tp_dict = PepType_GetDict(&PyType_Type);
     if (Py_TYPE(dict) != Py_TYPE(pyTypeType_tp_dict)) {
         tpDict.reset(PyObject_GetAttr(dict, PyName::orig_dict()));
         dict = tpDict.object();
index ce85d4946c018287d521c6f8369e160582d0096a..5f251afa3903a5cff8b3fa9088575a4ab1966638 100644 (file)
@@ -187,11 +187,9 @@ static const char falseString[] = "False" ;
 
 PyObject *SbkVoidPtrObject_repr(PyObject *v)
 {
-
-
     auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(v);
     PyObject *s = PyUnicode_FromFormat("%s(%p, %zd, %s)",
-                           Py_TYPE(sbkObject)->tp_name,
+                           Py_TYPE(v)->tp_name,
                            sbkObject->cptr,
                            sbkObject->size,
                            sbkObject->isWritable ? trueString : falseString);
@@ -203,7 +201,7 @@ PyObject *SbkVoidPtrObject_str(PyObject *v)
 {
     auto *sbkObject = reinterpret_cast<SbkVoidPtrObject *>(v);
     PyObject *s = PyUnicode_FromFormat("%s(Address %p, Size %zd, isWritable %s)",
-                           Py_TYPE(sbkObject)->tp_name,
+                           Py_TYPE(v)->tp_name,
                            sbkObject->cptr,
                            sbkObject->size,
                            sbkObject->isWritable ? trueString : falseString);
@@ -299,7 +297,7 @@ static int voidPointerInitialized = false;
 void init()
 {
     if (PyType_Ready(SbkVoidPtr_TypeF()) < 0)
-        Py_FatalError("[libshiboken] Failed to initialize Shiboken.VoidPtr type.");
+        Py_FatalError("libshiboken: Failed to initialize Shiboken.VoidPtr type.");
     else
         voidPointerInitialized = true;
 }
@@ -307,9 +305,10 @@ void init()
 void addVoidPtrToModule(PyObject *module)
 {
     if (voidPointerInitialized) {
-        Py_INCREF(SbkVoidPtr_TypeF());
-        PyModule_AddObject(module, PepType_GetNameStr(SbkVoidPtr_TypeF()),
-                           reinterpret_cast<PyObject *>(SbkVoidPtr_TypeF()));
+        auto *type = SbkVoidPtr_TypeF();
+        auto *obType = reinterpret_cast<PyObject *>(type);
+        Py_INCREF(obType);
+        PepModule_AddType(module, type);
     }
 }
 
index 8eea431c3bbfd3444ec7a61dfed9bc02ddeba31f..98335a98484054be4a1a049e87ab6e2fbd47aed8 100644 (file)
@@ -372,6 +372,14 @@ def create_signature_union(props, key):
         param = inspect.Parameter(name, kind, annotation=ann, default=default)
         params.append(param)
 
+    # Find the index of variadic positional parameter, if any
+    # And update the parameter kind that comes after
+    idx = next((i for i, p in enumerate(params) if p.kind == _VAR_POSITIONAL), None)
+    if idx is not None:
+        for i, p in enumerate(params):
+            if i > idx and p.kind != _VAR_KEYWORD:
+                params[i] = p.replace(kind=_KEYWORD_ONLY)
+
     ret_anno = annotations.get('return', _empty)
     if ret_anno is not _empty and props["fullname"] in missing_optional_return:
         ret_anno = typing.Union[ret_anno]
index 6f86df8c814e813badc0bb098f3d25875ffcf71d..b31b161a30436258a74525199bf5a3c41505da2e 100644 (file)
@@ -53,6 +53,12 @@ def is_inconsistent_overload(signatures):
     return count != 0 and count != len(signatures)
 
 
+def is_relevant_type(thing):
+    t = str(type(thing))
+    return (("PySide" in t or "getset_descriptor" in t)
+            and "QMetaObject" not in t)
+
+
 class ExactEnumerator:
     """
     ExactEnumerator enumerates all signatures in a module as they are.
@@ -178,7 +184,9 @@ class ExactEnumerator:
             # Support attributes that have PySide types as values,
             # but we skip the 'staticMetaObject' that needs
             # to be defined at a QObject level.
-            elif "PySide" in str(type(thing)) and "QMetaObject" not in str(type(thing)):
+            # PYSIDE-3034: added public variables, extracted helper function to
+            # avoid repetitive calls of str(type(thing))
+            elif is_relevant_type(thing):
                 if class_name not in attributes:
                     attributes[class_name] = {}
                 attributes[class_name][thing_name] = thing
index c5dc44644503e1aaab888c0112c56833914fccf5..cca7ae7424c059e8b45ce8bf04c864b5f144fa6e 100644 (file)
@@ -1,7 +1,9 @@
 LICENSE_TEXT = """
 # Copyright (C) 2022 The Qt Company Ltd.
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-from __future__ import annotations
+"""
+MYPY_TEXT = """
+# mypy: disable-error-code="override, overload-overlap"
 """
 
 # flake8: noqa E:402
@@ -30,6 +32,38 @@ from shibokensupport.signature.lib.tool import build_brace_pattern
 
 indent = " " * 4
 
+TYPE_MAP = {
+    # Qt integer types
+    "qint64": "int",
+    "qint32": "int",
+    "qint16": "int",
+    "qsizetype": "int",
+    "quint32": "int",
+    "quint64": "int",
+    "size_t": "int",
+    "uint": "int",
+    "ushort": "int",
+    "ulong": "int",
+    "unsigned char": "int",
+    "unsigned int": "int",
+
+    # Qt floating types
+    "qreal": "float",
+
+    # Qt string-like
+    "QString": "str",
+    "QStringList": "typing.List[str]",
+    "QChar": "str",
+
+    # Qt containers (minimal)
+    "QList": "typing.List",
+    "QVariant": "typing.Any",
+
+    # C strings
+    "char*": "str",
+    "const char*": "str",
+}
+
 
 class Writer:
     def __init__(self, outfile, *args):
@@ -69,12 +103,12 @@ class Formatter(Writer):
     backup = inspect.formatannotation
 
     @classmethod
-    def formatannotation(cls, annotation, base_module=None):
+    def formatannotation(cls, annotation, base_module=None, *args, **kwargs):
         if getattr(annotation, '__module__', None) == 'typing':
             # do not remove the prefix!
             return repr(annotation)
         # do the normal action.
-        return cls.backup(annotation, base_module)
+        return cls.backup(annotation, base_module, *args, **kwargs)
 
     @classmethod
     def fix_typing_prefix(cls, signature):
@@ -84,6 +118,29 @@ class Formatter(Writer):
         inspect.formatannotation = cls.backup
         return stringized
 
+    @classmethod
+    def normalize_type(cls, type_repr: str) -> str:
+        if not type_repr:
+            return "typing.Any"
+        if type_repr in {"void", "void*"}:
+            return "typing.Any"
+        if any(x in type_repr for x in ("QRhi", ".ComponentType", ".Semantic")):
+            return "int"
+        if ( " " in type_repr and
+            not any(x in type_repr for x in ("*", "::", "<", ">", "[", "]"))):
+            return "typing.Any"
+        if type_repr.startswith("QList["):
+            inner = type_repr[len("QList["):-1]
+            inner = cls.normalize_type(inner)
+            return f"typing.List[{inner}]"
+        if type_repr.startswith("QMap[") or type_repr.startswith("QHash["):
+            inner = type_repr[type_repr.find("[") + 1:-1]
+            key, value = map(str.strip, inner.split(",", 1))
+            key = cls.normalize_type(key)
+            value = cls.normalize_type(value)
+            return f"typing.Dict[{key}, {value}]"
+        return TYPE_MAP.get(type_repr, type_repr)
+
     # Adding a pattern to substitute "Union[T, NoneType]" by "Optional[T]"
     # I tried hard to replace typing.Optional by a simple override, but
     # this became _way_ too much.
@@ -214,7 +271,7 @@ class Formatter(Writer):
     def enum(self, class_name, enum_name, value):
         spaces = indent * self.level
         hexval = hex(value)
-        self.print(f"{spaces}{enum_name:25} = ...  # {hexval if value >= 0 else value}")
+        self.print(f"{spaces}{enum_name:25} = {hexval if value >= 0 else value}")
         yield
 
     @contextmanager
@@ -222,7 +279,12 @@ class Formatter(Writer):
         spaces = indent * self.level
         # PYSIDE-2903: Use a fully qualified name in the type comment.
         full_name = f"{type(attr_value).__module__}.{type(attr_value).__qualname__}"
-        self.print(f"{spaces}{attr_name:25} = ...  # type: {full_name}")
+        if full_name == "builtins.getset_descriptor":
+            # PYSIDE-3034: Public variable types added to __doc__
+            type_repr = self.normalize_type(attr_value.__doc__)
+        else:
+            type_repr = full_name
+        self.print(f"{spaces}{attr_name:25} = ...  # type: {type_repr}")
         yield
 
     @contextmanager
@@ -311,10 +373,9 @@ def generate_pyi(import_name, outpath, options):
         """
         This file contains the exact signatures for all functions in module
         {import_name}, except for defaults which are replaced by "...".
-
-        # mypy: disable-error-code="override, overload-overlap"
         """
         '''))
+    fmt.print(MYPY_TEXT.strip())
     HintingEnumerator(fmt).module(import_name)
     fmt.print("# eof")
     # Postprocess: resolve the imports
index 4c20a111402d70b5209987d675406ba5f613a006..9d1c3fd5f0cd3343c515c323656f3f4bdf3daa71 100644 (file)
@@ -266,6 +266,7 @@ type_map.update({
     "QSet": typing.Set,
     "QString": str,
     "QLatin1String": str,
+    "QAnyStringView": str,
     "QStringView": str,
     "QStringList": StringList,
     "quint16": int,
@@ -514,6 +515,7 @@ def init_PySide6_QtCore():
         "PySide6.QtCore.QUrl.ComponentFormattingOptions":
             PySide6.QtCore.QUrl.ComponentFormattingOption,  # mismatch option/enum, why???
         "PyUnicode": typing.Text,
+        "QByteArray": typing.Union[PySide6.QtCore.QByteArray, bytes, bytearray, memoryview],
         "QByteArrayView": PySide6.QtCore.QByteArray,
         "Q_NULLPTR": None,
         "QCalendar.Unspecified": PySide6.QtCore.QCalendar.Unspecified,
@@ -541,6 +543,8 @@ def init_PySide6_QtCore():
         "QVariant.Type": type,  # not so sure here...
         "QVariantMap": typing.Dict[str, Variant],
         "std.chrono.seconds{5}" : ellipsis,
+        "Internal.defaultTryTimeout": 5000,
+        "static_cast<int>(Internal.defaultTryTimeout.count())": 5000
     })
     from shibokensupport.signature.parser import using_snake_case
     if using_snake_case():
@@ -556,6 +560,9 @@ def init_PySide6_QtCore():
     type_map_tuple.update({("PySide6.QtCore.QObject.disconnect", "char*"): str})
     type_map_tuple.update({("PySide6.QtCore.QObject.receivers", "char*"): str})
     type_map_tuple.update({("PySide6.QtCore.qtTrId", "char*"): str})
+    # special case - char default is 'int'.
+    # Here we manually set it to map to 'str'.
+    type_map_tuple.update({("PySide6.QtCore.QLocale.toString", "char"): str})
 
     return locals()
 
@@ -620,7 +627,7 @@ def init_PySide6_QtWidgets():
 def init_PySide6_QtSql():
     from PySide6.QtSql import QSqlDatabase
     type_map.update({
-        "QLatin1StringView(QSqlDatabase.defaultConnection)": QSqlDatabase.defaultConnection,
+        "QSqlDatabase.defaultConnectionName()": "",
         "QVariant.Invalid": Invalid("Variant"),  # not sure what I should create, here...
     })
     return locals()
@@ -736,6 +743,9 @@ def init_testbinding():
 
 # Functions which should return Optional(result) but don't.
 missing_optional_return = {
+    "PySide6.QtCore.QObject.parent",
+    "PySide6.QtGui.QGuiApplication.modalWindow",
+    "PySide6.QtGui.QGuiApplication.screenAt",
     "PySide6.QtWidgets.QApplication.activeModalWidget",
     "PySide6.QtWidgets.QApplication.activePopupWidget",
     "PySide6.QtWidgets.QApplication.activeWindow",
@@ -743,16 +753,41 @@ missing_optional_return = {
     "PySide6.QtWidgets.QApplication.setStyle",
     "PySide6.QtWidgets.QApplication.topLevelAt",
     "PySide6.QtWidgets.QApplication.widgetAt",
+    "PySide6.QtWidgets.QBoxLayout.itemAt",
+    "PySide6.QtWidgets.QBoxLayout.takeAt",
+    "PySide6.QtWidgets.QButtonGroup.checkedButton",
     "PySide6.QtWidgets.QComboBox.completer",
     "PySide6.QtWidgets.QComboBox.lineEdit",
     "PySide6.QtWidgets.QComboBox.validator",
+    "PySide6.QtWidgets.QCompleter.popup",
+    "PySide6.QtWidgets.QFormLayout.itemAt",
+    "PySide6.QtWidgets.QFormLayout.takeAt",
+    "PySide6.QtWidgets.QGraphicsAnchorLayout.itemAt",
+    "PySide6.QtWidgets.QGraphicsGridLayout.itemAt",
+    "PySide6.QtWidgets.QGraphicsLayout.itemAt",
+    "PySide6.QtWidgets.QGraphicsLinearLayout.itemAt",
+    "PySide6.QtWidgets.QGraphicsScene.itemAt",
+    "PySide6.QtWidgets.QGraphicsView.itemAt",
     "PySide6.QtWidgets.QGridLayout.itemAt",
     "PySide6.QtWidgets.QGridLayout.itemAtPosition",
+    "PySide6.QtWidgets.QGridLayout.takeAt",
     "PySide6.QtWidgets.QLayout.itemAt",
+    "PySide6.QtWidgets.QLayout.replaceWidget",
+    "PySide6.QtWidgets.QLayout.takeAt",
+    "PySide6.QtWidgets.QListWidget.itemAt",
+    "PySide6.QtWidgets.QScrollArea.widget",
+    "PySide6.QtWidgets.QSplitter.widget",
+    "PySide6.QtWidgets.QStackedLayout.itemAt",
+    "PySide6.QtWidgets.QStackedLayout.takeAt",
+    "PySide6.QtWidgets.QStackedLayout.widget",
+    "PySide6.QtWidgets.QStackedWidget.widget",
+    "PySide6.QtWidgets.QTabWidget.widget",
     "PySide6.QtWidgets.QTableWidget.horizontalHeaderItem",
     "PySide6.QtWidgets.QTableWidget.item",
     "PySide6.QtWidgets.QTableWidget.itemAt",
     "PySide6.QtWidgets.QTableWidget.mimeData",
+    "PySide6.QtWidgets.QToolBox.widget",
+    "PySide6.QtWidgets.QTreeWidget.itemAt",
     "PySide6.QtWidgets.QTreeWidget.takeTopLevelItem",
     "PySide6.QtWidgets.QTreeWidget.topLevelItem",
     "PySide6.QtWidgets.QWidget.childAt",
index 7eb7e37f413db38b8ae39b06e647784071fa104c..f07eaf0b62b10d8298f37d0e2f925f6c2878da9f 100644 (file)
@@ -11,8 +11,8 @@ auto *pyType = reinterpret_cast<PyTypeObject *>(%2);
 if (Shiboken::ObjectType::checkType(pyType)) {
     auto *ptr = reinterpret_cast<void *>(%1);
     if (auto *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(ptr)) {
-        Py_INCREF(wrapper);
         %PYARG_0 = reinterpret_cast<PyObject *>(wrapper);
+        Py_INCREF(%PYARG_0);
     } else {
         %PYARG_0 = Shiboken::Object::newObject(pyType, ptr, false, true);
     }
@@ -117,7 +117,7 @@ PyTuple_SetItem(version, 1, PyLong_FromLong(SHIBOKEN_MINOR_VERSION));
 PyTuple_SetItem(version, 2, PyLong_FromLong(SHIBOKEN_MICRO_VERSION));
 PyTuple_SetItem(version, 3, Shiboken::String::fromCString(SHIBOKEN_RELEASE_LEVEL));
 PyTuple_SetItem(version, 4, PyLong_FromLong(SHIBOKEN_SERIAL));
-PyModule_AddObject(module, "__version_info__", version);
+PepModule_Add(module, "__version_info__", version);
 PyModule_AddStringConstant(module, "__version__", SHIBOKEN_VERSION);
 VoidPtr::addVoidPtrToModule(module);
 Shiboken::initShibokenSupport(module);
index 05f6e9e604ba3af9483c424802579f767d31a060..6de8199ef6a0f599e8d7314d798980cd5e0ba888 100644 (file)
@@ -24,6 +24,11 @@ else()
     set(GENERATOR_EXTRA_FLAGS )
 endif()
 list(APPEND GENERATOR_EXTRA_FLAGS ${SHIBOKEN_GENERATOR_EXTRA_FLAGS} ${debug_level})
+if (CMAKE_CROSSCOMPILING)
+    list(APPEND GENERATOR_EXTRA_FLAGS
+         "--platform=${CMAKE_SYSTEM_NAME}" "--arch=${CMAKE_SYSTEM_PROCESSOR}"
+         "--compiler-path=${CMAKE_CXX_COMPILER}")
+endif()
 
 add_subdirectory(minimalbinding)
 if(NOT DEFINED MINIMAL_TESTS)
index e1d7dda384d78149726e0ca9e4870db99fb00758..3bd31839d901835af14f8fec24dd7f3f6def3c97 100644 (file)
@@ -12,6 +12,7 @@ obj.cpp obj.h
 spanuser.cpp spanuser.h
 typedef.cpp typedef.h
 val.h
+invisiblenamespace.h
 )
 
 add_library(libminimal SHARED ${libminimal_SRC})
diff --git a/sources/shiboken6/tests/libminimal/invisiblenamespace.h b/sources/shiboken6/tests/libminimal/invisiblenamespace.h
new file mode 100644 (file)
index 0000000..9ac8a70
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef INVISIBLENAMESPACE_H
+#define INVISIBLENAMESPACE_H
+
+#include "libminimalmacros.h"
+
+namespace InvisibleNamespace::VisibleNamespace {
+    struct ValueType {
+        int i = 0;
+    };
+}
+
+#endif
index cafd382a9c2d96d0de694f8b2153c4eb4139827f..43a32f41467af60b03e8e4d2581c803a764c95a2 100644 (file)
@@ -40,15 +40,21 @@ bool Bucket::empty()
 
 void Bucket::lock()
 {
-    m_locked = true;
-    while (m_locked) {
-        SLEEP(300);
+    bool expected = false;
+    if (m_locked.compare_exchange_strong(expected, true)) {
+        while (m_locked) {
+            SLEEP(300);
+        }
+    } else {
+        std::cerr << __FUNCTION__ << " Attempt to lock twice.\n";
     }
 }
 
 void Bucket::unlock()
 {
-    m_locked = false;
+    bool expected = true;
+    if (!m_locked.compare_exchange_strong(expected, false))
+        std::cerr << __FUNCTION__ << " Attempt to unlock twice.\n";
 }
 
 bool Bucket::virtualBlockerMethod()
index 73e8edd780060bb2c9a938b82e5bf421698c560a..677069fe733ac73a0f5749b1e39e9dd9b0eb793d 100644 (file)
@@ -7,6 +7,7 @@
 #include "libsamplemacros.h"
 #include "objecttype.h"
 
+#include <atomic>
 #include <list>
 
 class ObjectType;
@@ -19,7 +20,7 @@ public:
     int pop();
     bool empty();
     void lock();
-    inline bool locked() { return m_locked; }
+    bool locked() { return m_locked.load(); }
     void unlock();
 
     virtual bool virtualBlockerMethod();
@@ -27,8 +28,7 @@ public:
 
 private:
     std::list<int> m_data;
-
-    volatile bool m_locked = false;
+    std::atomic<bool> m_locked{false};
 };
 
 #endif // BUCKET_H
index 78d5162b00fbc4eb3d5242db62dd5cf4cd50a8ca..a70950c6d5dd13996aaccbdc4bd856b4e5a73eba 100644 (file)
@@ -59,7 +59,7 @@ ByteArray &ByteArray::append(char c)
 ByteArray &ByteArray::append(const char *data)
 {
     m_data.pop_back();
-    std::copy(data, data + strlen(data), std::back_inserter(m_data));
+    std::copy(data, data + std::strlen(data), std::back_inserter(m_data));
     m_data.push_back('\0');
     return *this;
 }
index b745aed6befecf3a7e8726592e5626a672a37bad..ef88e543e6c56ad22a0381f8f045ecc4b8ae2d54 100644 (file)
@@ -21,6 +21,8 @@ enum GlobalEnum {
     ThirdThing
 };
 
+using GlobalEnumAlias = GlobalEnum;
+
 enum GlobalOverloadFuncEnum {
     GlobalOverloadFunc_i,
     GlobalOverloadFunc_d
index 9bb7f7798faf1901d4a64c1b370f4354d4186e9a..c3b3295ccc87ef31162d1e63f3a51bcbb1d38c52 100644 (file)
@@ -61,3 +61,14 @@ void ListUser::multiplyPointList(PointList &points, double multiplier)
         point->setY(point->y() * multiplier);
     }
 }
+
+std::vector<int> ListUser::passThroughIntVector(const std::vector<int> &v)
+{
+    return v;
+}
+
+std::vector<bool> ListUser::passThroughBoolVector(const std::vector<bool> &v)
+{
+    return v;
+}
+
index 69559cde51afb1876c9980ee8813fc4e2189169c..9b7a57c7706fba39960a462c7e9f0969d29b61a6 100644 (file)
@@ -11,6 +11,7 @@
 #include "libsamplemacros.h"
 
 #include <list>
+#include <vector>
 
 class LIBSAMPLE_API ListUser
 {
@@ -45,6 +46,9 @@ public:
     inline void setList(std::list<int> lst) { m_lst = lst; }
     inline std::list<int> getList() const { return m_lst; }
 
+    static std::vector<int> passThroughIntVector(const std::vector<int> &v);
+    static std::vector<bool> passThroughBoolVector(const std::vector<bool> &v);
+
 private:
     std::list<int> m_lst;
 };
index 7e5d128abed20eb5b958e965500c496fff23b5a7..61cc68ac0f335501104ce732ede89c1bc2e9ca41 100644 (file)
@@ -71,6 +71,4 @@ LIBSAMPLE_API bool operator!(const Point &pt);
 LIBSAMPLE_API Complex transmutePointIntoComplex(const Point &point);
 LIBSAMPLE_API Point transmuteComplexIntoPoint(const Complex &cpx);
 
-LIBSAMPLE_API Point operator*(const Point &pt, double multiplier);
-
 #endif // POINT_H
index 49e00946781b010b356010cb888f24fd271c1c25..5e7fd7ee631636ecf6680886da6d231a7830911a 100644 (file)
@@ -60,6 +60,4 @@ LIBSAMPLE_API PointF operator*(int mult, const PointF &pt);
 LIBSAMPLE_API PointF operator-(const PointF &pt);
 LIBSAMPLE_API bool operator!(const PointF &pt);
 
-LIBSAMPLE_API PointF operator*(const PointF &pt, double multiplier);
-
 #endif // POINTF_H
index 18a18d28d7a438553d314a91ead7cd71b2d24be4..5e7b67a62bd42d9909043a2e709f4bfee404b166 100644 (file)
 namespace SampleNamespace
 {
 
+SomeClass::OptionAlias SomeClass::passThroughOptionAlias(OptionAlias ov)
+{
+    return ov;
+}
+
+Option SomeClass::passThroughOption(Option ov)
+{
+    return ov;
+}
+
 // PYSIDE-817, scoped enums must not be converted to int in the wrappers generated
 // for the protected hacks
 SomeClass::PublicScopedEnum SomeClass::protectedMethodReturningPublicScopedEnum() const
@@ -97,6 +107,15 @@ int passReferenceToObjectType(const ObjectType &obj, int multiplier)
     return obj.objectName().size() * multiplier;
 }
 
+// Exercise specifying complete template specializations as primitive types.
+std::optional<long> optionalMultiply(const std::optional<long> &v1,
+                                     const std::optional<long> &v2)
+{
+    if (!v1.has_value() || !v2.has_value())
+        return std::nullopt;
+    return v1.value() * v2.value();
+}
+
 int variableInNamespace = 42;
 
 } // namespace SampleNamespace
index 99a0787eef5b63b293079f5c40a6e76e346bfcd1..1613ce63087712c1c78077c39679b16d4e3df23f 100644 (file)
@@ -5,10 +5,12 @@
 #define SAMPLENAMESPACE_H
 
 #include "libsamplemacros.h"
+#include "samplenamespace.h"
 #include "str.h"
 #include "point.h"
 #include "objecttype.h"
 
+#include <optional>
 #include <list>
 
 // Anonymous global enum
@@ -85,6 +87,12 @@ class LIBSAMPLE_API SomeClass
 public:
     enum class PublicScopedEnum { v1, v2 };
 
+    // Alias an enumeration
+    using OptionAlias = Option;
+    inline static constexpr auto None_ = Option::None_;
+    inline static constexpr auto RandomNumber = Option::RandomNumber;
+    inline static constexpr auto UnixTime  = Option::UnixTime;
+
     class SomeInnerClass
     {
     public:
@@ -106,16 +114,21 @@ public:
             inline int someMethod(SomeInnerClass *) { return 0; }
             virtual OkThisIsRecursiveEnough *someVirtualMethod(OkThisIsRecursiveEnough *arg)
             { return arg; }
-        };
+        }; // OkThisIsRecursiveEnough
     protected:
         enum ProtectedEnum {
             ProtectedItem0,
             ProtectedItem1
         };
-    };
+    }; // SomeInnerClass
+
     struct SomeOtherInnerClass {
         std::list<SomeInnerClass> someInnerClasses;
     };
+
+    static OptionAlias passThroughOptionAlias(OptionAlias ov);
+    static Option passThroughOption(Option ov);
+
 protected:
     enum ProtectedEnum {
         ProtectedItem0,
@@ -157,6 +170,9 @@ LIBSAMPLE_API double passReferenceToValueType(const Point &point, double multipl
 // Add a new signature on type system with only a ObjectType pointer as parameter.
 LIBSAMPLE_API int passReferenceToObjectType(const ObjectType &obj, int multiplier);
 
+LIBSAMPLE_API std::optional<long> optionalMultiply(const std::optional<long> &v1,
+                                                   const std::optional<long> &v2);
+
 extern LIBSAMPLE_API int variableInNamespace;
 
 } // namespace SampleNamespace
index 6eaae8182bf5abee3909a7b0ceec15a5af9a59a5..672b3f2be390464d27a1c3546742a775978f9e5d 100644 (file)
@@ -15,6 +15,8 @@ ${CMAKE_CURRENT_BINARY_DIR}/minimal/val_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/minimal/listuser_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/minimal/spanuser_wrapper.cpp
 ${CMAKE_CURRENT_BINARY_DIR}/minimal/minbooluser_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/minimal/invisiblenamespace_visiblenamespace_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/minimal/invisiblenamespace_visiblenamespace_valuetype_wrapper.cpp
 )
 
 configure_file("${CMAKE_CURRENT_SOURCE_DIR}/minimal-binding.txt.in"
index fc5c59a26d650a1da70cad3de3648913bf06c6d0..4f104b214cccb74b0520f845eda6711c3f9f69b8 100644 (file)
@@ -8,3 +8,4 @@
 #include "listuser.h"
 #include "spanuser.h"
 #include "typedef.h"
+#include "invisiblenamespace.h"
index e18bf868694bbfab84babc54582a5057c2572e72..032ab92d7afeaaa66699beda797c1596456c0384 100644 (file)
 
     <value-type name="MinBoolUser"/>
 
+    <namespace-type name="InvisibleNamespace" visible="no">
+        <namespace-type name="VisibleNamespace">
+            <value-type name="ValueType"/>
+        </namespace-type>
+    </namespace-type>
+
     <value-type name="ContainerUser">
         <modify-function signature="intVector()">
             <modify-argument index="return">
index 63b8b8fa32a763163ee766665301fb008fb1457f..11052c5ba6c43eb8adb0ff4f27c19334d49d4f94 100644 (file)
@@ -17,6 +17,9 @@ init_paths()
 from sample import Derived, Point, ObjectType
 
 
+REF_COUNT_DELTA = 2 if sys.version_info >= (3, 14) else 1
+
+
 class TestAccessingCppFields(unittest.TestCase):
     '''Simple test case for accessing the exposed C++ class fields.'''
 
@@ -125,7 +128,7 @@ class TestAccessingCppFields(unittest.TestCase):
         refcount1 = sys.getrefcount(o1)
         d.objectTypeField = o1
         self.assertEqual(d.objectTypeField, o1)
-        self.assertEqual(sys.getrefcount(d.objectTypeField), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(d.objectTypeField), refcount1 + REF_COUNT_DELTA)
 
         # attributing a new object to instance's field should decrease the previous
         # object's reference count
@@ -134,7 +137,7 @@ class TestAccessingCppFields(unittest.TestCase):
         d.objectTypeField = o2
         self.assertEqual(d.objectTypeField, o2)
         self.assertEqual(sys.getrefcount(o1), refcount1)
-        self.assertEqual(sys.getrefcount(d.objectTypeField), refcount2 + 1)
+        self.assertEqual(sys.getrefcount(d.objectTypeField), refcount2 + REF_COUNT_DELTA)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testRefCountingOfReferredObjectAfterDeletingReferrer(self):
index 1c431763e82f32b19bf8e4ffd46cb972433500f0..0403efa93cae156bc55d13bc4f7826fa248c220a 100644 (file)
@@ -15,6 +15,9 @@ init_paths()
 from sample import ObjectModel, ObjectView
 
 
+REF_COUNT_DELTA = 2 if sys.version_info >= (3, 14) else 1
+
+
 class TestKeepReference(unittest.TestCase):
     '''Test case for objects that keep references to other object without
        owning them (e.g. model/view relationships).'''
@@ -26,15 +29,15 @@ class TestKeepReference(unittest.TestCase):
         refcount1 = sys.getrefcount(model1)
         view1 = ObjectView()
         view1.setModel(model1)
-        self.assertEqual(sys.getrefcount(view1.model()), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(view1.model()), refcount1 + REF_COUNT_DELTA)
 
         view2 = ObjectView()
         view2.setModel(model1)
-        self.assertEqual(sys.getrefcount(view2.model()), refcount1 + 2)
+        self.assertEqual(sys.getrefcount(view2.model()), refcount1 + REF_COUNT_DELTA + 1)
 
         model2 = ObjectModel()
         view2.setModel(model2)
-        self.assertEqual(sys.getrefcount(view1.model()), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(view1.model()), refcount1 + REF_COUNT_DELTA)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testReferenceCountingWhenDeletingReferrer(self):
@@ -43,7 +46,7 @@ class TestKeepReference(unittest.TestCase):
         refcount1 = sys.getrefcount(model)
         view = ObjectView()
         view.setModel(model)
-        self.assertEqual(sys.getrefcount(view.model()), refcount1 + 1)
+        self.assertEqual(sys.getrefcount(view.model()), refcount1 + REF_COUNT_DELTA)
 
         del view
         self.assertEqual(sys.getrefcount(model), refcount1)
index 4d113722f1a2f4b5908f9b40b50cdcdcdc55a323..37691c790cf03d9a2114718c63a0c8f29069da5e 100644 (file)
@@ -100,6 +100,16 @@ class ListConversionTest(unittest.TestCase):
         self.assertEqual(ListUser.ListOfPointF, ListUser.listOfPoints([PointF()]))
         self.assertEqual(ListUser.ListOfPoint, ListUser.listOfPoints([Point()]))
 
+    def testStdVector(self):
+        """PYSIDE-3259: Test std::vector<bool> in case some compiler
+           does the std::vector<bool> optimization."""
+        intList = [1, 2]
+        actual = ListUser.passThroughIntVector(intList)
+        self.assertEqual(intList, actual)
+        boolList = [True, False]
+        actual = ListUser.passThroughBoolVector(boolList)
+        self.assertEqual(boolList, actual)
+
 
 if __name__ == '__main__':
     unittest.main()
index 0d67c7497c1fbd3055ef37b8f072f55c967c6686..0e92d6e27701e04571651ffa89f51ee6ecd731e0 100644 (file)
@@ -64,6 +64,20 @@ class TestClassesUnderNamespace(unittest.TestCase):
         cls.setValue(SampleNamespace.EnumWithinInlineNamespace.EWIN_Value1)
         self.assertEqual(cls.value(), SampleNamespace.EnumWithinInlineNamespace.EWIN_Value1)
 
+    def testEnumAlias(self):
+        """Test whether an enumeration can be aliased to another one and values
+           can be used interchangeably."""
+        expected = SampleNamespace.SomeClass.OptionAlias.None_
+        actual = SampleNamespace.SomeClass.passThroughOptionAlias(expected)
+        self.assertEqual(expected, actual)
+        actual = SampleNamespace.SomeClass.passThroughOption(expected)
+        self.assertEqual(expected, actual)
+        # The alias source values should also work
+        actual = SampleNamespace.SomeClass.passThroughOptionAlias(SampleNamespace.Option.None_)
+        self.assertEqual(expected, actual)
+        actual = SampleNamespace.SomeClass.passThroughOption(SampleNamespace.Option.None_)
+        self.assertEqual(expected, actual)
+
 
 if __name__ == '__main__':
     unittest.main()
index 758ba83511f57f59400e9b36c8c003c045942bba..d8ead5309fed3b23c3976a2bf4d0c8dd0953e2ef 100644 (file)
@@ -48,9 +48,10 @@ class DeleteParentTest(unittest.TestCase):
         del parent
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
+        EXPECTED_REF_COUNT = 3 if sys.version_info >= (3, 14) else 4
         for i, child in enumerate(children):
             self.assertRaises(RuntimeError, child.objectName)
-            self.assertEqual(sys.getrefcount(child), 4)
+            self.assertEqual(sys.getrefcount(child), EXPECTED_REF_COUNT)
 
     @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
     def testRecursiveParentDelete(self):
@@ -62,10 +63,11 @@ class DeleteParentTest(unittest.TestCase):
         del parent
         # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
         gc.collect()
+        EXPECTED_REF_COUNT = 1 if sys.version_info >= (3, 14) else 2
         self.assertRaises(RuntimeError, child.objectName)
-        self.assertEqual(sys.getrefcount(child), 2)
+        self.assertEqual(sys.getrefcount(child), EXPECTED_REF_COUNT)
         self.assertRaises(RuntimeError, grandchild.objectName)
-        self.assertEqual(sys.getrefcount(grandchild), 2)
+        self.assertEqual(sys.getrefcount(grandchild), EXPECTED_REF_COUNT)
 
 
 if __name__ == '__main__':
index c003ad3984114cc95a91e5bf8d671dbc9ac37c93..43b84d36ede6d4bdc9c100f3a0b98e3c9a7c920d 100644 (file)
@@ -79,6 +79,13 @@ class ModuleTest(unittest.TestCase):
         mo2 = sample.MoveOnlyHandler.passMoveOnly(mo)
         self.assertEqual(mo2.value(), v)
 
+    def testOptionalLong(self):
+        v1 = 2
+        v2 = 3
+        self.assertEqual(sample.SampleNamespace.optionalMultiply(v1, v2), 6)
+        self.assertIsNone(sample.SampleNamespace.optionalMultiply(v1, None))
+        self.assertIsNone(sample.SampleNamespace.optionalMultiply(None, v2))
+
 
 if __name__ == '__main__':
     unittest.main()
index 3f1b2e96f201c404468c95175141cbbb491ba877..73f2e14252253e94be06de89f55aa6dd2ba8c656 100644 (file)
         </conversion-rule>
     </primitive-type>
 
+    <primitive-type name="std::optional&lt;long&gt;">
+      <conversion-rule>
+          <native-to-target>
+          if (!%in.has_value())
+              Py_RETURN_NONE;
+          return PyLong_FromLong(%in.value());
+          </native-to-target>
+          <target-to-native>
+              <add-conversion type="Py_None">
+              SBK_UNUSED(%in)
+              %out = %OUTTYPE();
+              </add-conversion>
+              <add-conversion type="PyLong" check="PyLong_CheckExact(%in)">
+              %out = %OUTTYPE(PyLong_AsLong(%in));
+              </add-conversion>
+          </target-to-native>
+      </conversion-rule>
+    </primitive-type>
+
     <value-type name="MoveOnly"/>
     <object-type name="MoveOnlyHandler"/>
 
     -->
     <enum-type name="GlobalEnum"/>
     <enum-type name="GlobalOverloadFuncEnum"/>
+    <enum-type name="GlobalEnumAlias"/>
 
     <enum-type identified-by-value="AnonymousGlobalEnum_Value0"/>
 
             <enum-type name="SampleNamespace"/>
         </object-type>
         <value-type name="SomeClass">
+            <enum-type name="OptionAlias"/>
+            <modify-field name="None_" remove="true"/>
+            <modify-field name="RandomNumber" remove="true"/>
+            <modify-field name="RandomNumber" remove="true"/>
             <enum-type name="PublicScopedEnum"/>
             <value-type name="SomeInnerClass">
                 <object-type name="OkThisIsRecursiveEnough">
                 <replace-default-expression with="3000"/>
             </modify-argument>
             <inject-code class="native" position="beginning">
-                PyObject* new_arg0 = PyLong_FromLong(PyLong_AS_LONG(%PYARG_1) - %2);
+                PyObject* new_arg0 = PyLong_FromLong(PyLong_AsLong(%PYARG_1) - %2);
                 Py_DECREF(%PYARG_1);
                 %PYARG_1 = new_arg0;
             </inject-code>
         </add-function>
         <add-function signature="__repr__" return-type="str">
             <inject-code class="target" position="beginning">
-            ByteArray b(Py_TYPE(%PYSELF)->tp_name);
+            ByteArray b(PepType_GetFullyQualifiedNameStr(Py_TYPE(%PYSELF)));
             PyObject* aux = Shiboken::String::fromStringAndSize(%CPPSELF.data(), %CPPSELF.size());
             if (PyUnicode_CheckExact(aux)) {
                 PyObject* tmp = PyUnicode_AsASCIIString(aux);
                 <conversion-rule class="native">
                     // Does nothing really, just test the code generation
                     // of constructors whose arguments where
-                    long %out = PyLong_AS_LONG(%PYARG_1) + 1;
+                    long %out = PyLong_AsLong(%PYARG_1) + 1;
                 </conversion-rule>
             </modify-argument>
         </modify-function>
index 10e72f125797c91f0b074ae50e2a21edba0c14d2..9e8bc8d4793a87aca8547744098228f76c1d5526 100644 (file)
@@ -26,6 +26,15 @@ def integer_from_value(v):
 
 class StdOptionalTests(unittest.TestCase):
 
+    def testConversionFromInt(self):
+        """PYSIDE-3107: Test whether a parameter taking a 'std::optional<int>'
+           accepts 'int'."""
+        b = StdOptionalTestBench()
+        b.setOptionalInt(43)
+        self.assertEqual(b.optionalInt().value(), 43)
+        b.setOptionalInt(None)
+        self.assertFalse(b.optionalInt().has_value())
+
     def testCInt(self):
         b = StdOptionalTestBench()
         ci = b.optionalInt()
@@ -42,8 +51,6 @@ class StdOptionalTests(unittest.TestCase):
         ci = std.optional_int(43)
         self.assertEqual(ci.value(), 43)
 
-    @unittest.skipIf(True, """PYSIDE-2854, T &std::optional::value() does not work/
-                              returns self (colocated).""")
     def testInteger(self):
         b = StdOptionalTestBench()
         i = b.optionalInteger()
index 14b181b6134385747a0ac268143dd66c80993926..4024036fa59f1bcc0654481e7d32d4e08e75879d 100644 (file)
                             value-check-method="has_value"
                             instantiations="Integer,int">
             <include file-name="optional" location="global"/>
+            <!-- PYSIDE-3107: Generic rule constructing std::optional<T>' from 'T'. -->
+            <conversion-rule>
+                <target-to-native>
+                    <add-conversion type="Py_None">
+                    SBK_UNUSED(pyIn)
+                    %out = std::nullopt;
+                    </add-conversion>
+                    <add-conversion type="InstType" check="%CHECKTYPE[%OUTTYPE_0](%in)">
+                    %OUTTYPE_0 v = %CONVERTTOCPP[%OUTTYPE_0](%in);
+                    %out = %OUTTYPE(v);
+                    </add-conversion>
+                </target-to-native>
+            </conversion-rule>
         </smart-pointer-type>
 
     </namespace-type>
index 4d96fdfc219893f98d9c9230ce6f9297124fb630..c85c4f1507c49cfee35a0b2dbfc5c2a044b8b00f 100644 (file)
@@ -214,11 +214,13 @@ class TestRunner:
                 os.rename(tmp_name, self.logfile)
             self.partial = True
         else:
+            _ = ctest_process.communicate()
             self.partial = False
         finally:
             print("End of the test run")
             print()
         tee_process.wait()
+        ctest_process.wait()
 
     def run(self, label, rerun, timeout):
         cmd = self.ctestCommand, "--output-log", self.logfile
index e5b47ffc1b7d214547ef56701e7e71fcd42a1907..5ac9333db2ce710f56b3fd2a2f9fc7fdbaa69d6b 100644 (file)
@@ -143,7 +143,7 @@ def generate_build_cmake():
     # Specify prefix path so find_package(Qt6) works.
     qmake_dir = Path(QMAKE_PATH).resolve().parent.parent
     args = [CMAKE_PATH, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release",
-                        f"-Dpython_interpreter={sys.executable}",
+                        f"-DPython_EXECUTABLE={sys.executable}",
                         f"-DCMAKE_PREFIX_PATH={qmake_dir}",
                         ".."]
     exit_code = run_process(args)
index a0d49d83c097b503e0412e9294eb1568b95d86c0..14adddbacc0f6724eceeb70a64fda6fdcd0ba1c5 100644 (file)
@@ -50,7 +50,7 @@ def change_log(version: list) -> Path:
 
 
 def is_lts_version(version: list) -> bool:
-    return version[0] == 5 or version[1] in (2, 5)
+    return version[0] == 5 or version[1] in (2, 5, 8)
 
 
 def version_tag(version: list) -> str:
index 6636d080f9fd5285ce67f1c948404866d1f817c2..e37ab51179fe43ae4c0b23c3df8a74eb402e7d39 100644 (file)
@@ -104,6 +104,9 @@ if __name__ == "__main__":
 
     parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const",
                         dest="loglevel", const=logging.INFO)
+    # As opposed to Qt, Qt for Python does not require API level 28 because it can be built with a
+    # higher API for toolchain compatibility, while still remaining compatible with Qt's runtime
+    # minimum.
     parser.add_argument("--api-level", type=str, default="35",
                         help="Minimum Android API level to use")
     parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r26b)")
index a26811a9033e3a00309a4a0b29221d9744e9b993..243dd28fd56aa02e98bf876eae0bbb0fdaa212e1 100644 (file)
@@ -118,6 +118,8 @@ def _write_global_header(modules, file):
     """Helper to write the global header for shiboken."""
     for module in modules:
         print(f"#include <{module}/{module}>", file=file)
+        if module == "QtGui":
+            print("#include <rhi/qrhi.h>", file=file)
 
 
 def write_global_header(modules, filename):
index 8dc0789fa292cf86deab2eeb1244079e5f338f00..6469c0c35523a4832b5fe088755b6b77f75d90e4 100644 (file)
@@ -2,19 +2,7 @@
 # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
 from __future__ import annotations
 
-
-"""
-This tool reads all the examples from the main repository that have a
-'.pyproject' file, and generates a special table/gallery in the documentation
-page.
-
-For the usage, simply run:
-    python tools/example_gallery/main.py
-since there is no special requirements.
-"""
-
-import json
-import math
+import fnmatch
 import os
 import shutil
 import zipfile
@@ -23,8 +11,12 @@ from argparse import ArgumentParser, RawTextHelpFormatter
 from dataclasses import dataclass
 from enum import IntEnum, Enum
 from pathlib import Path
-from textwrap import dedent
 from collections import defaultdict
+from typing import DefaultDict
+
+sys.path.append(os.fspath(Path(__file__).parent.parent.parent / "sources" / "pyside-tools"))
+from project_lib import parse_pyproject_json, parse_pyproject_toml, \
+    PYPROJECT_FILE_PATTERNS, PYPROJECT_TOML_PATTERN, PYPROJECT_JSON_PATTERN  # noqa: E402
 
 
 class Format(Enum):
@@ -32,41 +24,39 @@ class Format(Enum):
     MD = 1
 
 
-class ModuleType(IntEnum):
-    ESSENTIALS = 0
-    ADDONS = 1
-    M2M = 2
-
-
-SUFFIXES = {Format.RST: "rst", Format.MD: "md"}
-
-
-opt_quiet = False
-
+__doc__ = """\
+This tool scans the main repository for examples with project files and generates a documentation
+page formatted as a gallery, displaying the examples in a table
 
+For the usage, simply run:
+    python tools/example_gallery/main.py
+"""
+DIR = Path(__file__).parent
+EXAMPLES_DOC = Path(f"{DIR}/../../sources/pyside6/doc/examples").resolve()
+EXAMPLES_DIR = Path(f"{DIR}/../../examples/").resolve()
+TARGET_HELP = f"Directory into which to generate Doc files (default: {str(EXAMPLES_DOC)})"
+BASE_URL = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree"
+DOC_SUFFIXES = {Format.RST: "rst", Format.MD: "md"}
 LITERAL_INCLUDE = ".. literalinclude::"
-
-
 IMAGE_SUFFIXES = (".png", ".jpg", ".jpeg", ".gif", ".svg", ".svgz", ".webp")
-
-
+# Suffixes to ignore when displaying source files that are referenced in the project file
 IGNORED_SUFFIXES = IMAGE_SUFFIXES + (".pdf", ".pyc", ".obj", ".mesh")
-
-
-suffixes = {
-    ".h": "cpp",
-    ".cpp": "cpp",
-    ".md": "markdown",
-    ".py": "py",
-    ".qml": "js",
-    ".conf": "ini",
-    ".qrc": "xml",
-    ".ui": "xml",
-    ".xbel": "xml",
-    ".xml": "xml",
+LANGUAGE_PATTERNS = {
+    "*.h": "cpp",
+    "*.cpp": "cpp",
+    "*.md": "markdown",
+    "*.py": "py",
+    "*.qml": "js",
+    "*.qmlproject": "js",
+    "*.conf": "ini",
+    "*.qrc": "xml",
+    "*.ui": "xml",
+    "*.xbel": "xml",
+    "*.xml": "xml",
+    "*.html": "html",
+    "CMakeLists.txt": "cmake",
 }
 
-
 BASE_CONTENT = """\
 .. _pyside6_examples:
 
@@ -82,42 +72,59 @@ Examples
  directory.
 
 """
+# We generate a 'toctree' at the end of the file to include the new 'example' rst files, so we get
+# no warnings and also that users looking for them will be able to, since they are indexed
+# Notice that :hidden: will not add the list of files by the end of the main examples HTML page.
+FOOTER_INDEX = """\
+.. toctree::
+    :hidden:
+    :maxdepth: 1
 
+"""
+TUTORIAL_HEADLINES = {
+    "tutorials/extending-qml/chapter": "Tutorial: Writing QML Extensions with Python",
+    "tutorials/extending-qml-advanced/advanced": "Tutorial: Writing advanced QML Extensions with"
+                                                 "Python",
+    "tutorials/finance_manager": "Tutorial: Finance Manager - Integrating PySide6 with SQLAlchemy "
+                                 "and FastAPI",
+}
 
-def tutorial_headline(path: str):
-    if "tutorials/extending-qml/chapter" in path:
-        return "Tutorial: Writing QML Extensions with Python"
-    if "tutorials/extending-qml-advanced/advanced" in path:
-        return "Tutorial: Writing advanced QML Extensions with Python"
-    if "tutorials/finance_manager" in path:
-        return "Tutorial: Finance Manager - Integrating PySide6 with SQLAlchemy and FastAPI"
-    return ""
+
+class ModuleType(IntEnum):
+    ESSENTIALS = 0
+    ADDONS = 1
+    M2M = 2
 
 
-def ind(x):
-    return " " * 4 * x
+def get_lexer(path: Path) -> str:
+    """Given a file path, return the language lexer to use for syntax highlighting"""
+    for pattern, lexer in LANGUAGE_PATTERNS.items():
+        if fnmatch.fnmatch(path.name, pattern):
+            return lexer
+    # Default to text
+    return "text"
 
 
-def get_lexer(path):
-    if path.name == "CMakeLists.txt":
-        return "cmake"
-    lexer = suffixes.get(path.suffix)
-    return lexer if lexer else "text"
+def ind(level: int) -> str:
+    """Return a string of spaces for string indentation given certain level"""
+    return " " * 4 * level
 
 
-def add_indent(s, level):
+def add_indent(s: str, level: int) -> str:
+    """Add indentation to a string"""
     new_s = ""
     for line in s.splitlines():
         if line.strip():
             new_s += f"{ind(level)}{line}\n"
         else:
+            # Empty line
             new_s += "\n"
     return new_s
 
 
-def check_img_ext(i):
-    """Check whether path is an image."""
-    return i.suffix in IMAGE_SUFFIXES
+def check_img_ext(image_path: Path) -> bool:
+    """Check whether a file path is an image depending on its suffix."""
+    return image_path.suffix in IMAGE_SUFFIXES
 
 
 @dataclass
@@ -177,15 +184,15 @@ MODULE_DESCRIPTIONS = {
 }
 
 
-def module_sort_key(name):
-    """Return key for sorting modules."""
+def module_sort_key(name: str) -> str:
+    """Return a key for sorting the Qt modules."""
     description = MODULE_DESCRIPTIONS.get(name)
     module_type = int(description.module_type) if description else 5
     sort_key = description.sort_key if description else 100
     return f"{module_type}:{sort_key:04}:{name}"
 
 
-def module_title(name):
+def module_title(name: str) -> str:
     """Return title for a module."""
     result = name.title()
     description = MODULE_DESCRIPTIONS.get(name)
@@ -205,25 +212,22 @@ def module_title(name):
 class ExampleData:
     """Example data for formatting the gallery."""
 
-    def __init__(self):
-        self.headline = ""
-
-    example: str
-    module: str
-    extra: str
-    doc_file: str
-    file_format: Format
-    abs_path: str
-    has_doc: bool
-    img_doc: Path
-    headline: str
-    tutorial: str
+    example_name: str = None
+    module: str = None
+    extra: str = None
+    doc_file: str = None
+    file_format: Format = None
+    abs_path: str = None
+    src_doc_file: Path = None
+    img_doc: Path = None
+    tutorial: str = None
+    headline: str = ""
 
 
-def get_module_gallery(examples):
+def get_module_gallery(examples: list[ExampleData]) -> str:
     """
-    This function takes a list of dictionaries, that contain examples
-    information, from one specific module.
+    This function takes a list of examples from one specific module and returns the resulting string
+    in RST format that can be used to generate the table for the examples
     """
 
     gallery = (
@@ -231,25 +235,22 @@ def get_module_gallery(examples):
         f"{ind(2)}:gutter: 3\n\n"
     )
 
-    # Iteration per rows
-    for i in range(math.ceil(len(examples))):
-        e = examples[i]
-        suffix = SUFFIXES[e.file_format]
+    for i, example in enumerate(examples):
+        suffix = DOC_SUFFIXES[example.file_format]
         # doc_file with suffix removed, to be used as a sphinx reference
-        doc_file_name = e.doc_file.replace(f".{suffix}", "")
         # lower case sphinx reference
         # this seems to be a bug or a requirement from sphinx
-        doc_file_name = doc_file_name.lower()
+        doc_file_name = example.doc_file.replace(f".{suffix}", "").lower()
 
-        name = e.example
-        underline = e.module
+        name = example.example_name
+        underline = example.module
 
-        if e.extra:
-            underline += f"/{e.extra}"
+        if example.extra:
+            underline += f"/{example.extra}"
 
         if i > 0:
             gallery += "\n"
-        img_name = e.img_doc.name if e.img_doc else "../example_no_image.png"
+        img_name = example.img_doc.name if example.img_doc else "../example_no_image.png"
 
         # Fix long names
         if name.startswith("chapter"):
@@ -259,28 +260,17 @@ def get_module_gallery(examples):
 
         # Handling description from original file
         desc = ""
-        original_dir = Path(e.abs_path) / "doc"
-
-        if e.has_doc:
-            # cannot use e.doc_file because that is the target file name
-            # so finding the original file by the name
-            original_file = (next(original_dir.glob("*.rst"), None)
-                             or next(original_dir.glob("*.md"), None))
-            if not original_file:
-                # ideally won't reach here because has_doc is True
-                print(f"example_gallery: No .rst or .md file found in {original_dir}")
-                continue
-
-            with original_file.open("r", encoding="utf-8") as f:
+        if example.src_doc_file:
+            with example.src_doc_file.open("r", encoding="utf-8") as f:
                 # Read the first line
                 first_line = f.readline().strip()
 
                 # Check if the first line is a reference (starts with '(' and ends with ')=' for MD,
                 # or starts with '.. ' and ends with '::' for RST)
-                if ((e.file_format == Format.MD and first_line.startswith('(')
+                if ((example.file_format == Format.MD and first_line.startswith('(')
                      and first_line.endswith(')='))
-                    or (e.file_format == Format.RST and first_line.startswith('.. ')
-                        and first_line.endswith('::'))):
+                        or (example.file_format == Format.RST and first_line.startswith('.. ')
+                            and first_line.endswith('::'))):
                     # The first line is a reference, so read the next lines until a non-empty line
                     # is found
                     while True:
@@ -294,21 +284,17 @@ def get_module_gallery(examples):
                 # The next line handling depends on the file format
                 line = f.readline().strip()
 
-                if e.file_format == Format.MD:
+                if example.file_format == Format.MD:
                     # For markdown, the second line is the empty line
-                    if line != "":
-                        # If the line is not empty, raise a runtime error
-                        raise RuntimeError(f"Unexpected line: {line} in {original_file}. "
-                                           "Needs handling.")
+                    pass
                 else:
-                    # For RST and other formats
-                    # The second line is the underline under the title
-                    _ = line
+                    # For RST and other formats, the second line is the underline under the title
                     # The next line should be empty
                     line = f.readline().strip()
-                    if line != "":
-                        raise RuntimeError(f"Unexpected line: {line} in {original_file}. "
-                                           "Needs handling.")
+
+                if line != "":
+                    raise RuntimeError(
+                        f"{line} was expected to be empty. Doc file: {example.src_doc_file}")
 
                 # Now read until another empty line
                 lines = []
@@ -328,10 +314,14 @@ def get_module_gallery(examples):
                 if len(desc) > 120:
                     desc = desc[:120] + "..."
         else:
-            print(f"example_gallery: No .rst or .md file found in {original_dir}")
-
-        title = e.headline
-        if not title:
+            if not opt_quiet:
+                print(
+                    f"example_gallery: No source doc file found in {example.abs_path}."
+                    f"Skipping example",
+                    file=sys.stderr,
+                )
+
+        if not (title := example.headline):
             title = f"{name} from ``{underline}``."
 
         # Clean refs from desc
@@ -344,28 +334,30 @@ def get_module_gallery(examples):
         gallery += f"{ind(3)}:link: {doc_file_name}\n"
         gallery += f"{ind(3)}:link-type: ref\n"
         gallery += f"{ind(3)}:img-top: {img_name}\n\n"
-        gallery += f"{ind(3)}+++\n{ind(3)}{desc}\n"
+        gallery += f"{ind(3)}+++\n"
+        gallery += f"{ind(3)}{desc}\n"
 
     return f"{gallery}\n"
 
 
-def remove_licenses(s):
+def remove_licenses(file_content: str) -> str:
+    # Return the content of the file with the Qt license removed
     new_s = []
-    for line in s.splitlines():
+    for line in file_content.splitlines():
         if line.strip().startswith(("/*", "**", "##")):
             continue
         new_s.append(line)
     return "\n".join(new_s)
 
 
-def make_zip_archive(zip_file, src, skip_dirs=None):
-    src_path = Path(src).expanduser().resolve(strict=True)
+def make_zip_archive(output_path: Path, src: Path, skip_dirs: list[str] = None):
+    # Create a .zip file from a source directory ignoring the specified directories
+    src_path = src.expanduser().resolve(strict=True)
     if skip_dirs is None:
         skip_dirs = []
     if not isinstance(skip_dirs, list):
-        print("Error: A list needs to be passed for 'skip_dirs'")
-        return
-    with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zf:
+        raise ValueError("Type error: 'skip_dirs' must be a list instance")
+    with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zf:
         for file in src_path.rglob('*'):
             skip = False
             _parts = file.relative_to(src_path).parts
@@ -377,75 +369,76 @@ def make_zip_archive(zip_file, src, skip_dirs=None):
                 zf.write(file, file.relative_to(src_path.parent))
 
 
-def doc_file(project_dir, project_file_entry):
-    """Return the (optional) .rstinc file describing a source file."""
+def get_rstinc_for_file(project_dir: Path, project_file: Path) -> Path | None:
+    """Return the .rstinc file in the doc folder describing a source file, if found"""
     rst_file = project_dir
-    if rst_file.name != "doc":  # Special case: Dummy .pyproject file in doc dir
+    if project_dir.name != "doc":  # Special case: Dummy .pyproject file in doc dir
         rst_file /= "doc"
-    rst_file /= Path(project_file_entry).name + ".rstinc"
+    rst_file /= project_file.name + ".rstinc"
     return rst_file if rst_file.is_file() else None
 
 
-def get_code_tabs(files, project_dir, file_format):
+def get_code_tabs(files: list[Path], project_dir: Path, file_format: Format) -> str:
+    """
+    Return the string which contains the code tabs source for the example
+    Also creates a .zip file for downloading the source files
+    """
     content = "\n"
 
     # Prepare ZIP file, and copy to final destination
-    # Handle examples which only have a dummy pyproject file in the "doc" dir
-    zip_root = project_dir.parent if project_dir.name == "doc" else project_dir
-    zip_name = f"{zip_root.name}.zip"
-    make_zip_archive(EXAMPLES_DOC / zip_name, zip_root, skip_dirs=["doc"])
+    zip_name = f"{project_dir.name}.zip"
+    make_zip_archive(EXAMPLES_DOC / zip_name, project_dir, skip_dirs=["doc"])
 
     if file_format == Format.RST:
         content += f":download:`Download this example <{zip_name}>`\n\n"
-    else:
+    elif file_format == Format.MD:
         content += f"{{download}}`Download this example <{zip_name}>`\n\n"
+        # MD files wrap the content in a eval-rst block
         content += "```{eval-rst}\n"
+    else:
+        raise ValueError(f"Unknown documentation file format {file_format}")
 
-    for i, project_file in enumerate(files):
-        if i == 0:
-            content += ".. tab-set::\n\n"
+    if files:
+        content += ".. tab-set::\n\n"
 
-        pfile = Path(project_file)
-        if pfile.suffix in IGNORED_SUFFIXES:
+    for i, file in enumerate(files):
+        if file.suffix in IGNORED_SUFFIXES:
             continue
 
-        content += f"{ind(1)}.. tab-item:: {project_file}\n\n"
+        try:
+            tab_title = file.relative_to(project_dir).as_posix()
+        except ValueError:
+            # The file is outside project_dir, so the best we can do is to use the file name
+            tab_title = file.name
+
+        content += f"{ind(1)}.. tab-item:: {tab_title}\n\n"
 
-        doc_rstinc_file = doc_file(project_dir, project_file)
-        if doc_rstinc_file:
-            indent = ind(2)
-            for line in doc_rstinc_file.read_text("utf-8").split("\n"):
-                content += indent + line + "\n"
+        if doc_rstinc_file := get_rstinc_for_file(project_dir, file):
+            content += add_indent(doc_rstinc_file.read_text("utf-8"), 2)
             content += "\n"
 
-        lexer = get_lexer(pfile)
-        content += add_indent(f"{ind(1)}.. code-block:: {lexer}", 1)
+        content += add_indent(f"{ind(1)}.. code-block:: {get_lexer(file)}", 1)
         content += "\n"
 
-        _path = project_dir / project_file
-        _file_content = ""
         try:
-            with open(_path, "r", encoding="utf-8") as _f:
-                _file_content = remove_licenses(_f.read())
+            file_content = remove_licenses(file.read_text(encoding="utf-8"))
         except UnicodeDecodeError as e:
-            print(f"example_gallery: error decoding {project_dir}/{_path}:{e}",
-                  file=sys.stderr)
-            raise
+            raise RuntimeError(f"example_gallery: error decoding {file}: {e}")
         except FileNotFoundError as e:
-            print(f"example_gallery: error opening {project_dir}/{_path}:{e}",
-                  file=sys.stderr)
-            raise
+            raise RuntimeError(f"example_gallery: error opening {file}: {e}")
 
-        content += add_indent(_file_content, 3)
+        content += add_indent(file_content, 3)
         content += "\n\n"
 
     if file_format == Format.MD:
+        # Close the eval-rst block
         content += "```"
 
     return content
 
 
-def get_header_title(example_dir):
+def get_default_header_title(example_dir: Path) -> str:
+    """Get a default header title for an example directory without a doc file"""
     _index = example_dir.parts.index("examples")
     rel_path = "/".join(example_dir.parts[_index:])
     _title = rel_path
@@ -459,24 +452,29 @@ def get_header_title(example_dir):
     )
 
 
-def rel_path(from_path, to_path):
-    """Determine relative paths for paths that are not subpaths (where
-       relative_to() fails) via a common root."""
-    common = Path(*os.path.commonprefix([from_path.parts, to_path.parts]))
-    up_dirs = len(from_path.parts) - len(common.parts)
+def rel_path(from_path: Path, to_path: Path) -> str:
+    """
+    Get a relative path for a given path that is not a subpath (where Path.relative_to() fails)
+    of from_path via a common ancestor path
+
+    For example: from_path = Path("/a/b/c/d"), to_path = Path("/a/b/e/f"). Returns: "../../e/f"
+    """
+    common_path = Path(*os.path.commonprefix([from_path.parts, to_path.parts]))
+    up_dirs = len(from_path.parts) - len(common_path.parts)
     prefix = up_dirs * "../"
-    rel_to_common = os.fspath(to_path.relative_to(common))
-    return f"{prefix}{rel_to_common}"
+    relative_to_common = to_path.relative_to(common_path).as_posix()
+    return f"{prefix}{relative_to_common}"
 
 
-def read_rst_file(project_dir, project_files, doc_rst):
-    """Read the example .rst file and expand literal includes to project files
-       by relative paths to the example directory. Note: sphinx does not
-       handle absolute paths as expected, they need to be relative."""
-    content = ""
-    with open(doc_rst, encoding="utf-8") as doc_f:
-        content = doc_f.read()
+def read_rst_file(project_dir: Path, project_files: list[Path], doc_rst: Path) -> str:
+    """
+    Read the example .rst file and replace Sphinx literal includes of project files by paths
+    relative to the example directory
+    Note: Sphinx does not handle absolute paths as expected, they need to be relative
+    """
+    content = Path(doc_rst).read_text(encoding="utf-8")
     if LITERAL_INCLUDE not in content:
+        # The file does not contain any literal includes, so we can return it as is
         return content
 
     result = []
@@ -484,14 +482,16 @@ def read_rst_file(project_dir, project_files, doc_rst):
     for line in content.split("\n"):
         if line.startswith(LITERAL_INCLUDE):
             file = line[len(LITERAL_INCLUDE) + 1:].strip()
-            if file in project_files:
-                line = f"{LITERAL_INCLUDE} {path_to_example}/{file}"
+            file_path = project_dir / file
+            if file_path not in project_files:
+                raise RuntimeError(f"File {file} not found in project files: {project_files}")
+            line = f"{LITERAL_INCLUDE} {path_to_example}/{file}"
         result.append(line)
     return "\n".join(result)
 
 
-def get_headline(text, file_format):
-    """Find the headline in the .rst file."""
+def get_headline(text: str, file_format: Format) -> str:
+    """Find the headline in the documentation file."""
     if file_format == Format.RST:
         underline = text.find("\n====")
         if underline != -1:
@@ -503,23 +503,32 @@ def get_headline(text, file_format):
             new_line = text.find("\n", headline + 1)
             if new_line != -1:
                 return text[headline + 2:new_line].strip()
+    else:
+        raise ValueError(f"Unknown file format {file_format}")
     return ""
 
 
-def get_doc_source_file(original_doc_dir, example_name):
-    """Find the doc source file, return (Path, Format)."""
-    if original_doc_dir.is_dir():
-        for file_format in (Format.RST, Format.MD):
-            suffix = SUFFIXES[file_format]
-            result = original_doc_dir / f"{example_name}.{suffix}"
-            if result.is_file():
-                return result, file_format
-    return None, Format.RST
+def get_doc_source_file(original_doc_dir: Path, example_name: str) -> tuple[Path, Format] | None:
+    """
+    Find the doc source file given a doc directory and an example name
+    Returns the doc file path and the file format, if found
+    """
+    if not original_doc_dir.is_dir():
+        return None
+
+    for file_format, suffix in DOC_SUFFIXES.items():
+        result = original_doc_dir / f"{example_name}.{suffix}"
+        if result.is_file():
+            return result, file_format
+    return None
 
 
-def get_screenshot(image_dir, example_name):
-    """Find screen shot: We look for an image with the same
-       example_name first, if not, we select the first."""
+def get_screenshot(image_dir: Path, example_name: str) -> Path | None:
+    """
+    Find an screenshot given an image directory and the example name
+    Returns the image with the same example_name, if found
+    If not found, the first image in the directory is returned
+    """
     if not image_dir.is_dir():
         return None
     images = [i for i in image_dir.glob("*") if i.is_file() and check_img_ext(i)]
@@ -531,36 +540,30 @@ def get_screenshot(image_dir, example_name):
     return None
 
 
-def write_resources(src_list, dst):
+def write_resources(src_list: list[Path], dst: Path):
     """Write a list of example resource paths to the dst path."""
     for src in src_list:
         resource_written = shutil.copy(src, dst / src.name)
         if not opt_quiet:
-            print("Written resource:", resource_written)
+            print(f"Written resource: {resource_written}")
 
 
 @dataclass
 class ExampleParameters:
     """Parameters obtained from scanning the examples directory."""
-
-    def __init__(self):
-        self.file_format = Format.RST
-        self.src_doc_dir = self.src_doc_file_path = self.src_screenshot = None
-        self.extra_names = ""
-
-    example_dir: Path
-    module_name: str
-    example_name: str
-    extra_names: str
-    file_format: Format
-    target_doc_file: str
-    src_doc_dir: Path
-    src_doc_file_path: Path
-    src_screenshot: Path
-
-
-def detect_pyside_example(example_root, pyproject_file):
-    """Detemine parameters of a PySide example."""
+    example_dir: Path = None
+    module_name: str = ""
+    example_name: str = ""
+    target_doc_file: str = None
+    extra_names: str = ""
+    src_doc_dir: Path = None
+    src_doc_file_path: Path = None
+    src_screenshot: Path = None
+    file_format: Format = Format.RST
+
+
+def get_pyside_example_parameters(example_root: Path, pyproject_file: Path) -> ExampleParameters:
+    """Analyze a PySide example folder to get the example parameters"""
     p = ExampleParameters()
 
     p.example_dir = pyproject_file.parent
@@ -568,7 +571,7 @@ def detect_pyside_example(example_root, pyproject_file):
         # Design Studio project example
         p.example_dir = pyproject_file.parent.parent
 
-    if p.example_dir.name == "doc":  # Dummy pyproject in doc dir (scriptableapplication)
+    if p.example_dir.name == "doc":  # Dummy pyproject file in doc dir (e.g. scriptableapplication)
         p.example_dir = p.example_dir.parent
 
     parts = p.example_dir.parts[len(example_root.parts):]
@@ -581,21 +584,23 @@ def detect_pyside_example(example_root, pyproject_file):
     src_doc_dir = p.example_dir / "doc"
 
     if src_doc_dir.is_dir():
-        src_doc_file_path, fmt = get_doc_source_file(src_doc_dir, p.example_name)
-        if src_doc_file_path:
-            p.src_doc_file_path = src_doc_file_path
-            p.file_format = fmt
+        src_doc_file = get_doc_source_file(src_doc_dir, p.example_name)
+        if src_doc_file:
+            p.src_doc_file_path, p.file_format = src_doc_file
             p.src_doc_dir = src_doc_dir
             p.src_screenshot = get_screenshot(src_doc_dir, p.example_name)
 
-    target_suffix = SUFFIXES[p.file_format]
+    target_suffix = DOC_SUFFIXES[p.file_format]
     doc_file = f"example_{p.module_name}_{p.extra_names}_{p.example_name}.{target_suffix}"
     p.target_doc_file = doc_file.replace("__", "_")
     return p
 
 
-def detect_qt_example(example_root, pyproject_file):
-    """Detemine parameters of an example from a Qt repository."""
+def get_qt_example_parameters(pyproject_file: Path) -> ExampleParameters:
+    """
+    Analyze a Qt repository example to get its parameters.
+    For instance, the qtdoc/examples/demos/mediaplayer example
+    """
     p = ExampleParameters()
 
     p.example_dir = pyproject_file.parent
@@ -604,142 +609,155 @@ def detect_qt_example(example_root, pyproject_file):
     # Check for a 'doc' directory inside the example (qdoc)
     doc_root = p.example_dir / "doc"
     if doc_root.is_dir():
-        src_doc_file_path, fmt = get_doc_source_file(doc_root / "src", p.example_name)
-        if src_doc_file_path:
-            p.src_doc_file_path = src_doc_file_path
-            p.file_format = fmt
+        src_doc_file = get_doc_source_file(doc_root / "src", p.example_name)
+        if src_doc_file:
+            p.src_doc_file_path, p.file_format = src_doc_file
             p.src_doc_dir = doc_root
             p.src_screenshot = get_screenshot(doc_root / "images", p.example_name)
-
-    target_suffix = SUFFIXES[p.file_format]
+        else:
+            raise ValueError(f"No source file found in {doc_root} / src given {p.example_name}")
+    else:
+        raise ValueError(f"No doc directory found in {p.example_dir}")
+    target_suffix = DOC_SUFFIXES[p.file_format]
     p.target_doc_file = f"example_qtdemos_{p.example_name}.{target_suffix}"
     return p
 
 
-def write_example(example_root, pyproject_file, pyside_example=True):
-    """Read the project file and documentation, create the .rst file and
-       copy the data. Return a tuple of module name and a dict of example data."""
-    p = (detect_pyside_example(example_root, pyproject_file) if pyside_example
-         else detect_qt_example(example_root, pyproject_file))
+def write_example(
+    example_root: Path, pyproject_file: Path, pyside_example: bool = True
+) -> tuple[str, ExampleData]:
+    """
+    Read the project file and documentation, create the .rst file and copy the example data
+    Return a tuple with the module name and an ExampleData instance
+    """
+    # Get the example parameters depending on whether it is a PySide example or a Qt one
+    p: ExampleParameters = (
+        get_pyside_example_parameters(example_root, pyproject_file)
+        if pyside_example else get_qt_example_parameters(pyproject_file))
 
     result = ExampleData()
-    result.example = p.example_name
+    result.example_name = p.example_name
     result.module = p.module_name
     result.extra = p.extra_names
     result.doc_file = p.target_doc_file
     result.file_format = p.file_format
     result.abs_path = str(p.example_dir)
-    result.has_doc = bool(p.src_doc_file_path)
+    result.src_doc_file = p.src_doc_file_path
     result.img_doc = p.src_screenshot
-    result.tutorial = tutorial_headline(result.abs_path)
-
-    files = []
-    try:
-        with pyproject_file.open("r", encoding="utf-8") as pyf:
-            pyproject = json.load(pyf)
-            # iterate through the list of files in .pyproject and
-            # check if they exist, before appending to the list.
-            for f in pyproject["files"]:
-                if not Path(f).exists:
-                    print(f"example_gallery: {f} listed in {pyproject_file} does not exist")
-                    raise FileNotFoundError
-                else:
-                    files.append(f)
-    except (json.JSONDecodeError, KeyError, FileNotFoundError) as e:
-        print(f"example_gallery: error reading {pyproject_file}: {e}")
-        raise
+    result.tutorial = TUTORIAL_HEADLINES.get(result.abs_path, "")
 
+    if pyproject_file.match(PYPROJECT_JSON_PATTERN):
+        pyproject_parse_result = parse_pyproject_json(pyproject_file)
+    elif pyproject_file.match(PYPROJECT_TOML_PATTERN):
+        pyproject_parse_result = parse_pyproject_toml(pyproject_file)
+    else:
+        raise RuntimeError(f"Invalid project file: {pyproject_file}")
+
+    if pyproject_parse_result.errors:
+        raise RuntimeError(f"Error reading {pyproject_file}: {pyproject_parse_result.errors}")
+
+    for file in pyproject_parse_result.files:
+        if not Path(file).exists:
+            raise FileNotFoundError(f"{file} listed in {pyproject_file} does not exist")
+
+    files = pyproject_parse_result.files
     headline = ""
     if files:
         doc_file = EXAMPLES_DOC / p.target_doc_file
-        sphnx_ref_example = p.target_doc_file.replace(f'.{SUFFIXES[p.file_format]}', '')
+        sphnx_ref_example = p.target_doc_file.replace(f'.{DOC_SUFFIXES[p.file_format]}', '')
         # lower case sphinx reference
         # this seems to be a bug or a requirement from sphinx
         sphnx_ref_example = sphnx_ref_example.lower()
-        content_f = ""
+
         if p.file_format == Format.RST:
             content_f = f".. _{sphnx_ref_example}:\n\n"
         elif p.file_format == Format.MD:
             content_f = f"({sphnx_ref_example})=\n\n"
         else:
-            print(f"example_gallery: Invalid file format {p.file_format}", file=sys.stderr)
-            raise ValueError
+            raise ValueError(f"Invalid file format {p.file_format}")
 
         with open(doc_file, "w", encoding="utf-8") as out_f:
             if p.src_doc_file_path:
                 content_f += read_rst_file(p.example_dir, files, p.src_doc_file_path)
                 headline = get_headline(content_f, p.file_format)
-                if not headline:
-                    print(f"example_gallery: No headline found in {doc_file}",
-                          file=sys.stderr)
+                if not headline and not opt_quiet:
+                    print(f"example_gallery: No headline found in {doc_file}", file=sys.stderr)
 
-                # Copy other files in the 'doc' directory, but
-                # excluding the main '.rst' file and all the
-                # directories.
+                # Copy other files in the 'doc' directory, but excluding the main '.rst' file and
+                # all the directories
                 resources = []
                 if pyside_example:
                     for _f in p.src_doc_dir.glob("*"):
                         if _f != p.src_doc_file_path and not _f.is_dir():
                             resources.append(_f)
-                else:  # Qt example: only use image.
-                    if p.src_screenshot:
-                        resources.append(p.src_screenshot)
+                elif p.src_screenshot:
+                    # Qt example: only use image, if found
+                    resources.append(p.src_screenshot)
                 write_resources(resources, EXAMPLES_DOC)
             else:
-                content_f += get_header_title(p.example_dir)
-            content_f += get_code_tabs(files, pyproject_file.parent, p.file_format)
+                content_f += get_default_header_title(p.example_dir)
+            content_f += get_code_tabs(files, p.example_dir, p.file_format)
             out_f.write(content_f)
 
         if not opt_quiet:
             print(f"Written: {doc_file}")
     else:
         if not opt_quiet:
-            print("Empty '.pyproject' file, skipping")
+            print(f"{pyproject_file} does not contain any file, skipping")
 
     result.headline = headline
 
-    return (p.module_name, result)
+    return p.module_name, result
 
 
-def example_sort_key(example: ExampleData):
+def example_sort_key(example: ExampleData) -> str:
+    """
+    Return a key for sorting the examples. Tutorials are sorted first, then the examples which
+    contain "gallery" in their name, then alphabetically
+    """
     result = ""
     if example.tutorial:
         result += "AA:" + example.tutorial + ":"
-    elif "gallery" in example.example:
+    elif "gallery" in example.example_name:
         result += "AB:"
-    result += example.example
+    result += example.example_name
     return result
 
 
-def sort_examples(example):
+def sort_examples(examples: dict[str, list[ExampleData]]) -> dict[str, list[ExampleData]]:
+    """Sort the examples using a custom function."""
     result = {}
-    for module in example.keys():
-        result[module] = sorted(example.get(module), key=example_sort_key)
+    for module in examples.keys():
+        result[module] = sorted(examples.get(module), key=example_sort_key)
     return result
 
 
-def scan_examples_dir(examples_dir, pyside_example=True):
-    """Scan a directory of examples."""
-    for pyproject_file in examples_dir.glob("**/*.pyproject"):
-        if pyproject_file.name != "examples.pyproject":
-            module_name, data = write_example(examples_dir, pyproject_file,
-                                              pyside_example)
-            if module_name not in examples:
-                examples[module_name] = []
-            examples[module_name].append(data)
+def scan_examples_dir(
+    examples_dir: Path, pyside_example: bool = True
+) -> dict[str, list[ExampleData]]:
+    """
+    Scan a directory of examples and return a dictionary with the found examples grouped by module
+    Also creates the .rst file for each example
+    """
+    examples: dict[str, list[ExampleData]] = defaultdict(list)
+    # Find all the project files contained in the examples directory
+    project_files: list[Path] = []
+    for project_file_pattern in PYPROJECT_FILE_PATTERNS:
+        project_files.extend(examples_dir.glob(f"**/{project_file_pattern}"))
+
+    for project_file in project_files:
+        if project_file.name == "examples.pyproject":
+            # Ignore this project file. It contains files from many examples
+            continue
+
+        module_name, data = write_example(examples_dir, project_file, pyside_example)
+        examples[module_name].append(data)
 
+    return dict(examples)
 
-if __name__ == "__main__":
-    # Only examples with a '.pyproject' file will be listed.
-    DIR = Path(__file__).parent
-    EXAMPLES_DOC = Path(f"{DIR}/../../sources/pyside6/doc/examples").resolve()
-    EXAMPLES_DIR = Path(f"{DIR}/../../examples/").resolve()
-    BASE_URL = "https://code.qt.io/cgit/pyside/pyside-setup.git/tree"
-    columns = 5
-    gallery = ""
 
+if __name__ == "__main__":
     parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
-    TARGET_HELP = f"Directory into which to generate Doc files (default: {str(EXAMPLES_DOC)})"
     parser.add_argument("--target", "-t", action="store", dest="target_dir", help=TARGET_HELP)
     parser.add_argument("--qt-src-dir", "-s", action="store", help="Qt source directory")
     parser.add_argument("--quiet", "-q", action="store_true", help="Quiet")
@@ -748,55 +766,43 @@ if __name__ == "__main__":
     if options.target_dir:
         EXAMPLES_DOC = Path(options.target_dir).resolve()
 
-    # This main loop will be in charge of:
-    #   * Getting all the .pyproject files,
-    #   * Gather the information of the examples and store them in 'examples'
-    #   * Read the .pyproject file to output the content of each file
-    #     on the final .rst file for that specific example.
-    examples = {}
+    # This script will be in charge of:
+    #   * Getting all the project files
+    #   * Gather the information of the examples
+    #   * Read the project file to output the content of each source file
+    #     on the final .rst file for that specific example
 
     # Create the 'examples' directory if it doesn't exist
-    # If it does exist, remove it and create a new one to start fresh
+    # If it does exist, try to remove it and create a new one to start fresh
     if EXAMPLES_DOC.is_dir():
         shutil.rmtree(EXAMPLES_DOC, ignore_errors=True)
         if not opt_quiet:
-            print("WARNING: Deleted old html directory")
+            print("WARNING: Deleted existing examples HTML directory")
     EXAMPLES_DOC.mkdir(exist_ok=True)
 
-    scan_examples_dir(EXAMPLES_DIR)
+    examples = scan_examples_dir(EXAMPLES_DIR)
+
     if options.qt_src_dir:
+        # Scan the Qt source directory for Qt examples and include them in the dictionary of
+        # discovered examples
         qt_src = Path(options.qt_src_dir)
         if not qt_src.is_dir():
-            print("Invalid Qt source directory: {}", file=sys.stderr)
-            sys.exit(-1)
-        scan_examples_dir(qt_src.parent / "qtdoc", pyside_example=False)
+            raise RuntimeError(f"Invalid Qt source directory: {qt_src}")
+        examples.update(scan_examples_dir(qt_src.parent / "qtdoc", pyside_example=False))
 
     examples = sort_examples(examples)
 
-    # We generate a 'toctree' at the end of the file, to include the new
-    # 'example' rst files, so we get no warnings, and also that users looking
-    # for them will be able to, since they are indexed.
-    # Notice that :hidden: will not add the list of files by the end of the
-    # main examples HTML page.
-    footer_index = dedent(
-        """\
-      .. toctree::
-         :hidden:
-         :maxdepth: 1
-
-        """
-    )
-
-    # Writing the main example rst file.
-    index_files = []
+    # List of all the example files found to be included in the index table of contents
+    index_files: list[str] = []
+    # Write the main example .rst file and the example files
     with open(f"{EXAMPLES_DOC}/index.rst", "w") as f:
         f.write(BASE_CONTENT)
         for module_name in sorted(examples.keys(), key=module_sort_key):
-            e = examples.get(module_name)
-            tutorial_examples = defaultdict(list)
-            non_tutorial_examples = []
+            module_examples = examples.get(module_name)
+            tutorial_examples: DefaultDict[str, list[ExampleData]] = defaultdict(list)
+            non_tutorial_examples: list[ExampleData] = []
 
-            for example in e:
+            for example in module_examples:
                 index_files.append(example.doc_file)
                 if example.tutorial:
                     tutorial_examples[example.tutorial].append(example)
@@ -817,12 +823,14 @@ if __name__ == "__main__":
                 f.write(get_module_gallery(non_tutorial_examples))
             # If no tutorials exist, list all examples
             elif not tutorial_examples:
-                f.write(get_module_gallery(e))
+                f.write(get_module_gallery(module_examples))
 
         f.write("\n\n")
-        f.write(footer_index)
-        for i in index_files:
-            f.write(f"   {i}\n")
+
+        # Add the list of the example files found to the index table of contents
+        f.write(FOOTER_INDEX)
+        for index_file in index_files:
+            f.write(f"{ind(1)}{index_file}\n")
 
     if not opt_quiet:
         print(f"Written index: {EXAMPLES_DOC}/index.rst")
index 77ce4742072f662d704fc351d9c502953f2afeba..1fd0fd9a9c819dc9aea7a4311f7769b74a8d3dd0 100644 (file)
@@ -36,17 +36,27 @@ This section contains the release notes for different versions of Qt for Python.
 """
 
 
+JIRA_URL = "https://qt-project.atlassian.net/browse/"
+
+
 class Changelog:
+
+    # for matching lines like *    PySide6    * to identify the section
+    section_pattern = re.compile(r"\* +(\w+) +\*")
+    # for line that start with ' -' which lists the changes
+    line_pattern = re.compile(r"^ -")
+    # for line that contains a bug report like PYSIDE-<bug_number>
+    bug_number_pattern = re.compile(r"\[PYSIDE-\d+\]")
+    # version from changelog file name
+    version_pattern = re.compile(r"changes-(\d+)\.(\d+)\.(\d+)")
+
     def __init__(self, file_path: Path):
         self.file_path = file_path
-        self.version = file_path.name.split("-")[-1]
+        version_match = self.version_pattern.match(file_path.name)
+        assert (version_match)
+        self.version = (int(version_match.group(1)), int(version_match.group(2)),
+                        int(version_match.group(3)))
         self.sections = {section: [] for section in SECTION_NAMES}
-        # for matching lines like *    PySide6    * to identify the section
-        self.section_pattern = re.compile(r"\* +(\w+) +\*")
-        # for line that start with ' -' which lists the changes
-        self.line_pattern = re.compile(r"^ -")
-        # for line that contains a bug report like PYSIDE-<bug_number>
-        self.bug_number_pattern = re.compile(r"\[PYSIDE-\d+\]")
 
     def add_line(self, section, line):
         self.sections[section].append(line)
@@ -94,8 +104,7 @@ class Changelog:
                     # remove the square brackets
                     actual_bug_number = bug_number[1:-1]
                     bug_number_replacement = (
-                        f"[{actual_bug_number}]"
-                        f"(https://bugreports.qt.io/browse/{actual_bug_number})"
+                        f"[{actual_bug_number}]({JIRA_URL}/{actual_bug_number})"
                     )
                     line = re.sub(re.escape(bug_number), bug_number_replacement, line)
 
@@ -144,7 +153,8 @@ def write_md_file(section: str, changelogs: list[Changelog], output_dir: Path):
         for changelog in changelogs:
             section_contents = changelog.parsed_sections()[section]
             if section_contents:
-                file.write(f"## {changelog.version}\n\n")
+                v = changelog.version
+                file.write(f"## {v[0]}.{v[1]}.{v[2]}\n\n")
                 for lines in section_contents:
                     # separate each line with a newline
                     file.write(f"{lines}\n")
diff --git a/tools/sync_examples.py b/tools/sync_examples.py
new file mode 100644 (file)
index 0000000..c2715f4
--- /dev/null
@@ -0,0 +1,199 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import os
+import shutil
+import sys
+from pathlib import Path
+from argparse import ArgumentParser, RawDescriptionHelpFormatter
+
+USAGE = """
+Updates example images, shaders, *.qml, *.ui, *.qrc and qmldir files from
+a Qt source tree.
+
+Check the diffs produced with care ("prefer" in qmldir, QML module
+definitions).
+"""
+
+BINARY_SUFFIXES = ["jpg", "png", "svgz", "webp"]
+TEXT_SUFFIXES = ["frag", "qrc", "qml", "svg", "ui", "vert"]
+SUFFIXES = BINARY_SUFFIXES + TEXT_SUFFIXES
+
+
+QML_SIMPLE_TUTORIAL_NAMES = ["chapter1-basics", "chapter2-methods",
+                             "chapter3-bindings", "chapter4-customPropertyTypes",
+                             "chapter5-listproperties", "chapter6-plugins"]
+QML_SIMPLE_TUTORIALS = ["qml/tutorials/extending-qml/" + n for n in QML_SIMPLE_TUTORIAL_NAMES]
+
+QML_ADVANCED_TUTORIAL_NAMES = ["advanced1-Base-project", "advanced2-Inheritance-and-coercion",
+                               "advanced3-Default-properties", "advanced4-Grouped-properties",
+                               "advanced5-Attached-properties", "advanced6-Property-value-source"]
+QML_ADVANCED_TUTORIALS = ["qml/tutorials/extending-qml-advanced/" + n
+                          for n in QML_ADVANCED_TUTORIAL_NAMES]
+
+EXAMPLE_MAPPING = {
+    "qtbase": ["corelib/ipc/sharedmemory", "gui/rhiwindow", "sql/books",
+               "widgets/animation/easing", "widgets/rhi/simplerhiwidget"],
+    "qtconnectivity": ["bluetooth/heartrate_game", "bluetooth/lowenergyscanner"],
+    "qtdeclarative": (QML_SIMPLE_TUTORIALS + QML_ADVANCED_TUTORIALS
+                      + ["quick/models/stringlistmodel", "quick/models/objectlistmodel",
+                         "quick/window",
+                         "quick/rendercontrol/rendercontrol_opengl",
+                         "quick/scenegraph/openglunderqml",
+                         "quick/scenegraph/scenegraph_customgeometry",
+                         "quick/customitems/painteditem",
+                         "quickcontrols/filesystemexplorer", "quickcontrols/gallery"]),
+    "qtgraphs": ["graphs/2d/hellographs", "graphs/3d/bars", "graphs/3d/widgetgraphgallery"],
+    "qtlocation": ["location/mapviewer"],
+    "qtmultimedia": ["multimedia/camera"],
+    "qtquick3d": ["quick3d/customgeometry", "quick3d/intro", "quick3d/proceduraltexture"],
+    "qtserialbus": ["serialbus/can", "serialbus/modbus/modbusclient"],
+    "qtserialport": ["serialport/terminal"],
+    "qtspeech": ["speech/hello_speak"],
+    "qtwebchannel": ["webchannel/standalone"],
+    "qtwebengine": ["pdfwidgets/pdfviewer", "webenginequick/nanobrowser",
+                    "webenginewidgets/notifications", "webenginewidgets/simplebrowser"],
+    "qtwebview": ["webview/minibrowser"],
+}
+
+
+file_count = 0
+updated_file_count = 0
+new_file_count = 0
+warnings_count = 0
+
+
+def pyside_2_qt_example(e):
+    """Fix some example names differing in PySide."""
+    if "heartrate" in e:
+        return e.replace("heartrate_", "heartrate-")
+    if e == "webenginequick/nanobrowser":
+        return "webenginequick/quicknanobrowser"
+    if e.endswith("scenegraph_customgeometry"):
+        return e.replace("scenegraph_customgeometry", "customgeometry")
+    if e.endswith("modbusclient"):
+        return e.replace("modbusclient", "client")
+    return e
+
+
+def files_differ(p1, p2):
+    return (p1.stat().st_size != p2.stat().st_size
+            or p1.read_bytes() != p2.read_bytes())
+
+
+def use_file(path):
+    """Exclude C++ docs and Qt Creator builds."""
+    path_str = os.fspath(path)
+    return "/doc/" not in path_str and "_install_" not in path_str
+
+
+def example_sources(qt_example):
+    """Retrieve all update-able files of a Qt C++ example."""
+    result = []
+    for suffix in SUFFIXES:
+        for file in qt_example.glob(f"**/*.{suffix}"):
+            if use_file(file):
+                result.append(file)
+    for file in qt_example.glob("**/qmldir*"):
+        if use_file(file):
+            result.append(file)
+    return result
+
+
+def detect_qml_module(pyside_example, sources):
+    """Detect the directory of a QML module of a PySide example.
+       While in Qt C++, the QML module's .qml files are typically
+       located in the example root, PySide has an additional directory
+       since it loads the QML files from the file system.
+       Read the qmldir file and check whether a module directory exists."""
+    qml_dir_file = None
+    for source in sources:
+        if source.name.startswith("qmldir"):  # "qmldir"/"qmldir.in"
+            qml_dir_file = source
+            break
+    if not qml_dir_file:
+        return None
+    for line in qml_dir_file.read_text(encoding="utf-8").split("\n"):
+        if line.startswith("module "):
+            module = line[7:].strip()
+            if (pyside_example / module).is_dir():
+                return module
+            break
+    return None
+
+
+def sync_example(pyside_example, qt_example, dry_run):
+    """Update files of a PySide example."""
+    global file_count, updated_file_count, new_file_count, warnings_count
+    sources = example_sources(qt_example)
+    source_count = len(sources)
+    if source_count == 0:
+        print(f"No sources found in {qt_example}", file=sys.stderr)
+        return
+    count = 0
+    qml_module = detect_qml_module(pyside_example, sources)
+    for source in sources:
+        rel_source = source.relative_to(qt_example)
+        target = pyside_example / rel_source
+        if qml_module and not target.is_file():
+            target = pyside_example / qml_module / rel_source
+        if target.is_file():
+            if files_differ(source, target):
+                if not dry_run:
+                    shutil.copy(source, target)
+                count += 1
+        else:
+            print(f"{qt_example.name}: {rel_source} does not have an equivalent "
+                  "PySide file, skipping", file=sys.stderr)
+            warnings_count += 1
+            new_file_count += 1
+    if count > 0:
+        print(f"  {qt_example.name:<30}: Updated {count}/{source_count} files(s)")
+    else:
+        print(f"  {qt_example.name:<30}: Unchanged, {source_count} files(s)")
+    file_count += source_count
+    updated_file_count += count
+
+
+def main():
+    global warnings_count
+    parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter,
+                            description=USAGE)
+    parser.add_argument("--dry-run", action="store_true", help="show the files to be updated")
+    parser.add_argument('qtsource', nargs=1)
+    args = parser.parse_args()
+    dry_run = args.dry_run
+    qt_source = Path(args.qtsource[0])
+    if not qt_source.is_dir():
+        raise Exception(f"{qt_source} is not a directory")
+
+    pyside_examples = Path(__file__).parents[1].resolve() / "examples"
+    print(qt_source, '->', pyside_examples)
+
+    for qt_module, example_list in EXAMPLE_MAPPING.items():
+        for example in example_list:
+            pyside_example = pyside_examples / example
+            qt_example = (qt_source / qt_module / "examples"
+                          / pyside_2_qt_example(example))
+            if pyside_example.is_dir() and qt_example.is_dir():
+                sync_example(pyside_example, qt_example, dry_run)
+            else:
+                print(f"Invalid mapping {qt_example} -> {pyside_example}",
+                      file=sys.stderr)
+                warnings_count += 1
+    msg = f"Updated {updated_file_count}/{file_count} file(s)"
+    if new_file_count:
+        msg += f", {new_file_count} new files(s)"
+    if warnings_count:
+        msg += f", {warnings_count} warning(s)"
+    print(f"\n{msg}.\n")
+    return 0
+
+
+if __name__ == "__main__":
+    r = -1
+    try:
+        r = main()
+    except Exception as e:
+        print(str(e), file=sys.stderr)
+    sys.exit(r)
index ad9f12c4381542ec21eeb2dce63a7cfaab85287e..b652f3e26e5476f40d86db8e5c7bf012a102c249 100644 (file)
@@ -10,7 +10,7 @@ authors = [
 description = "PROJECT_DESCRIPTION"
 readme = "PROJECT_README"
 dynamic = ["version"]
-requires-python = ">=3.9, <3.14"
+requires-python = ">=3.9, <3.15"
 keywords = ["Qt"]
 license = {text="LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"}
 dependencies = "PROJECT_DEPENDENCIES"
@@ -34,6 +34,7 @@ classifiers = [
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
+    "Programming Language :: Python :: 3.14",
     "Topic :: Database",
     "Topic :: Software Development",
     "Topic :: Software Development :: Code Generators",