Merge remote-tracking branch 'origin/qds/dev'

Conflicts: src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
  src/plugins/updateinfo/updateinfoplugin.cpp
  src/tools/qml2puppet/CMakeLists.txt
  src/tools/qml2puppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp

Change-Id: I6b65ce2d603453fcebc2291574d55db80853f247
This commit is contained in:
Tim Jenssen
2022-12-22 12:55:00 +01:00
72 changed files with 2106 additions and 720 deletions

View File

@@ -31,6 +31,13 @@
can be built with CMake. You can open the \e CMakeLists.txt project file in can be built with CMake. You can open the \e CMakeLists.txt project file in
Qt Creator to continue developing the project. 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) \if defined(qtdesignstudio)
For more information, see \l{Designer-Developer Workflow}. For more information, see \l{Designer-Developer Workflow}.
\else \else
@@ -149,10 +156,8 @@
\section1 Adding Qt Quick Designer Components to Qt Installations \section1 Adding Qt Quick Designer Components to Qt Installations
If you use Qt Quick Studio Components or Effects in your project, you have Since \QDS 3.9, the Qt Quick Studio Components module is installed by default
to check out and install the \e {Qt Quick Designer Components} module from as part of the application. You can also install the module manually.
\l{https://code.qt.io/cgit/qt-labs/qtquickdesigner-components.git/}
{Qt Code Review}.
For example: For example:
\list 1 \list 1

View File

@@ -32,8 +32,8 @@
your project as a fully working C++ application with developers. your project as a fully working C++ application with developers.
If you add or remove QML files in \QDS, you have to regenerate the If you add or remove QML files in \QDS, you have to regenerate the
\e CMakeLists.txt project configuration file by selecting \uicontrol Build \e CMakeLists.txt project configuration file by selecting \uicontrol File
> \uicontrol Run > \uicontrol {Generate CMakeLists.txt Files}. > \uicontrol {Export Project} > \uicontrol {Generate CMake Build Files}.
If you use Git, you can clone an example project If you use Git, you can clone an example project
\l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/playground/AuroraCluster0} \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/playground/AuroraCluster0}
@@ -99,6 +99,8 @@
This isn't mandatory. This isn't mandatory.
\li Generate CMake files and C++ source files that are used to compile the application into \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 \endlist
*/ */

View File

