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