diff --git a/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc b/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc index 1e6956a449a..72836730372 100644 --- a/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-from-qmlproject-to-pro.qdoc @@ -31,6 +31,13 @@ can be built with CMake. You can open the \e CMakeLists.txt project file in Qt Creator to continue developing the project. + \target wizard-template-note + \note Since \QDS 3.9.0, \QDS project wizard templates generate projects that + automatically checkout and build the Qt Quick Studio Components from + \l{https://code.qt.io/cgit/qt-labs/qtquickdesigner-components.git/} {Qt Code Review}, + using CMake. To turn off this feature, use the option \e BUILD_QDS_COMPONENTS + in the CMake configuration. + \if defined(qtdesignstudio) For more information, see \l{Designer-Developer Workflow}. \else @@ -149,10 +156,8 @@ \section1 Adding Qt Quick Designer Components to Qt Installations - If you use Qt Quick Studio Components or Effects in your project, you have - to check out and install the \e {Qt Quick Designer Components} module from - \l{https://code.qt.io/cgit/qt-labs/qtquickdesigner-components.git/} - {Qt Code Review}. + Since \QDS 3.9, the Qt Quick Studio Components module is installed by default + as part of the application. You can also install the module manually. For example: \list 1 diff --git a/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc b/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc index 1753da63f7b..146501328c2 100644 --- a/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc +++ b/doc/qtdesignstudio/src/developers/studio-designer-developer-workflow.qdoc @@ -32,8 +32,8 @@ your project as a fully working C++ application with developers. If you add or remove QML files in \QDS, you have to regenerate the - \e CMakeLists.txt project configuration file by selecting \uicontrol Build - > \uicontrol Run > \uicontrol {Generate CMakeLists.txt Files}. + \e CMakeLists.txt project configuration file by selecting \uicontrol File + > \uicontrol {Export Project} > \uicontrol {Generate CMake Build Files}. If you use Git, you can clone an example project \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/playground/AuroraCluster0} @@ -99,6 +99,8 @@ This isn't mandatory. \li Generate CMake files and C++ source files that are used to compile the application into - an executable file by selecting \uicontrol Build > \uicontrol{Generate CMakeLists.txt files}. + an executable file by selecting \uicontrol File > \uicontrol {Export Project} > + \uicontrol {Generate CMake Build Files}. + \endlist */ diff --git a/doc/qtdesignstudio/src/qtbridge/qtbridge-figma-setup.qdoc b/doc/qtdesignstudio/src/qtbridge/qtbridge-figma-setup.qdoc index 4d71150dcaf..33d130f3360 100644 --- a/doc/qtdesignstudio/src/qtbridge/qtbridge-figma-setup.qdoc +++ b/doc/qtdesignstudio/src/qtbridge/qtbridge-figma-setup.qdoc @@ -10,11 +10,19 @@ \note \QBF is included in the \l{https://www.qt.io/pricing}{\QDS Enterprise license}. - \QBF is delivered with \QDS as a developer plugin that you can install to - the Desktop version of Figma. To install the plugin, open the Plugin Manager - of Figma and press the plus button to create a new plugin. Then choose the - \e Manifest.json file that comes with \QDS. + You need both Figma and Qt accounts to use \QBF in \QDS. + To use \QBF in \QDS: + \list 1 + \li Go to \l {https://www.figma.com/community/plugin/1167809465162924409/Qt-Bridge-for-Figma} + {\QBF plugin page}. + \li Select \uicontrol {Try it out}. + \li Select your logged in Figma account. + \li After the plugin loads, select \uicontrol Run. + \li Select \uicontrol Export, to get the \e {.qtbridge} file in your + local drive. + \li In \QDS, drag the file to the \uicontrol 2D, \uicontrol 3D, \uicontrol Assets, + or \uicontrol Navigator view in an open project . + \endlist - You can launch the Figma plugin from \uicontrol Plugins > - \uicontrol Development > \uicontrol {\QBF} in Figma. + You can launch the installed Figma plugin from \uicontrol Plugins > \uicontrol {\QBF} in Figma. */ diff --git a/doc/qtdesignstudio/src/qtdesignstudio-packaging.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-packaging.qdoc index a1e35898812..1e426f011e1 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-packaging.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-packaging.qdoc @@ -24,9 +24,9 @@ set of files (icons, translation files, and so on) and you don't want to run the risk of losing the files. - To package your application, select \uicontrol Build > - \uicontrol {Generate QRC Resource File}. Then select the - files to package in the \uicontrol {Add Resources} dialog. + To package your application, select \uicontrol File > + \uicontrol {Export Project} > \uicontrol {Generate QRC Resource File}. + Then select the files to package in the \uicontrol {Add Resources} dialog. \image studio-add-resources.png "Add Resources dialog" @@ -44,10 +44,11 @@ \section1 Embedding Resources into Applications - Alternatively, you can embedd the resources into your application by - selecting \uicontrol Build > \uicontrol {Generate Deployable Package}. - Select the location for the .qmlrc file, and then select the files to - embedd in the \uicontrol {Add Resources} dialog. + Alternatively, you can embed the resources into your application by + selecting \uicontrol File > \uicontrol {Export Project} > + \uicontrol {Generate Deployable Package}. Select the location for + the .qmlrc file, and then select the files to embed in the + \uicontrol {Add Resources} dialog. When you select \uicontrol OK, \QDS creates a resource collection file (.qmlrc) in the location you selected. diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml index d6c24d4b2af..494bebd07cc 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml @@ -23,6 +23,7 @@ Image { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true onPressed: (mouse) => { if (mouse.button === Qt.LeftButton) @@ -31,4 +32,20 @@ Image { root.showContextMenu() } } + + ToolTip { + visible: mouseArea.containsMouse + // contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than + // needed). Using a helper Text to calculate the correct width + contentWidth: helperText.width + bottomInset: -2 + text: modelData.textureToolTip + delay: 1000 + + Text { + id: helperText + text: modelData.textureToolTip + visible: false + } + } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml index a28becff06b..2bbfff5223c 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetDelegate.qml @@ -12,7 +12,7 @@ TreeViewDelegate { required property Item assetsRoot property bool hasChildWithDropHover: false - property bool isHoveringDrop: false + property bool isHighlighted: false readonly property string suffix: model.fileName.substr(-4) readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf" readonly property bool isEffect: root.suffix === ".qep" @@ -26,7 +26,12 @@ TreeViewDelegate { readonly property int __dirItemHeight: 21 implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight - implicitWidth: root.assetsView.width > 0 ? root.assetsView.width : 10 + implicitWidth: { + if (root.assetsView.verticalScrollBar.scrollBarVisible) + return root.assetsView.width - root.indentation - root.assetsView.verticalScrollBar.width + else + return root.assetsView.width - root.indentation + } leftMargin: root.__isDirectory ? 0 : thumbnailImage.width @@ -54,17 +59,6 @@ TreeViewDelegate { } } - onImplicitWidthChanged: { - // a small hack, to fix a glitch: when resizing the width of the tree view, - // the widths of the delegate items remain the same as before, unless we re-set - // that width explicitly. - var newWidth = root.implicitWidth - (root.assetsView.verticalScrollBar.scrollBarVisible - ? root.assetsView.verticalScrollBar.width - : 0) - bg.width = newWidth - bg.implicitWidth = newWidth - } - onDepthChanged: { if (root.depth > root.initialDepth && root.initialDepth >= 0) root.depth = root.initialDepth @@ -73,8 +67,10 @@ TreeViewDelegate { background: Rectangle { id: bg + width: root.implicitWidth + color: { - if (root.__isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover)) + if (root.__isDirectory && (root.isHighlighted || root.hasChildWithDropHover)) return StudioTheme.Values.themeInteraction if (!root.__isDirectory && root.assetsView.selectedAssets[root.__itemPath]) @@ -120,40 +116,6 @@ TreeViewDelegate { } } - DropArea { - id: treeDropArea - - enabled: true - anchors.fill: parent - - onEntered: (drag) => { - root.assetsRoot.updateDropExtFiles(drag) - root.isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0 - if (root.isHoveringDrop) - root.assetsView.startDropHoverOver(root.__currentRow) - } - - onDropped: (drag) => { - root.isHoveringDrop = false - root.assetsView.endDropHover(root.__currentRow) - - let dirPath = root.__isDirectory - ? model.filePath - : assetsModel.parentDirPath(model.filePath); - - rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles, - root.assetsRoot.dropComplexExtFiles, - dirPath) - } - - onExited: { - if (root.isHoveringDrop) { - root.isHoveringDrop = false - root.assetsView.endDropHover(root.__currentRow) - } - } - } - MouseArea { id: mouseArea @@ -247,6 +209,14 @@ TreeViewDelegate { } } // MouseArea + function getDirPath() + { + if (root.__isDirectory) + return model.filePath + else + return assetsModel.parentDirPath(model.filePath) + } + function __openContextMenuForCurrentRow() { let modelIndex = assetsModel.indexForPath(model.filePath) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 6dedaf79012..3337be90ef0 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -183,4 +183,17 @@ StudioControls.Menu { } } } + + StudioControls.MenuItem { + text: qsTr("New Effect") + visible: assetsModel.canCreateEffects() + + NewEffectDialog { + id: newEffectDialog + parent: root.assetsView + dirPath: root.__dirPath + } + + onTriggered: newEffectDialog.open() + } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml index 782cca5ebc6..dcffab432be 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml @@ -250,8 +250,12 @@ TreeView { function startDropHoverOver(row) { let index = root.__modelIndex(row) - if (assetsModel.isDirectory(index)) + if (assetsModel.isDirectory(index)) { + let item = root.__getDelegateItemForIndex(index) + if (item) + item.isHighlighted = true return + } let parentItem = root.__getDelegateParentForIndex(index) if (parentItem) @@ -261,8 +265,12 @@ TreeView { function endDropHover(row) { let index = root.__modelIndex(row) - if (assetsModel.isDirectory(index)) + if (assetsModel.isDirectory(index)) { + let item = root.__getDelegateItemForIndex(index) + if (item) + item.isHighlighted = false return + } let parentItem = root.__getDelegateParentForIndex(index) if (parentItem) @@ -292,6 +300,12 @@ TreeView { return root.itemAtCell(parentCell) } + function __getDelegateItemForIndex(index) + { + let cell = root.cellAtIndex(index) + return root.itemAtCell(cell) + } + function __modelIndex(row) { // The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a @@ -303,6 +317,76 @@ TreeView { return root.modelIndex(row, 0) } + DropArea { + id: dropArea + enabled: true + anchors.fill: parent + + property bool __isHoveringDrop: false + property int __rowHoveringOver: -1 + + function __rowAndItem(drag) + { + let pos = dropArea.mapToItem(root, drag.x, drag.y) + let cell = root.cellAtPos(pos.x, pos.y, true) + let item = root.itemAtCell(cell) + + return [cell.y, item] + } + + onEntered: (drag) => { + root.assetsRoot.updateDropExtFiles(drag) + + let [row, item] = dropArea.__rowAndItem(drag) + dropArea.__isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0 + + if (item && dropArea.__isHoveringDrop) + root.startDropHoverOver(row) + + dropArea.__rowHoveringOver = row + } + + onDropped: (drag) => { + let [row, item] = dropArea.__rowAndItem(drag) + + if (item) { + root.endDropHover(row) + + let dirPath = item.getDirPath() + + rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles, + root.assetsRoot.dropComplexExtFiles, + dirPath) + } + + dropArea.__isHoveringDrop = false + dropArea.__rowHoveringOver = -1 + } + + onPositionChanged: (drag) => { + let [row, item] = dropArea.__rowAndItem(drag) + + if (dropArea.__rowHoveringOver !== row && dropArea.__rowHoveringOver > -1) { + root.endDropHover(dropArea.__rowHoveringOver) + + if (item) + root.startDropHoverOver(row) + } + + dropArea.__rowHoveringOver = row + } + + onExited: { + if (!dropArea.__isHoveringDrop || dropArea.__rowHoveringOver === -1) + return + + root.endDropHover(dropArea.__rowHoveringOver) + + dropArea.__isHoveringDrop = false + dropArea.__rowHoveringOver = -1 + } + } + delegate: AssetDelegate { assetsView: root assetsRoot: root.assetsRoot diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewEffectDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewEffectDialog.qml new file mode 100644 index 00000000000..f57284e6782 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewEffectDialog.qml @@ -0,0 +1,105 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: root + + title: qsTr("Create New Effect") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + modal: true + + required property string dirPath + readonly property int __maxPath: 32 + + HelperWidgets.RegExpValidator { + id: effectNameValidator + regExp: /^[A-Z]\w[A-Za-z0-9_]*$/ + } + + ErrorDialog { + id: creationFailedDialog + title: qsTr("Could not create effect") + message: qsTr("An error occurred while trying to create the effect.") + } + + contentItem: Column { + spacing: 2 + + Row { + Text { + text: qsTr("Effect name: ") + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: effectName + + actionIndicator.visible: false + translationIndicator.visible: false + validator: effectNameValidator + + Keys.onEnterPressed: btnCreate.onClicked() + Keys.onReturnPressed: btnCreate.onClicked() + } + } + + Text { + text: qsTr("Effect name cannot be empty.") + color: "#ff0000" + anchors.right: parent.right + visible: effectName.text === "" + } + + Text { + text: qsTr("Effect path is too long.") + color: "#ff0000" + anchors.right: parent.right + visible: effectName.text.length > root.__maxPath + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + + HelperWidgets.Button { + id: btnCreate + + text: qsTr("Create") + enabled: effectName.text !== "" + && effectName.length >=3 + && effectName.text.length <= root.__maxPath + onClicked: { + const path = assetsModel.getUniqueEffectPath(root.dirPath, effectName.text) + if (assetsModel.createNewEffect(path)) + root.accept() + else + creationFailedDialog.open() + } + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: root.reject() + } + } + } + + onOpened: { + const path = assetsModel.getUniqueEffectPath(root.dirPath, "Effect01") + effectName.text = path.split('/').pop().replace(".qep", '') + effectName.selectAll() + effectName.forceActiveFocus() + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Menu.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Menu.qml index 306d4aeda98..d0fb8719ad1 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Menu.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/Menu.qml @@ -40,5 +40,13 @@ T.Menu { color: StudioTheme.Values.themeControlBackground border.color: StudioTheme.Values.themeControlOutline border.width: StudioTheme.Values.border + MouseArea { + // This mouse area is here to eat clicks that are not handled by menu items + // to prevent them going through to the underlying view. + // This is primarily problem with disabled menu items, but right clicks would go + // through enabled menu items as well. + anchors.fill: parent + acceptedButtons: Qt.AllButtons + } } } diff --git a/share/qtcreator/qmldesigner/studio_templates/files/effect/file.qep b/share/qtcreator/qmldesigner/studio_templates/files/effect/file.qep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/share/qtcreator/qmldesigner/studio_templates/files/effect/wizard.json b/share/qtcreator/qmldesigner/studio_templates/files/effect/wizard.json deleted file mode 100644 index 3dedbb5080d..00000000000 --- a/share/qtcreator/qmldesigner/studio_templates/files/effect/wizard.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "version": 1, - "supportedProjectTypes": [ ], - "id": "J.QEP", - "category": "U.QEP", - "trDescription": "Creates an Effect Maker file.", - "trDisplayName": "Effect File (Effect Maker)", - "trDisplayCategory": "Effects", - "iconText": "qep", - "platformIndependent": true, - "enabled": "%{JS: value('Features').indexOf('QmlDesigner.Wizards.Enterprise') >= 0}", - "featuresRequired": [ "QmlDesigner.Wizards.Enterprise" ], - - "options": [ - { "key": "EffectFile", "value": "%{Class}.qep" }, - { "key": "DoNotOpenFile", "value": "true" } - ], - - "pages" : - [ - { - "trDisplayName": "Define Class", - "trShortTitle": "Details", - "typeId": "Fields", - "data" : - [ - { - "name": "Class", - "trDisplayName": "Effect name:", - "mandatory": true, - "type": "LineEdit", - "data": { - "validator": "(?:[A-Z_][a-zA-Z_0-9]*|)", - "fixup": "%{JS: '%{INPUT}'.charAt(0).toUpperCase() + '%{INPUT}'.slice(1) }" - } - }, - { - "name": "TargetPath", - "type": "PathChooser", - "trDisplayName": "Path:", - "mandatory": true, - "data": - { - "kind": "existingDirectory", - "basePath": "%{InitialPath}", - "path": "%{InitialPath}" - } - } - ] - } -], - "generators" : - [ - { - "typeId": "File", - "data": - { - "source": "file.qep", - "target": "%{TargetPath}/%{EffectFile}", - "openInEditor": false - } - } - ] -} diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl index 6878a1023c3..9daf9759b19 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/CMakeLists.main.txt.tpl @@ -10,6 +10,11 @@ set(CMAKE_AUTOMOC ON) find_package(QT NAMES Qt6 COMPONENTS Gui Qml Quick) find_package(Qt6 REQUIRED COMPONENTS Core Qml Quick) +# To build this application you need Qt 6.2.0 or higher +if (Qt6_VERSION VERSION_LESS 6.2.0) +message(FATAL_ERROR "You need Qt 6.2.0 or newer to build the application.") +endif() + qt_add_executable(${CMAKE_PROJECT_NAME} src/main.cpp) # qt_standard_project_setup() requires Qt 6.3 or higher. See https://doc.qt.io/qt-6/qt-standard-project-setup.html for details. diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp index 23e74f819bd..8075e6821ed 100644 --- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp +++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp @@ -63,7 +63,7 @@ static const char *qtQuickUISuffix = "ui.qml"; static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path) { - if (!path.isEmpty() && !(context.paths.count(path) > 0)) + if (!path.isEmpty() && (context.paths.count(path) <= 0)) context.paths.insert(path); } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8e524d8c5bc..ffb9faf87df 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -35,8 +35,10 @@ add_qtc_library(QmlDesignerUtils STATIC PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/utils SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/utils SOURCES + asset.cpp asset.h designersettings.cpp designersettings.h hdrimage.cpp hdrimage.h + imageutils.cpp imageutils.h qmldesignerutils_global.h ) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index bc56e9b7f3f..14a433e8602 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "assetslibraryiconprovider.h" -#include "assetslibrarymodel.h" +#include "asset.h" #include "modelnodeoperations.h" #include @@ -25,13 +25,17 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size, pixmap = m_thumbnails[id]; } else { pixmap = fetchPixmap(id, requestedSize); - if (pixmap.isNull()) + bool haveValidImage = true; + if (pixmap.isNull()) { pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); + haveValidImage = false; + } if (requestedSize.isValid()) pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - m_thumbnails[id] = pixmap; + if (haveValidImage) + m_thumbnails[id] = pixmap; } if (size) { @@ -53,24 +57,25 @@ QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, co QPixmap AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const { - const QString suffix = "*." + id.split('.').last().toLower(); + Asset asset(id); + if (id == "browse") { return Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png"); - } else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { + } else if (asset.isFont()) { return generateFontIcons(id, requestedSize); - } else if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { + } else if (asset.isImage()) { return Utils::StyleHelper::dpiSpecificImageFile(id); - } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { + } else if (asset.isTexture3D()) { return HdrImage{id}.toPixmap(); } else { QString type; - if (AssetsLibraryModel::supportedShaderSuffixes().contains(suffix)) + if (asset.isShader()) type = "shader"; - else if (AssetsLibraryModel::supportedAudioSuffixes().contains(suffix)) + else if (asset.isAudio()) type = "sound"; - else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) + else if (asset.isVideo()) type = "video"; - else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) + else if (asset.isEffect()) type = QmlDesigner::ModelNodeOperations::getEffectIcon(id); QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 6587c89cf32..0953eff9eb1 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -1,13 +1,13 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include #include #include -#include #include #include +#include "asset.h" #include "assetslibrarymodel.h" #include @@ -134,32 +134,10 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString & bool AssetsLibraryModel::addNewFolder(const QString &folderPath) { QString iterPath = folderPath; - static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string QDir dir{folderPath}; while (dir.exists()) { - // if the folder name ends with a number, increment it - QRegularExpressionMatch match = rgx.match(iterPath); - if (match.hasMatch()) { // ends with a number - QString numStr = match.captured(0); - int num = match.captured(0).toInt(); - - // get number of padding zeros, ex: for "005" = 2 - int nPaddingZeros = 0; - for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros); - - ++num; - - // if the incremented number's digits increased, decrease the padding zeros - if (std::fmod(std::log10(num), 1.0) == 0) - --nPaddingZeros; - - iterPath = folderPath.mid(0, match.capturedStart()) - + QString('0').repeated(nPaddingZeros) - + QString::number(num); - } else { - iterPath = folderPath + '1'; - } + iterPath = getUniqueName(iterPath); dir.setPath(iterPath); } @@ -180,12 +158,49 @@ bool AssetsLibraryModel::deleteFolderRecursively(const QModelIndex &folderIndex) bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) const { return Utils::allOf(filePaths, [](const QString &path) { - const QString suffix = "*." + path.split('.').last().toLower(); - - return AssetsLibraryModel::supportedImageSuffixes().contains(suffix); + return Asset(path).isImage(); }); } +QString AssetsLibraryModel::getUniqueEffectPath(const QString &parentFolder, const QString &effectName) +{ + auto genEffectPath = [=](const QString &name) { + return QString(parentFolder + "/" + name + ".qep"); + }; + + QString uniqueName = effectName; + QString path = genEffectPath(uniqueName); + QFileInfo file{path}; + + while (file.exists()) { + uniqueName = getUniqueName(uniqueName); + + path = genEffectPath(uniqueName); + file.setFile(path); + } + + return path; +} + +bool AssetsLibraryModel::createNewEffect(const QString &effectPath, bool openEffectMaker) +{ + bool created = QFile(effectPath).open(QIODevice::WriteOnly); + + if (created && openEffectMaker) + ModelNodeOperations::openEffectMaker(effectPath); + + return created; +} + +bool AssetsLibraryModel::canCreateEffects() const +{ +#ifdef LICENSECHECKER + return checkLicense() == FoundLicense::enterprise; +#else + return true; +#endif +} + bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QString path = m_sourceFsModel->filePath(sourceParent); @@ -242,6 +257,36 @@ void AssetsLibraryModel::syncHaveFiles() setHaveFiles(checkHaveFiles()); } +QString AssetsLibraryModel::getUniqueName(const QString &oldName) { + static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string + + QString uniqueName = oldName; + // if the folder name ends with a number, increment it + QRegularExpressionMatch match = rgx.match(uniqueName); + if (match.hasMatch()) { // ends with a number + QString numStr = match.captured(0); + int num = match.captured(0).toInt(); + + // get number of padding zeros, ex: for "005" = 2 + int nPaddingZeros = 0; + for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros); + + ++num; + + // if the incremented number's digits increased, decrease the padding zeros + if (std::fmod(std::log10(num), 1.0) == 0) + --nPaddingZeros; + + uniqueName = oldName.mid(0, match.capturedStart()) + + QString('0').repeated(nPaddingZeros) + + QString::number(num); + } else { + uniqueName = oldName + '1'; + } + + return uniqueName; +} + void AssetsLibraryModel::setRootPath(const QString &newPath) { beginResetModel(); @@ -252,7 +297,7 @@ void AssetsLibraryModel::setRootPath(const QString &newPath) m_rootPath = newPath; m_sourceFsModel->setRootPath(newPath); - m_sourceFsModel->setNameFilters(supportedSuffixes().values()); + m_sourceFsModel->setNameFilters(Asset::supportedSuffixes().values()); m_sourceFsModel->setNameFilterDisables(false); endResetModel(); @@ -327,80 +372,4 @@ QString AssetsLibraryModel::parentDirPath(const QString &path) const return filePath(parentIdx); } -const QStringList &AssetsLibraryModel::supportedImageSuffixes() -{ - static QStringList retList; - if (retList.isEmpty()) { - const QList suffixes = QImageReader::supportedImageFormats(); - for (const QByteArray &suffix : suffixes) - retList.append("*." + QString::fromUtf8(suffix)); - } - return retList; -} - -const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes() -{ - static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedShaderSuffixes() -{ - static const QStringList retList {"*.frag", "*.vert", - "*.glsl", "*.glslv", "*.glslf", - "*.vsh", "*.fsh"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedFontSuffixes() -{ - static const QStringList retList {"*.ttf", "*.otf"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedAudioSuffixes() -{ - static const QStringList retList {"*.wav", "*.mp3"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedVideoSuffixes() -{ - static const QStringList retList {"*.mp4"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes() -{ - // These are file types only supported by 3D textures - static QStringList retList {"*.hdr", "*.ktx"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedEffectMakerSuffixes() -{ - // These are file types only supported by Effect Maker - static QStringList retList {"*.qep"}; - return retList; -} - -const QSet &AssetsLibraryModel::supportedSuffixes() -{ - static QSet allSuffixes; - if (allSuffixes.isEmpty()) { - auto insertSuffixes = [](const QStringList &suffixes) { - for (const auto &suffix : suffixes) - allSuffixes.insert(suffix); - }; - insertSuffixes(supportedImageSuffixes()); - insertSuffixes(supportedShaderSuffixes()); - insertSuffixes(supportedFontSuffixes()); - insertSuffixes(supportedAudioSuffixes()); - insertSuffixes(supportedVideoSuffixes()); - insertSuffixes(supportedTexture3DSuffixes()); - insertSuffixes(supportedEffectMakerSuffixes()); - } - return allSuffixes; -} - } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 99c96017084..0538bedf42b 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -1,14 +1,14 @@ -// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #pragma once +#include #include #include -#include -#include #include +#include namespace QmlDesigner { @@ -47,6 +47,11 @@ public: Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); Q_INVOKABLE bool allFilePathsAreImages(const QStringList &filePaths) const; + Q_INVOKABLE QString getUniqueEffectPath(const QString &parentFolder, const QString &effectName); + Q_INVOKABLE bool createNewEffect(const QString &effectPath, bool openEffectMaker = true); + + Q_INVOKABLE bool canCreateEffects() const; + int columnCount(const QModelIndex &parent = QModelIndex()) const override { int result = QSortFilterProxyModel::columnCount(parent); @@ -55,16 +60,6 @@ public: bool haveFiles() const { return m_haveFiles; } - static const QStringList &supportedImageSuffixes(); - static const QStringList &supportedFragmentShaderSuffixes(); - static const QStringList &supportedShaderSuffixes(); - static const QStringList &supportedFontSuffixes(); - static const QStringList &supportedAudioSuffixes(); - static const QStringList &supportedVideoSuffixes(); - static const QStringList &supportedTexture3DSuffixes(); - static const QStringList &supportedEffectMakerSuffixes(); - static const QSet &supportedSuffixes(); - signals: void directoryLoaded(const QString &path); void rootPathChanged(); @@ -79,6 +74,7 @@ private: void destroyBackendModel(); bool checkHaveFiles(const QModelIndex &parentIdx) const; bool checkHaveFiles() const; + QString getUniqueName(const QString &oldName); QString m_searchText; QString m_rootPath; diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 05ebb729869..cc9263eda46 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -3,8 +3,9 @@ #include "assetslibrarywidget.h" -#include "assetslibrarymodel.h" +#include "asset.h" #include "assetslibraryiconprovider.h" +#include "assetslibrarymodel.h" #include @@ -229,7 +230,7 @@ QSet AssetsLibraryWidget::supportedAssetSuffixes(bool complex) QSet suffixes; for (const AddResourceHandler &handler : handlers) { - if (AssetsLibraryModel::supportedSuffixes().contains(handler.filter) != complex) + if (Asset(handler.filter).isSupported() != complex) suffixes.insert(handler.filter); } @@ -290,32 +291,32 @@ void AssetsLibraryWidget::startDragAsset(const QStringList &assetPaths, const QP QPair AssetsLibraryWidget::getAssetTypeAndData(const QString &assetPath) { - QString suffix = "*." + assetPath.split('.').last().toLower(); - if (!suffix.isEmpty()) { - if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { + Asset asset(assetPath); + if (asset.hasSuffix()) { + if (asset.isImage()) { // Data: Image format (suffix) - return {Constants::MIME_TYPE_ASSET_IMAGE, suffix.toUtf8()}; - } else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { + return {Constants::MIME_TYPE_ASSET_IMAGE, asset.suffix().toUtf8()}; + } else if (asset.isFont()) { // Data: Font family name QRawFont font(assetPath, 10); QString fontFamily = font.isValid() ? font.familyName() : ""; return {Constants::MIME_TYPE_ASSET_FONT, fontFamily.toUtf8()}; - } else if (AssetsLibraryModel::supportedShaderSuffixes().contains(suffix)) { + } else if (asset.isShader()) { // Data: shader type, frament (f) or vertex (v) return {Constants::MIME_TYPE_ASSET_SHADER, - AssetsLibraryModel::supportedFragmentShaderSuffixes().contains(suffix) ? "f" : "v"}; - } else if (AssetsLibraryModel::supportedAudioSuffixes().contains(suffix)) { + asset.isFragmentShader() ? "f" : "v"}; + } else if (asset.isAudio()) { // No extra data for sounds return {Constants::MIME_TYPE_ASSET_SOUND, {}}; - } else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) { + } else if (asset.isVideo()) { // No extra data for videos return {Constants::MIME_TYPE_ASSET_VIDEO, {}}; - } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { + } else if (asset.isTexture3D()) { // Data: Image format (suffix) - return {Constants::MIME_TYPE_ASSET_TEXTURE3D, suffix.toUtf8()}; - } else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) { + return {Constants::MIME_TYPE_ASSET_TEXTURE3D, asset.suffix().toUtf8()}; + } else if (asset.isEffect()) { // Data: Effect Maker format (suffix) - return {Constants::MIME_TYPE_ASSET_EFFECT, suffix.toUtf8()}; + return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()}; } } return {}; diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 711b84c3798..bd9031ddc26 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1525,8 +1525,11 @@ void mergeWithTemplate(const SelectionContext &selectionContext, ExternalDepende const QString templateFile = getTemplateDialog(projectPath); - if (QFileInfo::exists(templateFile)) - StylesheetMerger::styleMerge(selectionContext.view()->model(), templateFile, externalDependencies); + if (QFileInfo::exists(templateFile)) { + StylesheetMerger::styleMerge(Utils::FilePath::fromString(templateFile), + selectionContext.view()->model(), + externalDependencies); + } } void removeGroup(const SelectionContext &selectionContext) @@ -1674,7 +1677,7 @@ void openEffectMaker(const QString &filePath) } } -Utils::FilePath getEffectsDirectory() +Utils::FilePath getEffectsImportDirectory() { QString defaultDir = "asset_imports/Effects"; Utils::FilePath projectPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); @@ -1688,6 +1691,11 @@ Utils::FilePath getEffectsDirectory() return effectsPath; } +QString getEffectsDefaultDirectory(const QString &defaultDir) +{ + return getAssetDefaultDirectory("effects", defaultDir); +} + QString getEffectIcon(const QString &effectPath) { const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget(); @@ -1715,7 +1723,7 @@ bool useLayerEffect() bool validateEffect(const QString &effectPath) { const QString effectName = QFileInfo(effectPath).baseName(); - Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsDirectory(); + Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsImportDirectory(); Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml"); if (!qmlPath.exists()) { QMessageBox msgBox; diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 2e60b253195..1cee080c561 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -119,7 +119,8 @@ void addMouseAreaFill(const SelectionContext &selectionContext); void openSignalDialog(const SelectionContext &selectionContext); void updateImported3DAsset(const SelectionContext &selectionContext); -QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsDirectory(); +QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsImportDirectory(); +QMLDESIGNERCORE_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir); void openEffectMaker(const QString &filePath); QString getEffectIcon(const QString &effectPath); bool useLayerEffect(); diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp index 487f84229c4..46c5b665f90 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -76,19 +76,19 @@ bool Navigation2dFilter::wheelEvent(QWheelEvent *event) bool zoomChangedConnected = QObject::isSignalConnected(zoomChangedSignal); if (zoomChangedConnected) { - const double globalMouseSpeed = - QmlDesignerPlugin::settings().value(DesignerSettingsKey::EDITOR_ZOOM_FACTOR).toDouble(); - - double speed = globalMouseSpeed/20.; - if (Utils::HostOsInfo::isMacHost()) - speed = 1.0/200.; - - if (QPointF delta = event->pixelDelta(); !delta.isNull()) { + double speed = 1.0 / 200.0; + bool isMac = Utils::HostOsInfo::isMacHost(); + if (QPointF delta = event->pixelDelta(); !delta.isNull() && isMac) { double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); emit zoomChanged(dist * speed, event->position()); event->accept(); return true; } else if (QPointF delta = event->angleDelta(); !delta.isNull()) { + + const double globalMouseSpeed = + QmlDesignerPlugin::settings().value(DesignerSettingsKey::EDITOR_ZOOM_FACTOR).toDouble(); + speed = globalMouseSpeed / 20.0; + constexpr double degreePerStep = 15.; constexpr double stepCount = 8.; double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp index 288e04aaff6..84cd0f98362 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp @@ -3,12 +3,17 @@ #include "contentlibrarytexture.h" +#include "imageutils.h" + namespace QmlDesigner { ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QString &path, const QUrl &icon) : QObject(parent) , m_path(path) - , m_icon(icon) {} + , m_icon(icon) +{ + m_toolTip = QLatin1String("%1\n%2").arg(path.split('/').last(), ImageUtils::imageInfo(path)); +} bool ContentLibraryTexture::filter(const QString &searchText) { diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h index 88d50158325..468dfea09e4 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h @@ -13,6 +13,7 @@ class ContentLibraryTexture : public QObject Q_OBJECT Q_PROPERTY(QString texturePath MEMBER m_path CONSTANT) + Q_PROPERTY(QString textureToolTip MEMBER m_toolTip CONSTANT) Q_PROPERTY(QUrl textureIcon MEMBER m_icon CONSTANT) Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged) @@ -29,6 +30,7 @@ signals: private: QString m_path; + QString m_toolTip; QUrl m_icon; bool m_visible = true; diff --git a/src/plugins/qmldesigner/components/createtexture.cpp b/src/plugins/qmldesigner/components/createtexture.cpp index 91e40959806..a17a61e84ea 100644 --- a/src/plugins/qmldesigner/components/createtexture.cpp +++ b/src/plugins/qmldesigner/components/createtexture.cpp @@ -65,19 +65,27 @@ ModelNode CreateTexture::createTextureFromImage(const QString &assetPath, AddTex NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo(); - Utils::FilePath imagePath = ModelNodeOperations::getImagesDefaultDirectory() - .pathAppended(Utils::FilePath::fromString(assetPath).fileName()); - QString sourceVal = imagePath.relativePathFrom( - QmlDesigner::DocumentManager::currentFilePath()).toString(); + Utils::FilePath currentDocumentPath = QmlDesigner::DocumentManager::currentFilePath(); + Utils::FilePath imageTargetPath; + if (m_importFile) { + QString assetName = Utils::FilePath::fromString(assetPath).fileName(); + // if the asset had to be imported from somewhere else, then assetPath is the source where + // the asset was taken from, and we have to compute where it was placed in the project. + imageTargetPath = ModelNodeOperations::getImagesDefaultDirectory().pathAppended(assetName); + } else { + imageTargetPath = Utils::FilePath::fromString(assetPath); + } - ModelNode newTexNode = m_view->getTextureDefaultInstance(sourceVal); + QString textureSource = imageTargetPath.relativePathFrom(currentDocumentPath).toString(); + + ModelNode newTexNode = m_view->getTextureDefaultInstance(textureSource); if (!newTexNode.isValid()) { newTexNode = m_view->createModelNode("QtQuick3D.Texture", metaInfo.majorVersion(), metaInfo.minorVersion()); newTexNode.validId(); VariantProperty sourceProp = newTexNode.variantProperty("source"); - sourceProp.setValue(sourceVal); + sourceProp.setValue(textureSource); matLib.defaultNodeListProperty().reparentHere(newTexNode); } diff --git a/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp b/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp index 07d65cb3c33..42f5bc45dd5 100644 --- a/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp +++ b/src/plugins/qmldesigner/components/edit3d/backgroundcolorselection.cpp @@ -44,7 +44,7 @@ QColorDialog *BackgroundColorSelection::createColorDialog(QWidget *parent, QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog, [actionType, view](const QColor &color) { - Edit3DViewConfig::setColor(view, actionType, color); + Edit3DViewConfig::setColors(view, actionType, {color}); }); QObject::connect(dialog, &QColorDialog::colorSelected, dialog, @@ -52,13 +52,13 @@ QColorDialog *BackgroundColorSelection::createColorDialog(QWidget *parent, if (colorSelected) colorSelected(); - Edit3DViewConfig::saveColor(key, color); + Edit3DViewConfig::saveColors(key, {color}); }); - if (Edit3DViewConfig::isColorValid(oldColorConfig)) { + if (Edit3DViewConfig::colorsValid(oldColorConfig)) { QObject::connect(dialog, &QColorDialog::rejected, dialog, [actionType, oldColorConfig, view]() { - Edit3DViewConfig::setColor(view, actionType, oldColorConfig); + Edit3DViewConfig::setColors(view, actionType, oldColorConfig); }); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp index d5c93ca4b97..5b271a4c13d 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp @@ -3,7 +3,6 @@ #include "edit3dactions.h" #include "edit3dview.h" -#include "edit3dwidget.h" #include #include @@ -18,7 +17,7 @@ namespace QmlDesigner { Edit3DActionTemplate::Edit3DActionTemplate(const QString &description, SelectionContextOperation action, - AbstractView *view, + Edit3DView *view, View3DActionType type) : DefaultAction(description) , m_action(action) @@ -45,12 +44,14 @@ Edit3DAction::Edit3DAction(const QByteArray &menuId, bool checked, const QIcon &iconOff, const QIcon &iconOn, - AbstractView *view, + Edit3DView *view, SelectionContextOperation selectionAction, const QString &toolTip) : AbstractAction(new Edit3DActionTemplate(description, selectionAction, view, type)) , m_menuId(menuId) + , m_actionTemplate(qobject_cast(defaultAction())) { + view->registerEdit3DAction(this); action()->setShortcut(key); action()->setShortcutContext(Qt::WidgetWithChildrenShortcut); action()->setCheckable(checkable); @@ -73,11 +74,21 @@ Edit3DAction::Edit3DAction(const QByteArray &menuId, } } +Edit3DAction::~Edit3DAction() +{ + m_actionTemplate->m_view->unregisterEdit3DAction(this); +} + QByteArray Edit3DAction::category() const { return QByteArray(); } +View3DActionType Edit3DAction::actionType() const +{ + return m_actionTemplate->m_type; +} + bool Edit3DAction::isVisible([[maybe_unused]] const SelectionContext &selectionContext) const { return true; @@ -96,7 +107,7 @@ Edit3DCameraAction::Edit3DCameraAction(const QByteArray &menuId, bool checked, const QIcon &iconOff, const QIcon &iconOn, - AbstractView *view, + Edit3DView *view, SelectionContextOperation selectionAction) : Edit3DAction(menuId, type, description, key, checkable, checked, iconOff, iconOn, view, selectionAction) { diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h index 14f63c3796e..a78b95d784a 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h @@ -10,19 +10,22 @@ namespace QmlDesigner { using SelectionContextOperation = std::function; +class Edit3DView; class Edit3DActionTemplate : public DefaultAction { + Q_OBJECT + public: Edit3DActionTemplate(const QString &description, SelectionContextOperation action, - AbstractView *view, + Edit3DView *view, View3DActionType type); void actionTriggered(bool b) override; SelectionContextOperation m_action; - AbstractView *m_view; + Edit3DView *m_view = nullptr; View3DActionType m_type; }; @@ -37,10 +40,12 @@ public: bool checked, const QIcon &iconOff, const QIcon &iconOn, - AbstractView *view, + Edit3DView *view, SelectionContextOperation selectionAction = nullptr, const QString &toolTip = {}); + virtual ~Edit3DAction(); + QByteArray category() const override; int priority() const override @@ -58,12 +63,15 @@ public: return m_menuId; } + View3DActionType actionType() const; + protected: bool isVisible(const SelectionContext &selectionContext) const override; bool isEnabled(const SelectionContext &selectionContext) const override; private: QByteArray m_menuId; + Edit3DActionTemplate *m_actionTemplate = nullptr; }; class Edit3DCameraAction : public Edit3DAction @@ -77,7 +85,7 @@ public: bool checked, const QIcon &iconOff, const QIcon &iconOn, - AbstractView *view, + Edit3DView *view, SelectionContextOperation selectionAction = nullptr); protected: diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index b7efef60d6a..038c658c51e 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -38,7 +38,9 @@ Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) } Edit3DView::~Edit3DView() -{} +{ + qDeleteAll(m_edit3DActions); +} void Edit3DView::createEdit3DWidget() { @@ -197,6 +199,31 @@ void Edit3DView::onEntriesChanged() m_compressionTimer.start(); } +void Edit3DView::registerEdit3DAction(Edit3DAction *action) +{ + View3DActionType actionType = action->actionType(); + if (actionType == View3DActionType::Empty) + return; + + if (m_edit3DActions.contains(actionType)) { + Edit3DAction *formerAction = m_edit3DActions.value(actionType); + if (formerAction == action) + return; + + qWarning() << Q_FUNC_INFO << __LINE__ << "Reregistering action for" << int(actionType); + delete formerAction; + } + + m_edit3DActions.insert(actionType, action); +} + +void Edit3DView::unregisterEdit3DAction(Edit3DAction *action) +{ + View3DActionType actionType = action->actionType(); + if (m_edit3DActions.value(actionType, nullptr) == action) + m_edit3DActions.remove(actionType); +} + void Edit3DView::handleEntriesChanged() { if (!model()) @@ -389,12 +416,12 @@ Edit3DAction *Edit3DView::createResetColorAction(QAction *syncBackgroundColorAct auto operation = [this, syncBackgroundColorAction](const SelectionContext &) { QList bgColors = {QRgb(0x222222), QRgb(0x999999)}; - Edit3DViewConfig::setColor(this, View3DActionType::SelectBackgroundColor, bgColors); - Edit3DViewConfig::saveColor(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors); + Edit3DViewConfig::setColors(this, View3DActionType::SelectBackgroundColor, bgColors); + Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors); QColor gridColor{0xaaaaaa}; - Edit3DViewConfig::setColor(this, View3DActionType::SelectGridColor, gridColor); - Edit3DViewConfig::saveColor(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, gridColor); + Edit3DViewConfig::setColors(this, View3DActionType::SelectGridColor, {gridColor}); + Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, {gridColor}); if (syncBackgroundColorAction->isChecked()) { Edit3DViewConfig::set(this, View3DActionType::SyncBackgroundColor, false); @@ -812,6 +839,11 @@ QVector Edit3DView::backgroundColorActions() const return m_backgroundColorActions; } +Edit3DAction *Edit3DView::edit3DAction(View3DActionType type) const +{ + return m_edit3DActions.value(type, nullptr); +} + void Edit3DView::addQuick3DImport() { DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index a14399e1c70..7653730480f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -57,6 +57,7 @@ public: QVector rightActions() const; QVector visibilityToggleActions() const; QVector backgroundColorActions() const; + Edit3DAction *edit3DAction(View3DActionType type) const; void setSeeker(SeekerSlider *slider); void addQuick3DImport(); @@ -77,6 +78,9 @@ private: None }; + void registerEdit3DAction(Edit3DAction *action); + void unregisterEdit3DAction(Edit3DAction *action); + void createEdit3DWidget(); void checkImports(); void handleEntriesChanged(); @@ -92,6 +96,7 @@ private: QVector m_rightActions; QVector m_visibilityToggleActions; QVector m_backgroundColorActions; + QMap m_edit3DActions; Edit3DAction *m_selectionModeAction = nullptr; Edit3DAction *m_moveToolAction = nullptr; Edit3DAction *m_rotateToolAction = nullptr; @@ -120,6 +125,8 @@ private: NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPos; QTimer m_compressionTimer; + + friend class Edit3DAction; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h b/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h index 267e27042c9..e0eba1115c9 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dviewconfig.h @@ -28,17 +28,9 @@ public: }); } - static void setColor(AbstractView *view, View3DActionType type, const QList &colorConfig) + static void setColors(AbstractView *view, View3DActionType type, const QList &colorConfig) { - if (colorConfig.size() == 1) - setColor(view, type, colorConfig.at(0)); - else - setVariant(view, type, QVariant::fromValue(colorConfig)); - } - - static void setColor(AbstractView *view, View3DActionType type, const QColor &color) - { - setVariant(view, type, QVariant::fromValue(color)); + setVariant(view, type, QVariant::fromValue(colorConfig)); } template @@ -47,7 +39,7 @@ public: setVariant(view, type, QVariant::fromValue(value)); } - static void saveColor(const QByteArray &key, const QList &colorConfig) + static void saveColors(const QByteArray &key, const QList &colorConfig) { QStringList colorNames = Utils::transform(colorConfig, [](const QColor &color) { return color.name(); @@ -56,12 +48,7 @@ public: saveVariant(key, QVariant::fromValue(colorNames)); } - static void saveColor(const QByteArray &key, const QColor &color) - { - saveVariant(key, QVariant::fromValue(color.name())); - } - - static bool isColorValid(const QList &colorConfig) { return !colorConfig.isEmpty(); } + static bool colorsValid(const QList &colorConfig) { return !colorConfig.isEmpty(); } private: static void setVariant(AbstractView *view, View3DActionType type, const QVariant &colorConfig) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index ca57d229d3b..f3af3a5ee2e 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -232,6 +232,14 @@ void Edit3DWidget::createContextMenu() view()->setSelectedModelNode(parentNode); }); + QAction *defaultToggleGroupAction = view()->edit3DAction(View3DActionType::SelectionModeToggle)->action(); + m_toggleGroupAction = m_contextMenu->addAction(tr("Group Selection Mode"), [&](const bool &mode) { + view()->edit3DAction(View3DActionType::SelectionModeToggle)->action()->trigger(); + }); + connect(defaultToggleGroupAction, &QAction::toggled, m_toggleGroupAction, &QAction::setChecked); + m_toggleGroupAction->setCheckable(true); + m_toggleGroupAction->setChecked(defaultToggleGroupAction->isChecked()); + m_contextMenu->addSeparator(); } @@ -375,6 +383,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode m_alignCameraAction->setEnabled(isCamera); m_alignViewAction->setEnabled(isCamera); m_selectParentAction->setEnabled(selectionExcludingRoot); + m_toggleGroupAction->setEnabled(true); m_contextMenu->popup(mapToGlobal(pos)); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index 25948d5d9aa..f1a9c09f445 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -72,6 +72,7 @@ private: QPointer m_alignCameraAction; QPointer m_alignViewAction; QPointer m_selectParentAction; + QPointer m_toggleGroupAction; QPointer m_createSubMenu; ModelNode m_contextMenuTarget; QVector3D m_contextMenuPos3d; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index f348580937d..9972a446227 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -3,8 +3,8 @@ #include "materialbrowsertexturesmodel.h" -#include "designeractionmanager.h" #include "designmodewidget.h" +#include "imageutils.h" #include "qmldesignerplugin.h" #include "qmlobjectnode.h" #include "variantproperty.h" @@ -52,24 +52,18 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) return m_textureList.at(index.row()).internalId(); if (role == RoleTexToolTip) { - QString source = QmlObjectNode(m_textureList.at(index.row())).modelValue("source").toString(); + QString source = data(index, RoleTexSource).toString(); // absolute path if (source.isEmpty()) return tr("Texture has no source image."); - const QString noData = tr("Texture has no data."); + ModelNode texNode = m_textureList.at(index.row()); + QString info = ImageUtils::imageInfo(source); - auto op = QmlDesignerPlugin::instance()->viewManager().designerActionManager() - .modelNodePreviewOperation(m_textureList.at(index.row())); - if (!op) - return noData; + if (info.isEmpty()) + return tr("Texture has no data."); - QVariantMap imgMap = op(m_textureList.at(index.row())).toMap(); - if (imgMap.isEmpty()) - return noData; - - return QLatin1String("%1\n%2\n%3").arg(imgMap.value("id").toString(), - source.split('/').last(), - imgMap.value("info").toString()); + QString sourceRelative = QmlObjectNode(texNode).modelValue("source").toString(); + return QLatin1String("%1\n%2\n%3").arg(texNode.id(), sourceRelative, info); } return {}; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 66b6f640faf..a7a163ab18b 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -797,10 +797,7 @@ void NavigatorTreeModel::handleMaterialDrop(const QMimeData *mimeData, int rowNu if (!targetNode.metaInfo().isQtQuick3DModel()) return; - QByteArray data = mimeData->data(Constants::MIME_TYPE_MATERIAL); - QDataStream stream(data); - qint32 internalId; - stream >> internalId; + qint32 internalId = mimeData->data(Constants::MIME_TYPE_MATERIAL).toInt(); ModelNode matNode = m_view->modelNodeForInternalId(internalId); m_view->executeInTransaction(__FUNCTION__, [&] { diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 2175f786691..31106a87356 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -253,10 +253,7 @@ void NavigatorView::dragStarted(QMimeData *mimeData) m_widget->setDragType(itemLibraryEntry.typeName()); m_widget->update(); } else if (mimeData->hasFormat(Constants::MIME_TYPE_MATERIAL)) { - QByteArray data = mimeData->data(Constants::MIME_TYPE_MATERIAL); - QDataStream stream(data); - qint32 internalId; - stream >> internalId; + qint32 internalId = mimeData->data(Constants::MIME_TYPE_MATERIAL).toInt(); ModelNode matNode = modelNodeForInternalId(internalId); m_widget->setDragType(matNode.metaInfo().typeName()); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp index e7ff48450d1..fc5a09818ca 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorimageprovider.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "propertyeditorimageprovider.h" -#include "assetslibrarymodel.h" +#include "asset.h" #include #include @@ -16,12 +16,12 @@ namespace QmlDesigner { QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - const QString suffix = "*." + id.split('.').last().toLower(); + Asset asset(id); - if (suffix == "*.mesh") + if (asset.suffix() == "*.mesh") return m_smallImageCacheProvider.requestImageResponse(id, requestedSize); - if (suffix == "*.builtin") + if (asset.suffix() == "*.builtin") return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(), requestedSize); @@ -29,15 +29,15 @@ QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QSt QMetaObject::invokeMethod( response.get(), - [response = QPointer(response.get()), suffix, id, requestedSize] { - if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { - QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(id)); + [response = QPointer(response.get()), asset, requestedSize] { + if (asset.isImage()) { + QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(asset.id())); if (!image.isNull()) { response->setImage(image.scaled(requestedSize, Qt::KeepAspectRatio)); return; } - } else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { - HdrImage hdr{id}; + } else if (asset.isTexture3D()) { + HdrImage hdr{asset.id()}; if (!hdr.image().isNull()) { response->setImage(hdr.image().scaled(requestedSize, Qt::KeepAspectRatio)); return; diff --git a/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h b/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h index f4e09cf6f5b..036cb9df659 100644 --- a/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h +++ b/src/plugins/qmldesigner/designercore/include/stylesheetmerger.h @@ -5,6 +5,8 @@ #include "qmldesignercorelib_global.h" +#include "utils/filepath.h" + #include #include #include @@ -26,7 +28,12 @@ class QMLDESIGNERCORE_EXPORT StylesheetMerger public: StylesheetMerger(AbstractView*, AbstractView*); void merge(); - static void styleMerge(Model *model, const QString &templateFile, class ExternalDependenciesInterface &externalDependencies); + static void styleMerge(const Utils::FilePath &templateFile, + Model *model, + class ExternalDependenciesInterface &ed); + static void styleMerge(const QString &qmlTemplateString, + Model *model, + class ExternalDependenciesInterface &externalDependencies); private: void preprocessStyleSheet(); diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 661b303a940..c715b659787 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -25,6 +25,7 @@ #include "createscenecommand.h" #include "debugoutputcommand.h" #include "informationchangedcommand.h" +#include "imageutils.h" #include "inputeventcommand.h" #include "nodeabstractproperty.h" #include "nodeinstanceserverproxy.h" @@ -83,6 +84,8 @@ #include #include +#include +#include #include #include #include @@ -135,12 +138,13 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager // related to a single event to be received before we act. m_resetTimer.setSingleShot(true); m_resetTimer.setInterval(100); - QObject::connect(&m_resetTimer, &QTimer::timeout, [this] { - resetPuppet(); + QObject::connect(&m_resetTimer, &QTimer::timeout, this, [this] { + if (isAttached()) + resetPuppet(); }); m_updateWatcherTimer.setSingleShot(true); m_updateWatcherTimer.setInterval(100); - QObject::connect(&m_updateWatcherTimer, &QTimer::timeout, [this] { + QObject::connect(&m_updateWatcherTimer, &QTimer::timeout, this, [this] { for (const auto &path : std::as_const(m_pendingUpdateDirs)) updateWatcher(path); m_pendingUpdateDirs.clear(); @@ -151,11 +155,11 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager // unnecessary generation when project with multiple shaders is opened. m_generateQsbFilesTimer.setSingleShot(true); m_generateQsbFilesTimer.setInterval(100); - QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, [this] { + QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, this, [this] { handleShaderChanges(); }); - connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, + connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString &path) { const QSet pendingDirs = m_pendingUpdateDirs; for (const auto &pendingPath : pendingDirs) { @@ -171,7 +175,7 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager m_updateWatcherTimer.start(); }); - connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this](const QString &path) { + connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &path) { if (m_qsbTargets.contains(path)) { m_qsbTargets.insert(path, true); m_generateQsbFilesTimer.start(); @@ -1909,23 +1913,7 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio); imageData.pixmap.setDevicePixelRatio(ratio); imageData.time = modified; - - double imgSize = double(imageFi.size()); - static QStringList units({::QmlDesigner::NodeInstanceView::tr("B"), - ::QmlDesigner::NodeInstanceView::tr("KB"), - ::QmlDesigner::NodeInstanceView::tr("MB"), - ::QmlDesigner::NodeInstanceView::tr("GB")}); - int unitIndex = 0; - while (imgSize > 1024. && unitIndex < units.size() - 1) { - ++unitIndex; - imgSize /= 1024.; - } - imageData.info = QStringLiteral("%1 x %2\n%3%4 (%5)") - .arg(originalPixmap.width()) - .arg(originalPixmap.height()) - .arg(QString::number(imgSize, 'g', 3)) - .arg(units[unitIndex]) - .arg(imageFi.suffix()); + imageData.info = ImageUtils::imageInfo(imageSource); m_imageDataMap.insert(imageData.id, imageData); } } diff --git a/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp b/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp index 268f43eb42d..56b3cdafc2d 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetstarter.cpp @@ -35,7 +35,7 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath, processFinishCallback); if (forwardOutput == puppetMode || forwardOutput == "all") { - puppetProcess->setProcessChannelMode(QProcess::MergedChannels); + puppetProcess->setProcessChannelMode(QProcess::ForwardedChannels); QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); } puppetProcess->setWorkingDirectory(workingDirectory); @@ -46,7 +46,6 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath, else processArguments = {socketToken, puppetMode}; - processArguments.push_back("-graphicssystem raster"); processArguments.push_back(freeTypeOption); puppetProcess->start(puppetPath, processArguments); diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index cdda880f84b..7914ca76437 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -562,9 +562,9 @@ QVector getObjectTypes(const ObjectValue *objectValue, const Conte const CppComponentValue * qmlObjectValue = value_cast(prototype); if (qmlObjectValue) - propertyList.append(getQmlTypes(qmlObjectValue, context, local, rec)); + propertyList.append(getQmlTypes(qmlObjectValue, context, local, rec + 1)); else - propertyList.append(getObjectTypes(prototype, context, local, rec)); + propertyList.append(getObjectTypes(prototype, context, local, rec + 1)); } return propertyList; diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index 20d39362a0e..50497c58359 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -559,6 +559,7 @@ void AbstractView::resetView() void AbstractView::resetPuppet() { + QTC_ASSERT(isAttached(), return); emitCustomNotification(QStringLiteral("reset QmlPuppet")); } @@ -689,7 +690,8 @@ void AbstractView::emitCustomNotification(const QString &identifier, const QList void AbstractView::emitCustomNotification(const QString &identifier, const QList &nodeList, const QList &data) { - model()->d->notifyCustomNotification(this, identifier, nodeList, data); + if (model()) + model()->d->notifyCustomNotification(this, identifier, nodeList, data); } void AbstractView::emitInstancePropertyChange(const QList > &propertyList) diff --git a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp index dd377cc0180..6df3e58a1d1 100644 --- a/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/stylesheetmerger.cpp @@ -515,11 +515,24 @@ void StylesheetMerger::merge() } } -void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, ExternalDependenciesInterface &externalDependencies) +void StylesheetMerger::styleMerge(const Utils::FilePath &templateFile, + Model *model, + ExternalDependenciesInterface &externalDependencies) +{ + Utils::FileReader reader; + + QTC_ASSERT(reader.fetch(templateFile), return ); + const QString qmlTemplateString = QString::fromUtf8(reader.data()); + StylesheetMerger::styleMerge(qmlTemplateString, model, externalDependencies); +} + +void StylesheetMerger::styleMerge(const QString &qmlTemplateString, + Model *model, + ExternalDependenciesInterface &externalDependencies) { Model *parentModel = model; - QTC_ASSERT(parentModel, return); + QTC_ASSERT(parentModel, return ); auto templateModel(Model::create("QtQuick.Item", 2, 1, parentModel)); Q_ASSERT(templateModel.get()); @@ -527,10 +540,6 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext templateModel->setFileUrl(parentModel->fileUrl()); QPlainTextEdit textEditTemplate; - Utils::FileReader reader; - - QTC_ASSERT(reader.fetch(Utils::FilePath::fromString(templateFile)), return); - QString qmlTemplateString = QString::fromUtf8(reader.data()); QString imports; for (const Import &import : parentModel->imports()) { @@ -541,13 +550,14 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext textEditTemplate.setPlainText(imports + qmlTemplateString); NotIndentingTextEditModifier textModifierTemplate(&textEditTemplate); - QScopedPointer templateRewriterView(new RewriterView(externalDependencies, RewriterView::Amend)); + QScopedPointer templateRewriterView( + new RewriterView(externalDependencies, RewriterView::Amend)); templateRewriterView->setTextModifier(&textModifierTemplate); templateModel->attachView(templateRewriterView.data()); templateRewriterView->setCheckSemanticErrors(false); ModelNode templateRootNode = templateRewriterView->rootModelNode(); - QTC_ASSERT(templateRootNode.isValid(), return); + QTC_ASSERT(templateRootNode.isValid(), return ); auto styleModel(Model::create("QtQuick.Item", 2, 1, parentModel)); Q_ASSERT(styleModel.get()); @@ -556,11 +566,12 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext QPlainTextEdit textEditStyle; RewriterView *parentRewriterView = parentModel->rewriterView(); - QTC_ASSERT(parentRewriterView, return); + QTC_ASSERT(parentRewriterView, return ); textEditStyle.setPlainText(parentRewriterView->textModifierContent()); NotIndentingTextEditModifier textModifierStyle(&textEditStyle); - QScopedPointer styleRewriterView(new RewriterView(externalDependencies, RewriterView::Amend)); + QScopedPointer styleRewriterView( + new RewriterView(externalDependencies, RewriterView::Amend)); styleRewriterView->setTextModifier(&textModifierStyle); styleModel->attachView(styleRewriterView.data()); diff --git a/src/plugins/qmldesigner/utils/asset.cpp b/src/plugins/qmldesigner/utils/asset.cpp new file mode 100644 index 00000000000..6a0a4658429 --- /dev/null +++ b/src/plugins/qmldesigner/utils/asset.cpp @@ -0,0 +1,182 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include + +#include "asset.h" + +namespace QmlDesigner { + +Asset::Asset(const QString &filePath) + : m_filePath(filePath) +{ + m_suffix = "*." + filePath.split('.').last().toLower(); +} + + +const QStringList &Asset::supportedImageSuffixes() +{ + static QStringList retList; + if (retList.isEmpty()) { + const QList suffixes = QImageReader::supportedImageFormats(); + for (const QByteArray &suffix : suffixes) + retList.append("*." + QString::fromUtf8(suffix)); + } + return retList; +} + +const QStringList &Asset::supportedFragmentShaderSuffixes() +{ + static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"}; + return retList; +} + +const QStringList &Asset::supportedShaderSuffixes() +{ + static const QStringList retList {"*.frag", "*.vert", + "*.glsl", "*.glslv", "*.glslf", + "*.vsh", "*.fsh"}; + return retList; +} + +const QStringList &Asset::supportedFontSuffixes() +{ + static const QStringList retList {"*.ttf", "*.otf"}; + return retList; +} + +const QStringList &Asset::supportedAudioSuffixes() +{ + static const QStringList retList {"*.wav", "*.mp3"}; + return retList; +} + +const QStringList &Asset::supportedVideoSuffixes() +{ + static const QStringList retList {"*.mp4"}; + return retList; +} + +const QStringList &Asset::supportedTexture3DSuffixes() +{ + // These are file types only supported by 3D textures + static QStringList retList {"*.hdr", "*.ktx"}; + return retList; +} + +const QStringList &Asset::supportedEffectMakerSuffixes() +{ + // These are file types only supported by Effect Maker + static QStringList retList {"*.qep"}; + return retList; +} + +const QSet &Asset::supportedSuffixes() +{ + static QSet allSuffixes; + if (allSuffixes.isEmpty()) { + auto insertSuffixes = [](const QStringList &suffixes) { + for (const auto &suffix : suffixes) + allSuffixes.insert(suffix); + }; + insertSuffixes(supportedImageSuffixes()); + insertSuffixes(supportedShaderSuffixes()); + insertSuffixes(supportedFontSuffixes()); + insertSuffixes(supportedAudioSuffixes()); + insertSuffixes(supportedVideoSuffixes()); + insertSuffixes(supportedTexture3DSuffixes()); + insertSuffixes(supportedEffectMakerSuffixes()); + } + return allSuffixes; +} + +Asset::Type Asset::type() const +{ + if (supportedImageSuffixes().contains(m_suffix)) + return Asset::Type::Image; + + if (supportedFragmentShaderSuffixes().contains(m_suffix)) + return Asset::Type::FragmentShader; + + if (supportedShaderSuffixes().contains(m_suffix)) + return Asset::Type::Shader; + + if (supportedFontSuffixes().contains(m_suffix)) + return Asset::Type::Font; + + if (supportedAudioSuffixes().contains(m_suffix)) + return Asset::Type::Audio; + + if (supportedVideoSuffixes().contains(m_suffix)) + return Asset::Type::Video; + + if (supportedTexture3DSuffixes().contains(m_suffix)) + return Asset::Type::Texture3D; + + if (supportedEffectMakerSuffixes().contains(m_suffix)) + return Asset::Type::Effect; + + return Asset::Type::Unknown; +} + +bool Asset::isImage() const +{ + return type() == Asset::Type::Image; +} + +bool Asset::isFragmentShader() const +{ + return type() == Asset::Type::FragmentShader; +} + +bool Asset::isShader() const +{ + return type() == Asset::Type::Shader; +} + +bool Asset::isFont() const +{ + return type() == Asset::Type::Font; +} + +bool Asset::isAudio() const +{ + return type() == Asset::Type::Audio; +} + +bool Asset::isVideo() const +{ + return type() == Asset::Type::Video; +} + +bool Asset::isTexture3D() const +{ + return type() == Asset::Type::Texture3D; +} + +bool Asset::isEffect() const +{ + return type() == Asset::Type::Effect; +} + +const QString Asset::suffix() const +{ + return m_suffix; +} + +const QString Asset::id() const +{ + return m_filePath; +} + +bool Asset::isSupported() const +{ + return supportedSuffixes().contains(m_filePath); +} + +bool Asset::hasSuffix() const +{ + return !m_suffix.isEmpty(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/utils/asset.h b/src/plugins/qmldesigner/utils/asset.h new file mode 100644 index 00000000000..279edb93e1b --- /dev/null +++ b/src/plugins/qmldesigner/utils/asset.h @@ -0,0 +1,45 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +namespace QmlDesigner { + +class Asset +{ +public: + enum Type { Unknown, Image, FragmentShader, Font, Audio, Video, Texture3D, Effect, Shader }; + + Asset(const QString &filePath); + + static const QStringList &supportedImageSuffixes(); + static const QStringList &supportedFragmentShaderSuffixes(); + static const QStringList &supportedShaderSuffixes(); + static const QStringList &supportedFontSuffixes(); + static const QStringList &supportedAudioSuffixes(); + static const QStringList &supportedVideoSuffixes(); + static const QStringList &supportedTexture3DSuffixes(); + static const QStringList &supportedEffectMakerSuffixes(); + static const QSet &supportedSuffixes(); + + const QString suffix() const; + const QString id() const; + bool hasSuffix() const; + + Type type() const; + bool isImage() const; + bool isFragmentShader() const; + bool isShader() const; + bool isFont() const; + bool isAudio() const; + bool isVideo() const; + bool isTexture3D() const; + bool isEffect() const; + bool isSupported() const; + +private: + QString m_filePath; + QString m_suffix; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/utils/imageutils.cpp b/src/plugins/qmldesigner/utils/imageutils.cpp new file mode 100644 index 00000000000..c1e3b8f4950 --- /dev/null +++ b/src/plugins/qmldesigner/utils/imageutils.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "imageutils.h" + +#include +#include +#include + +namespace QmlDesigner { + +QString QmlDesigner::ImageUtils::imageInfo(const QString &path) +{ + QFileInfo info(path); + if (!info.exists()) + return {}; + + int width = 0; + int height = 0; + if (info.suffix() == "hdr") { + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return {}; + + while (!file.atEnd()) { + QByteArray line = file.readLine(); + if (sscanf(line.constData(), "-Y %d +X %d", &height, &width)) + break; + } + } else { + QSize size = QImageReader(path).size(); + width = size.width(); + height = size.height(); + } + + if (width == 0 && height == 0) + return {}; + + return QLatin1String("%1 x %2\n%3 (%4)") + .arg(QString::number(width), + QString::number(height), + QLocale::system().formattedDataSize(info.size(), 2, QLocale::DataSizeTraditionalFormat), + info.suffix()); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/utils/imageutils.h b/src/plugins/qmldesigner/utils/imageutils.h new file mode 100644 index 00000000000..3b740b76b1e --- /dev/null +++ b/src/plugins/qmldesigner/utils/imageutils.h @@ -0,0 +1,17 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#pragma once + +#include + +namespace QmlDesigner { + +class ImageUtils +{ +public: + ImageUtils(); + + static QString imageInfo(const QString &path); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/studiowelcome/examplecheckout.cpp b/src/plugins/studiowelcome/examplecheckout.cpp index 8d7eee5a13c..0211b8d3cd0 100644 --- a/src/plugins/studiowelcome/examplecheckout.cpp +++ b/src/plugins/studiowelcome/examplecheckout.cpp @@ -75,13 +75,13 @@ void FileDownloader::start() auto request = QNetworkRequest(m_url); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::UserVerifiedRedirectPolicy); - QNetworkReply *reply = Utils::NetworkAccessManager::instance()->get(request); + m_reply = Utils::NetworkAccessManager::instance()->get(request); - QNetworkReply::connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { - m_tempFile.write(reply->readAll()); + QNetworkReply::connect(m_reply, &QNetworkReply::readyRead, this, [this]() { + m_tempFile.write(m_reply->readAll()); }); - QNetworkReply::connect(reply, + QNetworkReply::connect(m_reply, &QNetworkReply::downloadProgress, this, [this](qint64 current, qint64 max) { @@ -92,16 +92,21 @@ void FileDownloader::start() emit progressChanged(); }); - QNetworkReply::connect(reply, &QNetworkReply::redirected, [reply](const QUrl &) { - emit reply->redirectAllowed(); + QNetworkReply::connect(m_reply, &QNetworkReply::redirected, [this](const QUrl &) { + emit m_reply->redirectAllowed(); }); - QNetworkReply::connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error()) { + QNetworkReply::connect(m_reply, &QNetworkReply::finished, this, [this]() { + if (m_reply->error()) { if (m_tempFile.exists()) m_tempFile.remove(); - qWarning() << Q_FUNC_INFO << m_url << reply->errorString(); - emit downloadFailed(); + + if (m_reply->error() != QNetworkReply::OperationCanceledError) { + qWarning() << Q_FUNC_INFO << m_url << m_reply->errorString(); + emit downloadFailed(); + } else { + emit downloadCanceled(); + } } else { m_tempFile.flush(); m_tempFile.close(); @@ -109,9 +114,17 @@ void FileDownloader::start() emit tempFileChanged(); emit finishedChanged(); } + + m_reply = nullptr; }); } +void FileDownloader::cancel() +{ + if (m_reply) + m_reply->abort(); +} + void FileDownloader::setUrl(const QUrl &url) { m_url = url; diff --git a/src/plugins/studiowelcome/examplecheckout.h b/src/plugins/studiowelcome/examplecheckout.h index 7e7a0dff882..8c1446518c9 100644 --- a/src/plugins/studiowelcome/examplecheckout.h +++ b/src/plugins/studiowelcome/examplecheckout.h @@ -112,6 +112,7 @@ public: bool available() const; Q_INVOKABLE void start(); + Q_INVOKABLE void cancel(); signals: void finishedChanged(); @@ -123,6 +124,8 @@ signals: void lastModifiedChanged(); void availableChanged(); + void downloadCanceled(); + private: void probeUrl(); @@ -133,6 +136,8 @@ private: QFile m_tempFile; QDateTime m_lastModified; bool m_available = false; + + QNetworkReply *m_reply = nullptr; }; class DataModelDownloader : public QObject diff --git a/src/plugins/updateinfo/updateinfoplugin.cpp b/src/plugins/updateinfo/updateinfoplugin.cpp index bac6122a86c..25c547e1358 100644 --- a/src/plugins/updateinfo/updateinfoplugin.cpp +++ b/src/plugins/updateinfo/updateinfoplugin.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -223,7 +224,12 @@ static void showUpdateInfo(const QList &updates, label->setText("

" + UpdateInfoPlugin::tr("Available updates:") + "

  • " + qtText + updateText + "

"); label->setContentsMargins(0, 0, 0, 8); - return label; + auto scrollArea = new QScrollArea; + scrollArea->setWidget(label); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->viewport()->setAutoFillBackground(false); + label->setAutoFillBackground(false); + return scrollArea; }); } ICore::infoBar()->removeInfo(InstallUpdates); // remove any existing notifications diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 88da74ea363..9703bb2cdb9 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -23,6 +23,8 @@ if (NOT QT_CREATOR_API_DEFINED) ) endif() +configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES) + if (NOT TARGET QmlPuppetCommunication) include(../../libs/qmlpuppetcommunication/QmlPuppetCommunication.cmake) endif() @@ -34,13 +36,34 @@ add_qtc_executable(qml2puppet Qt5::CorePrivate Qt5::Widgets Qt5::QmlPrivate Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate QmlPuppetCommunication + INCLUDES + ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} SOURCES - qml2puppet/qml2puppetmain.cpp + qml2puppet/main.cpp + qml2puppet/qmlbase.h qml2puppet/appmetadata.h + qml2puppet/qmlpuppet.h qml2puppet/qmlpuppet.cpp qml2puppet/configcrashpad.h qmlpuppet.qrc PROPERTIES OUTPUT_NAME qml2puppet-${IDE_VERSION} ) +if(TARGET qml2puppet) + execute_process( + COMMAND git describe --tags --always --dirty=+ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE GIT_SHA_RESULT + OUTPUT_VARIABLE GIT_SHA_OUTPUT + ERROR_VARIABLE GIT_SHA_ERROR + ) + + #if we are not a git repository use the .tag file + if(NOT GIT_SHA_OUTPUT) + file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../../.tag GIT_SHA_OUTPUT) + endif() + + add_definitions( -D GIT_SHA=${GIT_SHA_OUTPUT} ) +endif() + extend_qtc_executable(qml2puppet CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 SOURCES @@ -191,10 +214,27 @@ extend_qtc_executable(qml2puppet DEPENDS Nanotrace ) -if (QTC_STATIC_BUILD AND Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0) - qt6_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR}) +if (Qt5_VERSION VERSION_GREATER_EQUAL 6.4.0) + extend_qtc_executable(qml2puppet + DEFINES ENABLE_INTERNAL_QML_RUNTIME + PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/qml2puppet/runner + SOURCES_PREFIX qml2puppet/runner + SOURCES + qmlruntime.h qmlruntime.cpp + qmlconfiguration.h loadwatcher.h + ) + + # Turn the tool into its own self-contained qml module + qt_add_qml_module(qml2puppet + URI QmlRuntime.QmlConfiguration + VERSION 1.0 + ) + if (QTC_STATIC_BUILD) + qt_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR}) + endif() endif() + # Crashpad # only windows requires separate crashpad client per process until client->SetHandlerIPCPipe() # is implemented (check the TODO inside startCrashpad()) diff --git a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml index 819f1e2be90..7f7b148460b 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml @@ -194,24 +194,18 @@ Item { function updateViewStates(viewStates) { if ("selectBackgroundColor" in viewStates) { - if (Array.isArray(viewStates.selectBackgroundColor)) { - var colors = viewStates.selectBackgroundColor - if (colors.length === 1) { - backgroundGradientColorStart = colors[0]; - backgroundGradientColorEnd = colors[0]; - } else { - backgroundGradientColorStart = colors[0]; - backgroundGradientColorEnd = colors[1]; - } + var colors = viewStates.selectBackgroundColor + if (colors.length === 1) { + backgroundGradientColorStart = colors[0]; + backgroundGradientColorEnd = colors[0]; } else { - var color = viewStates.selectBackgroundColor - backgroundGradientColorStart = color; - backgroundGradientColorEnd = color; + backgroundGradientColorStart = colors[0]; + backgroundGradientColorEnd = colors[1]; } } - if ("selectGridColor" in viewStates) - viewRoot.gridColor = viewStates.selectGridColor + if ("selectGridColor" in viewStates && viewStates.selectGridColor.length === 1) + viewRoot.gridColor = viewStates.selectGridColor[0] } // If resetToDefault is true, tool states not specifically set to anything will be reset to diff --git a/src/tools/qml2puppet/qml2puppet/appmetadata.h b/src/tools/qml2puppet/qml2puppet/appmetadata.h new file mode 100644 index 00000000000..7f11276e619 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/appmetadata.h @@ -0,0 +1,96 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH +// Qt-GPL-exception-1.0 +#pragma once + +#include +#include + +#include + +// Common functions can be used in all QDS apps +namespace QDSMeta { + +namespace Logging { +inline Q_LOGGING_CATEGORY(deprecated, "qt.tools.qds.deprecated"); +inline Q_LOGGING_CATEGORY(verbose1, "qt.tools.qds.verbose1"); +inline Q_LOGGING_CATEGORY(verbose2, "qt.tools.qds.verbose2"); + +inline void registerMessageHandler() +{ + qInstallMessageHandler( + [](QtMsgType type, const QMessageLogContext &context, const QString &msg) { + auto tPrinter = [&](const QString &msgPrefix) { + fprintf(stderr, + "%s: %s (%s:%u, %s)\n", + msgPrefix.toLocal8Bit().constData(), + msg.toLocal8Bit().constData(), + context.file, + context.line, + context.function); + }; + + if (type == QtDebugMsg) + tPrinter("Debug"); + else if (type == QtInfoMsg) + tPrinter("Info"); + else if (type == QtWarningMsg) + tPrinter("Warning"); + else if (type == QtCriticalMsg) + tPrinter("Critical"); + else if (type == QtFatalMsg) { + tPrinter("Fatal"); + abort(); + } + }); +} +} // namespace Logging + +namespace AppInfo { + +#define STRINGIFY_INTERNAL(x) #x +#define QDS_STRINGIFY(x) STRINGIFY_INTERNAL(x) + +inline void printAppInfo() +{ + qInfo() << Qt::endl + << "<< QDS Meta Info >>" << Qt::endl + << "App Info" << Qt::endl + << " - Name :" << Core::Constants::IDE_ID << Qt::endl + << " - Version :" << Core::Constants::IDE_VERSION_DISPLAY << Qt::endl + << " - Author :" << Core::Constants::IDE_AUTHOR << Qt::endl + << " - Year :" << Core::Constants::IDE_YEAR << Qt::endl + << " - App :" << QCoreApplication::applicationName() << Qt::endl + << "Build Info " << Qt::endl + << " - Date :" << __DATE__ << Qt::endl + << " - Commit :" << QStringLiteral(QDS_STRINGIFY(GIT_SHA)) << Qt::endl + << " - Qt Version :" << QT_VERSION_STR << Qt::endl + << "Compiler Info " << Qt::endl +#if defined(__GNUC__) + << " - GCC :" << __GNUC__ << Qt::endl + << " - GCC Minor :" << __GNUC_MINOR__ << Qt::endl + << " - GCC Patch :" << __GNUC_PATCHLEVEL__ << Qt::endl +#endif +#if defined(_MSC_VER) + << " - MSC Short :" << _MSC_VER << Qt::endl + << " - MSC Full :" << _MSC_FULL_VER << Qt::endl +#endif +#if defined(__clang__) + << " - clang maj :" << __clang_major__ << Qt::endl + << " - clang min :" << __clang_minor__ << Qt::endl + << " - clang patch :" << __clang_patchlevel__ << Qt::endl +#endif + << "<< End Of QDS Meta Info >>" << Qt::endl; + exit(0); +} + +inline void registerAppInfo(const QString &appName) +{ + QCoreApplication::setOrganizationName(Core::Constants::IDE_AUTHOR); + QCoreApplication::setOrganizationDomain("qt-project.org"); + QCoreApplication::setApplicationName(appName); + QCoreApplication::setApplicationVersion(Core::Constants::IDE_VERSION_LONG); +} + +} // namespace AppInfo +} // namespace QDSMeta diff --git a/src/tools/qml2puppet/qml2puppet/configcrashpad.h b/src/tools/qml2puppet/qml2puppet/configcrashpad.h new file mode 100644 index 00000000000..7b663b84498 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/configcrashpad.h @@ -0,0 +1,66 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#pragma once + +#include + +#ifdef ENABLE_QT_BREAKPAD +#include +#endif + +#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) +#define NOMINMAX +#include "client/crash_report_database.h" +#include "client/crashpad_client.h" +#include "client/settings.h" +#endif + +namespace { +#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) + bool startCrashpad() + { + using namespace crashpad; + + // Cache directory that will store crashpad information and minidumps + base::FilePath database(L"crashpad_reports"); + base::FilePath handler(L"crashpad_handler.exe"); + + // URL used to submit minidumps to + std::string url(CRASHPAD_BACKEND_URL); + + // Optional annotations passed via --annotations to the handler + std::map annotations; + annotations["qt-version"] = QT_VERSION_STR; + + // Optional arguments to pass to the handler + std::vector arguments; + arguments.push_back("--no-rate-limit"); + + CrashpadClient *client = new CrashpadClient(); + bool success = client->StartHandler(handler, + database, + database, + url, + annotations, + arguments, + /* restartable */ true, + /* asynchronous_start */ true); + // TODO: research using this method, should avoid creating a separate CrashpadClient for the + // puppet (needed only on windows according to docs). + // client->SetHandlerIPCPipe(L"\\\\.\\pipe\\qml2puppet"); + + return success; + } + +#ifdef ENABLE_QT_BREAKPAD + const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + + RELATIVE_LIBEXEC_PATH; + QtSystemExceptionHandler systemExceptionHandler(libexecPath); +#endif //#ifdef ENABLE_QT_BREAKPAD +#else //#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) + bool startCrashpad() + { + return false; + } +#endif //#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) +} diff --git a/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.cpp index d3209e4ccbe..05692d000bd 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -1043,6 +1043,21 @@ QList NodeInstanceServer::allGroupStateInstances() const return groups; } +QList NodeInstanceServer::allView3DInstances() const +{ + QList view3Ds; + std::copy_if(nodeInstances().cbegin(), + nodeInstances().cend(), + std::back_inserter(view3Ds), + [](const ServerNodeInstance &instance) { + return instance.isValid() + && ServerNodeInstance::isSubclassOf(instance.internalObject(), + QByteArrayLiteral("QQuick3DViewport")); + }); + + return view3Ds; +} + void NodeInstanceServer::setStateInstance(const ServerNodeInstance &stateInstance) { m_activeStateInstance = stateInstance; diff --git a/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.h index 59236a236fc..70b0c841859 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserver.h @@ -179,6 +179,7 @@ public: ServerNodeInstance rootNodeInstance() const; QList allGroupStateInstances() const; + QList allView3DInstances() const; void notifyPropertyChange(qint32 instanceid, const PropertyName &propertyName); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index f71d1ebde8b..a84409b3331 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -921,9 +921,9 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D([[maybe_unu if (toolStates.contains("syncBackgroundColor")) { bool sync = toolStates["syncBackgroundColor"].toBool(); if (sync) { - QColor color = helper->sceneEnvironmentColor(sceneId); + QList colors{helper->sceneEnvironmentColor(sceneId)}; View3DActionCommand cmd(View3DActionType::SelectBackgroundColor, - QVariant::fromValue(color)); + QVariant::fromValue(colors)); view3DAction(cmd); } } @@ -2276,9 +2276,10 @@ void Qt5InformationNodeInstanceServer::setSceneEnvironmentColor(const PropertyVa if (toolStates.contains("syncBackgroundColor")) { bool sync = toolStates["syncBackgroundColor"].toBool(); + QList colors{color}; if (sync) { View3DActionCommand cmd(View3DActionType::SelectBackgroundColor, - QVariant::fromValue(color)); + QVariant::fromValue(colors)); view3DAction(cmd); } } diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp index 6f3fced1b78..125a128594d 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp @@ -53,28 +53,41 @@ void Qt5PreviewNodeInstanceServer::collectItemChangesAndSendChangeCommands() QQuickDesignerSupport::polishItems(quickWindow()); QVector imageContainerVector; + + // Base state needs to be rendered twice to properly render shared resources, + // if there is more than one View3D and at least one of them is dirty. + bool dirtyView3d = false; + const QList view3dInstances = allView3DInstances(); + for (const auto &instance : view3dInstances) { + if (QQuickDesignerSupport::isDirty(instance.rootQuickItem(), + QQuickDesignerSupport::ContentUpdateMask)) { + dirtyView3d = true; + break; + } + } + if (dirtyView3d) + renderPreviewImage(); imageContainerVector.append(ImageContainer(0, renderPreviewImage(), -1)); - QList stateInstances = rootNodeInstance().stateInstances(); + QList stateInstances = rootNodeInstance().stateInstances(); - const QList groupInstances = allGroupStateInstances(); + const QList groupInstances = allGroupStateInstances(); - for (ServerNodeInstance instance : groupInstances) { - stateInstances.append(instance.stateInstances()); - } + for (const ServerNodeInstance &instance : groupInstances) + stateInstances.append(instance.stateInstances()); - for (ServerNodeInstance instance : std::as_const(stateInstances)) { - instance.activateState(); - QImage previewImage = renderPreviewImage(); - if (!previewImage.isNull()) - imageContainerVector.append(ImageContainer(instance.instanceId(), - renderPreviewImage(), - instance.instanceId())); - instance.deactivateState(); - } + for (ServerNodeInstance instance : std::as_const(stateInstances)) { + instance.activateState(); + QImage previewImage = renderPreviewImage(); + if (!previewImage.isNull()) + imageContainerVector.append(ImageContainer(instance.instanceId(), + renderPreviewImage(), + instance.instanceId())); + instance.deactivateState(); + } nodeInstanceClient()->statePreviewImagesChanged( - StatePreviewImageChangedCommand(imageContainerVector)); + StatePreviewImageChangedCommand(imageContainerVector)); slowDownRenderTimer(); handleExtraRender(); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp index b1af7fb4f6d..c237b9451b3 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp @@ -56,6 +56,7 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands() if (quickWindow() && nodeInstanceClient()->bytesToWrite() < 10000) { bool windowDirty = false; + bool hasView3D = false; for (QQuickItem *item : allItems()) { if (item) { if (Internal::QuickItemNodeInstance::unifiedRenderPath()) { @@ -65,8 +66,13 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands() } } else { if (hasInstanceForObject(item)) { - if (QQuickDesignerSupport::isDirty(item, QQuickDesignerSupport::ContentUpdateMask)) + if (QQuickDesignerSupport::isDirty(item, QQuickDesignerSupport::ContentUpdateMask)) { + if (!hasView3D && ServerNodeInstance::isSubclassOf( + item, QByteArrayLiteral("QQuick3DViewport"))) { + hasView3D = true; + } m_dirtyInstanceSet.insert(instanceForObject(item)); + } if (QQuickItem *effectParent = parentEffectItem(item)) { if ((QQuickDesignerSupport::isDirty( item, @@ -100,9 +106,19 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands() nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); } else { if (!m_dirtyInstanceSet.isEmpty()) { - nodeInstanceClient()->pixmapChanged( - createPixmapChangedCommand(QtHelpers::toList(m_dirtyInstanceSet))); - m_dirtyInstanceSet.clear(); + auto renderList = QtHelpers::toList(m_dirtyInstanceSet); + + // If there is a View3D to be rendered, add all other View3Ds to be rendered + // as well, in case they share materials. + if (hasView3D) { + const QList view3Ds = allView3DInstances(); + for (auto &view3D : view3Ds) { + if (!m_dirtyInstanceSet.contains(view3D)) + renderList.append(view3D); + } + } + + nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand(renderList)); } } diff --git a/src/tools/qml2puppet/qml2puppet/main.cpp b/src/tools/qml2puppet/qml2puppet/main.cpp new file mode 100644 index 00000000000..84f273a31cb --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/main.cpp @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "qmlpuppet.h" + +#ifdef ENABLE_INTERNAL_QML_RUNTIME +#include "runner/qmlruntime.h" +#endif + +QmlBase *getQmlRunner(int &argc, char **argv) +{ +#ifdef ENABLE_INTERNAL_QML_RUNTIME + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--qml-runtime")){ + qInfo() << "Starting QML Runtime"; + return new QmlRuntime(argc, argv); + } + } +#endif + qInfo() << "Starting QML Puppet"; + return new QmlPuppet(argc, argv); +} + +int main(int argc, char *argv[]) +{ + QDSMeta::Logging::registerMessageHandler(); + QDSMeta::AppInfo::registerAppInfo("Qml2Puppet"); + + QmlBase *qmlRunner = getQmlRunner(argc, argv); + return qmlRunner->run(); +} diff --git a/src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp b/src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp deleted file mode 100644 index 2d7518b81bc..00000000000 --- a/src/tools/qml2puppet/qml2puppet/qml2puppetmain.cpp +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "iconrenderer/iconrenderer.h" -#include "import3d/import3d.h" - -#include -#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#ifdef ENABLE_QT_BREAKPAD -#include -#endif - -#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) -#define NOMINMAX -#include "client/crashpad_client.h" -#include "client/crash_report_database.h" -#include "client/settings.h" -#endif - -#ifdef Q_OS_WIN -#include -#endif - -namespace { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - QByteArray localMsg = msg.toLocal8Bit(); - switch (type) { - case QtDebugMsg: - fprintf(stderr, - "Debug: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtInfoMsg: - fprintf(stderr, - "Info: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtWarningMsg: - fprintf(stderr, - "Warning: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtCriticalMsg: - fprintf(stderr, - "Critical: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - break; - case QtFatalMsg: - fprintf(stderr, - "Fatal: %s (%s:%u, %s)\n", - localMsg.constData(), - context.file, - context.line, - context.function); - abort(); - } -} -#endif - -#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) -bool startCrashpad() -{ - using namespace crashpad; - - // Cache directory that will store crashpad information and minidumps - base::FilePath database(L"crashpad_reports"); - base::FilePath handler(L"crashpad_handler.exe"); - - // URL used to submit minidumps to - std::string url(CRASHPAD_BACKEND_URL); - - // Optional annotations passed via --annotations to the handler - std::map annotations; - annotations["qt-version"] = QT_VERSION_STR; - - // Optional arguments to pass to the handler - std::vector arguments; - arguments.push_back("--no-rate-limit"); - - CrashpadClient *client = new CrashpadClient(); - bool success = client->StartHandler( - handler, - database, - database, - url, - annotations, - arguments, - /* restartable */ true, - /* asynchronous_start */ true - ); - // TODO: research using this method, should avoid creating a separate CrashpadClient for the - // puppet (needed only on windows according to docs). -// client->SetHandlerIPCPipe(L"\\\\.\\pipe\\qml2puppet"); - - return success; -} -#endif - -int internalMain(QGuiApplication *application) -{ - QCoreApplication::setOrganizationName("QtProject"); - QCoreApplication::setOrganizationDomain("qt-project.org"); - QCoreApplication::setApplicationName("Qml2Puppet"); - QCoreApplication::setApplicationVersion("1.0.0"); - - if (application->arguments().count() < 2 - || (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() < 3) - || (application->arguments().at(1) == "--rendericon" && application->arguments().count() < 5) - || (application->arguments().at(1) == "--import3dAsset" && application->arguments().count() < 6)) { - qDebug() << "Usage:\n"; - qDebug() << "--test"; - qDebug() << "--version"; - qDebug() << "--readcapturedstream [control stream file]"; - qDebug() << "--rendericon "; - qDebug() << "--import3dAsset "; - - return -1; - } - - if (application->arguments().at(1) == "--readcapturedstream" && application->arguments().count() > 2) { - QFileInfo inputStreamFileInfo(application->arguments().at(2)); - if (!inputStreamFileInfo.exists()) { - qDebug() << "Input stream does not exist:" << inputStreamFileInfo.absoluteFilePath(); - - return -1; - } - - if (application->arguments().count() > 3) { - QFileInfo controlStreamFileInfo(application->arguments().at(3)); - if (!controlStreamFileInfo.exists()) { - qDebug() << "Output stream does not exist:" << controlStreamFileInfo.absoluteFilePath(); - - return -1; - } - } - } - - if (application->arguments().count() == 2 && application->arguments().at(1) == "--test") { - qDebug() << QCoreApplication::applicationVersion(); - QQmlEngine engine; - - QQmlComponent component(&engine); - component.setData("import QtQuick 2.0\nItem {\n}\n", QUrl::fromLocalFile("test.qml")); - - QObject *object = component.create(); - - if (object) { - qDebug() << "Basic QtQuick 2.0 working..."; - } else { - qDebug() << "Basic QtQuick 2.0 not working..."; - qDebug() << component.errorString(); - } - delete object; - return 0; - } - - if (application->arguments().count() == 2 && application->arguments().at(1) == "--version") { - std::cout << 2; - return 0; - } - - if (application->arguments().at(1) != "--readcapturedstream" && application->arguments().count() < 4) { - qDebug() << "Wrong argument count: " << application->arguments().count(); - return -1; - } - - if (application->arguments().at(1) == "--rendericon") { - int size = application->arguments().at(2).toInt(); - QString iconFileName = application->arguments().at(3); - QString iconSource = application->arguments().at(4); - - IconRenderer *iconRenderer = new IconRenderer(size, iconFileName, iconSource); - iconRenderer->setupRender(); - - return application->exec(); - } - - if (application->arguments().at(1) == "--import3dAsset") { - QString sourceAsset = application->arguments().at(2); - QString outDir = application->arguments().at(3); - QString options = application->arguments().at(4); - - Import3D::import3D(sourceAsset, outDir, options); - - return application->exec(); - } - -#ifdef ENABLE_QT_BREAKPAD - const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH; - QtSystemExceptionHandler systemExceptionHandler(libexecPath); -#endif - -#if defined(ENABLE_CRASHPAD) && defined(Q_OS_WIN) - /* startCrashpad(); */ -#endif - - new QmlDesigner::Qt5NodeInstanceClientProxy(application); - -#if defined(Q_OS_WIN) && defined(QT_NO_DEBUG) - SetErrorMode(SEM_NOGPFAULTERRORBOX); //We do not want to see any message boxes -#endif - - if (application->arguments().at(1) == "--readcapturedstream") - return 0; - - return application->exec(); -} -} // namespace - -int main(int argc, char *argv[]) -{ -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - qInstallMessageHandler(myMessageOutput); -#endif - // Since we always render text into an FBO, we need to globally disable - // subpixel antialiasing and instead use gray. - qputenv("QSG_DISTANCEFIELD_ANTIALIASING", "gray"); -#ifdef Q_OS_MACOS - qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "true"); -#endif - - //If a style different from Desktop is set we have to use QGuiApplication - bool useGuiApplication = (!qEnvironmentVariableIsSet("QMLDESIGNER_FORCE_QAPPLICATION") - || qgetenv("QMLDESIGNER_FORCE_QAPPLICATION") != "true") - && qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE") - && qgetenv("QT_QUICK_CONTROLS_STYLE") != "Desktop"; - -#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER - Sqlite::LibraryInitializer::initialize(); -#endif - - if (useGuiApplication) { - QGuiApplication application(argc, argv); - return internalMain(&application); - } else { - QApplication application(argc, argv); - return internalMain(&application); - } -} diff --git a/src/tools/qml2puppet/qml2puppet/qmlbase.h b/src/tools/qml2puppet/qml2puppet/qmlbase.h new file mode 100644 index 00000000000..9bc41bef752 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/qmlbase.h @@ -0,0 +1,111 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH +// Qt-GPL-exception-1.0 + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include "appmetadata.h" +#include + +#include +class QmlBase : public QObject +{ + Q_OBJECT +public: + struct AppArgs + { + public: + int argc; + char **argv; + }; + + QmlBase(int &argc, char **argv, QObject *parent = nullptr) + : QObject{parent} + , m_args({argc, argv}) + { + m_argParser.setApplicationDescription("QML Runtime Provider for QDS"); + m_argParser.addOptions( + {{"qml-puppet", "Run QML Puppet (default)"}, + {"qml-runtime", "Run QML Runtime"}, + {"appinfo", "Print build information"}, + {"test", "Run test mode"} + }); + } + + int run() + { + populateParser(); + initCoreApp(); + initParser(); + initQmlRunner(); + return m_coreApp->exec(); + } + + QSharedPointer coreApp() const { return m_coreApp; } + +protected: + virtual void initCoreApp() = 0; + virtual void populateParser() = 0; + virtual void initQmlRunner() = 0; + + virtual int startTestMode() + { + qDebug() << "Test mode is not implemented for this type of runner"; + return 0; + } + + template + void createCoreApp() + { + m_coreApp.reset(new T(m_args.argc, m_args.argv)); + } + + QSharedPointer m_coreApp; + QCommandLineParser m_argParser; + QSharedPointer m_qmlEngine; + + AppArgs m_args; + +private: + void initParser() + { + QCommandLineOption optHelp = m_argParser.addHelpOption(); + QCommandLineOption optVers = m_argParser.addVersionOption(); + + if (!m_coreApp) { + qCritical() << "Cannot initialize coreapp!"; + m_argParser.showHelp(); + } + + if (!m_argParser.parse(m_coreApp->arguments())) { + std::cout << "Error: " << m_argParser.errorText().toStdString() << std::endl + << std::endl; + m_argParser.showHelp(1); + } else if (m_argParser.isSet(optVers)) { + m_argParser.showVersion(); + } else if (m_argParser.isSet(optHelp)) { + m_argParser.showHelp(0); + } else if (m_argParser.isSet("appinfo")) { + QDSMeta::AppInfo::printAppInfo(); + } else if (m_argParser.isSet("test")) { + exit(startTestMode()); + } + } +}; diff --git a/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp b/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp new file mode 100644 index 00000000000..8a3822a86e7 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/qmlpuppet.cpp @@ -0,0 +1,130 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "qmlpuppet.h" +#include "configcrashpad.h" + +#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER +#include +#endif + +#include +#include + +#include + +#include +#include +#include + +#if defined(Q_OS_WIN) && defined(QT_NO_DEBUG) + #include +#endif + +void QmlPuppet::initCoreApp() +{ + // Since we always render text into an FBO, we need to globally disable + // subpixel antialiasing and instead use gray. + qputenv("QSG_DISTANCEFIELD_ANTIALIASING", "gray"); +#ifdef Q_OS_MACOS + qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "true"); +#endif +#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER + Sqlite::LibraryInitializer::initialize(); +#endif + + //If a style different from Desktop is set we have to use QGuiApplication + bool useGuiApplication = (!qEnvironmentVariableIsSet("QMLDESIGNER_FORCE_QAPPLICATION") + || qgetenv("QMLDESIGNER_FORCE_QAPPLICATION") != "true") + && qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_STYLE") + && qgetenv("QT_QUICK_CONTROLS_STYLE") != "Desktop"; +#ifndef QT_GUI_LIB + createCoreApp(); +#else +#if defined QT_WIDGETS_LIB + if (!useGuiApplication) + createCoreApp(); + else +#endif //QT_WIDGETS_LIB + createCoreApp(); +#endif //QT_GUI_LIB +} + +int QmlPuppet::startTestMode() +{ + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData("import QtQuick 2.0\nItem {\n}\n", QUrl::fromLocalFile("test.qml")); + + if (!QSharedPointer(component.create())) { + qDebug() << "Basic QtQuick 2.0 not working..."; + qDebug() << component.errorString(); + return -1; + } + + qDebug() << "Basic QtQuick 2.0 working..."; + return 0; +} + +void QmlPuppet::populateParser() +{ + // we're not using the commandline parser but just populating the help text + m_argParser.addOptions( + {{"readcapturedstream", "Read captured stream.", "inputStream, [outputStream]"}, + {"rendericon", "Renders icon.", "size, fileName, sourceQml"}, + {"import3dAsset", "Import 3d asset.", "sourceAsset, outDir, importOptJson"}}); +} + +void QmlPuppet::initQmlRunner() +{ + if (m_coreApp->arguments().count() < 2 + || (m_argParser.isSet("readcapturedstream") && m_coreApp->arguments().count() < 3) + || (m_argParser.isSet("rendericon") && m_coreApp->arguments().count() < 5) + || (m_argParser.isSet("import3dAsset") && m_coreApp->arguments().count() < 6) + || (!m_argParser.isSet("readcapturedstream") && m_coreApp->arguments().count() < 4)) { + qDebug() << "Wrong argument count: " << m_coreApp->arguments().count(); + m_argParser.showHelp(1); + } + + if (m_argParser.isSet("readcapturedstream") && m_coreApp->arguments().count() > 2) { + QString fileName = m_argParser.value("readcapturedstream"); + if (!QFile::exists(fileName)) { + qDebug() << "Input stream does not exist:" << fileName; + exit(-1); + } + + if (m_coreApp->arguments().count() > 3) { + fileName = m_coreApp->arguments().at(3); + if (!QFile::exists(fileName)) { + qDebug() << "Output stream does not exist:" << fileName; + exit(-1); + } + } + } + + if (m_argParser.isSet("rendericon")) { + int size = m_coreApp->arguments().at(2).toInt(); + QString iconFileName = m_coreApp->arguments().at(3); + QString iconSource = m_coreApp->arguments().at(4); + + m_iconRenderer.reset(new IconRenderer(size, iconFileName, iconSource)); + m_iconRenderer->setupRender(); + } else if (m_argParser.isSet("import3dAsset")) { + QString sourceAsset = m_coreApp->arguments().at(2); + QString outDir = m_coreApp->arguments().at(3); + QString options = m_coreApp->arguments().at(4); + + Import3D::import3D(sourceAsset, outDir, options); + } + + startCrashpad(); + + new QmlDesigner::Qt5NodeInstanceClientProxy(m_coreApp.get()); + +#if defined(Q_OS_WIN) && defined(QT_NO_DEBUG) + SetErrorMode(SEM_NOGPFAULTERRORBOX); //We do not want to see any message boxes +#endif + + if (m_argParser.isSet("readcapturedstream")) + exit(0); +} diff --git a/src/tools/qml2puppet/qml2puppet/qmlpuppet.h b/src/tools/qml2puppet/qml2puppet/qmlpuppet.h new file mode 100644 index 00000000000..66838164cbb --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/qmlpuppet.h @@ -0,0 +1,21 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../qmlbase.h" + +class IconRenderer; +class QmlPuppet : public QmlBase +{ + using QmlBase::QmlBase; + +private: + void initCoreApp() override; + void populateParser() override; + int startTestMode() override; + void initQmlRunner() override; + +private: + QSharedPointer m_iconRenderer; +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/loadwatcher.h b/src/tools/qml2puppet/qml2puppet/runner/loadwatcher.h new file mode 100644 index 00000000000..737557a45cc --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/loadwatcher.h @@ -0,0 +1,97 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qmlconfiguration.h" + +#include "QtQml/qqmlcomponent.h" +#include +#include + +//// Listens to the appEngine signals to determine if all files failed to load +class LoadWatcher : public QObject +{ + // Q_OBJECT +public: + LoadWatcher(QQmlApplicationEngine *e, int expected, Config *conf) + : QObject(e) + , qae(e) + , conf(conf) + , expectedFileCount(expected) + { + connect(e, &QQmlApplicationEngine::objectCreated, this, &LoadWatcher::checkFinished); + // QQmlApplicationEngine also connects quit() to QCoreApplication::quit + // and exit() to QCoreApplication::exit but if called before exec() + // then QCoreApplication::quit or QCoreApplication::exit does nothing + connect(e, &QQmlEngine::quit, this, &LoadWatcher::quit); + connect(e, &QQmlEngine::exit, this, &LoadWatcher::exit); + } + + int returnCode = 0; + bool earlyExit = false; + +public Q_SLOTS: + void checkFinished(QObject *o, const QUrl &url) + { + Q_UNUSED(url); + if (o) { + checkForWindow(o); + if (conf && qae) { + for (PartialScene *ps : std::as_const(conf->completers)) { + if (o->inherits(ps->itemType().toUtf8().constData())) + contain(o, ps->container()); + } + } + } + if (haveWindow) + return; + + if (!--expectedFileCount) { + printf("qml: Did not load any objects, exiting.\n"); + exit(2); + QCoreApplication::exit(2); + } + } + + void quit() + { + // Will be checked before calling exec() + earlyExit = true; + returnCode = 0; + } + void exit(int retCode) + { + earlyExit = true; + returnCode = retCode; + } + +private: + void contain(QObject *o, const QUrl &containPath) + { + QQmlComponent c(qae, containPath); + QObject *o2 = c.create(); + if (!o2) + return; + o2->setParent(this); + checkForWindow(o2); + bool success = false; + int idx; + if ((idx = o2->metaObject()->indexOfProperty("containedObject")) != -1) + success = o2->metaObject()->property(idx).write(o2, QVariant::fromValue(o)); + if (!success) + o->setParent(o2); // Set QObject parent, and assume container will react as needed + } + void checkForWindow(QObject *o) + { + if (o->isWindowType() && o->inherits("QQuickWindow")) + haveWindow = true; + } + +private: + QQmlApplicationEngine *qae; + Config *conf; + + bool haveWindow = false; + int expectedFileCount; +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/qmlconfiguration.h b/src/tools/qml2puppet/qml2puppet/runner/qmlconfiguration.h new file mode 100644 index 00000000000..7563d7ce781 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/qmlconfiguration.h @@ -0,0 +1,66 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +class PartialScene : public QObject +{ + Q_OBJECT + Q_PROPERTY(QUrl container READ container WRITE setContainer NOTIFY containerChanged) + Q_PROPERTY(QString itemType READ itemType WRITE setItemType NOTIFY itemTypeChanged) + QML_ELEMENT + QML_ADDED_IN_VERSION(1, 0) +public: + PartialScene(QObject *parent = nullptr) + : QObject(parent) + {} + + const QUrl container() const { return m_container; } + const QString itemType() const { return m_itemType; } + + void setContainer(const QUrl &a) + { + if (a == m_container) + return; + m_container = a; + emit containerChanged(); + } + void setItemType(const QString &a) + { + if (a == m_itemType) + return; + m_itemType = a; + emit itemTypeChanged(); + } + +signals: + void containerChanged(); + void itemTypeChanged(); + +private: + QUrl m_container; + QString m_itemType; +}; + +class Config : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty sceneCompleters READ sceneCompleters) + Q_CLASSINFO("DefaultProperty", "sceneCompleters") + QML_NAMED_ELEMENT(Configuration) + QML_ADDED_IN_VERSION(1, 0) +public: + Config(QObject *parent = nullptr) + : QObject(parent) + {} + + QQmlListProperty sceneCompleters() + { + return QQmlListProperty(this, &completers); + } + + QList completers; +}; diff --git a/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp b/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp new file mode 100644 index 00000000000..e3e962483f8 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.cpp @@ -0,0 +1,348 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include + +#include "loadwatcher.h" +#include "qmlruntime.h" + +#include +#include +#if QT_CONFIG(qml_animation) +#include +#endif + +#define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms +#define QSL QStringLiteral + +void QmlRuntime::populateParser() +{ + m_argParser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + m_argParser.setOptionsAfterPositionalArgumentsMode( + QCommandLineParser::ParseAsPositionalArguments); + + m_argParser.addOptions( + {{QStringList() << QSL("a") << QSL("apptype"), + QSL("Select which application class to use. Default is gui."), + QSL("core|gui|widget")}, // just for translation + + {QSL("I"), QSL("Prepend the given path to the import paths."), QSL("path")}, + + {QSL("f"), QSL("Load the given file as a QML file."), QSL("file")}, + + {QStringList() << QSL("c") << QSL("config"), + QSL("Load the given built-in configuration or configuration file."), + QSL("file")}, + + {QStringList() << QSL("list-conf"), QSL("List the built-in configurations.")}, + + {QSL("translation"), QSL("Load the given file as the translations file."), QSL("file")}, + +#ifdef QT_GUI_LIB + // OpenGL options + {QSL("desktop"), QSL("Force use of desktop OpenGL (AA_UseDesktopOpenGL).")}, + + {QSL("gles"), QSL("Force use of GLES (AA_UseOpenGLES).")}, + + {QSL("software"), QSL("Force use of software rendering (AA_UseSoftwareOpenGL).")}, + + {QSL("core-profile"), QSL("Force use of OpenGL Core Profile.")}, + + {QSL("disable-context-sharing"), + QSL("Disable the use of a shared GL context for QtQuick Windows")}, +#endif // QT_GUI_LIB + + // Debugging and verbosity options + {QSL("quiet"), QSL("Suppress all output.")}, + + {QSL("verbose"), + QSL("Print information about what qml is doing, like specific file URLs being loaded.")}, + + {QSL("slow-animations"), QSL("Run all animations in slow motion.")}, + + {QSL("fixed-animations"), QSL("Run animations off animation tick rather than wall time.")}, + + {QStringList() << QSL("r") << QSL("rhi"), + QSL("Set the backend for the Qt graphics abstraction (RHI). " + "Backend is one of: default, vulkan, metal, d3d11, gl"), + QSL("backend")}, + + {QSL("S"), QSL("Add selector to the list of QQmlFileSelectors."), QSL("selector")}}); + + // Positional arguments + m_argParser.addPositionalArgument( + "files", + QSL("Any number of QML files can be loaded. They will share the same engine."), + "[files...]"); + m_argParser.addPositionalArgument("args", + QSL("Arguments after '--' are ignored, but passed through to " + "the application.arguments variable in QML."), + "[-- args...]"); +} + +void QmlRuntime::initCoreApp() +{ + bool glShareContexts = true; + + // these attributes must be set before the QCoreApp is initialized + for (int i = 0; i < m_args.argc; i++) { + if (!strcmp(m_args.argv[i], "-desktop") || !strcmp(m_args.argv[i], "--desktop")) { + QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + } else if (!strcmp(m_args.argv[i], "-gles") || !strcmp(m_args.argv[i], "--gles")) { + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); + } else if (!strcmp(m_args.argv[i], "-software") || !strcmp(m_args.argv[i], "--software")) { + QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + } else if (!strcmp(m_args.argv[i], "-disable-context-sharing") + || !strcmp(m_args.argv[i], "--disable-context-sharing")) { + glShareContexts = false; + } + } + + if (glShareContexts) + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + + // since we handled all attributes above, now we can initialize the core app + for (int i = 0; i < m_args.argc; i++) { + if (!strcmp(m_args.argv[i], "--apptype") || !strcmp(m_args.argv[i], "-a") + || !strcmp(m_args.argv[i], "-apptype")) { + if (i + 1 < m_args.argc) { + ++i; + if (!strcmp(m_args.argv[i], "core")) { + createCoreApp(); + } + else if (!strcmp(m_args.argv[i], "gui")) { + createCoreApp(); + } +#ifdef QT_WIDGETS_LIB + else if (!strcmp(m_args.argv[i], "widget")) { + createCoreApp(); + static_cast(m_coreApp.get()) + ->setWindowIcon(QIcon(m_iconResourcePath)); + } +#endif // QT_WIDGETS_LIB + } + } + } +} + +void QmlRuntime::initQmlRunner() +{ + m_qmlEngine.reset(new QQmlApplicationEngine()); + + QStringList files; + QString confFile; + QString translationFile; + + if (!m_argParser.parse(QCoreApplication::arguments())) { + qWarning() << m_argParser.errorText(); + exit(1); + } + + // TODO: replace below logging modes with a proper logging category + m_verboseMode = m_argParser.isSet("verbose"); + m_quietMode = (!m_verboseMode && m_argParser.isSet("quiet")); + // FIXME: need to re-evaluate. we have our own message handler. + // if (quietMode) { + // qInstallMessageHandler(quietMessageHandler); + // QLoggingCategory::setFilterRules(QStringLiteral("*=false")); + // } + + if (m_argParser.isSet("list-conf")) { + listConfFiles(); + exit(0); + } + +#if QT_CONFIG(qml_animation) + if (m_argParser.isSet("slow-animations")) + QUnifiedTimer::instance()->setSlowModeEnabled(true); + if (m_argParser.isSet("fixed-animations")) + QUnifiedTimer::instance()->setConsistentTiming(true); +#endif + const auto valsImportPath = m_argParser.values("I"); + for (const QString &importPath : valsImportPath) + m_qmlEngine->addImportPath(importPath); + + QStringList customSelectors; + + const auto valsSelectors = m_argParser.values("S"); + for (const QString &selector : valsSelectors) + customSelectors.append(selector); + + if (!customSelectors.isEmpty()) + m_qmlEngine->setExtraFileSelectors(customSelectors); + + if (qEnvironmentVariableIsSet("QSG_CORE_PROFILE") + || qEnvironmentVariableIsSet("QML_CORE_PROFILE") || m_argParser.isSet("core-profile")) { + QSurfaceFormat surfaceFormat; + surfaceFormat.setStencilBufferSize(8); + surfaceFormat.setDepthBufferSize(24); + surfaceFormat.setVersion(4, 1); + surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(surfaceFormat); + } + + if (m_argParser.isSet("config")) + confFile = m_argParser.value("config"); + if (m_argParser.isSet("translation")) + translationFile = m_argParser.value("translation"); + if (m_argParser.isSet("rhi")) { + const QString rhiBackend = m_argParser.value("rhi"); + if (rhiBackend == QLatin1String("default")) + qunsetenv("QSG_RHI_BACKEND"); + else + qputenv("QSG_RHI_BACKEND", rhiBackend.toLatin1()); + } + + const auto valsPosArgs = m_argParser.positionalArguments(); + files << m_argParser.values("f"); + for (const QString &posArg : valsPosArgs) { + if (posArg == QLatin1String("--")) + break; + else + files << posArg; + } + +#if QT_CONFIG(translation) + // Need to be installed before QQmlApplicationEngine's automatic translation loading + // (qt_ translations are loaded there) + if (!translationFile.isEmpty()) { + QTranslator translator; + + if (translator.load(translationFile)) { + m_coreApp->installTranslator(&translator); + if (m_verboseMode) + qInfo() << "qml: Loaded translation file %s\n", + qPrintable(QDir::toNativeSeparators(translationFile)); + } else { + if (!m_quietMode) + qInfo() << "qml: Could not load the translation file %s\n", + qPrintable(QDir::toNativeSeparators(translationFile)); + } + } +#else + if (!translationFile.isEmpty() && !quietMode) + qInfo() << "qml: Translation file specified, but Qt built without translation support.\n"); +#endif + + if (files.size() <= 0) { +#if defined(Q_OS_DARWIN) + if (qobject_cast(m_coreApp.data())) { + m_exitTimerId = static_cast(m_coreApp.get()) + ->startTimer(FILE_OPEN_EVENT_WAIT_TIME); + } else +#endif + { + if (!m_quietMode) + qCritical() << "No files specified. Terminating.\n"; + exit(1); + } + } + + loadConf(confFile, !m_verboseMode); + + // Load files + QScopedPointer lw(new LoadWatcher(m_qmlEngine.data(), files.size(), m_conf.data())); + + for (const QString &path : std::as_const(files)) { + QUrl url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + if (m_verboseMode) + qInfo() << "qml: loading %s\n", qPrintable(url.toString()); + m_qmlEngine->load(url); + } + + if (lw->earlyExit) + exit(lw->returnCode); +} + +void QmlRuntime::loadConf(const QString &override, bool quiet) // Terminates app on failure +{ + const QString defaultFileName = QLatin1String("default.qml"); + QUrl settingsUrl; + bool builtIn = false; //just for keeping track of the warning + if (override.isEmpty()) { + QFileInfo fi; + fi.setFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, defaultFileName)); + if (fi.exists()) { + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + } else { + // If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path + fi.setFile(m_confResourcePath + defaultFileName); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + builtIn = true; + } + } else { + QFileInfo fi; + fi.setFile(m_confResourcePath + override + QLatin1String(".qml")); + if (fi.exists()) { + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + builtIn = true; + } else { + fi.setFile(QDir(QStandardPaths::locate(QStandardPaths::AppConfigLocation, + override, + QStandardPaths::LocateDirectory)), + m_confResourcePath); + if (fi.exists()) + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + else + fi.setFile(override); + if (!fi.exists()) { + qCritical() << "qml: Couldn't find required configuration file: %s\n", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath())); + exit(1); + } + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + } + } + + if (!quiet) { + qInfo() << "qml: %s\n", QLibraryInfo::build(); + if (builtIn) { + qInfo() << "qml: Using built-in configuration: %s\n", + qPrintable(override.isEmpty() ? defaultFileName : override); + } else { + qInfo() << "qml: Using configuration: %s\n", + qPrintable(settingsUrl.isLocalFile() + ? QDir::toNativeSeparators(settingsUrl.toLocalFile()) + : settingsUrl.toString()); + } + } + + // TODO: When we have better engine control, ban QtQuick* imports on this engine + QQmlEngine e2; + QQmlComponent c2(&e2, settingsUrl); + m_conf.reset(qobject_cast(c2.create())); + + if (!m_conf) { + qCritical() << "qml: Error loading configuration file: %s\n", qPrintable(c2.errorString()); + exit(1); + } +} + +void QmlRuntime::listConfFiles() +{ + const QDir confResourceDir(m_confResourcePath); + qInfo() << "%s\n", qPrintable(QCoreApplication::translate("main", "Built-in configurations:")); + for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files)) + qInfo() << " %s\n", qPrintable(fi.baseName()); + qInfo() << "%s\n", qPrintable(QCoreApplication::translate("main", "Other configurations:")); + bool foundOther = false; + const QStringList otherLocations = QStandardPaths::standardLocations( + QStandardPaths::AppConfigLocation); + for (const auto &confDirPath : otherLocations) { + const QDir confDir(confDirPath); + for (const QFileInfo &fi : confDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { + foundOther = true; + if (m_verboseMode) + qInfo() << " %s\n", qPrintable(fi.absoluteFilePath()); + else + qInfo() << " %s\n", qPrintable(fi.baseName()); + } + } + if (!foundOther) + qInfo() << " %s\n", qPrintable(QCoreApplication::translate("main", "none")); + if (m_verboseMode) { + qInfo() << "%s\n", qPrintable(QCoreApplication::translate("main", "Checked in:")); + for (const auto &confDirPath : otherLocations) + qInfo() << " %s\n", qPrintable(confDirPath); + } +} diff --git a/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.h b/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.h new file mode 100644 index 00000000000..c69481ca039 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/runner/qmlruntime.h @@ -0,0 +1,30 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../qmlbase.h" +#include "qmlconfiguration.h" + +class QmlRuntime : public QmlBase +{ + using QmlBase::QmlBase; + +private: + void initCoreApp() override; + void populateParser() override; + void initQmlRunner() override; + + void listConfFiles(); + void loadConf(const QString &override, bool quiet); + + const QString m_iconResourcePath = QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png"); + const QString m_confResourcePath = QStringLiteral(":/runner/runnerconf/qmlruntime/"); + + + QSharedPointer m_conf; + bool m_verboseMode = false; + bool m_quietMode = false; + int m_exitTimerId = -1; +}; + diff --git a/src/tools/qml2puppet/qmlpuppet.qrc b/src/tools/qml2puppet/qmlpuppet.qrc index 162c955ac14..7ec26950034 100644 --- a/src/tools/qml2puppet/qmlpuppet.qrc +++ b/src/tools/qml2puppet/qmlpuppet.qrc @@ -12,4 +12,9 @@ mockfiles/ToolBarButton.qml mockfiles/ToggleButton.qml + + runnerconf/qmlruntime/default.qml + runnerconf/qmlruntime/content/resizeItemToWindow.qml + runnerconf/qmlruntime/content/resizeWindowToItem.qml + diff --git a/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml new file mode 100644 index 00000000000..ca4618ba734 --- /dev/null +++ b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeItemToWindow.qml @@ -0,0 +1,25 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + property bool __resizeGuard: false + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false + return + } + __resizeGuard = true + width = containedObject.width + height = containedObject.height + containedObject.parent = contentItem + visible = true + __resizeGuard = false + } + onWidthChanged: if (!__resizeGuard && containedObject) + containedObject.width = width + onHeightChanged: if (!__resizeGuard && containedObject) + containedObject.height = height +} diff --git a/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml new file mode 100644 index 00000000000..30029bf973f --- /dev/null +++ b/src/tools/qml2puppet/runnerconf/qmlruntime/content/resizeWindowToItem.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false + return + } + width = Qt.binding(function () { + return containedObject.width + }) + height = Qt.binding(function () { + return containedObject.height + }) + containedObject.parent = contentItem + visible = true + } +} diff --git a/src/tools/qml2puppet/runnerconf/qmlruntime/default.qml b/src/tools/qml2puppet/runnerconf/qmlruntime/default.qml new file mode 100644 index 00000000000..961f2a890bd --- /dev/null +++ b/src/tools/qml2puppet/runnerconf/qmlruntime/default.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import QmlRuntime.QmlConfiguration 1.0 + +Configuration { + PartialScene { + itemType: "QQuickItem" + container: Qt.resolvedUrl("content/resizeItemToWindow.qml") + } +}