@@ -10,11 +10,19 @@
\note \QBF is included in the \note \QBF is included in the
\l{https://www.qt.io/pricing}{\QDS Enterprise license}. \l{https://www.qt.io/pricing}{\QDS Enterprise license}.
\QBF is delivered with \QDS as a developer plugin that you can install to You need both Figma and Qt accounts to use \QBF in \QDS.
the Desktop version of Figma. To install the plugin, open the Plugin Manager To use \QBF in \QDS:
of Figma and press the plus button to create a new plugin. Then choose the \list 1
\e Manifest.json file that comes with \QDS. \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 > You can launch the installed Figma plugin from \uicontrol Plugins > \uicontrol {\QBF} in Figma.
\uicontrol Development > \uicontrol {\QBF} in Figma.
*/ */

View File

@@ -24,9 +24,9 @@
set of files (icons, translation files, and so on) and you don't want set of files (icons, translation files, and so on) and you don't want
to run the risk of losing the files. to run the risk of losing the files.
To package your application, select \uicontrol Build > To package your application, select \uicontrol File >
\uicontrol {Generate QRC Resource File}. Then select the \uicontrol {Export Project} > \uicontrol {Generate QRC Resource File}.
files to package in the \uicontrol {Add Resources} dialog. Then select the files to package in the \uicontrol {Add Resources} dialog.
\image studio-add-resources.png "Add Resources dialog" \image studio-add-resources.png "Add Resources dialog"
@@ -44,10 +44,11 @@
\section1 Embedding Resources into Applications \section1 Embedding Resources into Applications
Alternatively, you can embedd the resources into your application by Alternatively, you can embed the resources into your application by
selecting \uicontrol Build > \uicontrol {Generate Deployable Package}. selecting \uicontrol File > \uicontrol {Export Project} >
Select the location for the .qmlrc file, and then select the files to \uicontrol {Generate Deployable Package}. Select the location for
embedd in the \uicontrol {Add Resources} dialog. 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 When you select \uicontrol OK, \QDS creates a resource collection file
(.qmlrc) in the location you selected. (.qmlrc) in the location you selected.

View File

@@ -23,6 +23,7 @@ Image {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
onPressed: (mouse) => { onPressed: (mouse) => {
if (mouse.button === Qt.LeftButton) if (mouse.button === Qt.LeftButton)
@@ -31,4 +32,20 @@ Image {
root.showContextMenu() 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
}
}
} }

View File

@@ -12,7 +12,7 @@ TreeViewDelegate {
required property Item assetsRoot required property Item assetsRoot
property bool hasChildWithDropHover: false property bool hasChildWithDropHover: false
property bool isHoveringDrop: false property bool isHighlighted: false
readonly property string suffix: model.fileName.substr(-4) readonly property string suffix: model.fileName.substr(-4)
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf" readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
readonly property bool isEffect: root.suffix === ".qep" readonly property bool isEffect: root.suffix === ".qep"
@@ -26,7 +26,12 @@ TreeViewDelegate {
readonly property int __dirItemHeight: 21 readonly property int __dirItemHeight: 21
implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight 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 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: { onDepthChanged: {
if (root.depth > root.initialDepth && root.initialDepth >= 0) if (root.depth > root.initialDepth && root.initialDepth >= 0)
root.depth = root.initialDepth root.depth = root.initialDepth
@@ -73,8 +67,10 @@ TreeViewDelegate {
background: Rectangle { background: Rectangle {
id: bg id: bg
width: root.implicitWidth
color: { color: {
if (root.__isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover)) if (root.__isDirectory && (root.isHighlighted || root.hasChildWithDropHover))
return StudioTheme.Values.themeInteraction return StudioTheme.Values.themeInteraction
if (!root.__isDirectory && root.assetsView.selectedAssets[root.__itemPath]) 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 { MouseArea {
id: mouseArea id: mouseArea
@@ -247,6 +209,14 @@ TreeViewDelegate {
} }
} // MouseArea } // MouseArea
function getDirPath()
{
if (root.__isDirectory)
return model.filePath
else
return assetsModel.parentDirPath(model.filePath)
}
function __openContextMenuForCurrentRow() function __openContextMenuForCurrentRow()
{ {
let modelIndex = assetsModel.indexForPath(model.filePath) let modelIndex = assetsModel.indexForPath(model.filePath)

View File

@@ -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()
}
} }

View File

@@ -250,8 +250,12 @@ TreeView {
function startDropHoverOver(row) function startDropHoverOver(row)
{ {
let index = root.__modelIndex(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 return
}
let parentItem = root.__getDelegateParentForIndex(index) let parentItem = root.__getDelegateParentForIndex(index)
if (parentItem) if (parentItem)
@@ -261,8 +265,12 @@ TreeView {
function endDropHover(row) function endDropHover(row)
{ {
let index = root.__modelIndex(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 return
}
let parentItem = root.__getDelegateParentForIndex(index) let parentItem = root.__getDelegateParentForIndex(index)
if (parentItem) if (parentItem)
@@ -292,6 +300,12 @@ TreeView {
return root.itemAtCell(parentCell) return root.itemAtCell(parentCell)
} }
function __getDelegateItemForIndex(index)
{
let cell = root.cellAtIndex(index)
return root.itemAtCell(cell)
}
function __modelIndex(row) function __modelIndex(row)
{ {
// The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a // 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) 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 { delegate: AssetDelegate {
assetsView: root assetsView: root
assetsRoot: root.assetsRoot assetsRoot: root.assetsRoot

View File

@@ -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()
}
}

View File

@@ -40,5 +40,13 @@ T.Menu {
color: StudioTheme.Values.themeControlBackground color: StudioTheme.Values.themeControlBackground
border.color: StudioTheme.Values.themeControlOutline border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border 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
}
} }
} }

View File

@@ -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
}
}
]
}

View File

@@ -10,6 +10,11 @@ set(CMAKE_AUTOMOC ON)
find_package(QT NAMES Qt6 COMPONENTS Gui Qml Quick) find_package(QT NAMES Qt6 COMPONENTS Gui Qml Quick)
find_package(Qt6 REQUIRED COMPONENTS Core 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_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. # qt_standard_project_setup() requires Qt 6.3 or higher. See https://doc.qt.io/qt-6/qt-standard-project-setup.html for details.

View File

@@ -63,7 +63,7 @@ static const char *qtQuickUISuffix = "ui.qml";
static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path) 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); context.paths.insert(path);
} }

View File

@@ -35,8 +35,10 @@ add_qtc_library(QmlDesignerUtils STATIC
PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/utils PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/utils
SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/utils SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/utils
SOURCES SOURCES
asset.cpp asset.h
designersettings.cpp designersettings.h designersettings.cpp designersettings.h
hdrimage.cpp hdrimage.h hdrimage.cpp hdrimage.h
imageutils.cpp imageutils.h
qmldesignerutils_global.h qmldesignerutils_global.h
) )

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "assetslibraryiconprovider.h" #include "assetslibraryiconprovider.h"
#include "assetslibrarymodel.h" #include "asset.h"
#include "modelnodeoperations.h" #include "modelnodeoperations.h"
#include <theme.h> #include <theme.h>
@@ -25,13 +25,17 @@ QPixmap AssetsLibraryIconProvider::requestPixmap(const QString &id, QSize *size,
pixmap = m_thumbnails[id]; pixmap = m_thumbnails[id];
} else { } else {
pixmap = fetchPixmap(id, requestedSize); pixmap = fetchPixmap(id, requestedSize);
if (pixmap.isNull()) bool haveValidImage = true;
if (pixmap.isNull()) {
pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png"); pixmap = Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/assets_default.png");
haveValidImage = false;
}
if (requestedSize.isValid()) if (requestedSize.isValid())
pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); pixmap = pixmap.scaled(requestedSize, Qt::KeepAspectRatio);
m_thumbnails[id] = pixmap; if (haveValidImage)
m_thumbnails[id] = pixmap;
} }
if (size) { if (size) {
@@ -53,24 +57,25 @@ QPixmap AssetsLibraryIconProvider::generateFontIcons(const QString &filePath, co
QPixmap AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const QPixmap AssetsLibraryIconProvider::fetchPixmap(const QString &id, const QSize &requestedSize) const
{ {
const QString suffix = "*." + id.split('.').last().toLower(); Asset asset(id);
if (id == "browse") { if (id == "browse") {
return Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png"); return Utils::StyleHelper::dpiSpecificImageFile(":/AssetsLibrary/images/browse.png");
} else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { } else if (asset.isFont()) {
return generateFontIcons(id, requestedSize); return generateFontIcons(id, requestedSize);
} else if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { } else if (asset.isImage()) {
return Utils::StyleHelper::dpiSpecificImageFile(id); return Utils::StyleHelper::dpiSpecificImageFile(id);
} else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { } else if (asset.isTexture3D()) {
return HdrImage{id}.toPixmap(); return HdrImage{id}.toPixmap();
} else { } else {
QString type; QString type;
if (AssetsLibraryModel::supportedShaderSuffixes().contains(suffix)) if (asset.isShader())
type = "shader"; type = "shader";
else if (AssetsLibraryModel::supportedAudioSuffixes().contains(suffix)) else if (asset.isAudio())
type = "sound"; type = "sound";
else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) else if (asset.isVideo())
type = "video"; type = "video";
else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) else if (asset.isEffect())
type = QmlDesigner::ModelNodeOperations::getEffectIcon(id); type = QmlDesigner::ModelNodeOperations::getEffectIcon(id);
QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type); QString pathTemplate = QString(":/AssetsLibrary/images/asset_%1%2.png").arg(type);

View File

@@ -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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include <QCheckBox> #include <QCheckBox>
#include <QFileInfo> #include <QFileInfo>
#include <QFileSystemModel> #include <QFileSystemModel>
#include <QImageReader>
#include <QMessageBox> #include <QMessageBox>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include "asset.h"
#include "assetslibrarymodel.h" #include "assetslibrarymodel.h"
#include <modelnodeoperations.h> #include <modelnodeoperations.h>
@@ -134,32 +134,10 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString &
bool AssetsLibraryModel::addNewFolder(const QString &folderPath) bool AssetsLibraryModel::addNewFolder(const QString &folderPath)
{ {
QString iterPath = folderPath; QString iterPath = folderPath;
static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
QDir dir{folderPath}; QDir dir{folderPath};
while (dir.exists()) { while (dir.exists()) {
// if the folder name ends with a number, increment it iterPath = getUniqueName(iterPath);
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';
}
dir.setPath(iterPath); dir.setPath(iterPath);
} }
@@ -180,12 +158,49 @@ bool AssetsLibraryModel::deleteFolderRecursively(const QModelIndex &folderIndex)
bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) const bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) const
{ {
return Utils::allOf(filePaths, [](const QString &path) { return Utils::allOf(filePaths, [](const QString &path) {
const QString suffix = "*." + path.split('.').last().toLower(); return Asset(path).isImage();
return AssetsLibraryModel::supportedImageSuffixes().contains(suffix);
}); });
} }
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 bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{ {
QString path = m_sourceFsModel->filePath(sourceParent); QString path = m_sourceFsModel->filePath(sourceParent);
@@ -242,6 +257,36 @@ void AssetsLibraryModel::syncHaveFiles()
setHaveFiles(checkHaveFiles()); 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) void AssetsLibraryModel::setRootPath(const QString &newPath)
{ {
beginResetModel(); beginResetModel();
@@ -252,7 +297,7 @@ void AssetsLibraryModel::setRootPath(const QString &newPath)
m_rootPath = newPath; m_rootPath = newPath;
m_sourceFsModel->setRootPath(newPath); m_sourceFsModel->setRootPath(newPath);
m_sourceFsModel->setNameFilters(supportedSuffixes().values()); m_sourceFsModel->setNameFilters(Asset::supportedSuffixes().values());
m_sourceFsModel->setNameFilterDisables(false); m_sourceFsModel->setNameFilterDisables(false);
endResetModel(); endResetModel();
@@ -327,80 +372,4 @@ QString AssetsLibraryModel::parentDirPath(const QString &path) const
return filePath(parentIdx); return filePath(parentIdx);
} }
const QStringList &AssetsLibraryModel::supportedImageSuffixes()
{
static QStringList retList;
if (retList.isEmpty()) {
const QList<QByteArray> 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<QString> &AssetsLibraryModel::supportedSuffixes()
{
static QSet<QString> 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 } // namespace QmlDesigner

View File

@@ -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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once #pragma once
#include <QFileInfo>
#include <QFileSystemModel> #include <QFileSystemModel>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QFileInfo>
#include <utils/qtcassert.h>
#include <utils/filesystemwatcher.h> #include <utils/filesystemwatcher.h>
#include <utils/qtcassert.h>
namespace QmlDesigner { namespace QmlDesigner {
@@ -47,6 +47,11 @@ public:
Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex);
Q_INVOKABLE bool allFilePathsAreImages(const QStringList &filePaths) const; 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 columnCount(const QModelIndex &parent = QModelIndex()) const override
{ {
int result = QSortFilterProxyModel::columnCount(parent); int result = QSortFilterProxyModel::columnCount(parent);
@@ -55,16 +60,6 @@ public:
bool haveFiles() const { return m_haveFiles; } 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<QString> &supportedSuffixes();
signals: signals:
void directoryLoaded(const QString &path); void directoryLoaded(const QString &path);
void rootPathChanged(); void rootPathChanged();
@@ -79,6 +74,7 @@ private:
void destroyBackendModel(); void destroyBackendModel();
bool checkHaveFiles(const QModelIndex &parentIdx) const; bool checkHaveFiles(const QModelIndex &parentIdx) const;
bool checkHaveFiles() const; bool checkHaveFiles() const;
QString getUniqueName(const QString &oldName);
QString m_searchText; QString m_searchText;
QString m_rootPath; QString m_rootPath;

View File

@@ -3,8 +3,9 @@
#include "assetslibrarywidget.h" #include "assetslibrarywidget.h"
#include "assetslibrarymodel.h" #include "asset.h"
#include "assetslibraryiconprovider.h" #include "assetslibraryiconprovider.h"
#include "assetslibrarymodel.h"
#include <theme.h> #include <theme.h>
@@ -229,7 +230,7 @@ QSet<QString> AssetsLibraryWidget::supportedAssetSuffixes(bool complex)
QSet<QString> suffixes; QSet<QString> suffixes;
for (const AddResourceHandler &handler : handlers) { for (const AddResourceHandler &handler : handlers) {
if (AssetsLibraryModel::supportedSuffixes().contains(handler.filter) != complex) if (Asset(handler.filter).isSupported() != complex)
suffixes.insert(handler.filter); suffixes.insert(handler.filter);
} }
@@ -290,32 +291,32 @@ void AssetsLibraryWidget::startDragAsset(const QStringList &assetPaths, const QP
QPair<QString, QByteArray> AssetsLibraryWidget::getAssetTypeAndData(const QString &assetPath) QPair<QString, QByteArray> AssetsLibraryWidget::getAssetTypeAndData(const QString &assetPath)
{ {
QString suffix = "*." + assetPath.split('.').last().toLower(); Asset asset(assetPath);
if (!suffix.isEmpty()) { if (asset.hasSuffix()) {
if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { if (asset.isImage()) {
// Data: Image format (suffix) // Data: Image format (suffix)
return {Constants::MIME_TYPE_ASSET_IMAGE, suffix.toUtf8()}; return {Constants::MIME_TYPE_ASSET_IMAGE, asset.suffix().toUtf8()};
} else if (AssetsLibraryModel::supportedFontSuffixes().contains(suffix)) { } else if (asset.isFont()) {
// Data: Font family name // Data: Font family name
QRawFont font(assetPath, 10); QRawFont font(assetPath, 10);
QString fontFamily = font.isValid() ? font.familyName() : ""; QString fontFamily = font.isValid() ? font.familyName() : "";
return {Constants::MIME_TYPE_ASSET_FONT, fontFamily.toUtf8()}; 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) // Data: shader type, frament (f) or vertex (v)
return {Constants::MIME_TYPE_ASSET_SHADER, return {Constants::MIME_TYPE_ASSET_SHADER,
AssetsLibraryModel::supportedFragmentShaderSuffixes().contains(suffix) ? "f" : "v"}; asset.isFragmentShader() ? "f" : "v"};
} else if (AssetsLibraryModel::supportedAudioSuffixes().contains(suffix)) { } else if (asset.isAudio()) {
// No extra data for sounds // No extra data for sounds
return {Constants::MIME_TYPE_ASSET_SOUND, {}}; return {Constants::MIME_TYPE_ASSET_SOUND, {}};
} else if (AssetsLibraryModel::supportedVideoSuffixes().contains(suffix)) { } else if (asset.isVideo()) {
// No extra data for videos // No extra data for videos
return {Constants::MIME_TYPE_ASSET_VIDEO, {}}; return {Constants::MIME_TYPE_ASSET_VIDEO, {}};
} else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { } else if (asset.isTexture3D()) {
// Data: Image format (suffix) // Data: Image format (suffix)
return {Constants::MIME_TYPE_ASSET_TEXTURE3D, suffix.toUtf8()}; return {Constants::MIME_TYPE_ASSET_TEXTURE3D, asset.suffix().toUtf8()};
} else if (AssetsLibraryModel::supportedEffectMakerSuffixes().contains(suffix)) { } else if (asset.isEffect()) {
// Data: Effect Maker format (suffix) // Data: Effect Maker format (suffix)
return {Constants::MIME_TYPE_ASSET_EFFECT, suffix.toUtf8()}; return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()};
} }
} }
return {}; return {};

View File

@@ -1525,8 +1525,11 @@ void mergeWithTemplate(const SelectionContext &selectionContext, ExternalDepende
const QString templateFile = getTemplateDialog(projectPath); const QString templateFile = getTemplateDialog(projectPath);
if (QFileInfo::exists(templateFile)) if (QFileInfo::exists(templateFile)) {
StylesheetMerger::styleMerge(selectionContext.view()->model(), templateFile, externalDependencies); StylesheetMerger::styleMerge(Utils::FilePath::fromString(templateFile),
selectionContext.view()->model(),
externalDependencies);
}
} }
void removeGroup(const SelectionContext &selectionContext) 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"; QString defaultDir = "asset_imports/Effects";
Utils::FilePath projectPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); Utils::FilePath projectPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath();
@@ -1688,6 +1691,11 @@ Utils::FilePath getEffectsDirectory()
return effectsPath; return effectsPath;
} }
QString getEffectsDefaultDirectory(const QString &defaultDir)
{
return getAssetDefaultDirectory("effects", defaultDir);
}
QString getEffectIcon(const QString &effectPath) QString getEffectIcon(const QString &effectPath)
{ {
const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget(); const ProjectExplorer::Target *target = ProjectExplorer::ProjectTree::currentTarget();
@@ -1715,7 +1723,7 @@ bool useLayerEffect()
bool validateEffect(const QString &effectPath) bool validateEffect(const QString &effectPath)
{ {
const QString effectName = QFileInfo(effectPath).baseName(); const QString effectName = QFileInfo(effectPath).baseName();
Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsDirectory(); Utils::FilePath effectsResDir = ModelNodeOperations::getEffectsImportDirectory();
Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml"); Utils::FilePath qmlPath = effectsResDir.resolvePath(effectName + "/" + effectName + ".qml");
if (!qmlPath.exists()) { if (!qmlPath.exists()) {
QMessageBox msgBox; QMessageBox msgBox;

View File

@@ -119,7 +119,8 @@ void addMouseAreaFill(const SelectionContext &selectionContext);
void openSignalDialog(const SelectionContext &selectionContext); void openSignalDialog(const SelectionContext &selectionContext);
void updateImported3DAsset(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); void openEffectMaker(const QString &filePath);
QString getEffectIcon(const QString &effectPath); QString getEffectIcon(const QString &effectPath);
bool useLayerEffect(); bool useLayerEffect();

View File

@@ -76,19 +76,19 @@ bool Navigation2dFilter::wheelEvent(QWheelEvent *event)
bool zoomChangedConnected = QObject::isSignalConnected(zoomChangedSignal); bool zoomChangedConnected = QObject::isSignalConnected(zoomChangedSignal);
if (zoomChangedConnected) { if (zoomChangedConnected) {
const double globalMouseSpeed = double speed = 1.0 / 200.0;
QmlDesignerPlugin::settings().value(DesignerSettingsKey::EDITOR_ZOOM_FACTOR).toDouble(); bool isMac = Utils::HostOsInfo::isMacHost();
if (QPointF delta = event->pixelDelta(); !delta.isNull() && isMac) {
double speed = globalMouseSpeed/20.;
if (Utils::HostOsInfo::isMacHost())
speed = 1.0/200.;
if (QPointF delta = event->pixelDelta(); !delta.isNull()) {
double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y();
emit zoomChanged(dist * speed, event->position()); emit zoomChanged(dist * speed, event->position());
event->accept(); event->accept();
return true; return true;
} else if (QPointF delta = event->angleDelta(); !delta.isNull()) { } 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 degreePerStep = 15.;
constexpr double stepCount = 8.; constexpr double stepCount = 8.;
double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y(); double dist = std::abs(delta.x()) > std::abs(delta.y()) ? -delta.x() : delta.y();

View File

@@ -3,12 +3,17 @@
#include "contentlibrarytexture.h" #include "contentlibrarytexture.h"
#include "imageutils.h"
namespace QmlDesigner { namespace QmlDesigner {
ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QString &path, const QUrl &icon) ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QString &path, const QUrl &icon)
: QObject(parent) : QObject(parent)
, m_path(path) , 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) bool ContentLibraryTexture::filter(const QString &searchText)
{ {

View File

@@ -13,6 +13,7 @@ class ContentLibraryTexture : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString texturePath MEMBER m_path CONSTANT) 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(QUrl textureIcon MEMBER m_icon CONSTANT)
Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged) Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged)
@@ -29,6 +30,7 @@ signals:
private: private:
QString m_path; QString m_path;
QString m_toolTip;
QUrl m_icon; QUrl m_icon;
bool m_visible = true; bool m_visible = true;

View File

@@ -65,19 +65,27 @@ ModelNode CreateTexture::createTextureFromImage(const QString &assetPath, AddTex
NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo(); NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo();
Utils::FilePath imagePath = ModelNodeOperations::getImagesDefaultDirectory() Utils::FilePath currentDocumentPath = QmlDesigner::DocumentManager::currentFilePath();
.pathAppended(Utils::FilePath::fromString(assetPath).fileName()); Utils::FilePath imageTargetPath;
QString sourceVal = imagePath.relativePathFrom( if (m_importFile) {
QmlDesigner::DocumentManager::currentFilePath()).toString(); 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()) { if (!newTexNode.isValid()) {
newTexNode = m_view->createModelNode("QtQuick3D.Texture", newTexNode = m_view->createModelNode("QtQuick3D.Texture",
metaInfo.majorVersion(), metaInfo.majorVersion(),
metaInfo.minorVersion()); metaInfo.minorVersion());
newTexNode.validId(); newTexNode.validId();
VariantProperty sourceProp = newTexNode.variantProperty("source"); VariantProperty sourceProp = newTexNode.variantProperty("source");
sourceProp.setValue(sourceVal); sourceProp.setValue(textureSource);
matLib.defaultNodeListProperty().reparentHere(newTexNode); matLib.defaultNodeListProperty().reparentHere(newTexNode);
} }

View File

@@ -44,7 +44,7 @@ QColorDialog *BackgroundColorSelection::createColorDialog(QWidget *parent,
QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog, QObject::connect(dialog, &QColorDialog::currentColorChanged, dialog,
[actionType, view](const QColor &color) { [actionType, view](const QColor &color) {
Edit3DViewConfig::setColor(view, actionType, color); Edit3DViewConfig::setColors(view, actionType, {color});
}); });
QObject::connect(dialog, &QColorDialog::colorSelected, dialog, QObject::connect(dialog, &QColorDialog::colorSelected, dialog,
@@ -52,13 +52,13 @@ QColorDialog *BackgroundColorSelection::createColorDialog(QWidget *parent,
if (colorSelected) if (colorSelected)
colorSelected(); colorSelected();
Edit3DViewConfig::saveColor(key, color); Edit3DViewConfig::saveColors(key, {color});
}); });
if (Edit3DViewConfig::isColorValid(oldColorConfig)) { if (Edit3DViewConfig::colorsValid(oldColorConfig)) {
QObject::connect(dialog, &QColorDialog::rejected, dialog, QObject::connect(dialog, &QColorDialog::rejected, dialog,
[actionType, oldColorConfig, view]() { [actionType, oldColorConfig, view]() {
Edit3DViewConfig::setColor(view, actionType, oldColorConfig); Edit3DViewConfig::setColors(view, actionType, oldColorConfig);
}); });
} }

View File

@@ -3,7 +3,6 @@
#include "edit3dactions.h" #include "edit3dactions.h"
#include "edit3dview.h" #include "edit3dview.h"
#include "edit3dwidget.h"
#include <viewmanager.h> #include <viewmanager.h>
#include <nodeinstanceview.h> #include <nodeinstanceview.h>
@@ -18,7 +17,7 @@ namespace QmlDesigner {
Edit3DActionTemplate::Edit3DActionTemplate(const QString &description, Edit3DActionTemplate::Edit3DActionTemplate(const QString &description,
SelectionContextOperation action, SelectionContextOperation action,
AbstractView *view, Edit3DView *view,
View3DActionType type) View3DActionType type)
: DefaultAction(description) : DefaultAction(description)
, m_action(action) , m_action(action)
@@ -45,12 +44,14 @@ Edit3DAction::Edit3DAction(const QByteArray &menuId,
bool checked, bool checked,
const QIcon &iconOff, const QIcon &iconOff,
const QIcon &iconOn, const QIcon &iconOn,
AbstractView *view, Edit3DView *view,
SelectionContextOperation selectionAction, SelectionContextOperation selectionAction,
const QString &toolTip) const QString &toolTip)
: AbstractAction(new Edit3DActionTemplate(description, selectionAction, view, type)) : AbstractAction(new Edit3DActionTemplate(description, selectionAction, view, type))
, m_menuId(menuId) , m_menuId(menuId)
, m_actionTemplate(qobject_cast<Edit3DActionTemplate *>(defaultAction()))
{ {
view->registerEdit3DAction(this);
action()->setShortcut(key); action()->setShortcut(key);
action()->setShortcutContext(Qt::WidgetWithChildrenShortcut); action()->setShortcutContext(Qt::WidgetWithChildrenShortcut);
action()->setCheckable(checkable); action()->setCheckable(checkable);
@@ -73,11 +74,21 @@ Edit3DAction::Edit3DAction(const QByteArray &menuId,
} }
} }
Edit3DAction::~Edit3DAction()
{
m_actionTemplate->m_view->unregisterEdit3DAction(this);
}
QByteArray Edit3DAction::category() const QByteArray Edit3DAction::category() const
{ {
return QByteArray(); return QByteArray();
} }
View3DActionType Edit3DAction::actionType() const
{
return m_actionTemplate->m_type;
}
bool Edit3DAction::isVisible([[maybe_unused]] const SelectionContext &selectionContext) const bool Edit3DAction::isVisible([[maybe_unused]] const SelectionContext &selectionContext) const
{ {
return true; return true;
@@ -96,7 +107,7 @@ Edit3DCameraAction::Edit3DCameraAction(const QByteArray &menuId,
bool checked, bool checked,
const QIcon &iconOff, const QIcon &iconOff,
const QIcon &iconOn, const QIcon &iconOn,
AbstractView *view, Edit3DView *view,
SelectionContextOperation selectionAction) SelectionContextOperation selectionAction)
: Edit3DAction(menuId, type, description, key, checkable, checked, iconOff, iconOn, view, selectionAction) : Edit3DAction(menuId, type, description, key, checkable, checked, iconOff, iconOn, view, selectionAction)
{ {

View File

@@ -10,19 +10,22 @@
namespace QmlDesigner { namespace QmlDesigner {
using SelectionContextOperation = std::function<void(const SelectionContext &)>; using SelectionContextOperation = std::function<void(const SelectionContext &)>;
class Edit3DView;
class Edit3DActionTemplate : public DefaultAction class Edit3DActionTemplate : public DefaultAction
{ {
Q_OBJECT
public: public:
Edit3DActionTemplate(const QString &description, Edit3DActionTemplate(const QString &description,
SelectionContextOperation action, SelectionContextOperation action,
AbstractView *view, Edit3DView *view,
View3DActionType type); View3DActionType type);
void actionTriggered(bool b) override; void actionTriggered(bool b) override;
SelectionContextOperation m_action; SelectionContextOperation m_action;
AbstractView *m_view; Edit3DView *m_view = nullptr;
View3DActionType m_type; View3DActionType m_type;
}; };
@@ -37,10 +40,12 @@ public:
bool checked, bool checked,
const QIcon &iconOff, const QIcon &iconOff,
const QIcon &iconOn, const QIcon &iconOn,
AbstractView *view, Edit3DView *view,
SelectionContextOperation selectionAction = nullptr, SelectionContextOperation selectionAction = nullptr,
const QString &toolTip = {}); const QString &toolTip = {});
virtual ~Edit3DAction();
QByteArray category() const override; QByteArray category() const override;
int priority() const override int priority() const override
@@ -58,12 +63,15 @@ public:
return m_menuId; return m_menuId;
} }
View3DActionType actionType() const;
protected: protected:
bool isVisible(const SelectionContext &selectionContext) const override; bool isVisible(const SelectionContext &selectionContext) const override;
bool isEnabled(const SelectionContext &selectionContext) const override; bool isEnabled(const SelectionContext &selectionContext) const override;
private: private:
QByteArray m_menuId; QByteArray m_menuId;
Edit3DActionTemplate *m_actionTemplate = nullptr;
}; };
class Edit3DCameraAction : public Edit3DAction class Edit3DCameraAction : public Edit3DAction
@@ -77,7 +85,7 @@ public:
bool checked, bool checked,
const QIcon &iconOff, const QIcon &iconOff,
const QIcon &iconOn, const QIcon &iconOn,
AbstractView *view, Edit3DView *view,
SelectionContextOperation selectionAction = nullptr); SelectionContextOperation selectionAction = nullptr);
protected: protected:

View File

@@ -38,7 +38,9 @@ Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies)
} }
Edit3DView::~Edit3DView() Edit3DView::~Edit3DView()
{} {
qDeleteAll(m_edit3DActions);
}
void Edit3DView::createEdit3DWidget() void Edit3DView::createEdit3DWidget()
{ {
@@ -197,6 +199,31 @@ void Edit3DView::onEntriesChanged()
m_compressionTimer.start(); 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() void Edit3DView::handleEntriesChanged()
{ {
if (!model()) if (!model())
@@ -389,12 +416,12 @@ Edit3DAction *Edit3DView::createResetColorAction(QAction *syncBackgroundColorAct
auto operation = [this, syncBackgroundColorAction](const SelectionContext &) { auto operation = [this, syncBackgroundColorAction](const SelectionContext &) {
QList<QColor> bgColors = {QRgb(0x222222), QRgb(0x999999)}; QList<QColor> bgColors = {QRgb(0x222222), QRgb(0x999999)};
Edit3DViewConfig::setColor(this, View3DActionType::SelectBackgroundColor, bgColors); Edit3DViewConfig::setColors(this, View3DActionType::SelectBackgroundColor, bgColors);
Edit3DViewConfig::saveColor(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors); Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_BACKGROUND_COLOR, bgColors);
QColor gridColor{0xaaaaaa}; QColor gridColor{0xaaaaaa};
Edit3DViewConfig::setColor(this, View3DActionType::SelectGridColor, gridColor); Edit3DViewConfig::setColors(this, View3DActionType::SelectGridColor, {gridColor});
Edit3DViewConfig::saveColor(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, gridColor); Edit3DViewConfig::saveColors(DesignerSettingsKey::EDIT3DVIEW_GRID_COLOR, {gridColor});
if (syncBackgroundColorAction->isChecked()) { if (syncBackgroundColorAction->isChecked()) {
Edit3DViewConfig::set(this, View3DActionType::SyncBackgroundColor, false); Edit3DViewConfig::set(this, View3DActionType::SyncBackgroundColor, false);
@@ -812,6 +839,11 @@ QVector<Edit3DAction *> Edit3DView::backgroundColorActions() const
return m_backgroundColorActions; return m_backgroundColorActions;
} }
Edit3DAction *Edit3DView::edit3DAction(View3DActionType type) const
{
return m_edit3DActions.value(type, nullptr);
}
void Edit3DView::addQuick3DImport() void Edit3DView::addQuick3DImport()
{ {
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();

View File

@@ -57,6 +57,7 @@ public:
QVector<Edit3DAction *> rightActions() const; QVector<Edit3DAction *> rightActions() const;
QVector<Edit3DAction *> visibilityToggleActions() const; QVector<Edit3DAction *> visibilityToggleActions() const;
QVector<Edit3DAction *> backgroundColorActions() const; QVector<Edit3DAction *> backgroundColorActions() const;
Edit3DAction *edit3DAction(View3DActionType type) const;
void setSeeker(SeekerSlider *slider); void setSeeker(SeekerSlider *slider);
void addQuick3DImport(); void addQuick3DImport();
@@ -77,6 +78,9 @@ private:
None None
}; };
void registerEdit3DAction(Edit3DAction *action);
void unregisterEdit3DAction(Edit3DAction *action);
void createEdit3DWidget(); void createEdit3DWidget();
void checkImports(); void checkImports();
void handleEntriesChanged(); void handleEntriesChanged();
@@ -92,6 +96,7 @@ private:
QVector<Edit3DAction *> m_rightActions; QVector<Edit3DAction *> m_rightActions;
QVector<Edit3DAction *> m_visibilityToggleActions; QVector<Edit3DAction *> m_visibilityToggleActions;
QVector<Edit3DAction *> m_backgroundColorActions; QVector<Edit3DAction *> m_backgroundColorActions;
QMap<View3DActionType, Edit3DAction *> m_edit3DActions;
Edit3DAction *m_selectionModeAction = nullptr; Edit3DAction *m_selectionModeAction = nullptr;
Edit3DAction *m_moveToolAction = nullptr; Edit3DAction *m_moveToolAction = nullptr;
Edit3DAction *m_rotateToolAction = nullptr; Edit3DAction *m_rotateToolAction = nullptr;
@@ -120,6 +125,8 @@ private:
NodeAtPosReqType m_nodeAtPosReqType; NodeAtPosReqType m_nodeAtPosReqType;
QPoint m_contextMenuPos; QPoint m_contextMenuPos;
QTimer m_compressionTimer; QTimer m_compressionTimer;
friend class Edit3DAction;
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -28,17 +28,9 @@ public:
}); });
} }
static void setColor(AbstractView *view, View3DActionType type, const QList<QColor> &colorConfig) static void setColors(AbstractView *view, View3DActionType type, const QList<QColor> &colorConfig)
{ {
if (colorConfig.size() == 1) setVariant(view, type, QVariant::fromValue(colorConfig));
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));
} }
template <typename T> template <typename T>
@@ -47,7 +39,7 @@ public:
setVariant(view, type, QVariant::fromValue(value)); setVariant(view, type, QVariant::fromValue(value));
} }
static void saveColor(const QByteArray &key, const QList<QColor> &colorConfig) static void saveColors(const QByteArray &key, const QList<QColor> &colorConfig)
{ {
QStringList colorNames = Utils::transform(colorConfig, [](const QColor &color) { QStringList colorNames = Utils::transform(colorConfig, [](const QColor &color) {
return color.name(); return color.name();
@@ -56,12 +48,7 @@ public:
saveVariant(key, QVariant::fromValue(colorNames)); saveVariant(key, QVariant::fromValue(colorNames));
} }
static void saveColor(const QByteArray &key, const QColor &color) static bool colorsValid(const QList<QColor> &colorConfig) { return !colorConfig.isEmpty(); }
{
saveVariant(key, QVariant::fromValue(color.name()));
}
static bool isColorValid(const QList<QColor> &colorConfig) { return !colorConfig.isEmpty(); }
private: private:
static void setVariant(AbstractView *view, View3DActionType type, const QVariant &colorConfig) static void setVariant(AbstractView *view, View3DActionType type, const QVariant &colorConfig)

View File

@@ -232,6 +232,14 @@ void Edit3DWidget::createContextMenu()
view()->setSelectedModelNode(parentNode); 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(); m_contextMenu->addSeparator();
} }
@@ -375,6 +383,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
m_alignCameraAction->setEnabled(isCamera); m_alignCameraAction->setEnabled(isCamera);
m_alignViewAction->setEnabled(isCamera); m_alignViewAction->setEnabled(isCamera);
m_selectParentAction->setEnabled(selectionExcludingRoot); m_selectParentAction->setEnabled(selectionExcludingRoot);
m_toggleGroupAction->setEnabled(true);
m_contextMenu->popup(mapToGlobal(pos)); m_contextMenu->popup(mapToGlobal(pos));
} }

View File

@@ -72,6 +72,7 @@ private:
QPointer<QAction> m_alignCameraAction; QPointer<QAction> m_alignCameraAction;
QPointer<QAction> m_alignViewAction; QPointer<QAction> m_alignViewAction;
QPointer<QAction> m_selectParentAction; QPointer<QAction> m_selectParentAction;
QPointer<QAction> m_toggleGroupAction;
QPointer<QMenu> m_createSubMenu; QPointer<QMenu> m_createSubMenu;
ModelNode m_contextMenuTarget; ModelNode m_contextMenuTarget;
QVector3D m_contextMenuPos3d; QVector3D m_contextMenuPos3d;

View File

@@ -3,8 +3,8 @@
#include "materialbrowsertexturesmodel.h" #include "materialbrowsertexturesmodel.h"
#include "designeractionmanager.h"
#include "designmodewidget.h" #include "designmodewidget.h"
#include "imageutils.h"
#include "qmldesignerplugin.h" #include "qmldesignerplugin.h"
#include "qmlobjectnode.h" #include "qmlobjectnode.h"
#include "variantproperty.h" #include "variantproperty.h"
@@ -52,24 +52,18 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role)
return m_textureList.at(index.row()).internalId(); return m_textureList.at(index.row()).internalId();
if (role == RoleTexToolTip) { 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()) if (source.isEmpty())
return tr("Texture has no source image."); 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() if (info.isEmpty())
.modelNodePreviewOperation(m_textureList.at(index.row())); return tr("Texture has no data.");
if (!op)
return noData;
QVariantMap imgMap = op(m_textureList.at(index.row())).toMap(); QString sourceRelative = QmlObjectNode(texNode).modelValue("source").toString();
if (imgMap.isEmpty()) return QLatin1String("%1\n%2\n%3").arg(texNode.id(), sourceRelative, info);
return noData;
return QLatin1String("%1\n%2\n%3").arg(imgMap.value("id").toString(),
source.split('/').last(),
imgMap.value("info").toString());
} }
return {}; return {};

