1 /*
2 SPDX-License-Identifier: GPL-2.0-or-later
3 SPDX-FileCopyrightText: 2002-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5
6 // own header
7 #include "statewidget.h"
8
9 // app includes
10 #include "cmds/cmdcreatediagram.h"
11 #include "debug_utils.h"
12 #include "dialog_utils.h"
13 #include "docwindow.h"
14 #include "listpopupmenu.h"
15 #include "statedialog.h"
16 #include "uml.h"
17 #include "umldoc.h"
18 #include "umlscene.h"
19 #include "umlview.h"
20 #include "umlwidget.h"
21
22 // kde includes
23 #include <KLocalizedString>
24
25 // qt includes
26 #include <QtGlobal>
27 #include <QPointer>
28 #include <QXmlStreamWriter>
29
DEBUG_REGISTER_DISABLED(StateWidget)30 DEBUG_REGISTER_DISABLED(StateWidget)
31
32 /**
33 * Creates a State widget.
34 *
35 * @param scene The parent of the widget.
36 * @param stateType The type of state.
37 * @param id The ID to assign (-1 will prompt a new ID.)
38 */
39 StateWidget::StateWidget(UMLScene * scene, StateType stateType, Uml::ID::Type id)
40 : UMLWidget(scene, WidgetBase::wt_State, id)
41 {
42 setStateType(stateType);
43 m_drawVertical = true;
44 m_Text = QLatin1String("State");
45 QSizeF size = minimumSize();
46 setSize(size.width(), size.height());
47 }
48
49 /**
50 * Destructor.
51 */
~StateWidget()52 StateWidget::~StateWidget()
53 {
54 }
55
56 /**
57 * Overrides the standard paint event.
58 */
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)59 void StateWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
60 {
61 Q_UNUSED(option);
62 Q_UNUSED(widget);
63
64 const qreal w = width();
65 const qreal h = height();
66 if (w == 0 || h == 0)
67 return;
68
69 setPenFromSettings(painter);
70 switch (m_stateType) {
71 case StateWidget::Normal:
72 {
73 if (UMLWidget::useFillColor()) {
74 painter->setBrush(UMLWidget::fillColor());
75 }
76 const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
77 const int fontHeight = fm.lineSpacing();
78 int textStartY = (h / 2) - (fontHeight / 2);
79 const int count = m_Activities.count();
80 if (count == 0) {
81 painter->drawRoundRect(0, 0, w, h, (h*40)/w, (w*40)/h);
82 painter->setPen(textColor());
83 QFont font = UMLWidget::font();
84 font.setBold(false);
85 painter->setFont(font);
86 painter->drawText(STATE_MARGIN, textStartY,
87 w - STATE_MARGIN * 2, fontHeight,
88 Qt::AlignCenter, name());
89 setPenFromSettings(painter);
90 } else {
91 painter->drawRoundRect(0, 0, w, h, (h*40)/w, (w*40)/h);
92 textStartY = STATE_MARGIN;
93 painter->setPen(textColor());
94 QFont font = UMLWidget::font();
95 font.setBold(true);
96 painter->setFont(font);
97 painter->drawText(STATE_MARGIN, textStartY, w - STATE_MARGIN * 2,
98 fontHeight, Qt::AlignCenter, name());
99 font.setBold(false);
100 painter->setFont(font);
101 setPenFromSettings(painter);
102 int linePosY = textStartY + fontHeight;
103
104 QStringList::Iterator end(m_Activities.end());
105 for(QStringList::Iterator it(m_Activities.begin()); it != end; ++it) {
106 textStartY += fontHeight;
107 painter->drawLine(0, linePosY, w, linePosY);
108 painter->setPen(textColor());
109 painter->drawText(STATE_MARGIN, textStartY, w - STATE_MARGIN * 2,
110 fontHeight, Qt::AlignCenter, *it);
111 setPenFromSettings(painter);
112 linePosY += fontHeight;
113 }//end for
114 }//end else
115 }
116 break;
117 case StateWidget::Initial :
118 painter->setBrush(WidgetBase::lineColor());
119 painter->drawEllipse(0, 0, w, h);
120 break;
121 case StateWidget::End :
122 painter->setBrush(WidgetBase::lineColor());
123 painter->drawEllipse(0, 0, w, h);
124 painter->setBrush(Qt::white);
125 painter->drawEllipse(1, 1, w - 2, h - 2);
126 painter->setBrush(WidgetBase::lineColor());
127 painter->drawEllipse(3, 3, w - 6, h - 6);
128 break;
129 case StateWidget::Fork:
130 case StateWidget::Join:
131 {
132 painter->setPen(Qt::black);
133 painter->setBrush(Qt::black);
134 painter->drawRect(rect());
135 }
136 break;
137 case StateWidget::Junction:
138 {
139 painter->setPen(Qt::black);
140 painter->setBrush(Qt::black);
141 painter->drawEllipse(rect());
142 }
143 break;
144 case StateWidget::DeepHistory:
145 {
146 painter->setBrush(Qt::white);
147 painter->drawEllipse(rect());
148 painter->setPen(Qt::black);
149 painter->setFont(UMLWidget::font());
150 const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
151 const int fontHeight = fm.lineSpacing() / 2;
152 const int xStar = fm.boundingRect(QLatin1String("H")).width();
153 const int yStar = fontHeight / 4;
154 painter->drawText((w / 6),
155 (h / 4) + fontHeight, QLatin1String("H"));
156 painter->drawText((w / 6) + xStar,
157 (h / 4) + fontHeight - yStar, QLatin1String("*"));
158 }
159 break;
160 case StateWidget::ShallowHistory:
161 {
162 painter->setBrush(Qt::white);
163 painter->drawEllipse(rect());
164 painter->setPen(Qt::black);
165 painter->setFont(UMLWidget::font());
166 const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
167 const int fontHeight = fm.lineSpacing() / 2;
168 painter->drawText((w / 6),
169 (h / 4) + fontHeight, QLatin1String("H"));
170 }
171 break;
172 case StateWidget::Choice:
173 {
174 const qreal x = w / 2;
175 const qreal y = h / 2;
176 QPolygonF polygon;
177 polygon << QPointF(x, 0) << QPointF(w, y)
178 << QPointF(x, h) << QPointF(0, y);
179 painter->setBrush(UMLWidget::fillColor());
180 painter->drawPolygon(polygon);
181 }
182 break;
183 case StateWidget::Combined:
184 {
185 const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
186 const int fontHeight = fm.lineSpacing();
187 setPenFromSettings(painter);
188 QPainterPath path;
189 path.addRoundedRect(rect(), STATE_MARGIN, STATE_MARGIN);
190 if (useFillColor())
191 painter->fillPath(path, UMLWidget::fillColor());
192 painter->drawPath(path);
193 painter->drawLine(QPointF(0, fontHeight), QPointF(w, fontHeight));
194 painter->setPen(textColor());
195 QFont font = UMLWidget::font();
196 font.setBold(false);
197 painter->setFont(font);
198 painter->drawText(STATE_MARGIN, 0, w - STATE_MARGIN * 2, fontHeight,
199 Qt::AlignCenter, name());
200 if (!linkedDiagram()) {
201 m_size = QSizeF(fm.width(name()) + STATE_MARGIN * 2, fm.lineSpacing() + STATE_MARGIN);
202 } else {
203 DiagramProxyWidget::setClientRect(rect().adjusted(STATE_MARGIN, fontHeight + STATE_MARGIN, - STATE_MARGIN, -STATE_MARGIN));
204 DiagramProxyWidget::paint(painter, option, widget);
205 QSizeF size = DiagramProxyWidget::sceneRect().size();
206 m_size = QSizeF(qMax<qreal>(fm.width(linkedDiagram()->name()), size.width()) + STATE_MARGIN * 2, fm.lineSpacing() + STATE_MARGIN + size.height());
207 setSize(m_size);
208 }
209 setPenFromSettings(painter);
210 }
211 break;
212 default:
213 uWarning() << "Unknown state type: " << stateTypeStr();
214 break;
215 }
216
217 UMLWidget::paint(painter, option, widget);
218 }
219
220 /**
221 * Overrides method from UMLWidget
222 */
minimumSize() const223 QSizeF StateWidget::minimumSize() const
224 {
225 int width = 10, height = 10;
226 switch (m_stateType) {
227 case StateWidget::Normal:
228 {
229 const QFontMetrics &fm = getFontMetrics(FT_BOLD);
230 const int fontHeight = fm.lineSpacing();
231 int textWidth = fm.width(name());
232 const int count = m_Activities.count();
233 height = fontHeight;
234 if(count > 0) {
235 height = fontHeight * (count + 1);
236
237 QStringList::ConstIterator end(m_Activities.end());
238 for(QStringList::ConstIterator it(m_Activities.begin()); it != end; ++it) {
239 int w = fm.width(*it);
240 if(w > textWidth)
241 textWidth = w;
242 }//end for
243 }//end if
244 width = textWidth > STATE_WIDTH?textWidth:STATE_WIDTH;
245 height = height > STATE_HEIGHT?height:STATE_HEIGHT;
246 width += STATE_MARGIN * 2;
247 height += STATE_MARGIN * 2;
248 break;
249 }
250 case StateWidget::Fork:
251 case StateWidget::Join:
252 if (m_drawVertical) {
253 width = 8;
254 height = 60;
255 } else {
256 width = 60;
257 height = 8;
258 }
259 break;
260 case StateWidget::Junction:
261 width = 18;
262 height = 18;
263 break;
264 case StateWidget::DeepHistory:
265 case StateWidget::ShallowHistory:
266 {
267 const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
268 width = height = fm.lineSpacing();
269 }
270 break;
271 case StateWidget::Choice:
272 width = 25;
273 height = 25;
274 break;
275 case StateWidget::Combined:
276 return m_size;
277 default:
278 break;
279 }
280
281 return QSizeF(width, height);
282 }
283
284 /**
285 * Overrides method from UMLWidget
286 */
maximumSize()287 QSizeF StateWidget::maximumSize()
288 {
289 switch (m_stateType) {
290 case StateWidget::Initial:
291 case StateWidget::End:
292 case StateWidget::Junction:
293 case StateWidget::Choice:
294 return QSizeF(35, 35);
295 break;
296 case StateWidget::DeepHistory:
297 case StateWidget::ShallowHistory:
298 {
299 const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
300 const int fontHeight = fm.lineSpacing();
301 return QSizeF(fontHeight + 10, fontHeight + 10);
302 }
303 break;
304 case StateWidget::Combined:
305 return m_size;
306 default:
307 break;
308 }
309 return UMLWidget::maximumSize();
310 }
311
312 /**
313 * Set the aspect ratio mode.
314 * Some state types have a fixed aspect ratio
315 */
setAspectRatioMode()316 void StateWidget::setAspectRatioMode()
317 {
318 switch (m_stateType) {
319 case StateWidget::Initial:
320 case StateWidget::End:
321 case StateWidget::Choice:
322 case StateWidget::DeepHistory:
323 case StateWidget::ShallowHistory:
324 case StateWidget::Fork:
325 case StateWidget::Join:
326 case StateWidget::Junction:
327 setFixedAspectRatio(true);
328 break;
329 default:
330 setFixedAspectRatio(false);
331 break;
332 }
333 }
334
contextMenuEvent(QGraphicsSceneContextMenuEvent * event)335 void StateWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
336 {
337 #ifdef ENABLE_COMBINED_STATE_DIRECT_EDIT
338 if (m_stateType == Combined)
339 DiagramProxyWidget::contextMenuEvent(event);
340 if (event->isAccepted())
341 #endif
342 UMLWidget::contextMenuEvent(event);
343 }
344
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)345 void StateWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
346 {
347 #ifdef ENABLE_COMBINED_STATE_DIRECT_EDIT
348 if (m_stateType == Combined)
349 DiagramProxyWidget::mouseDoubleClickEvent(event);
350 if (event->isAccepted())
351 #endif
352 UMLWidget::mouseDoubleClickEvent(event);
353 }
354
mousePressEvent(QGraphicsSceneMouseEvent * event)355 void StateWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
356 {
357 #ifdef ENABLE_COMBINED_STATE_DIRECT_EDIT
358 if (m_stateType == Combined)
359 DiagramProxyWidget::mousePressEvent(event);
360 if (event->isAccepted())
361 #endif
362 UMLWidget::mousePressEvent(event);
363 }
364
mouseMoveEvent(QGraphicsSceneMouseEvent * event)365 void StateWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
366 {
367 #ifdef ENABLE_COMBINED_STATE_DIRECT_EDIT
368 if (m_stateType == Combined)
369 DiagramProxyWidget::mouseMoveEvent(event);
370 if (event->isAccepted())
371 #endif
372 UMLWidget::mouseMoveEvent(event);
373 }
374
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)375 void StateWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
376 {
377 #ifdef ENABLE_COMBINED_STATE_DIRECT_EDIT
378 if (m_stateType == Combined)
379 DiagramProxyWidget::mouseReleaseEvent(event);
380 if (event->isAccepted())
381 #endif
382 UMLWidget::mouseReleaseEvent(event);
383 }
384
385 /**
386 * Returns the type of state.
387 * @return StateType
388 */
stateType() const389 StateWidget::StateType StateWidget::stateType() const
390 {
391 return m_stateType;
392 }
393
394 /**
395 * Returns the type string of state.
396 */
stateTypeStr() const397 QString StateWidget::stateTypeStr() const
398 {
399 return QLatin1String(ENUM_NAME(StateWidget, StateType, m_stateType));
400 }
401
402 /**
403 * Sets the type of state.
404 */
setStateType(StateType stateType)405 void StateWidget::setStateType(StateType stateType)
406 {
407 m_stateType = stateType;
408 setAspectRatioMode();
409 if (stateType == Combined) {
410 setAutoResize(false);
411 setResizable(false);
412 }
413 }
414
415 /**
416 * Adds an activity to this widget.
417 * @return true on success
418 */
addActivity(const QString & activity)419 bool StateWidget::addActivity(const QString &activity)
420 {
421 m_Activities.append(activity);
422 updateGeometry();
423 return true;
424 }
425
426 /**
427 * Removes the given activity from the state.
428 */
removeActivity(const QString & activity)429 bool StateWidget::removeActivity(const QString &activity)
430 {
431 if(m_Activities.removeAll(activity) == 0)
432 return false;
433 updateGeometry();
434 return true;
435 }
436
437 /**
438 * Renames the given activity.
439 */
renameActivity(const QString & activity,const QString & newName)440 bool StateWidget::renameActivity(const QString &activity, const QString &newName)
441 {
442 int index = - 1;
443 if((index = m_Activities.indexOf(activity)) == -1)
444 return false;
445 m_Activities[ index ] = newName;
446 return true;
447 }
448
449 /**
450 * Sets the states activities to the ones given.
451 */
setActivities(const QStringList & list)452 void StateWidget::setActivities(const QStringList &list)
453 {
454 m_Activities = list;
455 updateGeometry();
456 }
457
458 /**
459 * Returns the list of activities.
460 */
activities() const461 QStringList StateWidget::activities() const
462 {
463 return m_Activities;
464 }
465
466 /**
467 * Get whether to draw a fork or join vertically.
468 */
drawVertical() const469 bool StateWidget::drawVertical() const
470 {
471 return m_drawVertical;
472 }
473
474 /**
475 * Set whether to draw a fork or join vertically.
476 */
setDrawVertical(bool to)477 void StateWidget::setDrawVertical(bool to)
478 {
479 m_drawVertical = to;
480 setSize(height(), width());
481 updateGeometry();
482 UMLWidget::adjustAssocs(x(), y());
483 }
484
485 /**
486 * Show a properties dialog for a StateWidget.
487 */
showPropertiesDialog()488 bool StateWidget::showPropertiesDialog()
489 {
490 bool result = false;
491 UMLApp::app()->docWindow()->updateDocumentation(false);
492
493 QPointer<StateDialog> dialog = new StateDialog(m_scene->activeView(), this);
494 if (dialog->exec() && dialog->getChangesMade()) {
495 UMLApp::app()->docWindow()->showDocumentation(this, true);
496 UMLApp::app()->document()->setModified(true);
497 result = true;
498 }
499 delete dialog;
500 return result;
501 }
502
503 /**
504 * Creates the "statewidget" XMI element.
505 */
saveToXMI1(QXmlStreamWriter & writer)506 void StateWidget::saveToXMI1(QXmlStreamWriter& writer)
507 {
508 writer.writeStartElement(QLatin1String("statewidget"));
509 UMLWidget::saveToXMI1(writer);
510 writer.writeAttribute(QLatin1String("statename"), m_Text);
511 writer.writeAttribute(QLatin1String("documentation"), m_Doc);
512 writer.writeAttribute(QLatin1String("statetype"), QString::number(m_stateType));
513 if (m_stateType == Fork || m_stateType == Join)
514 writer.writeAttribute(QLatin1String("drawvertical"), QString::number(m_drawVertical));
515 //save states activities
516 writer.writeStartElement(QLatin1String("Activities"));
517
518 QStringList::Iterator end(m_Activities.end());
519 for (QStringList::Iterator it(m_Activities.begin()); it != end; ++it) {
520 writer.writeStartElement(QLatin1String("Activity"));
521 writer.writeAttribute(QLatin1String("name"), *it);
522 writer.writeEndElement();
523 }
524 writer.writeEndElement(); // Activities
525 writer.writeEndElement(); // statewidget
526 }
527
528 /**
529 * Loads a "statewidget" XMI element.
530 */
loadFromXMI1(QDomElement & qElement)531 bool StateWidget::loadFromXMI1(QDomElement & qElement)
532 {
533 if(!UMLWidget::loadFromXMI1(qElement))
534 return false;
535 m_Text = qElement.attribute(QLatin1String("statename"));
536 m_Doc = qElement.attribute(QLatin1String("documentation"));
537 QString type = qElement.attribute(QLatin1String("statetype"), QLatin1String("1"));
538 setStateType((StateType)type.toInt());
539 setAspectRatioMode();
540 QString drawVertical = qElement.attribute(QLatin1String("drawvertical"), QLatin1String("1"));
541 m_drawVertical = (bool)drawVertical.toInt();
542 //load states activities
543 QDomNode node = qElement.firstChild();
544 QDomElement tempElement = node.toElement();
545 if(!tempElement.isNull() && tempElement.tagName() == QLatin1String("Activities")) {
546 QDomNode node = tempElement.firstChild();
547 QDomElement activityElement = node.toElement();
548 while(!activityElement.isNull()) {
549 if(activityElement.tagName() == QLatin1String("Activity")) {
550 QString name = activityElement.attribute(QLatin1String("name"));
551 if(!name.isEmpty())
552 m_Activities.append(name);
553 }//end if
554 node = node.nextSibling();
555 activityElement = node.toElement();
556 }//end while
557 }//end if
558 return true;
559 }
560
561 /**
562 * Captures any popup menu signals for menus it created.
563 */
slotMenuSelection(QAction * action)564 void StateWidget::slotMenuSelection(QAction* action)
565 {
566 bool ok = false;
567 QString nameNew = name();
568
569 ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action);
570 switch(sel) {
571 case ListPopupMenu::mt_Rename:
572 nameNew = name();
573 ok = Dialog_Utils::askNewName(WidgetBase::WidgetType::wt_State, nameNew);
574 if (ok && nameNew.length() > 0) {
575 setName(nameNew);
576 }
577 break;
578
579 case ListPopupMenu::mt_Properties:
580 showPropertiesDialog();
581 break;
582
583 case ListPopupMenu::mt_New_Activity:
584 nameNew = i18n("new activity");
585 ok = Dialog_Utils::askName(i18n("Enter Activity"),
586 i18n("Enter the name of the new activity:"),
587 nameNew);
588 if (ok && nameNew.length() > 0) {
589 addActivity(nameNew);
590 }
591 break;
592
593 case ListPopupMenu::mt_FlipHorizontal:
594 setDrawVertical(false);
595 break;
596 case ListPopupMenu::mt_FlipVertical:
597 setDrawVertical(true);
598 break;
599
600 default:
601 DiagramProxyWidget::slotMenuSelection(action);
602 break;
603 }
604 }
605
606