Scxmleditor: Add showing onEntry and on Exit events

This commit enhances the SCXML Editor by adding a new
feature that enables the display of onEntry and onExit
events in the state item. This feature provides developers
with a more comprehensive view of the state machine's behavior,
allowing them to better understand the transitions that occur
when entering or exiting a particular state.

Fixes: QTCREATORBUG-17378
Change-Id: Iaf84af209d9acf13bd4183f7da9acbcd6c1c8214
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Artem Sokolovskii
2023-04-18 16:46:04 +02:00
parent 5614696002
commit 2d1b8dbc29
8 changed files with 228 additions and 15 deletions

View File

@@ -41,7 +41,8 @@ BaseItem::~BaseItem()
void BaseItem::checkParentBoundingRect()
{
BaseItem *parentBaseItem = this->parentBaseItem();
if (parentBaseItem && type() >= InitialStateType && !parentBaseItem->blockUpdates()) {
if ((parentBaseItem && type() >= InitialStateType && !parentBaseItem->blockUpdates())
|| (parentBaseItem && type() == StateWarningType)) {
auto parentStateItem = qgraphicsitem_cast<StateItem*>(parentBaseItem);
if (parentStateItem && (parentStateItem->type() >= StateType))
parentStateItem->updateBoundingRect();

View File

@@ -93,6 +93,7 @@ public:
ScxmlUiFactory *uiFactory() const;
virtual void updateUIProperties();
virtual void addChild(ScxmlTag */*tag*/) {};
protected:
virtual void updatePolygon();

View File

@@ -0,0 +1,108 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "eventitem.h"
#include <QColor>
#include <QFont>
#include <QGraphicsItem>
#include <QList>
#include <QString>
namespace ScxmlEditor {
namespace PluginInterface {
EventItem::EventItem(const QPointF &pos, BaseItem *parent)
: BaseItem(parent)
{
m_eventNameItem = new TextItem(this);
m_eventNameItem->setParentItem(this);
QFont serifFont("Times", 13, QFont::Normal);
m_eventNameItem->setFont(serifFont);
QString color = editorInfo("fontColor");
m_eventNameItem->setDefaultTextColor(color.isEmpty() ? QColor(Qt::black) : QColor(color));
setPos(pos);
m_eventNameItem->setTextInteractionFlags(Qt::NoTextInteraction);
setItemBoundingRect(m_eventNameItem->boundingRect());
}
void EventItem::updateAttributes()
{
QString text = " " + tag()->tagName();
if (tag()->attributeNames().size() > 0) {
for (int i = 0; i < tag()->attributeNames().size(); ++i)
if (tag()->attributeNames().at(i) == "event") {
if (tag()->attributeValues().size() > i)
text += " / " + tag()->attributeValues().at(i);
break;
}
}
m_eventNameItem->setText(text);
setItemBoundingRect(m_eventNameItem->boundingRect());
}
OnEntryExitItem::OnEntryExitItem(BaseItem *parent)
: BaseItem(parent)
{
m_eventNameItem = new TextItem(this);
m_eventNameItem->setParentItem(this);
QFont serifFont("Times", 13, QFont::Normal);
m_eventNameItem->setFont(serifFont);
m_eventNameItem->setDefaultTextColor(Qt::black);
m_eventNameItem->setTextInteractionFlags(Qt::NoTextInteraction);
}
void OnEntryExitItem::updateAttributes()
{
QString text = tag()->tagName();
m_eventNameItem->setText(text);
setItemBoundingRect(childBoundingRect());
checkParentBoundingRect();
}
void OnEntryExitItem::finalizeCreation()
{
auto children = tag()->allChildren();
auto pos = m_eventNameItem->boundingRect().bottomLeft();
for (auto child : children) {
EventItem *item = new EventItem(pos, this);
item->setTag(child);
item->updateAttributes();
pos = item->pos() + item->boundingRect().bottomLeft();
}
setItemBoundingRect(childBoundingRect());
}
void OnEntryExitItem::addChild(ScxmlTag *tag)
{
auto pos = childBoundingRect().bottomLeft();
EventItem *item = new EventItem(pos, this);
item->setTag(tag);
item->updateAttributes();
setItemBoundingRect(childBoundingRect());
checkParentBoundingRect();
}
QRectF OnEntryExitItem::childBoundingRect() const
{
QRectF r = m_eventNameItem->boundingRect();
const QList<QGraphicsItem *> children = childItems();
for (const QGraphicsItem *child : children) {
QRectF br = child->boundingRect();
QPointF p = child->pos() + br.topLeft();
br.moveTopLeft(p);
r = r.united(br);
}
return r;
}
} // namespace PluginInterface
} // namespace ScxmlEditor

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "baseitem.h"
#include "textitem.h"
namespace ScxmlEditor::PluginInterface {
class EventItem : public BaseItem
{
public:
explicit EventItem(const QPointF &pos = QPointF(), BaseItem *parent = nullptr);
void updateAttributes() override;
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
private:
TextItem *m_eventNameItem;
};
class OnEntryExitItem : public BaseItem
{
public:
explicit OnEntryExitItem(BaseItem *parent = nullptr);
int type() const override { return StateWarningType; }
void updateAttributes() override;
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
void finalizeCreation() override;
void addChild(ScxmlTag *tag) override;
QRectF childBoundingRect() const;
private:
TextItem *m_eventNameItem;
};
} // namespace ScxmlEditor::PluginInterface

View File

@@ -467,8 +467,7 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag,
break;
}
case ScxmlDocument::TagChangeParent: {
auto childItem = qobject_cast<ConnectableItem*>(findItem(tag));
auto childItem = findItem(tag);
if (childItem) {
QTC_ASSERT(tag, break);
BaseItem *newParentItem = findItem(tag->parentTag());
@@ -485,8 +484,11 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag,
childItem->setParentItem(newParentItem);
childItem->updateUIProperties();
childItem->updateTransitions(true);
childItem->updateTransitionAttributes(true);
if (auto childConItem = qobject_cast<ConnectableItem*>(findItem(tag))) {
childConItem->updateTransitions(true);
childConItem->updateTransitionAttributes(true);
}
childItem->checkWarnings();
childItem->checkInitial();
if (newParentItem) {
@@ -495,6 +497,8 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag,
newParentItem->checkWarnings();
newParentItem->checkOverlapping();
newParentItem->updateUIProperties();
if (auto newConItem = qobject_cast<StateItem*>(newParentItem))
newConItem->updateBoundingRect();
}
if (oldParentItem)
@@ -549,6 +553,9 @@ void GraphicsScene::endTagChange(ScxmlDocument::TagChange change, ScxmlTag *tag,
}
if (parentItem) {
if (childItem == nullptr)
parentItem->addChild(childTag);
parentItem->updateAttributes();
parentItem->updateUIProperties();
parentItem->checkInitial();

View File

@@ -1,19 +1,18 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "finalstateitem.h"
#include "stateitem.h"
#include "eventitem.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 "scxmleditortr.h"
#include "scxmltagutils.h"
#include "scxmluifactory.h"
#include "stateitem.h"
#include "statewarningitem.h"
#include "textitem.h"
#include "transitionitem.h"
@@ -28,6 +27,7 @@
#include <QTextOption>
#include <QUndoStack>
#include <QtMath>
#include <QRubberBand>
using namespace ScxmlEditor::PluginInterface;
@@ -171,6 +171,7 @@ void StateItem::updateBoundingRect()
// Check if we need to increase parent boundingrect
if (!r2.isNull()) {
positionOnExitItems();
QRectF r = boundingRect();
QRectF r3 = r.united(r2);
@@ -244,7 +245,6 @@ void StateItem::transitionCountChanged()
QRectF StateItem::childItemsBoundingRect() const
{
QRectF r;
QRectF rr = boundingRect();
QList<QGraphicsItem*> children = childItems();
for (int i = 0; i < children.count(); ++i) {
@@ -256,15 +256,26 @@ QRectF StateItem::childItemsBoundingRect() const
}
}
if (m_onEntryItem) {
QRectF br = m_onEntryItem->childBoundingRect();
QPointF p = m_onEntryItem->pos() + br.topLeft();
br.moveTopLeft(p);
r = r.united(br);
}
if (m_onExitItem) {
QRectF br = m_onExitItem->childBoundingRect();
QPointF p = m_onExitItem->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;
}
@@ -418,11 +429,18 @@ void StateItem::updatePolygon()
<< 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);
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);
if (m_onEntryItem)
m_onEntryItem->setPos(m_titleRect.x(), m_titleRect.bottom());
positionOnExitItems();
updateTextPositions();
}
@@ -517,13 +535,41 @@ void StateItem::init(ScxmlTag *tag, BaseItem *parentItem, bool initChildren, boo
if (newItem) {
newItem->init(child, this, initChildren, blockUpdates);
newItem->finalizeCreation();
}
} else
addChild(child);
}
}
if (blockUpdates)
setBlockUpdates(false);
}
void StateItem::addChild(ScxmlTag *child)
{
if (child->tagName() == "onentry") {
OnEntryExitItem *item = new OnEntryExitItem(this);
m_onEntryItem = item;
item->setTag(child);
item->finalizeCreation();
item->updateAttributes();
m_onEntryItem->setPos(m_titleRect.x(), m_titleRect.bottom());
} else if (child->tagName() == "onexit") {
OnEntryExitItem *item = new OnEntryExitItem(this);
m_onExitItem = item;
item->setTag(child);
item->finalizeCreation();
item->updateAttributes();
positionOnExitItems();
}
}
void StateItem::positionOnExitItems()
{
int offset = m_onEntryItem ? m_onEntryItem->boundingRect().height() : 0;
if (m_onExitItem)
m_onExitItem->setPos(m_titleRect.x(), m_titleRect.bottom() + offset);
}
QString StateItem::itemId() const
{
return m_stateNameItem ? m_stateNameItem->toPlainText() : QString();

View File

@@ -4,7 +4,9 @@
#pragma once
#include "connectableitem.h"
#include "textitem.h"
#include <QPen>
#include <QStyleOptionGraphicsItem>
QT_FORWARD_DECLARE_CLASS(QGraphicsSceneMouseEvent)
@@ -16,6 +18,7 @@ class TransitionItem;
class TextItem;
class IdWarningItem;
class StateWarningItem;
class OnEntryExitItem;
/**
* @brief The StateItem class represents the SCXML-State.
@@ -49,6 +52,8 @@ public:
QRectF childItemsBoundingRect() const;
void connectToParent(BaseItem *parentItem) override;
void addChild(ScxmlTag *child) override;
protected:
void updatePolygon() override;
void transitionsChanged() override;
@@ -69,6 +74,7 @@ private:
void updateTextPositions();
void checkParentBoundingRect();
void checkWarningItems();
void positionOnExitItems();
TextItem *m_stateNameItem;
StateWarningItem *m_stateWarningItem = nullptr;
@@ -76,6 +82,8 @@ private:
QPen m_pen;
bool m_initial = false;
bool m_parallelState = false;
QPointer<OnEntryExitItem> m_onEntryItem;
QPointer<OnEntryExitItem> m_onExitItem;
QImage m_backgroundImage;
};