View File

@@ -797,10 +797,7 @@ void NavigatorTreeModel::handleMaterialDrop(const QMimeData *mimeData, int rowNu
if (!targetNode.metaInfo().isQtQuick3DModel()) if (!targetNode.metaInfo().isQtQuick3DModel())
return; return;
QByteArray data = mimeData->data(Constants::MIME_TYPE_MATERIAL); qint32 internalId = mimeData->data(Constants::MIME_TYPE_MATERIAL).toInt();
QDataStream stream(data);
qint32 internalId;
stream >> internalId;
ModelNode matNode = m_view->modelNodeForInternalId(internalId); ModelNode matNode = m_view->modelNodeForInternalId(internalId);
m_view->executeInTransaction(__FUNCTION__, [&] { m_view->executeInTransaction(__FUNCTION__, [&] {

View File

@@ -253,10 +253,7 @@ void NavigatorView::dragStarted(QMimeData *mimeData)
m_widget->setDragType(itemLibraryEntry.typeName()); m_widget->setDragType(itemLibraryEntry.typeName());
m_widget->update(); m_widget->update();
} else if (mimeData->hasFormat(Constants::MIME_TYPE_MATERIAL)) { } else if (mimeData->hasFormat(Constants::MIME_TYPE_MATERIAL)) {
QByteArray data = mimeData->data(Constants::MIME_TYPE_MATERIAL); qint32 internalId = mimeData->data(Constants::MIME_TYPE_MATERIAL).toInt();
QDataStream stream(data);
qint32 internalId;
stream >> internalId;
ModelNode matNode = modelNodeForInternalId(internalId); ModelNode matNode = modelNodeForInternalId(internalId);
m_widget->setDragType(matNode.metaInfo().typeName()); m_widget->setDragType(matNode.metaInfo().typeName());

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "propertyeditorimageprovider.h" #include "propertyeditorimageprovider.h"
#include "assetslibrarymodel.h" #include "asset.h"
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <utils/hdrimage.h> #include <utils/hdrimage.h>
@@ -16,12 +16,12 @@ namespace QmlDesigner {
QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QString &id, QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QString &id,
const QSize &requestedSize) 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); return m_smallImageCacheProvider.requestImageResponse(id, requestedSize);
if (suffix == "*.builtin") if (asset.suffix() == "*.builtin")
return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(), return m_smallImageCacheProvider.requestImageResponse("#" + id.split('.').first(),
requestedSize); requestedSize);
@@ -29,15 +29,15 @@ QQuickImageResponse *PropertyEditorImageProvider::requestImageResponse(const QSt
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
response.get(), response.get(),
[response = QPointer<QmlDesigner::ImageResponse>(response.get()), suffix, id, requestedSize] { [response = QPointer<QmlDesigner::ImageResponse>(response.get()), asset, requestedSize] {
if (AssetsLibraryModel::supportedImageSuffixes().contains(suffix)) { if (asset.isImage()) {
QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(id)); QImage image = QImage(Utils::StyleHelper::dpiSpecificImageFile(asset.id()));
if (!image.isNull()) { if (!image.isNull()) {
response->setImage(image.scaled(requestedSize, Qt::KeepAspectRatio)); response->setImage(image.scaled(requestedSize, Qt::KeepAspectRatio));
return; return;
} }
} else if (AssetsLibraryModel::supportedTexture3DSuffixes().contains(suffix)) { } else if (asset.isTexture3D()) {
HdrImage hdr{id}; HdrImage hdr{asset.id()};
if (!hdr.image().isNull()) { if (!hdr.image().isNull()) {
response->setImage(hdr.image().scaled(requestedSize, Qt::KeepAspectRatio)); response->setImage(hdr.image().scaled(requestedSize, Qt::KeepAspectRatio));
return; return;

View File

@@ -5,6 +5,8 @@
#include "qmldesignercorelib_global.h" #include "qmldesignercorelib_global.h"
#include "utils/filepath.h"
#include <QString> #include <QString>
#include <QHash> #include <QHash>
#include <modelnode.h> #include <modelnode.h>
@@ -26,7 +28,12 @@ class QMLDESIGNERCORE_EXPORT StylesheetMerger
public: public:
StylesheetMerger(AbstractView*, AbstractView*); StylesheetMerger(AbstractView*, AbstractView*);
void merge(); 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: private:
void preprocessStyleSheet(); void preprocessStyleSheet();

View File

@@ -25,6 +25,7 @@
#include "createscenecommand.h" #include "createscenecommand.h"
#include "debugoutputcommand.h" #include "debugoutputcommand.h"
#include "informationchangedcommand.h" #include "informationchangedcommand.h"
#include "imageutils.h"
#include "inputeventcommand.h" #include "inputeventcommand.h"
#include "nodeabstractproperty.h" #include "nodeabstractproperty.h"
#include "nodeinstanceserverproxy.h" #include "nodeinstanceserverproxy.h"
@@ -83,6 +84,8 @@
#include <QDirIterator> #include <QDirIterator>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QImageReader>
#include <QLocale>
#include <QMultiHash> #include <QMultiHash>
#include <QPainter> #include <QPainter>
#include <QPicture> #include <QPicture>
@@ -135,12 +138,13 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
// related to a single event to be received before we act. // related to a single event to be received before we act.
m_resetTimer.setSingleShot(true); m_resetTimer.setSingleShot(true);
m_resetTimer.setInterval(100); m_resetTimer.setInterval(100);
QObject::connect(&m_resetTimer, &QTimer::timeout, [this] { QObject::connect(&m_resetTimer, &QTimer::timeout, this, [this] {
resetPuppet(); if (isAttached())
resetPuppet();
}); });
m_updateWatcherTimer.setSingleShot(true); m_updateWatcherTimer.setSingleShot(true);
m_updateWatcherTimer.setInterval(100); 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)) for (const auto &path : std::as_const(m_pendingUpdateDirs))
updateWatcher(path); updateWatcher(path);
m_pendingUpdateDirs.clear(); m_pendingUpdateDirs.clear();
@@ -151,11 +155,11 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
// unnecessary generation when project with multiple shaders is opened. // unnecessary generation when project with multiple shaders is opened.
m_generateQsbFilesTimer.setSingleShot(true); m_generateQsbFilesTimer.setSingleShot(true);
m_generateQsbFilesTimer.setInterval(100); m_generateQsbFilesTimer.setInterval(100);
QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, [this] { QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, this, [this] {
handleShaderChanges(); handleShaderChanges();
}); });
connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this,
[this](const QString &path) { [this](const QString &path) {
const QSet<QString> pendingDirs = m_pendingUpdateDirs; const QSet<QString> pendingDirs = m_pendingUpdateDirs;
for (const auto &pendingPath : pendingDirs) { for (const auto &pendingPath : pendingDirs) {
@@ -171,7 +175,7 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
m_updateWatcherTimer.start(); 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)) { if (m_qsbTargets.contains(path)) {
m_qsbTargets.insert(path, true); m_qsbTargets.insert(path, true);
m_generateQsbFilesTimer.start(); m_generateQsbFilesTimer.start();
@@ -1909,23 +1913,7 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo
imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio); imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio);
imageData.pixmap.setDevicePixelRatio(ratio); imageData.pixmap.setDevicePixelRatio(ratio);
imageData.time = modified; imageData.time = modified;
imageData.info = ImageUtils::imageInfo(imageSource);
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());
m_imageDataMap.insert(imageData.id, imageData); m_imageDataMap.insert(imageData.id, imageData);
} }
} }

View File

@@ -35,7 +35,7 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath,
processFinishCallback); processFinishCallback);
if (forwardOutput == puppetMode || forwardOutput == "all") { if (forwardOutput == puppetMode || forwardOutput == "all") {
puppetProcess->setProcessChannelMode(QProcess::MergedChannels); puppetProcess->setProcessChannelMode(QProcess::ForwardedChannels);
QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback);
} }
puppetProcess->setWorkingDirectory(workingDirectory); puppetProcess->setWorkingDirectory(workingDirectory);
@@ -46,7 +46,6 @@ QProcessUniquePointer puppetProcess(const QString &puppetPath,
else else
processArguments = {socketToken, puppetMode}; processArguments = {socketToken, puppetMode};
processArguments.push_back("-graphicssystem raster");
processArguments.push_back(freeTypeOption); processArguments.push_back(freeTypeOption);
puppetProcess->start(puppetPath, processArguments); puppetProcess->start(puppetPath, processArguments);

View File

@@ -562,9 +562,9 @@ QVector<PropertyInfo> getObjectTypes(const ObjectValue *objectValue, const Conte
const CppComponentValue * qmlObjectValue = value_cast<CppComponentValue>(prototype); const CppComponentValue * qmlObjectValue = value_cast<CppComponentValue>(prototype);
if (qmlObjectValue) if (qmlObjectValue)
propertyList.append(getQmlTypes(qmlObjectValue, context, local, rec)); propertyList.append(getQmlTypes(qmlObjectValue, context, local, rec + 1));
else else
propertyList.append(getObjectTypes(prototype, context, local, rec)); propertyList.append(getObjectTypes(prototype, context, local, rec + 1));
} }
return propertyList; return propertyList;

