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