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 "combinedfragmentwidget.h"
8 
9 // app includes
10 #include "debug_utils.h"
11 #include "dialog_utils.h"
12 #include "listpopupmenu.h"
13 #include "uml.h"
14 #include "umldoc.h"
15 #include "umlscene.h"
16 #include "umlview.h"
17 
18 // kde includes
19 #include <KLocalizedString>
20 
21 // qt includes
22 #include <QPainter>
23 #include <QString>
24 #include <QXmlStreamWriter>
25 
26 DEBUG_REGISTER_DISABLED(CombinedFragmentWidget)
27 
28 static const int defaultWidth = 100;
29 static const int defaultHeight = 50;
30 static const int initialLabelWidth = 45;
31 static const int labelHeight = 20;
32 static const int labelBorderMargin = 10;
33 
34 /**
35  * Creates a Combined Fragment widget.
36  *
37  * @param scene              The parent of the widget.
38  * @param combinedfragmentType      The type of combined fragment.
39  * @param id                The ID to assign (-1 will prompt a new ID.)
40  */
CombinedFragmentWidget(UMLScene * scene,CombinedFragmentType combinedfragmentType,Uml::ID::Type id)41 CombinedFragmentWidget::CombinedFragmentWidget(UMLScene * scene, CombinedFragmentType combinedfragmentType, Uml::ID::Type id)
42   : UMLWidget(scene, WidgetBase::wt_CombinedFragment, id),
43     m_labelWidth(initialLabelWidth)
44 {
45     setZValue(-10);
46     setCombinedFragmentType(combinedfragmentType);
47 }
48 
49 /**
50  * Destructor.
51  */
~CombinedFragmentWidget()52 CombinedFragmentWidget::~CombinedFragmentWidget()
53 {
54     for (QList<FloatingDashLineWidget*>::iterator it=m_dashLines.begin() ; it!=m_dashLines.end() ; ++it) {
55         delete(*it);
56     }
57 }
58 
59 /**
60  * Overrides the standard paint event.
61  */
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)62 void CombinedFragmentWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
63 {
64     Q_UNUSED(option);
65     Q_UNUSED(widget);
66 
67     int w = width();
68     int h = height();
69     int line_width = initialLabelWidth;
70     qreal old_Y;
71 
72     setPenFromSettings(painter);
73 
74     if (m_CombinedFragment == Ref) {
75         if (UMLWidget::useFillColor()) {
76             painter->setBrush(UMLWidget::fillColor());
77         }
78     }
79     const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
80     const int fontHeight  = fm.lineSpacing();
81     const QString combined_fragment_value =  name();
82     int textStartY = (h / 2) - (fontHeight / 2);
83     painter->drawRect(0, 0, w, h);
84 
85     painter->setPen(textColor());
86     painter->setFont(UMLWidget::font());
87         QString temp = QLatin1String("loop");
88 
89     switch (m_CombinedFragment)
90     {
91         case Ref :
92         painter->drawText(defaultMargin, textStartY, w - defaultMargin * 2, fontHeight, Qt::AlignCenter, combined_fragment_value);
93         painter->drawText(defaultMargin, 0, w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("ref"));
94         break;
95 
96         case Opt :
97         painter->drawText(defaultMargin, 0,
98             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("opt"));
99         break;
100 
101         case Break :
102         painter->drawText(defaultMargin, 0,
103             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("break"));
104         break;
105 
106         case Loop :
107                 if (combined_fragment_value != QLatin1String("-"))
108                 {
109                      temp += QLatin1String(" [") + combined_fragment_value + QLatin1Char(']');
110                      line_width += (combined_fragment_value.size() + 2) * 8;
111                 }
112         painter->drawText(defaultMargin, 0, w - defaultMargin * 2, fontHeight, Qt::AlignLeft, temp);
113 
114         break;
115 
116         case Neg :
117         painter->drawText(defaultMargin, 0,
118             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("neg"));
119         break;
120 
121         case Crit :
122         painter->drawText(defaultMargin, 0,
123             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("critical"));
124         break;
125 
126         case Ass :
127         painter->drawText(defaultMargin, 0,
128             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("assert"));
129         break;
130 
131         case Alt :
132                 if (combined_fragment_value != QLatin1String("-"))
133                 {
134                      temp = QLatin1Char('[') + combined_fragment_value + QLatin1Char(']');
135             painter->drawText(defaultMargin, labelHeight,
136                               w - defaultMargin * 2, fontHeight, Qt::AlignLeft, temp);
137                     if (m_dashLines.size() == 1 && m_dashLines.first()->y() < y() + labelHeight + fontHeight)
138                         m_dashLines.first()->setY(y() + h/2);
139                 }
140                 painter->drawText(defaultMargin, 0,
141             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("alt"));
142                 // dash lines
143                 //m_dashLines.first()->paint(painter);
144                 // TODO: move to UMLWidget::calculateSize api
145                 for (QList<FloatingDashLineWidget*>::iterator it=m_dashLines.begin() ; it!=m_dashLines.end() ; ++it) {
146                     (*it)->setX(x());
147                     old_Y = (*it)->getYMin();
148                     (*it)->setYMin(y());
149                     (*it)->setYMax(y() + height());
150                     (*it)->setY(y() + (*it)->y() - old_Y);
151                     (*it)->setSize(w, (*it)->height());
152                     (*it)->setLineColor(lineColor());
153                     (*it)->setLineWidth(lineWidth());
154                 }
155 
156         break;
157 
158         case Par :
159                 painter->drawText(defaultMargin, 0,
160             w - defaultMargin * 2, fontHeight, Qt::AlignLeft, QLatin1String("parallel"));
161                 // dash lines
162                 if (m_dashLines.size() != 0) {
163                     //m_dashLines.first()->paint(painter);
164                     // TODO: move to UMLWidget::calculateSize api
165                     for (QList<FloatingDashLineWidget*>::iterator it=m_dashLines.begin() ; it!=m_dashLines.end() ; ++it) {
166                         (*it)->setX(x());
167                         old_Y = (*it)->getYMin();
168                         (*it)->setYMin(y());
169                         (*it)->setYMax(y() + height());
170                         (*it)->setY(y() + (*it)->y() - old_Y);
171                         (*it)->setSize(w, (*it)->height());
172                         (*it)->setLineColor(lineColor());
173                         (*it)->setLineWidth(lineWidth());
174                     }
175                 }
176         break;
177 
178     default : break;
179     }
180 
181     setPenFromSettings(painter);
182     painter->drawLine(0, labelHeight, line_width, labelHeight);
183     painter->drawLine(line_width, labelHeight, line_width + labelBorderMargin, labelHeight-labelBorderMargin);
184     painter->drawLine(line_width + labelBorderMargin, labelHeight-labelBorderMargin, line_width + labelBorderMargin, 0);
185     m_labelWidth = line_width;
186 
187     UMLWidget::paint(painter, option, widget);
188 }
189 
190 /**
191  * Overrides method from UMLWidget.
192  */
minimumSize() const193 QSizeF CombinedFragmentWidget::minimumSize() const
194 {
195     int width = 10, height = 10;
196     const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
197     const int fontHeight  = fm.lineSpacing();
198     const int textWidth = fm.width(name());
199     height = fontHeight;
200     width = textWidth + 60 > defaultWidth ? textWidth + 60: defaultWidth;
201     if (m_CombinedFragment == Loop)
202          width += int((float)textWidth * 0.4f);
203     if (m_CombinedFragment == Alt)
204          height += fontHeight + 40;
205     height = height > defaultHeight ? height : defaultHeight;
206     width += defaultMargin * 2;
207     height += defaultMargin * 2;
208 
209     return QSizeF(width, height);
210 }
211 
212 /**
213  * Returns the type of combined fragment.
214  */
combinedFragmentType() const215 CombinedFragmentWidget::CombinedFragmentType CombinedFragmentWidget::combinedFragmentType() const
216 {
217     return m_CombinedFragment;
218 }
219 
220 /**
221  * Sets the type of combined fragment.
222  */
setCombinedFragmentType(CombinedFragmentType combinedfragmentType)223 void CombinedFragmentWidget::setCombinedFragmentType(CombinedFragmentType combinedfragmentType)
224 {
225     m_CombinedFragment = combinedfragmentType;
226     UMLWidget::m_resizable =  true ; //(m_CombinedFragment == Normal);
227     bool isLoading = UMLApp::app()->document()->loading();
228     // creates a dash line if the combined fragment type is alternative or parallel
229     if (!isLoading && m_CombinedFragment == Alt && m_dashLines.isEmpty())
230     {
231         m_dashLines.push_back(new FloatingDashLineWidget(m_scene, Uml::ID::None, this));
232         m_scene->addWidgetCmd(m_dashLines.back());
233     }
234 }
235 
236 /**
237  * Returns the type of combined fragment.
238  */
combinedFragmentType(const QString & type) const239 CombinedFragmentWidget::CombinedFragmentType CombinedFragmentWidget::combinedFragmentType(const QString& type) const
240 {
241     if (type == QLatin1String("Reference"))
242         return (CombinedFragmentWidget::Ref);
243     if (type == QLatin1String("Option"))
244         return (CombinedFragmentWidget::Opt);
245     if (type == QLatin1String("Break"))
246         return (CombinedFragmentWidget::Break);
247     if (type == QLatin1String("Loop"))
248         return (CombinedFragmentWidget::Loop);
249     if (type == QLatin1String("Negative"))
250         return (CombinedFragmentWidget::Neg);
251     if (type == QLatin1String("Critical"))
252         return (CombinedFragmentWidget::Crit);
253     if (type == QLatin1String("Assertion"))
254         return (CombinedFragmentWidget::Ass);
255     if (type == QLatin1String("Alternative"))
256         return (CombinedFragmentWidget::Alt);
257     if (type == QLatin1String("Parallel"))
258         return (CombinedFragmentWidget::Par);
259     // Shouldn't happen
260     Q_ASSERT(0);
261     return (CombinedFragmentWidget::Ref);
262 }
263 
264 /**
265  * Sets the type of combined fragment.
266  */
setCombinedFragmentType(const QString & combinedfragmentType)267 void CombinedFragmentWidget::setCombinedFragmentType(const QString& combinedfragmentType)
268 {
269     setCombinedFragmentType(combinedFragmentType(combinedfragmentType));
270 }
271 
272 /**
273  * ...
274  */
askNameForWidgetType(UMLWidget * & targetWidget,const QString & dialogTitle,const QString & dialogPrompt,const QString & defaultName)275 void CombinedFragmentWidget::askNameForWidgetType(UMLWidget* &targetWidget, const QString& dialogTitle,
276     const QString& dialogPrompt, const QString& defaultName)
277 {
278     Q_UNUSED(defaultName);
279     bool pressedOK = false;
280     const QStringList list = QStringList()
281                              << QLatin1String("Reference")
282                              << QLatin1String("Option")
283                              << QLatin1String("Break")
284                              << QLatin1String("Loop")
285                              << QLatin1String("Negative")
286                              << QLatin1String("Critical")
287                              << QLatin1String("Assertion")
288                              << QLatin1String("Alternative")
289                              << QLatin1String("Parallel") ;
290 #if QT_VERSION >= 0x050000
291     QPointer<QInputDialog> inputDlg = new QInputDialog();
292     inputDlg->setComboBoxItems(list);
293     inputDlg->setOptions(QInputDialog::UseListViewForComboBoxItems);
294     inputDlg->setWindowTitle(dialogTitle);
295     inputDlg->setLabelText(dialogPrompt);
296     pressedOK = inputDlg->exec();
297     QStringList result;
298     result.append(inputDlg->textValue());
299     delete inputDlg;
300 #else
301     const QStringList select = list;
302     QStringList result = KInputDialog::getItemList (dialogTitle, dialogPrompt, list, select, false, &pressedOK, UMLApp::app());
303 #endif
304     if (pressedOK) {
305         QString type = result.join(QString());
306         targetWidget->asCombinedFragmentWidget()->setCombinedFragmentType(type);
307         if (type == QLatin1String("Reference"))
308             Dialog_Utils::askNameForWidget(targetWidget, i18n("Enter the name of the diagram referenced"), i18n("Enter the name of the diagram referenced"), i18n("Diagram name"));
309         else if (type == QLatin1String(QLatin1String("Loop")))
310             Dialog_Utils::askNameForWidget(targetWidget, i18n("Enter the guard of the loop"), i18n("Enter the guard of the loop"), i18n("-"));
311         else if (type == QLatin1String("Alternative"))
312             Dialog_Utils::askNameForWidget(targetWidget, i18n("Enter the first alternative name"), i18n("Enter the first alternative name"), i18n("-"));
313     } else {
314         targetWidget->cleanup();
315         delete targetWidget;
316         targetWidget = 0;
317     }
318 }
319 
320 /**
321  * Saves the widget to the "combinedFragmentwidget" XMI element.
322  */
saveToXMI1(QXmlStreamWriter & writer)323 void CombinedFragmentWidget::saveToXMI1(QXmlStreamWriter& writer)
324 {
325     writer.writeStartElement(QLatin1String("combinedFragmentwidget"));
326     UMLWidget::saveToXMI1(writer);
327     writer.writeAttribute(QLatin1String("combinedFragmentname"), m_Text);
328     writer.writeAttribute(QLatin1String("documentation"), m_Doc);
329     writer.writeAttribute(QLatin1String("CombinedFragmenttype"), QString::number(m_CombinedFragment));
330 
331     // save the corresponding floating dash lines
332     for (QList<FloatingDashLineWidget*>::iterator it = m_dashLines.begin() ; it != m_dashLines.end() ; ++it) {
333         (*it)-> saveToXMI1(writer);
334     }
335 
336     writer.writeEndElement();
337 }
338 
339 /**
340  * Loads the widget from the "CombinedFragmentwidget" XMI element.
341  */
loadFromXMI1(QDomElement & qElement)342 bool CombinedFragmentWidget::loadFromXMI1(QDomElement & qElement)
343 {
344     if (!UMLWidget::loadFromXMI1(qElement))
345         return false;
346     m_Text = qElement.attribute(QLatin1String("combinedFragmentname"));
347     m_Doc = qElement.attribute(QLatin1String("documentation"));
348     QString type = qElement.attribute(QLatin1String("CombinedFragmenttype"));
349 
350     //now load child elements
351     QDomNode node = qElement.firstChild();
352     QDomElement element = node.toElement();
353     while (!element.isNull()) {
354         QString tag = element.tagName();
355         if (tag == QLatin1String("floatingdashlinewidget")) {
356             FloatingDashLineWidget * fdlwidget = new FloatingDashLineWidget(m_scene, Uml::ID::None, this);
357             m_dashLines.push_back(fdlwidget);
358             if (!fdlwidget->loadFromXMI1(element)) {
359               // Most likely cause: The FloatingTextWidget is empty.
360                 delete m_dashLines.back();
361                 return false;
362             }
363             else {
364                 m_scene->addWidgetCmd(fdlwidget);
365                 fdlwidget->clipSize();
366             }
367         } else {
368             uError() << "unknown tag " << tag;
369         }
370         node = node.nextSibling();
371         element = node.toElement();
372     }
373    // m_dashLines = listline;
374     setCombinedFragmentType((CombinedFragmentType)type.toInt());
375 
376     return true;
377 }
378 
removeDashLine(FloatingDashLineWidget * line)379 void CombinedFragmentWidget::removeDashLine(FloatingDashLineWidget *line)
380 {
381     if (m_dashLines.contains(line))
382         m_dashLines.removeOne(line);
383 }
384 
385 /**
386  * Overrides the function from UMLWidget. Deletes all FloatingDashLineWidgets
387  * that are associated with this instance.
388  */
cleanup()389 void CombinedFragmentWidget::cleanup()
390 {
391   foreach (FloatingDashLineWidget* w, m_dashLines)
392   {
393     if (!w->isSelected()) {
394       // no need to make this undoable, since the dashlines will be
395       // reconstructed when deleting the combined fragment is undone.
396       umlScene()->removeWidgetCmd(w);
397     }
398   }
399 }
400 
401 /**
402  * Overrides the function from UMLWidget.
403  *
404  * @param action  The command to be executed.
405  */
slotMenuSelection(QAction * action)406 void CombinedFragmentWidget::slotMenuSelection(QAction* action)
407 {
408     ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action);
409     switch (sel) {
410           // for alternative or parallel combined fragments
411     case ListPopupMenu::mt_AddInteractionOperand:
412         m_dashLines.push_back(new FloatingDashLineWidget(m_scene, Uml::ID::None, this));
413         if (m_CombinedFragment == Alt)
414         {
415             m_dashLines.back()->setText(QLatin1String("else"));
416         }
417         // TODO: move to UMLWidget::calculateSize api
418         m_dashLines.back()->setX(x());
419         m_dashLines.back()->setYMin(y());
420         m_dashLines.back()->setYMax(y() + height());
421         m_dashLines.back()->setY(y() + height() / 2);
422         m_dashLines.back()->setSize(width(), m_dashLines.back()->height());
423         m_scene->setupNewWidget(m_dashLines.back());
424         break;
425 
426     case ListPopupMenu::mt_Rename:
427         {
428             bool ok = false;
429             QString name = m_Text;
430             if (m_CombinedFragment == Alt) {
431                 ok = Dialog_Utils::askName(i18n("Enter first alternative"),
432                                            i18n("Enter first alternative :"),
433                                            name);
434             }
435             else if (m_CombinedFragment == Ref) {
436                 ok = Dialog_Utils::askName(i18n("Enter referenced diagram name"),
437                                            i18n("Enter referenced diagram name :"),
438                                            name);
439             }
440             else if (m_CombinedFragment == Loop) {
441                 ok = Dialog_Utils::askName(i18n("Enter the guard of the loop"),
442                                            i18n("Enter the guard of the loop:"),
443                                            name);
444             }
445             if (ok && name.length() > 0)
446                 m_Text = name;
447         }
448         break;
449 
450     case ListPopupMenu::mt_Properties:
451         showPropertiesDialog();
452         break;
453 
454     default:
455         UMLWidget::slotMenuSelection(action);
456     }
457 }
458 
459 /**
460  * Overrides UMLWidget method to optionally set-up a dashed line
461  */
activate(IDChangeLog * ChangeLog)462 bool CombinedFragmentWidget::activate(IDChangeLog *ChangeLog)
463 {
464     if(UMLWidget::activate(ChangeLog))
465     {
466         if (!UMLApp::app()->document()->loading())
467             setDashLineGeometryAndPosition();
468         return true;
469     }
470     return false;
471 }
472 
473 /**
474  * Sets the geometry and positions of the last dash line
475  */
setDashLineGeometryAndPosition() const476 void CombinedFragmentWidget::setDashLineGeometryAndPosition() const
477 {
478     if (m_CombinedFragment == Alt && !m_dashLines.isEmpty())
479     {
480         m_dashLines.back()->setText(QLatin1String("else"));
481         m_dashLines.back()->setX(x());
482         m_dashLines.back()->setYMin(y());
483         m_dashLines.back()->setYMax(y() + height());
484         m_dashLines.back()->setY(y() + height() / 2);
485         m_dashLines.back()->setSize(width(), m_dashLines.back()->height());
486     }
487 }
488 
toForeground()489 void CombinedFragmentWidget::toForeground()
490 {
491 }
492 
boundingRect() const493 QRectF CombinedFragmentWidget::boundingRect() const
494 {
495     const qreal r = defaultMargin / 2.0;
496     return rect().adjusted(-r, -r, r, r);
497 }
498 
shape() const499 QPainterPath CombinedFragmentWidget::shape() const
500 {
501     const qreal w = width();
502     const qreal h = height();
503     const QFontMetrics &fm = getFontMetrics(FT_NORMAL);
504     const int fontHeight = fm.lineSpacing();
505 
506     const qreal s = selectionMarkerSize * resizeMarkerLineCount + 4;
507     const qreal r = defaultMargin / 2.0;
508     qreal lw = m_labelWidth + r;
509     qreal lh = labelHeight + r;
510     if (m_CombinedFragment == Alt) {
511         const int textWidth = fm.width(name() + QLatin1String("[]"));
512         lh += fontHeight;
513         if (lw < textWidth)
514             lw = textWidth + r;
515     }
516 
517     QPainterPath outerPath;
518     outerPath.setFillRule(Qt::WindingFill);
519     outerPath.addRect(boundingRect());
520 
521     QPolygonF inner;
522     inner.append(QPointF(r, lh));
523     inner.append(QPointF(lw, lh));
524     inner.append(QPointF(lw + labelBorderMargin, lh - labelBorderMargin));
525     inner.append(QPointF(lw + labelBorderMargin, r));
526     inner.append(QPointF(w - selectionMarkerSize - r, r));
527     inner.append(QPointF(w - r, selectionMarkerSize + r));
528     inner.append(QPointF(w - r, h - s));
529     inner.append(QPointF(w - s, h - r));
530     inner.append(QPointF(selectionMarkerSize + r, h - r));
531     inner.append(QPointF(r, h - selectionMarkerSize - r));
532     inner.append(QPointF(r, lh));
533     QPainterPath innerPath;
534     innerPath.addPolygon(inner);
535 
536     QPainterPath result = outerPath.subtracted(innerPath);
537     return result.simplified();
538 }
539