View File

@@ -559,6 +559,7 @@ void AbstractView::resetView()
void AbstractView::resetPuppet() void AbstractView::resetPuppet()
{ {
QTC_ASSERT(isAttached(), return);
emitCustomNotification(QStringLiteral("reset QmlPuppet")); emitCustomNotification(QStringLiteral("reset QmlPuppet"));
} }
@@ -689,7 +690,8 @@ void AbstractView::emitCustomNotification(const QString &identifier, const QList
void AbstractView::emitCustomNotification(const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data) void AbstractView::emitCustomNotification(const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{ {
model()->d->notifyCustomNotification(this, identifier, nodeList, data); if (model())
model()->d->notifyCustomNotification(this, identifier, nodeList, data);
} }
void AbstractView::emitInstancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) void AbstractView::emitInstancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList)

View File

@@ -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; Model *parentModel = model;
QTC_ASSERT(parentModel, return); QTC_ASSERT(parentModel, return );
auto templateModel(Model::create("QtQuick.Item", 2, 1, parentModel)); auto templateModel(Model::create("QtQuick.Item", 2, 1, parentModel));
Q_ASSERT(templateModel.get()); Q_ASSERT(templateModel.get());
@@ -527,10 +540,6 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext
templateModel->setFileUrl(parentModel->fileUrl()); templateModel->setFileUrl(parentModel->fileUrl());
QPlainTextEdit textEditTemplate; QPlainTextEdit textEditTemplate;
Utils::FileReader reader;
QTC_ASSERT(reader.fetch(Utils::FilePath::fromString(templateFile)), return);
QString qmlTemplateString = QString::fromUtf8(reader.data());
QString imports; QString imports;
for (const Import &import : parentModel->imports()) { for (const Import &import : parentModel->imports()) {
@@ -541,13 +550,14 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext
textEditTemplate.setPlainText(imports + qmlTemplateString); textEditTemplate.setPlainText(imports + qmlTemplateString);
NotIndentingTextEditModifier textModifierTemplate(&textEditTemplate); NotIndentingTextEditModifier textModifierTemplate(&textEditTemplate);
QScopedPointer<RewriterView> templateRewriterView(new RewriterView(externalDependencies, RewriterView::Amend)); QScopedPointer<RewriterView> templateRewriterView(
new RewriterView(externalDependencies, RewriterView::Amend));
templateRewriterView->setTextModifier(&textModifierTemplate); templateRewriterView->setTextModifier(&textModifierTemplate);
templateModel->attachView(templateRewriterView.data()); templateModel->attachView(templateRewriterView.data());
templateRewriterView->setCheckSemanticErrors(false); templateRewriterView->setCheckSemanticErrors(false);
ModelNode templateRootNode = templateRewriterView->rootModelNode(); ModelNode templateRootNode = templateRewriterView->rootModelNode();
QTC_ASSERT(templateRootNode.isValid(), return); QTC_ASSERT(templateRootNode.isValid(), return );
auto styleModel(Model::create("QtQuick.Item", 2, 1, parentModel)); auto styleModel(Model::create("QtQuick.Item", 2, 1, parentModel));
Q_ASSERT(styleModel.get()); Q_ASSERT(styleModel.get());
@@ -556,11 +566,12 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext
QPlainTextEdit textEditStyle; QPlainTextEdit textEditStyle;
RewriterView *parentRewriterView = parentModel->rewriterView(); RewriterView *parentRewriterView = parentModel->rewriterView();
QTC_ASSERT(parentRewriterView, return); QTC_ASSERT(parentRewriterView, return );
textEditStyle.setPlainText(parentRewriterView->textModifierContent()); textEditStyle.setPlainText(parentRewriterView->textModifierContent());
NotIndentingTextEditModifier textModifierStyle(&textEditStyle); NotIndentingTextEditModifier textModifierStyle(&textEditStyle);
QScopedPointer<RewriterView> styleRewriterView(new RewriterView(externalDependencies, RewriterView::Amend)); QScopedPointer<RewriterView> styleRewriterView(
new RewriterView(externalDependencies, RewriterView::Amend));
styleRewriterView->setTextModifier(&textModifierStyle); styleRewriterView->setTextModifier(&textModifierStyle);
styleModel->attachView(styleRewriterView.data()); styleModel->attachView(styleRewriterView.data());

View File

@@ -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 <QImageReader>
#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<QByteArray> 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<QString> &Asset::supportedSuffixes()
{
static QSet<QString> 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

View File

@@ -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<QString> &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

View File

@@ -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 <QFile>
#include <QFileInfo>
#include <QImageReader>
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

View File

@@ -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 <QString>
namespace QmlDesigner {
class ImageUtils
{
public:
ImageUtils();
static QString imageInfo(const QString &path);
};
} // namespace QmlDesigner

View File

@@ -75,13 +75,13 @@ void FileDownloader::start()
auto request = QNetworkRequest(m_url); auto request = QNetworkRequest(m_url);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::UserVerifiedRedirectPolicy); QNetworkRequest::UserVerifiedRedirectPolicy);
QNetworkReply *reply = Utils::NetworkAccessManager::instance()->get(request); m_reply = Utils::NetworkAccessManager::instance()->get(request);
QNetworkReply::connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { QNetworkReply::connect(m_reply, &QNetworkReply::readyRead, this, [this]() {
m_tempFile.write(reply->readAll()); m_tempFile.write(m_reply->readAll());
}); });
QNetworkReply::connect(reply, QNetworkReply::connect(m_reply,
&QNetworkReply::downloadProgress, &QNetworkReply::downloadProgress,
this, this,
[this](qint64 current, qint64 max) { [this](qint64 current, qint64 max) {
@@ -92,16 +92,21 @@ void FileDownloader::start()
emit progressChanged(); emit progressChanged();
}); });
QNetworkReply::connect(reply, &QNetworkReply::redirected, [reply](const QUrl &) { QNetworkReply::connect(m_reply, &QNetworkReply::redirected, [this](const QUrl &) {
emit reply->redirectAllowed(); emit m_reply->redirectAllowed();
}); });
QNetworkReply::connect(reply, &QNetworkReply::finished, this, [this, reply]() { QNetworkReply::connect(m_reply, &QNetworkReply::finished, this, [this]() {
if (reply->error()) { if (m_reply->error()) {
if (m_tempFile.exists()) if (m_tempFile.exists())
m_tempFile.remove(); 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 { } else {
m_tempFile.flush(); m_tempFile.flush();
m_tempFile.close(); m_tempFile.close();
@@ -109,9 +114,17 @@ void FileDownloader::start()
emit tempFileChanged(); emit tempFileChanged();
emit finishedChanged(); emit finishedChanged();
} }
m_reply = nullptr;
}); });
} }
void FileDownloader::cancel()
{
if (m_reply)
m_reply->abort();
}
void FileDownloader::setUrl(const QUrl &url) void FileDownloader::setUrl(const QUrl &url)
{ {
m_url = url; m_url = url;

View File

@@ -112,6 +112,7 @@ public:
bool available() const; bool available() const;
Q_INVOKABLE void start(); Q_INVOKABLE void start();
Q_INVOKABLE void cancel();
signals: signals:
void finishedChanged(); void finishedChanged();
@@ -123,6 +124,8 @@ signals:
void lastModifiedChanged(); void lastModifiedChanged();
void availableChanged(); void availableChanged();
void downloadCanceled();
private: private:
void probeUrl(); void probeUrl();
@@ -133,6 +136,8 @@ private:
QFile m_tempFile; QFile m_tempFile;
QDateTime m_lastModified; QDateTime m_lastModified;
bool m_available = false; bool m_available = false;
QNetworkReply *m_reply = nullptr;
}; };
class DataModelDownloader : public QObject class DataModelDownloader : public QObject

