QmlDesigner: Implement transition tool

Implementing a custom tool to create transitions in flows.

Change-Id: I721895c67084707bea8504c657ec9af2b5df2c22
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Thomas Hartmann
2020-04-14 18:26:04 +02:00
parent 8c50ceb294
commit 88e07d8a7a
6 changed files with 545 additions and 2 deletions

View File

@@ -243,6 +243,7 @@ extend_qtc_plugin(QmlDesigner
snapper.cpp snapper.h
snappinglinecreator.cpp snappinglinecreator.h
toolbox.cpp toolbox.h
transitiontool.cpp transitiontool.h
)
extend_qtc_plugin(QmlDesigner

View File

@@ -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

View File

@@ -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_block)
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_block)
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_block = 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_block = 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";
}
}
}

View File

@@ -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_block = true;
};
} //QmlDesigner

View File

@@ -38,6 +38,7 @@
#include <sourcetool/sourcetool.h>
#include <colortool/colortool.h>
#include <annotationeditor/annotationtool.h>
#include <formeditor/transitiontool.h>
#include <texttool/texttool.h>
#include <timelineeditor/timelineview.h>
#include <pathtool/pathtool.h>
@@ -242,6 +243,7 @@ bool QmlDesignerPlugin::delayedInitialize()
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::AnnotationTool);
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::TextTool);
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::PathTool);
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::TransitionTool);
return true;
}

View File

@@ -530,6 +530,8 @@ Project {
"formeditor/toolbox.h",
"formeditor/formeditortoolbutton.cpp",
"formeditor/formeditortoolbutton.h",
"formeditor/transitiontool.cpp",
"formeditor/transitiontool.h",
"importmanager/importlabel.cpp",
"importmanager/importlabel.h",
"importmanager/importmanagercombobox.cpp",