// Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "transitiontool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 QByteArray &menuId, const QString &name) : AbstractAction(name) , m_menuId(menuId) {} QByteArray category() const override { return QByteArray(); } QByteArray menuId() const override { return m_menuId; } 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); } private: QByteArray m_menuId; }; class TransitionCustomAction : public TransitionToolAction { public: TransitionCustomAction(const QByteArray &menuId, const QString &name) : TransitionToolAction(menuId, 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("AddTransition", tr("Add Transition")); QmlDesignerPlugin::instance()->designerActionManager().addDesignerAction(transitionToolAction); connect(transitionToolAction->action(), &QAction::triggered, this, &TransitionTool::activateTool); TransitionCustomAction *removeAction = new TransitionCustomAction("RemoveTransition", 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( "RemoveAllTransitions", 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( "RemoveDanglindTransitions", 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 &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 & 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 & itemList, QGraphicsSceneMouseEvent *event) { mouseMoveEvent(itemList, event); } void TransitionTool::keyPressEvent(QKeyEvent * /*keyEvent*/) { } void TransitionTool::keyReleaseEvent(QKeyEvent * /*keyEvent*/) { view()->changeToSelectionTool(); } void TransitionTool::dragLeaveEvent(const QList &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) { } void TransitionTool::dragMoveEvent(const QList &/*itemList*/, QGraphicsSceneDragDropEvent * /*event*/) { } void TransitionTool::mouseReleaseEvent(const QList &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 &itemList, QGraphicsSceneMouseEvent *event) { AbstractFormEditorTool::mouseDoubleClickEvent(itemList, event); } void TransitionTool::itemsAboutToRemoved(const QList &) { view()->changeCurrentToolTo(this); } void TransitionTool::selectedItemsChanged(const QList &itemList) { if (!itemList.isEmpty()) { createItems(); m_formEditorItem = itemList.first(); setToBoundingRect(rectangleItem1(), m_formEditorItem); } } void TransitionTool::instancesCompleted(const QList & /*itemList*/) { } void TransitionTool::instancesParentChanged(const QList & /*itemList*/) { } void TransitionTool::instancePropertyChange(const QList > & /*propertyList*/) { } void TransitionTool::formEditorItemsChanged(const QList & /*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"; } } }