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