View File

@@ -23,6 +23,7 @@
#include <QMetaEnum> #include <QMetaEnum>
#include <QTimer> #include <QTimer>
#include <QVersionNumber> #include <QVersionNumber>
#include <QScrollArea>
#include <memory> #include <memory>
@@ -223,7 +224,12 @@ static void showUpdateInfo(const QList<Update> &updates,
label->setText("<qt><p>" + UpdateInfoPlugin::tr("Available updates:") + "<ul><li>" label->setText("<qt><p>" + UpdateInfoPlugin::tr("Available updates:") + "<ul><li>"
+ qtText + updateText + "</li></ul></p></qt>"); + qtText + updateText + "</li></ul></p></qt>");
label->setContentsMargins(0, 0, 0, 8); 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 ICore::infoBar()->removeInfo(InstallUpdates); // remove any existing notifications

View File

@@ -23,6 +23,8 @@ if (NOT QT_CREATOR_API_DEFINED)
) )
endif() endif()
configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES)
if (NOT TARGET QmlPuppetCommunication) if (NOT TARGET QmlPuppetCommunication)
include(../../libs/qmlpuppetcommunication/QmlPuppetCommunication.cmake) include(../../libs/qmlpuppetcommunication/QmlPuppetCommunication.cmake)
endif() endif()
@@ -34,13 +36,34 @@ add_qtc_executable(qml2puppet
Qt5::CorePrivate Qt5::Widgets Qt5::QmlPrivate Qt5::CorePrivate Qt5::Widgets Qt5::QmlPrivate
Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate
QmlPuppetCommunication QmlPuppetCommunication
INCLUDES
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
SOURCES SOURCES
qml2puppet/qml2puppetmain.cpp qml2puppet/main.cpp
qml2puppet/qmlbase.h qml2puppet/appmetadata.h
qml2puppet/qmlpuppet.h qml2puppet/qmlpuppet.cpp qml2puppet/configcrashpad.h
qmlpuppet.qrc qmlpuppet.qrc
PROPERTIES PROPERTIES
OUTPUT_NAME qml2puppet-${IDE_VERSION} 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 extend_qtc_executable(qml2puppet
CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0
SOURCES SOURCES
@@ -191,10 +214,27 @@ extend_qtc_executable(qml2puppet
DEPENDS Nanotrace DEPENDS Nanotrace
) )
if (QTC_STATIC_BUILD AND Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0) if (Qt5_VERSION VERSION_GREATER_EQUAL 6.4.0)
qt6_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR}) 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() endif()
# Crashpad # Crashpad
# only windows requires separate crashpad client per process until client->SetHandlerIPCPipe() # only windows requires separate crashpad client per process until client->SetHandlerIPCPipe()
# is implemented (check the TODO inside startCrashpad()) # is implemented (check the TODO inside startCrashpad())

