QmlDesigner: Add button to component specifics

* Add a button to component specifics for simpler access to the
  component. Currently it is only possible to access a component via
  the context menu "Go into Component" or shortcut F2.
* Add image template
* Fix other templates
* Cleanup component specifics and introduce nested sections

Task-number: QDS-3062
Task-number: QDS-2358
Change-Id: I6b245b013fbf4b960509b0a357ae62d20e0383cc
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2020-11-05 16:11:13 +01:00
committed by Henning Gründl
parent 50405d7153
commit 6a1f51b195
12 changed files with 514 additions and 132 deletions

View File

@@ -1,11 +1,14 @@
// Dummy comment to consume the first argument and suppress warnings %1
Section {
anchors.left: parent.left
anchors.right: parent.right
caption: "%1"
caption: qsTr("Color")
expanded: false
level: 2
ColorEditor {
ColorEditor {
backendValue: backendValues.%2
supportGradient: false
}
}
}

View File

@@ -1,7 +1,10 @@
// Dummy comment to consume the first argument and suppress warnings %1
FontSection {
anchors.left: parent.left
anchors.right: parent.right
caption: "%1"
caption: qsTr("Font")
fontName: "%2"
expanded: false
level: 2
}

View File

@@ -0,0 +1,215 @@
// Dummy comment to consume the first argument and suppress warnings %1
Section {
anchors.left: parent.left
anchors.right: parent.right
caption: qsTr("Image")
expanded: false
level: 2
SectionLayout {
Label {
text: qsTr("Source")
}
SecondColumnLayout {
UrlChooser {
Layout.fillWidth: true
backendValue: backendValues.%2_source
}
ExpandingSpacer {
}
}
Label {
text: qsTr("Fill mode")
}
SecondColumnLayout {
ComboBox {
scope: "Image"
model: ["Stretch", "PreserveAspectFit", "PreserveAspectCrop", "Tile", "TileVertically", "TileHorizontally", "Pad"]
backendValue: backendValues.%2_fillMode
implicitWidth: 180
Layout.fillWidth: true
}
ExpandingSpacer {
}
}
Label {
text: qsTr("Source size")
disabledState: !backendValues.%2_sourceSize.isAvailable
}
SecondColumnLayout {
Label {
text: "W"
width: 12
disabledStateSoft: !backendValues.%2_sourceSize_width.isAvailable
}
SpinBox {
backendValue: backendValues.%2_sourceSize_width
minimumValue: 0
maximumValue: 8192
decimals: 0
enabled: backendValue.isAvailable
}
Item {
width: 4
height: 4
}
Label {
text: "H"
width: 12
disabledStateSoft: !backendValues.%2_sourceSize_height.isAvailable
}
SpinBox {
backendValue: backendValues.%2_sourceSize_height
minimumValue: 0
maximumValue: 8192
decimals: 0
enabled: backendValue.isAvailable
}
ExpandingSpacer {
}
}
Label {
text: qsTr("Horizontal alignment")
}
SecondColumnLayout {
ComboBox {
scope: "Image"
model: ["AlignLeft", "AlignRight", "AlignHCenter"]
backendValue: backendValues.%2_horizontalAlignment
implicitWidth: 180
Layout.fillWidth: true
}
ExpandingSpacer {
}
}
Label {
text: qsTr("Vertical alignment")
}
SecondColumnLayout {
ComboBox {
scope: "Image"
model: ["AlignTop", "AlignBottom", "AlignVCenter"]
backendValue: backendValues.%2_verticalAlignment
implicitWidth: 180
Layout.fillWidth: true
}
ExpandingSpacer {
}
}
Label {
text: qsTr("Asynchronous")
tooltip: qsTr("Loads images on the local filesystem asynchronously in a separate thread.")
disabledState: !backendValues.%2_asynchronous.isAvailable
}
SecondColumnLayout {
CheckBox {
enabled: backendValues.%2_asynchronous.isAvailable
text: backendValues.%2_asynchronous.valueToString
backendValue: backendValues.%2_asynchronous
implicitWidth: 180
}
ExpandingSpacer {}
}
Label {
text: qsTr("Auto transform")
tooltip: qsTr("Automatically applies image transformation metadata such as EXIF orientation.")
disabledState: !backendValues.%2_autoTransform.isAvailable
}
SecondColumnLayout {
CheckBox {
enabled: backendValues.%2_autoTransform.isAvailable
text: backendValues.%2_autoTransform.valueToString
backendValue: backendValues.%2_autoTransform
implicitWidth: 180
}
ExpandingSpacer {}
}
Label {
text: qsTr("Cache")
tooltip: qsTr("Caches the image.")
disabledState: !backendValues.%2_cache.isAvailable
}
SecondColumnLayout {
CheckBox {
enabled: backendValues.%2_cache.isAvailable
text: backendValues.%2_cache.valueToString
backendValue: backendValues.%2_cache
implicitWidth: 180
}
ExpandingSpacer {}
}
Label {
text: qsTr("Mipmap")
tooltip: qsTr("Uses mipmap filtering when the image is scaled or transformed.")
disabledState: !backendValues.%2_mipmap.isAvailable
}
SecondColumnLayout {
CheckBox {
enabled: backendValues.%2_mipmap.isAvailable
text: backendValues.%2_mipmap.valueToString
backendValue: backendValues.%2_mipmap
implicitWidth: 180
}
ExpandingSpacer {}
}
Label {
text: qsTr("Mirror")
tooltip: qsTr("Inverts the image horizontally.")
disabledState: !backendValues.%2_mirror.isAvailable
}
SecondColumnLayout {
CheckBox {
enabled: backendValues.%2_mirror.isAvailable
text: backendValues.%2_mirror.valueToString
backendValue: backendValues.%2_mirror
implicitWidth: 180
}
ExpandingSpacer {}
}
Label {
text: qsTr("Smooth")
tooltip: qsTr("Smoothly filters the image when it is scaled or transformed.")
disabledState: !backendValues.%2_smooth.isAvailable
}
SecondColumnLayout {
CheckBox {
enabled: backendValues.%2_smooth.isAvailable
text: backendValues.%2_smooth.valueToString
backendValue: backendValues.%2_smooth
implicitWidth: 180
}
ExpandingSpacer {}
}
}
}

