forked from qt-creator/qt-creator
Add Cmd+Shift+- for decreasing font size on macOS. Do not add Cmd+= for increasing size, because it conflicts with the existing shortcut for "Replace and Find Next". Sprinkle some Backspace shortcuts in addition to Delete for removing items. There are (laptop) keyboards that either do not have a designated Delete key (requiring Fn+Backspace) or where the Delete key is not conveniently located/sized, and there is no benefit in making the distinction in that case anyhow. Fixes: QTCREATORBUG-706 Fixes: QTCREATORBUG-13733 Change-Id: I06274a9810b82800ec6158a883c95d2a7ae2465e Reviewed-by: David Schulz <david.schulz@qt.io>
956 lines
28 KiB
C++
956 lines
28 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "graphicsscene.h"
|
|
#include "actionhandler.h"
|
|
#include "actionprovider.h"
|
|
#include "finalstateitem.h"
|
|
#include "initialstateitem.h"
|
|
#include "parallelitem.h"
|
|
#include "sceneutils.h"
|
|
#include "scxmleditorconstants.h"
|
|
#include "scxmltagutils.h"
|
|
#include "scxmluifactory.h"
|
|
#include "snapline.h"
|
|
#include "stateitem.h"
|
|
#include "transitionitem.h"
|
|
#include "utilsprovider.h"
|
|
#include "warning.h"
|
|
#include "warningitem.h"
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
#include <QAction>
|
|
#include <QGuiApplication>
|
|
#include <QClipboard>
|
|
#include <QMenu>
|
|
#include <QMimeData>
|
|
#include <QThread>
|
|
#include <QUndoStack>
|
|
|
|
using namespace ScxmlEditor::PluginInterface;
|
|
|
|
GraphicsScene::GraphicsScene(QObject *parent)
|
|
: QGraphicsScene(parent)
|
|
{
|
|
//setMinimumRenderSize(5);
|
|
setItemIndexMethod(QGraphicsScene::NoIndex);
|
|
}
|
|
|
|
GraphicsScene::~GraphicsScene()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
void GraphicsScene::unselectAll()
|
|
{
|
|
const QList<QGraphicsItem*> selectedItems = this->selectedItems();
|
|
foreach (QGraphicsItem *it, selectedItems)
|
|
it->setSelected(false);
|
|
if (m_document)
|
|
m_document->setCurrentTag(nullptr);
|
|
}
|
|
|
|
void GraphicsScene::unhighlightAll()
|
|
{
|
|
foreach (BaseItem *it, m_baseItems)
|
|
it->setHighlight(false);
|
|
}
|
|
|
|
void GraphicsScene::highlightItems(const QVector<ScxmlTag*> &lstIds)
|
|
{
|
|
foreach (BaseItem *it, m_baseItems)
|
|
it->setHighlight(lstIds.contains(it->tag()));
|
|
}
|
|
|
|
QRectF GraphicsScene::selectedBoundingRect() const
|
|
{
|
|
QRectF r;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected())
|
|
r = r.united(item->sceneBoundingRect());
|
|
}
|
|
return r;
|
|
}
|
|
|
|
qreal GraphicsScene::selectedMaxWidth() const
|
|
{
|
|
qreal maxw = 0;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
maxw = qMax(maxw, item->sceneBoundingRect().width());
|
|
}
|
|
return maxw;
|
|
}
|
|
|
|
qreal GraphicsScene::selectedMaxHeight() const
|
|
{
|
|
qreal maxh = 0;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
maxh = qMax(maxh, item->sceneBoundingRect().height());
|
|
}
|
|
return maxh;
|
|
}
|
|
|
|
void GraphicsScene::alignStates(int alignType)
|
|
{
|
|
if (alignType >= ActionAlignLeft && alignType <= ActionAlignVertical) {
|
|
m_document->undoStack()->beginMacro(tr("Align states"));
|
|
QRectF r = selectedBoundingRect();
|
|
|
|
if (r.isValid()) {
|
|
switch (alignType) {
|
|
case ActionAlignLeft:
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
item->moveStateBy(r.left() - item->sceneBoundingRect().left(), 0);
|
|
}
|
|
break;
|
|
case ActionAlignRight:
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
item->moveStateBy(r.right() - item->sceneBoundingRect().right(), 0);
|
|
}
|
|
break;
|
|
case ActionAlignTop:
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
item->moveStateBy(0, r.top() - item->sceneBoundingRect().top());
|
|
}
|
|
break;
|
|
case ActionAlignBottom:
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
item->moveStateBy(0, r.bottom() - item->sceneBoundingRect().bottom());
|
|
}
|
|
break;
|
|
case ActionAlignHorizontal:
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
item->moveStateBy(0, r.center().y() - item->sceneBoundingRect().center().y());
|
|
}
|
|
break;
|
|
case ActionAlignVertical:
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType)
|
|
item->moveStateBy(r.center().x() - item->sceneBoundingRect().center().x(), 0);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_document->undoStack()->endMacro();
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::adjustStates(int adjustType)
|
|
{
|
|
if (adjustType >= ActionAdjustWidth && adjustType <= ActionAdjustSize) {
|
|
m_document->undoStack()->beginMacro(tr("Adjust states"));
|
|
qreal maxw = selectedMaxWidth();
|
|
qreal maxh = selectedMaxHeight();
|
|
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= InitialStateType) {
|
|
QRectF rr = item->boundingRect();
|
|
if ((adjustType == ActionAdjustWidth || adjustType == ActionAdjustSize) && !qFuzzyCompare(rr.width(), maxw))
|
|
rr.setWidth(maxw);
|
|
if ((adjustType == ActionAdjustHeight || adjustType == ActionAdjustSize) && !qFuzzyCompare(rr.height(), maxh))
|
|
rr.setHeight(maxh);
|
|
|
|
item->setItemBoundingRect(rr);
|
|
qgraphicsitem_cast<ConnectableItem*>(item)->updateTransitions(true);
|
|
}
|
|
}
|
|
m_document->undoStack()->endMacro();
|
|
}
|
|
}
|
|
void GraphicsScene::cut()
|
|
{
|
|
m_document->undoStack()->beginMacro(tr("Cut"));
|
|
copy();
|
|
removeSelectedItems();
|
|
m_document->undoStack()->endMacro();
|
|
}
|
|
|
|
void GraphicsScene::removeSelectedItems()
|
|
{
|
|
QVector<ScxmlTag*> tags = SceneUtils::findRemovedTags(m_baseItems);
|
|
if (!tags.isEmpty()) {
|
|
m_document->undoStack()->beginMacro(tr("Remove items"));
|
|
|
|
// Then remove found tags
|
|
for (int i = tags.count(); i--;) {
|
|
m_document->setCurrentTag(tags[i]);
|
|
m_document->removeTag(tags[i]);
|
|
}
|
|
m_document->setCurrentTag(nullptr);
|
|
m_document->undoStack()->endMacro();
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::copy()
|
|
{
|
|
if (!m_document->currentTag())
|
|
return;
|
|
|
|
QPointF minPos;
|
|
QVector<ScxmlTag*> tags;
|
|
if (m_document->currentTag()->tagType() == Scxml) {
|
|
QVector<BaseItem*> items;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (!item->parentItem())
|
|
items << item;
|
|
}
|
|
tags = SceneUtils::findCopyTags(items, minPos);
|
|
} else {
|
|
tags = SceneUtils::findCopyTags(m_baseItems, minPos);
|
|
}
|
|
|
|
if (tags.isEmpty() && m_document->currentTag())
|
|
tags << m_document->currentTag();
|
|
|
|
if (!tags.isEmpty()) {
|
|
auto mime = new QMimeData;
|
|
QByteArray result = m_document->content(tags);
|
|
mime->setText(QLatin1String(result));
|
|
mime->setData("StateChartEditor/StateData", result);
|
|
QStringList strTypes;
|
|
foreach (const ScxmlTag *tag, tags) {
|
|
strTypes << tag->tagName(false);
|
|
}
|
|
mime->setData("StateChartEditor/CopiedTagTypes", strTypes.join(",").toLocal8Bit());
|
|
mime->setData("StateChartEditor/CopiedMinPos", QString::fromLatin1("%1:%2").arg(minPos.x()).arg(minPos.y()).toLocal8Bit());
|
|
QGuiApplication::clipboard()->setMimeData(mime);
|
|
}
|
|
|
|
checkPaste();
|
|
}
|
|
|
|
void GraphicsScene::checkPaste()
|
|
{
|
|
const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
|
|
const QString copiedTagTypes = QLatin1String(mimeData->data("StateChartEditor/CopiedTagTypes"));
|
|
|
|
emit pasteAvailable(TagUtils::checkPaste(copiedTagTypes, m_document->currentTag()));
|
|
}
|
|
|
|
void GraphicsScene::paste(const QPointF &targetPos)
|
|
{
|
|
const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
|
|
|
|
QPointF startPos(targetPos);
|
|
|
|
BaseItem *targetItem = nullptr;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= StateType) {
|
|
targetItem = item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (m_lastPasteTargetItem != targetItem)
|
|
m_pasteCounter = 0;
|
|
m_lastPasteTargetItem = targetItem;
|
|
|
|
if (m_lastPasteTargetItem)
|
|
startPos = m_lastPasteTargetItem->boundingRect().topLeft();
|
|
QPointF pastedPos = startPos + QPointF(m_pasteCounter * 30, m_pasteCounter * 30);
|
|
m_pasteCounter++;
|
|
|
|
QString strMinPos = QLatin1String(mimeData->data("StateChartEditor/CopiedMinPos"));
|
|
QPointF minPos(0, 0);
|
|
if (!strMinPos.isEmpty()) {
|
|
QStringList coords = strMinPos.split(":", QString::SkipEmptyParts);
|
|
if (coords.count() == 2)
|
|
minPos = QPointF(coords[0].toDouble(), coords[1].toDouble());
|
|
}
|
|
|
|
m_document->pasteData(mimeData->data("StateChartEditor/StateData"), minPos, pastedPos);
|
|
}
|
|
|
|
void GraphicsScene::setEditorInfo(const QString &key, const QString &value)
|
|
{
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected() && item->type() >= TransitionType)
|
|
item->setEditorInfo(key, value);
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::setDocument(ScxmlDocument *document)
|
|
{
|
|
if (m_document)
|
|
disconnect(m_document, nullptr, this, nullptr);
|
|
|
|
m_document = document;
|
|
|
|
init();
|
|
connectDocument();
|
|
}
|
|
|
|
void GraphicsScene::connectDocument()
|
|
{
|
|
if (m_document) {
|
|
connect(m_document.data(), &ScxmlDocument::beginTagChange, this, &GraphicsScene::beginTagChange);
|
|
connect(m_document.data(), &ScxmlDocument::endTagChange, this, &GraphicsScene::endTagChange);
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::disconnectDocument()
|
|
{
|
|
if (m_document)
|
|
m_document->disconnect(this);
|
|
}
|
|
|
|
void GraphicsScene::init()
|
|
{
|
|
m_initializing = true;
|
|
|
|
disconnectDocument();
|
|
clear();
|
|
addItem(m_lineX = new SnapLine);
|
|
addItem(m_lineY = new SnapLine);
|
|
|
|
if (m_document) {
|
|
const ScxmlTag *rootTag = m_document->rootTag();
|
|
if (rootTag) {
|
|
|
|
for (int i = 0; i < rootTag->childCount(); ++i) {
|
|
ScxmlTag *child = rootTag->child(i);
|
|
ConnectableItem *newItem = SceneUtils::createItemByTagType(child->tagType());
|
|
if (newItem) {
|
|
addItem(newItem);
|
|
newItem->init(child);
|
|
}
|
|
}
|
|
|
|
const QList<QGraphicsItem*> items = this->items();
|
|
for (int i = 0; i < items.count(); ++i) {
|
|
if (items[i]->type() >= TransitionType) {
|
|
auto item = qgraphicsitem_cast<BaseItem*>(items[i]);
|
|
if (item)
|
|
item->finalizeCreation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_initializing = false;
|
|
warningVisibilityChanged(0, nullptr);
|
|
emit selectedStateCountChanged(0);
|
|
emit selectedBaseItemCountChanged(0);
|
|
}
|
|
|
|
void GraphicsScene::runLayoutToSelectedStates()
|
|
{
|
|
m_document->undoStack()->beginMacro(tr("Re-layout"));
|
|
|
|
QVector<BaseItem*> selectedItems;
|
|
foreach (BaseItem *node, m_baseItems) {
|
|
if (node->isSelected()) {
|
|
int index = 0;
|
|
for (int i = 0; i < selectedItems.count(); ++i) {
|
|
if (node->depth() <= selectedItems[i]->depth()) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
selectedItems.insert(index, node);
|
|
}
|
|
}
|
|
|
|
// Layout selected items
|
|
for (int i = 0; i < selectedItems.count(); ++i)
|
|
selectedItems[i]->doLayout(selectedItems[i]->depth());
|
|
|
|
// Layout scene items if necessary
|
|
if (selectedItems.isEmpty()) {
|
|
QList<QGraphicsItem*> sceneItems;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->type() >= InitialStateType && !item->parentItem())
|
|
sceneItems << item;
|
|
}
|
|
SceneUtils::layout(sceneItems);
|
|
|
|
foreach (QGraphicsItem *item, sceneItems) {
|
|
if (item->type() >= StateType)
|
|
static_cast<StateItem*>(item)->shrink();
|
|
}
|
|
}
|
|
|
|
// Update properties
|
|
foreach (BaseItem *node, selectedItems) {
|
|
node->updateUIProperties();
|
|
}
|
|
|
|
m_document->undoStack()->endMacro();
|
|
}
|
|
|
|
void GraphicsScene::runAutomaticLayout()
|
|
{
|
|
m_autoLayoutRunning = true;
|
|
|
|
// 1. Find max depth
|
|
int maxDepth = 0;
|
|
foreach (BaseItem *node, m_baseItems) {
|
|
maxDepth = qMax(maxDepth, node->depth());
|
|
node->setBlockUpdates(true);
|
|
}
|
|
|
|
// 2. Layout every depth-level separately
|
|
for (int d = (maxDepth + 1); d--;) {
|
|
foreach (BaseItem *node, m_baseItems)
|
|
node->doLayout(d);
|
|
}
|
|
|
|
// 3. Layout scene items
|
|
QList<QGraphicsItem*> sceneItems;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->type() >= InitialStateType && !item->parentItem())
|
|
sceneItems << item;
|
|
}
|
|
SceneUtils::layout(sceneItems);
|
|
|
|
foreach (QGraphicsItem *item, sceneItems) {
|
|
if (item->type() >= StateType)
|
|
static_cast<StateItem*>(item)->shrink();
|
|
}
|
|
|
|
foreach (BaseItem *node, m_baseItems) {
|
|
node->updateUIProperties();
|
|
node->setBlockUpdates(false);
|
|
}
|
|
|
|
m_autoLayoutRunning = false;
|
|
}
|
|
|
|
void GraphicsScene::beginTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag, const QVariant &value)
|
|
{
|
|
switch (change) {
|
|
case ScxmlDocument::TagRemoveChild: {
|
|
if (tag)
|
|
removeItems(tag->child(value.toInt()));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag, const QVariant &value)
|
|
{
|
|
Q_UNUSED(value)
|
|
|
|
switch (change) {
|
|
case ScxmlDocument::TagAttributesChanged: {
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->tag() == tag)
|
|
item->updateAttributes();
|
|
}
|
|
break;
|
|
}
|
|
case ScxmlDocument::TagEditorInfoChanged: {
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->tag() == tag)
|
|
item->updateEditorInfo();
|
|
}
|
|
break;
|
|
}
|
|
case ScxmlDocument::TagCurrentChanged: {
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (!item->isSelected() && item->tag() == tag)
|
|
item->setSelected(true);
|
|
}
|
|
checkPaste();
|
|
break;
|
|
}
|
|
case ScxmlDocument::TagChangeParent: {
|
|
auto childItem = qobject_cast<ConnectableItem*>(findItem(tag));
|
|
|
|
if (childItem) {
|
|
QTC_ASSERT(tag, break);
|
|
BaseItem *newParentItem = findItem(tag->parentTag());
|
|
BaseItem *oldParentItem = childItem->parentBaseItem();
|
|
|
|
QPointF sPos = childItem->scenePos();
|
|
if (oldParentItem) {
|
|
childItem->setParentItem(nullptr);
|
|
childItem->setPos(sPos);
|
|
}
|
|
|
|
if (newParentItem)
|
|
childItem->setPos(newParentItem->mapFromScene(childItem->sceneBoundingRect().center()) - childItem->boundingRect().center());
|
|
|
|
childItem->setParentItem(newParentItem);
|
|
childItem->updateUIProperties();
|
|
childItem->updateTransitions(true);
|
|
childItem->updateTransitionAttributes(true);
|
|
childItem->checkWarnings();
|
|
childItem->checkInitial();
|
|
if (newParentItem) {
|
|
newParentItem->checkInitial();
|
|
newParentItem->updateAttributes();
|
|
newParentItem->checkWarnings();
|
|
newParentItem->checkOverlapping();
|
|
newParentItem->updateUIProperties();
|
|
}
|
|
|
|
if (oldParentItem)
|
|
oldParentItem->checkInitial();
|
|
|
|
if (!oldParentItem || !newParentItem)
|
|
checkInitialState();
|
|
}
|
|
break;
|
|
}
|
|
case ScxmlDocument::TagAddTags: {
|
|
// Finalize transitions
|
|
if (!tag)
|
|
break;
|
|
|
|
QVector<ScxmlTag*> childTransitionTags;
|
|
if (tag->tagName(false) == "transition")
|
|
childTransitionTags << tag;
|
|
|
|
TagUtils::findAllTransitionChildren(tag, childTransitionTags);
|
|
for (int i = 0; i < childTransitionTags.count(); ++i) {
|
|
BaseItem *item = findItem(childTransitionTags[i]);
|
|
if (item)
|
|
item->finalizeCreation();
|
|
}
|
|
}
|
|
break;
|
|
case ScxmlDocument::TagAddChild: {
|
|
ScxmlTag *childTag = tag ? tag->child(value.toInt()) : nullptr;
|
|
if (childTag) {
|
|
// Check that there is no any item with this tag
|
|
BaseItem *childItem = findItem(childTag);
|
|
BaseItem *parentItem = findItem(tag);
|
|
if (!childItem) {
|
|
if (childTag->tagType() == Transition || childTag->tagType() == InitialTransition) {
|
|
auto transition = new TransitionItem;
|
|
addItem(transition);
|
|
transition->setStartItem(qgraphicsitem_cast<ConnectableItem*>(parentItem));
|
|
transition->init(childTag, nullptr, false, false);
|
|
transition->updateAttributes();
|
|
} else {
|
|
childItem = SceneUtils::createItemByTagType(childTag->tagType(), QPointF());
|
|
if (childItem) {
|
|
childItem->init(childTag, parentItem, false);
|
|
if (!parentItem)
|
|
addItem(childItem);
|
|
|
|
childItem->finalizeCreation();
|
|
childItem->updateUIProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parentItem) {
|
|
parentItem->updateAttributes();
|
|
parentItem->updateUIProperties();
|
|
parentItem->checkInitial();
|
|
} else
|
|
checkInitialState();
|
|
}
|
|
break;
|
|
}
|
|
case ScxmlDocument::TagRemoveChild: {
|
|
if (tag) {
|
|
BaseItem *parentItem = findItem(tag);
|
|
if (parentItem) {
|
|
parentItem->updateAttributes();
|
|
parentItem->checkInitial();
|
|
} else {
|
|
checkInitialState();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ScxmlDocument::TagChangeOrder: {
|
|
if (tag) {
|
|
BaseItem *parentItem = findItem(tag->parentTag());
|
|
if (parentItem)
|
|
parentItem->updateAttributes();
|
|
else
|
|
checkInitialState();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::setTopMostScene(bool topmost)
|
|
{
|
|
m_topMostScene = topmost;
|
|
}
|
|
|
|
bool GraphicsScene::topMostScene() const
|
|
{
|
|
return m_topMostScene;
|
|
}
|
|
|
|
void GraphicsScene::setActionHandler(ActionHandler *mgr)
|
|
{
|
|
m_actionHandler = mgr;
|
|
}
|
|
|
|
void GraphicsScene::setWarningModel(ScxmlEditor::OutputPane::WarningModel *model)
|
|
{
|
|
m_warningModel = model;
|
|
}
|
|
|
|
void GraphicsScene::setUiFactory(ScxmlUiFactory *uifactory)
|
|
{
|
|
m_uiFactory = uifactory;
|
|
}
|
|
|
|
ActionHandler *GraphicsScene::actionHandler() const
|
|
{
|
|
return m_actionHandler;
|
|
}
|
|
|
|
ScxmlEditor::OutputPane::WarningModel *GraphicsScene::warningModel() const
|
|
{
|
|
return m_warningModel;
|
|
}
|
|
|
|
ScxmlUiFactory *GraphicsScene::uiFactory() const
|
|
{
|
|
return m_uiFactory;
|
|
}
|
|
|
|
void GraphicsScene::addConnectableItem(ItemType type, const QPointF &pos, BaseItem *parentItem)
|
|
{
|
|
m_document->undoStack()->beginMacro(tr("Add new state"));
|
|
ConnectableItem *newItem = SceneUtils::createItem(type, pos);
|
|
|
|
if (newItem) {
|
|
ScxmlTag *newTag = SceneUtils::createTag(type, m_document);
|
|
ScxmlTag *parentTag = parentItem ? parentItem->tag() : m_document->rootTag();
|
|
|
|
newItem->setTag(newTag);
|
|
newItem->setParentItem(parentItem);
|
|
if (!parentItem)
|
|
addItem(newItem);
|
|
|
|
newItem->updateAttributes();
|
|
newItem->updateEditorInfo();
|
|
newItem->updateUIProperties();
|
|
|
|
if (parentItem)
|
|
parentItem->updateUIProperties();
|
|
|
|
m_document->addTag(parentTag, newTag);
|
|
unselectAll();
|
|
newItem->setSelected(true);
|
|
}
|
|
m_document->undoStack()->endMacro();
|
|
}
|
|
|
|
void GraphicsScene::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
QGraphicsItem *focusItem = this->focusItem();
|
|
if (!focusItem || focusItem->type() != TextType) {
|
|
if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace)
|
|
removeSelectedItems();
|
|
}
|
|
QGraphicsScene::keyPressEvent(event);
|
|
}
|
|
|
|
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
QGraphicsItem *it = itemAt(event->scenePos(), QTransform());
|
|
if (!it || it->type() == LayoutType) {
|
|
if (event->button() == Qt::LeftButton) {
|
|
QGraphicsScene::mousePressEvent(event);
|
|
m_document->setCurrentTag(m_document->rootTag());
|
|
return;
|
|
} else if (m_actionHandler && event->button() == Qt::RightButton) {
|
|
event->accept();
|
|
QMenu menu;
|
|
menu.addAction(m_actionHandler->action(ActionCopy));
|
|
menu.addAction(m_actionHandler->action(ActionPaste));
|
|
menu.addAction(m_actionHandler->action(ActionScreenshot));
|
|
menu.addAction(m_actionHandler->action(ActionExportToImage));
|
|
menu.addSeparator();
|
|
menu.addAction(m_actionHandler->action(ActionZoomIn));
|
|
menu.addAction(m_actionHandler->action(ActionZoomOut));
|
|
menu.addAction(m_actionHandler->action(ActionFitToView));
|
|
|
|
if (m_uiFactory) {
|
|
auto actionProvider = static_cast<ActionProvider*>(m_uiFactory->object(Constants::C_UI_FACTORY_OBJECT_ACTIONPROVIDER));
|
|
if (actionProvider) {
|
|
menu.addSeparator();
|
|
actionProvider->initStateMenu(m_document->rootTag(), &menu);
|
|
}
|
|
}
|
|
|
|
menu.exec(event->screenPos());
|
|
return;
|
|
}
|
|
}
|
|
QGraphicsScene::mousePressEvent(event);
|
|
}
|
|
|
|
BaseItem *GraphicsScene::findItem(const ScxmlTag *tag) const
|
|
{
|
|
if (!tag)
|
|
return nullptr;
|
|
|
|
foreach (BaseItem *it, m_baseItems) {
|
|
if (it->tag() == tag)
|
|
return it;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void GraphicsScene::removeItems(const ScxmlTag *tag)
|
|
{
|
|
if (tag) {
|
|
// Find right items
|
|
QVector<BaseItem*> items;
|
|
foreach (BaseItem *it, m_baseItems) {
|
|
if (it->tag() == tag)
|
|
items << it;
|
|
}
|
|
|
|
// Then delete them
|
|
for (int i = items.count(); i--;) {
|
|
items[i]->setTag(nullptr);
|
|
delete items[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
QPair<bool, bool> GraphicsScene::checkSnapToItem(BaseItem *item, const QPointF &p, QPointF &pp)
|
|
{
|
|
if (m_selectedStateCount > 1)
|
|
return QPair<bool, bool>(false, false);
|
|
|
|
QGraphicsItem *parentItem = item->parentItem();
|
|
|
|
qreal diffX = 8;
|
|
qreal diffXdY = 2000;
|
|
|
|
qreal diffY = 8;
|
|
qreal diffYdX = 2000;
|
|
|
|
foreach (BaseItem *it, m_baseItems) {
|
|
if (!it->isSelected() && it != item && it->parentItem() == parentItem && it->type() >= InitialStateType) {
|
|
QPointF c = it->sceneCenter();
|
|
qreal dX = qAbs(c.x() - p.x());
|
|
qreal dY = qAbs(c.y() - p.y());
|
|
|
|
if (dX < 7 && dY < diffXdY) {
|
|
pp.setX(c.x());
|
|
diffX = dX;
|
|
diffXdY = dY;
|
|
m_lineY->show(c.x(), c.y(), c.x(), p.y());
|
|
}
|
|
|
|
if (dY < 7 && dX < diffYdX) {
|
|
pp.setY(c.y());
|
|
diffY = dY;
|
|
diffYdX = dX;
|
|
m_lineX->show(c.x(), c.y(), p.x(), c.y());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (qFuzzyCompare(diffX, 8))
|
|
m_lineY->hideLine();
|
|
if (qFuzzyCompare(diffY, 8))
|
|
m_lineX->hideLine();
|
|
|
|
return QPair<bool, bool>(diffX < 8, diffY < 8);
|
|
}
|
|
|
|
void GraphicsScene::selectionChanged(bool para)
|
|
{
|
|
Q_UNUSED(para)
|
|
|
|
int count = 0;
|
|
int baseCount = 0;
|
|
int stateTypeCount = 0;
|
|
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->isSelected()) {
|
|
if (item->type() >= TransitionType)
|
|
baseCount++;
|
|
if (item->type() >= InitialStateType)
|
|
count++;
|
|
if (item->type() >= StateType)
|
|
stateTypeCount++;
|
|
}
|
|
}
|
|
|
|
m_selectedStateTypeCount = stateTypeCount;
|
|
|
|
if (count != m_selectedStateCount) {
|
|
m_selectedStateCount = count;
|
|
emit selectedStateCountChanged(m_selectedStateCount);
|
|
}
|
|
|
|
if (baseCount != m_selectedBaseItemCount) {
|
|
m_selectedBaseItemCount = baseCount;
|
|
emit selectedBaseItemCountChanged(m_selectedBaseItemCount);
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::addWarningItem(WarningItem *item)
|
|
{
|
|
if (!m_allWarnings.contains(item)) {
|
|
m_allWarnings << item;
|
|
if (!m_autoLayoutRunning && !m_initializing)
|
|
QMetaObject::invokeMethod(this, "warningVisibilityChanged", Qt::QueuedConnection, Q_ARG(int, 0));
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::removeWarningItem(WarningItem *item)
|
|
{
|
|
m_allWarnings.removeAll(item);
|
|
|
|
if (!m_autoLayoutRunning && !m_initializing)
|
|
QMetaObject::invokeMethod(this, "warningVisibilityChanged", Qt::QueuedConnection, Q_ARG(int, 0));
|
|
}
|
|
|
|
void GraphicsScene::warningVisibilityChanged(int type, WarningItem *item)
|
|
{
|
|
if (!m_autoLayoutRunning && !m_initializing) {
|
|
foreach (WarningItem *it, m_allWarnings) {
|
|
if (it != item && (type == 0 || it->type() == type))
|
|
it->check();
|
|
}
|
|
}
|
|
}
|
|
|
|
ScxmlTag *GraphicsScene::tagByWarning(const ScxmlEditor::OutputPane::Warning *w) const
|
|
{
|
|
ScxmlTag *tag = nullptr;
|
|
foreach (WarningItem *it, m_allWarnings) {
|
|
if (it->warning() == w) {
|
|
tag = it->tag();
|
|
break;
|
|
}
|
|
}
|
|
return tag;
|
|
}
|
|
|
|
void GraphicsScene::highlightWarningItem(const ScxmlEditor::OutputPane::Warning *w)
|
|
{
|
|
ScxmlTag *tag = tagByWarning(w);
|
|
|
|
if (tag)
|
|
highlightItems({tag});
|
|
else
|
|
unhighlightAll();
|
|
}
|
|
|
|
void GraphicsScene::selectWarningItem(const ScxmlEditor::OutputPane::Warning *w)
|
|
{
|
|
ScxmlTag *tag = tagByWarning(w);
|
|
|
|
if (tag) {
|
|
unselectAll();
|
|
m_document->setCurrentTag(tag);
|
|
}
|
|
}
|
|
|
|
QList<QGraphicsItem*> GraphicsScene::sceneItems(Qt::SortOrder order) const
|
|
{
|
|
QList<QGraphicsItem*> children;
|
|
QList<QGraphicsItem*> items = this->items(order);
|
|
for (int i = 0; i < items.count(); ++i) {
|
|
if (!items[i]->parentItem() && items[i]->type() >= InitialStateType)
|
|
children << items[i];
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
void GraphicsScene::addChild(BaseItem *item)
|
|
{
|
|
if (!m_baseItems.contains(item)) {
|
|
connect(item, &BaseItem::selectedStateChanged, this, &GraphicsScene::selectionChanged);
|
|
connect(item, &BaseItem::openToDifferentView, this, [=](BaseItem *item){
|
|
emit openStateView(item);
|
|
}, Qt::QueuedConnection);
|
|
m_baseItems << item;
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::removeChild(BaseItem *item)
|
|
{
|
|
if (item)
|
|
disconnect(item, nullptr, this, nullptr);
|
|
m_baseItems.removeAll(item);
|
|
|
|
selectionChanged(false);
|
|
}
|
|
|
|
void GraphicsScene::checkItemsVisibility(double scaleFactor)
|
|
{
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
item->checkVisibility(scaleFactor);
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::checkInitialState()
|
|
{
|
|
if (m_document) {
|
|
QList<QGraphicsItem*> sceneItems;
|
|
foreach (BaseItem *item, m_baseItems) {
|
|
if (item->type() >= InitialStateType && !item->parentItem())
|
|
sceneItems << item;
|
|
}
|
|
if (m_uiFactory) {
|
|
auto utilsProvider = static_cast<UtilsProvider*>(m_uiFactory->object("utilsProvider"));
|
|
if (utilsProvider)
|
|
utilsProvider->checkInitialState(sceneItems, m_document->rootTag());
|
|
}
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::clearAllTags()
|
|
{
|
|
foreach (BaseItem *it, m_baseItems) {
|
|
it->setTag(nullptr);
|
|
}
|
|
}
|
|
|
|
void GraphicsScene::setBlockUpdates(bool block)
|
|
{
|
|
foreach (BaseItem *it, m_baseItems) {
|
|
it->setBlockUpdates(block);
|
|
}
|
|
}
|