View File

@@ -194,24 +194,18 @@ Item {
function updateViewStates(viewStates) function updateViewStates(viewStates)
{ {
if ("selectBackgroundColor" in viewStates) { if ("selectBackgroundColor" in viewStates) {
if (Array.isArray(viewStates.selectBackgroundColor)) { var colors = viewStates.selectBackgroundColor
var colors = viewStates.selectBackgroundColor if (colors.length === 1) {
if (colors.length === 1) { backgroundGradientColorStart = colors[0];
backgroundGradientColorStart = colors[0]; backgroundGradientColorEnd = colors[0];
backgroundGradientColorEnd = colors[0];
} else {
backgroundGradientColorStart = colors[0];
backgroundGradientColorEnd = colors[1];
}
} else { } else {
var color = viewStates.selectBackgroundColor backgroundGradientColorStart = colors[0];
backgroundGradientColorStart = color; backgroundGradientColorEnd = colors[1];
backgroundGradientColorEnd = color;
} }
} }
if ("selectGridColor" in viewStates) if ("selectGridColor" in viewStates && viewStates.selectGridColor.length === 1)
viewRoot.gridColor = viewStates.selectGridColor viewRoot.gridColor = viewStates.selectGridColor[0]
} }
// If resetToDefault is true, tool states not specifically set to anything will be reset to // If resetToDefault is true, tool states not specifically set to anything will be reset to

View File

@@ -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 <QCommandLineParser>
#include <QLoggingCategory>
#include <app/app_version.h>
// 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

View File

@@ -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 <QtGlobal>
#ifdef ENABLE_QT_BREAKPAD
#include <qtsystemexceptionhandler.h>
#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<std::string, std::string> annotations;
annotations["qt-version"] = QT_VERSION_STR;
// Optional arguments to pass to the handler
std::vector<std::string> 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)
}

