KDStateMachineEditor API Documentation 2.1
Loading...
Searching...
No Matches
graphvizlayouterbackend.cpp
Go to the documentation of this file.
1/*
2 This file is part of the KDAB State Machine Editor Library.
3
4 SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: Kevin Funk <kevin.funk@kdab.com>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LicenseRef-KDAB-KDStateMachineEditor
8
9 Licensees holding valid commercial KDAB State Machine Editor Library
10 licenses may use this file in accordance with the KDAB State Machine Editor
11 Library License Agreement provided with the Software.
12
13 Contact info@kdab.com if any conditions of this licensing are not clear to you.
14*/
15
17
18#include <config-kdsme.h>
19
20#include "gvutils.h"
21
22#include "state.h"
23#include "transition.h"
24#include "elementmodel.h"
25#include "elementwalker.h"
26#include "layoutproperties.h"
27#include "layoututils.h"
28#include "util/objecthelper.h"
29
30#include <graphviz/gvc.h>
31#ifdef WITH_CGRAPH
32#include <graphviz/cgraph.h>
33#else
34#include <graphviz/graph.h>
35#endif
36
37#include "debug.h"
38
39#include <QDir>
40#include <QFile>
41#include <QPainterPath>
42#include <QPoint>
43
44#include <clocale> //for LC_NUMERIC
45
46#define IF_DEBUG(x)
47
48using namespace KDSME;
49using namespace KDSME::GVUtils;
50using namespace ObjectHelper;
51
52namespace {
53
55const char *DEFAULT_LAYOUT_TOOL = "dot";
56
63const qreal DOT_DEFAULT_DPI = 72.0;
64const qreal DISPLAY_DPI = 96.0;
65const qreal TO_DOT_DPI_RATIO = DISPLAY_DPI / DOT_DEFAULT_DPI;
66
67QVector<QPair<const char *, const char *>> attributesForState(const State *state)
68{
69 typedef QPair<const char *, const char *> EntryType;
70 typedef QVector<EntryType> ResultType;
71
72 if (const auto *pseudoState = qobject_cast<const PseudoState *>(state)) {
73 switch (pseudoState->kind()) {
75 return ResultType()
76 << EntryType("label", "") // get rid off 'no space for label' warnings
77 << EntryType("shape", "circle")
78 << EntryType("fixedsize", "true")
79 << EntryType("height", "0.20")
80 << EntryType("width", "0.20");
81 default:
82 break;
83 }
84 }
85
86 if (state->type() == State::HistoryStateType) {
87 return ResultType()
88 << EntryType("label", "H*") // get rid off 'no space for label' warnings
89 << EntryType("shape", "circle")
90 << EntryType("fixedsize", "true");
91 }
92
93 if (state->type() == State::FinalStateType) {
94 return ResultType()
95 << EntryType("shape", "doublecircle")
96 << EntryType("label", "")
97 << EntryType("style", "filled")
98 << EntryType("fillcolor", "black")
99 << EntryType("fixedsize", "true")
100 << EntryType("height", "0.15")
101 << EntryType("width", "0.15");
102 }
103
104 // default:
105 return ResultType()
106 << EntryType("shape", "rectangle")
107 << EntryType("style", "rounded");
108}
109
110bool isAncestorCollapsed(const State *current)
111{
112 while (current) {
113 auto parent = current->parentState();
114 if (parent && !parent->isExpanded())
115 return true;
116
117 current = parent;
118 }
119 return false;
120}
121
122}
123
124struct GraphvizLayouterBackend::Private // NOLINT(clang-analyzer-cplusplus.NewDelete)
125{
126 Private();
127 ~Private();
128
129 void buildState(State *state, Agraph_t *graph);
130 void buildTransitions(const State *state, Agraph_t *graph);
131 void buildTransition(Transition *transition, Agraph_t *graph);
132
133 void import();
134 void importItem(Element *item, void *obj) const;
135 void importState(State *state, Agnode_t *node) const;
136 void importState(State *state, Agraph_t *graph) const;
137 void importTransition(Transition *transition, Agedge_t *edge) const;
138
140 void openContext(const QString &id);
142 void closeLayout();
143
147 QRectF boundingRectForGraph(Agraph_t *graph) const;
151 QRectF labelRectForEdge(Agedge_t *edge) const;
155 QPainterPath pathForEdge(Agedge_t *edge) const;
156
157 inline Agnode_t *agnodeForState(State *state) const;
158
160 Agraph_t *m_graph = nullptr;
162 GVC_t *m_context = nullptr;
163
164 LayoutMode m_layoutMode = RecursiveMode;
165 const LayoutProperties *m_properties = nullptr;
166
168 QPointer<State> m_root;
169 QHash<Element *, Agnode_t *> m_elementToDummyNodeMap;
170 QHash<Element *, void *> m_elementToPointerMap;
171};
172
173GraphvizLayouterBackend::Private::Private()
174{
175 // hide non-critical warnings, such as
176 // Warning: node '0x15e1800', graph 'GraphvizLayouterBackend@0xa90330' size too small for label
177 // decrease debug level to AGERR from AGWARN (default)
178 agseterr(AGERR);
179}
180
181GraphvizLayouterBackend::Private::~Private()
182{
183}
184
185void GraphvizLayouterBackend::Private::buildState(State *state, Agraph_t *graph)
186{
187 Q_ASSERT(state);
188 Q_ASSERT(graph);
189
190 IF_DEBUG(qCDebug(KDSME_CORE) << state->label() << *state << graph);
191
192 const LocaleLocker _;
193
194 // build nodes
195 if (m_layoutMode == RecursiveMode && !state->childStates().isEmpty()) {
196 const QString graphName = u"cluster" + addressToString(state);
197 Agraph_t *newGraph = _agsubg(graph, graphName);
198
199 m_elementToPointerMap[state] = newGraph;
200 _agset(newGraph, QStringLiteral("label"), state->label().isEmpty() ? QObject::tr("<unnamed>") : state->label() + u" ###"); // add a placeholder for the expand/collapse button
201
202 auto dummyNode = _agnode(newGraph, u"dummynode_" + graphName);
203 _agset(dummyNode, QStringLiteral("shape"), QStringLiteral("point"));
204 _agset(dummyNode, QStringLiteral("style"), QStringLiteral("invis"));
205 m_elementToDummyNodeMap[state] = dummyNode;
206
207 if (!isAncestorCollapsed(state)) {
208 const auto childStates = state->childStates();
209 for (State *childState : childStates) {
210 buildState(childState, newGraph);
211 }
212 }
213 } else {
214 if (m_layoutMode == RecursiveMode && isAncestorCollapsed(state)) {
215 return;
216 }
217
218 Agnode_t *newNode = _agnode(graph, addressToString(state));
219 m_elementToPointerMap[state] = newNode;
220
221 if (!qIsNull(state->width()) && !qIsNull(state->height())) {
222 _agset(newNode, QStringLiteral("width"), QString::number(state->width() / DISPLAY_DPI));
223 _agset(newNode, QStringLiteral("height"), QString::number(state->height() / DISPLAY_DPI));
224 _agset(newNode, QStringLiteral("fixedsize"), QStringLiteral("true"));
225 }
226 if (!state->label().isEmpty()) {
227 _agset(newNode, QStringLiteral("label"), state->label());
228 }
229
230 const auto attrs = attributesForState(qobject_cast<State *>(state));
231 for (const auto &kv : attrs) {
232 _agset(newNode, QString::fromLatin1(kv.first), QString::fromLatin1(kv.second));
233 }
234 }
235}
236
237void GraphvizLayouterBackend::Private::buildTransitions(const State *state, Agraph_t *graph)
238{
239 IF_DEBUG(qCDebug(KDSME_CORE) << state->label() << *state << graph);
240
241 const auto stateTransitions = state->transitions();
242 for (Transition *transition : stateTransitions) {
243 buildTransition(transition, graph);
244 }
245
246 if (m_layoutMode == RecursiveMode) {
247 const auto childStates = state->childStates();
248 for (const State *childState : childStates) {
249 buildTransitions(childState, graph); // recursive call
250 }
251 }
252}
253
254void GraphvizLayouterBackend::Private::buildTransition(Transition *transition, Agraph_t *graph)
255{
256 if (!transition->targetState()) {
257 return;
258 }
259
260 if (m_layoutMode == RecursiveMode && isAncestorCollapsed(transition->sourceState())) {
261 return;
262 }
263
264 IF_DEBUG(qCDebug(KDSME_CORE) << transition->label() << *transition << graph);
265
266 const auto sourceState = transition->sourceState();
267 const auto targetState = transition->targetState();
268
269 Agnode_t *source = agnodeForState(sourceState);
270 if (!source)
271 return;
272 Agnode_t *target = agnodeForState(targetState);
273 if (!target)
274 return;
275
276 auto sourceDummyNode = m_elementToDummyNodeMap.value(sourceState);
277 auto targetDummyNode = m_elementToDummyNodeMap.value(targetState);
278
279 Agedge_t *edge = _agedge(graph,
280 sourceDummyNode ? sourceDummyNode : source,
281 targetDummyNode ? targetDummyNode : target,
282 addressToString(transition), true);
283 if (!transition->label().isEmpty() && m_properties->showTransitionLabels()) {
284 _agset(edge, QStringLiteral("label"), transition->label());
285 }
286
287 // in order to connect subgraphs we need to leverage ltail + lhead attribute of edges
288 // see: https://stackoverflow.com/questions/2012036/graphviz-how-to-connect-subgraphs
289 if (sourceDummyNode) {
290 const QString graphName = u"cluster" + addressToString(sourceState);
291 _agset(edge, QStringLiteral("ltail"), graphName);
292 }
293 if (targetDummyNode) {
294 const QString graphName = u"cluster" + addressToString(targetState);
295 _agset(edge, QStringLiteral("lhead"), graphName);
296 }
297 m_elementToPointerMap[transition] = edge;
298 Q_ASSERT(edge);
299}
300
301void GraphvizLayouterBackend::Private::import()
302{
303 IF_DEBUG(qCDebug(KDSME_CORE) << m_elementToPointerMap.keys();)
304
305 const LocaleLocker _;
307 walker.walkItems(m_root, [this](Element *element) {
308 if (auto obj = m_elementToPointerMap.value(element)) {
309 importItem(element, obj);
310 }
312 });
313}
314
315void GraphvizLayouterBackend::Private::importItem(Element *item, void *obj) const
316{
317 if (auto *state = qobject_cast<State *>(item)) {
318 if (m_layoutMode == RecursiveMode && !state->childStates().isEmpty()) {
319 importState(state, static_cast<Agraph_t *>(obj));
320 } else {
321 importState(state, static_cast<Agnode_t *>(obj));
322 }
323 } else if (auto *transition = qobject_cast<Transition *>(item)) {
324 importTransition(transition, static_cast<Agedge_t *>(obj));
325 }
326}
327
328void GraphvizLayouterBackend::Private::importState(State *state, Agnode_t *node) const
329{
330 Q_ASSERT(state);
331 Q_ASSERT(node);
332
333 IF_DEBUG(qCDebug(KDSME_CORE) << "before" << state->label() << *state << node);
334
335 // cppcheck-suppress-begin cstyleCast
336 // Fetch the X coordinate, apply the DPI conversion rate (actual DPI / 72, used by dot)
337 const qreal x = ND_coord(node).x * TO_DOT_DPI_RATIO;
338
339 // Translate the Y coordinate from bottom-left to top-left corner
340 const qreal y = (GD_bb(m_graph).UR.y - ND_coord(node).y) * TO_DOT_DPI_RATIO;
341
342 // Transform the width and height from inches to pixels
343
344 state->setWidth(ND_width(node) * DISPLAY_DPI);
345 state->setHeight(ND_height(node) * DISPLAY_DPI);
346 // cppcheck-suppress-end cstyleCast
347
348 const QPointF absolutePos = QPointF(x - state->width() / 2, y - state->height() / 2);
349 if (m_layoutMode == RecursiveMode) {
350 const QPointF relativePos = absolutePos - (state->parentElement() ? state->parentElement()->absolutePos() : QPointF());
351 state->setPos(relativePos);
352 } else {
353 state->setPos(absolutePos);
354 }
355
356 IF_DEBUG(qCDebug(KDSME_CORE) << "after" << state->label() << *state << node);
357}
358
359void GraphvizLayouterBackend::Private::importState(State *state, Agraph_t *graph) const
360{
361 Q_ASSERT(state);
362 Q_ASSERT(graph);
363
364 IF_DEBUG(qCDebug(KDSME_CORE) << "before" << state->label() << *state << graph);
365
366 const QRectF rect = boundingRectForGraph(graph);
367
368 state->setWidth(rect.width());
369 state->setHeight(rect.height());
370
371 const QPointF absolutePos = rect.topLeft();
372 if (m_layoutMode == RecursiveMode) {
373 const QPointF relativePos = absolutePos - (state->parentElement() ? state->parentElement()->absolutePos() : QPointF());
374 state->setPos(relativePos);
375 } else {
376 state->setPos(absolutePos);
377 }
378
379 IF_DEBUG(qCDebug(KDSME_CORE) << "after" << state->label() << *state << graph);
380}
381
382void GraphvizLayouterBackend::Private::importTransition(Transition *transition, Agedge_t *edge) const
383{
384 Q_ASSERT(transition);
385 Q_ASSERT(edge);
386
387 IF_DEBUG(qCDebug(KDSME_CORE) << "before" << transition << edge);
388 // transform to local coordinate system, set position offset
389 const QPainterPath path = pathForEdge(edge);
390 const QRectF labelRect = labelRectForEdge(edge);
391 const QRectF boundingRect = labelRect.united(path.boundingRect());
392 const QPointF absolutePos = boundingRect.topLeft();
393 Q_ASSERT(transition->parentElement());
394 if (m_layoutMode == RecursiveMode) {
395 const QPointF relativePos = absolutePos - transition->parentElement()->absolutePos();
396 transition->setPos(relativePos);
397 } else {
398 transition->setPos(absolutePos - transition->parentElement()->pos());
399 }
400 transition->setShape(path.translated(-absolutePos));
401 transition->setLabelBoundingRect(labelRect.translated(-absolutePos));
402 IF_DEBUG(qCDebug(KDSME_CORE) << "after" << transition << edge);
403}
404
405extern "C" {
406#if KDSME_STATIC_GRAPHVIZ
407GVC_t *gvContextWithStaticPlugins();
408#endif
409}
410
411void GraphvizLayouterBackend::Private::openContext(const QString &id)
412{
413 const LocaleLocker _;
414
415 m_elementToDummyNodeMap.clear();
416 m_elementToPointerMap.clear();
417
418#ifdef WITH_CGRAPH
419 m_graph = _agopen(id, Agdirected, &AgDefaultDisc);
420#else
421 m_graph = _agopen(id, AGDIGRAPH);
422#endif
423
424 // modify settings
425 if (m_layoutMode == RecursiveMode) {
426 _agset(m_graph, QStringLiteral("compound"), QStringLiteral("true"));
427 }
428 _agset(m_graph, QStringLiteral("overlap"), QStringLiteral("prism"));
429 _agset(m_graph, QStringLiteral("overlap_shrink"), QStringLiteral("true"));
430 _agset(m_graph, QStringLiteral("splines"), QStringLiteral("true"));
431 _agset(m_graph, QStringLiteral("pad"), QStringLiteral("0.0"));
432 _agset(m_graph, QStringLiteral("dpi"), QStringLiteral("96.0"));
433 _agset(m_graph, QStringLiteral("nodesep"), QStringLiteral("0.2"));
434}
435
436void GraphvizLayouterBackend::Private::closeLayout()
437{
438 if (!m_graph) {
439 return;
440 }
441
442 // TODO: Intentional leak: Graphviz segfaults otherwise
443 // gvFreeLayout(m_context, m_graph);
444
445 agclose(m_graph);
446 m_graph = nullptr;
447
448 m_root = nullptr;
449 m_properties = nullptr;
450
451 agreseterrors();
452}
453
454QRectF GraphvizLayouterBackend::Private::boundingRectForGraph(Agraph_t *graph) const
455{
456 // cppcheck-suppress-begin cstyleCast
457 const qreal left = GD_bb(graph).LL.x * TO_DOT_DPI_RATIO;
458 const qreal top = (GD_bb(m_graph).UR.y - GD_bb(graph).LL.y) * TO_DOT_DPI_RATIO;
459 const qreal right = GD_bb(graph).UR.x * TO_DOT_DPI_RATIO;
460 const qreal bottom = (GD_bb(m_graph).UR.y - GD_bb(graph).UR.y) * TO_DOT_DPI_RATIO;
461 // cppcheck-suppress-end cstyleCast
462 return QRectF(left, top, right - left, bottom - top).normalized();
463}
464
465QRectF GraphvizLayouterBackend::Private::labelRectForEdge(Agedge_t *edge) const
466{
467 // cppcheck-suppress-begin cstyleCast
468 if (!ED_label(edge))
469 return QRectF();
470
471 // note that the position attributes in graphviz point to the *center* of this element.
472 // we need to subtract half of the width/height to get the top-left position
473 const double posx = ED_label(edge)->pos.x;
474 const double posy = ED_label(edge)->pos.y;
475 const QRectF labelBoundingRect = QRectF(
476 (posx - ED_label(edge)->dimen.x / 2.0) * TO_DOT_DPI_RATIO,
477 ((GD_bb(m_graph).UR.y - posy) - ED_label(edge)->dimen.y / 2.0) * TO_DOT_DPI_RATIO,
478 ED_label(edge)->dimen.x * TO_DOT_DPI_RATIO,
479 ED_label(edge)->dimen.y * TO_DOT_DPI_RATIO);
480 // cppcheck-suppress-end cstyleCast
481 return labelBoundingRect;
482}
483
484QPainterPath GraphvizLayouterBackend::Private::pathForEdge(Agedge_t *edge) const
485{
486 QPainterPath path;
487 // Calculate the path from the spline (only one spline, as the graph is strict.
488 // If it wasn't, we would have to iterate over the first list too)
489 // Calculate the path from the spline (only one as the graph is strict)
490 // cppcheck-suppress-begin cstyleCast
491 if (ED_spl(edge) && (ED_spl(edge)->list != nullptr) && (ED_spl(edge)->list->size % 3 == 1)) {
492 // If there is a starting point, draw a line from it to the first curve point
493 if (ED_spl(edge)->list->sflag) {
494 path.moveTo(ED_spl(edge)->list->sp.x * TO_DOT_DPI_RATIO,
495 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->sp.y) * TO_DOT_DPI_RATIO);
496 path.lineTo(ED_spl(edge)->list->list[0].x * TO_DOT_DPI_RATIO,
497 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->list[0].y) * TO_DOT_DPI_RATIO);
498 } else {
499 path.moveTo(ED_spl(edge)->list->list[0].x * TO_DOT_DPI_RATIO,
500 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->list[0].y) * TO_DOT_DPI_RATIO);
501 }
502
503 // Loop over the curve points
504 for (size_t i = 1; i < ED_spl(edge)->list->size; i += 3) {
505 path.cubicTo(ED_spl(edge)->list->list[i].x * TO_DOT_DPI_RATIO,
506 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->list[i].y) * TO_DOT_DPI_RATIO,
507 ED_spl(edge)->list->list[i + 1].x * TO_DOT_DPI_RATIO,
508 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->list[i + 1].y) * TO_DOT_DPI_RATIO,
509 ED_spl(edge)->list->list[i + 2].x * TO_DOT_DPI_RATIO,
510 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->list[i + 2].y) * TO_DOT_DPI_RATIO);
511 }
512
513 // If there is an ending point, draw a line to it
514 if (ED_spl(edge)->list->eflag) {
515 path.lineTo(ED_spl(edge)->list->ep.x * TO_DOT_DPI_RATIO,
516 (GD_bb(m_graph).UR.y - ED_spl(edge)->list->ep.y) * TO_DOT_DPI_RATIO);
517 }
518 }
519 // cppcheck-suppress-end cstyleCast
520
521 return path;
522}
523
524Agnode_t *GraphvizLayouterBackend::Private::agnodeForState(State *state) const
525{
526 Q_ASSERT(state);
527 return static_cast<Agnode_t *>(m_elementToPointerMap.value(state));
528}
529
530#if !KDSME_STATIC_GRAPHVIZ && !defined(Q_OS_WINDOWS)
531extern "C" {
532
533extern gvplugin_library_t gvplugin_dot_layout_LTX_library;
534
535lt_symlist_t lt_preloaded_symbols[] = {
536 { "gvplugin_dot_layout_LTX_library", &gvplugin_dot_layout_LTX_library },
537 { 0, 0 },
538};
539}
540#endif
541
543 : d(new Private)
544{
545 // create context
546#if KDSME_STATIC_GRAPHVIZ
547 d->m_context = gvContextWithStaticPlugins();
548#elif !defined(Q_OS_WINDOWS)
549 d->m_context = gvContextPlugins(lt_preloaded_symbols, 1);
550#else
551 d->m_context = gvContext();
552#endif
553 Q_ASSERT(d->m_context);
554}
555
557{
558 closeLayout();
559
560 // close context
561 Q_ASSERT(d->m_context);
562 gvFreeContext(d->m_context);
563 d->m_context = nullptr;
564
565 delete d;
566}
567
572
574{
575 d->m_layoutMode = mode;
576}
577
579{
580 // do the actual layouting
581 _gvLayout(d->m_context, d->m_graph, DEFAULT_LAYOUT_TOOL);
582
583 if (qEnvironmentVariableIsSet("KDSME_DEBUG_GRAPHVIZ")) {
584 const auto state = d->m_root;
585 const auto machine = state->machine();
586 Q_ASSERT(machine);
587 const QString machineName = !machine->label().isEmpty() ? machine->label() : ObjectHelper::addressToString(machine);
588 const QString stateName = !state->label().isEmpty() ? state->label() : ObjectHelper::addressToString(state);
589 const QDir tmpDir = QDir::temp();
590 tmpDir.mkdir(QStringLiteral("kdsme_debug"));
591 const QString baseName = QStringLiteral("%1/%2_%3").arg(tmpDir.filePath(QStringLiteral("kdsme_debug"))).arg(machineName).arg(stateName); // clazy:exclude=qstring-arg
592 saveToFile(baseName + u".png");
593 saveToFile(baseName + u".dot", QStringLiteral("dot"));
594 }
595}
596
597void GraphvizLayouterBackend::saveToFile(const QString &filePath, const QString &format)
598{
599 if (!d->m_context) {
600 qCDebug(KDSME_CORE) << "Cannot render image, context not open:" << filePath;
601 return;
602 }
603
604 const LocaleLocker _;
605 QFile file(filePath);
606 if (file.open(QIODevice::WriteOnly)) {
607 const int rc = gvRenderFilename(d->m_context, d->m_graph, qPrintable(format), qPrintable(filePath));
608 if (rc != 0) {
609 qCDebug(KDSME_CORE) << "gvRenderFilename to" << filePath << "failed with return-code:" << rc;
610 }
611 } else {
612 qCDebug(KDSME_CORE) << "Cannot render image, cannot open:" << filePath;
613 return;
614 }
615}
616
618{
619 d->m_root = state; // NOLINT(clang-analyzer-cplusplus.NewDelete)
620 d->m_properties = properties;
621
622 d->openContext(QStringLiteral("GraphvizLayouterBackend@%1").arg(addressToString(this)));
623}
624
626{
627 d->closeLayout();
628}
629
631{
632 d->buildState(state, d->m_graph);
633}
634
636{
637 d->buildTransitions(state, d->m_graph);
638}
639
641{
642 d->buildTransition(transition, d->m_graph);
643}
644
646{
647 d->import();
648}
649
651{
652 if (!d->m_graph)
653 return QRectF();
654
655 return d->boundingRectForGraph(d->m_graph);
656}
void openLayout(KDSME::State *state, const KDSME::LayoutProperties *properties)
void buildTransitions(const KDSME::State *state)
void buildState(KDSME::State *state)
void setLayoutMode(LayoutMode mode)
void buildTransition(KDSME::Transition *transition)
void saveToFile(const QString &filePath, const QString &format=QStringLiteral("png"))
@ RecursiveMode
Performs a recursive import of all state machine elements,.
qreal width
Definition element.h:43
void setPos(const QPointF &pos)
Definition element.cpp:97
QPointF absolutePos() const
Definition element.cpp:134
qreal height
Definition element.h:44
QPointF pos
The position of the element from the top-left corner.
Definition element.h:42
void setWidth(qreal width)
Definition element.cpp:111
QString label
Definition element.h:40
void setHeight(qreal height)
Definition element.cpp:125
Element * parentElement() const
Definition element.cpp:205
QList< Transition * > transitions() const
Definition state.cpp:98
QList< State * > childStates() const
Definition state.cpp:93
Q_INVOKABLE KDSME::StateMachine * machine() const
Definition state.cpp:195
Q_INVOKABLE KDSME::State * parentState() const
Definition state.cpp:78
Type type() const override
Definition state.cpp:73
KDSME::State * sourceState
Definition transition.h:26
KDSME::State * targetState
Definition transition.h:27
void setShape(const QPainterPath &shape)
void setLabelBoundingRect(const QRectF &rect)
@ RecursiveWalk
Traverse the children of this item.
Definition treewalker.h:49
lt_symlist_t lt_preloaded_symbols[]
gvplugin_library_t gvplugin_dot_layout_LTX_library
#define IF_DEBUG(x)
QRectF boundingRect(QSGGeometry *geometry)
int _agset(void *object, const QString &attr, const QString &value)
Directly use agsafeset which always works, contrarily to agset.
Definition gvutils.cpp:119
int _gvLayout(GVC_t *gvc, graph_t *g, const char *engine)
Definition gvutils.cpp:125
Agraph_t * _agopen(const QString &name, int kind)
The agopen method for opening a graph.
Definition gvutils.cpp:33
Agnode_t * _agnode(Agraph_t *graph, const QString &attr, bool create=true)
Definition gvutils.cpp:81
Agraph_t * _agsubg(Agraph_t *graph, const QString &attr, bool create=true)
Definition gvutils.cpp:107
Agedge_t * _agedge(Agraph_t *graph, Agnode_t *tail, Agnode_t *head, const QString &name=QString(), bool create=true)
Definition gvutils.cpp:93
KDSME_CORE_EXPORT QString addressToString(const void *p)

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDStateMachineEditor
Create Qt State Machine metacode using a graphical user interface
https://github.com/KDAB/KDStateMachineEditor
Generated on Tue Jul 15 2025 15:21:47 for KDStateMachineEditor API Documentation by doxygen 1.9.8