diff --git a/share/qtcreator/qmldesigner/formatconfiguration.json b/share/qtcreator/qmldesigner/formatconfiguration.json new file mode 100644 index 00000000000..8526c710c70 --- /dev/null +++ b/share/qtcreator/qmldesigner/formatconfiguration.json @@ -0,0 +1,24 @@ +{ + "propertylist" : [ + { + "id": "textItem", + "subclasses": ["QtQuick.Text","QtQuick.TextInput","QtQuick.TextEdit"], + "properties": ["font.bold","font.italic","font.underline","color","font.capitalization","font.family","font.hintingPreference", + "font.kerning","font.letterSpacing","font.pixelSize","font.pointSize","font.preferShaping","font.strikeout", + "font.styleName","font.weight","font.wordSpacing","fontInfo.bold","fontInfo.family","fontInfo.italic","fontInfo.pixelSize", + "fontInfo.pointSize","fontInfo.styleName","fontInfo.weight","lineHeight","lineHeightMode","linkColor", + "maximumLineCount","minimumPixelSize","minimumPointSize","renderType","style","styleColor","padding","topPadding", + "bottomPadding","leftPadding","rightPadding"] + }, + { + "id": "rectangleItem", + "subclasses": ["QtQuick.Rectangle"], + "properties": ["color","border.color","border.width","radius"] + }, + { + "id":"positionerItem", + "subclasses": ["QtQuick.Row","QtQuick.Column","QtQuick.Grid"], + "properties": ["padding", "spacing", "topPadding", "bottomPadding", "leftPadding", "rightPadding"] + } + ] +} diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index e79c69ea6c3..5d4f8e84da0 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -210,6 +210,7 @@ extend_qtc_plugin(QmlDesigner modelnodecontextmenu.cpp modelnodecontextmenu.h modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h modelnodeoperations.cpp modelnodeoperations.h + formatoperation.cpp formatoperation.h navigation2d.cpp navigation2d.h gestures.cpp gestures.h qmldesignericonprovider.cpp qmldesignericonprovider.h diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore.pri b/src/plugins/qmldesigner/components/componentcore/componentcore.pri index 174ed0341a5..a64f61ab466 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore.pri +++ b/src/plugins/qmldesigner/components/componentcore/componentcore.pri @@ -15,6 +15,7 @@ SOURCES += modelnodecontextmenu_helper.cpp SOURCES += selectioncontext.cpp SOURCES += designeractionmanager.cpp SOURCES += modelnodeoperations.cpp +SOURCES += formatoperation.cpp SOURCES += navigation2d.cpp SOURCES += crumblebar.cpp SOURCES += qmldesignericonprovider.cpp @@ -37,6 +38,7 @@ HEADERS += selectioncontext.h HEADERS += componentcore_constants.h HEADERS += designeractionmanager.h HEADERS += modelnodeoperations.h +HEADERS += formatoperation.h HEADERS += navigation2d.h HEADERS += actioninterface.h HEADERS += crumblebar.h diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 69e4e39b558..9024b32776e 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -55,6 +55,8 @@ const char resetZCommandId[] = "ResetZ"; const char reverseCommandId[] = "Reverse"; const char resetSizeCommandId[] = "ResetSize"; const char resetPositionCommandId[] = "ResetPosition"; +const char copyFormatCommandId[] = "CopyFormat"; +const char applyFormatCommandId[] = "ApplyFormat"; const char visiblityCommandId[] = "ToggleVisiblity"; const char anchorsFillCommandId[] = "AnchorsFill"; const char anchorsResetCommandId[] = "AnchorsReset"; @@ -123,6 +125,8 @@ const char visibilityDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", const char resetSizeDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset Size"); const char resetPositionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset Position"); +const char copyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Copy Formatting"); +const char applyFormatDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Apply Formatting"); const char goIntoComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go into Component"); const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Merge File With Template"); @@ -176,6 +180,8 @@ const char lowerToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Lower s const char resetSizeToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset size and use implicit size."); const char resetPositionTooltip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset position and use implicit position."); +const char copyFormatTooltip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Copy formatting."); +const char applyFormatTooltip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Apply formatting."); const char anchorsFillToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fill selected component to parent."); const char anchorsResetToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Reset anchors for selected component."); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 808882220eb..dbfa2f82d07 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -28,6 +28,7 @@ #include "changestyleaction.h" #include "designeractionmanagerview.h" #include "modelnodecontextmenu_helper.h" +#include "formatoperation.h" #include "qmldesignerconstants.h" #include "rewritingexception.h" #include @@ -909,11 +910,13 @@ bool anchorsMenuEnabled(const SelectionContext &context) || singleSelectionItemIsAnchored(context); } + void DesignerActionManager::createDefaultDesignerActions() { using namespace SelectionContextFunctors; using namespace ComponentCoreConstants; using namespace ModelNodeOperations; + using namespace FormatOperation; const Utils::Icon prevIcon({ {QLatin1String(":/utils/images/prev.png"), Utils::Theme::QmlDesigner_FormEditorForegroundColor}}, Utils::Icon::MenuTintedStyle); @@ -1001,6 +1004,29 @@ void DesignerActionManager::createDefaultDesignerActions() &resetPosition, &selectionNotEmptyAndHasXorYProperty)); + addDesignerAction(new ModelNodeAction( + copyFormatCommandId, + copyFormatDisplayName, + Utils::Icon({{":/qmldesigner/icon/designeractions/images/raise.png", Utils::Theme::IconsBaseColor}}).icon(), + copyFormatTooltip, + editCategory, + QKeySequence(), + 120, + ©Format, + &propertiesCopyable)); + + addDesignerAction(new ModelNodeAction( + applyFormatCommandId, + applyFormatDisplayName, + Utils::Icon({{":/qmldesigner/icon/designeractions/images/lower.png", Utils::Theme::IconsBaseColor}}).icon(), + applyFormatTooltip, + editCategory, + QKeySequence(), + 120, + &applyFormat, + &propertiesApplyable)); + + addDesignerAction(new ModelNodeAction( resetSizeCommandId, resetSizeDisplayName, diff --git a/src/plugins/qmldesigner/components/componentcore/formatoperation.cpp b/src/plugins/qmldesigner/components/componentcore/formatoperation.cpp new file mode 100644 index 00000000000..cfa3bf4f9e3 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/formatoperation.cpp @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "formatoperation.h" +#include "utils/fileutils.h" + +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { +namespace FormatOperation{ + +void readFormatConfiguration(){ + + if (copyableProperties.isEmpty()){ + QString source = "formatconfiguration.json"; + Utils::FilePath path = Core::ICore::resourcePath() + "/qmldesigner/" + source; + QString errorString; + Utils::FileReader reader; + + if (reader.fetch(path, &errorString)){ + QJsonParseError jsonError; + QJsonDocument document = QJsonDocument::fromJson(reader.data(), &jsonError ); + + if (jsonError.error != QJsonParseError::NoError) + return; + + QJsonObject jsonObject = document.object(); + QVariantMap rootMap = jsonObject.toVariantMap(); + QJsonArray jsonArray = rootMap["propertylist"].toJsonArray(); + + for (int i=0; i< jsonArray.count(); ++i){ + auto item = jsonArray.at(i).toObject(); + QVariantMap itemMap = item.toVariantMap(); + StylePropertyStruct current; + current.id = itemMap["id"].toString(); + QVariantList subclassVariantList = itemMap["subclasses"].toList(); + QStringList subclassList; + + for (auto subclass : subclassVariantList) + subclassList.append(subclass.toString()); + + current.subclasses = subclassList; + + QVariantList propertyList = itemMap["properties"].toList(); + QStringList propertyStringList; + + for (auto property : propertyList) + propertyStringList.append(property.toString()); + + current.properties = propertyStringList; + copyableProperties.append(current); + } + } + } +} + +bool propertiesCopyable(const SelectionContext &selectionState) +{ + if (!selectionState.singleNodeIsSelected()) + return false; + + readFormatConfiguration(); + + ModelNode modelNode = selectionState.currentSingleSelectedNode(); + + for (StylePropertyStruct copyable : copyableProperties) + for (QString copyableSubclass : copyable.subclasses) + if (modelNode.metaInfo().isSubclassOf(copyableSubclass.toUtf8())) + return true; + + return false; +} + +bool propertiesApplyable(const SelectionContext &selectionState) +{ + if (selectionState.selectedModelNodes().isEmpty()) + return false; + + if (chosenItem.subclasses.isEmpty()) + return false; + + const ModelNode firstSelectedNode = selectionState.firstSelectedModelNode(); + bool found = false; + + for (QString copyableSubclass : chosenItem.subclasses){ + if (firstSelectedNode.metaInfo().isSubclassOf(copyableSubclass.toUtf8())){ + found = true; + break; + } + } + + if (!found) + return false; + + for (const ModelNode &modelNode : selectionState.selectedModelNodes()){ + found = false; + + for (QString subclass : chosenItem.subclasses) + if (modelNode.metaInfo().isSubclassOf(subclass.toUtf8())){ + found = true; + break; + } + + if (found) + continue; + + return false; + } + + return true; +} + +void copyFormat(const SelectionContext &selectionState) +{ + if (!selectionState.view()) + return; + + selectionState.view()->executeInTransaction("DesignerActionManager|copyFormat",[selectionState](){ + + applyableProperties.clear(); + + ModelNode node = selectionState.currentSingleSelectedNode(); + QStringList propertyList; + for (StylePropertyStruct copyable : copyableProperties){ + bool found = false; + for (QString copyableSubclass : copyable.subclasses) + if (node.metaInfo().isSubclassOf(copyableSubclass.toUtf8())){ + propertyList = copyable.properties; + chosenItem = copyable; + found = true; + break; + } + if (found) + break; + } + + QmlObjectNode qmlObjectNode(node); + + for (auto propertyName : propertyList){ + if (qmlObjectNode.propertyAffectedByCurrentState(propertyName.toUtf8())) { + StyleProperties property; + property.propertyName = propertyName.toUtf8(); + property.value = qmlObjectNode.modelValue(propertyName.toUtf8()); + applyableProperties.append(property); + } + } + }); +} + +void applyFormat(const SelectionContext &selectionState) +{ + if (!selectionState.view()) + return; + + selectionState.view()->executeInTransaction("DesignerActionManager|applyFormat",[selectionState](){ + + for (ModelNode node : selectionState.selectedModelNodes()) { + QmlObjectNode qmlObjectNode(node); + QStringList propertyList; + + for (StylePropertyStruct copyable : copyableProperties){ + bool found = false; + for (QString copyableSubclass : copyable.subclasses) + if (node.metaInfo().isSubclassOf(copyableSubclass.toUtf8())){ + propertyList = copyable.properties; + found = true; + break; + } + if (found) + break; + } + + for (auto propertyName : propertyList) + if (qmlObjectNode.propertyAffectedByCurrentState(propertyName.toUtf8())) + qmlObjectNode.removeProperty(propertyName.toUtf8()); + + for (StyleProperties currentProperty : applyableProperties) + if (node.metaInfo().hasProperty((currentProperty.propertyName))) + qmlObjectNode.setVariantProperty(currentProperty.propertyName, currentProperty.value); + } + }); +} + +} // namespace FormatOperation +} // QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/formatoperation.h b/src/plugins/qmldesigner/components/componentcore/formatoperation.h new file mode 100644 index 00000000000..c4aa94476bc --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/formatoperation.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "selectioncontext.h" + +namespace QmlDesigner { +namespace FormatOperation { + +struct StylePropertyStruct +{ +QString id; +QStringList subclasses; +QStringList properties; +}; + +struct StyleProperties +{ + QmlDesigner::PropertyName propertyName; + QVariant value; +}; + +static QList copyableProperties = {}; +static QList applyableProperties = {}; +static StylePropertyStruct chosenItem = {}; + +bool propertiesCopyable(const SelectionContext &selectionState); +bool propertiesApplyable(const SelectionContext &selectionState); +void copyFormat(const SelectionContext &selectionState); +void applyFormat(const SelectionContext &selectionState); +void readFormatConfiguration(); + +} // namespace FormatOperation +} // QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 5d9d748f8f9..599620fd02b 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -458,6 +458,8 @@ Project { "componentcore/designeractionmanagerview.h", "componentcore/findimplementation.cpp", "componentcore/findimplementation.h", + "componentcore/formatoperation.cpp", + "componentcore/formatoperation.h", "componentcore/gestures.cpp", "componentcore/gestures.h", "componentcore/layoutingridlayout.cpp",