View File

@@ -1043,6 +1043,21 @@ QList<ServerNodeInstance> NodeInstanceServer::allGroupStateInstances() const
return groups; return groups;
} }
QList<ServerNodeInstance> NodeInstanceServer::allView3DInstances() const
{
QList<ServerNodeInstance> 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) void NodeInstanceServer::setStateInstance(const ServerNodeInstance &stateInstance)
{ {
m_activeStateInstance = stateInstance; m_activeStateInstance = stateInstance;

View File

@@ -179,6 +179,7 @@ public:
ServerNodeInstance rootNodeInstance() const; ServerNodeInstance rootNodeInstance() const;
QList<ServerNodeInstance> allGroupStateInstances() const; QList<ServerNodeInstance> allGroupStateInstances() const;
QList<ServerNodeInstance> allView3DInstances() const;
void notifyPropertyChange(qint32 instanceid, const PropertyName &propertyName); void notifyPropertyChange(qint32 instanceid, const PropertyName &propertyName);

View File

@@ -921,9 +921,9 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D([[maybe_unu
if (toolStates.contains("syncBackgroundColor")) { if (toolStates.contains("syncBackgroundColor")) {
bool sync = toolStates["syncBackgroundColor"].toBool(); bool sync = toolStates["syncBackgroundColor"].toBool();
if (sync) { if (sync) {
QColor color = helper->sceneEnvironmentColor(sceneId); QList<QColor> colors{helper->sceneEnvironmentColor(sceneId)};
View3DActionCommand cmd(View3DActionType::SelectBackgroundColor, View3DActionCommand cmd(View3DActionType::SelectBackgroundColor,
QVariant::fromValue(color)); QVariant::fromValue(colors));
view3DAction(cmd); view3DAction(cmd);
} }
} }
@@ -2276,9 +2276,10 @@ void Qt5InformationNodeInstanceServer::setSceneEnvironmentColor(const PropertyVa
if (toolStates.contains("syncBackgroundColor")) { if (toolStates.contains("syncBackgroundColor")) {
bool sync = toolStates["syncBackgroundColor"].toBool(); bool sync = toolStates["syncBackgroundColor"].toBool();
QList<QColor> colors{color};
if (sync) { if (sync) {
View3DActionCommand cmd(View3DActionType::SelectBackgroundColor, View3DActionCommand cmd(View3DActionType::SelectBackgroundColor,
QVariant::fromValue(color)); QVariant::fromValue(colors));
view3DAction(cmd); view3DAction(cmd);
} }
} }

View File

@@ -53,28 +53,41 @@ void Qt5PreviewNodeInstanceServer::collectItemChangesAndSendChangeCommands()
QQuickDesignerSupport::polishItems(quickWindow()); QQuickDesignerSupport::polishItems(quickWindow());
QVector<ImageContainer> imageContainerVector; QVector<ImageContainer> 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<ServerNodeInstance> 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)); imageContainerVector.append(ImageContainer(0, renderPreviewImage(), -1));
QList<ServerNodeInstance> stateInstances = rootNodeInstance().stateInstances(); QList<ServerNodeInstance> stateInstances = rootNodeInstance().stateInstances();
const QList<ServerNodeInstance> groupInstances = allGroupStateInstances(); const QList<ServerNodeInstance> groupInstances = allGroupStateInstances();
for (ServerNodeInstance instance : groupInstances) { for (const ServerNodeInstance &instance : groupInstances)
stateInstances.append(instance.stateInstances()); stateInstances.append(instance.stateInstances());
}
for (ServerNodeInstance instance : std::as_const(stateInstances)) { for (ServerNodeInstance instance : std::as_const(stateInstances)) {
instance.activateState(); instance.activateState();
QImage previewImage = renderPreviewImage(); QImage previewImage = renderPreviewImage();
if (!previewImage.isNull()) if (!previewImage.isNull())
imageContainerVector.append(ImageContainer(instance.instanceId(), imageContainerVector.append(ImageContainer(instance.instanceId(),
renderPreviewImage(), renderPreviewImage(),
instance.instanceId())); instance.instanceId()));
instance.deactivateState(); instance.deactivateState();
} }
nodeInstanceClient()->statePreviewImagesChanged( nodeInstanceClient()->statePreviewImagesChanged(
StatePreviewImageChangedCommand(imageContainerVector)); StatePreviewImageChangedCommand(imageContainerVector));
slowDownRenderTimer(); slowDownRenderTimer();
handleExtraRender(); handleExtraRender();

View File

@@ -56,6 +56,7 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
if (quickWindow() && nodeInstanceClient()->bytesToWrite() < 10000) { if (quickWindow() && nodeInstanceClient()->bytesToWrite() < 10000) {
bool windowDirty = false; bool windowDirty = false;
bool hasView3D = false;
for (QQuickItem *item : allItems()) { for (QQuickItem *item : allItems()) {
if (item) { if (item) {
if (Internal::QuickItemNodeInstance::unifiedRenderPath()) { if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
@@ -65,8 +66,13 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
} }
} else { } else {
if (hasInstanceForObject(item)) { 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)); m_dirtyInstanceSet.insert(instanceForObject(item));
}
if (QQuickItem *effectParent = parentEffectItem(item)) { if (QQuickItem *effectParent = parentEffectItem(item)) {
if ((QQuickDesignerSupport::isDirty( if ((QQuickDesignerSupport::isDirty(
item, item,
@@ -100,9 +106,19 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()}));
} else { } else {
if (!m_dirtyInstanceSet.isEmpty()) { if (!m_dirtyInstanceSet.isEmpty()) {
nodeInstanceClient()->pixmapChanged( auto renderList = QtHelpers::toList(m_dirtyInstanceSet);
createPixmapChangedCommand(QtHelpers::toList(m_dirtyInstanceSet)));
m_dirtyInstanceSet.clear(); // 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<ServerNodeInstance> view3Ds = allView3DInstances();
for (auto &view3D : view3Ds) {
if (!m_dirtyInstanceSet.contains(view3D))
renderList.append(view3D);
}
}
nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand(renderList));
} }
} }

View File

@@ -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();
}

View File

@@ -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 <qt5nodeinstanceclientproxy.h>
#ifdef MULTILANGUAGE_TRANSLATIONPROVIDER
#include <sqlitelibraryinitializer.h>
#endif
#include <QQmlComponent>
#include <QQmlEngine>
#include <QDebug>
#include <QApplication>
#include <QStringList>
#include <QFileInfo>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#ifdef ENABLE_QT_BREAKPAD
#include <qtsystemexceptionhandler.h>
#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 <windows.h>
#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<std::string, std::string> annotations;
annotations["qt-version"] = QT_VERSION_STR;
// Optional arguments to pass to the handler
std::vector<std::string> 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 <stream file> [control stream file]";
qDebug() << "--rendericon <icon size> <icon file name> <icon source qml>";
qDebug() << "--import3dAsset <source asset file name> <output dir> <id number> <import options JSON>";
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);
}
}

View File

@@ -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 <QDir>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQmlContext>
#include <QFileInfo>
#include <QFileOpenEvent>
#include <QLibraryInfo>
#include <QSurfaceFormat>
#include <QCommandLineParser>
#include <QStandardPaths>
#include <QTranslator>
#include <QSharedPointer>
#include "appmetadata.h"
#include <iostream>
#include <QApplication>
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<QCoreApplication> 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<typename T>
void createCoreApp()
{
m_coreApp.reset(new T(m_args.argc, m_args.argv));
}
QSharedPointer<QCoreApplication> m_coreApp;
QCommandLineParser m_argParser;
QSharedPointer<QQmlApplicationEngine> 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());
}
}
};

