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 // onw header
7 #include "messagewidget.h"
8 
9 //app includes
10 #include "classifier.h"
11 #include "debug_utils.h"
12 #include "dialog_utils.h"
13 #include "docwindow.h"
14 #include "floatingtextwidget.h"
15 #include "listpopupmenu.h"
16 #include "objectwidget.h"
17 #include "operation.h"
18 #include "uml.h"
19 #include "umldoc.h"
20 #include "messagewidgetpropertiesdialog.h"
21 #include "umlview.h"
22 #include "uniqueid.h"
23 #include "idchangelog.h"
24 
25 //qt includes
26 #include <QMoveEvent>
27 #include <QPainter>
28 #include <QPolygon>
29 #include <QResizeEvent>
30 #include <QXmlStreamWriter>
31 
32 //kde includes
33 #include <KLocalizedString>
34 
35 DEBUG_REGISTER_DISABLED(MessageWidget)
36 
37 static const int circleWidth = 10;
38 
39 /**
40  * Constructs a MessageWidget.
41  *
42  * This method is used for creation, synchronous and synchronous message types.
43  *
44  * @param scene   The parent to this class.
45  * @param a       The role A widget for this message.
46  * @param b       The role B widget for this message.
47  * @param y       The vertical position to display this message.
48  * @param sequenceMessageType Whether synchronous or asynchronous
49  * @param id      A unique id used for deleting this object cleanly.
50  *                The default (-1) will prompt generation of a new ID.
51  */
MessageWidget(UMLScene * scene,ObjectWidget * a,ObjectWidget * b,int y,Uml::SequenceMessage::Enum sequenceMessageType,Uml::ID::Type id)52 MessageWidget::MessageWidget(UMLScene * scene, ObjectWidget* a, ObjectWidget* b,
53                              int y, Uml::SequenceMessage::Enum sequenceMessageType,
54                              Uml::ID::Type id /* = Uml::id_None */)
55   : UMLWidget(scene, WidgetBase::wt_Message, id)
56 {
57     init();
58     m_pOw[Uml::RoleType::A] = a;
59     m_pOw[Uml::RoleType::B] = b;
60     m_sequenceMessageType = sequenceMessageType;
61     if (m_sequenceMessageType == Uml::SequenceMessage::Creation) {
62         y -= m_pOw[Uml::RoleType::B]->height() / 2;
63         m_pOw[Uml::RoleType::B]->setY(y);
64     } else if (m_sequenceMessageType == Uml::SequenceMessage::Destroy)
65         m_pOw[Uml::RoleType::B]->setShowDestruction(true);
66     updateResizability();
67     calculateWidget();
68     y = y < getMinY() ? getMinY() : y;
69     if (y > b->getEndLineY())
70         b->setEndLine(y);
71     setY(y);
72 
73     this->activate();
74 }
75 
76 /**
77  * Constructs a MessageWidget.
78  *
79  * @param scene       The parent to this class.
80  * @param seqMsgType  The Uml::SequenceMessage::Enum of this message widget
81  * @param id          The ID to assign (-1 will prompt a new ID.)
82  */
MessageWidget(UMLScene * scene,Uml::SequenceMessage::Enum seqMsgType,Uml::ID::Type id)83 MessageWidget::MessageWidget(UMLScene * scene, Uml::SequenceMessage::Enum seqMsgType,
84                              Uml::ID::Type id)
85   : UMLWidget(scene, WidgetBase::wt_Message, id)
86 {
87     init();
88     m_sequenceMessageType = seqMsgType;
89 }
90 
91 /**
92  * Constructs a Lost or Found MessageWidget.
93  *
94  * @param scene  The parent to this class.
95  * @param a      The role A widget for this message.
96  * @param xclick The horizontal position clicked by the user
97  * @param yclick The vertical position clicked by the user
98  * @param sequenceMessageType Whether lost or found
99  * @param id     The ID to assign (-1 will prompt a new ID.)
100  */
MessageWidget(UMLScene * scene,ObjectWidget * a,int xclick,int yclick,Uml::SequenceMessage::Enum sequenceMessageType,Uml::ID::Type id)101 MessageWidget::MessageWidget(UMLScene * scene, ObjectWidget* a, int xclick, int yclick,
102                              Uml::SequenceMessage::Enum sequenceMessageType,
103                              Uml::ID::Type id /*= Uml::id_None*/)
104   : UMLWidget(scene, WidgetBase::wt_Message, id)
105 {
106     init();
107     m_pOw[Uml::RoleType::A] = a;
108     m_pOw[Uml::RoleType::B] = a;
109 
110     m_sequenceMessageType = sequenceMessageType;
111 
112     m_xclicked = xclick;
113     m_yclicked = yclick;
114 
115     updateResizability();
116     calculateWidget();
117     yclick = yclick < getMinY() ? getMinY() : yclick;
118     yclick = yclick > getMaxY() ? getMaxY() : yclick;
119     setY(yclick);
120     m_yclicked = yclick;
121 
122     this->activate();
123 }
124 
125 /**
126  * Initializes key variables of the class.
127  */
init()128 void MessageWidget::init()
129 {
130     m_xclicked = -1;
131     m_yclicked = -1;
132     m_ignoreSnapToGrid = true;
133     m_ignoreSnapComponentSizeToGrid = true;
134     m_pOw[Uml::RoleType::A] = m_pOw[Uml::RoleType::B] = 0;
135     m_pFText = 0;
136 }
137 
138 /**
139  * Standard destructor.
140  */
~MessageWidget()141 MessageWidget::~MessageWidget()
142 {
143     if (m_pOw[Uml::RoleType::B] && m_sequenceMessageType == Uml::SequenceMessage::Destroy)
144         m_pOw[Uml::RoleType::B]->setShowDestruction(false);
145 }
146 
147 /**
148  * Sets the y-coordinate.
149  * Reimplemented from UMLWidget.
150  *
151  * @param y The y-coordinate to be set.
152  */
setY(qreal y)153 void MessageWidget::setY(qreal y)
154 {
155     if (y < getMinY()) {
156         DEBUG(DBG_SRC) << "got out of bounds y position, check the reason" << this->y() << getMinY();
157         return;
158     }
159 
160     UMLWidget::setY(y);
161     if (m_sequenceMessageType == Uml::SequenceMessage::Creation) {
162         const qreal objWidgetHalfHeight = m_pOw[Uml::RoleType::B]->height() / 2;
163         m_pOw[Uml::RoleType::B]->setY(y - objWidgetHalfHeight);
164     }
165 
166     if (m_pFText && !UMLApp::app()->document()->loading()) {
167         setTextPosition();
168         emit sigMessageMoved();
169     }
170 }
171 
172 /**
173  * Update the UMLWidget::m_resizable flag according to the
174  * charactersitics of this message.
175  */
updateResizability()176 void MessageWidget::updateResizability()
177 {
178     if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous)
179         UMLWidget::m_resizable = true;
180     else
181         UMLWidget::m_resizable = false;
182 }
183 
184 /**
185  * Overridden from UMLWidget.
186  * Checks if the mouse is in resize area and sets the cursor accordingly.
187  * The resize area is usually at the right bottom corner of the widget
188  * except in case of a message widget running from right to left.
189  * In that case the resize area is at the left bottom corner in order
190  * to avoid overlap with an execution rectangle at the right.
191  *
192  * @param me The QMouseEVent to check.
193  * @return true if the mouse is in resize area, false otherwise.
194  */
isInResizeArea(QGraphicsSceneMouseEvent * me)195 bool MessageWidget::isInResizeArea(QGraphicsSceneMouseEvent *me)
196 {
197     if (!m_resizable) {
198         m_scene->activeView()->setCursor(Qt::ArrowCursor);
199         DEBUG(DBG_SRC) << "!m_resizable";
200         return false;
201     }
202 
203     qreal m = 7.0;
204     const qreal w = width();
205     const qreal h = height();
206 
207     // If the widget itself is very small then make the resize area small, too.
208     // Reason: Else it becomes impossible to do a move instead of resize.
209     if (w - m < m || h - m < m) {
210         m = 2.0;
211     }
212 
213     if (me->scenePos().y() < y() + h - m) {
214         m_scene->activeView()->setCursor(Qt::ArrowCursor);
215         DEBUG(DBG_SRC) << "Y condition not satisfied";
216         return false;
217     }
218 
219     int x1 = m_pOw[Uml::RoleType::A]->x();
220     int x2 = m_pOw[Uml::RoleType::B]->x();
221     if ((x1 < x2 && me->scenePos().x() >= x() + w - m) ||
222         (x1 > x2 && me->scenePos().x() >= x() - m)) {
223         m_scene->activeView()->setCursor(Qt::SizeVerCursor);
224         DEBUG(DBG_SRC) << "X condition is satisfied";
225         return true;
226     } else {
227         m_scene->activeView()->setCursor(Qt::ArrowCursor);
228         DEBUG(DBG_SRC) << "X condition not satisfied";
229         return false;
230     }
231 }
232 
233 /**
234  * Overridden from UMLWidget.
235  * Resizes the height of the message widget and emits the message moved signal.
236  * Message widgets can only be resized vertically, so width isn't modified.
237  *
238  * @param newW   The new width for the widget (isn't used).
239  * @param newH   The new height for the widget.
240  */
resizeWidget(qreal newW,qreal newH)241 void MessageWidget::resizeWidget(qreal newW, qreal newH)
242 {
243     if (sequenceMessageType() == Uml::SequenceMessage::Creation)
244         setSize(width(), newH);
245     else {
246         qreal x1 = m_pOw[Uml::RoleType::A]->x();
247         qreal x2 = getxclicked();
248         qreal diffX = 0;
249         if (x1 < x2) {
250             diffX = x2 + (newW - width());
251         }
252         else {
253             diffX = x2 - (newW - width());
254         }
255         if (diffX <= 0 )
256             diffX = 10;
257         setxclicked (diffX);
258         setSize(newW, newH);
259         calculateWidget();
260 
261     }
262     emit sigMessageMoved();
263 }
264 
265 /**
266  * Constrains the vertical position of the message widget so it doesn't go
267  * above the bottom side of the lower object.
268  * The height of the floating text widget in the message is taken into account
269  * if there is any and it isn't empty.
270  *
271  * @param diffY The difference between current Y position and new Y position.
272  * @return The new Y position, constrained.
273  */
constrainPositionY(qreal diffY)274 qreal MessageWidget::constrainPositionY(qreal diffY)
275 {
276     qreal newY = y() + diffY;
277 
278     qreal minY = getMinY();
279     if (m_pFText && !m_pFText->displayText().isEmpty()) {
280         minY += m_pFText->height();
281     }
282 
283     if (newY < minY) {
284         newY = minY;
285     }
286 
287     return newY;
288 }
289 
290 /**
291  * Overridden from UMLWidget.
292  * Moves the widget to a new position using the difference between the
293  * current position and the new position. X position is ignored, and widget
294  * is only moved along Y axis. If message goes upper than the object, it's
295  * kept at this position until it should be lowered again (the unconstrained
296  * Y position is saved to know when it's the time to lower it again).
297  * If the message is a creation message, the object created is also moved to
298  * the new vertical position.
299  * @see constrainPositionY
300  *
301  * @param diffX The difference between current X position and new X position
302  *                          (isn't used).
303  * @param diffY The difference between current Y position and new Y position.
304  */
moveWidgetBy(qreal diffX,qreal diffY)305 void MessageWidget::moveWidgetBy(qreal diffX, qreal diffY)
306 {
307     Q_UNUSED(diffX);
308     qreal newY = constrainPositionY(diffY);
309     setY(newY);
310 }
311 
312 /**
313  * Overridden from UMLWidget.
314  * Modifies the value of the diffX and diffY variables used to move the widgets.
315  * All the widgets are constrained to be moved only in Y axis (diffX is set to 0).
316  * @see constrainPositionY
317  *
318  * @param diffX The difference between current X position and new X position.
319  * @param diffY The difference between current Y position and new Y position.
320  */
constrainMovementForAllWidgets(qreal & diffX,qreal & diffY)321 void MessageWidget::constrainMovementForAllWidgets(qreal &diffX, qreal &diffY)
322 {
323     diffX = 0;
324     diffY = constrainPositionY(diffY) - y();
325 }
326 
327 /**
328  * Reimplemented from UMLWidget and calls other paint...() methods
329  * depending on the message type.
330  */
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)331 void MessageWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
332 {
333     Q_UNUSED(option);
334     Q_UNUSED(widget);
335 
336     if(!m_pOw[Uml::RoleType::A] || !m_pOw[Uml::RoleType::B]) {
337         return;
338     }
339     setPenFromSettings(painter);
340     if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) {
341         paintSynchronous(painter, option);
342     } else if (m_sequenceMessageType == Uml::SequenceMessage::Asynchronous) {
343         paintAsynchronous(painter, option);
344     } else if (m_sequenceMessageType == Uml::SequenceMessage::Creation) {
345         paintCreation(painter, option);
346     } else if (m_sequenceMessageType == Uml::SequenceMessage::Destroy) {
347         paintDestroy(painter, option);
348     } else if (m_sequenceMessageType == Uml::SequenceMessage::Lost) {
349         paintLost(painter, option);
350     } else if (m_sequenceMessageType == Uml::SequenceMessage::Found) {
351         paintFound(painter, option);
352     } else {
353         uWarning() << "Unknown message type";
354     }
355 }
356 
357 /**
358  * Draw a solid (triangular) arrowhead pointing in the given direction.
359  * The direction can be either Qt::LeftArrow or Qt::RightArrow.
360  */
paintSolidArrowhead(QPainter * p,int x,int y,Qt::ArrowType direction)361 void MessageWidget::paintSolidArrowhead(QPainter *p, int x, int y, Qt::ArrowType direction)
362 {
363     int arrowheadExtentX = 4;
364     if (direction == Qt::RightArrow) {
365         arrowheadExtentX = -arrowheadExtentX;
366     }
367     QPolygon points;
368     points.putPoints(0, 3, x, y, x + arrowheadExtentX, y - 3, x + arrowheadExtentX, y + 3);
369     p->setBrush(QBrush(p->pen().color()));
370     p->drawPolygon(points);
371 }
372 
373 /**
374  * Draw an arrow pointing in the given direction.
375  * The arrow head is not solid, i.e. it is made up of two lines
376  * like so:  --->
377  * The direction can be either Qt::LeftArrow or Qt::RightArrow.
378  */
paintArrow(QPainter * p,int x,int y,int w,Qt::ArrowType direction,bool useDottedLine)379 void MessageWidget::paintArrow(QPainter *p, int x, int y, int w,
380                               Qt::ArrowType direction, bool useDottedLine /* = false */)
381 {
382     if (w > 3) {
383         int arrowheadStartX = x;
384         int arrowheadExtentX = 4;
385         if (direction == Qt::RightArrow) {
386             arrowheadStartX += w;
387             arrowheadExtentX = -arrowheadExtentX;
388         }
389         // draw upper half of arrowhead
390         p->drawLine(arrowheadStartX, y, arrowheadStartX + arrowheadExtentX, y - 3);
391         // draw lower half of arrowhead
392         p->drawLine(arrowheadStartX, y, arrowheadStartX + arrowheadExtentX, y + 3);
393     }
394     // draw arrow line
395     if (useDottedLine) {
396         QPen pen = p->pen();
397         pen.setStyle(Qt::DotLine);
398         p->setPen(pen);
399     }
400     p->drawLine(x, y, x + w, y);
401 }
402 
403 /**
404  * Draws the calling arrow with filled in arrowhead, the
405  * timeline box and the returning arrow with a dashed line and
406  * stick arrowhead.
407  */
paintSynchronous(QPainter * painter,const QStyleOptionGraphicsItem * option)408 void MessageWidget::paintSynchronous(QPainter *painter, const QStyleOptionGraphicsItem *option)
409 {
410     int x1 = m_pOw[Uml::RoleType::A]->x();
411     int x2 = m_pOw[Uml::RoleType::B]->x();
412     int w = width() - 1;
413     int h = height();
414     int offsetX = 0;
415     int offsetY = 0;
416 
417     bool messageOverlaps = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this);
418     const int boxWidth = 17;
419     const int wr = w < boxWidth ? w : boxWidth;
420     const int arrowWidth = 4;
421     if (UMLWidget::useFillColor())
422         painter->setBrush(UMLWidget::fillColor());
423     else
424         painter->setBrush(m_scene->backgroundColor());
425 
426     if(isSelf()) {
427         painter->fillRect(offsetX, offsetY, wr, h,  QBrush(Qt::white));              //box
428         painter->drawRect(offsetX, offsetY, wr, h);                                    //box
429         offsetX += wr;
430         w -= wr;
431         offsetY += 3;
432         const int lowerLineY = offsetY + h - 6;
433         // draw upper line segment (leaving the life line)
434         painter->drawLine(offsetX, offsetY, offsetX + w, offsetY);
435         // draw line segment parallel to (and at the right of) the life line
436         painter->drawLine(offsetX + w, offsetY, offsetX + w, lowerLineY);
437         // draw lower line segment (back to the life line)
438         paintArrow(painter, offsetX, lowerLineY, w, Qt::LeftArrow);
439         offsetX -= wr;
440         offsetY -= 3;
441     } else if(x1 < x2) {
442         if (messageOverlaps)  {
443             offsetX += 8;
444             w -= 8;
445         }
446         QPen pen = painter->pen();
447         int startX = offsetX + w - wr + 1;
448         painter->fillRect(startX, offsetY, wr, h,  QBrush(Qt::white));         //box
449         painter->drawRect(startX, offsetY, wr, h);                             //box
450         painter->drawLine(offsetX, offsetY + arrowWidth, startX, offsetY + arrowWidth);          //arrow line
451         if (w > boxWidth + arrowWidth)
452             paintSolidArrowhead(painter, startX - 1, offsetY + arrowWidth, Qt::RightArrow);
453         paintArrow(painter, offsetX, offsetY + h - arrowWidth + 1, w - wr + 1, Qt::LeftArrow, true); // return arrow
454         if (messageOverlaps)  {
455             offsetX -= 8; //reset for drawSelected()
456         }
457     } else      {
458         if (messageOverlaps)  {
459             w -=8;
460         }
461         QPen pen = painter->pen();
462         painter->fillRect(offsetX, offsetY, wr, h,  QBrush(Qt::white));              //box
463         painter->drawRect(offsetX, offsetY, wr, h);                                    //box
464         painter->drawLine(offsetX + wr + 1, offsetY + arrowWidth, offsetX + w, offsetY + arrowWidth);    //arrow line
465         if (w > boxWidth + arrowWidth)
466             paintSolidArrowhead(painter, offsetX + wr, offsetY + arrowWidth, Qt::LeftArrow);
467         paintArrow(painter, offsetX + wr + 1, offsetY + h - arrowWidth + 1, w - wr - 1, Qt::RightArrow, true); // return arrow
468     }
469 
470     UMLWidget::paint(painter, option);
471 }
472 
473 /**
474  * Draws a solid arrow line and a stick arrow head.
475  */
paintAsynchronous(QPainter * painter,const QStyleOptionGraphicsItem * option)476 void MessageWidget::paintAsynchronous(QPainter *painter, const QStyleOptionGraphicsItem *option)
477 {
478     int x1 = m_pOw[Uml::RoleType::A]->x();
479     int x2 = m_pOw[Uml::RoleType::B]->x();
480     int w = width() - 1;
481     int h = height() - 1;
482     int offsetX = 0;
483     int offsetY = 0;
484     bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this);
485     //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this);
486 
487     if(isSelf()) {
488         if (messageOverlapsA)  {
489             offsetX += 7;
490             w -= 7;
491         }
492         const int lowerLineY = offsetY + h - 3;
493         // draw upper line segment (leaving the life line)
494         painter->drawLine(offsetX, offsetY, offsetX + w, offsetY);
495         // draw line segment parallel to (and at the right of) the life line
496         painter->drawLine(offsetX + w, offsetY, offsetX + w, lowerLineY);
497         // draw lower line segment (back to the life line)
498         paintArrow(painter, offsetX, lowerLineY, w, Qt::LeftArrow);
499         if (messageOverlapsA)  {
500             offsetX -= 7; //reset for drawSelected()
501         }
502     } else if(x1 < x2) {
503         if (messageOverlapsA) {
504             offsetX += 7;
505             w -= 7;
506         }
507         paintArrow(painter, offsetX, offsetY + 4, w, Qt::RightArrow);
508         if (messageOverlapsA) {
509             offsetX -= 7;
510         }
511     } else      {
512         if (messageOverlapsA) {
513             w -= 7;
514         }
515         paintArrow(painter, offsetX, offsetY + 4, w, Qt::LeftArrow);
516     }
517 
518     UMLWidget::paint(painter, option);
519 }
520 
521 /**
522  * Draws a solid arrow line and a stick arrow head to the
523  * edge of the target object widget instead of to the
524  * sequence line.
525  */
paintCreation(QPainter * painter,const QStyleOptionGraphicsItem * option)526 void MessageWidget::paintCreation(QPainter *painter, const QStyleOptionGraphicsItem *option)
527 {
528     int x1 = m_pOw[Uml::RoleType::A]->x();
529     int x2 = m_pOw[Uml::RoleType::B]->x();
530     int w = width();
531     //int h = height() - 1;
532     int offsetX = 0;
533     int offsetY = 0;
534     bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this);
535     //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this);
536 
537     const int lineY = offsetY + 4;
538     if (x1 < x2) {
539         if (messageOverlapsA) {
540             offsetX += 7;
541             w -= 7;
542         }
543         paintArrow(painter, offsetX, lineY, w, Qt::RightArrow, true);
544         if (messageOverlapsA) {
545             offsetX -= 7;
546         }
547     } else      {
548         if (messageOverlapsA) {
549             w -= 7;
550         }
551         paintArrow(painter, offsetX, lineY, w, Qt::LeftArrow, true);
552     }
553 
554     UMLWidget::paint(painter, option);
555 }
556 
paintDestroy(QPainter * painter,const QStyleOptionGraphicsItem * option)557 void MessageWidget::paintDestroy(QPainter *painter, const QStyleOptionGraphicsItem *option)
558 {
559     paintSynchronous(painter, option);
560 }
561 
562 /**
563  * Draws a solid arrow line and a stick arrow head
564  * and a circle
565  */
paintLost(QPainter * painter,const QStyleOptionGraphicsItem * option)566 void MessageWidget::paintLost(QPainter *painter, const QStyleOptionGraphicsItem *option)
567 {
568     int x1 = m_pOw[Uml::RoleType::A]->centerX();
569     int x2 = m_xclicked;
570     int w = width();
571     int h = height();
572     int offsetX = 0;
573     int offsetY = 0;
574     bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this);
575     //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this);
576 
577     if(x1 < x2) {
578         if (messageOverlapsA)  {
579             offsetX += 7;
580             w -= 7;
581         }
582 
583         setPenFromSettings(painter);
584         painter->setBrush(WidgetBase::lineColor());
585         painter->drawEllipse(offsetX + w - h, offsetY, h, h);
586         paintArrow(painter, offsetX, offsetY + h/2, w - h, Qt::RightArrow);
587 
588         if (messageOverlapsA)  {
589             offsetX -= 7;
590         }
591     } else      {
592         setPenFromSettings(painter);
593         painter->setBrush(WidgetBase::lineColor());
594         painter->drawEllipse(offsetX, offsetY, h, h);
595         paintArrow(painter, offsetX + h, offsetY + h/2, w - h, Qt::LeftArrow);
596     }
597 
598     UMLWidget::paint(painter, option);
599 }
600 
601 /**
602  * Draws a circle and a solid arrow line and a stick arrow head.
603  */
paintFound(QPainter * painter,const QStyleOptionGraphicsItem * option)604 void MessageWidget::paintFound(QPainter *painter, const QStyleOptionGraphicsItem *option)
605 {
606     int x1 = m_pOw[Uml::RoleType::A]->centerX();
607     int x2 = m_xclicked;
608     int w = width();
609     int h = height();
610     int offsetX = 0;
611     int offsetY = 0;
612     bool messageOverlapsA = m_pOw[Uml::RoleType::A]->messageOverlap(y(), this);
613     //bool messageOverlapsB = m_pOw[Uml::RoleType::B]->messageOverlap(y(), this);
614 
615     if(x1 < x2) {
616         if (messageOverlapsA)  {
617             offsetX += 7;
618             w -= 7;
619         }
620         setPenFromSettings(painter);
621         painter->setBrush(WidgetBase::lineColor());
622         painter->drawEllipse(offsetX + w - h, offsetY, h, h);
623         paintArrow(painter, offsetX, offsetY + h/2, w, Qt::LeftArrow);
624         if (messageOverlapsA)  {
625             offsetX -= 7;
626         }
627     } else {
628         if (messageOverlapsA)  {
629             w -= 7;
630         }
631         setPenFromSettings(painter);
632         painter->setBrush(WidgetBase::lineColor());
633         painter->drawEllipse(offsetX, offsetY, h, h);
634         paintArrow(painter, offsetX, offsetY + h/2, w, Qt::RightArrow);
635     }
636 
637     UMLWidget::paint(painter, option);
638 }
639 
640 /**
641  * Overrides operation from UMLWidget.
642  *
643  * @param p Point to be checked.
644  *
645  * @return 'this' if the point is on a part of the MessageWidget.
646  *         NB In case of a synchronous message, the empty space
647  *         between call line and return line does not count, i.e. if
648  *         the point is located in that space the function returns NULL.
649  */
onWidget(const QPointF & p)650 UMLWidget* MessageWidget::onWidget(const QPointF& p)
651 {
652     if (m_sequenceMessageType != Uml::SequenceMessage::Synchronous) {
653         return UMLWidget::onWidget(p);
654     }
655     // Synchronous message:
656     // Consists of top arrow (call) and bottom arrow (return.)
657     if (p.x() < x() || p.x() > x() + width())
658         return 0;
659     const int tolerance = 5;  // pixels
660     const int pY = p.y();
661     const int topArrowY = y() + 3;
662     const int bottomArrowY = y() + height() - 3;
663     if (pY < topArrowY - tolerance || pY > bottomArrowY + tolerance)
664         return 0;
665     if (height() <= 2 * tolerance)
666         return this;
667     if (pY > topArrowY + tolerance && pY < bottomArrowY - tolerance)
668         return 0;
669     return this;
670 }
671 
672 /**
673  * Sets the text position relative to the sequence message.
674  */
setTextPosition()675 void MessageWidget::setTextPosition()
676 {
677     if (m_pFText == 0) {
678         DEBUG(DBG_SRC) << "m_pFText is NULL";
679         return;
680     }
681     if (m_pFText->displayText().isEmpty()) {
682         return;
683     }
684     m_pFText->updateGeometry();
685     int ftX = constrainX(m_pFText->x(), m_pFText->width(), m_pFText->textRole());
686     int ftY = y() - m_pFText->height();
687     m_pFText->setX(ftX);
688     m_pFText->setY(ftY);
689 }
690 
691 /**
692  * Returns the textX arg with constraints applied.
693  * Auxiliary to setTextPosition() and constrainTextPos().
694  */
constrainX(int textX,int textWidth,Uml::TextRole::Enum tr)695 int MessageWidget::constrainX(int textX, int textWidth, Uml::TextRole::Enum tr)
696 {
697     int result = textX;
698     const int minTextX = x() + 5;
699     if (textX < minTextX || tr == Uml::TextRole::Seq_Message_Self) {
700         result = minTextX;
701     } else {
702         ObjectWidget *objectAtRight = 0;
703         if (m_pOw[Uml::RoleType::B]->x() > m_pOw[Uml::RoleType::A]->x())
704             objectAtRight = m_pOw[Uml::RoleType::B];
705         else
706             objectAtRight = m_pOw[Uml::RoleType::A];
707         const int objRight_seqLineX = objectAtRight->centerX();
708         const int maxTextX = objRight_seqLineX - textWidth - 5;
709         if (maxTextX <= minTextX)
710             result = minTextX;
711         else if (textX > maxTextX)
712             result = maxTextX;
713     }
714     return result;
715 }
716 
717 /**
718  * Constrains the FloatingTextWidget X and Y values supplied.
719  * Overrides operation from LinkWidget.
720  *
721  * @param textX        candidate X value (may be modified by the constraint)
722  * @param textY        candidate Y value (may be modified by the constraint)
723  * @param textWidth    width of the text
724  * @param textHeight   height of the text
725  * @param tr           Uml::TextRole::Enum of the text
726  */
constrainTextPos(qreal & textX,qreal & textY,qreal textWidth,qreal textHeight,Uml::TextRole::Enum tr)727 void MessageWidget::constrainTextPos(qreal &textX, qreal &textY, qreal textWidth, qreal textHeight,
728                                      Uml::TextRole::Enum tr)
729 {
730     textX = constrainX(textX, textWidth, tr);
731     // Constrain Y.
732     const qreal minTextY = getMinY();
733     const qreal maxTextY = getMaxY() - textHeight - 5;
734     if (textY < minTextY)
735         textY = minTextY;
736     else if (textY > maxTextY)
737         textY = maxTextY;
738 //     setY(textY + textHeight);   // NB: side effect
739 }
740 
741 /**
742  * Shortcut for calling m_pFText->setLink() followed by
743  * this->setTextPosition().
744  */
setLinkAndTextPos()745 void MessageWidget::setLinkAndTextPos()
746 {
747     if (m_pFText) {
748         m_pFText->setLink(this);
749         setTextPosition();
750     }
751 }
752 
resizeEvent(QResizeEvent *)753 void MessageWidget::resizeEvent(QResizeEvent* /*re*/)
754 {
755 }
756 
757 /**
758  * Calculate the geometry of the widget.
759  */
calculateWidget()760 void MessageWidget::calculateWidget()
761 {
762     setMessageText(m_pFText);
763     calculateDimensions();
764     setVisible(true);
765 }
766 
slotWidgetMoved(Uml::ID::Type id)767 void MessageWidget::slotWidgetMoved(Uml::ID::Type id)
768 {
769     const Uml::ID::Type idA = m_pOw[Uml::RoleType::A]->localID();
770     const Uml::ID::Type idB = m_pOw[Uml::RoleType::B]->localID();
771     if (idA != id && idB != id) {
772         DEBUG(DBG_SRC) << "id=" << Uml::ID::toString(id) << ": ignoring for idA=" << Uml::ID::toString(idA)
773             << ", idB=" << Uml::ID::toString(idB);
774         return;
775     }
776     qreal y = this->y();
777     if (y < getMinY())
778         y = getMinY();
779     if (y > getMaxY())
780         y = getMaxY();
781     setPos(x(), y);
782     calculateWidget();
783     if(!m_pFText)
784         return;
785     if (m_scene->selectedCount(true) > 1)
786         return;
787     setTextPosition();
788 }
789 
790 /**
791  * Check to see if the given ObjectWidget is involved in the message.
792  *
793  * @param w The ObjectWidget to check for.
794  * @return  true - if is contained, false - not contained.
795  */
hasObjectWidget(ObjectWidget * w)796 bool MessageWidget::hasObjectWidget(ObjectWidget * w)
797 {
798     if(m_pOw[Uml::RoleType::A] == w || m_pOw[Uml::RoleType::B] == w)
799         return true;
800     else
801         return false;
802 }
803 
804 /**
805  * This method determines whether the message is for "Self" for
806  * an ObjectWidget.
807  *
808  * @retval True If both ObjectWidgets for this widget exists and
809  *              are same.
810  */
isSelf() const811 bool MessageWidget::isSelf() const
812 {
813     return (m_pOw[Uml::RoleType::A] && m_pOw[Uml::RoleType::B] &&
814             m_pOw[Uml::RoleType::A] == m_pOw[Uml::RoleType::B]);
815 }
816 
slotMenuSelection(QAction * action)817 void MessageWidget::slotMenuSelection(QAction* action)
818 {
819     ListPopupMenu::MenuType sel = ListPopupMenu::typeFromAction(action);
820     if (sel == ListPopupMenu::mt_Delete) {
821         if (Dialog_Utils::askDeleteAssociation()) {
822             // This will clean up this widget and the text widget:
823             m_scene->removeWidget(this);
824         }
825     } else {
826 
827         UMLWidget::slotMenuSelection(action);
828     }
829 }
830 
831 /**
832  * Activates a MessageWidget.  Connects its m_pOw[] pointers
833  * to UMLObjects and also send signals about its FloatingTextWidget.
834  */
activate(IDChangeLog *)835 bool MessageWidget::activate(IDChangeLog * /*Log = 0*/)
836 {
837     m_scene->resetPastePoint();
838     // UMLWidget::activate(Log);   CHECK: I don't think we need this ?
839     if (m_pOw[Uml::RoleType::A] == 0) {
840         UMLWidget *pWA = m_scene->findWidget(m_widgetAId);
841         if (pWA == 0) {
842             DEBUG(DBG_SRC) << "role A object " << Uml::ID::toString(m_widgetAId) << " not found";
843             return false;
844         }
845         m_pOw[Uml::RoleType::A] = pWA->asObjectWidget();
846         if (m_pOw[Uml::RoleType::A] == 0) {
847             DEBUG(DBG_SRC) << "role A widget " << Uml::ID::toString(m_widgetAId)
848                 << " is not an ObjectWidget";
849             return false;
850         }
851     }
852     if (m_pOw[Uml::RoleType::B] == 0) {
853         UMLWidget *pWB = m_scene->findWidget(m_widgetBId);
854         if (pWB == 0) {
855             DEBUG(DBG_SRC) << "role B object " << Uml::ID::toString(m_widgetBId) << " not found";
856             return false;
857         }
858         m_pOw[Uml::RoleType::B] = pWB->asObjectWidget();
859         if (m_pOw[Uml::RoleType::B] == 0) {
860             DEBUG(DBG_SRC) << "role B widget " << Uml::ID::toString(m_widgetBId)
861                 << " is not an ObjectWidget";
862             return false;
863         }
864     }
865     updateResizability();
866 
867     UMLClassifier *c = m_pOw[Uml::RoleType::B]->umlObject()->asUMLClassifier();
868     UMLOperation *op = 0;
869     if (c && !m_CustomOp.isEmpty()) {
870         Uml::ID::Type opId = Uml::ID::fromString(m_CustomOp);
871         op = c->findChildObjectById(opId, true)->asUMLOperation();
872         if (op) {
873             // If the UMLOperation is set, m_CustomOp isn't used anyway.
874             // Just setting it empty for the sake of sanity.
875             m_CustomOp.clear();
876         }
877     }
878 
879     if(!m_pFText) {
880         Uml::TextRole::Enum tr = Uml::TextRole::Seq_Message;
881         if (isSelf())
882             tr = Uml::TextRole::Seq_Message_Self;
883         m_pFText = new FloatingTextWidget(m_scene, tr, operationText(m_scene));
884         m_scene->addFloatingTextWidget(m_pFText);
885         m_pFText->setFontCmd(UMLWidget::font());
886     }
887     if (op)
888         setOperation(op);  // This requires a valid m_pFText.
889     setLinkAndTextPos();
890     m_pFText->setText(QString());
891     m_pFText->setActivated();
892     QString messageText = m_pFText->text();
893     m_pFText->setVisible(messageText.length() > 1);
894 
895     connect(m_pOw[Uml::RoleType::A], SIGNAL(sigWidgetMoved(Uml::ID::Type)), this, SLOT(slotWidgetMoved(Uml::ID::Type)));
896     connect(m_pOw[Uml::RoleType::B], SIGNAL(sigWidgetMoved(Uml::ID::Type)), this, SLOT(slotWidgetMoved(Uml::ID::Type)));
897 
898     connect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::A], SLOT(slotMessageMoved()));
899     connect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::B], SLOT(slotMessageMoved()));
900     m_pOw[Uml::RoleType::A]->messageAdded(this);
901     if (!isSelf())
902         m_pOw[Uml::RoleType::B]->messageAdded(this);
903 
904     // Calculate the size and position of the message widget
905     calculateDimensions();
906 
907     // Position the floating text accordingly
908     setTextPosition();
909 
910     emit sigMessageMoved();
911     return true;
912 }
913 
914 /**
915  * Resolve references of this message so they reference the correct
916  * new object widgets after paste.
917  */
resolveObjectWidget(IDChangeLog * log)918 void MessageWidget::resolveObjectWidget(IDChangeLog* log) {
919     m_widgetAId = log->findNewID(m_widgetAId);
920     m_widgetBId = log->findNewID(m_widgetBId);
921 }
922 
923 /**
924  * Overrides operation from LinkWidget.
925  * Required by FloatingTextWidget.
926  *
927  * @param ft   The text widget which to update.
928  */
setMessageText(FloatingTextWidget * ft)929 void MessageWidget::setMessageText(FloatingTextWidget *ft)
930 {
931     if (ft == 0)
932         return;
933     ft->setSequenceNumber(m_SequenceNumber);
934     ft->setText(operationText(m_scene));
935     setTextPosition();
936 }
937 
938 /**
939  * Overrides operation from LinkWidget.
940  * Required by FloatingTextWidget.
941  *
942  * @param ft        The text widget which to update.
943  * @param newText   The new text to set.
944  */
setText(FloatingTextWidget * ft,const QString & newText)945 void MessageWidget::setText(FloatingTextWidget *ft, const QString &newText)
946 {
947     ft->setText(newText);
948     UMLApp::app()->document()->setModified(true);
949 }
950 
951 /**
952  * Overrides operation from LinkWidget.
953  * Required by FloatingTextWidget.
954  *
955  * @param op        The new operation string to set.
956  */
setOperationText(const QString & op)957 void MessageWidget::setOperationText(const QString &op)
958 {
959     m_CustomOp = op;   ///FIXME m_pOperation
960 }
961 
962 /**
963  * Implements operation from LinkWidget.
964  * Required by FloatingTextWidget.
965  */
lwSetFont(QFont font)966 void MessageWidget::lwSetFont (QFont font)
967 {
968     UMLWidget::setFont(font);
969 }
970 
971 /**
972  * Overrides operation from LinkWidget.
973  * Required by FloatingTextWidget.
974  * @todo Move to LinkWidget.
975  */
operationOwner()976 UMLClassifier *MessageWidget::operationOwner()
977 {
978     UMLObject *pObject = m_pOw[Uml::RoleType::B]->umlObject();
979     if (pObject == 0)
980         return 0;
981     UMLClassifier *c = pObject->asUMLClassifier();
982     return c;
983 }
984 
985 /**
986  * Implements operation from LinkWidget.
987  * Motivated by FloatingTextWidget.
988  */
operation()989 UMLOperation *MessageWidget::operation()
990 {
991     return m_umlObject->asUMLOperation();
992 }
993 
994 /**
995  * Implements operation from LinkWidget.
996  * Motivated by FloatingTextWidget.
997  */
setOperation(UMLOperation * op)998 void MessageWidget::setOperation(UMLOperation *op)
999 {
1000     if (m_umlObject && m_pFText)
1001         disconnect(m_umlObject, SIGNAL(modified()), m_pFText, SLOT(setMessageText()));
1002     m_umlObject = op;
1003     if (m_umlObject && m_pFText) {
1004         connect(m_umlObject, SIGNAL(modified()), m_pFText, SLOT(setMessageText()));
1005         m_pFText->setMessageText();
1006     }
1007 }
1008 
1009 /**
1010  * Overrides operation from LinkWidget.
1011  * Required by FloatingTextWidget.
1012  */
customOpText()1013 QString MessageWidget::customOpText()
1014 {
1015     return m_CustomOp;
1016 }
1017 
1018 /**
1019  * Overrides operation from LinkWidget.
1020  * Required by FloatingTextWidget.
1021  */
setCustomOpText(const QString & opText)1022 void MessageWidget::setCustomOpText(const QString &opText)
1023 {
1024     m_CustomOp = opText;
1025     m_pFText->setMessageText();
1026 }
1027 
1028 /**
1029  * Overrides operation from LinkWidget.
1030  * Required by FloatingTextWidget.
1031  */
lwOperationText()1032 QString MessageWidget::lwOperationText()
1033 {
1034     UMLOperation *pOperation = operation();
1035     if (pOperation != 0) {
1036         return pOperation->toString(Uml::SignatureType::SigNoVis);
1037     } else {
1038         return customOpText();
1039     }
1040 }
1041 
1042 /**
1043  * Overrides operation from LinkWidget.
1044  * Required by FloatingTextWidget.
1045  */
lwClassifier()1046 UMLClassifier *MessageWidget::lwClassifier()
1047 {
1048     UMLObject *o = m_pOw[Uml::RoleType::B]->umlObject();
1049     UMLClassifier *c = o->asUMLClassifier();
1050     return c;
1051 }
1052 
1053 /**
1054  * Calculates the size of the widget by calling
1055  * calculateDimensionsSynchronous(),
1056  * calculateDimensionsAsynchronous(), or
1057  * calculateDimensionsCreation()
1058  */
calculateDimensions()1059 void MessageWidget::calculateDimensions()
1060 {
1061     if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) {
1062         calculateDimensionsSynchronous();
1063     } else if (m_sequenceMessageType == Uml::SequenceMessage::Asynchronous) {
1064         calculateDimensionsAsynchronous();
1065     } else if (m_sequenceMessageType == Uml::SequenceMessage::Creation) {
1066         calculateDimensionsCreation();
1067     } else if (m_sequenceMessageType == Uml::SequenceMessage::Destroy) {
1068         calculateDimensionsDestroy();
1069     } else if (m_sequenceMessageType == Uml::SequenceMessage::Lost) {
1070         calculateDimensionsLost();
1071     } else if (m_sequenceMessageType == Uml::SequenceMessage::Found) {
1072         calculateDimensionsFound();
1073     } else {
1074         uWarning() << "Unknown message type";
1075     }
1076     if (! UMLApp::app()->document()->loading()) {
1077         adjustAssocs(x(), y());  // adjust assoc lines
1078     }
1079 }
1080 
1081 /**
1082  * Calculates and sets the size of the widget for a synchronous message.
1083  */
calculateDimensionsSynchronous()1084 void MessageWidget::calculateDimensionsSynchronous()
1085 {
1086     int x = 0;
1087 
1088     int x1 = m_pOw[Uml::RoleType::A]->centerX();
1089     int x2 = m_pOw[Uml::RoleType::B]->centerX();
1090 
1091     int widgetWidth = 0;
1092     if(isSelf()) {
1093         widgetWidth = 50;
1094         x = x1 - 2;
1095     } else if(x1 < x2) {
1096         x = x1;
1097         widgetWidth = x2 - x1 + 8;
1098     } else {
1099         x = x2 - 8;
1100         widgetWidth = x1 - x2 + 8;
1101     }
1102 
1103     QSizeF minSize = minimumSize();
1104     int widgetHeight = 0;
1105     if (height() < minSize.height()) {
1106         widgetHeight = minSize.height();
1107     } else {
1108         widgetHeight = height();
1109     }
1110 
1111     setX(x);
1112     setSize(widgetWidth, widgetHeight);
1113 }
1114 
1115 /**
1116  * Calculates and sets the size of the widget for an asynchronous message.
1117  */
calculateDimensionsAsynchronous()1118 void MessageWidget::calculateDimensionsAsynchronous()
1119 {
1120     int x = 0;
1121 
1122     int x1 = m_pOw[Uml::RoleType::A]->centerX();
1123     int x2 = m_pOw[Uml::RoleType::B]->centerX();
1124 
1125     int widgetWidth = 0;
1126     if(isSelf()) {
1127         widgetWidth = 50;
1128         x = x1;
1129     } else if(x1 < x2) {
1130         x = x1;
1131         widgetWidth = x2 - x1;
1132     } else {
1133         x = x2;
1134         widgetWidth = x1 - x2;
1135     }
1136     x += 1;
1137     widgetWidth -= 2;
1138 
1139     QSizeF minSize = minimumSize();
1140     int widgetHeight = 0;
1141     if (height() < minSize.height()) {
1142         widgetHeight = minSize.height();
1143     } else {
1144         widgetHeight = height();
1145     }
1146 
1147     setX(x);
1148     setSize(widgetWidth, widgetHeight);
1149 }
1150 
1151 /**
1152  * Calculates and sets the size of the widget for a creation message.
1153  */
calculateDimensionsCreation()1154 void MessageWidget::calculateDimensionsCreation()
1155 {
1156     int x = 0;
1157 
1158     int x1 = m_pOw[Uml::RoleType::A]->centerX();
1159     int x2 = m_pOw[Uml::RoleType::B]->x();
1160     int w2 = m_pOw[Uml::RoleType::B]->width();
1161 
1162     if (x1 > x2)
1163         x2 += w2;
1164 
1165     int widgetWidth = 0;
1166     if (x1 < x2) {
1167         x = x1;
1168         widgetWidth = x2 - x1;
1169     } else {
1170         x = x2;
1171         widgetWidth = x1 - x2;
1172     }
1173     x += 1;
1174     widgetWidth -= 2;
1175 
1176     int widgetHeight = minimumSize().height();
1177 
1178     setPos(x, m_pOw[Uml::RoleType::B]->y() + m_pOw[Uml::RoleType::B]->height() / 2);
1179     setSize(widgetWidth, widgetHeight);
1180 }
1181 
1182 /**
1183  * Calculates and sets the size of the widget for a destroy message.
1184  */
calculateDimensionsDestroy()1185 void MessageWidget::calculateDimensionsDestroy()
1186 {
1187     calculateDimensionsSynchronous();
1188 }
1189 
1190 /**
1191  * Calculates and sets the size of the widget for a lost message.
1192  */
calculateDimensionsLost()1193 void MessageWidget::calculateDimensionsLost()
1194 {
1195     int x = 0;
1196 
1197     int x1 = m_pOw[Uml::RoleType::A]->centerX();
1198     int x2 = m_xclicked;
1199 
1200     int widgetWidth = 0;
1201     if(x1 < x2) {
1202         x = x1;
1203         widgetWidth = x2 - x1 + circleWidth/2;
1204     } else {
1205         x = x2 - circleWidth/2;
1206         widgetWidth = x1 - x2 + circleWidth/2;
1207     }
1208 
1209     int widgetHeight = minimumSize().height();
1210 
1211     setX(x);
1212     setSize(widgetWidth, widgetHeight);
1213 }
1214 
1215 /**
1216  * Calculates and sets the size of the widget for a found message.
1217  */
calculateDimensionsFound()1218 void MessageWidget::calculateDimensionsFound()
1219 {
1220     int x = 0;
1221 
1222     int x1 = m_pOw[Uml::RoleType::A]->centerX();
1223     int x2 = m_xclicked;
1224 
1225     int widgetWidth = 0;
1226     if(x1 < x2) {
1227         x = x1;
1228         widgetWidth = x2 - x1 + circleWidth/2;
1229     } else {
1230         x = x2 - circleWidth/2;
1231         widgetWidth = x1 - x2 + circleWidth/2;
1232     }
1233 
1234     int widgetHeight = minimumSize().height();
1235 
1236     setX(x);
1237     setSize(widgetWidth, widgetHeight);
1238 }
1239 
1240 /**
1241  * Used to cleanup any other widget it may need to delete.
1242  */
cleanup()1243 void MessageWidget::cleanup()
1244 {
1245     if (m_pOw[Uml::RoleType::A]) {
1246         disconnect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::A], SLOT(slotMessageMoved()));
1247         m_pOw[Uml::RoleType::A]->messageRemoved(this);
1248     }
1249     if (m_pOw[Uml::RoleType::B]) {
1250         disconnect(this, SIGNAL(sigMessageMoved()), m_pOw[Uml::RoleType::B], SLOT(slotMessageMoved()));
1251         m_pOw[Uml::RoleType::B]->messageRemoved(this);
1252     }
1253 
1254     UMLWidget::cleanup();
1255     if (m_pFText) {
1256         m_scene->removeWidgetCmd(m_pFText);
1257         m_pFText = 0;
1258     }
1259 }
1260 
1261 /**
1262  * Sets the state of whether the widget is selected.
1263  *
1264  * @param _select   True if the widget is selected.
1265  */
setSelected(bool _select)1266 void MessageWidget::setSelected(bool _select)
1267 {
1268     UMLWidget::setSelected(_select);
1269     if(!m_pFText || m_pFText->displayText().isEmpty())
1270         return;
1271     if(isSelected() && m_pFText->isSelected())
1272         return;
1273     if(!isSelected() && !m_pFText->isSelected())
1274         return;
1275 
1276     m_pFText->setSelected(isSelected());
1277 }
1278 
1279 /**
1280  * Returns the minimum height this widget should be set at on
1281  * a sequence diagrams.  Takes into account the widget positions
1282  * it is related to.
1283  */
getMinY()1284 int MessageWidget::getMinY()
1285 {
1286     if (!m_pOw[Uml::RoleType::A] || !m_pOw[Uml::RoleType::B]) {
1287         return 0;
1288     }
1289     if (m_sequenceMessageType == Uml::SequenceMessage::Creation) {
1290         return m_pOw[Uml::RoleType::A]->y() + m_pOw[Uml::RoleType::A]->height();
1291     }
1292     int heightA = m_pOw[Uml::RoleType::A]->y() + m_pOw[Uml::RoleType::A]->height();
1293     int heightB = m_pOw[Uml::RoleType::B]->y() + m_pOw[Uml::RoleType::B]->height();
1294     int height = heightA;
1295     if(heightA < heightB) {
1296         height = heightB;
1297     }
1298     return height;
1299 }
1300 
1301 /**
1302  * Returns the maximum height this widget should be set at on
1303  * a sequence diagrams.  Takes into account the widget positions
1304  * it is related to.
1305  */
getMaxY()1306 int MessageWidget::getMaxY()
1307 {
1308     if(!m_pOw[Uml::RoleType::A] || !m_pOw[Uml::RoleType::B]) {
1309         return 0;
1310     }
1311     int heightA = (int)((ObjectWidget*)m_pOw[Uml::RoleType::A])->getEndLineY();
1312     int heightB = (int)((ObjectWidget*)m_pOw[Uml::RoleType::B])->getEndLineY();
1313     int height = heightA;
1314     if(heightA > heightB) {
1315         height = heightB;
1316     }
1317     return (height - this->height());
1318 }
1319 /**
1320  * Overrides method from UMLWidget.
1321  */
minimumSize() const1322 QSizeF MessageWidget::minimumSize() const
1323 {
1324     if (m_sequenceMessageType == Uml::SequenceMessage::Synchronous) {
1325         return QSizeF(width(), 20);
1326     } else if (m_sequenceMessageType == Uml::SequenceMessage::Asynchronous) {
1327         return isSelf() ? QSizeF(width(), 20) : QSizeF(width(), 8);
1328     } else if (m_sequenceMessageType == Uml::SequenceMessage::Creation) {
1329         return QSizeF(width(), 8);
1330     } else if (m_sequenceMessageType == Uml::SequenceMessage::Destroy) {
1331         return QSizeF(width(), 8);
1332     } else if (m_sequenceMessageType == Uml::SequenceMessage::Lost) {
1333         return QSizeF(width(), 10);
1334     } else if (m_sequenceMessageType == Uml::SequenceMessage::Found) {
1335         return QSizeF(width(), 10);
1336     } else {
1337         uWarning() << "Unknown message type";
1338     }
1339     return QSize(width(), height());
1340 }
1341 
1342 /**
1343  * Sets the related widget on the given side.
1344  *
1345  * @param ow     The ObjectWidget we are related to.
1346  * @param role   The Uml::RoleType::Enum to be set for the ObjectWidget
1347  */
setObjectWidget(ObjectWidget * ow,Uml::RoleType::Enum role)1348 void MessageWidget::setObjectWidget(ObjectWidget * ow, Uml::RoleType::Enum role)
1349 {
1350     m_pOw[role] = ow;
1351     updateResizability();
1352 }
1353 
1354 /**
1355  * Returns the related widget on the given side.
1356  *
1357  * @return  The ObjectWidget we are related to.
1358  */
objectWidget(Uml::RoleType::Enum role)1359 ObjectWidget* MessageWidget::objectWidget(Uml::RoleType::Enum role)
1360 {
1361     return m_pOw[role];
1362 }
1363 
1364 /**
1365  * Set the xclicked
1366  */
setxclicked(int xclick)1367 void MessageWidget::setxclicked(int xclick)
1368 {
1369     m_xclicked = xclick;
1370 }
1371 
1372 /**
1373  * Set the yclicked
1374  */
setyclicked(int yclick)1375 void MessageWidget::setyclicked(int yclick)
1376 {
1377     m_yclicked = yclick;
1378 }
1379 
1380 /**
1381  * Show a properties dialog for an ObjectWidget.
1382  */
showPropertiesDialog()1383 bool MessageWidget::showPropertiesDialog()
1384 {
1385     if (!lwClassifier()) {
1386         uError() << "lwClassifier() returns a NULL classifier";
1387         return false;
1388     }
1389     bool result = false;
1390     UMLApp::app()->docWindow()->updateDocumentation(false);
1391     QPointer<MessageWidgetPropertiesDialog> dlg = new MessageWidgetPropertiesDialog(0, this);
1392     if (dlg->exec()) {
1393         m_pFText->setMessageText();
1394         UMLApp::app()->docWindow()->showDocumentation(this, true);
1395         UMLApp::app()->document()->setModified(true);
1396         result = true;
1397     }
1398     delete dlg;
1399     return result;
1400 }
1401 
1402 /**
1403  * Saves to the "messagewidget" XMI element.
1404  */
saveToXMI1(QXmlStreamWriter & writer)1405 void MessageWidget::saveToXMI1(QXmlStreamWriter& writer)
1406 {
1407     writer.writeStartElement(QLatin1String("messagewidget"));
1408     UMLWidget::saveToXMI1(writer);
1409     LinkWidget::saveToXMI1(writer);
1410     if (m_pOw[Uml::RoleType::A])
1411         writer.writeAttribute(QLatin1String("widgetaid"), Uml::ID::toString(m_pOw[Uml::RoleType::A]->localID()));
1412     if (m_pOw[Uml::RoleType::B])
1413         writer.writeAttribute(QLatin1String("widgetbid"), Uml::ID::toString(m_pOw[Uml::RoleType::B]->localID()));
1414     UMLOperation *pOperation = operation();
1415     if (pOperation)
1416         writer.writeAttribute(QLatin1String("operation"), Uml::ID::toString(pOperation->id()));
1417     else
1418         writer.writeAttribute(QLatin1String("operation"), m_CustomOp);
1419     writer.writeAttribute(QLatin1String("sequencemessagetype"), QString::number(m_sequenceMessageType));
1420     if (m_sequenceMessageType == Uml::SequenceMessage::Lost || m_sequenceMessageType == Uml::SequenceMessage::Found) {
1421         writer.writeAttribute(QLatin1String("xclicked"), QString::number(m_xclicked));
1422         writer.writeAttribute(QLatin1String("yclicked"), QString::number(m_yclicked));
1423     }
1424 
1425     // save the corresponding message text
1426     if (m_pFText && !m_pFText->text().isEmpty()) {
1427         writer.writeAttribute(QLatin1String("textid"), Uml::ID::toString(m_pFText->id()));
1428         m_pFText->saveToXMI1(writer);
1429     }
1430 
1431     writer.writeEndElement();
1432 }
1433 
1434 /**
1435  * Loads from the "messagewidget" XMI element.
1436  */
loadFromXMI1(QDomElement & qElement)1437 bool MessageWidget::loadFromXMI1(QDomElement& qElement)
1438 {
1439     if (!UMLWidget::loadFromXMI1(qElement)) {
1440         return false;
1441     }
1442     if (!LinkWidget::loadFromXMI1(qElement)) {
1443         return false;
1444     }
1445     QString textid = qElement.attribute(QLatin1String("textid"), QLatin1String("-1"));
1446     QString widgetaid = qElement.attribute(QLatin1String("widgetaid"), QLatin1String("-1"));
1447     QString widgetbid = qElement.attribute(QLatin1String("widgetbid"), QLatin1String("-1"));
1448     m_CustomOp = qElement.attribute(QLatin1String("operation"));
1449     QString sequenceMessageType = qElement.attribute(QLatin1String("sequencemessagetype"), QLatin1String("1001"));
1450     m_sequenceMessageType = Uml::SequenceMessage::fromInt(sequenceMessageType.toInt());
1451     if (m_sequenceMessageType == Uml::SequenceMessage::Lost || m_sequenceMessageType == Uml::SequenceMessage::Found) {
1452         m_xclicked = qElement.attribute(QLatin1String("xclicked"), QLatin1String("-1")).toInt();
1453         m_yclicked = qElement.attribute(QLatin1String("yclicked"), QLatin1String("-1")).toInt();
1454     }
1455 
1456     m_widgetAId = Uml::ID::fromString(widgetaid);
1457     m_widgetBId = Uml::ID::fromString(widgetbid);
1458     m_textId = Uml::ID::fromString(textid);
1459 
1460     Uml::TextRole::Enum tr = Uml::TextRole::Seq_Message;
1461     if (m_widgetAId == m_widgetBId)
1462         tr = Uml::TextRole::Seq_Message_Self;
1463 
1464     //now load child elements
1465     QDomNode node = qElement.firstChild();
1466     QDomElement element = node.toElement();
1467     if (!element.isNull()) {
1468         QString tag = element.tagName();
1469         if (tag == QLatin1String("floatingtext") || tag == QLatin1String("UML::FloatingTextWidget")) {
1470             m_pFText = new FloatingTextWidget(m_scene, tr, operationText(m_scene), m_textId);
1471             m_scene->addFloatingTextWidget(m_pFText);
1472             if(! m_pFText->loadFromXMI1(element)) {
1473                 // Most likely cause: The FloatingTextWidget is empty.
1474                 delete m_pFText;
1475                 m_pFText = 0;
1476             }
1477             else
1478                 m_pFText->setSequenceNumber(m_SequenceNumber);
1479         } else {
1480             uError() << "unknown tag " << tag;
1481         }
1482     }
1483     return true;
1484 }
1485 
1486