forked from qt-creator/qt-creator
Change-Id: I0a89d2808c6d041da0dc41ea5aea58e6e8759bb4 Reviewed-by: Orgad Shaneh <orgads@gmail.com>
582 lines
18 KiB
C++
582 lines
18 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 "stateitem.h"
|
|
#include "finalstateitem.h"
|
|
#include "graphicsitemprovider.h"
|
|
#include "graphicsscene.h"
|
|
#include "idwarningitem.h"
|
|
#include "imageprovider.h"
|
|
#include "initialstateitem.h"
|
|
#include "parallelitem.h"
|
|
#include "sceneutils.h"
|
|
#include "scxmleditorconstants.h"
|
|
#include "scxmltagutils.h"
|
|
#include "scxmluifactory.h"
|
|
#include "statewarningitem.h"
|
|
#include "textitem.h"
|
|
#include "transitionitem.h"
|
|
#include "utilsprovider.h"
|
|
|
|
#include <QBrush>
|
|
#include <QCoreApplication>
|
|
#include <QDebug>
|
|
#include <QPainter>
|
|
#include <QTextOption>
|
|
#include <QUndoStack>
|
|
#include <QtMath>
|
|
|
|
using namespace ScxmlEditor::PluginInterface;
|
|
|
|
const qreal TEXT_ITEM_HEIGHT = 20;
|
|
const qreal TEXT_ITEM_CAP = 10;
|
|
const qreal STATE_RADIUS = 10;
|
|
|
|
StateItem::StateItem(const QPointF &pos, BaseItem *parent)
|
|
: ConnectableItem(pos, parent)
|
|
{
|
|
m_stateNameItem = new TextItem(this);
|
|
m_stateNameItem->setParentItem(this);
|
|
|
|
checkWarningItems();
|
|
|
|
connect(m_stateNameItem, &TextItem::selected, this, [=](bool sel){
|
|
setItemSelected(sel);
|
|
});
|
|
connect(m_stateNameItem, &TextItem::textChanged, this, &StateItem::updateTextPositions);
|
|
connect(m_stateNameItem, &TextItem::textReady, this, &StateItem::titleHasChanged);
|
|
|
|
m_pen = QPen(QColor(0x45, 0x45, 0x45));
|
|
|
|
updateColors();
|
|
updatePolygon();
|
|
}
|
|
|
|
void StateItem::checkWarningItems()
|
|
{
|
|
ScxmlUiFactory *uifactory = uiFactory();
|
|
if (uifactory) {
|
|
auto provider = static_cast<GraphicsItemProvider*>(uifactory->object("graphicsItemProvider"));
|
|
if (provider) {
|
|
if (!m_idWarningItem)
|
|
m_idWarningItem = static_cast<IdWarningItem*>(provider->createWarningItem(Constants::C_STATE_WARNING_ID, this));
|
|
if (!m_stateWarningItem)
|
|
m_stateWarningItem = static_cast<StateWarningItem*>(provider->createWarningItem(Constants::C_STATE_WARNING_STATE, this));
|
|
|
|
if (m_idWarningItem && m_stateWarningItem)
|
|
m_stateWarningItem->setIdWarning(m_idWarningItem);
|
|
|
|
checkWarnings();
|
|
|
|
if (m_idWarningItem || m_stateWarningItem)
|
|
updateAttributes();
|
|
}
|
|
}
|
|
}
|
|
|
|
QVariant StateItem::itemChange(GraphicsItemChange change, const QVariant &value)
|
|
{
|
|
QVariant retValue = ConnectableItem::itemChange(change, value);
|
|
|
|
switch (change) {
|
|
case QGraphicsItem::ItemSceneHasChanged:
|
|
checkWarningItems();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
void StateItem::updateAttributes()
|
|
{
|
|
if (tag()) {
|
|
ConnectableItem::updateAttributes();
|
|
|
|
// Check initial attribute
|
|
QString strNewId = tagValue("id", true);
|
|
if (!m_parallelState) {
|
|
QStringList NSIDs = strNewId.split(tag()->document()->nameSpaceDelimiter(), QString::SkipEmptyParts);
|
|
if (!NSIDs.isEmpty()) {
|
|
NSIDs[NSIDs.count() - 1] = m_stateNameItem->toPlainText();
|
|
QString strOldId = NSIDs.join(tag()->document()->nameSpaceDelimiter());
|
|
ScxmlTag *parentTag = tag()->parentTag();
|
|
if (parentTag && !strOldId.isEmpty() && parentTag->attribute("initial") == strOldId)
|
|
parentTag->setAttribute("initial", strNewId);
|
|
}
|
|
}
|
|
|
|
m_stateNameItem->setText(tagValue("id"));
|
|
if (m_idWarningItem)
|
|
m_idWarningItem->setId(strNewId);
|
|
updateTextPositions();
|
|
|
|
if (m_parallelState)
|
|
checkInitial(true);
|
|
}
|
|
|
|
updateBoundingRect();
|
|
}
|
|
|
|
void StateItem::updateEditorInfo(bool allChildren)
|
|
{
|
|
ConnectableItem::updateEditorInfo(allChildren);
|
|
|
|
QString color = editorInfo(Constants::C_SCXML_EDITORINFO_FONTCOLOR);
|
|
m_stateNameItem->setDefaultTextColor(color.isEmpty() ? QColor(Qt::black) : QColor(color));
|
|
|
|
// Update child too if necessary
|
|
if (allChildren) {
|
|
QList<QGraphicsItem*> children = childItems();
|
|
for (int i = 0; i < children.count(); ++i) {
|
|
if (children[i]->type() >= InitialStateType) {
|
|
auto child = qgraphicsitem_cast<BaseItem*>(children[i]);
|
|
if (child)
|
|
child->updateEditorInfo(allChildren);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void StateItem::updateColors()
|
|
{
|
|
updateDepth();
|
|
m_parallelState = parentItem() && parentItem()->type() == ParallelType;
|
|
|
|
if (m_parallelState)
|
|
m_pen.setStyle(Qt::DashLine);
|
|
else
|
|
m_pen.setStyle(Qt::SolidLine);
|
|
|
|
// Update child color-indices too
|
|
QList<QGraphicsItem*> children = childItems();
|
|
for (int i = 0; i < children.count(); ++i) {
|
|
if (children[i]->type() >= StateType) {
|
|
auto child = qgraphicsitem_cast<StateItem*>(children[i]);
|
|
if (child)
|
|
child->updateColors();
|
|
}
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void StateItem::updateBoundingRect()
|
|
{
|
|
QRectF r2 = childItemsBoundingRect();
|
|
|
|
// Check if we need to increase parent boundingrect
|
|
if (!r2.isNull()) {
|
|
QRectF r = boundingRect();
|
|
QRectF r3 = r.united(r2);
|
|
|
|
if (r != r3) {
|
|
setItemBoundingRect(r3);
|
|
updateTransitions();
|
|
updateUIProperties();
|
|
checkOverlapping();
|
|
}
|
|
}
|
|
}
|
|
|
|
void StateItem::shrink()
|
|
{
|
|
QRectF trect;
|
|
foreach (TransitionItem *item, outputTransitions()) {
|
|
if (item->targetType() == TransitionItem::InternalSameTarget || item->targetType() == TransitionItem::InternalNoTarget) {
|
|
trect = trect.united(item->wholeBoundingRect());
|
|
}
|
|
}
|
|
|
|
QRectF r = boundingRect();
|
|
QRectF r2 = childItemsBoundingRect();
|
|
double minWidth = qMax(120.0, r2.width());
|
|
double minH = qMax((double)minHeight(), r2.height());
|
|
minWidth = qMax(minWidth, m_stateNameItem->boundingRect().width() + (double)WARNING_ITEM_SIZE + (double)TEXT_ITEM_CAP);
|
|
|
|
if (!m_backgroundImage.isNull()) {
|
|
minWidth = qMax(minWidth, (double)m_backgroundImage.width() + 3 * STATE_RADIUS + trect.width());
|
|
minH = qMax(minH, ((double)m_backgroundImage.height() + 3 * STATE_RADIUS + TEXT_ITEM_HEIGHT) / 0.94);
|
|
}
|
|
|
|
r2 = QRectF(0, 0, minWidth, minH);
|
|
r2.moveCenter(r.center());
|
|
|
|
// Check if we need to increase parent boundingrect
|
|
if (r != r2) {
|
|
setItemBoundingRect(r2);
|
|
updateTransitions();
|
|
updateUIProperties();
|
|
}
|
|
}
|
|
|
|
void StateItem::transitionsChanged()
|
|
{
|
|
QRectF rr = boundingRect();
|
|
QRectF rectInternalTransitions;
|
|
QVector<TransitionItem*> internalTransitions = outputTransitions();
|
|
foreach (TransitionItem *item, internalTransitions) {
|
|
if (item->targetType() <= TransitionItem::InternalNoTarget) {
|
|
QRectF br = mapFromItem(item, item->boundingRect()).boundingRect();
|
|
br.setLeft(rr.left() + 20);
|
|
br.setTop(br.top() + 4);
|
|
br.setWidth(br.width() + item->textWidth());
|
|
rectInternalTransitions = rectInternalTransitions.united(br);
|
|
}
|
|
}
|
|
|
|
m_transitionRect = rectInternalTransitions;
|
|
updateBoundingRect();
|
|
}
|
|
|
|
void StateItem::transitionCountChanged()
|
|
{
|
|
ConnectableItem::transitionsChanged();
|
|
checkWarnings();
|
|
transitionsChanged();
|
|
}
|
|
|
|
QRectF StateItem::childItemsBoundingRect() const
|
|
{
|
|
QRectF r;
|
|
QRectF rr = boundingRect();
|
|
|
|
QList<QGraphicsItem*> children = childItems();
|
|
for (int i = 0; i < children.count(); ++i) {
|
|
if (children[i]->type() >= InitialStateType) {
|
|
QRectF br = children[i]->boundingRect();
|
|
QPointF p = children[i]->pos() + br.topLeft();
|
|
br.moveTopLeft(p);
|
|
r = r.united(br);
|
|
}
|
|
}
|
|
|
|
if (m_transitionRect.isValid()) {
|
|
r.setLeft(r.left() - m_transitionRect.width());
|
|
r.setHeight(qMax(r.height(), m_transitionRect.height()));
|
|
r.moveBottom(qMax(r.bottom(), m_transitionRect.bottom()));
|
|
}
|
|
|
|
if (!r.isNull())
|
|
r.adjust(-20, -(rr.height() * 0.06 + 40), 20, 20);
|
|
|
|
return r;
|
|
}
|
|
|
|
void StateItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
ConnectableItem::mouseDoubleClickEvent(event);
|
|
shrink();
|
|
updateUIProperties();
|
|
}
|
|
|
|
void StateItem::createContextMenu(QMenu *menu)
|
|
{
|
|
QVariantMap data;
|
|
if (!m_parallelState) {
|
|
data[Constants::C_SCXMLTAG_ACTIONTYPE] = TagUtils::SetAsInitial;
|
|
menu->addAction(tr("Set as Initial"))->setData(data);
|
|
}
|
|
|
|
data[Constants::C_SCXMLTAG_ACTIONTYPE] = TagUtils::ZoomToState;
|
|
menu->addAction(tr("Zoom to State"))->setData(data);
|
|
|
|
if (type() == ParallelType) {
|
|
data[Constants::C_SCXMLTAG_ACTIONTYPE] = TagUtils::Relayout;
|
|
menu->addAction(tr("Re-Layout"))->setData(data);
|
|
}
|
|
|
|
menu->addSeparator();
|
|
ConnectableItem::createContextMenu(menu);
|
|
}
|
|
|
|
void StateItem::selectedMenuAction(const QAction *action)
|
|
{
|
|
if (!action)
|
|
return;
|
|
|
|
const ScxmlTag *tag = this->tag();
|
|
|
|
if (tag) {
|
|
const QVariantMap data = action->data().toMap();
|
|
const int actionType = data.value(Constants::C_SCXMLTAG_ACTIONTYPE, -1).toInt();
|
|
|
|
ScxmlDocument *document = tag->document();
|
|
switch (actionType) {
|
|
case TagUtils::SetAsInitial: {
|
|
ScxmlTag *parentTag = tag->parentTag();
|
|
if (parentTag) {
|
|
document->undoStack()->beginMacro(tr("Change initial state"));
|
|
|
|
ScxmlTag *initialTag = parentTag->child("initial");
|
|
if (initialTag) {
|
|
ScxmlTag *initialTransitionTag = initialTag->child("transition");
|
|
if (initialTransitionTag)
|
|
document->setValue(initialTransitionTag, "target", tag->attribute("id", true));
|
|
else {
|
|
auto newTransition = new ScxmlTag(Transition, document);
|
|
newTransition->setAttribute("target", tag->attribute("id", true));
|
|
document->addTag(initialTag, newTransition);
|
|
}
|
|
} else
|
|
document->setValue(parentTag, "initial", tag->attribute("id", true));
|
|
|
|
checkInitial(true);
|
|
document->undoStack()->endMacro();
|
|
}
|
|
break;
|
|
}
|
|
case TagUtils::Relayout: {
|
|
document->undoStack()->beginMacro(tr("Re-Layout"));
|
|
doLayout(depth());
|
|
document->undoStack()->endMacro();
|
|
break;
|
|
}
|
|
case TagUtils::ZoomToState: {
|
|
emit BaseItem::openToDifferentView(this);
|
|
break;
|
|
}
|
|
default:
|
|
ConnectableItem::selectedMenuAction(action);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StateItem::checkInitial(bool parent)
|
|
{
|
|
QList<QGraphicsItem*> items;
|
|
ScxmlTag *tag = nullptr;
|
|
|
|
if (parent) {
|
|
if (parentItem()) {
|
|
items = parentItem()->childItems();
|
|
if (parentBaseItem())
|
|
tag = parentBaseItem()->tag();
|
|
} else {
|
|
auto sc = static_cast<GraphicsScene*>(scene());
|
|
if (sc) {
|
|
sc->checkInitialState();
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
items = childItems();
|
|
tag = this->tag();
|
|
}
|
|
|
|
if (!items.isEmpty() && tag && uiFactory()) {
|
|
auto utilsProvider = static_cast<UtilsProvider*>(uiFactory()->object("utilsProvider"));
|
|
if (utilsProvider)
|
|
utilsProvider->checkInitialState(items, tag);
|
|
}
|
|
}
|
|
|
|
void StateItem::titleHasChanged(const QString &text)
|
|
{
|
|
QString strOldId = tagValue("id", true);
|
|
setTagValue("id", text);
|
|
|
|
// Check initial attribute
|
|
if (tag() && !m_parallelState) {
|
|
ScxmlTag *parentTag = tag()->parentTag();
|
|
if (!strOldId.isEmpty() && parentTag->attribute("initial") == strOldId)
|
|
parentTag->setAttribute("initial", tagValue("id", true));
|
|
}
|
|
}
|
|
|
|
void StateItem::updateTextPositions()
|
|
{
|
|
if (m_parallelState) {
|
|
m_stateNameItem->setPos(m_titleRect.x(), m_titleRect.top());
|
|
m_stateNameItem->setItalic(true);
|
|
} else {
|
|
m_stateNameItem->setPos(m_titleRect.center().x() - m_stateNameItem->boundingRect().width() / 2, m_titleRect.top());
|
|
m_stateNameItem->setItalic(false);
|
|
}
|
|
|
|
QPointF p(m_stateNameItem->pos().x() - WARNING_ITEM_SIZE, m_titleRect.center().y() - WARNING_ITEM_SIZE / 2);
|
|
if (m_idWarningItem)
|
|
m_idWarningItem->setPos(p);
|
|
if (m_stateWarningItem)
|
|
m_stateWarningItem->setPos(p);
|
|
}
|
|
|
|
void StateItem::updatePolygon()
|
|
{
|
|
m_drawingRect = boundingRect().adjusted(5, 5, -5, -5);
|
|
|
|
m_polygon.clear();
|
|
m_polygon << m_drawingRect.topLeft()
|
|
<< m_drawingRect.topRight()
|
|
<< m_drawingRect.bottomRight()
|
|
<< m_drawingRect.bottomLeft()
|
|
<< m_drawingRect.topLeft();
|
|
|
|
m_titleRect = QRectF(m_drawingRect.left(), m_drawingRect.top(), m_drawingRect.width(), TEXT_ITEM_HEIGHT + m_drawingRect.height() * 0.06);
|
|
QFont f = m_stateNameItem->font();
|
|
f.setPixelSize(m_titleRect.height() * 0.65);
|
|
m_stateNameItem->setFont(f);
|
|
|
|
updateTextPositions();
|
|
}
|
|
|
|
bool StateItem::canStartTransition(ItemType /*type*/) const
|
|
{
|
|
return !m_parallelState;
|
|
}
|
|
|
|
void StateItem::connectToParent(BaseItem *parentItem)
|
|
{
|
|
ConnectableItem::connectToParent(parentItem);
|
|
updateTextPositions();
|
|
checkInitial(true);
|
|
}
|
|
|
|
void StateItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
|
{
|
|
ConnectableItem::paint(painter, option, widget);
|
|
|
|
painter->save();
|
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
// Set opacity and color
|
|
painter->setOpacity(getOpacity());
|
|
m_pen.setColor(overlapping() ? qRgb(0xff, 0x00, 0x60) : qRgb(0x45, 0x45, 0x45));
|
|
painter->setPen(m_pen);
|
|
QColor stateColor(editorInfo(Constants::C_SCXML_EDITORINFO_STATECOLOR));
|
|
if (!stateColor.isValid())
|
|
stateColor = tag() ? tag()->document()->getColor(depth()) : QColor(0x12, 0x34, 0x56);
|
|
|
|
// Draw basic frame
|
|
QRectF r = boundingRect();
|
|
QLinearGradient grad(r.topLeft(), r.bottomLeft());
|
|
grad.setColorAt(0, stateColor.lighter(115));
|
|
grad.setColorAt(1, stateColor);
|
|
painter->setBrush(QBrush(grad));
|
|
|
|
painter->drawRoundedRect(m_drawingRect, STATE_RADIUS, STATE_RADIUS);
|
|
|
|
// Background image
|
|
ScxmlUiFactory *uifactory = uiFactory();
|
|
if (uifactory) {
|
|
auto imageProvider = static_cast<ImageProvider*>(uifactory->object("imageProvider"));
|
|
if (imageProvider) {
|
|
const QImage *image = imageProvider->backgroundImage(tag());
|
|
if (image) {
|
|
int w = m_drawingRect.width() - 2 * STATE_RADIUS;
|
|
int h = m_drawingRect.height() - m_titleRect.height() - 2 * STATE_RADIUS;
|
|
int left = m_drawingRect.left() + STATE_RADIUS;
|
|
int top = m_drawingRect.top() + m_titleRect.height() + STATE_RADIUS;
|
|
if (m_transitionRect.isValid()) {
|
|
w = m_drawingRect.right() - m_transitionRect.right() - 2 * STATE_RADIUS;
|
|
left = m_transitionRect.right() + STATE_RADIUS;
|
|
}
|
|
|
|
m_backgroundImage = image->scaled(w, h, Qt::KeepAspectRatio);
|
|
painter->drawImage(QPointF(left + (w - m_backgroundImage.width()) / 2, top + (h - m_backgroundImage.height()) / 2), m_backgroundImage);
|
|
} else
|
|
m_backgroundImage = QImage();
|
|
}
|
|
}
|
|
|
|
// Draw title rect
|
|
if (!m_parallelState) {
|
|
painter->drawLine(QLineF(m_titleRect.bottomLeft(), m_titleRect.bottomRight()));
|
|
|
|
if (m_initial) {
|
|
double size = m_titleRect.height() * 0.3;
|
|
painter->setBrush(QColor(0x4d, 0x4d, 0x4d));
|
|
painter->drawEllipse(QPointF(m_titleRect.left() + 2 * size, m_titleRect.center().y()), size, size);
|
|
}
|
|
}
|
|
|
|
// Transition part
|
|
if (m_transitionRect.isValid()) {
|
|
qreal tx = m_transitionRect.right();
|
|
painter->drawLine(QLineF(QPointF(tx, m_titleRect.bottom() + STATE_RADIUS), QPointF(tx, m_drawingRect.bottom() - STATE_RADIUS)));
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
void StateItem::init(ScxmlTag *tag, BaseItem *parentItem, bool initChildren, bool blockUpdates)
|
|
{
|
|
setBlockUpdates(blockUpdates);
|
|
ConnectableItem::init(tag, parentItem);
|
|
|
|
if (initChildren) {
|
|
for (int i = 0; i < tag->childCount(); ++i) {
|
|
ScxmlTag *child = tag->child(i);
|
|
ConnectableItem *newItem = SceneUtils::createItemByTagType(child->tagType(), QPointF());
|
|
if (newItem) {
|
|
newItem->init(child, this, initChildren, blockUpdates);
|
|
newItem->finalizeCreation();
|
|
}
|
|
}
|
|
}
|
|
if (blockUpdates)
|
|
setBlockUpdates(false);
|
|
}
|
|
|
|
QString StateItem::itemId() const
|
|
{
|
|
return m_stateNameItem ? m_stateNameItem->toPlainText() : QString();
|
|
}
|
|
|
|
void StateItem::doLayout(int d)
|
|
{
|
|
if (depth() != d)
|
|
return;
|
|
|
|
SceneUtils::layout(childItems());
|
|
updateBoundingRect();
|
|
shrink();
|
|
}
|
|
|
|
void StateItem::setInitial(bool initial)
|
|
{
|
|
m_initial = initial;
|
|
update();
|
|
checkWarnings();
|
|
}
|
|
|
|
void StateItem::checkWarnings()
|
|
{
|
|
if (m_idWarningItem)
|
|
m_idWarningItem->check();
|
|
if (m_stateWarningItem)
|
|
m_stateWarningItem->check();
|
|
|
|
if (parentItem() && parentItem()->type() == StateType)
|
|
qgraphicsitem_cast<StateItem*>(parentItem())->checkWarnings();
|
|
}
|
|
|
|
bool StateItem::isInitial() const
|
|
{
|
|
return m_initial;
|
|
}
|