QmlDesigner: Allow inserting SVG snippets

Allows to paste SVG clipboard content into the Form Editor. It will
create a Group item which acts as the SVGs view box and groups multiple
items together. All SVG items will be transformed into SvgPathItem.

* Supports all basic SVG shapes path, rect, polygon, circle, ellipse
* Supports the following SVG presentation attributes as CSS-inline
  definition, XML-attribute, style element: fill, stroke, stroke-width,
  opacity, fill-opacity, stroke-opacity
* Supports all transform operations

Task-number: QDS-5259
Change-Id: I9b7027992de60e5c87f2031251348dbb31fe03fe
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2021-10-11 09:46:21 +02:00
committed by Thomas Hartmann
parent dbd5a4fe1b
commit 14c4f257fe
8 changed files with 1342 additions and 5 deletions

View File

@@ -7,7 +7,7 @@ add_qtc_plugin(QmlDesigner
CONDITION TARGET Qt5::QuickWidgets
DEPENDS
QmlJS LanguageUtils QmlEditorWidgets AdvancedDockingSystem
Qt5::QuickWidgets Qt5::CorePrivate Sqlite
Qt5::QuickWidgets Qt5::CorePrivate Sqlite Qt5::Xml Qt5::Svg
DEFINES
DESIGNER_CORE_LIBRARY
IDE_LIBRARY_BASENAME=\"${IDE_LIBRARY_BASE_PATH}\"
@@ -139,6 +139,7 @@ extend_qtc_plugin(QmlDesigner
theme.cpp theme.h
zoomaction.cpp zoomaction.h
hdrimage.cpp hdrimage.h
svgpasteaction.cpp svgpasteaction.h
)
extend_qtc_plugin(QmlDesigner

View File

@@ -20,6 +20,7 @@ SOURCES += crumblebar.cpp
SOURCES += qmldesignericonprovider.cpp
SOURCES += zoomaction.cpp
SOURCES += hdrimage.cpp
SOURCES += svgpasteaction.cpp
HEADERS += modelnodecontextmenu.h
HEADERS += addimagesdialog.h
@@ -43,6 +44,7 @@ HEADERS += crumblebar.h
HEADERS += qmldesignericonprovider.h
HEADERS += zoomaction.h
HEADERS += hdrimage.h
HEADERS += svgpasteaction.h
FORMS += \
$$PWD/addsignalhandlerdialog.ui

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <qmlitemnode.h>
#include <QDomDocument>
namespace QmlDesigner {
struct CSSProperty
{
QString directive;
QString value;
};
using CSSRule = std::vector<CSSProperty>;
using CSSRules = QHash<QString, CSSRule>;
using PropertyMap = QHash<QByteArray, QVariant>;
class SVGPasteAction
{
public:
SVGPasteAction();
bool containsSVG(const QString &str);
QmlObjectNode createQmlObjectNode(QmlDesigner::ModelNode &targetNode);
private:
QDomDocument m_domDocument;
};
} // namespace QmlDesigner

View File

@@ -51,9 +51,12 @@
#include <coreplugin/editormanager/editormanager.h>
#include <utils/algorithm.h>
#include <timelineactions.h>
#include <svgpasteaction.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/qtcassert.h>
#include <QFileInfo>
#include <QUrl>
#include <QDebug>
@@ -62,6 +65,7 @@
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QRandomGenerator>
#include <QClipboard>
using namespace ProjectExplorer;
@@ -171,6 +175,41 @@ Model* DesignDocument::createInFileComponentModel()
return model;
}
bool DesignDocument::pasteSVG()
{
SVGPasteAction svgPasteAction;
if (!svgPasteAction.containsSVG(QApplication::clipboard()->text()))
return false;
rewriterView()->executeInTransaction("DesignDocument::paste1", [&]() {
ModelNode targetNode;
// If nodes are currently selected make the first node in selection the target
if (!view()->selectedModelNodes().isEmpty())
targetNode = view()->firstSelectedModelNode();
// If target is still invalid make the root node the target
if (!targetNode.isValid())
targetNode = view()->rootModelNode();
// Check if document has studio components import, if not create it
QmlDesigner::Import import = QmlDesigner::Import::createLibraryImport("QtQuick.Studio.Components", "1.0");
if (!currentModel()->hasImport(import, true, true)) {
QmlDesigner::Import studioComponentsImport = QmlDesigner::Import::createLibraryImport("QtQuick.Studio.Components", "1.0");
try {
currentModel()->changeImports({studioComponentsImport}, {});
} catch (const QmlDesigner::Exception &) {
QTC_ASSERT(false, return);
}
}
svgPasteAction.createQmlObjectNode(targetNode);
});
return true;
}
QList<DocumentMessage> DesignDocument::qmlParseWarnings() const
{
return m_rewriterView->warnings();
@@ -495,6 +534,9 @@ static void scatterItem(const ModelNode &pastedNode, const ModelNode &targetNode
void DesignDocument::paste()
{
if (pasteSVG())
return;
if (TimelineActions::clipboardContainsKeyframes()) // pasting keyframes is handled in TimelineView
return;
@@ -518,7 +560,7 @@ void DesignDocument::paste()
ModelNode targetNode;
if (!view.selectedModelNodes().isEmpty())
targetNode = view.selectedModelNodes().constFirst();
targetNode = view.firstSelectedModelNode();
// in case we copy and paste a selection we paste in the parent item
if ((view.selectedModelNodes().count() == selectedNodes.count()) && targetNode.isValid() && targetNode.hasParentProperty()) {
@@ -572,7 +614,7 @@ void DesignDocument::paste()
ModelNode targetNode;
if (!view.selectedModelNodes().isEmpty()) {
targetNode = view.selectedModelNodes().constFirst();
targetNode = view.firstSelectedModelNode();
} else {
// if selection is empty and this is a 3D Node, paste it under the active scene
if (pastedNode.isSubclassOf("QtQuick3D.Node")) {

View File

@@ -145,6 +145,8 @@ private: // functions
Model *createInFileComponentModel();
bool pasteSVG();
private: // variables
QScopedPointer<Model> m_documentModel;
QScopedPointer<Model> m_inFileComponentModel;

View File

@@ -1,4 +1,4 @@
QT += quickwidgets core-private
QT += quickwidgets core-private xml svg
CONFIG += exceptions
INCLUDEPATH += $$PWD

View File

@@ -10,7 +10,7 @@ Project {
Depends {
name: "Qt";
submodules: [
"core-private", "quickwidgets"
"core-private", "quickwidgets", "xml", "svg"
]
}
Depends { name: "AdvancedDockingSystem" }
@@ -482,6 +482,8 @@ Project {
"componentcore/zoomaction.h",
"componentcore/hdrimage.cpp",
"componentcore/hdrimage.h",
"componentcore/svgpasteaction.cpp",
"componentcore/svgpasteaction.h",
"texteditor/texteditorstatusbar.cpp",
"texteditor/texteditorstatusbar.h",
"componentcore/changestyleaction.cpp",