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
Qt Creator to continue developing the project.
\target wizard-template-note
\note Since \QDS 3.9.0, \QDS project wizard templates generate projects that
automatically checkout and build the Qt Quick Studio Components from
\l{https://code.qt.io/cgit/qt-labs/qtquickdesigner-components.git/} {Qt Code Review},
using CMake. To turn off this feature, use the option \e BUILD_QDS_COMPONENTS
in the CMake configuration.
\if defined(qtdesignstudio)
For more information, see \l{Designer-Developer Workflow}.
\else
@@ -149,10 +156,8 @@
\section1 Adding Qt Quick Designer Components to Qt Installations
If you use Qt Quick Studio Components or Effects in your project, you have
to check out and install the \e {Qt Quick Designer Components} module from
\l{https://code.qt.io/cgit/qt-labs/qtquickdesigner-components.git/}
{Qt Code Review}.
Since \QDS 3.9, the Qt Quick Studio Components module is installed by default
as part of the application. You can also install the module manually.
For example:
\list 1

View File

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

View File

@@ -10,11 +10,19 @@
\note \QBF is included in the
\l{https://www.qt.io/pricing}{\QDS Enterprise license}.
\QBF is delivered with \QDS as a developer plugin that you can install to
the Desktop version of Figma. To install the plugin, open the Plugin Manager
of Figma and press the plus button to create a new plugin. Then choose the
\e Manifest.json file that comes with \QDS.
You need both Figma and Qt accounts to use \QBF in \QDS.
To use \QBF in \QDS:
\list 1
\li Go to \l {https://www.figma.com/community/plugin/1167809465162924409/Qt-Bridge-for-Figma}
{\QBF plugin page}.
\li Select \uicontrol {Try it out}.
\li Select your logged in Figma account.
\li After the plugin loads, select \uicontrol Run.
\li Select \uicontrol Export, to get the \e {.qtbridge} file in your
local drive.
\li In \QDS, drag the file to the \uicontrol 2D, \uicontrol 3D, \uicontrol Assets,
or \uicontrol Navigator view in an open project .
\endlist
You can launch the Figma plugin from \uicontrol Plugins >
\uicontrol Development > \uicontrol {\QBF} in Figma.
You can launch the installed Figma plugin from \uicontrol Plugins > \uicontrol {\QBF} in Figma.
*/

View File

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

View File

@@ -23,6 +23,7 @@ Image {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
onPressed: (mouse) => {
if (mouse.button === Qt.LeftButton)
@@ -31,4 +32,20 @@ Image {
root.showContextMenu()
}
}
ToolTip {
visible: mouseArea.containsMouse
// contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than
// needed). Using a helper Text to calculate the correct width
contentWidth: helperText.width
bottomInset: -2
text: modelData.textureToolTip
delay: 1000
Text {
id: helperText
text: modelData.textureToolTip
visible: false
}
}
}

View File

@@ -12,7 +12,7 @@ TreeViewDelegate {
required property Item assetsRoot
property bool hasChildWithDropHover: false
property bool isHoveringDrop: false
property bool isHighlighted: false
readonly property string suffix: model.fileName.substr(-4)
readonly property bool isFont: root.suffix === ".ttf" || root.suffix === ".otf"
readonly property bool isEffect: root.suffix === ".qep"
@@ -26,7 +26,12 @@ TreeViewDelegate {
readonly property int __dirItemHeight: 21
implicitHeight: root.__isDirectory ? root.__dirItemHeight : root.__fileItemHeight
implicitWidth: root.assetsView.width > 0 ? root.assetsView.width : 10
implicitWidth: {
if (root.assetsView.verticalScrollBar.scrollBarVisible)
return root.assetsView.width - root.indentation - root.assetsView.verticalScrollBar.width
else
return root.assetsView.width - root.indentation
}
leftMargin: root.__isDirectory ? 0 : thumbnailImage.width
@@ -54,17 +59,6 @@ TreeViewDelegate {
}
}
onImplicitWidthChanged: {
// a small hack, to fix a glitch: when resizing the width of the tree view,
// the widths of the delegate items remain the same as before, unless we re-set
// that width explicitly.
var newWidth = root.implicitWidth - (root.assetsView.verticalScrollBar.scrollBarVisible
? root.assetsView.verticalScrollBar.width
: 0)
bg.width = newWidth
bg.implicitWidth = newWidth
}
onDepthChanged: {
if (root.depth > root.initialDepth && root.initialDepth >= 0)
root.depth = root.initialDepth
@@ -73,8 +67,10 @@ TreeViewDelegate {
background: Rectangle {
id: bg
width: root.implicitWidth
color: {
if (root.__isDirectory && (root.isHoveringDrop || root.hasChildWithDropHover))
if (root.__isDirectory && (root.isHighlighted || root.hasChildWithDropHover))
return StudioTheme.Values.themeInteraction
if (!root.__isDirectory && root.assetsView.selectedAssets[root.__itemPath])
@@ -120,40 +116,6 @@ TreeViewDelegate {
}
}
DropArea {
id: treeDropArea
enabled: true
anchors.fill: parent
onEntered: (drag) => {
root.assetsRoot.updateDropExtFiles(drag)
root.isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0
if (root.isHoveringDrop)
root.assetsView.startDropHoverOver(root.__currentRow)
}
onDropped: (drag) => {
root.isHoveringDrop = false
root.assetsView.endDropHover(root.__currentRow)
let dirPath = root.__isDirectory
? model.filePath
: assetsModel.parentDirPath(model.filePath);
rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles,
root.assetsRoot.dropComplexExtFiles,
dirPath)
}
onExited: {
if (root.isHoveringDrop) {
root.isHoveringDrop = false
root.assetsView.endDropHover(root.__currentRow)
}
}
}
MouseArea {
id: mouseArea
@@ -247,6 +209,14 @@ TreeViewDelegate {
}
} // MouseArea
function getDirPath()
{
if (root.__isDirectory)
return model.filePath
else
return assetsModel.parentDirPath(model.filePath)
}
function __openContextMenuForCurrentRow()
{
let modelIndex = assetsModel.indexForPath(model.filePath)

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)
{
let index = root.__modelIndex(row)
if (assetsModel.isDirectory(index))
if (assetsModel.isDirectory(index)) {
let item = root.__getDelegateItemForIndex(index)
if (item)
item.isHighlighted = true
return
}
let parentItem = root.__getDelegateParentForIndex(index)
if (parentItem)
@@ -261,8 +265,12 @@ TreeView {
function endDropHover(row)
{
let index = root.__modelIndex(row)
if (assetsModel.isDirectory(index))
if (assetsModel.isDirectory(index)) {
let item = root.__getDelegateItemForIndex(index)
if (item)
item.isHighlighted = false
return
}
let parentItem = root.__getDelegateParentForIndex(index)
if (parentItem)
@@ -292,6 +300,12 @@ TreeView {
return root.itemAtCell(parentCell)
}
function __getDelegateItemForIndex(index)
{
let cell = root.cellAtIndex(index)
return root.itemAtCell(cell)
}
function __modelIndex(row)
{
// The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a
@@ -303,6 +317,76 @@ TreeView {
return root.modelIndex(row, 0)
}
DropArea {
id: dropArea
enabled: true
anchors.fill: parent
property bool __isHoveringDrop: false
property int __rowHoveringOver: -1
function __rowAndItem(drag)
{
let pos = dropArea.mapToItem(root, drag.x, drag.y)
let cell = root.cellAtPos(pos.x, pos.y, true)
let item = root.itemAtCell(cell)
return [cell.y, item]
}
onEntered: (drag) => {
root.assetsRoot.updateDropExtFiles(drag)
let [row, item] = dropArea.__rowAndItem(drag)
dropArea.__isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0
if (item && dropArea.__isHoveringDrop)
root.startDropHoverOver(row)
dropArea.__rowHoveringOver = row
}
onDropped: (drag) => {
let [row, item] = dropArea.__rowAndItem(drag)
if (item) {
root.endDropHover(row)
let dirPath = item.getDirPath()
rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles,
root.assetsRoot.dropComplexExtFiles,
dirPath)
}
dropArea.__isHoveringDrop = false
dropArea.__rowHoveringOver = -1
}
onPositionChanged: (drag) => {
let [row, item] = dropArea.__rowAndItem(drag)
if (dropArea.__rowHoveringOver !== row && dropArea.__rowHoveringOver > -1) {
root.endDropHover(dropArea.__rowHoveringOver)
if (item)
root.startDropHoverOver(row)
}
dropArea.__rowHoveringOver = row
}
onExited: {
if (!dropArea.__isHoveringDrop || dropArea.__rowHoveringOver === -1)
return
root.endDropHover(dropArea.__rowHoveringOver)
dropArea.__isHoveringDrop = false
dropArea.__rowHoveringOver = -1
}
}
delegate: AssetDelegate {
assetsView: root
assetsRoot: root.assetsRoot

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
border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border
MouseArea {
// This mouse area is here to eat clicks that are not handled by menu items
// to prevent them going through to the underlying view.
// This is primarily problem with disabled menu items, but right clicks would go
// through enabled menu items as well.
anchors.fill: parent
acceptedButtons: Qt.AllButtons
}
}
}

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(Qt6 REQUIRED COMPONENTS Core Qml Quick)
# To build this application you need Qt 6.2.0 or higher
if (Qt6_VERSION VERSION_LESS 6.2.0)
message(FATAL_ERROR "You need Qt 6.2.0 or newer to build the application.")
endif()
qt_add_executable(${CMAKE_PROJECT_NAME} src/main.cpp)
# qt_standard_project_setup() requires Qt 6.3 or higher. See https://doc.qt.io/qt-6/qt-standard-project-setup.html for details.

View File

@@ -63,7 +63,7 @@ static const char *qtQuickUISuffix = "ui.qml";
static void maybeAddPath(ViewerContext &context, const Utils::FilePath &path)
{
if (!path.isEmpty() && !(context.paths.count(path) > 0))
if (!path.isEmpty() && (context.paths.count(path) <= 0))
context.paths.insert(path);
}

View File

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

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

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
#include <QCheckBox>
#include <QFileInfo>
#include <QFileSystemModel>
#include <QImageReader>
#include <QMessageBox>
#include <QSortFilterProxyModel>
#include "asset.h"
#include "assetslibrarymodel.h"
#include <modelnodeoperations.h>
@@ -134,32 +134,10 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString &
bool AssetsLibraryModel::addNewFolder(const QString &folderPath)
{
QString iterPath = folderPath;
static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
QDir dir{folderPath};
while (dir.exists()) {
// if the folder name ends with a number, increment it
QRegularExpressionMatch match = rgx.match(iterPath);
if (match.hasMatch()) { // ends with a number
QString numStr = match.captured(0);
int num = match.captured(0).toInt();
// get number of padding zeros, ex: for "005" = 2
int nPaddingZeros = 0;
for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros);
++num;
// if the incremented number's digits increased, decrease the padding zeros
if (std::fmod(std::log10(num), 1.0) == 0)
--nPaddingZeros;
iterPath = folderPath.mid(0, match.capturedStart())
+ QString('0').repeated(nPaddingZeros)
+ QString::number(num);
} else {
iterPath = folderPath + '1';
}
iterPath = getUniqueName(iterPath);
dir.setPath(iterPath);
}
@@ -180,12 +158,49 @@ bool AssetsLibraryModel::deleteFolderRecursively(const QModelIndex &folderIndex)
bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) const
{
return Utils::allOf(filePaths, [](const QString &path) {
const QString suffix = "*." + path.split('.').last().toLower();
return AssetsLibraryModel::supportedImageSuffixes().contains(suffix);
return Asset(path).isImage();
});
}
QString AssetsLibraryModel::getUniqueEffectPath(const QString &parentFolder, const QString &effectName)
{
auto genEffectPath = [=](const QString &name) {
return QString(parentFolder + "/" + name + ".qep");
};
QString uniqueName = effectName;
QString path = genEffectPath(uniqueName);
QFileInfo file{path};
while (file.exists()) {
uniqueName = getUniqueName(uniqueName);
path = genEffectPath(uniqueName);
file.setFile(path);
}
return path;
}
bool AssetsLibraryModel::createNewEffect(const QString &effectPath, bool openEffectMaker)
{
bool created = QFile(effectPath).open(QIODevice::WriteOnly);
if (created && openEffectMaker)
ModelNodeOperations::openEffectMaker(effectPath);
return created;
}
bool AssetsLibraryModel::canCreateEffects() const
{
#ifdef LICENSECHECKER
return checkLicense() == FoundLicense::enterprise;
#else
return true;
#endif
}
bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QString path = m_sourceFsModel->filePath(sourceParent);
@@ -242,6 +257,36 @@ void AssetsLibraryModel::syncHaveFiles()
setHaveFiles(checkHaveFiles());
}
QString AssetsLibraryModel::getUniqueName(const QString &oldName) {
static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
QString uniqueName = oldName;
// if the folder name ends with a number, increment it
QRegularExpressionMatch match = rgx.match(uniqueName);
if (match.hasMatch()) { // ends with a number
QString numStr = match.captured(0);
int num = match.captured(0).toInt();
// get number of padding zeros, ex: for "005" = 2
int nPaddingZeros = 0;
for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros);
++num;
// if the incremented number's digits increased, decrease the padding zeros
if (std::fmod(std::log10(num), 1.0) == 0)
--nPaddingZeros;
uniqueName = oldName.mid(0, match.capturedStart())
+ QString('0').repeated(nPaddingZeros)
+ QString::number(num);
} else {
uniqueName = oldName + '1';
}
return uniqueName;
}
void AssetsLibraryModel::setRootPath(const QString &newPath)
{
beginResetModel();
@@ -252,7 +297,7 @@ void AssetsLibraryModel::setRootPath(const QString &newPath)
m_rootPath = newPath;
m_sourceFsModel->setRootPath(newPath);
m_sourceFsModel->setNameFilters(supportedSuffixes().values());
m_sourceFsModel->setNameFilters(Asset::supportedSuffixes().values());
m_sourceFsModel->setNameFilterDisables(false);
endResetModel();
@@ -327,80 +372,4 @@ QString AssetsLibraryModel::parentDirPath(const QString &path) const
return filePath(parentIdx);
}
const QStringList &AssetsLibraryModel::supportedImageSuffixes()
{
static QStringList retList;
if (retList.isEmpty()) {
const QList<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

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
#pragma once
#include <QFileInfo>
#include <QFileSystemModel>
#include <QSortFilterProxyModel>
#include <QFileInfo>
#include <utils/qtcassert.h>
#include <utils/filesystemwatcher.h>
#include <utils/qtcassert.h>
namespace QmlDesigner {
@@ -47,6 +47,11 @@ public:
Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex);
Q_INVOKABLE bool allFilePathsAreImages(const QStringList &filePaths) const;
Q_INVOKABLE QString getUniqueEffectPath(const QString &parentFolder, const QString &effectName);
Q_INVOKABLE bool createNewEffect(const QString &effectPath, bool openEffectMaker = true);
Q_INVOKABLE bool canCreateEffects() const;
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
int result = QSortFilterProxyModel::columnCount(parent);
@@ -55,16 +60,6 @@ public:
bool haveFiles() const { return m_haveFiles; }
static const QStringList &supportedImageSuffixes();
static const QStringList &supportedFragmentShaderSuffixes();
static const QStringList &supportedShaderSuffixes();
static const QStringList &supportedFontSuffixes();
static const QStringList &supportedAudioSuffixes();
static const QStringList &supportedVideoSuffixes();
static const QStringList &supportedTexture3DSuffixes();
static const QStringList &supportedEffectMakerSuffixes();
static const QSet<QString> &supportedSuffixes();
signals:
void directoryLoaded(const QString &path);
void rootPathChanged();
@@ -79,6 +74,7 @@ private:
void destroyBackendModel();
bool checkHaveFiles(const QModelIndex &parentIdx) const;
bool checkHaveFiles() const;
QString getUniqueName(const QString &oldName);
QString m_searchText;
QString m_rootPath;

View File

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

View File

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

View File

@@ -119,7 +119,8 @@ void addMouseAreaFill(const SelectionContext &selectionContext);
void openSignalDialog(const SelectionContext &selectionContext);
void updateImported3DAsset(const SelectionContext &selectionContext);
QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsDirectory();
QMLDESIGNERCORE_EXPORT Utils::FilePath getEffectsImportDirectory();
QMLDESIGNERCORE_EXPORT QString getEffectsDefaultDirectory(const QString &defaultDir);
void openEffectMaker(const QString &filePath);
QString getEffectIcon(const QString &effectPath);
bool useLayerEffect();

View File

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

View File

@@ -3,12 +3,17 @@
#include "contentlibrarytexture.h"
#include "imageutils.h"
namespace QmlDesigner {
ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QString &path, const QUrl &icon)
: QObject(parent)
, m_path(path)
, m_icon(icon) {}
, m_icon(icon)
{
m_toolTip = QLatin1String("%1\n%2").arg(path.split('/').last(), ImageUtils::imageInfo(path));
}
bool ContentLibraryTexture::filter(const QString &searchText)
{

View File

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

View File

@@ -65,19 +65,27 @@ ModelNode CreateTexture::createTextureFromImage(const QString &assetPath, AddTex
NodeMetaInfo metaInfo = m_view->model()->qtQuick3DTextureMetaInfo();
Utils::FilePath imagePath = ModelNodeOperations::getImagesDefaultDirectory()
.pathAppended(Utils::FilePath::fromString(assetPath).fileName());
QString sourceVal = imagePath.relativePathFrom(
QmlDesigner::DocumentManager::currentFilePath()).toString();
Utils::FilePath currentDocumentPath = QmlDesigner::DocumentManager::currentFilePath();
Utils::FilePath imageTargetPath;
if (m_importFile) {
QString assetName = Utils::FilePath::fromString(assetPath).fileName();
// if the asset had to be imported from somewhere else, then assetPath is the source where
// the asset was taken from, and we have to compute where it was placed in the project.
imageTargetPath = ModelNodeOperations::getImagesDefaultDirectory().pathAppended(assetName);
} else {
imageTargetPath = Utils::FilePath::fromString(assetPath);
}
ModelNode newTexNode = m_view->getTextureDefaultInstance(sourceVal);
QString textureSource = imageTargetPath.relativePathFrom(currentDocumentPath).toString();
ModelNode newTexNode = m_view->getTextureDefaultInstance(textureSource);
if (!newTexNode.isValid()) {
newTexNode = m_view->createModelNode("QtQuick3D.Texture",
metaInfo.majorVersion(),
metaInfo.minorVersion());
newTexNode.validId();
VariantProperty sourceProp = newTexNode.variantProperty("source");
sourceProp.setValue(sourceVal);
sourceProp.setValue(textureSource);
matLib.defaultNodeListProperty().reparentHere(newTexNode);
}

View File

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

View File

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

View File

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

View File

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

View File

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

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)
setColor(view, type, colorConfig.at(0));
else
setVariant(view, type, QVariant::fromValue(colorConfig));
}
static void setColor(AbstractView *view, View3DActionType type, const QColor &color)
{
setVariant(view, type, QVariant::fromValue(color));
setVariant(view, type, QVariant::fromValue(colorConfig));
}
template <typename T>
@@ -47,7 +39,7 @@ public:
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) {
return color.name();
@@ -56,12 +48,7 @@ public:
saveVariant(key, QVariant::fromValue(colorNames));
}
static void saveColor(const QByteArray &key, const QColor &color)
{
saveVariant(key, QVariant::fromValue(color.name()));
}
static bool isColorValid(const QList<QColor> &colorConfig) { return !colorConfig.isEmpty(); }
static bool colorsValid(const QList<QColor> &colorConfig) { return !colorConfig.isEmpty(); }
private:
static void setVariant(AbstractView *view, View3DActionType type, const QVariant &colorConfig)

View File

@@ -232,6 +232,14 @@ void Edit3DWidget::createContextMenu()
view()->setSelectedModelNode(parentNode);
});
QAction *defaultToggleGroupAction = view()->edit3DAction(View3DActionType::SelectionModeToggle)->action();
m_toggleGroupAction = m_contextMenu->addAction(tr("Group Selection Mode"), [&](const bool &mode) {
view()->edit3DAction(View3DActionType::SelectionModeToggle)->action()->trigger();
});
connect(defaultToggleGroupAction, &QAction::toggled, m_toggleGroupAction, &QAction::setChecked);
m_toggleGroupAction->setCheckable(true);
m_toggleGroupAction->setChecked(defaultToggleGroupAction->isChecked());
m_contextMenu->addSeparator();
}
@@ -375,6 +383,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
m_alignCameraAction->setEnabled(isCamera);
m_alignViewAction->setEnabled(isCamera);
m_selectParentAction->setEnabled(selectionExcludingRoot);
m_toggleGroupAction->setEnabled(true);
m_contextMenu->popup(mapToGlobal(pos));
}

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -5,6 +5,8 @@
#include "qmldesignercorelib_global.h"
#include "utils/filepath.h"
#include <QString>
#include <QHash>
#include <modelnode.h>
@@ -26,7 +28,12 @@ class QMLDESIGNERCORE_EXPORT StylesheetMerger
public:
StylesheetMerger(AbstractView*, AbstractView*);
void merge();
static void styleMerge(Model *model, const QString &templateFile, class ExternalDependenciesInterface &externalDependencies);
static void styleMerge(const Utils::FilePath &templateFile,
Model *model,
class ExternalDependenciesInterface &ed);
static void styleMerge(const QString &qmlTemplateString,
Model *model,
class ExternalDependenciesInterface &externalDependencies);
private:
void preprocessStyleSheet();

View File

@@ -25,6 +25,7 @@
#include "createscenecommand.h"
#include "debugoutputcommand.h"
#include "informationchangedcommand.h"
#include "imageutils.h"
#include "inputeventcommand.h"
#include "nodeabstractproperty.h"
#include "nodeinstanceserverproxy.h"
@@ -83,6 +84,8 @@
#include <QDirIterator>
#include <QFileSystemWatcher>
#include <QImageReader>
#include <QLocale>
#include <QMultiHash>
#include <QPainter>
#include <QPicture>
@@ -135,12 +138,13 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
// related to a single event to be received before we act.
m_resetTimer.setSingleShot(true);
m_resetTimer.setInterval(100);
QObject::connect(&m_resetTimer, &QTimer::timeout, [this] {
resetPuppet();
QObject::connect(&m_resetTimer, &QTimer::timeout, this, [this] {
if (isAttached())
resetPuppet();
});
m_updateWatcherTimer.setSingleShot(true);
m_updateWatcherTimer.setInterval(100);
QObject::connect(&m_updateWatcherTimer, &QTimer::timeout, [this] {
QObject::connect(&m_updateWatcherTimer, &QTimer::timeout, this, [this] {
for (const auto &path : std::as_const(m_pendingUpdateDirs))
updateWatcher(path);
m_pendingUpdateDirs.clear();
@@ -151,11 +155,11 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
// unnecessary generation when project with multiple shaders is opened.
m_generateQsbFilesTimer.setSingleShot(true);
m_generateQsbFilesTimer.setInterval(100);
QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, [this] {
QObject::connect(&m_generateQsbFilesTimer, &QTimer::timeout, this, [this] {
handleShaderChanges();
});
connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged,
connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this,
[this](const QString &path) {
const QSet<QString> pendingDirs = m_pendingUpdateDirs;
for (const auto &pendingPath : pendingDirs) {
@@ -171,7 +175,7 @@ NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager
m_updateWatcherTimer.start();
});
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, [this](const QString &path) {
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &path) {
if (m_qsbTargets.contains(path)) {
m_qsbTargets.insert(path, true);
m_generateQsbFilesTimer.start();
@@ -1909,23 +1913,7 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo
imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio);
imageData.pixmap.setDevicePixelRatio(ratio);
imageData.time = modified;
double imgSize = double(imageFi.size());
static QStringList units({::QmlDesigner::NodeInstanceView::tr("B"),
::QmlDesigner::NodeInstanceView::tr("KB"),
::QmlDesigner::NodeInstanceView::tr("MB"),
::QmlDesigner::NodeInstanceView::tr("GB")});
int unitIndex = 0;
while (imgSize > 1024. && unitIndex < units.size() - 1) {
++unitIndex;
imgSize /= 1024.;
}
imageData.info = QStringLiteral("%1 x %2\n%3%4 (%5)")
.arg(originalPixmap.width())
.arg(originalPixmap.height())
.arg(QString::number(imgSize, 'g', 3))
.arg(units[unitIndex])
.arg(imageFi.suffix());
imageData.info = ImageUtils::imageInfo(imageSource);
m_imageDataMap.insert(imageData.id, imageData);
}
}

View File

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

View File

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

View File

@@ -559,6 +559,7 @@ void AbstractView::resetView()
void AbstractView::resetPuppet()
{
QTC_ASSERT(isAttached(), return);
emitCustomNotification(QStringLiteral("reset QmlPuppet"));
}
@@ -689,7 +690,8 @@ void AbstractView::emitCustomNotification(const QString &identifier, const QList
void AbstractView::emitCustomNotification(const QString &identifier, const QList<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)

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;
QTC_ASSERT(parentModel, return);
QTC_ASSERT(parentModel, return );
auto templateModel(Model::create("QtQuick.Item", 2, 1, parentModel));
Q_ASSERT(templateModel.get());
@@ -527,10 +540,6 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext
templateModel->setFileUrl(parentModel->fileUrl());
QPlainTextEdit textEditTemplate;
Utils::FileReader reader;
QTC_ASSERT(reader.fetch(Utils::FilePath::fromString(templateFile)), return);
QString qmlTemplateString = QString::fromUtf8(reader.data());
QString imports;
for (const Import &import : parentModel->imports()) {
@@ -541,13 +550,14 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext
textEditTemplate.setPlainText(imports + qmlTemplateString);
NotIndentingTextEditModifier textModifierTemplate(&textEditTemplate);
QScopedPointer<RewriterView> templateRewriterView(new RewriterView(externalDependencies, RewriterView::Amend));
QScopedPointer<RewriterView> templateRewriterView(
new RewriterView(externalDependencies, RewriterView::Amend));
templateRewriterView->setTextModifier(&textModifierTemplate);
templateModel->attachView(templateRewriterView.data());
templateRewriterView->setCheckSemanticErrors(false);
ModelNode templateRootNode = templateRewriterView->rootModelNode();
QTC_ASSERT(templateRootNode.isValid(), return);
QTC_ASSERT(templateRootNode.isValid(), return );
auto styleModel(Model::create("QtQuick.Item", 2, 1, parentModel));
Q_ASSERT(styleModel.get());
@@ -556,11 +566,12 @@ void StylesheetMerger::styleMerge(Model *model, const QString &templateFile, Ext
QPlainTextEdit textEditStyle;
RewriterView *parentRewriterView = parentModel->rewriterView();
QTC_ASSERT(parentRewriterView, return);
QTC_ASSERT(parentRewriterView, return );
textEditStyle.setPlainText(parentRewriterView->textModifierContent());
NotIndentingTextEditModifier textModifierStyle(&textEditStyle);
QScopedPointer<RewriterView> styleRewriterView(new RewriterView(externalDependencies, RewriterView::Amend));
QScopedPointer<RewriterView> styleRewriterView(
new RewriterView(externalDependencies, RewriterView::Amend));
styleRewriterView->setTextModifier(&textModifierStyle);
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);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::UserVerifiedRedirectPolicy);
QNetworkReply *reply = Utils::NetworkAccessManager::instance()->get(request);
m_reply = Utils::NetworkAccessManager::instance()->get(request);
QNetworkReply::connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
m_tempFile.write(reply->readAll());
QNetworkReply::connect(m_reply, &QNetworkReply::readyRead, this, [this]() {
m_tempFile.write(m_reply->readAll());
});
QNetworkReply::connect(reply,
QNetworkReply::connect(m_reply,
&QNetworkReply::downloadProgress,
this,
[this](qint64 current, qint64 max) {
@@ -92,16 +92,21 @@ void FileDownloader::start()
emit progressChanged();
});
QNetworkReply::connect(reply, &QNetworkReply::redirected, [reply](const QUrl &) {
emit reply->redirectAllowed();
QNetworkReply::connect(m_reply, &QNetworkReply::redirected, [this](const QUrl &) {
emit m_reply->redirectAllowed();
});
QNetworkReply::connect(reply, &QNetworkReply::finished, this, [this, reply]() {
if (reply->error()) {
QNetworkReply::connect(m_reply, &QNetworkReply::finished, this, [this]() {
if (m_reply->error()) {
if (m_tempFile.exists())
m_tempFile.remove();
qWarning() << Q_FUNC_INFO << m_url << reply->errorString();
emit downloadFailed();
if (m_reply->error() != QNetworkReply::OperationCanceledError) {
qWarning() << Q_FUNC_INFO << m_url << m_reply->errorString();
emit downloadFailed();
} else {
emit downloadCanceled();
}
} else {
m_tempFile.flush();
m_tempFile.close();
@@ -109,9 +114,17 @@ void FileDownloader::start()
emit tempFileChanged();
emit finishedChanged();
}
m_reply = nullptr;
});
}
void FileDownloader::cancel()
{
if (m_reply)
m_reply->abort();
}
void FileDownloader::setUrl(const QUrl &url)
{
m_url = url;

View File

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

View File

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

View File

@@ -23,6 +23,8 @@ if (NOT QT_CREATOR_API_DEFINED)
)
endif()
configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES)
if (NOT TARGET QmlPuppetCommunication)
include(../../libs/qmlpuppetcommunication/QmlPuppetCommunication.cmake)
endif()
@@ -34,13 +36,34 @@ add_qtc_executable(qml2puppet
Qt5::CorePrivate Qt5::Widgets Qt5::QmlPrivate
Qt5::QuickPrivate Qt5::Network Qt5::GuiPrivate
QmlPuppetCommunication
INCLUDES
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
SOURCES
qml2puppet/qml2puppetmain.cpp
qml2puppet/main.cpp
qml2puppet/qmlbase.h qml2puppet/appmetadata.h
qml2puppet/qmlpuppet.h qml2puppet/qmlpuppet.cpp qml2puppet/configcrashpad.h
qmlpuppet.qrc
PROPERTIES
OUTPUT_NAME qml2puppet-${IDE_VERSION}
)
if(TARGET qml2puppet)
execute_process(
COMMAND git describe --tags --always --dirty=+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE GIT_SHA_RESULT
OUTPUT_VARIABLE GIT_SHA_OUTPUT
ERROR_VARIABLE GIT_SHA_ERROR
)
#if we are not a git repository use the .tag file
if(NOT GIT_SHA_OUTPUT)
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../../.tag GIT_SHA_OUTPUT)
endif()
add_definitions( -D GIT_SHA=${GIT_SHA_OUTPUT} )
endif()
extend_qtc_executable(qml2puppet
CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0
SOURCES
@@ -191,10 +214,27 @@ extend_qtc_executable(qml2puppet
DEPENDS Nanotrace
)
if (QTC_STATIC_BUILD AND Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0)
qt6_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR})
if (Qt5_VERSION VERSION_GREATER_EQUAL 6.4.0)
extend_qtc_executable(qml2puppet
DEFINES ENABLE_INTERNAL_QML_RUNTIME
PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/qml2puppet/runner
SOURCES_PREFIX qml2puppet/runner
SOURCES
qmlruntime.h qmlruntime.cpp
qmlconfiguration.h loadwatcher.h
)
# Turn the tool into its own self-contained qml module
qt_add_qml_module(qml2puppet
URI QmlRuntime.QmlConfiguration
VERSION 1.0
)
if (QTC_STATIC_BUILD)
qt_import_qml_plugins(qml2puppet PATH_TO_SCAN ${SRCDIR})
endif()
endif()
# Crashpad
# only windows requires separate crashpad client per process until client->SetHandlerIPCPipe()
# is implemented (check the TODO inside startCrashpad())

View File

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

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;
}
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)
{
m_activeStateInstance = stateInstance;

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,7 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
if (quickWindow() && nodeInstanceClient()->bytesToWrite() < 10000) {
bool windowDirty = false;
bool hasView3D = false;
for (QQuickItem *item : allItems()) {
if (item) {
if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
@@ -65,8 +66,13 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
}
} else {
if (hasInstanceForObject(item)) {
if (QQuickDesignerSupport::isDirty(item, QQuickDesignerSupport::ContentUpdateMask))
if (QQuickDesignerSupport::isDirty(item, QQuickDesignerSupport::ContentUpdateMask)) {
if (!hasView3D && ServerNodeInstance::isSubclassOf(
item, QByteArrayLiteral("QQuick3DViewport"))) {
hasView3D = true;
}
m_dirtyInstanceSet.insert(instanceForObject(item));
}
if (QQuickItem *effectParent = parentEffectItem(item)) {
if ((QQuickDesignerSupport::isDirty(
item,
@@ -100,9 +106,19 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands()
nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()}));
} else {
if (!m_dirtyInstanceSet.isEmpty()) {
nodeInstanceClient()->pixmapChanged(
createPixmapChangedCommand(QtHelpers::toList(m_dirtyInstanceSet)));
m_dirtyInstanceSet.clear();
auto renderList = QtHelpers::toList(m_dirtyInstanceSet);
// If there is a View3D to be rendered, add all other View3Ds to be rendered
// as well, in case they share materials.
if (hasView3D) {
const QList<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/ToggleButton.qml</file>
</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>

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