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