diff options
46 files changed, 776 insertions, 240 deletions
diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 949eecad3..38bab3e31 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -328,6 +328,110 @@ instructions: condition: property property: host.osVersion not_in_values: [RHEL_8_6, RHEL_8_8, RHEL_8_10] + + + - type: ExecuteCommand + command: "pyenv install 3.11.9" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + ignoreExitCode: true + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + in_values: [MacOS_13, MacOS_14] + - condition: property + property: host.arch + equals_value: ARM64 + userMessageOnFailure: > + Failed to install python 3.11 + - type: PrependToEnvironmentVariable + variableName: PATH + variableValue: "/Users/qt/.pyenv/versions/3.11.9/bin:" + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + in_values: [MacOS_13, MacOS_14] + - condition: property + property: host.arch + equals_value: ARM64 + - type: EnvironmentVariable + variableName: interpreter + variableValue: "python3.11" + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + in_values: [MacOS_13, MacOS_14] + - condition: property + property: host.arch + equals_value: ARM64 + + - type: ExecuteCommand + 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 yum -y install python3.11-devel python3.11-pip" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.osVersion + equals_value: RHEL_9_4 + userMessageOnFailure: > + Failed to install python 3.11 + + - type: EnvironmentVariable + variableName: interpreter + variableValue: "python3.11" + enable_if: + condition: property + property: host.osVersion + equals_value: RHEL_9_4 + # ToDo: can be removed after 3.11 is available on qt5#3.8 on macOS # start of ToDo - type: ExecuteCommand diff --git a/coin/instructions_utils.py b/coin/instructions_utils.py index 52c8211e0..0337a0a4c 100644 --- a/coin/instructions_utils.py +++ b/coin/instructions_utils.py @@ -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. diff --git a/doc/changelogs/changes-6.9.3 b/doc/changelogs/changes-6.9.3 new file mode 100644 index 000000000..b0f55fcfc --- /dev/null +++ b/doc/changelogs/changes-6.9.3 @@ -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. diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py index b0ab567d8..cadfe2d09 100644 --- a/examples/multimedia/audiooutput/audiooutput.py +++ b/examples/multimedia/audiooutput/audiooutput.py @@ -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}") diff --git a/examples/multimedia/audiosource/audiosource.py b/examples/multimedia/audiosource/audiosource.py index 1c0d98412..807a70526 100644 --- a/examples/multimedia/audiosource/audiosource.py +++ b/examples/multimedia/audiosource/audiosource.py @@ -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) diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py index 23fdbb4cc..985996115 100644 --- a/examples/multimedia/player/player.py +++ b/examples/multimedia/player/player.py @@ -10,7 +10,7 @@ from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, QMainWindow, QSlider, QStyle, QToolBar) from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat, - QMediaPlayer, QAudio) + QMediaPlayer, QtAudio) from PySide6.QtMultimediaWidgets import QVideoWidget @@ -186,9 +186,9 @@ class MainWindow(QMainWindow): @Slot() def setVolume(self): - self.volumeValue = QAudio.convertVolume(self._volume_slider.value() / 100.0, - QAudio.VolumeScale.LogarithmicVolumeScale, - QAudio.VolumeScale.LinearVolumeScale) + self.volumeValue = QtAudio.convertVolume(self._volume_slider.value() / 100.0, + QtAudio.VolumeScale.LogarithmicVolumeScale, + QtAudio.VolumeScale.LinearVolumeScale) self._audio_output.setVolume(self.volumeValue) diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml index 7f7798ed8..36f2ac3b2 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/Main.qml @@ -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 diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml index 178bf03e4..0d308a2a1 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml @@ -16,7 +16,7 @@ ApplicationWindow { id: menuBar dragWindow: root - implicitHeight: 27 + implicitHeight: 30 infoText: "About Qt" } diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml index 80f7c04c5..2f995c88c 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Editor.qml @@ -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 diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml index 0df65bf82..5d3b68b35 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/ResizeButton.qml @@ -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 diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml index aac530394..f739e0f93 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/Sidebar.qml @@ -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 diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir index b1f684600..f94e68a8a 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir @@ -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 diff --git a/examples/webenginequick/nanobrowser/ApplicationRoot.qml b/examples/webenginequick/nanobrowser/ApplicationRoot.qml index f36249802..ec5716207 100644 --- a/examples/webenginequick/nanobrowser/ApplicationRoot.qml +++ b/examples/webenginequick/nanobrowser/ApplicationRoot.qml @@ -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; } } diff --git a/examples/webenginequick/nanobrowser/BrowserWindow.qml b/examples/webenginequick/nanobrowser/BrowserWindow.qml index 365d77d21..474968b87 100644 --- a/examples/webenginequick/nanobrowser/BrowserWindow.qml +++ b/examples/webenginequick/nanobrowser/BrowserWindow.qml @@ -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); + } + } } diff --git a/examples/webenginequick/nanobrowser/DownloadView.qml b/examples/webenginequick/nanobrowser/DownloadView.qml index b116ab867..ef0c7f5a7 100644 --- a/examples/webenginequick/nanobrowser/DownloadView.qml +++ b/examples/webenginequick/nanobrowser/DownloadView.qml @@ -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); } } } diff --git a/examples/webenginequick/nanobrowser/FindBar.qml b/examples/webenginequick/nanobrowser/FindBar.qml index 409d8dcff..013f28e88 100644 --- a/examples/webenginequick/nanobrowser/FindBar.qml +++ b/examples/webenginequick/nanobrowser/FindBar.qml @@ -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 } } } diff --git a/examples/webenginequick/nanobrowser/FullScreenNotification.qml b/examples/webenginequick/nanobrowser/FullScreenNotification.qml index 779406432..cdf154c78 100644 --- a/examples/webenginequick/nanobrowser/FullScreenNotification.qml +++ b/examples/webenginequick/nanobrowser/FullScreenNotification.qml @@ -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/rc_resources.py b/examples/webenginequick/nanobrowser/rc_resources.py index 990f10274..b517c39a0 100644 --- a/examples/webenginequick/nanobrowser/rc_resources.py +++ b/examples/webenginequick/nanobrowser/rc_resources.py @@ -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(): diff --git a/requirements-doc.txt b/requirements-doc.txt index 7e795c4f5..a7d4d5443 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -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 diff --git a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml index f62b1ec7d..cbb392761 100644 --- a/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml +++ b/sources/pyside6/PySide6/QtMultimedia/typesystem_multimedia.xml @@ -21,14 +21,9 @@ </namespace-type> <namespace-type name="QtAudio"> -<!-- FIXME PYSIDE7: Remove namespace "QAudio" and enable the enums. <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-import-enums"/> <modify-field name="NoError" remove="true"/> <modify-field name="OpenError" remove="true"/> <modify-field name="IOError" remove="true"/> diff --git a/sources/pyside6/PySide6/glue/qtmultimedia.cpp b/sources/pyside6/PySide6/glue/qtmultimedia.cpp index 65cefa5fc..457621638 100644 --- a/sources/pyside6/PySide6/glue/qtmultimedia.cpp +++ b/sources/pyside6/PySide6/glue/qtmultimedia.cpp @@ -26,15 +26,3 @@ const auto size = %CPPSELF.byteCount(); const float result = QtAudio::convertVolume(%1, %2, %3); %PYARG_0 = %CONVERTTOPYTHON[float](result); // @snippet qaudio-convertvolume - -// @snippet qtaudio-namespace-import-enums -Shiboken::AutoDecRef qAudio(PyObject_GetAttrString(module, "QAudio")); -if (!qAudio.isNull()) { - Shiboken::AutoDecRef qAudioTypeDict(PepType_GetDict(reinterpret_cast<PyTypeObject *>(qAudio.object()))); - Shiboken::AutoDecRef qtAudioTypeDict(PepType_GetDict(pyType)); - for (const auto *oldEnum : {"Error", "State", "VolumeScale"}) { - if (auto *enumType = PyDict_GetItemString(qAudioTypeDict, oldEnum)) - PyDict_SetItemString(qtAudioTypeDict, oldEnum, enumType); - } -} -// @snippet qtaudio-namespace-import-enums diff --git a/sources/pyside6/doc/conf.py.in b/sources/pyside6/doc/conf.py.in index 5af91019b..38c2c606c 100644 --- a/sources/pyside6/doc/conf.py.in +++ b/sources/pyside6/doc/conf.py.in @@ -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", diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp index 9c65eeb5e..9807e1ea6 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -538,6 +538,15 @@ void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom, } } + 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()) + ")."); @@ -827,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; } } @@ -861,10 +879,10 @@ std::optional<AbstractMetaEnum> 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); @@ -880,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)) { @@ -967,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, @@ -1192,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()); + } + } } } @@ -1706,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); diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h index 28b23c35b..0a09d578b 100644 --- a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h @@ -96,6 +96,12 @@ public: std::optional<AbstractMetaEnum> traverseEnum(const EnumModelItem &item, 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 ¤tClass); diff --git a/sources/shiboken6/ApiExtractor/addedfunction.cpp b/sources/shiboken6/ApiExtractor/addedfunction.cpp index ee8009cfe..649296066 100644 --- a/sources/shiboken6/ApiExtractor/addedfunction.cpp +++ b/sources/shiboken6/ApiExtractor/addedfunction.cpp @@ -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()) { diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp index 33f3f1ae6..cc2924287 100644 --- a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp +++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp @@ -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); } diff --git a/sources/shiboken6/ApiExtractor/documentation.h b/sources/shiboken6/ApiExtractor/documentation.h index 580d8f969..a623529ca 100644 --- a/sources/shiboken6/ApiExtractor/documentation.h +++ b/sources/shiboken6/ApiExtractor/documentation.h @@ -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); diff --git a/sources/shiboken6/ApiExtractor/enumtypeentry.h b/sources/shiboken6/ApiExtractor/enumtypeentry.h index 3360d7db5..633ab3183 100644 --- a/sources/shiboken6/ApiExtractor/enumtypeentry.h +++ b/sources/shiboken6/ApiExtractor/enumtypeentry.h @@ -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; diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel.cpp b/sources/shiboken6/ApiExtractor/parser/codemodel.cpp index d7f431547..8a56c9eee 100644 --- a/sources/shiboken6/ApiExtractor/parser/codemodel.cpp +++ b/sources/shiboken6/ApiExtractor/parser/codemodel.cpp @@ -1206,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 diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel.h b/sources/shiboken6/ApiExtractor/parser/codemodel.h index 122bbba15..77082efa5 100644 --- a/sources/shiboken6/ApiExtractor/parser/codemodel.h +++ b/sources/shiboken6/ApiExtractor/parser/codemodel.h @@ -559,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; }; diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h b/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h index e5c429bd0..c3bb10e5f 100644 --- a/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h +++ b/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h @@ -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 diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp index 93627e6d5..c530cafea 100644 --- a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp @@ -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) diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.h b/sources/shiboken6/ApiExtractor/parser/typeinfo.h index 6f75b5737..092fbb724 100644 --- a/sources/shiboken6/ApiExtractor/parser/typeinfo.h +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.h @@ -48,6 +48,9 @@ public: bool isVoid() const; + TypeCategory typeCategory() const; + void setTypeCategory(TypeCategory c); + bool isConstant() const; void setConstant(bool is); diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.cpp b/sources/shiboken6/ApiExtractor/qtdocparser.cpp index c7361b87a..a9b76d32c 100644 --- a/sources/shiboken6/ApiExtractor/qtdocparser.cpp +++ b/sources/shiboken6/ApiExtractor/qtdocparser.cpp @@ -30,6 +30,7 @@ #include <algorithm> #include <iterator> +#include <utility> using namespace Qt::StringLiterals; @@ -245,23 +246,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 +421,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 diff --git a/sources/shiboken6/ApiExtractor/tests/a.xml b/sources/shiboken6/ApiExtractor/tests/a.xml index 3c09d3800..bb771ed1f 100644 --- a/sources/shiboken6/ApiExtractor/tests/a.xml +++ b/sources/shiboken6/ApiExtractor/tests/a.xml @@ -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> diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp index c2fc3b212..9e59ebde6 100644 --- a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp +++ b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp @@ -28,7 +28,7 @@ R"(<typesystem package="Foo"> </value-type> <value-type name='A'> <modify-documentation xpath='description/brief'><brief>Modified Brief</brief></modify-documentation> - <modify-documentation xpath='description/para[3]'><para>Some changed contents here</para></modify-documentation> + <modify-documentation xpath='description/para[4]'><para>Some changed contents here</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> diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp index 46e88291d..c02ec0c93 100644 --- a/sources/shiboken6/ApiExtractor/typesystem.cpp +++ b/sources/shiboken6/ApiExtractor/typesystem.cpp @@ -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); diff --git a/sources/shiboken6/doc/typesystem_specifying_types.rst b/sources/shiboken6/doc/typesystem_specifying_types.rst index 798ef5719..7fe4df392 100644 --- a/sources/shiboken6/doc/typesystem_specifying_types.rst +++ b/sources/shiboken6/doc/typesystem_specifying_types.rst @@ -347,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 diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp index c99acb3d2..87e01f35b 100644 --- a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp @@ -507,6 +507,25 @@ void QtDocGenerator::writeDetailedDescription(TextStream &s, 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) { @@ -521,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) { @@ -560,9 +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"; - - writeDetailedDescription(s, metaClass, scope, &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); diff --git a/sources/shiboken6/generator/shiboken/cppgenerator.cpp b/sources/shiboken6/generator/shiboken/cppgenerator.cpp index 8bc5e4dca..c90a0557c 100644 --- a/sources/shiboken6/generator/shiboken/cppgenerator.cpp +++ b/sources/shiboken6/generator/shiboken/cppgenerator.cpp @@ -1737,6 +1737,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(); @@ -3538,9 +3549,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, diff --git a/sources/shiboken6/generator/shiboken/headergenerator.cpp b/sources/shiboken6/generator/shiboken/headergenerator.cpp index 181f04a9d..6ffeef9a3 100644 --- a/sources/shiboken6/generator/shiboken/headergenerator.cpp +++ b/sources/shiboken6/generator/shiboken/headergenerator.cpp @@ -814,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()); @@ -846,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); diff --git a/sources/shiboken6/tests/libsample/functions.h b/sources/shiboken6/tests/libsample/functions.h index b745aed6b..ef88e543e 100644 --- a/sources/shiboken6/tests/libsample/functions.h +++ b/sources/shiboken6/tests/libsample/functions.h @@ -21,6 +21,8 @@ enum GlobalEnum { ThirdThing }; +using GlobalEnumAlias = GlobalEnum; + enum GlobalOverloadFuncEnum { GlobalOverloadFunc_i, GlobalOverloadFunc_d diff --git a/sources/shiboken6/tests/libsample/samplenamespace.cpp b/sources/shiboken6/tests/libsample/samplenamespace.cpp index 3836f43aa..5e7b67a62 100644 --- a/sources/shiboken6/tests/libsample/samplenamespace.cpp +++ b/sources/shiboken6/tests/libsample/samplenamespace.cpp @@ -11,6 +11,16 @@ 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 diff --git a/sources/shiboken6/tests/libsample/samplenamespace.h b/sources/shiboken6/tests/libsample/samplenamespace.h index 63dc2f316..1613ce630 100644 --- a/sources/shiboken6/tests/libsample/samplenamespace.h +++ b/sources/shiboken6/tests/libsample/samplenamespace.h @@ -5,6 +5,7 @@ #define SAMPLENAMESPACE_H #include "libsamplemacros.h" +#include "samplenamespace.h" #include "str.h" #include "point.h" #include "objecttype.h" @@ -86,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: @@ -107,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, diff --git a/sources/shiboken6/tests/samplebinding/namespace_test.py b/sources/shiboken6/tests/samplebinding/namespace_test.py index 0d67c7497..0e92d6e27 100644 --- a/sources/shiboken6/tests/samplebinding/namespace_test.py +++ b/sources/shiboken6/tests/samplebinding/namespace_test.py @@ -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() diff --git a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml index 5503d9bb7..711db9c4a 100644 --- a/sources/shiboken6/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken6/tests/samplebinding/typesystem_sample.xml @@ -420,6 +420,7 @@ --> <enum-type name="GlobalEnum"/> <enum-type name="GlobalOverloadFuncEnum"/> + <enum-type name="GlobalEnumAlias"/> <enum-type identified-by-value="AnonymousGlobalEnum_Value0"/> @@ -437,6 +438,10 @@ <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"> |