View File

@@ -1,21 +1,25 @@
// Dummy comment to consume the first argument and suppress warnings %1
Section {
anchors.left: parent.left
anchors.right: parent.right
caption: qsTr("%2 Color")
caption: qsTr("Color")
expanded: false
level: 2
ColorEditor {
caption: qsTr("Color")
backendValue: backendValues.%2_color
supportGradient: true
}
}
Section {
anchors.left: parent.left
anchors.right: parent.right
caption: qsTr("%2 Border Color")
caption: qsTr("Border Color")
expanded: false
level: 2
ColorEditor {
caption: qsTr("Border Color")
@@ -24,11 +28,12 @@ Section {
}
}
Section {
anchors.left: parent.left
anchors.right: parent.right
caption: "%2 Rectangle"
caption: qsTr("Rectangle")
expanded: false
level: 2
SectionLayout {
rows: 2

View File

@@ -24,7 +24,7 @@
****************************************************************************/
AutoTypes {
imports: [ "import HelperWidgets 2.0", "import QtQuick 2.1", "import QtQuick.Layouts 1.1" ]
imports: [ "import HelperWidgets 2.0", "import QtQuick 2.15", "import QtQuick.Layouts 1.15" ]
Type {
typeNames: ["int"]
@@ -71,4 +71,9 @@ AutoTypes {
separateSection: true
}
Type {
typeNames: ["Image"]
sourceFile: "ImageEditorTemplate.template"
separateSection: true
}
}

View File

@@ -1,7 +1,9 @@
Section {
anchors.left: parent.left
anchors.right: parent.right
caption: "%1"
caption: qsTr("Text")
expanded: false
level: 2
SectionLayout {
columns: 2

View File

@@ -0,0 +1,52 @@
/****************************************************************************
**
** Copyright (C) 2020 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.
**
****************************************************************************/
import QtQuick 2.15
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
Column {
id: column
width: parent.width
spacing: 10
padding: 10
Label {
text: qsTr("This item is an instance of a Component")
anchors.horizontalCenter: parent.horizontalCenter
width: 220
}
StudioControls.AbstractButton {
id: testtest
anchors.horizontalCenter: parent.horizontalCenter
width: 180
buttonIcon: qsTr("Edit Master Component")
iconFont: StudioTheme.Constants.font
onClicked: goIntoComponent()
}
}

View File

@@ -23,9 +23,9 @@
**
****************************************************************************/
import QtQuick 2.1
import QtQuick 2.15
import QtQuick.Controls 2.12 as Controls
import QtQuick.Layouts 1.0
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import StudioTheme 1.0 as StudioTheme
@@ -35,28 +35,22 @@ Item {
property int leftPadding: 8
property int topPadding: 4
property int rightPadding: 0
property int bottomPadding: 4
property int animationDuration: 0
property bool expanded: true
property int level: 0
property int levelShift: 10
clip: true
Rectangle {
id: header
height: 20
anchors.left: parent.left
anchors.right: parent.right
Controls.Label {
id: label
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
x: 22
font.bold: true
font.pixelSize: StudioTheme.Values.myFontSize
}
color: Qt.lighter(StudioTheme.Values.themeSectionHeadBackground, 1.0 + (0.2 * level))
Image {
id: arrow
@@ -64,35 +58,27 @@ Item {
height: 4
source: "image://icons/down-arrow"
anchors.left: parent.left
anchors.leftMargin: 4
anchors.leftMargin: 4 + (level * levelShift)
anchors.verticalCenter: parent.verticalCenter
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: animationDuration
duration: section.animationDuration
}
}
}
}
color: StudioTheme.Values.themeSectionHeadBackground
Rectangle {
visible: false
color:"#333"
width: parent.width
height: 1
}
Rectangle {
visible: false
color: "#333"
anchors.bottom: parent.bottom
width: parent.width
height: 1
Controls.Label {
id: label
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
x: 22 + (level * levelShift)
font.bold: true
font.pixelSize: StudioTheme.Values.myFontSize
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: {
section.animationDuration = 120
@@ -105,22 +91,23 @@ Item {
readonly property alias contentItem: row
implicitHeight: Math.round(row.height + header.height + 8)
implicitHeight: Math.round(row.height + header.height
+ section.topPadding + section.bottomPadding)
Row {
anchors.left: parent.left
anchors.leftMargin: leftPadding
anchors.right: parent.right
anchors.rightMargin: rightPadding
anchors.top: header.bottom
anchors.topMargin: topPadding
id: row
anchors.left: parent.left
anchors.leftMargin: section.leftPadding
anchors.right: parent.right
anchors.rightMargin: section.rightPadding
anchors.top: header.bottom
anchors.topMargin: section.topPadding
}
Behavior on implicitHeight {
NumberAnimation {
easing.type: Easing.OutCubic
duration: animationDuration
duration: section.animationDuration
}
}

View File

@@ -14,6 +14,7 @@ ColorEditor 2.0 ColorEditor.qml
ColorLine 2.0 ColorLine.qml
ColorLogic 2.0 ColorLogic.qml
ComboBox 2.0 ComboBox.qml
ComponentButton 2.0 ComponentButton.qml
EditableListView 2.0 EditableListView.qml
ExpandingSpacer 2.0 ExpandingSpacer.qml
ExtendedFunctionLogic 2.0 ExtendedFunctionLogic.qml

View File

@@ -180,6 +180,21 @@ void PropertyEditorContextObject::toogleExportAlias()
}
}
void PropertyEditorContextObject::goIntoComponent()
{
QTC_ASSERT(m_model && m_model->rewriterView(), return);
/* Ideally we should not missuse the rewriterView
* If we add more code here we have to forward the property editor view */
RewriterView *rewriterView = m_model->rewriterView();
QTC_ASSERT(!rewriterView->selectedModelNodes().isEmpty(), return);
const ModelNode selectedNode = rewriterView->selectedModelNodes().constFirst();
DocumentManager::goIntoComponent(selectedNode);
}
void PropertyEditorContextObject::changeTypeName(const QString &typeName)
{
QTC_ASSERT(m_model && m_model->rewriterView(), return);

View File

@@ -86,6 +86,8 @@ public:
Q_INVOKABLE void toogleExportAlias();
Q_INVOKABLE void goIntoComponent();
Q_INVOKABLE void changeTypeName(const QString &typeName);
Q_INVOKABLE void insertKeyframe(const QString &propertyName);

View File

@@ -542,7 +542,7 @@ inline bool dotPropertyHeuristic(const QmlObjectNode &node, const NodeMetaInfo &
if (name.count('.') > 1)
return false;
QList<QByteArray> list =name.split('.');
QList<QByteArray> list = name.split('.');
const PropertyName parentProperty = list.first();
const PropertyName itemProperty = list.last();
@@ -551,17 +551,13 @@ inline bool dotPropertyHeuristic(const QmlObjectNode &node, const NodeMetaInfo &
NodeMetaInfo itemInfo = node.view()->model()->metaInfo("QtQuick.Item");
NodeMetaInfo textInfo = node.view()->model()->metaInfo("QtQuick.Text");
NodeMetaInfo rectangleInfo = node.view()->model()->metaInfo("QtQuick.Rectangle");
NodeMetaInfo imageInfo = node.view()->model()->metaInfo("QtQuick.Image");
if (itemInfo.hasProperty(itemProperty))
return false;
if (typeName == "font")
return false;
if (textInfo.isSubclassOf(typeName))
return false;
if (rectangleInfo.isSubclassOf(typeName))
if (typeName == "font"
|| itemInfo.hasProperty(itemProperty)
|| textInfo.isSubclassOf(typeName)
|| rectangleInfo.isSubclassOf(typeName)
|| imageInfo.isSubclassOf(typeName))
return false;
return true;
@@ -576,92 +572,188 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type,
const auto nodes = templateConfiguration()->children();
QStringList sectorTypes;
QStringList allTypes; // all template types
QStringList separateSectionTypes; // separate section types only
for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) {
if (node->propertyNames().contains("separateSection"))
sectorTypes.append(variantToStringList(node->property("typeNames")));
separateSectionTypes.append(variantToStringList(node->property("typeNames")));
allTypes.append(variantToStringList(node->property("typeNames")));
}
QStringList imports = variantToStringList(templateConfiguration()->property(QStringLiteral("imports")));
const QList<PropertyName> allProperties = type.propertyNames();
QString qmlTemplate = imports.join(QLatin1Char('\n')) + QLatin1Char('\n');
QMap<PropertyName, QList<PropertyName>> propertyMap;
QList<PropertyName> separateSectionProperties;
qmlTemplate += "Column {\n";
qmlTemplate += "anchors.left: parent.left\n";
qmlTemplate += "anchors.right: parent.right\n";
// Iterate over all properties and isolate the properties which have their own template
for (const PropertyName &propertyName : allProperties) {
if (propertyName.startsWith("__"))
continue; // private API
QList<PropertyName> orderedList = type.propertyNames();
Utils::sort(orderedList, [type, &sectorTypes](const PropertyName &left, const PropertyName &right){
const QString typeNameLeft = QString::fromLatin1(type.propertyTypeName(left));
const QString typeNameRight = QString::fromLatin1(type.propertyTypeName(right));
if (typeNameLeft == typeNameRight)
return left > right;
if (!superType.hasProperty(propertyName)
&& type.propertyIsWritable(propertyName)
&& dotPropertyHeuristic(node, type, propertyName)) {
const QString typeName = QString::fromLatin1(type.propertyTypeName(propertyName));
if (sectorTypes.contains(typeNameLeft)) {
if (sectorTypes.contains(typeNameRight))
return left > right;
return true;
} else if (sectorTypes.contains(typeNameRight)) {
return false;
// Check if a template for the type exists
if (allTypes.contains(typeName)) {
if (separateSectionTypes.contains(typeName)) { // template enforces separate section
separateSectionProperties.append(propertyName);
} else {
if (propertyName.contains('.')) {
const PropertyName parentProperty = propertyName.split('.').first();
if (propertyMap.contains(parentProperty))
propertyMap[parentProperty].append(propertyName);
else
propertyMap[parentProperty] = { propertyName };
} else {
if (!propertyMap.contains(propertyName))
propertyMap[propertyName] = {};
}
}
}
}
}
return left > right;
});
bool emptyTemplate = true;
// Filter out the properties which have a basic type e.g. int, string, bool
QList<PropertyName> basicProperties;
for (auto k : propertyMap.keys()) {
if (propertyMap.value(k).empty()) {
basicProperties.append(k);
propertyMap.remove(k);
}
}
bool sectionStarted = false;
Utils::sort(basicProperties);
foreach (const PropertyName &name, orderedList) {
auto findAndFillTemplate = [&nodes, &node, &type](const PropertyName &label,
const PropertyName &property) {
PropertyName underscoreProperty = property;
underscoreProperty.replace('.', '_');
if (name.startsWith("__"))
continue; //private API
PropertyName properName = name;
properName.replace('.', '_');
TypeName typeName = type.propertyTypeName(name);
//alias resolution only possible with instance
TypeName typeName = type.propertyTypeName(property);
// alias resolution only possible with instance
if (typeName == "alias" && node.isValid())
typeName = node.instanceType(name);
typeName = node.instanceType(property);
auto nodes = templateConfiguration()->children();
if (!superType.hasProperty(name) && type.propertyIsWritable(name) && dotPropertyHeuristic(node, type, name)) {
for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) {
if (variantToStringList(node->property(QStringLiteral("typeNames"))).contains(QString::fromLatin1(typeName))) {
const QString fileName = propertyTemplatesPath() + node->property(QStringLiteral("sourceFile")).toString();
QString filledTemplate;
for (const QmlJS::SimpleReaderNode::Ptr &n : nodes) {
// Check if we have a template for the type
if (variantToStringList(n->property(QStringLiteral("typeNames"))).contains(QString::fromLatin1(typeName))) {
const QString fileName = propertyTemplatesPath() + n->property(QStringLiteral("sourceFile")).toString();
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
QString source = QString::fromUtf8(file.readAll());
file.close();
const bool section = node->propertyNames().contains("separateSection");
if (section) {
} else if (!sectionStarted) {
qmlTemplate += QStringLiteral("Section {\n");
qmlTemplate += QStringLiteral("caption: \"%1\"\n").arg(QString::fromUtf8(type.simplifiedTypeName()));
qmlTemplate += "anchors.left: parent.left\n";
qmlTemplate += "anchors.right: parent.right\n";
qmlTemplate += QStringLiteral("SectionLayout {\n");
sectionStarted = true;
}
qmlTemplate += source.arg(QString::fromUtf8(name)).arg(QString::fromUtf8(properName));
emptyTemplate = false;
filledTemplate = source.arg(QString::fromUtf8(label)).arg(QString::fromUtf8(underscoreProperty));
} else {
qWarning().nospace() << "template definition source file not found:" << fileName;
}
}
}
}
}
if (sectionStarted) {
qmlTemplate += QStringLiteral("}\n"); //Section
qmlTemplate += QStringLiteral("}\n"); //SectionLayout
return filledTemplate;
};
// QML specfics preparation
QStringList imports = variantToStringList(templateConfiguration()->property(QStringLiteral("imports")));
QString qmlTemplate = imports.join(QLatin1Char('\n')) + QLatin1Char('\n');
bool emptyTemplate = true;
const QString anchorLeftRight = "anchors.left: parent.left\nanchors.right: parent.right\n";
const QString paddingLeftTopBottom = "leftPadding: 0\ntopPadding: 0\nbottomPadding: 0\n";
qmlTemplate += "Column {\n";
qmlTemplate += anchorLeftRight;
if (node.modelNode().isComponent())
qmlTemplate += "ComponentButton {}\n";
qmlTemplate += "Section {\n";
qmlTemplate += "caption: \"User added properties\"\n";
qmlTemplate += anchorLeftRight;
qmlTemplate += paddingLeftTopBottom;
qmlTemplate += "Column {\n";
qmlTemplate += "width: parent.width\n";
// First the section containing properties of basic type e.g. int, string, bool
if (!basicProperties.empty()) {
emptyTemplate = false;
qmlTemplate += "Column {\n";
qmlTemplate += "width: parent.width\n";
qmlTemplate += "leftPadding: 8\n";
qmlTemplate += "rightPadding: 0\n";
qmlTemplate += "topPadding: 4\n";
qmlTemplate += "bottomPadding: 4\n";
qmlTemplate += "SectionLayout {\n";
for (const auto &p : qAsConst(basicProperties))
qmlTemplate += findAndFillTemplate(p, p);
qmlTemplate += "}\n"; // SectionLayout
qmlTemplate += "}\n"; // Column
}
qmlTemplate += "}\n";
// Second the section containing properties of complex type for which no specific template exists e.g. Button
if (!propertyMap.empty()) {
emptyTemplate = false;
for (const auto &k : propertyMap.keys()) {
TypeName parentTypeName = type.propertyTypeName(k);
// alias resolution only possible with instance
if (parentTypeName == "alias" && node.isValid())
parentTypeName = node.instanceType(k);
qmlTemplate += "Section {\n";
qmlTemplate += QStringLiteral("caption: \"%1 - %2\"\n").arg(QString::fromUtf8(k)).arg(QString::fromUtf8(parentTypeName));
qmlTemplate += anchorLeftRight;
qmlTemplate += "expanded: false\n";
qmlTemplate += "level: 1\n";
qmlTemplate += "SectionLayout {\n";
auto properties = propertyMap.value(k);
Utils::sort(properties);
for (const auto &p : qAsConst(properties)) {
const PropertyName shortName = p.contains('.') ? p.split('.').last() : p;
qmlTemplate += findAndFillTemplate(shortName, p);
}
qmlTemplate += "}\n"; // SectionLayout
qmlTemplate += "}\n"; // Section
}
}
// Third the section containing properties of complex type for which a specific template exists e.g. Rectangle, Image
if (!separateSectionProperties.empty()) {
emptyTemplate = false;
Utils::sort(separateSectionProperties);
for (const auto &p : qAsConst(separateSectionProperties)) {
TypeName parentTypeName = type.propertyTypeName(p);
// alias resolution only possible with instance
if (parentTypeName == "alias" && node.isValid())
parentTypeName = node.instanceType(p);
qmlTemplate += "Section {\n";
qmlTemplate += QStringLiteral("caption: \"%1 - %2\"\n").arg(QString::fromUtf8(p)).arg(QString::fromUtf8(parentTypeName));
qmlTemplate += anchorLeftRight;
qmlTemplate += paddingLeftTopBottom;
qmlTemplate += "level: 1\n";
qmlTemplate += "Column {\n";
qmlTemplate += "width: parent.width\n";
qmlTemplate += findAndFillTemplate(p, p);
qmlTemplate += "}\n"; // Column
qmlTemplate += "}\n"; // Section
}
}
qmlTemplate += "}\n"; // Column
qmlTemplate += "}\n"; // Section
qmlTemplate += "}\n"; // Column
if (emptyTemplate)
return QString();