1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2004-2020 Umbrello UML Modeller Authors <umbrello-devel@kde.org>
4 */
5 
6 // own header
7 #include "widget_utils.h"
8 
9 // app includes
10 #include "debug_utils.h"
11 #include "objectwidget.h"
12 #include "messagewidget.h"
13 #include "umlwidget.h"
14 
15 #include <KLocalizedString>
16 
17 // qt includes
18 #include <QBuffer>
19 #include <QImageReader>
20 #include <QGraphicsItem>
21 #include <QGraphicsRectItem>
22 #include <QPolygonF>
23 #include <QXmlStreamWriter>
24 
25 // c++ include
26 #include <cmath>
27 
28 namespace Widget_Utils
29 {
30 
31     /**
32      * Find the widget identified by the given ID in the given widget
33      * or message list.
34      *
35      * @param id         The unique ID to find.
36      * @param widgets    The UMLWidgetList to search in.
37      * @param messages   Optional pointer to a MessageWidgetList to search in.
38      */
findWidget(Uml::ID::Type id,const UMLWidgetList & widgets,const MessageWidgetList * messages)39     UMLWidget* findWidget(Uml::ID::Type id,
40                           const UMLWidgetList& widgets,
41                           const MessageWidgetList* messages /* = 0 */)
42     {
43         foreach (UMLWidget* obj, widgets) {
44             if (obj->isObjectWidget()) {
45                 if (obj->localID() == id)
46                     return obj;
47             } else if (obj->id() == id) {
48                 return obj;
49             }
50         }
51 
52         if (messages) {
53             foreach (UMLWidget* obj, *messages) {
54                 if (obj->id() == id)
55                     return obj;
56             }
57         }
58         return 0;
59     }
60 
61     /**
62      * Creates the decoration point.
63      * @param p        base point to decorate
64      * @param parent   parent item
65      * @return         decoration point
66      */
decoratePoint(const QPointF & p,QGraphicsItem * parent)67     QGraphicsRectItem* decoratePoint(const QPointF &p, QGraphicsItem* parent)
68     {
69         const qreal SIZE = 4.0;
70         const qreal SIZE_HALF = SIZE / 2.0;
71         QGraphicsRectItem *rect = new QGraphicsRectItem(p.x() - SIZE_HALF,
72                                                         p.y() - SIZE_HALF,
73                                                         SIZE, SIZE,
74                                                         parent);
75         rect->setBrush(QBrush(Qt::blue));
76         rect->setPen(QPen(Qt::blue));
77         return rect;
78     }
79 
80     /**
81      * Calculates and draws a cross inside an ellipse
82      * @param p  Pointer to a QPainter object.
83      * @param r  The rectangle describing the ellipse.
84      */
drawCrossInEllipse(QPainter * p,const QRectF & r)85     void drawCrossInEllipse(QPainter *p, const QRectF& r)
86     {
87         QRectF ellipse = r;
88         ellipse.moveCenter(QPointF(0, 0));
89         qreal a = ellipse.width() * 0.5;
90         qreal b = ellipse.height() * .5;
91         qreal xc = ellipse.center().x();
92         qreal yc = ellipse.center().y();
93 
94         // The first point's x value is chosen to be center.x() + 70% of x radius.
95         qreal x1 = ellipse.center().x() + .7 * .5 * ellipse.width();
96         // Calculate y1 corresponding to x1 using formula.
97         qreal y1_sqr = b*b*(1 - (x1 * x1) / (a*a));
98         qreal y1 = std::sqrt(y1_sqr);
99 
100         // Mirror x1, y1 along both the axes to get 4 points for the cross.
101         QPointF p1(xc + x1, yc + y1);
102         QPointF p2(xc - x1, yc + y1);
103         QPointF p3(xc + x1, yc - y1);
104         QPointF p4(xc - x1, yc - y1);
105 
106         // Translate as we calculate for ellipse with (0, 0) as center.
107         p->translate(r.center().x(), r.center().y());
108 
109         // Draw the cross now
110         p->drawLine(QLineF(p1, p4));
111         p->drawLine(QLineF(p2, p3));
112 
113         // Restore the translate on painter.
114         p->translate(-r.center().x(), -r.center().y());
115     }
116 
117     /**
118      * Draws a polygon which is almost rectangular except for the top
119      * right corner. A triangle is drawn in top right corner of the
120      * rectangle.
121      *
122      * @param painter The painter with which this shape is to be drawn.
123      * @param rect    The rectangle dimensions.
124      * @param triSize The size of the triangle in the top-right corner.
125      */
drawTriangledRect(QPainter * painter,const QRectF & rect,const QSizeF & triSize)126     void drawTriangledRect(QPainter *painter,
127                            const QRectF &rect, const QSizeF &triSize)
128     {
129         // Draw outer boundary defined by polygon "poly".
130         QPolygonF poly(5);
131         poly[0] = rect.topLeft();
132         poly[1] = rect.topRight() - QPointF(triSize.width(), 0);
133         poly[2] = rect.topRight() + QPointF(0, triSize.height());
134         poly[3] = rect.bottomRight();
135         poly[4] = rect.bottomLeft();
136         painter->drawPolygon(poly);
137 
138         // Now draw the triangle base and height edges.
139         QLineF heightEdge(poly[1], poly[1] + QPointF(0, triSize.height()));
140         painter->drawLine(heightEdge);
141         QLineF baseEdge(heightEdge.p2(), poly[2]);
142         painter->drawLine(baseEdge);
143     }
144 
145 //    /**
146 //     * Draws an arrow head with the given painter, with the arrow
147 //     * sharp point at \a headPos.
148 //     *
149 //     *  param painter    The painter with which this arrow should be drawn.
150 //     *  param headPos    The position where the head of the arrow should lie.
151 //     *  param arrowSize  This indicates the size of the arrow head.
152 //     *  param arrowType  This indicates direction of arrow as in LeftArrow, RightArrow..
153 //     *  param solid      If true, a solid head is drawn. Otherwise 2 lines are drawn.
154 //     */
155 //    void drawArrowHead(QPainter *painter, const QPointF &arrowPos,
156 //                       const QSizeF& arrowSize, Qt::ArrowType arrowType,
157 //                       bool  solid)
158 //    {
159 //        QPolygonF poly;
160 //        if (arrowType == Qt::LeftArrow) {
161 //            poly << QPointF(arrowPos.x() + arrowSize.width(), arrowPos.y() - .5 * arrowSize.height())
162 //                 << arrowPos
163 //                 << QPointF(arrowPos.x() + arrowSize.width(), arrowPos.y() + .5 * arrowSize.height());
164 //        }
165 //        else if (arrowType == Qt::RightArrow) {
166 //            poly << QPointF(arrowPos.x() - arrowSize.width(), arrowPos.y() - .5 * arrowSize.height())
167 //                 << arrowPos
168 //                 << QPointF(arrowPos.x() - arrowSize.width(), arrowPos.y() + .5 * arrowSize.height());
169 //        }
170 
171 //        if (solid) {
172 //            painter->drawPolygon(poly);
173 //        }
174 //        else {
175 //            painter->drawPolyline(poly);
176 //        }
177 //    }
178 
179 //    /**
180 //     * Draws a rounded rect rounded at specified corners.
181 //     *
182 //     *  param painter The painter with which this round rect should be drawn.
183 //     *  param rect    The rectangle to be drawn.
184 //     *  param xRadius The x radius of rounded corner.
185 //     *  param yRadius The y radius of rounded corner.
186 //     *  param corners The corners to be rounded.
187 //     */
188 //    void drawRoundedRect(QPainter *painter, const QRectF& rect, qreal xRadius,
189 //            qreal yRadius, Uml::Corners corners)
190 //    {
191 //        if (xRadius < 0 || yRadius < 0) {
192 //            painter->drawRect(rect);
193 //            return;
194 //        }
195 //        QRectF arcRect(0, 0, 2 * xRadius, 2 * yRadius);
196 
197 //        QPainterPath path;
198 //        path.moveTo(rect.left(), rect.top() + yRadius);
199 //        if (corners.testFlag(Uml::Corner::TopLeft)) {
200 //            arcRect.moveTopLeft(rect.topLeft());
201 //            path.arcTo(arcRect, 180, -90);
202 //        } else {
203 //            path.lineTo(rect.topLeft());
204 //        }
205 
206 //        path.lineTo(rect.right() - xRadius, rect.top());
207 
208 //        if (corners.testFlag(Uml::Corner::TopRight)) {
209 //            arcRect.moveTopRight(rect.topRight());
210 //            path.arcTo(arcRect, 90, -90);
211 //        } else {
212 //            path.lineTo(rect.topRight());
213 //        }
214 
215 //        path.lineTo(rect.right(), rect.bottom() - yRadius);
216 
217 //        if (corners.testFlag(Uml::Corner::BottomRight)) {
218 //            arcRect.moveBottomRight(rect.bottomRight());
219 //            path.arcTo(arcRect, 0, -90);
220 //        } else {
221 //            path.lineTo(rect.bottomRight());
222 //        }
223 
224 //        path.lineTo(rect.left() + xRadius, rect.bottom());
225 
226 //        if (corners.testFlag(Uml::Corner::BottomLeft)) {
227 //            arcRect.moveBottomLeft(rect.bottomLeft());
228 //            path.arcTo(arcRect, 270, 90);
229 //        } else {
230 //            path.lineTo(rect.bottomLeft());
231 //        }
232 
233 //        path.closeSubpath();
234 //        painter->drawPath(path);
235 //    }
236 
237     /**
238      * Converts a point to a comma separated string i.e "x,y"
239      * @param point  The QPointF to convert.
240      */
pointToString(const QPointF & point)241     QString pointToString(const QPointF& point)
242     {
243         return QString::fromLatin1("%1,%2").arg(point.x()).arg(point.y());
244     }
245 
246     /**
247      * Converts a comma separated string to point.
248      */
stringToPoint(const QString & str)249     QPointF stringToPoint(const QString& str)
250     {
251         QPointF retVal;
252         QStringList list = str.split(QLatin1Char(','));
253 
254         if(list.size() == 2) {
255             retVal.setX(list.first().toDouble());
256             retVal.setY(list.last().toDouble());
257         }
258         return retVal;
259     }
260 
261     /**
262      * Loads pixmap from xmi.
263      *
264      * @param pixEle  The dom element from which pixmap should be loaded.
265      *
266      * @param pixmap  The pixmap into which the image should be loaded.
267      *
268      * @return  True or false based on success or failure of this method.
269      */
loadPixmapFromXMI(QDomElement & pixEle,QPixmap & pixmap)270     bool loadPixmapFromXMI(QDomElement &pixEle, QPixmap &pixmap)
271     {
272         if (pixEle.isNull()) {
273             return false;
274         }
275         QDomElement xpmElement = pixEle.firstChildElement(QLatin1String("xpm"));
276 
277         QByteArray xpmData = xpmElement.text().toLatin1();
278         QBuffer buffer(&xpmData);
279         buffer.open(QIODevice::ReadOnly);
280 
281         QImageReader reader(&buffer, "xpm");
282         QImage image;
283         if (!reader.read(&image)) {
284             return false;
285         }
286 
287         pixmap = QPixmap::fromImage(image);
288         return true;
289     }
290 
291     /**
292      * Saves pixmap information into DOM element \a qElement.
293      *
294      * @param qDoc The DOM document object.
295      *
296      * @param qElement The DOM element into which the pixmap should be
297      *                 saved.
298      *
299      * @param pixmap The pixmap to be saved.
300      */
savePixmapToXMI(QXmlStreamWriter & stream,const QPixmap & pixmap)301     void savePixmapToXMI(QXmlStreamWriter& stream, const QPixmap& pixmap)
302     {
303         stream.writeStartElement(QLatin1String("pixmap"));
304 
305         stream.writeStartElement(QLatin1String("xpm"));
306         stream.writeEndElement();
307 
308         QBuffer buffer;
309         buffer.open(QIODevice::WriteOnly);
310         pixmap.save(&buffer, "xpm");
311         buffer.close();
312 
313         stream.writeTextElement(QString(), QString::fromLatin1(buffer.data()));
314 
315         stream.writeEndElement();
316     }
317 
318     /**
319      * Loads gradient from xmi. The gradient pointer should be null
320      * and the new gradient object will be created inside this method.
321      * The gradient should later be deleted externally.
322      *
323      * @param gradientElement The DOM element from which gradient should be
324      *                        loaded.
325      *
326      * @param gradient The pointer to gradient into which the gradient
327      *                 should be loaded. (Allocated inside this
328      *                 method)
329      *
330      * @return True or false based on success or failure of this method.
331      */
loadGradientFromXMI(QDomElement & gradientElement,QGradient * & gradient)332     bool loadGradientFromXMI(QDomElement &gradientElement, QGradient *&gradient)
333     {
334         if(gradientElement.isNull()) {
335             return false;
336         }
337 
338         int type_as_int;
339         QGradient::Type type;
340         QGradientStops stops;
341         QGradient::CoordinateMode cmode = QGradient::LogicalMode;
342         QGradient::Spread spread = QGradient::PadSpread;
343 
344         type_as_int = gradientElement.attribute(QLatin1String("type")).toInt();
345         type = QGradient::Type(type_as_int);
346         type_as_int = gradientElement.attribute(QLatin1String("spread")).toInt();
347         spread = QGradient::Spread(type_as_int);
348         type_as_int = gradientElement.attribute(QLatin1String("coordinatemode")).toInt();
349         cmode = QGradient::CoordinateMode(type_as_int);
350 
351         QDomElement stopElement = gradientElement.firstChildElement(QLatin1String("stops"));
352         if(stopElement.isNull()) {
353             return false;
354         }
355         for(QDomNode node = stopElement.firstChild(); !node.isNull(); node = node.nextSibling()) {
356             QDomElement ele = node.toElement();
357             if(ele.tagName() != QLatin1String("stop")) {
358                 continue;
359             }
360 
361             qreal posn = ele.attribute(QLatin1String("position")).toDouble();
362             QColor color = QColor(ele.attribute(QLatin1String("color")));
363             stops << QGradientStop(posn, color);
364         }
365 
366         if (type == QGradient::LinearGradient) {
367             QPointF p1 = stringToPoint(gradientElement.attribute(QLatin1String("start")));
368             QPointF p2 = stringToPoint(gradientElement.attribute(QLatin1String("finalstop")));
369             gradient = new QLinearGradient(p1, p2);
370         }
371         else if (type == QGradient::RadialGradient) {
372             QPointF center = stringToPoint(gradientElement.attribute(QLatin1String("center")));
373             QPointF focal = stringToPoint(gradientElement.attribute(QLatin1String("focalpoint")));
374             double radius = gradientElement.attribute(QLatin1String("radius")).toDouble();
375             gradient = new QRadialGradient(center, radius, focal);
376         }
377         else { // type == QGradient::ConicalGradient
378             QPointF center = stringToPoint(gradientElement.attribute(QLatin1String("center")));
379             double angle = gradientElement.attribute(QLatin1String("angle")).toDouble();
380             gradient = new QConicalGradient(center, angle);
381         }
382 
383         if(gradient) {
384             gradient->setStops(stops);
385             gradient->setSpread(spread);
386             gradient->setCoordinateMode(cmode);
387             return true;
388         }
389 
390         return false;
391     }
392 
393     /**
394      * Saves gradient information into DOM element \a qElement.
395      *
396      * @param qDoc The DOM document object.
397      *
398      * @param qElement The DOM element into which the gradient should be
399      *                 saved.
400      *
401      * @param gradient The gradient to be saved.
402      */
saveGradientToXMI(QXmlStreamWriter & stream,const QGradient * gradient)403     void saveGradientToXMI(QXmlStreamWriter& stream, const QGradient *gradient)
404     {
405         stream.writeStartElement(QLatin1String("gradient"));
406 
407         stream.writeAttribute(QLatin1String("type"), QString::number(gradient->type()));
408         stream.writeAttribute(QLatin1String("spread"), QString::number(gradient->spread()));
409         stream.writeAttribute(QLatin1String("coordinatemode"), QString::number(gradient->coordinateMode()));
410 
411         QGradient::Type type = gradient->type();
412 
413         if(type == QGradient::LinearGradient) {
414             const QLinearGradient *lg = static_cast<const QLinearGradient*>(gradient);
415             stream.writeAttribute(QLatin1String("start"), pointToString(lg->start()));
416             stream.writeAttribute(QLatin1String("finalstop"), pointToString(lg->finalStop()));
417         }
418         else if(type == QGradient::RadialGradient) {
419             const QRadialGradient *rg = static_cast<const QRadialGradient*>(gradient);
420             stream.writeAttribute(QLatin1String("center"), pointToString(rg->center()));
421             stream.writeAttribute(QLatin1String("focalpoint"), pointToString(rg->focalPoint()));
422             stream.writeAttribute(QLatin1String("radius"), QString::number(rg->radius()));
423         }
424         else { //type == QGradient::ConicalGradient
425             const QConicalGradient *cg = static_cast<const QConicalGradient*>(gradient);
426             stream.writeAttribute(QLatin1String("center"), pointToString(cg->center()));
427             stream.writeAttribute(QLatin1String("angle"), QString::number(cg->angle()));
428         }
429 
430         stream.writeStartElement(QLatin1String("stops"));
431 
432         foreach (const QGradientStop& stop, gradient->stops()) {
433             stream.writeStartElement(QLatin1String("stop"));
434             stream.writeAttribute(QLatin1String("position"), QString::number(stop.first));
435             stream.writeAttribute(QLatin1String("color"), stop.second.name());
436             stream.writeEndElement();
437         }
438 
439         stream.writeEndElement();            // stops
440         stream.writeEndElement();   // gradient
441     }
442 
443     /**
444      * Extracts the QBrush properties into brush from the XMI xml
445      * element qElement.
446      *
447      * @param qElement The DOM element from which the xmi info should
448      *                 be extracted.
449      *
450      * @param brush The QBrush object into which brush details should
451      *              be read into.
452      */
loadBrushFromXMI(QDomElement & qElement,QBrush & brush)453     bool loadBrushFromXMI(QDomElement &qElement, QBrush &brush)
454     {
455         if(qElement.isNull()) {
456             return false;
457         }
458 
459         quint8 style = qElement.attribute(QLatin1String("style")).toShort();
460         const QString colorString = qElement.attribute(QLatin1String("color"));
461         QColor color;
462         color.setNamedColor(colorString);
463 
464         if(style == Qt::TexturePattern) {
465             QPixmap pixmap;
466             QDomElement pixElement = qElement.firstChildElement(QLatin1String("pixmap"));
467             if(!loadPixmapFromXMI(pixElement, pixmap)) {
468                 return false;
469             }
470             brush = QBrush(color, pixmap);
471         }
472 
473         else if(style == Qt::LinearGradientPattern
474                 || style == Qt::RadialGradientPattern
475                 || style == Qt::ConicalGradientPattern) {
476             QGradient *gradient = 0;
477             QDomElement gradElement = qElement.firstChildElement(QLatin1String("gradient"));
478 
479             if(!loadGradientFromXMI(gradElement, gradient) || !gradient) {
480                 delete gradient;
481                 return false;
482             }
483 
484             brush = QBrush(*gradient);
485             delete gradient;
486         }
487 
488         else {
489             brush = QBrush(color, (Qt::BrushStyle)style);
490         }
491 
492         //TODO: Checks if transform needs to be loaded.
493 
494         return true;
495     }
496 
497     /**
498      * Saves the brush info as xmi into the DOM element \a qElement.
499      *
500      * @param qDoc The QDomDocument object pointing to the xmi document.
501      *
502      * @param qElement The element into which the pen, brush and font
503      *                 info should be saved.
504      *
505      * @param brush The QBrush whose details should be saved.
506      */
saveBrushToXMI(QXmlStreamWriter & stream,const QBrush & brush)507     void saveBrushToXMI(QXmlStreamWriter& stream, const QBrush& brush)
508     {
509         stream.writeStartElement(QLatin1String("brush"));
510 
511         stream.writeAttribute(QLatin1String("style"), QString::number(brush.style()));
512         stream.writeAttribute(QLatin1String("color"), brush.color().name());
513 
514         if(brush.style() == Qt::TexturePattern) {
515             savePixmapToXMI(stream, brush.texture());
516         }
517         else if(brush.style() == Qt::LinearGradientPattern
518                 || brush.style() == Qt::RadialGradientPattern
519                 || brush.style() == Qt::ConicalGradientPattern) {
520             saveGradientToXMI(stream, brush.gradient());
521         }
522 
523         //TODO: Check if transform of this brush needs to be saved.
524         stream.writeEndElement();
525     }
526 
527     /**
528      * Returns true if the first widget's X is smaller than second's.
529      * Used for sorting the UMLWidgetList.
530      * @param widget1 The widget to compare.
531      * @param widget2 The widget to compare with.
532      */
hasSmallerX(const UMLWidget * widget1,const UMLWidget * widget2)533     bool hasSmallerX(const UMLWidget* widget1, const UMLWidget* widget2)
534     {
535         return widget1->x() < widget2->x();
536     }
537 
538     /**
539      * Returns true if the first widget's Y is smaller than second's.
540      * Used for sorting the UMLWidgetList.
541      * @param widget1 The widget to compare.
542      * @param widget2 The widget to compare with.
543      */
hasSmallerY(const UMLWidget * widget1,const UMLWidget * widget2)544     bool hasSmallerY(const UMLWidget* widget1, const UMLWidget* widget2)
545     {
546         return widget1->y() < widget2->y();
547     }
548 
549     /**
550      * Find the region in which the rectangle \a other lies with respect to
551      * the rectangle \a self.
552      * Beware that the Qt coordinate system has its origin point (0,0) in
553      * the upper left corner with Y values growing downwards, thus the Y
554      * related comparisons might look inverted if your are used to the
555      * natural coordinate system with (0,0) in the lower left corner.
556      */
findRegion(const QRectF & self,const QRectF & other)557     Uml::Region::Enum findRegion(const QRectF& self, const QRectF &other)
558     {
559         const qreal ownX      = self.x();
560         const qreal ownY      = self.y();
561         const qreal ownWidth  = self.width();
562         const qreal ownHeight = self.height();
563         const qreal otherX      = other.x();
564         const qreal otherY      = other.y();
565         const qreal otherWidth  = other.width();
566         const qreal otherHeight = other.height();
567         Uml::Region::Enum region = Uml::Region::Center;
568         if (otherX + otherWidth < ownX) {
569             if (otherY + otherHeight < ownY)
570                 region = Uml::Region::NorthWest;
571             else if (otherY > ownY + ownHeight)
572                 region = Uml::Region::SouthWest;
573             else
574                 region = Uml::Region::West;
575         } else if (otherX > ownX + ownWidth) {
576             if (otherY + otherHeight < ownY)
577                 region = Uml::Region::NorthEast;
578             else if (otherY > ownY + ownHeight)
579                 region = Uml::Region::SouthEast;
580             else
581                 region = Uml::Region::East;
582         } else {
583             if (otherY + otherHeight < ownY)
584                 region = Uml::Region::North;
585             else if (otherY > ownY + ownHeight)
586                 region = Uml::Region::South;
587             else
588                 region = Uml::Region::Center;
589         }
590         return region;
591     }
592 
593     /**
594      * Return the point in \a poly which precedes the point at index \a index.
595      * If \a index is 0 then return the last (or, if \a poly.isClosed() is
596      * true, the second to last) point.
597      */
prevPoint(int index,const QPolygonF & poly)598     QPointF prevPoint(int index, const QPolygonF& poly) {
599         if (poly.size() < 3 || index >= poly.size())
600             return QPoint();
601         if (index == 0)
602             return poly.at(poly.size() - 1 - (int)poly.isClosed());
603         return poly.at(index - 1);
604     }
605 
606     /**
607      * Return the point in \a poly which follows the point at index \a index.
608      * If \a index is the last index then return the first (or, if
609      * \a poly.isClosed() is true, the second) point.
610      */
nextPoint(int index,const QPolygonF & poly)611     QPointF nextPoint(int index, const QPolygonF& poly) {
612         if (poly.size() < 3 || index >= poly.size())
613             return QPoint();
614         if (index == poly.size() - 1)
615             return poly.at((int)poly.isClosed());
616         return poly.at(index + 1);
617     }
618 
619     /**
620      * Return the middle value between \a a and \a b.
621      */
middle(qreal a,qreal b)622     qreal middle(qreal a, qreal b)
623     {
624         return (a + b) / 2.0;
625     }
626 
627     /**
628      * Auxiliary type for function findLine()
629      */
630     enum Axis_Type { X , Y };
631 
632     /**
633      * Auxiliary type for function findLine()
634      */
635     enum Comparison_Type { Smallest, Largest };
636 
637     /**
638      * Find the line of \a poly with the smallest or largest value (controlled by \a seek)
639      * along the axis controlled by \a axis.
640      * In case \a axis is X, do not consider lines whose Y values lie outside the Y values
641      * defined by \a boundingRect.
642      * In case \a axis is Y, do not consider lines whose X values lie outside the X values
643      * defined by \a boundingRect.
644      */
findLine(const QPolygonF & poly,Axis_Type axis,Comparison_Type seek,const QRectF & boundingRect)645     QLineF findLine(const QPolygonF& poly, Axis_Type axis, Comparison_Type seek, const QRectF& boundingRect)
646     {
647         const int lastIndex = poly.size() - 1 - (int)poly.isClosed();
648         QPointF prev = poly.at(lastIndex), curr;
649         QPointF p1(seek == Smallest ? QPointF(1.0e6, 1.0e6) : QPointF(-1.0e6, -1.0e6));
650         QPointF p2;
651         for (int i = 0; i <= lastIndex; i++) {
652             curr = poly.at(i);
653             // uDebug() << "  poly[" << i << "] = " << curr;
654             if (axis == X) {
655                 if (fmin(prev.y(), curr.y()) > boundingRect.y() + boundingRect.height() ||
656                     fmax(prev.y(), curr.y()) < boundingRect.y()) {
657                     // line is outside Y-axis range defined by boundingRect
658                 } else if ((seek == Smallest && curr.x() <= p1.x()) ||
659                            (seek == Largest  && curr.x() >= p1.x())) {
660                     p1 = curr;
661                     p2 = prev;
662                 }
663             } else {
664                 if (fmin(prev.x(), curr.x()) > boundingRect.x() + boundingRect.width() ||
665                     fmax(prev.x(), curr.x()) < boundingRect.x()) {
666                     // line is outside X-axis range defined by boundingRect
667                 } else if ((seek == Smallest && curr.y() <= p1.y()) ||
668                            (seek == Largest  && curr.y() >= p1.y())) {
669                     p1 = curr;
670                     p2 = prev;
671                 }
672             }
673             prev = curr;
674         }
675         return QLineF(p1, p2);
676     }
677 
678     /**
679      * Determine the approximate closest points of two polygons.
680      * @param self  First QPolygonF.
681      * @param other Second QPolygonF.
682      * @return  QLineF::p1() returns point of \a self;
683      *          QLineF::p2() returns point of \a other.
684      */
closestPoints(const QPolygonF & self,const QPolygonF & other)685     QLineF closestPoints(const QPolygonF& self, const QPolygonF& other)
686     {
687         const QRectF& selfRect = self.boundingRect();
688         const QRectF& otherRect = other.boundingRect();
689         Uml::Region::Enum region = findRegion(selfRect, otherRect);
690         if (region == Uml::Region::Center)
691             return QLineF();
692         if (self.size() < 3 || other.size() < 3)
693             return QLineF();
694         QLineF result;
695         const int selfLastIndex  = self.size()  - 1 - (int)self.isClosed();
696         const int otherLastIndex = other.size() - 1 - (int)other.isClosed();
697         QPointF selfPoint(self.at(selfLastIndex));
698         QPointF otherPoint(other.at(otherLastIndex));
699         QLineF selfLine, otherLine;
700         int i;
701 
702         switch (region) {
703 
704         case Uml::Region::North:
705             // Find other's line with largest Y values
706             otherLine = findLine(other, Y, Largest, selfRect);
707             // Find own line with smallest Y values
708             selfLine = findLine(self, Y, Smallest, otherRect);
709             // Use the middle value of the X values
710             result.setLine(middle(selfLine.p2().x(), selfLine.p1().x()), selfLine.p1().y(),
711                            middle(otherLine.p2().x(), otherLine.p1().x()), otherLine.p1().y());
712             break;
713 
714         case Uml::Region::South:
715             // Find other's line with smallest Y values
716             otherLine = findLine(other, Y, Smallest, selfRect);
717             // Find own line with largest Y values
718             selfLine = findLine(self, Y, Largest, otherRect);
719             // Use the middle value of the X values
720             result.setLine(middle(selfLine.p2().x(), selfLine.p1().x()), selfLine.p1().y(),
721                            middle(otherLine.p2().x(), otherLine.p1().x()), otherLine.p1().y());
722             break;
723 
724         case Uml::Region::West:
725             // Find other's line with largest X values
726             otherLine = findLine(other, X, Largest, selfRect);
727             // Find own line with smallest X values
728             selfLine = findLine(self, X, Smallest, otherRect);
729             // Use the middle value of the Y values
730             result.setLine(selfLine.p1().x(), middle(selfLine.p2().y(), selfLine.p1().y()),
731                            otherLine.p1().x(), middle(otherLine.p2().y(), otherLine.p1().y()));
732             break;
733 
734         case Uml::Region::East:
735             // Find other's line with smallest X values
736             otherLine = findLine(other, X, Smallest, selfRect);
737             // Find own line with largest X values
738             selfLine = findLine(self, X, Largest, otherRect);
739             // Use the middle value of the Y values
740             result.setLine(selfLine.p1().x(), middle(selfLine.p2().y(), selfLine.p1().y()),
741                            otherLine.p1().x(), middle(otherLine.p2().y(), otherLine.p1().y()));
742             break;
743 
744         case Uml::Region::NorthWest:
745             // Find other's point with largest X and largest Y value
746             for (i = 0; i < otherLastIndex; ++i) {
747                 QPointF current(other.at(i));
748                 if (current.x() + current.y() >= otherPoint.x() + otherPoint.y()) {
749                     otherPoint = current;
750                 }
751             }
752             // Find own point with smallest X and smallest Y value
753             for (i = 0; i < selfLastIndex; ++i) {
754                 QPointF current(self.at(i));
755                 if (current.x() + current.y() <= selfPoint.x() + selfPoint.y()) {
756                     selfPoint = current;
757                 }
758             }
759             result.setPoints(selfPoint, otherPoint);
760             break;
761 
762         case Uml::Region::SouthWest:
763             // Find other's point with largest X and smallest Y value
764             for (i = 0; i < otherLastIndex; ++i) {
765                 QPointF current(other.at(i));
766                 if (current.x() >= otherPoint.x() && current.y() <= otherPoint.y()) {
767                     otherPoint = current;
768                 }
769             }
770             // Find own point with smallest X and largest Y value
771             for (i = 0; i < selfLastIndex; ++i) {
772                 QPointF current(self.at(i));
773                 if (current.x() <= selfPoint.x() && current.y() >= selfPoint.y()) {
774                     selfPoint = current;
775                 }
776             }
777             result.setPoints(selfPoint, otherPoint);
778             break;
779 
780         case Uml::Region::NorthEast:
781             // Find other's point with smallest X and largest Y value
782             for (i = 0; i < otherLastIndex; ++i) {
783                 QPointF current(other.at(i));
784                 if (current.x() <= otherPoint.x() && current.y() >= otherPoint.y()) {
785                     otherPoint = current;
786                 }
787             }
788             // Find own point with largest X and smallest Y value
789             for (i = 0; i < selfLastIndex; ++i) {
790                 QPointF current(self.at(i));
791                 if (current.x() >= selfPoint.x() && current.y() <= selfPoint.y()) {
792                     selfPoint = current;
793                 }
794             }
795             result.setPoints(selfPoint, otherPoint);
796             break;
797 
798         case Uml::Region::SouthEast:
799             // Find other's point with smallest X and smallest Y value
800             for (i = 0; i < otherLastIndex; ++i) {
801                 QPointF current(other.at(i));
802                 if (current.x() + current.y() <= otherPoint.x() + otherPoint.y()) {
803                     otherPoint = current;
804                 }
805             }
806             // Find own point with largest X and largest Y value
807             for (i = 0; i < selfLastIndex; ++i) {
808                 QPointF current(self.at(i));
809                 if (current.x() + current.y() >= selfPoint.x() + selfPoint.y()) {
810                     selfPoint = current;
811                 }
812             }
813             result.setPoints(selfPoint, otherPoint);
814             break;
815 
816         default:
817             // Error
818             break;
819         }
820 
821         return result;
822     }
823 
824     /**
825      * Returns a default name for the new widget
826      * @param type the widget type
827      * @return the default name
828      */
defaultWidgetName(WidgetBase::WidgetType type)829     QString defaultWidgetName(WidgetBase::WidgetType type)
830     {
831         switch(type) {
832         case WidgetBase::wt_Activity:         return i18n("new activity");
833         case WidgetBase::wt_Actor:            return i18n("new actor");
834         case WidgetBase::wt_Artifact:         return i18n("new artifact");
835         case WidgetBase::wt_Association:      return i18n("new association");
836         case WidgetBase::wt_Box:              return i18n("new box");
837         case WidgetBase::wt_Category:         return i18n("new category");
838         case WidgetBase::wt_Class:            return i18n("new class");
839         case WidgetBase::wt_CombinedFragment: return i18n("new combined fragment");
840         case WidgetBase::wt_Component:        return i18n("new component");
841         case WidgetBase::wt_Datatype:         return i18n("new datatype");
842         case WidgetBase::wt_Entity:           return i18n("new entity");
843         case WidgetBase::wt_Enum:             return i18n("new enum");
844         case WidgetBase::wt_FloatingDashLine: return i18n("new floating dash line");
845         case WidgetBase::wt_ForkJoin:         return i18n("new fork/join");
846         case WidgetBase::wt_Instance:         return i18n("new instance");
847         case WidgetBase::wt_Interface:        return i18n("new interface");
848         case WidgetBase::wt_Message:          return i18n("new message");
849         case WidgetBase::wt_Node:             return i18n("new node");
850         case WidgetBase::wt_Note:             return i18n("new note");
851         case WidgetBase::wt_Object:           return i18n("new object");
852         case WidgetBase::wt_ObjectNode:       return i18n("new object node");
853         case WidgetBase::wt_Package:          return i18n("new package");
854         case WidgetBase::wt_Pin:              return i18n("new pin");
855         case WidgetBase::wt_Port:             return i18n("new port");
856         case WidgetBase::wt_Precondition:     return i18n("new precondition");
857         case WidgetBase::wt_Region:           return i18n("new region");
858         case WidgetBase::wt_Signal:           return i18n("new signal");
859         case WidgetBase::wt_State:            return i18n("new state");
860         case WidgetBase::wt_Text:             return i18n("new text");
861         case WidgetBase::wt_UMLWidget:        return i18n("new UML widget");
862         case WidgetBase::wt_UseCase:          return i18n("new use case");
863         default:
864             uWarning() << "unknown widget type:" << WidgetBase::toString(type);
865             return i18n("new widget");
866             break;
867         }
868     }
869 
870     /**
871      * Returns translated title string used by widget related dialogs
872      * @param type widget type
873      * @return translated title string
874      */
newTitle(WidgetBase::WidgetType type)875     QString newTitle(WidgetBase::WidgetType type)
876     {
877         switch(type) {
878         case WidgetBase::wt_Activity:         return i18n("New activity");
879         case WidgetBase::wt_Actor:            return i18n("New actor");
880         case WidgetBase::wt_Artifact:         return i18n("New artifact");
881         case WidgetBase::wt_Association:      return i18n("New association");
882         case WidgetBase::wt_Box:              return i18n("New box");
883         case WidgetBase::wt_Category:         return i18n("New category");
884         case WidgetBase::wt_Class:            return i18n("New class");
885         case WidgetBase::wt_CombinedFragment: return i18n("New combined fragment");
886         case WidgetBase::wt_Component:        return i18n("New component");
887         case WidgetBase::wt_Datatype:         return i18n("New datatype");
888         case WidgetBase::wt_Entity:           return i18n("New entity");
889         case WidgetBase::wt_Enum:             return i18n("New enum");
890         case WidgetBase::wt_FloatingDashLine: return i18n("New floating dash line");
891         case WidgetBase::wt_ForkJoin:         return i18n("New fork/join");
892         case WidgetBase::wt_Instance:         return i18n("New instance");
893         case WidgetBase::wt_Interface:        return i18n("New interface");
894         case WidgetBase::wt_Message:          return i18n("New message");
895         case WidgetBase::wt_Node:             return i18n("New node");
896         case WidgetBase::wt_Note:             return i18n("New note");
897         case WidgetBase::wt_Object:           return i18n("New object");
898         case WidgetBase::wt_ObjectNode:       return i18n("New object node");
899         case WidgetBase::wt_Package:          return i18n("New package");
900         case WidgetBase::wt_Pin:              return i18n("New pin");
901         case WidgetBase::wt_Port:             return i18n("New port");
902         case WidgetBase::wt_Precondition:     return i18n("New precondition");
903         case WidgetBase::wt_Region:           return i18n("New region");
904         case WidgetBase::wt_Signal:           return i18n("New signal");
905         case WidgetBase::wt_State:            return i18n("New state");
906         case WidgetBase::wt_Text:             return i18n("New text");
907         case WidgetBase::wt_UMLWidget:        return i18n("New UML widget");
908         case WidgetBase::wt_UseCase:          return i18n("New use case");
909         default:
910             uWarning() << "unknown widget type:" << WidgetBase::toString(type);
911             return i18n("New widget");
912         }
913     }
914 
915     /**
916      * Returns translated text string used by widget related dialogs
917      * @param type widget type
918      * @return translated text string
919      */
newText(WidgetBase::WidgetType type)920     QString newText(WidgetBase::WidgetType type)
921     {
922         switch(type) {
923         case WidgetBase::wt_Activity:         return i18n("Enter the name of the new activity:");
924         case WidgetBase::wt_Actor:            return i18n("Enter the name of the new actor:");
925         case WidgetBase::wt_Artifact:         return i18n("Enter the name of the new artifact:");
926         case WidgetBase::wt_Association:      return i18n("Enter the name of the new association:");
927         case WidgetBase::wt_Box:              return i18n("Enter the name of the new box:");
928         case WidgetBase::wt_Category:         return i18n("Enter the name of the new category:");
929         case WidgetBase::wt_Class:            return i18n("Enter the name of the new class:");
930         case WidgetBase::wt_CombinedFragment: return i18n("Enter the name of the new combined fragment:");
931         case WidgetBase::wt_Component:        return i18n("Enter the name of the new component:");
932         case WidgetBase::wt_Datatype:         return i18n("Enter the name of the new datatype:");
933         case WidgetBase::wt_Entity:           return i18n("Enter the name of the new entity:");
934         case WidgetBase::wt_Enum:             return i18n("Enter the name of the new enum:");
935         case WidgetBase::wt_FloatingDashLine: return i18n("Enter the name of the new floating dash Line:");
936         case WidgetBase::wt_ForkJoin:         return i18n("Enter the name of the new fork/join:");
937         case WidgetBase::wt_Instance:         return i18n("Enter the name of the new instance:");
938         case WidgetBase::wt_Interface:        return i18n("Enter the name of the new interface:");
939         case WidgetBase::wt_Message:          return i18n("Enter the name of the new message:");
940         case WidgetBase::wt_Node:             return i18n("Enter the name of the new node:");
941         case WidgetBase::wt_Note:             return i18n("Enter the name of the new note:");
942         case WidgetBase::wt_Object:           return i18n("Enter the name of the new object:");
943         case WidgetBase::wt_ObjectNode:       return i18n("Enter the name of the new object node:");
944         case WidgetBase::wt_Package:          return i18n("Enter the name of the new package:");
945         case WidgetBase::wt_Pin:              return i18n("Enter the name of the new pin:");
946         case WidgetBase::wt_Port:             return i18n("Enter the name of the new port:");
947         case WidgetBase::wt_Precondition:     return i18n("Enter the name of the new precondition:");
948         case WidgetBase::wt_Region:           return i18n("Enter the name of the new region:");
949         case WidgetBase::wt_Signal:           return i18n("Enter the name of the new signal:");
950         case WidgetBase::wt_State:            return i18n("Enter the name of the new state:");
951         case WidgetBase::wt_Text:             return i18n("Enter the name of the new text:");
952         case WidgetBase::wt_UMLWidget:        return i18n("Enter the name of the new uml widget:");
953         case WidgetBase::wt_UseCase:          return i18n("Enter the name of the new use case:");
954         default:
955             uWarning() << "unknown widget type:" << WidgetBase::toString(type);
956             return i18n("Enter the name of the new widget:");
957         }
958     }
959 
960     /**
961      * Returns translated title string used by widget related dialogs
962      * @param type widget type
963      * @return translated title string
964      */
renameTitle(WidgetBase::WidgetType type)965     QString renameTitle(WidgetBase::WidgetType type)
966     {
967         switch(type) {
968         case WidgetBase::wt_Activity:         return i18n("Rename activity");
969         case WidgetBase::wt_Actor:            return i18n("Rename actor");
970         case WidgetBase::wt_Artifact:         return i18n("Rename artifact");
971         case WidgetBase::wt_Association:      return i18n("Rename association");
972         case WidgetBase::wt_Box:              return i18n("Rename box");
973         case WidgetBase::wt_Category:         return i18n("Rename category");
974         case WidgetBase::wt_Class:            return i18n("Rename class");
975         case WidgetBase::wt_CombinedFragment: return i18n("Rename combined fragment");
976         case WidgetBase::wt_Component:        return i18n("Rename component");
977         case WidgetBase::wt_Datatype:         return i18n("Rename datatype");
978         case WidgetBase::wt_Entity:           return i18n("Rename entity");
979         case WidgetBase::wt_Enum:             return i18n("Rename enum");
980         case WidgetBase::wt_FloatingDashLine: return i18n("Rename floating dash line");
981         case WidgetBase::wt_ForkJoin:         return i18n("Rename fork/join");
982         case WidgetBase::wt_Instance:         return i18n("Rename instance");
983         case WidgetBase::wt_Interface:        return i18n("Rename interface");
984         case WidgetBase::wt_Message:          return i18n("Rename message");
985         case WidgetBase::wt_Node:             return i18n("Rename node");
986         case WidgetBase::wt_Note:             return i18n("Rename note");
987         case WidgetBase::wt_Object:           return i18n("Rename object");
988         case WidgetBase::wt_ObjectNode:       return i18n("Rename object node");
989         case WidgetBase::wt_Package:          return i18n("Rename package");
990         case WidgetBase::wt_Pin:              return i18n("Rename pin");
991         case WidgetBase::wt_Port:             return i18n("Rename port");
992         case WidgetBase::wt_Precondition:     return i18n("Rename precondition");
993         case WidgetBase::wt_Region:           return i18n("Rename region");
994         case WidgetBase::wt_Signal:           return i18n("Rename signal");
995         case WidgetBase::wt_State:            return i18n("Rename state");
996         case WidgetBase::wt_Text:             return i18n("Rename text");
997         case WidgetBase::wt_UMLWidget:        return i18n("Rename UML widget");
998         case WidgetBase::wt_UseCase:          return i18n("Rename use case");
999         default:
1000             uWarning() << "unknown widget type:" << WidgetBase::toString(type);
1001             return i18n("Rename widget");
1002         }
1003     }
1004 
1005     /**
1006      * Returns translated text string used by widget related dialogs
1007      * @param type widget type
1008      * @return translated text string
1009      */
renameText(WidgetBase::WidgetType type)1010     QString renameText(WidgetBase::WidgetType type)
1011     {
1012         switch(type) {
1013         case WidgetBase::wt_Activity:         return i18n("Enter the new name of the activity:");
1014         case WidgetBase::wt_Actor:            return i18n("Enter the new name of the actor:");
1015         case WidgetBase::wt_Artifact:         return i18n("Enter the new name of the artifact:");
1016         case WidgetBase::wt_Association:      return i18n("Enter the new name of the association:");
1017         case WidgetBase::wt_Box:              return i18n("Enter the new name of the box:");
1018         case WidgetBase::wt_Category:         return i18n("Enter the new name of the category:");
1019         case WidgetBase::wt_Class:            return i18n("Enter the new name of the class:");
1020         case WidgetBase::wt_CombinedFragment: return i18n("Enter the new name of the combined fragment:");
1021         case WidgetBase::wt_Component:        return i18n("Enter the new name of the component:");
1022         case WidgetBase::wt_Datatype:         return i18n("Enter the new name of the datatype:");
1023         case WidgetBase::wt_Entity:           return i18n("Enter the new name of the entity:");
1024         case WidgetBase::wt_Enum:             return i18n("Enter the new name of the enum:");
1025         case WidgetBase::wt_FloatingDashLine: return i18n("Enter the new name of the floating dash Line:");
1026         case WidgetBase::wt_ForkJoin:         return i18n("Enter the new name of the fork/join widget:");
1027         case WidgetBase::wt_Instance:         return i18n("Enter the new name of the instance:");
1028         case WidgetBase::wt_Interface:        return i18n("Enter the new name of the interface:");
1029         case WidgetBase::wt_Message:          return i18n("Enter the new name of the message:");
1030         case WidgetBase::wt_Node:             return i18n("Enter the new name of the node:");
1031         case WidgetBase::wt_Note:             return i18n("Enter the new name of the note:");
1032         case WidgetBase::wt_Object:           return i18n("Enter the new name of the object:");
1033         case WidgetBase::wt_ObjectNode:       return i18n("Enter the new name of the object node:");
1034         case WidgetBase::wt_Package:          return i18n("Enter the new name of the package:");
1035         case WidgetBase::wt_Pin:              return i18n("Enter the new name of the pin:");
1036         case WidgetBase::wt_Port:             return i18n("Enter the new name of the port:");
1037         case WidgetBase::wt_Precondition:     return i18n("Enter the new name of the precondition:");
1038         case WidgetBase::wt_Region:           return i18n("Enter the new name of the region:");
1039         case WidgetBase::wt_Signal:           return i18n("Enter the new name of the signal:");
1040         case WidgetBase::wt_State:            return i18n("Enter the new name of the state:");
1041         case WidgetBase::wt_Text:             return i18n("Enter the new name of the text:");
1042         case WidgetBase::wt_UMLWidget:        return i18n("Enter the new name of the uml widget:");
1043         case WidgetBase::wt_UseCase:          return i18n("Enter the new name of the use case:");
1044         default:
1045             uWarning() << "unknown widget type:" << WidgetBase::toString(type);
1046             return i18n("Enter the new name of the widget:");
1047         }
1048     }
1049 }
1050