diff options
Diffstat (limited to 'src/plugins/qmldesigner/components')
36 files changed, 1797 insertions, 268 deletions
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 2965bcd90b..e76b5c1ff7 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -39,8 +39,10 @@ const char qmlPreviewCategory[] = "QmlPreview"; const char editCategory[] = "Edit"; const char anchorsCategory[] = "Anchors"; const char positionCategory[] = "Position"; +const char groupCategory[] = "Group"; const char layoutCategory[] = "Layout"; const char flowCategory[] = "Flow"; +const char flowEffectCategory[] = "FlowEffect"; const char flowConnectionCategory[] = "FlowConnection"; const char stackedContainerCategory[] = "StackedContainer"; const char genericToolBarCategory[] = "GenericToolBar"; @@ -57,6 +59,8 @@ const char anchorsFillCommandId[] = "AnchorsFill"; const char anchorsResetCommandId[] = "AnchorsReset"; const char removePositionerCommandId[] = "RemovePositioner"; const char createFlowActionAreaCommandId[] = "CreateFlowActionArea"; +const char setFlowStartCommandId[] = "SetFlowStart"; +const char selectFlowEffectCommandId[] = "SelectFlowEffect"; const char layoutRowPositionerCommandId[] = "LayoutRowPositioner"; const char layoutColumnPositionerCommandId[] = "LayoutColumnPositioner"; const char layoutGridPositionerCommandId[] = "LayoutGridPositioner"; @@ -76,15 +80,19 @@ const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer const char increaseIndexOfStackedContainerCommandId[] = "IncreaseIndexOfStackedContainer"; const char decreaseIndexOfStackedContainerCommandId[] = "DecreaseIndexOfStackedContainer"; const char flowAssignEffectCommandId[] = "AssignFlowEffect"; +const char addToGroupItemCommandId[] = "AddToGroupItem"; const char selectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Selection"); const char flowConnectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connect"); +const char selectEffectDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select Effect"); const char stackCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Stack (z)"); const char editCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit"); const char anchorsCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Anchors"); const char positionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position"); +const char groupCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Group"); const char layoutCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Layout"); const char flowCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow"); +const char flowEffectCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Flow Effects"); const char stackedContainerCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Stacked Container"); const char cutSelectionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Cut"); @@ -124,8 +132,11 @@ const char layoutGridPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerCon const char layoutFlowPositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Position in Flow"); const char removePositionerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove Positioner"); const char createFlowActionAreaDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Flow Action"); +const char setFlowStartDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Flow Start"); const char removeLayoutDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Remove Layout"); +const char addToGroupItemDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Group in GroupItem"); + const char addItemToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add Item"); const char addTabBarToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add Tab Bar"); const char increaseIndexToStackedContainerDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Increase Index"); @@ -165,6 +176,7 @@ const int priorityStackCategory = 180; const int priorityEditCategory = 160; const int priorityAnchorsCategory = 140; const int priorityFlowCategory = 240; +const int priorityGroupCategory = 140; const int priorityPositionCategory = 130; const int priorityLayoutCategory = 120; const int priorityStackedContainerCategory = priorityLayoutCategory; diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 42f95952db..07246f175c 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -346,12 +346,28 @@ bool isFlowItem(const SelectionContext &context) && QmlFlowItemNode::isValidQmlFlowItemNode(context.currentSingleSelectedNode()); } +bool isFlowTarget(const SelectionContext &context) +{ + return context.singleNodeIsSelected() + && QmlFlowTargetNode::isFlowEditorTarget(context.currentSingleSelectedNode()); +} + bool isFlowTransitionItem(const SelectionContext &context) { return context.singleNodeIsSelected() && QmlFlowItemNode::isFlowTransition(context.currentSingleSelectedNode()); } +bool isFlowTransitionItemWithEffect(const SelectionContext &context) +{ + if (!isFlowTransitionItem(context)) + return false; + + ModelNode node = context.currentSingleSelectedNode(); + + return node.hasNodeProperty("effect"); +} + bool isFlowActionItemItem(const SelectionContext &context) { const ModelNode selectedNode = context.currentSingleSelectedNode(); @@ -362,9 +378,9 @@ bool isFlowActionItemItem(const SelectionContext &context) || QmlVisualNode::isFlowWildcard(selectedNode)); } -bool isFlowItemOrTransition(const SelectionContext &context) +bool isFlowTargetOrTransition(const SelectionContext &context) { - return isFlowItem(context) || isFlowTransitionItem(context); + return isFlowTarget(context) || isFlowTransitionItem(context); } class FlowActionConnectAction : public ActionGroup @@ -642,6 +658,12 @@ bool positionOptionVisible(const SelectionContext &context) || isPositioner(context); } +bool studioComponentsAvailable(const SelectionContext &context) +{ + const Import import = Import::createLibraryImport("QtQuick.Studio.Components", "1.0"); + return context.view()->model()->isImportPossible(import, true, true); +} + bool singleSelectedAndUiFile(const SelectionContext &context) { if (!singleSelection(context)) @@ -853,15 +875,31 @@ void DesignerActionManager::createDefaultDesignerActions() priorityLayoutCategory, &layoutOptionVisible)); - //isFlowTransitionItem + addDesignerAction(new ActionGroup( + groupCategoryDisplayName, + groupCategory, + priorityGroupCategory, + &positionOptionVisible, + &studioComponentsAvailable)); addDesignerAction(new ActionGroup( flowCategoryDisplayName, flowCategory, priorityFlowCategory, - &isFlowItemOrTransition, + &isFlowTargetOrTransition, &flowOptionVisible)); + + auto effectMenu = new ActionGroup( + flowEffectCategoryDisplayName, + flowEffectCategory, + priorityFlowCategory, + &isFlowTransitionItem, + &flowOptionVisible); + + effectMenu->setCategory(flowCategory); + addDesignerAction(effectMenu); + addDesignerAction(new ModelNodeFormEditorAction( createFlowActionAreaCommandId, createFlowActionAreaDisplayName, @@ -874,26 +912,41 @@ void DesignerActionManager::createDefaultDesignerActions() &isFlowItem, &flowOptionVisible)); + addDesignerAction(new ModelNodeContextMenuAction( + setFlowStartCommandId, + setFlowStartDisplayName, + {}, + flowCategory, + priorityFirst, + {}, + &setFlowStartItem, + &isFlowItem, + &flowOptionVisible)); + addDesignerAction(new FlowActionConnectAction( flowConnectionCategoryDisplayName, flowConnectionCategory, priorityFlowCategory)); - const QList<TypeName> types = {"FlowActionArea", - "FlowFadeEffect", - "FlowPushRightEffect", - "FlowPushLeftEffect", - "FlowPushUpEffect", - "FlowSlideDownEffect", - "FlowSlideLeftEffect", - "FlowSlideRightEffect", - "FlowSlideUpEffect", + const QList<TypeName> transitionTypes = {"FlowFadeEffect", + "FlowPushEffect", + "FlowMoveEffect", "None"}; - for (const TypeName &typeName : types) + for (const TypeName &typeName : transitionTypes) addTransitionEffectAction(typeName); + addDesignerAction(new ModelNodeContextMenuAction( + selectFlowEffectCommandId, + selectEffectDisplayName, + {}, + flowCategory, + {}, + priorityFlowCategory, + &selectFlowEffect, + &isFlowTransitionItemWithEffect)); + addDesignerAction(new ActionGroup( stackedContainerCategoryDisplayName, stackedContainerCategory, @@ -968,6 +1021,18 @@ void DesignerActionManager::createDefaultDesignerActions() &isLayout, &isLayout)); + addDesignerAction(new ModelNodeContextMenuAction( + addToGroupItemCommandId, + addToGroupItemDisplayName, + {}, + groupCategory, + QKeySequence(), + 110, + &addToGroupItem, + &selectionCanBeLayouted, + &selectionCanBeLayouted)); + + addDesignerAction(new ModelNodeFormEditorAction( addItemToStackedContainerCommandId, addItemToStackedContainerDisplayName, @@ -1175,7 +1240,7 @@ void DesignerActionManager::addTransitionEffectAction(const TypeName &typeName) QByteArray(ComponentCoreConstants::flowAssignEffectCommandId) + typeName, QLatin1String(ComponentCoreConstants::flowAssignEffectDisplayName) + typeName, {}, - ComponentCoreConstants::flowCategory, + ComponentCoreConstants::flowEffectCategory, {}, typeName == "None" ? 100 : 140, [typeName](const SelectionContext &context) @@ -1186,6 +1251,10 @@ void DesignerActionManager::addTransitionEffectAction(const TypeName &typeName) DesignerActionToolBar::DesignerActionToolBar(QWidget *parentWidget) : Utils::StyledBar(parentWidget), m_toolBar(new QToolBar("ActionToolBar", this)) { + QWidget* empty = new QWidget(); + empty->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); + m_toolBar->addWidget(empty); + m_toolBar->setContentsMargins(0, 0, 0, 0); m_toolBar->setFloatable(true); m_toolBar->setMovable(true); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.cpp index 6876684504..8a6c06fcdc 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.cpp @@ -25,6 +25,8 @@ #include "designeractionmanagerview.h" +#include <customnotifications.h> + #include <selectioncontext.h> #include <actioninterface.h> #include <variantproperty.h> @@ -53,7 +55,7 @@ void DesignerActionManagerView::modelAboutToBeDetached(Model *model) void DesignerActionManagerView::nodeCreated(const ModelNode &) { - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::NodeCreated); } void DesignerActionManagerView::nodeRemoved(const ModelNode &, const NodeAbstractProperty &, AbstractView::PropertyChangeFlags) @@ -63,17 +65,17 @@ void DesignerActionManagerView::nodeRemoved(const ModelNode &, const NodeAbstrac void DesignerActionManagerView::nodeAboutToBeReparented(const ModelNode &, const NodeAbstractProperty &, const NodeAbstractProperty &, AbstractView::PropertyChangeFlags) { - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::NodeHierachy); } void DesignerActionManagerView::nodeReparented(const ModelNode &, const NodeAbstractProperty &, const NodeAbstractProperty &, AbstractView::PropertyChangeFlags) { - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::NodeHierachy); } void DesignerActionManagerView::propertiesRemoved(const QList<AbstractProperty> &) { - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::Properties); } void DesignerActionManagerView::rootNodeTypeChanged(const QString &, int, int) @@ -112,7 +114,7 @@ void DesignerActionManagerView::selectedNodesChanged(const QList<ModelNode> &sel void DesignerActionManagerView::nodeOrderChanged(const NodeListProperty &, const ModelNode &, int) { - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::NodeHierachy); } void DesignerActionManagerView::importsChanged(const QList<Import> &, const QList<Import> &) @@ -122,27 +124,38 @@ void DesignerActionManagerView::importsChanged(const QList<Import> &, const QLis void DesignerActionManagerView::signalHandlerPropertiesChanged(const QVector<SignalHandlerProperty> &, AbstractView::PropertyChangeFlags) { - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::Properties); } void DesignerActionManagerView::variantPropertiesChanged(const QList<VariantProperty> &, AbstractView::PropertyChangeFlags propertyChangeFlag) { if (propertyChangeFlag == AbstractView::PropertiesAdded) - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::Properties); else if (hasSingleSelectedModelNode()) - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::Properties); } void DesignerActionManagerView::bindingPropertiesChanged(const QList<BindingProperty> &, AbstractView::PropertyChangeFlags propertyChangeFlag) { if (propertyChangeFlag == AbstractView::PropertiesAdded) - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::Properties); } void DesignerActionManagerView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName> > &) { if (hasSingleSelectedModelNode()) - setupContext(SelectionContext::UpdateMode::Fast); + setupContext(SelectionContext::UpdateMode::Properties); +} + +void DesignerActionManagerView::customNotification(const AbstractView * /*view*/, + const QString &identifier, + const QList<ModelNode> & /* nodeList */, + const QList<QVariant> & /*data */) +{ + if (identifier == StartRewriterAmend) + m_isInRewriterTransaction = true; + else if (identifier == EndRewriterAmend) + m_isInRewriterTransaction = false; } DesignerActionManager &DesignerActionManagerView::designerActionManager() diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.h b/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.h index 5eb0efe542..a7a34271ea 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.h +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanagerview.h @@ -71,6 +71,10 @@ public: void emitSelectionChanged(); void setupContext(SelectionContext::UpdateMode updateMode = SelectionContext::UpdateMode::Normal); + void customNotification(const AbstractView *, + const QString &identifier, + const QList<ModelNode> &, + const QList<QVariant> &) override; signals: void selectionChanged(bool itemsSelected, bool rootItemIsSelected); diff --git a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp index a1629c0b5b..e106fc9357 100644 --- a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp +++ b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp @@ -167,7 +167,6 @@ void LayoutInGridLayout::doIt() const TypeName layoutType = "QtQuick.Layouts.GridLayout"; if (!m_selectionContext.view() - || !m_selectionContext.hasSingleSelectedModelNode() || !m_selectionContext.view()->model()->hasNodeMetaInfo(layoutType)) return; diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h index 67a53ef997..59029400e5 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h @@ -132,16 +132,22 @@ public: bool isVisible(const SelectionContext &m_selectionState) const override { return m_visibility(m_selectionState); } bool isEnabled(const SelectionContext &m_selectionState) const override { return m_enabled(m_selectionState); } - QByteArray category() const override { return QByteArray(); } + QByteArray category() const override { return m_category; } QByteArray menuId() const override { return m_menuId; } int priority() const override { return m_priority; } Type type() const override { return ContextMenu; } + void setCategory(const QByteArray &catageoryId) + { + m_category = catageoryId; + } + private: const QByteArray m_menuId; const int m_priority; SelectionContextPredicate m_enabled; SelectionContextPredicate m_visibility; + QByteArray m_category; }; class SeperatorDesignerAction : public AbstractAction diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 2c239a8aa9..00e2ac028a 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -311,8 +311,10 @@ void resetSize(const SelectionContext &selectionState) selectionState.view()->executeInTransaction("DesignerActionManager|resetSize",[selectionState](){ foreach (ModelNode node, selectionState.selectedModelNodes()) { QmlItemNode itemNode(node); - itemNode.removeProperty("width"); - itemNode.removeProperty("height"); + if (itemNode.isValid()) { + itemNode.removeProperty("width"); + itemNode.removeProperty("height"); + } } }); } @@ -325,8 +327,10 @@ void resetPosition(const SelectionContext &selectionState) selectionState.view()->executeInTransaction("DesignerActionManager|resetPosition",[selectionState](){ foreach (ModelNode node, selectionState.selectedModelNodes()) { QmlItemNode itemNode(node); - itemNode.removeProperty("x"); - itemNode.removeProperty("y"); + if (itemNode.isValid()) { + itemNode.removeProperty("x"); + itemNode.removeProperty("y"); + } } }); } @@ -348,7 +352,8 @@ void resetZ(const SelectionContext &selectionState) selectionState.view()->executeInTransaction("DesignerActionManager|resetZ",[selectionState](){ foreach (ModelNode node, selectionState.selectedModelNodes()) { QmlItemNode itemNode(node); - itemNode.removeProperty("z"); + if (itemNode.isValid()) + itemNode.removeProperty("z"); } }); } @@ -452,7 +457,6 @@ static void layoutHelperFunction(const SelectionContext &selectionContext, const LessThan &lessThan) { if (!selectionContext.view() - || !selectionContext.hasSingleSelectedModelNode() || !selectionContext.view()->model()->hasNodeMetaInfo(layoutType)) return; @@ -1095,7 +1099,122 @@ void addFlowEffect(const SelectionContext &selectionContext, const TypeName &typ container.nodeProperty("effect").reparentHere(effectNode); view->setSelectedModelNode(effectNode); } - }); + }); +} + +void setFlowStartItem(const SelectionContext &selectionContext) +{ + AbstractView *view = selectionContext.view(); + + QTC_ASSERT(view && selectionContext.hasSingleSelectedModelNode(), return); + ModelNode node = selectionContext.currentSingleSelectedNode(); + QTC_ASSERT(node.isValid(), return); + QTC_ASSERT(node.metaInfo().isValid(), return); + QmlFlowItemNode flowItem(node); + QTC_ASSERT(flowItem.isValid(), return); + QTC_ASSERT(flowItem.flowView().isValid(), return); + view->executeInTransaction("DesignerActionManager:setFlowStartItem", + [&flowItem](){ + flowItem.flowView().setStartFlowItem(flowItem); + }); +} + + +bool static hasStudioComponentsImport(const SelectionContext &context) +{ + if (context.view() && context.view()->model()) { + Import import = Import::createLibraryImport("QtQuick.Studio.Components", "1.0"); + return context.view()->model()->hasImport(import, true, true); + } + + return false; +} + +static inline void setAdjustedPos(const QmlDesigner::ModelNode &modelNode) +{ + if (modelNode.hasParentProperty()) { + ModelNode parentNode = modelNode.parentProperty().parentModelNode(); + + const QPointF instancePos = QmlItemNode(modelNode).instancePosition(); + const int x = instancePos.x() - parentNode.variantProperty("x").value().toInt(); + const int y = instancePos.y() - parentNode.variantProperty("y").value().toInt(); + + modelNode.variantProperty("x").setValue(x); + modelNode.variantProperty("y").setValue(y); + } +} + +void reparentToNodeAndAdjustPosition(const ModelNode &parentModelNode, + const QList<ModelNode> &modelNodeList) +{ + for (ModelNode modelNode : modelNodeList) { + reparentTo(modelNode, parentModelNode); + setAdjustedPos(modelNode); + + for (const VariantProperty &variantProperty : modelNode.variantProperties()) { + if (variantProperty.name().contains("anchors.")) + modelNode.removeProperty(variantProperty.name()); + } + for (const BindingProperty &bindingProperty : modelNode.bindingProperties()) { + if (bindingProperty.name().contains("anchors.")) + modelNode.removeProperty(bindingProperty.name()); + } + } +} + +void addToGroupItem(const SelectionContext &selectionContext) +{ + const TypeName typeName = "QtQuick.Studio.Components.GroupItem"; + + try { + if (!hasStudioComponentsImport(selectionContext)) { + Import studioImport = Import::createLibraryImport("QtQuick.Studio.Components", "1.0"); + selectionContext.view()-> model()->changeImports({studioImport}, {}); + } + + if (!selectionContext.view()) + return; + + if (QmlItemNode::isValidQmlItemNode(selectionContext.firstSelectedModelNode())) { + const QmlItemNode qmlItemNode = QmlItemNode(selectionContext.firstSelectedModelNode()); + + if (qmlItemNode.hasInstanceParentItem()) { + ModelNode groupNode; + selectionContext.view()->executeInTransaction("DesignerActionManager|addToGroupItem1",[=, &groupNode](){ + + QmlItemNode parentNode = qmlItemNode.instanceParentItem(); + NodeMetaInfo metaInfo = selectionContext.view()->model()->metaInfo(typeName); + groupNode = selectionContext.view()->createModelNode(typeName, metaInfo.majorVersion(), metaInfo.minorVersion()); + reparentTo(groupNode, parentNode); + }); + selectionContext.view()->executeInTransaction("DesignerActionManager|addToGroupItem2",[=](){ + + QList<ModelNode> selectedNodes = selectionContext.selectedModelNodes(); + setUpperLeftPostionToNode(groupNode, selectedNodes); + + reparentToNodeAndAdjustPosition(groupNode, selectedNodes); + }); + } + } + } catch (RewritingException &e) { + e.showException(); + } +} + +void selectFlowEffect(const SelectionContext &selectionContext) +{ + if (!selectionContext.singleNodeIsSelected()) + return; + + ModelNode node = selectionContext.currentSingleSelectedNode(); + QmlVisualNode transition(node); + + QTC_ASSERT(transition.isValid(), return); + QTC_ASSERT(transition.isFlowTransition(), return); + + if (node.hasNodeProperty("effect")) { + selectionContext.view()->setSelectedModelNode(node.nodeProperty("effect").modelNode()); + } } } // namespace Mode diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 994110297e..220afe9d4e 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -77,6 +77,9 @@ bool addFontToProject(const QStringList &fileNames, const QString &directory); void createFlowActionArea(const SelectionContext &selectionContext); void addTransition(const SelectionContext &selectionState); void addFlowEffect(const SelectionContext &selectionState, const TypeName &typeName); +void setFlowStartItem(const SelectionContext &selectionContext); +void addToGroupItem(const SelectionContext &selectionContext); +void selectFlowEffect(const SelectionContext &selectionContext); } // namespace ModelNodeOperationso } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp b/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp index 59a9454b09..80201b315b 100644 --- a/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp +++ b/src/plugins/qmldesigner/components/componentcore/selectioncontext.cpp @@ -124,12 +124,17 @@ bool SelectionContext::isValid() const bool SelectionContext::fastUpdate() const { - return m_updateMode == UpdateMode::Fast; + return m_updateReason != UpdateMode::Normal; } void SelectionContext::setUpdateMode(UpdateMode mode) { - m_updateMode = mode; + m_updateReason = mode; +} + +SelectionContext::UpdateMode SelectionContext::updateReason() const +{ + return m_updateReason; } } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/selectioncontext.h b/src/plugins/qmldesigner/components/componentcore/selectioncontext.h index 929bf4eebb..3fe2157788 100644 --- a/src/plugins/qmldesigner/components/componentcore/selectioncontext.h +++ b/src/plugins/qmldesigner/components/componentcore/selectioncontext.h @@ -35,7 +35,14 @@ namespace QmlDesigner { class QMLDESIGNERCORE_EXPORT SelectionContext { public: - enum class UpdateMode {Normal, Fast}; + enum class UpdateMode { + Normal, + Fast, + Properties, + NodeCreated, + NodeHierachy, + Selection + }; SelectionContext(); SelectionContext(AbstractView *view); @@ -68,13 +75,15 @@ public: bool fastUpdate() const; void setUpdateMode(UpdateMode mode); + UpdateMode updateReason() const; + private: QPointer<AbstractView> m_view; ModelNode m_targetNode; QPointF m_scenePosition; bool m_showSelectionTools = false; bool m_toggled = false; - UpdateMode m_updateMode = UpdateMode::Normal; + UpdateMode m_updateReason = UpdateMode::Normal; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/theme.cpp b/src/plugins/qmldesigner/components/componentcore/theme.cpp index ec4128479f..29b937a502 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.cpp +++ b/src/plugins/qmldesigner/components/componentcore/theme.cpp @@ -28,19 +28,46 @@ #include <qmldesignerplugin.h> +#include <coreplugin/icore.h> + #include <utils/stylehelper.h> #include <QApplication> #include <QRegExp> #include <QScreen> #include <QPointer> +#include <QQmlEngine> +#include <QQmlComponent> +#include <QQmlProperty> #include <qqml.h> +static Q_LOGGING_CATEGORY(themeLog, "qtc.qmldesigner.theme", QtWarningMsg) + namespace QmlDesigner { Theme::Theme(Utils::Theme *originTheme, QObject *parent) : Utils::Theme(originTheme, parent) + , m_constants(nullptr) { + QString constantsPath = Core::ICore::resourcePath() + + QStringLiteral("/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml"); + + QQmlEngine* engine = new QQmlEngine(this); + QQmlComponent component(engine, QUrl::fromLocalFile(constantsPath)); + + if (component.status() == QQmlComponent::Ready) { + m_constants = component.create(); + } + else if (component.status() == QQmlComponent::Error ) { + qCWarning(themeLog) << "Couldn't load" << constantsPath + << "due to the following error(s):"; + for (QQmlError error : component.errors()) + qCWarning(themeLog) << error.toString(); + } + else { + qCWarning(themeLog) << "Couldn't load" << constantsPath + << "the status of the QQmlComponent is" << component.status(); + } } QColor Theme::evaluateColorAtThemeInstance(const QString &themeColorName) @@ -129,6 +156,25 @@ QPixmap Theme::getPixmap(const QString &id) return QmlDesignerIconProvider::getPixmap(id); } +QString Theme::getIconUnicode(Theme::Icon i) +{ + if (!instance()->m_constants) + return QString(); + + const QMetaObject *m = instance()->metaObject(); + const char *enumName = "Icon"; + int enumIndex = m->indexOfEnumerator(enumName); + + if (enumIndex == -1) { + qCWarning(themeLog) << "Couldn't find enum" << enumName; + return QString(); + } + + QMetaEnum e = m->enumerator(enumIndex); + + return instance()->m_constants->property(e.valueToKey(i)).toString(); +} + QColor Theme::qmlDesignerBackgroundColorDarker() const { return getColor(QmlDesigner_BackgroundColorDarker); diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 6940c0c1cf..4cc4deb298 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -41,12 +41,95 @@ namespace QmlDesigner { class QMLDESIGNERCORE_EXPORT Theme : public Utils::Theme { Q_OBJECT + + Q_ENUMS(Icon) + public: + enum Icon { + actionIcon, + actionIconBinding, + addColumnAfter, + addColumnBefore, + addFile, + addRowAfter, + addRowBefore, + addTable, + adsClose, + adsDetach, + adsDropDown, + aliasAnimated, + aliasProperty, + alignBottom, + alignCenterHorizontal, + alignCenterVertical, + alignLeft, + alignRight, + alignTo, + alignTop, + assign, + anchorBaseline, + anchorBottom, + anchorFill, + anchorLeft, + anchorRight, + anchorTop, + annotationBubble, + annotationDecal, + centerHorizontal, + centerVertical, + curveEditor, + closeCross, + decisionNode, + deleteColumn, + deleteRow, + deleteTable, + detach, + distributeBottom, + distributeCenterHorizontal, + distributeCenterVertical, + distributeLeft, + distributeOriginBottomRight, + distributeOriginCenter, + distributeOriginNone, + distributeOriginTopLeft, + distributeRight, + distributeSpacingHorizontal, + distributeSpacingVertical, + distributeTop, + edit, + fontStyleBold, + fontStyleItalic, + fontStyleStrikethrough, + fontStyleUnderline, + mergeCells, + redo, + splitColumns, + splitRows, + startNode, + testIcon, + textAlignBottom, + textAlignCenter, + textAlignLeft, + textAlignMiddle, + textAlignRight, + textAlignTop, + textBulletList, + textFullJustification, + textNumberedList, + tickIcon, + triState, + undo, + upDownIcon, + upDownSquare2, + wildcard + }; + static Theme *instance(); static QString replaceCssColors(const QString &input); static void setupTheme(QQmlEngine *engine); static QColor getColor(Color role); static QPixmap getPixmap(const QString &id); + static QString getIconUnicode(Theme::Icon i); Q_INVOKABLE QColor qmlDesignerBackgroundColorDarker() const; Q_INVOKABLE QColor qmlDesignerBackgroundColorDarkAlternate() const; @@ -58,9 +141,12 @@ public: Q_INVOKABLE int smallFontPixelSize() const; Q_INVOKABLE int captionFontPixelSize() const; Q_INVOKABLE bool highPixelDensity() const; + private: Theme(Utils::Theme *originTheme, QObject *parent); QColor evaluateColorAtThemeInstance(const QString &themeColorName); + + QObject *m_constants; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index c8e1e222e5..9e882be2fd 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -179,6 +179,17 @@ void Edit3DView::importsChanged(const QList<Import> &addedImports, checkImports(); } +void Edit3DView::customNotification(const AbstractView *view, const QString &identifier, + const QList<ModelNode> &nodeList, const QList<QVariant> &data) +{ + Q_UNUSED(view) + Q_UNUSED(nodeList) + Q_UNUSED(data) + + if (identifier == "asset_import_update") + resetPuppet(); +} + void Edit3DView::sendInputEvent(QInputEvent *e) const { if (nodeInstanceView()) @@ -301,14 +312,16 @@ QVector<Edit3DAction *> Edit3DView::rightActions() const void Edit3DView::addQuick3DImport() { - const QList<Import> imports = model()->possibleImports(); - for (const auto &import : imports) { - if (import.url() == "QtQuick3D") { - model()->changeImports({import}, {}); - - // Subcomponent manager update needed to make item library entries appear - QmlDesignerPlugin::instance()->currentDesignDocument()->updateSubcomponentManager(); - return; + if (model()) { + const QList<Import> imports = model()->possibleImports(); + for (const auto &import : imports) { + if (import.url() == "QtQuick3D") { + model()->changeImports({import}, {}); + + // Subcomponent manager update needed to make item library entries appear + QmlDesignerPlugin::instance()->currentDesignDocument()->updateSubcomponentManager(); + return; + } } } Core::AsynchronousMessageBox::warning(tr("Failed to Add Import"), diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index d1646470b7..6c3ae892a9 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -58,6 +58,7 @@ public: void modelAttached(Model *model) override; void modelAboutToBeDetached(Model *model) override; void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override; + void customNotification(const AbstractView *view, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data) override; void sendInputEvent(QInputEvent *e) const; void edit3DViewResized(const QSize &size) const; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index c948227233..3d0d3f2f64 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -106,12 +106,11 @@ Edit3DWidget::Edit3DWidget(Edit3DView *view) : // Onboarding label contains instructions for new users how to get 3D content into the project m_onboardingLabel = new QLabel(this); QString labelText = - "No 3D import here yet!<br><br>" - "To create a 3D View you need to add the QtQuick3D import to your file.<br>" - "You can add the import via the QML Imports tab of the Library view, or alternatively click" - " <a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">here</span></a> " - "to add it straight away.<br><br>" - "If you want to import 3D assets from another tool, click on the \"Add New Assets...\" button in the Assets tab of the Library view."; + tr("Your file does not import Qt Quick 3D.<br><br>" + "To create a 3D view, add the QtQuick3D import to your file in the QML Imports tab of the Library view. Or click" + " <a href=\"#add_import\"><span style=\"text-decoration:none;color:%1\">here</span></a> " + "here to add it immediately.<br><br>" + "To import 3D assets from another tool, click on the \"Add New Assets...\" button in the Assets tab of the Library view."); m_onboardingLabel->setText(labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name())); m_onboardingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); connect(m_onboardingLabel, &QLabel::linkActivated, this, &Edit3DWidget::linkActivated); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditor.pri b/src/plugins/qmldesigner/components/formeditor/formeditor.pri index 3464ba3afe..4609b277f9 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditor.pri +++ b/src/plugins/qmldesigner/components/formeditor/formeditor.pri @@ -36,7 +36,8 @@ SOURCES += formeditoritem.cpp \ contentnoteditableindicator.cpp \ backgroundaction.cpp \ formeditortoolbutton.cpp \ - formeditorannotationicon.cpp + formeditorannotationicon.cpp \ + transitiontool.cpp HEADERS += formeditorscene.h \ formeditorwidget.h \ @@ -75,6 +76,7 @@ HEADERS += formeditorscene.h \ contentnoteditableindicator.h \ backgroundaction.h \ formeditortoolbutton.h \ - formeditorannotationicon.h + formeditorannotationicon.h \ + transitiontool.h RESOURCES += formeditor.qrc diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 96a4cc5adf..13ab98daff 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -26,16 +26,20 @@ #include "formeditoritem.h" #include "formeditorscene.h" +#include <variantproperty.h> #include <bindingproperty.h> #include <modelnode.h> #include <nodehints.h> #include <nodemetainfo.h> +#include <theme.h> + #include <utils/theme/theme.h> #include <utils/qtcassert.h> #include <QDebug> +#include <QFontDatabase> #include <QPainter> #include <QPainterPath> #include <QStyleOptionGraphicsItem> @@ -47,6 +51,36 @@ namespace QmlDesigner { const int flowBlockSize = 200; +const int blockRadius = 18; +const int blockAdjust = 40; + +const char startNodeIcon[] = "\u0055"; + +void drawIcon(QPainter *painter, + int x, + int y, + const QString &iconSymbol, + int fontSize, int iconSize, + const QColor &penColor) +{ + static QFontDatabase a; + + const QString fontName = "qtds_propertyIconFont.ttf"; + + Q_ASSERT(a.hasFamily(fontName)); + + if (a.hasFamily(fontName)) { + QFont font(fontName); + font.setPixelSize(fontSize); + + painter->save(); + painter->setPen(penColor); + painter->setFont(font); + painter->drawText(QRectF(x, y, iconSize, iconSize), iconSymbol); + + painter->restore(); + } +} FormEditorScene *FormEditorItem::scene() const { return qobject_cast<FormEditorScene*>(QGraphicsItem::scene()); @@ -419,6 +453,7 @@ void FormEditorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, } } + painter->setClipping(false); if (!qmlItemNode().isRootModelNode()) paintBoundingRect(painter); @@ -578,6 +613,7 @@ void FormEditorFlowActionItem::paint(QPainter *painter, const QStyleOptionGraphi return; painter->save(); + painter->setRenderHint(QPainter::Antialiasing); QPen pen; pen.setJoinStyle(Qt::MiterJoin); @@ -622,10 +658,9 @@ void FormEditorFlowActionItem::paint(QPainter *painter, const QStyleOptionGraphi fillColor = qmlItemNode().modelNode().auxiliaryData("fillColor").value<QColor>(); if (fillColor.alpha() > 0) - painter->fillRect(boundingRect(), fillColor); - - painter->drawRect(boundingRect()); + painter->setBrush(fillColor); + painter->drawRoundedRect(boundingRect(), blockRadius, blockRadius); painter->restore(); } @@ -719,7 +754,7 @@ void FormEditorTransitionItem::updateGeometry() QPointF toP = QmlItemNode(resolved.to).flowPosition(); if (QmlItemNode(resolved.to).isFlowDecision()) - sizeTo = QRectF(0, 0, flowBlockSize, flowBlockSize); + sizeTo = QRectF(0, 0, flowBlockSize * 2, flowBlockSize * 2); qreal x1 = fromP.x(); qreal x2 = toP.x(); @@ -757,10 +792,10 @@ QPointF FormEditorTransitionItem::instancePosition() const static bool verticalOverlap(const QRectF &from, const QRectF &to) { - if (from.top() < to.bottom() && (from.top() + from.height()) > to.top()) + if (from.top() < to.bottom() && from.bottom() > to.top()) return true; - if (to.top() < from.bottom() && (to.top() + to.height()) > from.top()) + if (to.top() < from.bottom() && to.bottom() > from.top()) return true; return false; @@ -769,25 +804,270 @@ static bool verticalOverlap(const QRectF &from, const QRectF &to) static bool horizontalOverlap(const QRectF &from, const QRectF &to) { - if (from.left() < to.right() && (from.left() + from.width()) > to.left()) + if (from.left() < to.right() && from.right() > to.left()) return true; - if (to.left() < from.right() && (to.left() + to.width()) > from.left()) + if (to.left() < from.right() && to.right() > from.left()) return true; return false; } +static void drawLabel(QPainter *painter, + const QPainterPath &path, + const QString &text, + const qreal percent, + const qreal offset, + bool flipSide) +{ + if (text.isEmpty()) + return; + + const int flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextDontClip; + + QPointF pos = path.pointAtPercent(percent); + qreal angle = path.angleAtPercent(percent); + + QLineF tmp(pos, QPointF(10, 10)); + tmp.setLength(offset); + tmp.setAngle(angle + (flipSide ? 270 : 90)); + + QRectF textRect(0, 0, 100, 50); + textRect.moveCenter(tmp.p2()); + + auto normalizeAngle = [](int angle) { + int newAngle = angle; + while (newAngle <= -90) newAngle += 180; + while (newAngle > 90) newAngle -= 180; + return newAngle; + }; + + painter->save(); + painter->translate(textRect.center()); + painter->rotate(-normalizeAngle(angle)); + painter->translate(-textRect.center()); + painter->drawText(textRect, flags, text); + painter->restore(); +} + +static void drawArrow(QPainter *painter, + const QPointF &point, + const qreal &angle, + int arrowLength, + int arrowWidth) +{ + const QPointF peakP(0, 0); + const QPointF leftP(-arrowLength, -arrowWidth * 0.5); + const QPointF rightP(-arrowLength, arrowWidth * 0.5); + + painter->save(); + + painter->translate(point); + painter->rotate(-angle); + painter->drawLine(leftP, peakP); + painter->drawLine(rightP, peakP); + + painter->restore(); +} + +static QPainterPath roundedCorner(const QPointF &s, + const QPointF &m, + const QPointF &e, + int radius) +{ + const QVector2D sm(m - s); + const QVector2D me(e - m); + const float smLength = sm.length(); + const float meLength = me.length(); + const int actualRadius = qMin(radius, static_cast<int>(qMin(smLength, meLength))); + const QVector2D smNorm = sm.normalized(); + const QVector2D meNorm = me.normalized(); + QRectF rect(m, QSizeF(actualRadius * 2, actualRadius * 2)); + + QPainterPath path(s); + + if (smNorm.y() < 0 && meNorm.x() > 0) { + rect.moveTopLeft(m); + path.arcTo(rect, 180, -90); + } else if (smNorm.x() < 0 && meNorm.y() > 0) { + rect.moveTopLeft(m); + path.arcTo(rect, 90, 90); + } else if (smNorm.y() > 0 && meNorm.x() > 0) { + rect.moveBottomLeft(m); + path.arcTo(rect, 180, 90); + } else if (smNorm.x() < 0 && meNorm.y() < 0) { + rect.moveBottomLeft(m); + path.arcTo(rect, 270, -90); + } else if (smNorm.x() > 0 && meNorm.y() > 0) { + rect.moveTopRight(m); + path.arcTo(rect, 90, -90); + } else if (smNorm.y() < 0 && meNorm.x() < 0) { + rect.moveTopRight(m); + path.arcTo(rect, 0, 90); + } else if (smNorm.y() > 0 && meNorm.x() < 0) { + rect.moveBottomRight(m); + path.arcTo(rect, 0, -90); + } else if (smNorm.x() > 0 && meNorm.y() < 0) { + rect.moveBottomRight(m); + path.arcTo(rect, 270, 90); + } + + path.lineTo(e); + return path; +} + +// This function determines whether the vertices are in cw or ccw order. +// It finds the lowest and rightmost vertex, and computes the cross-product +// of the vectors along its incident edges. +// Written by Joseph O'Rourke, 25 August 1995. orourke@cs.smith.edu +// 1: ccw +// 0: default +// -1: cw + +static int counterClockWise(const std::vector<QPointF> &points) +{ + if (points.empty()) + return 0; + + // FindLR finds the lowest, rightmost point. + auto findLR = [](const std::vector<QPointF> &points) { + int i = 0; + int m = 0; + QPointF min = points.front(); + + for (const auto p : points) { + if ((p.y() < min.y()) || ((p.y() == min.y()) && (p.x() > min.x()))) { + m = i; + min = p; + } + ++i; + } + return m; + }; + + const int m = findLR(points); + const int n = points.size(); + + // Determine previous and next point to m (the lowest, rightmost point). + const QPointF a = points[(m + (n - 1)) % n]; + const QPointF b = points[m]; + const QPointF c = points[(m + 1) % n]; + + const int area = a.x() * b.y() - a.y() * b.x() + + a.y() * c.x() - a.x() * c.y() + + b.x() * c.y() - c.x() * b.y(); + + if (area > 0) + return 1; + else if (area < 0) + return -1; + else + return 0; +} + +static QPainterPath quadBezier(const QPointF &s, + const QPointF &c, + const QPointF &e, + int bezier, + int breakOffset) +{ + QLineF se(s, e); + QPointF breakPoint = se.pointAt(breakOffset / 100.0); + QLineF breakLine; + + if (counterClockWise({s, c, e}) == 1) + breakLine = QLineF(breakPoint, breakPoint + QPointF(se.dy(), -se.dx())); + else + breakLine = QLineF(breakPoint, breakPoint + QPointF(-se.dy(), se.dx())); + + breakLine.setLength(se.length()); + + const QPointF controlPoint = breakLine.pointAt(bezier / 100.0); + + QPainterPath path(s); + path.quadTo(controlPoint, e); + + return path; +} + +static QPainterPath cubicBezier(const QPointF &s, + const QPointF &c1, + const QPointF &c2, + const QPointF &e, + int bezier) +{ + QPainterPath path(s); + const QPointF adjustedC1 = QLineF(s, c1).pointAt(bezier / 100.0); + const QPointF adjustedC2 = QLineF(e, c2).pointAt(bezier / 100.0); + + path.cubicTo(adjustedC1, adjustedC2, e); + + return path; +} + + +static QPainterPath lShapedConnection(const QPointF &start, + const QPointF &end, + Qt::Orientation orientation, + const ConnectionStyle &style) +{ + const QPointF mid = (orientation == Qt::Horizontal) ? QPointF(end.x(), start.y()) + : QPointF(start.x(), end.y()); + + if (style.type == ConnectionType::Default) { + if (style.radius == 0) { + QPainterPath path(start); + path.lineTo(mid); + path.lineTo(end); + return path; + } else { + return roundedCorner(start, mid, end, style.radius); + } + } else { + return quadBezier(start, mid, end, style.bezier, style.breakOffset); + } +} + +static QPainterPath sShapedConnection(const QPointF &start, + const QPointF &end, + Qt::Orientation orientation, + const ConnectionStyle &style) +{ + const qreal middleFactor = style.breakOffset / 100.0; + QPointF mid1; + QPointF mid2; + + if (orientation == Qt::Horizontal) { + mid1 = QPointF(start.x() * middleFactor + end.x() * (1 - middleFactor), start.y()); + mid2 = QPointF(mid1.x(), end.y()); + } else { + mid1 = QPointF(start.x(), start.y() * middleFactor + end.y() * (1 - middleFactor)); + mid2 = QPointF(end.x(), mid1.y()); + } + + if (style.type == ConnectionType::Default) { + if (style.radius == 0) { + QPainterPath path(start); + path.lineTo(mid1); + path.lineTo(mid2); + path.lineTo(end); + return path; + } else { + const QLineF breakLine(mid1, mid2); + QPainterPath path1 = roundedCorner(start, mid1, breakLine.center(), style.radius); + QPainterPath path2 = roundedCorner(breakLine.center(), mid2, end, style.radius); + return path1 + path2; + } + } else { + return cubicBezier(start, mid1, mid2, end, style.bezier); + } +} + static void paintConnection(QPainter *painter, const QRectF &from, const QRectF &to, - qreal width, - qreal adjustedWidth, - const QColor &color, - bool dash, - int startOffset, - int endOffset, - int breakOffset) + const ConnectionStyle &style, + const QString &label) { painter->save(); painter->setRenderHint(QPainter::Antialiasing); @@ -796,23 +1076,22 @@ static void paintConnection(QPainter *painter, pen.setCosmetic(true); pen.setJoinStyle(Qt::MiterJoin); pen.setCapStyle(Qt::RoundCap); + pen.setColor(style.color); - pen.setColor(color); - - if (dash) + if (style.dash) pen.setStyle(Qt::DashLine); else pen.setStyle(Qt::SolidLine); - pen.setWidthF(width); + pen.setWidthF(style.width); painter->setPen(pen); //const bool forceVertical = false; //const bool forceHorizontal = false; - const int padding = 2 * width + 2 * adjustedWidth; + const int padding = 2 * style.width + 2 * style.adjustedWidth; - const int arrowLength = 4 * adjustedWidth; - const int arrowWidth = 8 * adjustedWidth; + const int arrowLength = 4 * style.adjustedWidth; + const int arrowWidth = 8 * style.adjustedWidth; const bool boolExitRight = from.right() < to.center().x(); const bool boolExitBottom = from.bottom() < to.center().y(); @@ -824,10 +1103,6 @@ static void paintConnection(QPainter *painter, horizontalFirst = false; */ - const qreal middleFactor = breakOffset / 100.0; - - QPointF startP; - bool extraLine = false; if (horizontalFirst) { @@ -849,131 +1124,57 @@ static void paintConnection(QPainter *painter, } } - if (horizontalFirst) { - const qreal startY = from.center().y() + startOffset; - qreal startX = from.x() - padding; - if (boolExitRight) - startX = from.right() + padding; + QPointF startP; + QPointF endP; + QPainterPath path; + if (horizontalFirst) { + const qreal startX = boolExitRight ? from.right() + padding : from.x() - padding; + const qreal startY = from.center().y() + style.outOffset; startP = QPointF(startX, startY); - qreal endY = to.top() - padding; - - if (from.bottom() > to.y()) - endY = to.bottom() + padding; - if (!extraLine) { - - - const qreal endX = to.center().x() + endOffset; - - const QPointF midP(endX, startY); - - const QPointF endP(endX, endY); - - painter->drawLine(startP, midP); - painter->drawLine(midP, endP); - - int flip = 1; - - if (midP.y() < endP.y()) - flip = -1; - - pen.setStyle(Qt::SolidLine); - painter->setPen(pen); - painter->drawLine(endP + flip * QPoint(arrowWidth / 2, arrowLength), endP); - painter->drawLine(endP + flip * QPoint(-arrowWidth / 2, arrowLength), endP); + const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding; + endP = QPointF(to.center().x() + style.inOffset, endY); + path = lShapedConnection(startP, endP, Qt::Horizontal, style); } else { - - qreal endX = to.left() - padding; - - if (from.right() > to.x()) - endX = to.right() + padding; - - const qreal midX = startX * middleFactor + endX * (1-middleFactor); - const QPointF midP(midX, startY); - const QPointF midP2(midX, to.center().y() + endOffset); - const QPointF endP(endX, to.center().y() + endOffset); - painter->drawLine(startP, midP); - painter->drawLine(midP, midP2); - painter->drawLine(midP2, endP); - - int flip = 1; - - if (midP2.x() < endP.x()) - flip = -1; - - pen.setStyle(Qt::SolidLine); - painter->setPen(pen); - painter->drawLine(endP + flip * QPoint(arrowWidth / 2, arrowWidth / 2), endP); - painter->drawLine(endP + flip * QPoint(arrowLength, -arrowWidth / 2), endP); + const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding; + endP = QPointF(endX, to.center().y() + style.inOffset); + path = sShapedConnection(startP, endP, Qt::Horizontal, style); } - } else { - const qreal startX = from.center().x() + startOffset; - - qreal startY = from.top() - padding; - if (boolExitBottom) - startY = from.bottom() + padding; - + const qreal startX = from.center().x() + style.outOffset; + const qreal startY = boolExitBottom ? from.bottom() + padding : from.top() - padding; startP = QPointF(startX, startY); - qreal endX = to.left() - padding; - - if (from.right() > to.x()) - endX = to.right() + padding; if (!extraLine) { - const qreal endY = to.center().y() + endOffset; - - const QPointF midP(startX, endY); - - const QPointF endP(endX, endY); - - painter->drawLine(startP, midP); - painter->drawLine(midP, endP); - - int flip = 1; - - if (midP.x() < endP.x()) - flip = -1; - - pen.setStyle(Qt::SolidLine); - painter->setPen(pen); - painter->drawLine(endP + flip * QPoint(arrowWidth / 2, arrowWidth / 2), endP); - painter->drawLine(endP + flip * QPoint(arrowLength, -arrowWidth / 2), endP); + const qreal endX = (from.right() > to.x()) ? to.right() + padding : to.left() - padding; + endP = QPointF(endX, to.center().y() + style.inOffset); + path = lShapedConnection(startP, endP, Qt::Vertical, style); } else { + const qreal endY = (from.bottom() > to.y()) ? to.bottom() + padding : to.top() - padding; + endP = QPointF(to.center().x() + style.inOffset, endY); + path = sShapedConnection(startP, endP, Qt::Vertical, style); + } + } - qreal endY = to.top() - padding; - - if (from.bottom() > to.y()) - endY = to.bottom() + padding; - - const qreal midY = startY * middleFactor + endY * (1-middleFactor); - const QPointF midP(startX, midY); - const QPointF midP2(to.center().x() + endOffset, midY); - const QPointF endP(to.center().x() + endOffset, endY); + painter->drawPath(path); - painter->drawLine(startP, midP); - painter->drawLine(midP, midP2); - painter->drawLine(midP2, endP); + pen.setWidthF(style.width); + pen.setStyle(Qt::SolidLine); + painter->setPen(pen); - int flip = 1; + qreal anglePercent = 1.0; - if (midP2.y() < endP.y()) - flip = -1; + if (extraLine && style.bezier < 80) + anglePercent = 1.0 - qMin(1.0, (80 - style.bezier) / 10.0) * 0.05; - pen.setStyle(Qt::SolidLine); - painter->setPen(pen); - painter->drawLine(endP + flip * QPoint(arrowWidth / 2, arrowLength), endP); - painter->drawLine(endP + flip * QPoint(-arrowWidth / 2, arrowLength), endP); - } - } + drawArrow(painter, endP, path.angleAtPercent(anglePercent), arrowLength, arrowWidth); - pen.setWidthF(width); - pen.setStyle(Qt::SolidLine); - painter->setPen(pen); painter->setBrush(Qt::white); - painter->drawEllipse(startP, arrowLength / 3, arrowLength / 3); + painter->drawEllipse(startP, arrowLength / 3, arrowLength / 3); + + drawLabel(painter, path, label, style.labelPosition / 100.0, style.labelOffset, style.labelFlipSide); painter->restore(); } @@ -991,6 +1192,8 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + ResolveConnection resolved(qmlItemNode()); if (!resolved.from.modelNode().isValid()) @@ -1022,77 +1225,133 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi toRect.translate(QmlItemNode(resolved.to).flowPosition()); if (resolved.isStartLine) { - fromRect = QRectF(0,0,50,50); - fromRect.translate(QmlItemNode(resolved.to).flowPosition() + QPoint(-120, toRect.height() / 2 - 25)); + fromRect = QRectF(0, 0, 96, 96); + fromRect.translate(QmlItemNode(resolved.to).flowPosition() + QPoint(-180, toRect.height() / 2 - 96 / 2)); } toRect.translate(-pos()); fromRect.translate(-pos()); - qreal width = 2; + ConnectionStyle style; + + style.width = 2; const qreal scaleFactor = viewportTransform().m11(); if (qmlItemNode().modelNode().hasAuxiliaryData("width")) - width = qmlItemNode().modelNode().auxiliaryData("width").toInt(); + style.width = qmlItemNode().modelNode().auxiliaryData("width").toInt(); - qreal adjustedWidth = width / scaleFactor; + style.adjustedWidth = style.width / scaleFactor; if (qmlItemNode().modelNode().isSelected()) - width += 2; + style.width += 2; if (m_hitTest) - width *= 8; + style.width *= 8; - QColor color = "#e71919"; + style.color = "#e71919"; if (resolved.isStartLine) - color = "blue"; + style.color = "blue"; if (resolved.isWildcardLine) - color = "green"; - - bool dash = false; + style.color = "green"; if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionColor")) - color = qmlItemNode().rootModelNode().auxiliaryData("transitionColor").value<QColor>(); + style.color = qmlItemNode().rootModelNode().auxiliaryData("transitionColor").value<QColor>(); if (qmlItemNode().modelNode().hasAuxiliaryData("color")) - color = qmlItemNode().modelNode().auxiliaryData("color").value<QColor>(); + style.color = qmlItemNode().modelNode().auxiliaryData("color").value<QColor>(); + + style.dash = false; if (qmlItemNode().modelNode().hasAuxiliaryData("dash")) - dash = qmlItemNode().modelNode().auxiliaryData("dash").toBool(); + style.dash = qmlItemNode().modelNode().auxiliaryData("dash").toBool(); - int outOffset = 0; - int inOffset = 0; + style.outOffset = 0; + style.inOffset = 0; if (qmlItemNode().modelNode().hasAuxiliaryData("outOffset")) - outOffset = qmlItemNode().modelNode().auxiliaryData("outOffset").toInt(); + style.outOffset = qmlItemNode().modelNode().auxiliaryData("outOffset").toInt(); if (qmlItemNode().modelNode().hasAuxiliaryData("inOffset")) - inOffset = qmlItemNode().modelNode().auxiliaryData("inOffset").toInt(); + style.inOffset = qmlItemNode().modelNode().auxiliaryData("inOffset").toInt(); - int breakOffset = 50; + style.breakOffset = 50; if (qmlItemNode().modelNode().hasAuxiliaryData("breakPoint")) - breakOffset = qmlItemNode().modelNode().auxiliaryData("breakPoint").toInt(); + style.breakOffset = qmlItemNode().modelNode().auxiliaryData("breakPoint").toInt(); + + style.radius = 8; + + if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionRadius")) + style.radius = qmlItemNode().rootModelNode().auxiliaryData("transitionRadius").toInt(); + + if (qmlItemNode().modelNode().hasAuxiliaryData("radius")) + style.radius = qmlItemNode().modelNode().auxiliaryData("radius").toInt(); + + style.bezier = 50; + + if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionBezier")) + style.bezier = qmlItemNode().rootModelNode().auxiliaryData("transitionBezier").toInt(); + + if (qmlItemNode().modelNode().hasAuxiliaryData("bezier")) + style.bezier = qmlItemNode().modelNode().auxiliaryData("bezier").toInt(); + + style.type = ConnectionType::Default; + + if (qmlItemNode().rootModelNode().hasAuxiliaryData("transitionType")) + style.type = static_cast<ConnectionType>(qmlItemNode().rootModelNode().auxiliaryData("transitionType").toInt()); + + if (qmlItemNode().modelNode().hasAuxiliaryData("type")) + style.type = static_cast<ConnectionType>(qmlItemNode().modelNode().auxiliaryData("type").toInt()); + + + QFont font = painter->font(); + font.setPixelSize(16 / scaleFactor); + painter->setFont(font); + + QString label; + + if (qmlItemNode().modelNode().hasBindingProperty("condition")) + label = qmlItemNode().modelNode().bindingProperty("condition").expression(); + + if (qmlItemNode().modelNode().hasVariantProperty("question")) + label = qmlItemNode().modelNode().variantProperty("question").value().toString(); - paintConnection(painter, fromRect, toRect, width, adjustedWidth ,color, dash, outOffset, inOffset, breakOffset); + style.labelOffset = 14 / scaleFactor; + + style.labelPosition = 50.0; + + if (qmlItemNode().modelNode().hasAuxiliaryData("labelPosition")) + style.labelPosition = qmlItemNode().modelNode().auxiliaryData("labelPosition").toReal(); + + style.labelFlipSide = false; + + if (qmlItemNode().modelNode().hasAuxiliaryData("labelFlipSide")) + style.labelFlipSide = qmlItemNode().modelNode().auxiliaryData("labelFlipSide").toBool(); + + if (resolved.isStartLine) + fromRect.translate(0, style.outOffset); + + paintConnection(painter, fromRect, toRect, style, label); if (resolved.isStartLine) { + + const QString icon = Theme::getIconUnicode(Theme::startNode); + QPen pen; pen.setCosmetic(true); - - pen.setColor(color); + pen.setColor(style.color); painter->setPen(pen); - painter->drawRect(fromRect); - - if (scaleFactor > 0.4) { - painter->drawLine(fromRect.topRight() + QPoint(20,10), fromRect.bottomRight() + QPoint(20,-10)); - painter->drawLine(fromRect.topRight() + QPoint(25,12), fromRect.bottomRight() + QPoint(25,-12)); - painter->drawLine(fromRect.topRight() + QPoint(30,15), fromRect.bottomRight() + QPoint(30,-15)); - painter->drawLine(fromRect.topRight() + QPoint(35,17), fromRect.bottomRight() + QPoint(35,-17)); - painter->drawLine(fromRect.topRight() + QPoint(40,20), fromRect.bottomRight() + QPoint(40,-20)); - } + + const int iconAdjust = 48; + const int offset = 96; + const int size = fromRect.width(); + const int iconSize = size - iconAdjust; + const int x = fromRect.topRight().x() - offset; + const int y = fromRect.topRight().y(); + painter->drawRoundedRect(x, y , size - 10, size, size / 2, iconSize / 2); + drawIcon(painter, x + iconAdjust / 2, y + iconAdjust / 2, icon, iconSize, iconSize, style.color); } painter->restore(); @@ -1141,6 +1400,9 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + painter->setRenderHint(QPainter::SmoothPixmapTransform); + QPen pen; pen.setJoinStyle(Qt::MiterJoin); pen.setCosmetic(true); @@ -1179,20 +1441,37 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap if (qmlItemNode().modelNode().hasAuxiliaryData("fillColor")) fillColor = qmlItemNode().modelNode().auxiliaryData("fillColor").value<QColor>(); + painter->save(); + + if (m_iconType == DecisionIcon) { + painter->translate(boundingRect().center()); + painter->rotate(45); + painter->translate(-boundingRect().center()); + } + if (fillColor.alpha() > 0) - painter->fillRect(boundingRect(), fillColor); + painter->setBrush(fillColor); + + int radius = blockRadius; + + const QRectF adjustedRect = boundingRect().adjusted(blockAdjust, + blockAdjust, + -blockAdjust, + -blockAdjust); - painter->drawLine(boundingRect().left(), boundingRect().center().y(), - boundingRect().center().x(), boundingRect().top()); + painter->drawRoundedRect(adjustedRect, radius, radius); - painter->drawLine(boundingRect().center().x(), boundingRect().top(), - boundingRect().right(), boundingRect().center().y()); + const int iconDecrement = 32; + const int iconSize = adjustedRect.width() - iconDecrement; + const int offset = iconDecrement / 2 + blockAdjust; + + painter->restore(); - painter->drawLine(boundingRect().right(), boundingRect().center().y(), - boundingRect().center().x(), boundingRect().bottom()); + const QString icon = (m_iconType == + WildcardIcon) ? Theme::getIconUnicode(Theme::wildcard) + : Theme::getIconUnicode(Theme::decisionNode); - painter->drawLine(boundingRect().center().x(), boundingRect().bottom(), - boundingRect().left(), boundingRect().center().y()); + drawIcon(painter, offset, offset, icon, iconSize, iconSize, flowColor); painter->restore(); } diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index d725afd0b0..a2a491fdbf 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -47,6 +47,30 @@ namespace Internal { class MoveController; } +enum ConnectionType +{ + Default = 0, + Bezier +}; + +class ConnectionStyle +{ +public: + qreal width; + qreal adjustedWidth; + QColor color; + bool dash; + int outOffset; + int inOffset; + int breakOffset; + int radius; + int bezier; + ConnectionType type; + qreal labelOffset; + qreal labelPosition; + bool labelFlipSide; +}; + class QMLDESIGNERCORE_EXPORT FormEditorItem : public QGraphicsItem { friend class QmlDesigner::FormEditorScene; @@ -207,9 +231,17 @@ public: bool flowHitTest(const QPointF &point) const override; protected: - FormEditorFlowDecisionItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene) - : FormEditorFlowItem(qmlItemNode, scene) + enum IconType { + DecisionIcon, + WildcardIcon + }; + + FormEditorFlowDecisionItem(const QmlItemNode &qmlItemNode, + FormEditorScene* scene, + IconType iconType = DecisionIcon) + : FormEditorFlowItem(qmlItemNode, scene), m_iconType(iconType) {} + IconType m_iconType; }; class FormEditorFlowWildcardItem : FormEditorFlowDecisionItem @@ -221,8 +253,9 @@ public: protected: FormEditorFlowWildcardItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene) - : FormEditorFlowDecisionItem(qmlItemNode, scene) - {} + : FormEditorFlowDecisionItem(qmlItemNode, scene, WildcardIcon) + { + } }; inline int FormEditorItem::type() const diff --git a/src/plugins/qmldesigner/components/formeditor/resizemanipulator.cpp b/src/plugins/qmldesigner/components/formeditor/resizemanipulator.cpp index 49bb98db4a..8907aab2bc 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizemanipulator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/resizemanipulator.cpp @@ -97,8 +97,11 @@ void ResizeManipulator::begin(const QPointF &/*beginPoint*/) // return QSizeF(sizeAsPoint.x(), sizeAsPoint.y()); //} -void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping useSnapping) +void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping useSnapping, Qt::KeyboardModifiers keyMods) { + const bool preserveAspectRatio = keyMods.testFlag(Qt::ShiftModifier); + const bool resizeFromCenter = keyMods.testFlag(Qt::AltModifier); + const double minimumWidth = 0.0; const double minimumHeight = 0.0; @@ -118,6 +121,16 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use QmlAnchors anchors(formEditorItem->qmlItemNode().anchors()); QRectF boundingRect(m_beginBoundingRect); + + auto getRatioSizes = [&](){ + double ratio = std::min(boundingRect.width() / m_beginBoundingRect.width(), + boundingRect.height() / m_beginBoundingRect.height()); + double newW = m_beginBoundingRect.width() * ratio; + double newH = m_beginBoundingRect.height() * ratio; + + return QSizeF(newW, newH); + }; + if (m_resizeHandle->isBottomRightHandle()) { boundingRect.setBottomRight(updatePointInLocalSpace); @@ -132,6 +145,28 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use } boundingRect.setBottomRight(updatePointInLocalSpace); + if (preserveAspectRatio) { + QSizeF newSize = getRatioSizes(); + + updatePointInLocalSpace.rx() = (boundingRect.topLeft().x() + newSize.width()); + updatePointInLocalSpace.ry() = (boundingRect.topLeft().y() + newSize.height()); + + boundingRect.setBottomRight(updatePointInLocalSpace); + } + + if (resizeFromCenter) { + QPointF grow = { boundingRect.width() - m_beginBoundingRect.width(), + boundingRect.height() - m_beginBoundingRect.height() }; + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineTop)) + boundingRect.setTop(boundingRect.top() - grow.y()); + if (!anchors.instanceHasAnchor(AnchorLineLeft)) + boundingRect.setLeft(boundingRect.left() - grow.x()); + } + } + if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) boundingRect.setLeft(boundingRect.left() - (updatePointInLocalSpace.x() - m_beginBoundingRect.right())); @@ -144,6 +179,7 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use boundingRect.setHeight(minimumHeight); formEditorItem->qmlItemNode().setSize(boundingRect.size()); + formEditorItem->qmlItemNode().setPosition(m_beginToParentTransform.map(boundingRect.topLeft())); if (anchors.instanceHasAnchor(AnchorLineBottom)) { anchors.setMargin(AnchorLineBottom, @@ -168,6 +204,28 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use } boundingRect.setTopLeft(updatePointInLocalSpace); + if (preserveAspectRatio) { + QSizeF newSize = getRatioSizes(); + + updatePointInLocalSpace.rx() = (boundingRect.bottomRight().x() - newSize.width()); + updatePointInLocalSpace.ry() = (boundingRect.bottomRight().y() - newSize.height()); + + boundingRect.setTopLeft(updatePointInLocalSpace); + } + + if (resizeFromCenter) { + QPointF grow = { boundingRect.width() - m_beginBoundingRect.width(), + boundingRect.height() - m_beginBoundingRect.height() }; + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineBottom)) + boundingRect.setBottom(boundingRect.bottom() + grow.y()); + if (!anchors.instanceHasAnchor(AnchorLineRight)) + boundingRect.setRight(boundingRect.right() + grow.x()); + } + } + if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) boundingRect.setRight(boundingRect.right() - (updatePointInLocalSpace.x() - m_beginBoundingRect.left())); @@ -208,6 +266,28 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use } boundingRect.setTopRight(updatePointInLocalSpace); + if (preserveAspectRatio) { + QSizeF newSize = getRatioSizes(); + + updatePointInLocalSpace.rx() = (boundingRect.bottomLeft().x() + newSize.width()); + updatePointInLocalSpace.ry() = (boundingRect.bottomLeft().y() - newSize.height()); + + boundingRect.setTopRight(updatePointInLocalSpace); + } + + if (resizeFromCenter) { + QPointF grow = { boundingRect.width() - m_beginBoundingRect.width(), + boundingRect.height() - m_beginBoundingRect.height() }; + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineBottom)) + boundingRect.setBottom(boundingRect.bottom() + grow.y()); + if (!anchors.instanceHasAnchor(AnchorLineLeft)) + boundingRect.setLeft(boundingRect.left() - grow.x()); + } + } + if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) boundingRect.setLeft(boundingRect.left() - (updatePointInLocalSpace.x() - m_beginBoundingRect.right())); @@ -246,6 +326,28 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use boundingRect.setBottomLeft(updatePointInLocalSpace); + if (preserveAspectRatio) { + QSizeF newSize = getRatioSizes(); + + updatePointInLocalSpace.rx() = (boundingRect.topRight().x() - newSize.width()); + updatePointInLocalSpace.ry() = (boundingRect.topRight().y() + newSize.height()); + + boundingRect.setBottomLeft(updatePointInLocalSpace); + } + + if (resizeFromCenter) { + QPointF grow = { boundingRect.width() - m_beginBoundingRect.width(), + boundingRect.height() - m_beginBoundingRect.height() }; + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineTop)) + boundingRect.setTop(boundingRect.top() - grow.y()); + if (!anchors.instanceHasAnchor(AnchorLineRight)) + boundingRect.setRight(boundingRect.right() + grow.x()); + } + } + if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) boundingRect.setRight(boundingRect.right() - (updatePointInLocalSpace.x() - m_beginBoundingRect.left())); @@ -280,13 +382,30 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use boundingRect.setBottom(updatePointInLocalSpace.y()); + if (resizeFromCenter) { + double grow = boundingRect.height() - m_beginBoundingRect.height(); + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineTop)) + boundingRect.setTop(boundingRect.top() - grow); + if (!anchors.instanceHasAnchor(AnchorLineLeft)) + boundingRect.setLeft(boundingRect.left() - grow); + if (!anchors.instanceHasAnchor(AnchorLineRight)) + boundingRect.setRight(boundingRect.right() + grow); + } + } + if (anchors.instanceHasAnchor(AnchorLineVerticalCenter)) boundingRect.setTop(boundingRect.top() - (updatePointInLocalSpace.y() - m_beginBoundingRect.bottom())); + if (boundingRect.width() < minimumWidth) + boundingRect.setWidth(minimumWidth); if (boundingRect.height() < minimumHeight) boundingRect.setHeight(minimumHeight); formEditorItem->qmlItemNode().setSize(boundingRect.size()); + formEditorItem->qmlItemNode().setPosition(m_beginToParentTransform.map(boundingRect.topLeft())); if (anchors.instanceHasAnchor(AnchorLineBottom)) { anchors.setMargin(AnchorLineBottom, @@ -303,9 +422,25 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use boundingRect.setTop(updatePointInLocalSpace.y()); + if (resizeFromCenter) { + double grow = boundingRect.height() - m_beginBoundingRect.height(); + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineBottom)) + boundingRect.setBottom(boundingRect.bottom() + grow); + if (!anchors.instanceHasAnchor(AnchorLineLeft)) + boundingRect.setLeft(boundingRect.left() - grow); + if (!anchors.instanceHasAnchor(AnchorLineRight)) + boundingRect.setRight(boundingRect.right() + grow); + } + } + if (anchors.instanceHasAnchor(AnchorLineVerticalCenter)) boundingRect.setBottom(boundingRect.bottom() - (updatePointInLocalSpace.y() - m_beginBoundingRect.top())); + if (boundingRect.width() < minimumWidth) + boundingRect.setWidth(minimumWidth); if (boundingRect.height() < minimumHeight) boundingRect.setTop(boundingRect.top() - minimumHeight + boundingRect.height()); @@ -327,13 +462,30 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use boundingRect.setRight(updatePointInLocalSpace.x()); + if (resizeFromCenter) { + double grow = boundingRect.width() - m_beginBoundingRect.width(); + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineTop)) + boundingRect.setTop(boundingRect.top() - grow); + if (!anchors.instanceHasAnchor(AnchorLineLeft)) + boundingRect.setLeft(boundingRect.left() - grow); + if (!anchors.instanceHasAnchor(AnchorLineBottom)) + boundingRect.setBottom(boundingRect.bottom() + grow); + } + } + if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) boundingRect.setLeft(boundingRect.left() - (updatePointInLocalSpace.x() - m_beginBoundingRect.right())); if (boundingRect.width() < minimumWidth) boundingRect.setWidth(minimumWidth); + if (boundingRect.height() < minimumHeight) + boundingRect.setHeight(minimumHeight); formEditorItem->qmlItemNode().setSize(boundingRect.size()); + formEditorItem->qmlItemNode().setPosition(m_beginToParentTransform.map(boundingRect.topLeft())); if (anchors.instanceHasAnchor(AnchorLineRight)) { @@ -351,11 +503,27 @@ void ResizeManipulator::update(const QPointF& updatePoint, Snapper::Snapping use boundingRect.setLeft(updatePointInLocalSpace.x()); + if (resizeFromCenter) { + double grow = boundingRect.width() - m_beginBoundingRect.width(); + + if (!anchors.instanceHasAnchor(AnchorLineHorizontalCenter) + && !anchors.instanceHasAnchor(AnchorLineVerticalCenter)) { + if (!anchors.instanceHasAnchor(AnchorLineTop)) + boundingRect.setTop(boundingRect.top() - grow); + if (!anchors.instanceHasAnchor(AnchorLineBottom)) + boundingRect.setBottom(boundingRect.bottom() + grow); + if (!anchors.instanceHasAnchor(AnchorLineRight)) + boundingRect.setRight(boundingRect.right() + grow); + } + } + if (anchors.instanceHasAnchor(AnchorLineHorizontalCenter)) boundingRect.setRight(boundingRect.right() - (updatePointInLocalSpace.x() - m_beginBoundingRect.left())); if (boundingRect.width() < minimumWidth) boundingRect.setLeft(boundingRect.left() - minimumWidth + boundingRect.width()); + if (boundingRect.height() < minimumHeight) + boundingRect.setHeight(minimumHeight); formEditorItem->qmlItemNode().setSize(boundingRect.size()); formEditorItem->qmlItemNode().setPosition(m_beginToParentTransform.map(boundingRect.topLeft())); diff --git a/src/plugins/qmldesigner/components/formeditor/resizemanipulator.h b/src/plugins/qmldesigner/components/formeditor/resizemanipulator.h index ab4f97f9cb..551006d161 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizemanipulator.h +++ b/src/plugins/qmldesigner/components/formeditor/resizemanipulator.h @@ -46,7 +46,8 @@ public: void removeHandle(); void begin(const QPointF& beginPoint); - void update(const QPointF& updatePoint, Snapper::Snapping useSnapping); + void update(const QPointF& updatePoint, Snapper::Snapping useSnapping, + Qt::KeyboardModifiers keyMods = Qt::NoModifier); void end(Snapper::Snapping useSnapping); void moveBy(double deltaX, double deltaY); diff --git a/src/plugins/qmldesigner/components/formeditor/resizetool.cpp b/src/plugins/qmldesigner/components/formeditor/resizetool.cpp index 8b4e5c552a..e959a4b6b1 100644 --- a/src/plugins/qmldesigner/components/formeditor/resizetool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/resizetool.cpp @@ -71,7 +71,8 @@ void ResizeTool::mouseMoveEvent(const QList<QGraphicsItem*> &, QGraphicsSceneMouseEvent *event) { if (m_resizeManipulator.isActive()) - m_resizeManipulator.update(event->scenePos(), generateUseSnapping(event->modifiers())); + m_resizeManipulator.update(event->scenePos(), generateUseSnapping(event->modifiers()), + event->modifiers()); } void ResizeTool::hoverMoveEvent(const QList<QGraphicsItem*> &itemList, diff --git a/src/plugins/qmldesigner/components/formeditor/transitiontool.cpp b/src/plugins/qmldesigner/components/formeditor/transitiontool.cpp new file mode 100644 index 0000000000..e8222e8ac8 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/transitiontool.cpp @@ -0,0 +1,438 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#include "transitiontool.h" + +#include <formeditorscene.h> +#include <formeditorview.h> +#include <formeditorwidget.h> +#include <itemutilfunctions.h> +#include <formeditoritem.h> +#include <layeritem.h> + +#include <resizehandleitem.h> + +#include <bindingproperty.h> +#include <nodeabstractproperty.h> +#include <nodelistproperty.h> +#include <nodemetainfo.h> +#include <qmlitemnode.h> +#include <qmldesignerplugin.h> +#include <abstractaction.h> +#include <designeractionmanager.h> +#include <variantproperty.h> +#include <rewritingexception.h> +#include <rewritertransaction.h> + +#include <coreplugin/icore.h> +#include <utils/qtcassert.h> + +#include <QApplication> +#include <QGraphicsSceneMouseEvent> +#include <QAction> +#include <QMessageBox> +#include <QPair> +#include <QGraphicsSceneMouseEvent> + +namespace QmlDesigner { + +static bool isTransitionSource(const ModelNode &node) +{ + return QmlFlowTargetNode::isFlowEditorTarget(node); +} + +static bool isTransitionTarget(const QmlItemNode &node) +{ + return QmlFlowTargetNode::isFlowEditorTarget(node) + && !node.isFlowActionArea() + && !node.isFlowWildcard(); +} + +class TransitionToolAction : public AbstractAction +{ +public: + TransitionToolAction(const QString &name) : AbstractAction(name) {} + + QByteArray category() const override + { + return QByteArray(); + } + + QByteArray menuId() const override + { + return "TransitionTool"; + } + + int priority() const override + { + return CustomActionsPriority; + } + + Type type() const override + { + return ContextMenuAction; + } + +protected: + bool isVisible(const SelectionContext &selectionContext) const override + { + if (selectionContext.scenePosition().isNull()) + return false; + + if (selectionContext.singleNodeIsSelected()) + return isTransitionSource(selectionContext.currentSingleSelectedNode()); + + return false; + } + + bool isEnabled(const SelectionContext &selectionContext) const override + { + return isVisible(selectionContext); + } +}; + +class TransitionCustomAction : public TransitionToolAction +{ +public: + TransitionCustomAction(const QString &name) : TransitionToolAction(name) {} + + QByteArray category() const override + { + return ComponentCoreConstants::flowCategory; + } + + SelectionContext selectionContext() const + { + return AbstractAction::selectionContext(); + } + +}; + +static QRectF paintedBoundingRect(FormEditorItem *item) +{ + QRectF boundingRect = item->qmlItemNode().instanceBoundingRect(); + if (boundingRect.width() < 4) + boundingRect = item->boundingRect(); + return boundingRect; +} + +static QPointF centerPoint(FormEditorItem *item) +{ + QRectF boundingRect = paintedBoundingRect(item); + return QPointF(item->scenePos().x() + boundingRect.width() / 2, + item->scenePos().y() + boundingRect.height() / 2); +} + +void static setToBoundingRect(QGraphicsRectItem *rect, FormEditorItem *item) +{ + QPolygonF boundingRectInSceneSpace(item->mapToScene(paintedBoundingRect(item))); + rect->setRect(boundingRectInSceneSpace.boundingRect()); +} + +TransitionTool::TransitionTool() + : QObject(), AbstractCustomTool() +{ + + TransitionToolAction *transitionToolAction = new TransitionToolAction(tr("Add Transition")); + QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(transitionToolAction); + + connect(transitionToolAction->action(), &QAction::triggered, + this, &TransitionTool::activateTool); + + TransitionCustomAction *removeAction = new TransitionCustomAction(tr("Remove Transitions")); + QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(removeAction); + + connect(removeAction->action(), &QAction::triggered, + this, [removeAction](){ + + SelectionContext context = removeAction->selectionContext(); + QmlFlowTargetNode node = QmlFlowTargetNode(context.currentSingleSelectedNode()); + + context.view()->executeInTransaction("Remove Transitions", [&node](){ + if (node.isValid()) + node.removeTransitions(); + }); + }); + + TransitionCustomAction *removeAllTransitionsAction = new TransitionCustomAction(tr("Remove All Transitions")); + QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(removeAllTransitionsAction); + + connect(removeAllTransitionsAction->action(), &QAction::triggered, + this, [removeAllTransitionsAction](){ + + if (QMessageBox::question(Core::ICore::dialogParent(), + tr("Remove All Transitions"), + tr("Do you really want to remove all transitions?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + SelectionContext context = removeAllTransitionsAction->selectionContext(); + QmlFlowTargetNode node = QmlFlowTargetNode(context.currentSingleSelectedNode()); + + context.view()->executeInTransaction("Remove All Transitions", [&node](){ + if (node.isValid() && node.flowView().isValid()) + node.flowView().removeAllTransitions(); + }); + }); + + TransitionCustomAction *removeDanglingTransitionAction = new TransitionCustomAction(tr("Remove Dangling Transitions")); + QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(removeDanglingTransitionAction); + + connect(removeDanglingTransitionAction->action(), &QAction::triggered, + this, [removeDanglingTransitionAction](){ + + SelectionContext context = removeDanglingTransitionAction->selectionContext(); + QmlFlowTargetNode node = QmlFlowTargetNode(context.currentSingleSelectedNode()); + + context.view()->executeInTransaction("Remove Dangling Transitions", [&node](){ + if (node.isValid() && node.flowView().isValid()) + node.flowView().removeDanglingTransitions(); + }); + }); +} + +TransitionTool::~TransitionTool() +{ +} + +void TransitionTool::clear() +{ + m_lineItem.reset(nullptr); + m_rectangleItem1.reset(nullptr); + m_rectangleItem2.reset(nullptr); + + AbstractFormEditorTool::clear(); +} + +void TransitionTool::mousePressEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) +{ + if (m_blockEvents) + return; + + if (event->button() != Qt::LeftButton) + return; + + AbstractFormEditorTool::mousePressEvent(itemList, event); + TransitionTool::mouseMoveEvent(itemList, event); +} + +void TransitionTool::mouseMoveEvent(const QList<QGraphicsItem*> & itemList, + QGraphicsSceneMouseEvent * event) +{ + if (!m_lineItem) + return; + + QTC_ASSERT(currentFormEditorItem(), return); + + const QPointF pos = centerPoint(m_formEditorItem); + lineItem()->setLine(pos.x(), + pos.y(), + event->scenePos().x(), + event->scenePos().y()); + + FormEditorItem *formEditorItem = nearestFormEditorItem(event->scenePos(), itemList); + + if (formEditorItem + && formEditorItem->qmlItemNode().isValid() + && isTransitionTarget(formEditorItem->qmlItemNode().modelNode())) { + rectangleItem2()->setVisible(true); + setToBoundingRect(rectangleItem2(), formEditorItem); + } else { + rectangleItem2()->setVisible(false); + } +} + +void TransitionTool::hoverMoveEvent(const QList<QGraphicsItem*> & itemList, + QGraphicsSceneMouseEvent *event) +{ + mouseMoveEvent(itemList, event); +} + +void TransitionTool::keyPressEvent(QKeyEvent * /*keyEvent*/) +{ +} + +void TransitionTool::keyReleaseEvent(QKeyEvent * /*keyEvent*/) +{ + view()->changeToSelectionTool(); +} + +void TransitionTool::dragLeaveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ +} + +void TransitionTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) +{ +} + +void TransitionTool::mouseReleaseEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) +{ + if (m_blockEvents) + return; + + if (event->button() == Qt::LeftButton) { + FormEditorItem *formEditorItem = nearestFormEditorItem(event->scenePos(), itemList); + + if (formEditorItem + && QmlFlowTargetNode(formEditorItem->qmlItemNode().modelNode()).isValid()) + createTransition(m_formEditorItem, formEditorItem); + } + + view()->changeToSelectionTool(); +} + + +void TransitionTool::mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList, QGraphicsSceneMouseEvent *event) +{ + AbstractFormEditorTool::mouseDoubleClickEvent(itemList, event); +} + +void TransitionTool::itemsAboutToRemoved(const QList<FormEditorItem*> &) +{ + view()->changeCurrentToolTo(this); +} + +void TransitionTool::selectedItemsChanged(const QList<FormEditorItem*> &itemList) +{ + if (!itemList.isEmpty()) { + createItems(); + + m_formEditorItem = itemList.first(); + setToBoundingRect(rectangleItem1(), m_formEditorItem); + } +} + +void TransitionTool::instancesCompleted(const QList<FormEditorItem*> & /*itemList*/) +{ +} + +void TransitionTool::instancesParentChanged(const QList<FormEditorItem *> & /*itemList*/) +{ +} + +void TransitionTool::instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > & /*propertyList*/) +{ +} + +void TransitionTool::formEditorItemsChanged(const QList<FormEditorItem*> & /*itemList*/) +{ +} + +int TransitionTool::wantHandleItem(const ModelNode &modelNode) const +{ + if (isTransitionSource(modelNode)) + return 10; + + return 0; +} + +QString TransitionTool::name() const +{ + return tr("Transition Tool"); +} + +void TransitionTool::activateTool() +{ + view()->changeToCustomTool(); +} + +void TransitionTool::unblock() +{ + m_blockEvents = false; +} + +QGraphicsLineItem *TransitionTool::lineItem() +{ + return m_lineItem.get(); +} + +QGraphicsRectItem *TransitionTool::rectangleItem1() +{ + return m_rectangleItem1.get(); +} + +QGraphicsRectItem *TransitionTool::rectangleItem2() +{ + return m_rectangleItem2.get(); +} + +FormEditorItem *TransitionTool::currentFormEditorItem() const +{ + if (scene()->items().contains(m_formEditorItem)) + return m_formEditorItem; + + return nullptr; +} + +void TransitionTool::createItems() { + m_blockEvents = true; + QTimer::singleShot(200, this, [this](){ unblock(); }); + + if (!lineItem()) + m_lineItem.reset(new QGraphicsLineItem(scene()->manipulatorLayerItem())); + + if (!rectangleItem1()) + m_rectangleItem1.reset(new QGraphicsRectItem(scene()->manipulatorLayerItem())); + + if (!rectangleItem2()) + m_rectangleItem2.reset(new QGraphicsRectItem(scene()->manipulatorLayerItem())); + + m_rectangleItem2->setVisible(false); + + QPen pen; + pen.setColor(QColor(Qt::lightGray)); + pen.setStyle(Qt::DashLine); + pen.setWidth(0); + m_lineItem->setPen(pen); + + pen.setColor(QColor(108, 141, 221)); + pen.setStyle(Qt::SolidLine); + pen.setWidth(4); + pen.setCosmetic(true); + m_rectangleItem1->setPen(pen); + + m_rectangleItem2->setPen(pen); +} + +void TransitionTool::createTransition(FormEditorItem *source, FormEditorItem *target) +{ + QmlFlowTargetNode sourceNode(source->qmlItemNode().modelNode()); + QmlFlowTargetNode targetNode(target->qmlItemNode().modelNode()); + + if (sourceNode.isValid() && targetNode.isValid() + && sourceNode != targetNode + && !targetNode.isFlowActionArea() + && !targetNode.isFlowWildcard()) { + view()->executeInTransaction("create transition", [&sourceNode, targetNode](){ + sourceNode.assignTargetItem(targetNode); + }); + } else { + qWarning() << Q_FUNC_INFO << "nodes invalid"; + } +} + +} diff --git a/src/plugins/qmldesigner/components/formeditor/transitiontool.h b/src/plugins/qmldesigner/components/formeditor/transitiontool.h new file mode 100644 index 0000000000..43c3894933 --- /dev/null +++ b/src/plugins/qmldesigner/components/formeditor/transitiontool.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#pragma once + +#include "abstractcustomtool.h" +#include "selectionindicator.h" + +#include <QGraphicsLineItem> +#include <QHash> +#include <QPointer> + +#include <memory> + +namespace QmlDesigner { + +class TransitionTool : public QObject, public AbstractCustomTool +{ + Q_OBJECT +public: + TransitionTool(); + ~TransitionTool(); + + void mousePressEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void hoverMoveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *keyEvent) override; + + void dragLeaveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneDragDropEvent * event) override; + void dragMoveEvent(const QList<QGraphicsItem*> &itemList, + QGraphicsSceneDragDropEvent * event) override; + + void itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) override; + + void selectedItemsChanged(const QList<FormEditorItem*> &itemList) override; + + void instancesCompleted(const QList<FormEditorItem*> &itemList) override; + void instancesParentChanged(const QList<FormEditorItem *> &itemList) override; + void instancePropertyChange(const QList<QPair<ModelNode, PropertyName> > &propertyList) override; + + void clear() override; + + void formEditorItemsChanged(const QList<FormEditorItem*> &itemList) override; + + int wantHandleItem(const ModelNode &modelNode) const override; + + QString name() const override; + + void activateTool(); + void unblock(); + + QGraphicsLineItem *lineItem(); + QGraphicsRectItem *rectangleItem1(); + QGraphicsRectItem *rectangleItem2(); + +private: + FormEditorItem *currentFormEditorItem() const; + void createItems(); + void createTransition(FormEditorItem *item1, FormEditorItem *item2); + + FormEditorItem* m_formEditorItem; + std::unique_ptr<QGraphicsLineItem> m_lineItem; + std::unique_ptr<QGraphicsRectItem> m_rectangleItem1; + std::unique_ptr<QGraphicsRectItem> m_rectangleItem2; + bool m_blockEvents = true; +}; + +} //QmlDesigner diff --git a/src/plugins/qmldesigner/components/importmanager/importmanagerview.cpp b/src/plugins/qmldesigner/components/importmanager/importmanagerview.cpp index df9590af64..c2bce0b0ce 100644 --- a/src/plugins/qmldesigner/components/importmanager/importmanagerview.cpp +++ b/src/plugins/qmldesigner/components/importmanager/importmanagerview.cpp @@ -81,14 +81,16 @@ void ImportManagerView::modelAboutToBeDetached(Model *model) void ImportManagerView::importsChanged(const QList<Import> &/*addedImports*/, const QList<Import> &/*removedImports*/) { - if (m_importsWidget) + if (m_importsWidget) { m_importsWidget->setImports(model()->imports()); + // setImports recreates labels, so we need to update used imports, as it is not guaranteed + // usedImportsChanged notification will come after this. + m_importsWidget->setUsedImports(model()->usedImports()); + } } void ImportManagerView::possibleImportsChanged(const QList<Import> &/*possibleImports*/) { - QmlDesignerPlugin::instance()->currentDesignDocument()->updateSubcomponentManager(); - if (m_importsWidget) m_importsWidget->setPossibleImports(model()->possibleImports()); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index 3f403d0313..0beb5bce72 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -32,6 +32,9 @@ #include "utils/outputformatter.h" #include "theme.h" +#include <projectexplorer/project.h> +#include <projectexplorer/session.h> + #include <QtCore/qfileinfo.h> #include <QtCore/qdir.h> #include <QtCore/qloggingcategory.h> @@ -97,6 +100,20 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &im ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true); + QStringList importPaths; + auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); + if (doc) { + Model *model = doc->currentModel(); + if (model) + importPaths = model->importPaths(); + } + + QString targetDir = defaulTargetDirectory; + + ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(doc->fileName()); + if (currentProject) + targetDir = currentProject->projectDirectory().toString(); + // Import is always done under known folder. The order of preference for folder is: // 1) An existing QUICK_3D_ASSETS_FOLDER under DEFAULT_ASSET_IMPORT_FOLDER project import path // 2) An existing QUICK_3D_ASSETS_FOLDER under any project import path @@ -105,19 +122,11 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &im // 5) New QUICK_3D_ASSETS_FOLDER under new DEFAULT_ASSET_IMPORT_FOLDER under project const QString defaultAssetFolder = QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER); const QString quick3DFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER); - QString candidatePath = defaulTargetDirectory + defaultAssetFolder + quick3DFolder; + QString candidatePath = targetDir + defaultAssetFolder + quick3DFolder; int candidatePriority = 5; - QStringList importPaths; - - auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); - if (doc) { - Model *model = doc->currentModel(); - if (model) - importPaths = model->importPaths(); - } for (auto importPath : qAsConst(importPaths)) { - if (importPath.startsWith(defaulTargetDirectory)) { + if (importPath.startsWith(targetDir)) { const bool isDefaultFolder = importPath.endsWith(defaultAssetFolder); const QString assetFolder = importPath + quick3DFolder; const bool exists = QFileInfo(assetFolder).exists(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 34fa71222d..6a5edb18a3 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -280,7 +280,16 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar return; } + QString originalAssetName = assetName; if (targetDir.exists(assetName)) { + // If we have a file system with case insensitive filenames, assetName may be + // different from the existing name. Modify assetName to ensure exact match to + // the overwritten old asset capitalization + const QStringList assetDirs = targetDir.entryList({assetName}, QDir::Dirs); + if (assetDirs.size() == 1) { + assetName = assetDirs[0]; + targetDirPath = targetDir.filePath(assetName); + } if (!confirmAssetOverwrite(assetName)) { addWarning(tr("Skipped import of existing asset: \"%1\"").arg(assetName)); return; @@ -306,6 +315,16 @@ void ItemLibraryAssetImporter::parseQuick3DAsset(const QString &file, const QVar return; } + // The importer is reset after every import to avoid issues with it caching various things + m_quick3DAssetImporter.reset(new QSSGAssetImportManager); + + if (originalAssetName != assetName) { + // Fix the generated qml file name + const QString assetQml = originalAssetName + ".qml"; + if (outDir.exists(assetQml)) + outDir.rename(assetQml, assetName + ".qml"); + } + QHash<QString, QString> assetFiles; const int outDirPathSize = outDir.path().size(); auto insertAsset = [&](const QString &filePath) { @@ -512,18 +531,24 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport() addInfo(progressTitle); notifyProgress(0, progressTitle); - // There is an inbuilt delay before rewriter change actually updates the data model, - // so we need to wait for a moment to allow the change to take effect. + // First we have to wait a while to ensure qmljs detects new files and updates its + // internal model. Then we make a non-change to the document to trigger qmljs snapshot + // update. There is an inbuilt delay before rewriter change actually updates the data + // model, so we need to wait for another moment to allow the change to take effect. // Otherwise subsequent subcomponent manager update won't detect new imports properly. QTimer *timer = new QTimer(parent()); static int counter; counter = 0; - timer->callOnTimeout([this, timer, progressTitle, model]() { + timer->callOnTimeout([this, timer, progressTitle, model, doc]() { if (!isCancelled()) { - notifyProgress(++counter * 10, progressTitle); - if (counter >= 10) { - // Trigger underlying qmljs snapshot update by making a non-change to the doc + notifyProgress(++counter * 5, progressTitle); + if (counter == 10) { model->rewriterView()->textModifier()->replace(0, 0, {}); + } else if (counter == 19) { + doc->updateSubcomponentManager(); + } else if (counter >= 20) { + if (!m_overwrittenImports.isEmpty()) + model->rewriterView()->emitCustomNotification("asset_import_update"); timer->stop(); notifyFinished(); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index caf0663dce..3f7ca2c366 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -384,7 +384,19 @@ void ItemLibraryWidget::updateModel() { QTC_ASSERT(m_itemLibraryModel, return); + if (m_compressionTimer.isActive()) { + m_updateRetry = false; + m_compressionTimer.stop(); + } + m_itemLibraryModel->update(m_itemLibraryInfo.data(), m_model.data()); + + if (m_itemLibraryModel->rowCount() == 0 && !m_updateRetry) { + m_updateRetry = true; // Only retry once to avoid endless loops + m_compressionTimer.start(); + } else { + m_updateRetry = false; + } updateImports(); updateSearch(); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h index 6e8fc0340a..11dea7d0c1 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h @@ -121,6 +121,7 @@ private: QPointer<Model> m_model; FilterChangeFlag m_filterFlag; ItemLibraryEntry m_currentitemLibraryEntry; + bool m_updateRetry = false; }; } diff --git a/src/plugins/qmldesigner/components/navigator/navigatormodelinterface.h b/src/plugins/qmldesigner/components/navigator/navigatormodelinterface.h index bedcc2f561..d560eb2824 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatormodelinterface.h +++ b/src/plugins/qmldesigner/components/navigator/navigatormodelinterface.h @@ -43,6 +43,7 @@ public: virtual void notifyModelNodesRemoved(const QList<ModelNode> &modelNodes) = 0; virtual void notifyModelNodesInserted(const QList<ModelNode> &modelNodes) = 0; virtual void notifyModelNodesMoved(const QList<ModelNode> &modelNodes) = 0; + virtual void notifyIconsChanged() = 0; virtual void setFilter(bool showObjects) = 0; virtual void resetModel() = 0; }; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index c91ff78626..70d2e405f9 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -695,6 +695,11 @@ void NavigatorTreeModel::notifyModelNodesMoved(const QList<ModelNode> &modelNode emit layoutChanged(indexes); } +void NavigatorTreeModel::notifyIconsChanged() +{ + emit dataChanged(index(0, 0), index(rowCount(), 0), {Qt::DecorationRole}); +} + void NavigatorTreeModel::setFilter(bool showOnlyVisibleItems) { m_showOnlyVisibleItems = showOnlyVisibleItems; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index f10198adcc..15e89d3636 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -87,6 +87,7 @@ public: void notifyModelNodesRemoved(const QList<ModelNode> &modelNodes) override; void notifyModelNodesInserted(const QList<ModelNode> &modelNodes) override; void notifyModelNodesMoved(const QList<ModelNode> &modelNodes) override; + void notifyIconsChanged() override; void setFilter(bool showOnlyVisibleItems) override; void resetModel() override; diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index ea64f0715a..cd6857d789 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -147,6 +147,17 @@ void NavigatorView::bindingPropertiesChanged(const QList<BindingProperty> & prop } } +void NavigatorView::customNotification(const AbstractView *view, const QString &identifier, + const QList<ModelNode> &nodeList, const QList<QVariant> &data) +{ + Q_UNUSED(view) + Q_UNUSED(nodeList) + Q_UNUSED(data) + + if (identifier == "asset_import_update") + m_currentModelInterface->notifyIconsChanged(); +} + void NavigatorView::handleChangedExport(const ModelNode &modelNode, bool exported) { const ModelNode rootNode = rootModelNode(); @@ -416,12 +427,24 @@ void NavigatorView::updateItemSelection() QItemSelection itemSelection; foreach (const ModelNode &node, selectedModelNodes()) { const QModelIndex index = indexForModelNode(node); + if (index.isValid()) { const QModelIndex beginIndex(currentModel()->index(index.row(), 0, index.parent())); const QModelIndex endIndex(currentModel()->index(index.row(), currentModel()->columnCount(index.parent()) - 1, index.parent())); if (beginIndex.isValid() && endIndex.isValid()) itemSelection.select(beginIndex, endIndex); - } + } else { + // if the node index is invalid expand ancestors manually if they are valid. + ModelNode parentNode = node; + while (parentNode.hasParentProperty()) { + parentNode = parentNode.parentProperty().parentQmlObjectNode(); + QModelIndex parentIndex = indexForModelNode(parentNode); + if (parentIndex.isValid()) + treeWidget()->expand(parentIndex); + else + break; + } + } } bool blocked = blockSelectionChangedSignal(true); @@ -431,10 +454,10 @@ void NavigatorView::updateItemSelection() if (!selectedModelNodes().isEmpty()) treeWidget()->scrollTo(indexForModelNode(selectedModelNodes().constFirst())); - // make sure selected nodes a visible + // make sure selected nodes are visible foreach (const QModelIndex &selectedIndex, itemSelection.indexes()) { if (selectedIndex.column() == 0) - expandRecursively(selectedIndex); + expandAncestors(selectedIndex); } } @@ -458,9 +481,9 @@ bool NavigatorView::blockSelectionChangedSignal(bool block) return oldValue; } -void NavigatorView::expandRecursively(const QModelIndex &index) +void NavigatorView::expandAncestors(const QModelIndex &index) { - QModelIndex currentIndex = index; + QModelIndex currentIndex = index.parent(); while (currentIndex.isValid()) { if (!treeWidget()->isExpanded(currentIndex)) treeWidget()->expand(currentIndex); diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.h b/src/plugins/qmldesigner/components/navigator/navigatorview.h index 852dddc70f..3bafe0fa80 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.h +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.h @@ -84,6 +84,8 @@ public: void bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags) override; + void customNotification(const AbstractView *view, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data) override; + void handleChangedExport(const ModelNode &modelNode, bool exported); bool isNodeInvisible(const ModelNode &modelNode) const; @@ -108,7 +110,7 @@ protected: //functions QTreeView *treeWidget() const; NavigatorTreeModel *treeModel(); bool blockSelectionChangedSignal(bool block); - void expandRecursively(const QModelIndex &index); + void expandAncestors(const QModelIndex &index); void reparentAndCatch(NodeAbstractProperty property, const ModelNode &modelNode); void setupWidget(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp index 8be49060e6..1a52a639a7 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp @@ -426,6 +426,17 @@ QStringList PropertyEditorContextObject::styleNamesForFamily(const QString &fami return dataBase.styles(family); } +QStringList PropertyEditorContextObject::allStatesForId(const QString &id) +{ + if (m_model && m_model->rewriterView()) { + const QmlObjectNode node = m_model->rewriterView()->modelNodeForId(id); + if (node.isValid()) + return node.allStateNames(); + } + + return {}; +} + void EasingCurveEditor::registerDeclarativeType() { qmlRegisterType<EasingCurveEditor>("HelperWidgets", 2, 0, "EasingCurveEditor"); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h index 9e3309ee35..03d82dbc34 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.h @@ -94,6 +94,8 @@ public: Q_INVOKABLE QStringList styleNamesForFamily(const QString &family); + Q_INVOKABLE QStringList allStatesForId(const QString &id); + int majorVersion() const; int majorQtQuickVersion() const; int minorQtQuickVersion() const; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index ddded6400b..6dbb0d471c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -47,6 +47,7 @@ #include <QApplication> #include <QDir> #include <QFileInfo> +#include <QVector3D> #include <QLoggingCategory> @@ -166,6 +167,22 @@ QVariant properDefaultAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, return 0; else if (propertyName == "breakPoint") return 50; + else if (propertyName == "transitionType") + return 0; + else if (propertyName == "type") + return 0; + else if (propertyName == "transitionRadius") + return 8; + else if (propertyName == "radius") + return 8; + else if (propertyName == "transitionBezier") + return 50; + else if (propertyName == "bezier") + return 50; + else if (propertyName == "labelPosition") + return 50.0; + else if (propertyName == "labelFlipSide") + return false; else if (propertyName == "customId") return QString(); else if (propertyName == "joinConnection") @@ -235,7 +252,7 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml propertyNames.append("customId"); if (itemNode.isFlowTransition()) { - propertyNames.append({"color", "width", "inOffset", "outOffset", "dash", "breakPoint"}); + propertyNames.append({"color", "width", "inOffset", "outOffset", "dash", "breakPoint", "type", "radius", "bezier", "labelPosition", "labelFlipSide"}); } else if (itemNode.isFlowItem()) { propertyNames.append({"color", "width", "inOffset", "outOffset", "joinConnection"}); } else if (itemNode.isFlowActionArea()) { @@ -245,7 +262,7 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml } else if (itemNode.isFlowWildcard()) { propertyNames.append({"color", "width", "fillColor", "dash"}); } else if (itemNode.isFlowView()) { - propertyNames.append({"transitionColor", "areaColor", "areaFillColor", "blockColor" }); + propertyNames.append({"transitionColor", "areaColor", "areaFillColor", "blockColor", "transitionType", "transitionRadius", "transitionBezier"}); } for (const PropertyName &propertyName : propertyNames) { @@ -294,11 +311,25 @@ void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qm void PropertyEditorQmlBackend::setValue(const QmlObjectNode & , const PropertyName &name, const QVariant &value) { - PropertyName propertyName = name; - propertyName.replace('.', '_'); - auto propertyValue = qobject_cast<PropertyEditorValue*>(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName)))); - if (propertyValue) - propertyValue->setValue(value); + if (value.type() == QVariant::Vector3D) { + // Vector3D values need to be split into their subcomponents + const char *suffix[3] = {"_x", "_y", "_z"}; + auto vecValue = value.value<QVector3D>(); + for (int i = 0; i < 3; ++i) { + PropertyName subPropName(name.size() + 2, '\0'); + subPropName.replace(0, name.size(), name); + subPropName.replace(name.size(), 2, suffix[i]); + auto propertyValue = qobject_cast<PropertyEditorValue *>(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(subPropName)))); + if (propertyValue) + propertyValue->setValue(QVariant(vecValue[i])); + } + } else { + PropertyName propertyName = name; + propertyName.replace('.', '_'); + auto propertyValue = qobject_cast<PropertyEditorValue *>(variantToQObject(m_backendValuesPropertyMap.value(QString::fromUtf8(propertyName)))); + if (propertyValue) + propertyValue->setValue(value); + } } QQmlContext *PropertyEditorQmlBackend::context() { |