View File

@@ -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 <sqlitelibraryinitializer.h>
#endif
#include <qml2puppet/iconrenderer/iconrenderer.h>
#include <qml2puppet/import3d/import3d.h>
#include <qt5nodeinstanceclientproxy.h>
#include <QFileInfo>
#include <QQmlComponent>
#include <QQmlEngine>
#if defined(Q_OS_WIN) && defined(QT_NO_DEBUG)
#include <Windows.h>
#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<QCoreApplication>();
#else
#if defined QT_WIDGETS_LIB
if (!useGuiApplication)
createCoreApp<QApplication>();
else
#endif //QT_WIDGETS_LIB
createCoreApp<QGuiApplication>();
#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<QObject>(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);
}

View File

@@ -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<IconRenderer> m_iconRenderer;
};

View File

@@ -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 <QCoreApplication>
#include <QQmlApplicationEngine>
//// 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<QObject *>(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;
};

View File

@@ -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 <QUrl>
#include <QtQml/qqml.h>
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<PartialScene> 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<PartialScene> sceneCompleters()
{
return QQmlListProperty<PartialScene>(this, &completers);
}
QList<PartialScene *> completers;
};

View File

@@ -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 <QIcon>
#include "loadwatcher.h"
#include "qmlruntime.h"
#include <private/qqmlimport_p.h>
#include <private/qtqmlglobal_p.h>
#if QT_CONFIG(qml_animation)
#include <private/qabstractanimation_p.h>
#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<QCoreApplication>();
}
else if (!strcmp(m_args.argv[i], "gui")) {
createCoreApp<QGuiApplication>();
}
#ifdef QT_WIDGETS_LIB
else if (!strcmp(m_args.argv[i], "widget")) {
createCoreApp<QApplication>();
static_cast<QApplication *>(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<QGuiApplication *>(m_coreApp.data())) {
m_exitTimerId = static_cast<QGuiApplication *>(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<LoadWatcher> 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<Config *>(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);
}
}

View File

@@ -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<Config> m_conf;
bool m_verboseMode = false;
bool m_quietMode = false;
int m_exitTimerId = -1;
};

View File

@@ -12,4 +12,9 @@
<file>mockfiles/ToolBarButton.qml</file> <file>mockfiles/ToolBarButton.qml</file>
<file>mockfiles/ToggleButton.qml</file> <file>mockfiles/ToggleButton.qml</file>
</qresource> </qresource>
<qresource prefix="/runner">
<file>runnerconf/qmlruntime/default.qml</file>
<file>runnerconf/qmlruntime/content/resizeItemToWindow.qml</file>
<file>runnerconf/qmlruntime/content/resizeWindowToItem.qml</file>
</qresource>
</RCC> </RCC>

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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")
}
}