1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "WorkflowViewItems.h"
23 
24 #include <QBitmap>
25 #include <QDomElement>
26 #include <QGraphicsItem>
27 #include <QGraphicsSceneMouseEvent>
28 #include <QGraphicsSimpleTextItem>
29 #include <QGraphicsTextItem>
30 #include <QGraphicsView>
31 #include <QPainter>
32 #include <QRadialGradient>
33 #include <QStyleOptionGraphicsItem>
34 #include <QTextDocument>
35 #include <QtMath>
36 
37 #include <U2Core/Log.h>
38 #include <U2Core/QVariantUtils.h>
39 
40 #include <U2Lang/ActorModel.h>
41 #include <U2Lang/ActorPrototypeRegistry.h>
42 #include <U2Lang/IntegralBus.h>
43 #include <U2Lang/IntegralBusModel.h>
44 #include <U2Lang/WorkflowRunTask.h>
45 #include <U2Lang/WorkflowSettings.h>
46 #include <U2Lang/WorkflowUtils.h>
47 
48 #include "ItemViewStyle.h"
49 #include "WorkflowEditor.h"
50 #include "WorkflowViewController.h"
51 
52 namespace U2 {
53 
WorkflowProcessItem(Actor * prc)54 WorkflowProcessItem::WorkflowProcessItem(Actor *prc)
55     : process(prc), hasBreakpoint(false), highlighting(nullptr) /*, inspectionItem(NULL)*/ {
56     setToolTip(process->getProto()->getDocumentation());
57     setFlag(QGraphicsItem::ItemIsSelectable, true);
58     setFlag(QGraphicsItem::ItemIsMovable, true);
59 #if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
60     setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
61 #endif
62     setAcceptHoverEvents(true);
63 
64     styles[ItemStyles::SIMPLE] = new SimpleProcStyle(this);
65     styles[ItemStyles::EXTENDED] = new ExtendedProcStyle(this);
66     currentStyle = getStyleByIdSafe(WorkflowSettings::defaultStyle());
67     currentStyle->setVisible(true);
68     createPorts();
69     connect(prc, SIGNAL(si_descriptionChanged()), SLOT(sl_descriptionChanged()));
70 }
71 
~WorkflowProcessItem()72 WorkflowProcessItem::~WorkflowProcessItem() {
73     qDeleteAll(styles.values());
74     qDeleteAll(ports);
75     delete highlighting;
76 }
77 
getStyleByIdSafe(StyleId id) const78 ItemViewStyle *WorkflowProcessItem::getStyleByIdSafe(StyleId id) const {
79     if (!styles.contains(id)) {
80         uiLog.trace(QString("Unknown workflow item style: %1").arg(id));
81         id = ItemStyles::EXTENDED;
82     }
83     ItemViewStyle *result = styles.value(id);
84     assert(result != nullptr);
85     return result;
86 }
87 
getStyleById(const StyleId & id) const88 ItemViewStyle *WorkflowProcessItem::getStyleById(const StyleId &id) const {
89     return styles.value(id);
90 }
91 
containsStyle(const StyleId & id) const92 bool WorkflowProcessItem::containsStyle(const StyleId &id) const {
93     return styles.contains(id);
94 }
95 
getPort(const QString & id) const96 WorkflowPortItem *WorkflowProcessItem::getPort(const QString &id) const {
97     foreach (WorkflowPortItem *pit, ports) {
98         if (pit->getPort()->getId() == id) {
99             return pit;
100         }
101     }
102     return nullptr;
103 }
104 
createPorts()105 void WorkflowProcessItem::createPorts() {
106     assert(ports.isEmpty());
107 
108     int num = process->getInputPorts().size() + 1;
109     qreal pie = 180 / num;
110     int i = 1;
111     QGraphicsScene *sc = scene();
112     foreach (Port *port, process->getInputPorts()) {
113         WorkflowPortItem *pit = new WorkflowPortItem(this, port);
114         connect(port, SIGNAL(si_enabledChanged(bool)), pit, SLOT(sl_onVisibleChanged(bool)));
115         ports << pit;
116         pit->setOrientation(90 + pie * i++);
117         if (sc) {
118             sc->addItem(pit);
119         }
120         pit->sl_onVisibleChanged(port->isEnabled());
121     }
122     num = process->getOutputPorts().size() + 1;
123     pie = 180 / num;
124     i = 1;
125     foreach (Port *port, process->getOutputPorts()) {
126         WorkflowPortItem *pit = new WorkflowPortItem(this, port);
127         connect(port, SIGNAL(si_enabledChanged(bool)), pit, SLOT(sl_onVisibleChanged(bool)));
128         ports << pit;
129         pit->setOrientation(270 + pie * i++);
130         if (sc) {
131             sc->addItem(pit);
132         }
133         pit->sl_onVisibleChanged(port->isEnabled());
134     }
135 }
136 
sl_update()137 void WorkflowProcessItem::sl_update() {
138     prepareGeometryChange();
139     currentStyle->refresh();
140     foreach (WorkflowPortItem *pit, ports) {
141         pit->adaptOwnerShape();
142     }
143     update();
144 }
145 
sl_descriptionChanged()146 void WorkflowProcessItem::sl_descriptionChanged() {
147     setToolTip(process->getProto()->getDocumentation());
148 }
149 
setStyle(StyleId s)150 void WorkflowProcessItem::setStyle(StyleId s) {
151     prepareGeometryChange();
152     currentStyle->setVisible(false);
153     currentStyle = getStyleByIdSafe(s);
154     currentStyle->setVisible(true);
155     currentStyle->refresh();
156     foreach (WorkflowPortItem *pit, ports) {
157         pit->setStyle(s);
158     }
159     assert(currentStyle);
160     update();
161 }
162 
boundingRect(void) const163 QRectF WorkflowProcessItem::boundingRect(void) const {
164     QRectF brect = currentStyle->boundingRect();
165     brect.setTop(brect.top() - QFontMetrics(QFont()).height() * 2 - 2);
166     return brect;
167 }
168 
portsBoundingRect() const169 QRectF WorkflowProcessItem::portsBoundingRect() const {
170     QRectF rect;  // null rect
171     foreach (WorkflowPortItem *p, getPortItems()) {
172         QRectF pBound = p->boundingRect();
173         QPointF pCenter = pBound.center();
174         pCenter = p->mapToItem(this, pCenter);
175         pBound.moveCenter(pCenter);
176         rect |= pBound;
177     }
178     return rect;
179 }
180 
shape() const181 QPainterPath WorkflowProcessItem::shape() const {
182     return currentStyle->shape();
183 }
184 
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)185 void WorkflowProcessItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) {
186     WorkflowAbstractRunner *rt = getWorkflowScene()->getRunner();
187     if (rt) {
188         //{WorkerWaiting, WorkerReady, WorkerRunning, WorkerDone};
189         static QColor rsColors[5] = {QColor(234, 143, 7), "#04AA04", "#AA0404", "#0404AA", "#9B30FF"};
190         //{QColor(234,143,7),QColor(Qt::red),QColor(Qt::green),QColor(0,0,255)};
191         static QString rsNames[5] = {("Waiting"), ("Ready"), ("Running"), ("Done"), ("Paused")};
192 
193         const QList<WorkerState> rsList = rt->getState(this->process);
194         WorkerState state = WorkerDone;
195         if (rsList.contains(WorkerRunning)) {
196             state = WorkerRunning;
197         } else if (rsList.contains(WorkerReady)) {
198             state = WorkerReady;
199         } else if (rsList.contains(WorkerWaiting)) {
200             state = WorkerWaiting;
201         } else if (rsList.contains(WorkerPaused)) {
202             state = WorkerPaused;
203         }
204 
205         QString stateName = rsNames[state];
206         QColor scolor = rsColors[state];
207         painter->setPen(scolor);
208         QRectF brect = boundingRect();
209         qreal fh = QFontMetrics(QFont()).height();
210 
211         if (rsList.size() == 1) {
212             brect.setTop(brect.top() + 2 + fh);
213         }
214 
215         painter->drawRoundedRect(brect, 5, 5);
216         painter->drawText(brect, Qt::AlignHCenter, stateName);
217         if (rsList.size() == 1) {
218         } else {
219             uint vals[4] = {0, 0, 0, 0};
220             for (int i = rsList.size(); i > 0;) {
221                 vals[rsList.at(--i)]++;
222             }
223 
224             // draw progress bar
225             if (state != WorkerDone) {
226                 QRectF textRect(brect.topLeft() + QPointF(0, fh - 1), QSizeF(brect.width(), 3));
227                 textRect.setLeft(brect.left() + 1);
228                 textRect.setRight(brect.right() - 1);
229                 QColor fc = /*rsColors[WorkerDone];//*/ QColor(0, 80, 222);
230                 fc.setAlpha(90);
231                 painter->setPen(fc);
232                 painter->drawRect(textRect);
233                 qreal done = (qreal)vals[WorkerDone] / rsList.size();
234                 QBrush brush(fc);
235                 //                 QLinearGradient lg(textRect.topLeft(),textRect.topRight());
236                 //                 lg.setColorAt(0, fc);
237                 //                 lg.setColorAt(done, fc.lighter(128));
238                 //                 lg.setColorAt(done + 1./rsList.size(), QColor(Qt::white));
239                 // lg.setColorAt(1, QColor(Qt::white));
240                 // brush = QBrush(lg);
241                 textRect.setRight(textRect.left() + textRect.width() * done);
242                 painter->fillRect(textRect, brush);
243             }
244 
245             // draw extended text
246             painter->save();
247             QTextDocument d;
248             d.setHtml("<center><font size='-1'>" + QString("<font color='%1'>%2/</font>"
249                                                            " <!--font color='%3'>%4/</font-->"
250                                                            " <font color='%5'>%6/</font>"
251                                                            " <font color='%7'>%8/</font>"
252                                                            " <font color='black'>%9</font>")
253                                                        .arg(rsColors[WorkerWaiting].name())
254                                                        .arg(vals[WorkerWaiting])
255                                                        .arg(rsColors[WorkerReady].name())
256                                                        .arg(vals[WorkerReady])
257                                                        .arg(rsColors[WorkerRunning].name())
258                                                        .arg(vals[WorkerRunning])
259                                                        .arg(rsColors[WorkerDone].name())
260                                                        .arg(vals[WorkerDone])
261                                                        .arg(rsList.size()) +
262                       "</font></center>");
263             // d.setTextWidth(brect.width());
264             painter->translate(brect.center().x() - d.idealWidth() / 2, brect.top() + fh);
265             d.drawContents(painter /*, brect*/);
266             painter->restore();
267 
268             //             qreal unit = brect.width()/rsList.size();
269             //             qreal step = 3;
270             //             qreal w = 10;
271             //             QPointF base(brect.topLeft());
272             //             base.ry() += 5;
273 
274             //             for (int i = 3; i>=0; i--) {
275             //  //             base.ry() -= step;
276             //  //             painter->setPen(rsColors[i]); painter->drawLine(base, base + QPointF(unit*vals[i],0));
277             //                 QRectF rt(base, base + QPointF(unit*vals[i],w)); base = rt.topRight();
278             //                 painter->setPen(rsColors[i]);
279             //                 QColor fc = rsColors[i]; fc.setAlpha(128);
280             //                 painter->fillRect(rt, QBrush(fc));
281             //                 painter->drawText(rt, Qt::AlignJustify|Qt::AlignVCenter, rsNames[i]);
282             //             }
283         }
284     }
285 }
286 
updatePorts()287 void WorkflowProcessItem::updatePorts() {
288     foreach (WorkflowPortItem *pit, ports) {
289         pit->setPos(pos());
290         foreach (WorkflowBusItem *bit, pit->getDataFlows()) {
291             bit->updatePos();
292         }
293     }
294 }
295 
itemChange(GraphicsItemChange change,const QVariant & value)296 QVariant WorkflowProcessItem::itemChange(GraphicsItemChange change, const QVariant &value) {
297     switch (change) {
298         case ItemSelectedHasChanged: {
299             currentStyle->update();
300         } break;
301         case ItemZValueHasChanged: {
302             qreal z = value.value<qreal>();
303             foreach (WorkflowPortItem *pit, ports) {
304                 pit->setZValue(z);
305             }
306         } break;
307         case ItemPositionChange: {
308             // value is the new position.
309             QPointF newPos = value.toPointF();
310             if (scene() && pos() != QPointF(0, 0)) {
311                 QRectF bound = boundingRect() | childrenBoundingRect() | portsBoundingRect();
312                 QRectF sceneRect = scene()->sceneRect();
313 
314                 qreal x0 = sceneRect.left() - bound.left();
315                 qreal x1 = sceneRect.left() + sceneRect.width() - bound.right() - 10;  // extra space for scroll bars
316                 qreal y0 = sceneRect.top() - bound.top();
317                 qreal y1 = sceneRect.top() + sceneRect.height() - bound.bottom() - 10;
318 
319                 newPos.setX(qBound(x0, newPos.x(), x1));
320                 newPos.setY(qBound(y0, newPos.y(), y1));
321             }
322             if (WorkflowSettings::snap2Grid()) {
323                 newPos.setX(round(newPos.x(), GRID_STEP));
324                 newPos.setY(round(newPos.y(), GRID_STEP));
325             }
326             return newPos;
327             /*foreach(WorkflowPortItem* pit, ports) {
328             foreach(WorkflowBusItem*bit, pit->getDataFlows()) {
329             bit->prepareGeometryChange();
330             }
331             }*/
332         } break;
333         case ItemPositionHasChanged: {
334             updatePorts();
335 
336             WorkflowScene *sc = qobject_cast<WorkflowScene *>(scene());
337             if (sc != nullptr) {
338                 if (!sc->views().isEmpty()) {
339                     foreach (QGraphicsView *view, sc->views()) {
340                         QRectF itemRect = boundingRect() | childrenBoundingRect();
341                         // ports are not the child items atm
342                         // unite with their bounds
343                         itemRect |= portsBoundingRect();
344                         QPointF itemCenter = itemRect.center();
345                         itemCenter = mapToScene(itemCenter);
346                         itemRect.moveCenter(itemCenter);
347                         view->ensureVisible(itemRect, 0, 0);
348                     }
349                 }
350                 sc->setModified(true);
351             }
352             if (scene()) {
353                 scene()->update();
354             }
355         } break;
356         case ItemSceneHasChanged: {
357             WorkflowScene *ws = getWorkflowScene();
358             if (ws) {
359                 ItemViewStyle *viewStyle = styles.value(ItemStyles::EXTENDED);
360                 ExtendedProcStyle *extStyle = qgraphicsitem_cast<ExtendedProcStyle *>(viewStyle);
361                 assert(extStyle);
362                 WorkflowView *view = ws->getController();
363                 if (view) {
364                     connect(extStyle, SIGNAL(linkActivated(const QString &)), view->getPropertyEditor(), SLOT(sl_linkActivated(const QString &)));
365                 }
366 
367                 foreach (WorkflowPortItem *pit, ports) {
368                     ws->addItem(pit);
369                 }
370             }
371         } break;
372         case ItemSceneChange:
373             if ((value.value<QGraphicsScene *>()) == nullptr) {
374                 foreach (WorkflowPortItem *pit, ports) {
375                     scene()->removeItem(pit);
376                 }
377                 // scene()->removeItem(inspectionItem);
378                 // delete inspectionItem;
379             }
380             break;
381             /*    case ItemSelectedChange:
382             if (NULL != inspectionItem) {
383                 inspectionItem->setPermanent(!inspectionItem->isPermanent());
384                 if (!inspectionItem->isPermanent()) {
385                     inspectionItem->eraseFromScene();
386                 }
387             }
388             break;*/
389         default:
390             break;
391     }
392     return QGraphicsItem::itemChange(change, value);
393 }
394 
sceneEvent(QEvent * event)395 bool WorkflowProcessItem::sceneEvent(QEvent *event) {
396     if (currentStyle->sceneEventFilter(this, event)) {
397         return true;
398     }
399     return QGraphicsItem::sceneEvent(event);
400 }
401 
getContextMenuActions() const402 QList<QAction *> WorkflowProcessItem::getContextMenuActions() const {
403     return currentStyle->getContextMenuActions();
404 }
405 
saveState(QDomElement & el) const406 void WorkflowProcessItem::saveState(QDomElement &el) const {
407     el.setAttribute("pos", QVariantUtils::var2String(pos()));
408     el.setAttribute("style", styles.key(currentStyle));
409     foreach (ItemViewStyle *style, styles) {
410         QDomElement stel = el.ownerDocument().createElement(style->getId());
411         style->saveState(stel);
412         if (stel.hasAttributes() || stel.hasChildNodes()) {
413             el.appendChild(stel);
414         }
415     }
416 }
417 
loadState(QDomElement & el)418 void WorkflowProcessItem::loadState(QDomElement &el) {
419     const QString posS = el.attribute("pos");
420     const QPointF pos = QVariantUtils::String2Var(posS).toPointF();
421     assert(!pos.isNull());
422     setPos(pos);
423 
424     foreach (ItemViewStyle *style, styles) {
425         QDomElement stel = el.elementsByTagName(style->getId()).item(0).toElement();
426         if (stel.isNull())
427             continue;
428         style->loadState(stel);
429     }
430     QString key = el.attribute("style");
431     if (styles.contains(key)) {
432         setStyle(key);
433     }
434 }
435 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)436 void WorkflowProcessItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
437     if (event->buttons() & Qt::LeftButton) {
438         if (initialPositions.isEmpty()) {
439             if (isSelected()) {
440                 QList<QGraphicsItem *> selectedItems = scene()->selectedItems();
441                 foreach (QGraphicsItem *item, selectedItems) {
442                     if (item->type() == WorkflowProcessItemType) {
443                         initialPositions[item] = item->scenePos();
444                     }
445                 }
446             } else {
447                 initialPositions[this] = scenePos();
448             }
449         }
450 
451         // Find the active view.
452         QGraphicsView *view = 0;
453         if (event->widget()) {
454             view = qobject_cast<QGraphicsView *>(event->widget()->parentWidget());
455         }
456 
457         for (int i = 0, n = initialPositions.keys().size(); i < n; i++) {
458             QGraphicsItem *item = initialPositions.keys().at(i);
459 
460             QPointF currentParentPos = view->mapToScene(view->mapFromGlobal(event->screenPos()));
461             QPointF buttonDownParentPos = view->mapToScene(view->mapFromGlobal(event->buttonDownScreenPos(Qt::LeftButton)));
462 
463             item->setPos(initialPositions.value(item) + currentParentPos - buttonDownParentPos);
464         }
465     } else {
466         event->ignore();
467     }
468 }
469 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)470 void WorkflowProcessItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
471     initialPositions.clear();
472     QGraphicsItem::mouseReleaseEvent(event);
473 }
474 
toggleBreakpoint()475 void WorkflowProcessItem::toggleBreakpoint() {
476     hasBreakpoint = !hasBreakpoint;
477     if (!hasBreakpoint) {
478         hasEnabledBreakpoint = false;
479     } else {
480         if (nullptr == highlighting) {
481             highlighting = new WorkflowHighlightItem(this);
482         }
483         hasEnabledBreakpoint = true;
484     }
485 }
486 
toggleBreakpointState()487 void WorkflowProcessItem::toggleBreakpointState() {
488     Q_ASSERT(hasBreakpoint);
489     hasEnabledBreakpoint = !hasEnabledBreakpoint;
490 }
491 
isBreakpointInserted()492 bool WorkflowProcessItem::isBreakpointInserted() {
493     return hasBreakpoint;
494 }
495 
isBreakpointEnabled()496 bool WorkflowProcessItem::isBreakpointEnabled() {
497     return hasEnabledBreakpoint;
498 }
499 
highlightItem()500 void WorkflowProcessItem::highlightItem() {
501     highlighting->replay();
502 }
503 
504 ///////////// PIO /////////////
505 
findNearbyBindingCandidate(const QPointF & pos) const506 WorkflowPortItem *WorkflowPortItem::findNearbyBindingCandidate(const QPointF &pos) const {
507     QPainterPath neighbourhood;
508     neighbourhood.addEllipse(pos, R / 2, R / 2);
509     // QRectF neighbourhood(pos.x() - R/2, pos.y() + R/2, R, R);
510     WorkflowPortItem *candidate = nullptr;
511     qreal distance = R * 2;
512     foreach (QGraphicsItem *it, scene()->items(neighbourhood, Qt::IntersectsItemBoundingRect)) {
513         WorkflowPortItem *next = qgraphicsitem_cast<WorkflowPortItem *>(it);
514         if (next) {
515             if (bindCandidates.contains(next)) {
516                 QLineF l(pos, next->headToScene());
517                 qreal len = l.length();
518                 if (distance > len) {
519                     distance = len;
520                     candidate = next;
521                 }
522             }
523         }
524     }
525     return candidate;
526 }
527 
528 // static const QCursor portRotationCursor = QCursor(QBitmap(":workflow_designer/images/rot_cur.png")); //FIXME
529 static const int portRotationModifier = Qt::AltModifier;
530 static const int bl = (int)A / 4;
531 
WorkflowPortItem(WorkflowProcessItem * owner,Port * p)532 WorkflowPortItem::WorkflowPortItem(WorkflowProcessItem *owner, Port *p)
533     : /*StyledItem(owner), */ currentStyle(owner->getStyle()), port(p), owner(owner), orientation(0), dragging(false), rotating(false),
534       sticky(false), highlight(false), mouseMoveIsBeingProcessed(false) {
535     setFlags(ItemIsSelectable | ItemIsFocusable);
536     setAcceptHoverEvents(true);
537     QString tt = p->isInput() ? "Input port (" : "Output port (";
538     tt += p->getDocumentation();
539     tt += ").\nDrag it to connect to other process/port."
540           "\nHold Alt key while dragging to change port orientation";
541     setToolTip(tt);
542 
543     setPos(owner->pos());
544     setZValue(owner->zValue());
545 }
546 
~WorkflowPortItem()547 WorkflowPortItem::~WorkflowPortItem() {
548     assert(flows.isEmpty());
549 }
550 
setStyle(StyleId s)551 void WorkflowPortItem::setStyle(StyleId s) {
552     Q_UNUSED(s);
553     currentStyle = owner->getStyle();
554     adaptOwnerShape();
555 }
556 
adaptOwnerShape()557 void WorkflowPortItem::adaptOwnerShape() {
558     setOrientation(orientation);
559 }
560 
setOrientation(qreal angle)561 void WorkflowPortItem::setOrientation(qreal angle) {
562     qreal oldOrientation = orientation;
563     orientation = angle;
564 
565     if (ItemStyles::SIMPLE == currentStyle) {
566         angle = -angle;
567         qreal x = R * qCos(angle * 2 * M_PI / 360);
568         qreal y = R * qSin(angle * 2 * M_PI / 360);
569 
570         resetTransform();
571         setTransform(QTransform::fromTranslate(x, y), true);
572         setRotation(angle);
573     } else {  // EXTENDED STYLE
574         resetTransform();
575         QRectF rec = owner->boundingRect();
576         QPolygonF pol(owner->shape().toFillPolygon());
577         qreal radius = qMax(rec.width(), rec.height()) * 2;
578         QLineF centerLine(0, 0, radius, 0);
579         assert(pol.containsPoint(centerLine.p1(), Qt::WindingFill));
580         assert(!pol.containsPoint(centerLine.p2(), Qt::WindingFill));
581         centerLine.setAngle(angle);
582         QPointF p1 = pol.first();
583         QPointF p2;
584         QLineF polyLine;
585         QPointF intersectPoint;
586         for (int i = 1; i < pol.count(); ++i) {
587             p2 = pol.at(i);
588             polyLine = QLineF(p1, p2);
589             if (QLineF::BoundedIntersection == polyLine.intersect(centerLine, &intersectPoint)) {
590                 break;
591             }
592             p1 = p2;
593         }
594 
595         setTransform(QTransform::fromTranslate(intersectPoint.x(), intersectPoint.y()), true);
596         qreal norm = polyLine.normalVector().angle();
597         qreal df = qAbs(norm - angle);
598         if (df > 90 && df < 270) {
599             norm += 180;
600         }
601         setRotation(-norm);
602     }
603     if (oldOrientation != orientation) {
604         WorkflowScene *sc = qobject_cast<WorkflowScene *>(owner->scene());
605         if (sc != nullptr) {
606             sc->setModified(true);
607             sc->update();
608         }
609     }
610 }
611 
getDataFlow(const WorkflowPortItem * otherPit) const612 WorkflowBusItem *WorkflowPortItem::getDataFlow(const WorkflowPortItem *otherPit) const {
613     foreach (WorkflowBusItem *dit, flows) {
614         if ((port->isInput() ? dit->getOutPort() : dit->getInPort()) == otherPit) {
615             return dit;
616         }
617     }
618     return nullptr;
619 }
620 
checkTypes(Port * p1,Port * p2)621 static bool checkTypes(Port *p1, Port *p2) {
622     Port *ip = p1->isInput() ? p1 : p2;
623     Port *op = p1->isInput() ? p2 : p1;
624     DataTypePtr idt = ip->getType();
625     DataTypePtr odt = op->getType();
626     if (idt->isSingle() && odt->isMap()) {
627         foreach (Descriptor d, odt->getAllDescriptors()) {
628             if (idt == odt->getDatatypeByDescriptor(d))
629                 return true;
630         }
631     }
632     if (idt->isMap() && odt->isMap()) {
633         if (idt->getDatatypesMap().isEmpty()) {
634             ActorPrototype *proto = ip->owner()->getProto();
635             return proto->isAllowsEmptyPorts();
636         }
637         foreach (Descriptor d1, idt->getAllDescriptors()) {
638             foreach (Descriptor d2, odt->getAllDescriptors()) {
639                 if (idt->getDatatypeByDescriptor(d1) == odt->getDatatypeByDescriptor(d2))
640                     return true;
641             }
642         }
643     }
644     return odt == idt;
645 }
646 
checkBindCandidate(const QGraphicsItem * it) const647 WorkflowPortItem *WorkflowPortItem::checkBindCandidate(const QGraphicsItem *it) const {
648     switch (it->type()) {
649         case WorkflowProcessItemType: {
650             const WorkflowProcessItem *receiver = static_cast<const WorkflowProcessItem *>(it);
651             // try best matches first
652             foreach (WorkflowPortItem *otherPit, receiver->getPortItems()) {
653                 if (port->canBind(otherPit->getPort()) && checkTypes(port, otherPit->getPort())) {
654                     return otherPit;
655                 }
656             }
657             // take first free port
658             foreach (WorkflowPortItem *otherPit, receiver->getPortItems()) {
659                 if (port->canBind(otherPit->getPort())) {
660                     return otherPit;
661                 }
662             }
663         } break;
664         case WorkflowPortItemType: {
665             WorkflowPortItem *otherPit = (WorkflowPortItem *)(it);
666             if (port->canBind(otherPit->getPort())) {
667                 return otherPit;
668             }
669         } break;
670     }
671     return nullptr;
672 }
673 
removeDataFlow(WorkflowBusItem * flow)674 void WorkflowPortItem::removeDataFlow(WorkflowBusItem *flow) {
675     assert(flows.contains(flow));
676     flows.removeOne(flow);
677     assert(!flows.contains(flow));
678 }
679 
addDataFlow(WorkflowBusItem * flow)680 void WorkflowPortItem::addDataFlow(WorkflowBusItem *flow) {
681     flows << flow;
682 }
683 
head(const QGraphicsItem * item) const684 QPointF WorkflowPortItem::head(const QGraphicsItem *item) const {
685     return mapToItem(item, A / 2 + bl, 0);
686 }
687 
headToScene() const688 QPointF WorkflowPortItem::headToScene() const {
689     return mapToScene(A, 0);
690 }
691 
arrow(const QGraphicsItem * item) const692 QLineF WorkflowPortItem::arrow(const QGraphicsItem *item) const {
693     return QLineF(mapToItem(item, 0, 0), mapToItem(item, A, 0));
694 }
695 
boundingRect(void) const696 QRectF WorkflowPortItem::boundingRect(void) const {
697     QRectF rect(0, -A, A + A / 2, 2 * A);
698     if (dragging) {
699         rect |= QRectF(QPointF(A, 0), dragPoint);
700         // FIXME arrow tip inclusion
701     }
702     return rect;
703 }
704 
drawArrow(QPainter * painter,const QPen & pen,const QPointF & p1,const QPointF & p2)705 static void drawArrow(QPainter *painter, const QPen &pen, const QPointF &p1, const QPointF &p2) {
706     painter->setPen(pen);
707     QLineF l(p1, p2);
708     painter->drawLine(l);
709     // draw arrow tip
710     painter->save();
711     painter->translate(p2);
712     painter->rotate(-l.angle());
713     QRectF rf(-3 * A, -A / 2, A * 1.5, A);
714     QPainterPath tip(QPointF(0, 0));
715     tip.arcTo(rf, -50, 100);
716     tip.closeSubpath();
717     painter->fillPath(tip, QBrush(pen.color()));
718     painter->restore();
719 }
720 
getCandidates(WorkflowPortItem * port)721 static QList<WorkflowPortItem *> getCandidates(WorkflowPortItem *port) {
722     QList<WorkflowPortItem *> l;
723     foreach (QGraphicsItem *it, port->scene()->items()) {
724         if (it->type() == WorkflowPortItemType) {
725             WorkflowPortItem *next = qgraphicsitem_cast<WorkflowPortItem *>(it);
726             if (port->getPort()->canBind(next->getPort()) && checkTypes(next->getPort(), port->getPort()) && !WorkflowUtils::isPathExist(port->getPort(), next->getPort())) {
727                 l.append(next);
728             }
729         }
730     }
731     return l;
732 }
733 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)734 void WorkflowPortItem::paint(QPainter *painter,
735                              const QStyleOptionGraphicsItem *option,
736                              QWidget *widget) {
737     Q_UNUSED(widget);
738     QPointF p1(A / 2 + bl, 0);
739     QColor greenLight(0, 0x99, 0x33, 128);
740     QColor stickyLight(0, 0x77, 0x33);
741 
742     if (highlight) {
743         QPen pen;
744         pen.setColor(greenLight);
745         painter->setPen(pen);
746     }
747 
748     // painter->fillRect(boundingRect(), QBrush(Qt::magenta, Qt::Dense4Pattern));
749     painter->setRenderHint(QPainter::Antialiasing);
750     painter->drawLine(0, 0, bl, 0);
751 
752     if (port->isInput()) {
753         if (highlight) {
754             QPainterPath path;
755             path.addEllipse(bl, -A / 2, A, A);
756             painter->fillPath(path, QBrush(greenLight));
757         } else {
758             painter->drawArc(QRectF(bl, -A / 2, A, A), 90 * 16, 180 * 16);
759         }
760     } else {
761         if (highlight) {
762             QPainterPath path;
763             path.addEllipse(p1, A / 2, A / 2);
764             painter->fillPath(path, QBrush(greenLight));
765         } else {
766             painter->drawEllipse(p1, A / 2, A / 2);
767         }
768     }
769     if (0 && port->getWidth() == 0) {
770         // draw a hint
771         painter->save();
772         QPen pen;
773         // pen.setWidthF(2);
774         pen.setStyle(Qt::DashLine);
775         painter->setPen(pen);
776         QPointF hc(R, 0);
777         drawArrow(painter, pen, hc, p1);
778 
779         painter->translate(R, 0);
780         painter->rotate(orientation);
781         QRectF approx(-10, -10, 200, 100);
782         QRectF htb = painter->boundingRect(approx, Qt::AlignCenter, port->getDisplayName());
783         // painter->drawRoundedRect(htb, 30, 30, Qt::RelativeSize);
784 
785         painter->drawRoundedRect(htb, 30, 30, Qt::RelativeSize);
786         painter->drawText(approx, Qt::AlignCenter, port->getDisplayName());
787         painter->setPen(Qt::red);
788         painter->drawPoint(0, 0);
789         painter->restore();
790     }
791     if (dragging) {
792         QPen pen;
793         // pen.setWidthF(3);
794         pen.setStyle(Qt::DotLine);
795         if (sticky) {
796             pen.setColor(stickyLight);
797         }
798         // put drag point inside of the scene rect
799         QPointF pp = dragPoint;
800         QRectF scRect = scene()->sceneRect();
801         QList<QLineF> sceneEdges;
802         sceneEdges << QLineF(scRect.topLeft(), scRect.topRight())
803                    << QLineF(scRect.topRight(), scRect.bottomRight())
804                    << QLineF(scRect.bottomLeft(), scRect.bottomRight())
805                    << QLineF(scRect.topLeft(), scRect.bottomLeft());
806         QLineF arr(mapToScene(dragPoint), mapToScene(p1));
807         QPointF crossPt;
808         foreach (QLineF scEdge, sceneEdges) {
809             if (scEdge.intersect(arr, &crossPt) == QLineF::BoundedIntersection) {
810                 pp = mapFromScene(crossPt);
811                 break;
812             }
813         }
814         //////////////////////////////////////////////////////////////////////////
815         if (port->isInput())
816             drawArrow(painter, pen, pp, p1);
817         else
818             drawArrow(painter, pen, p1, pp);
819     } else if (option->state & QStyle::State_Selected) {
820         QPen pen;
821         // pen.setWidthF(2);
822         pen.setStyle(Qt::DotLine);
823         painter->setPen(pen);
824         painter->drawRoundedRect(boundingRect(), 30, 30, Qt::RelativeSize);
825     }
826 }
827 
focusOutEvent(QFocusEvent *)828 void WorkflowPortItem::focusOutEvent(QFocusEvent * /*event*/) {
829     if (dragging) {
830         dragging = false;
831         scene()->update();
832     }
833 }
834 
835 namespace {
836 
837 class ScopedFlagFlipper {
838 public:
ScopedFlagFlipper(bool & flag)839     ScopedFlagFlipper(bool &flag)
840         : flag(flag) {
841         flag = true;
842     }
843 
~ScopedFlagFlipper()844     ~ScopedFlagFlipper() {
845         flag = false;
846     }
847 
848 private:
849     bool &flag;
850 };
851 
852 }  // namespace
853 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)854 void WorkflowPortItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
855     CHECK(!mouseMoveIsBeingProcessed, );
856 
857     ScopedFlagFlipper guard(mouseMoveIsBeingProcessed);
858     Q_UNUSED(guard);
859     if (!dragging && !rotating && (event->buttons() & Qt::LeftButton) && !dragPoint.isNull()) {
860         // log.debug("port grabbed mouse");
861         if ((event->pos().toPoint() - dragPoint.toPoint()).manhattanLength() < 10)
862             return;
863         event->accept();
864         // grabMouse();
865         if (event->modifiers() & portRotationModifier) {
866             rotating = true;
867             // setCursor(portRotationCursor);
868             setCursor(QCursor(QPixmap(":workflow_designer/images/rot_cur.png")));
869         } else {
870             dragging = true;
871             setCursor(Qt::ClosedHandCursor);
872             bindCandidates = getCandidates(this);
873             foreach (WorkflowPortItem *it, bindCandidates) {
874                 it->setHighlight(true);
875                 it->update(it->boundingRect());
876             }
877         }
878     }
879 
880     sticky = false;
881 
882     if (!dragging && !rotating) {
883         return;
884     }
885     event->accept();
886     prepareGeometryChange();
887     if (rotating) {
888         qreal angle = QLineF(owner->pos(), event->scenePos()).angle();
889         setOrientation(angle);
890     }
891     if (dragging) {
892         foreach (QGraphicsView *v, scene()->views()) {
893             QRectF r(0, 0, 5, 5);
894             r.moveCenter(mapToScene(dragPoint));
895             v->ensureVisible(r, 0, 0);
896         }
897         dragPoint += event->pos() - event->lastPos();
898 
899         WorkflowPortItem *preferable = findNearbyBindingCandidate(event->scenePos());
900         if (preferable) {
901             dragPoint = preferable->head(this);
902             sticky = true;
903         }
904     }
905 }
906 
mousePressEvent(QGraphicsSceneMouseEvent * event)907 void WorkflowPortItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
908     dragPoint = QPointF();
909     if ((event->buttons() & Qt::LeftButton) && !getWorkflowScene()->isLocked()) {
910         dragPoint = event->pos();
911         if ((event->modifiers() & portRotationModifier) && !dragging) {
912             rotating = true;
913             setCursor(QCursor(QPixmap(":workflow_designer/images/rot_cur.png")));
914         } else {
915             setCursor(Qt::ClosedHandCursor);
916         }
917     } else {
918         QGraphicsItem::mousePressEvent(event);
919     }
920 }
921 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)922 void WorkflowPortItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
923     ungrabMouse();
924     unsetCursor();
925     QGraphicsItem::mouseReleaseEvent(event);
926     rotating = false;
927     if (dragging && (event->button() == Qt::LeftButton)) {
928         event->accept();
929         QList<QGraphicsItem *> li = scene()->items(/*event->scenePos()*/ mapToScene(dragPoint));
930         // bool done = false;
931         WorkflowPortItem *otherPit = nullptr;
932         foreach (QGraphicsItem *it, li) {
933             WorkflowView *ctl = getWorkflowScene()->getController();
934             if (ctl) {
935                 otherPit = checkBindCandidate(it);
936                 WorkflowBusItem *dit;
937                 if (otherPit && (dit = ctl->tryBind(this, otherPit))) {
938                     scene()->clearSelection();
939                     IntegralBusPort *bp = qobject_cast<IntegralBusPort *>(dit->getInPort()->getPort());
940                     if (bp) {
941                         bp->setupBusMap();
942                     }
943                     dit->getInPort()->setSelected(true);
944                     break;
945                 }
946             }
947         }
948         prepareGeometryChange();
949         dragging = false;
950         dragPoint = QPointF();
951         foreach (WorkflowPortItem *it, bindCandidates) {
952             it->setHighlight(false);
953         }
954         scene()->update();
955         bindCandidates.clear();
956     }
957 }
958 
hoverEnterEvent(QGraphicsSceneHoverEvent * event)959 void WorkflowPortItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
960     if (getWorkflowScene()->isLocked()) {
961         return;
962     }
963     setCursor((event->modifiers() & portRotationModifier) ? QCursor(QPixmap(":workflow_designer/images/rot_cur.png")) : QCursor(Qt::OpenHandCursor));
964 }
965 
hoverLeaveEvent(QGraphicsSceneHoverEvent *)966 void WorkflowPortItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
967     unsetCursor();
968 }
969 
itemChange(GraphicsItemChange change,const QVariant & value)970 QVariant WorkflowPortItem::itemChange(GraphicsItemChange change, const QVariant &value) {
971     if (change == ItemPositionChange || change == ItemTransformChange) {
972         foreach (WorkflowBusItem *dit, flows) {
973             dit->prepareGeometryChange();
974         }
975     } else if (change == ItemPositionHasChanged || change == ItemTransformHasChanged) {
976         foreach (WorkflowBusItem *dit, flows) {
977             // TODO correct update
978             // dit->update(dit->boundingRect());
979             dit->updatePos();
980         }
981     } else if (change == ItemSceneChange && (value.value<QGraphicsScene *>()) == nullptr) {
982         foreach (WorkflowBusItem *dit, flows) {
983             scene()->removeItem(dit);
984             delete dit;
985         }
986     }
987 
988     return QGraphicsItem::itemChange(change, value);
989 }
990 
sl_onVisibleChanged(bool isVisible)991 void WorkflowPortItem::sl_onVisibleChanged(bool isVisible) {
992     setVisible(isVisible);
993     if (false == isVisible) {
994         foreach (WorkflowBusItem *flow, flows) {
995             WorkflowScene *ws = getWorkflowScene();
996             if (nullptr != ws) {
997                 WorkflowView *view = ws->getController();
998                 view->removeBusItem(flow);
999             }
1000         }
1001     }
1002 }
1003 
1004 ////////////////// Flow //////////////
1005 
WorkflowBusItem(WorkflowPortItem * p1,WorkflowPortItem * p2,Link * link)1006 WorkflowBusItem::WorkflowBusItem(WorkflowPortItem *p1, WorkflowPortItem *p2, Link *link)
1007     : bus(link) {
1008     if (p1->getPort()->isInput()) {
1009         assert(!p2->getPort()->isInput());
1010         dst = p1;
1011         src = p2;
1012     } else {
1013         assert(p2->getPort()->isInput());
1014         dst = p2;
1015         src = p1;
1016     }
1017 
1018     setAcceptHoverEvents(true);
1019     setFlag(QGraphicsItem::ItemIsSelectable, true);
1020     setZValue(-1000);
1021 
1022     this->text = new HintItem(src->getPort()->getDisplayName(), this);
1023     connect(dst->getPort(), SIGNAL(bindingChanged()), this, SLOT(sl_update()));
1024 }
1025 
~WorkflowBusItem()1026 WorkflowBusItem::~WorkflowBusItem() {
1027     assert(bus == nullptr);
1028 }
1029 
updatePos()1030 void WorkflowBusItem::updatePos() {
1031     QPointF p1 = dst->headToScene();
1032     QPointF p2 = src->headToScene();
1033 
1034     setPos((p1.x() + p2.x()) / 2, (p1.y() + p2.y()) / 2);
1035 }
1036 
itemChange(GraphicsItemChange change,const QVariant & value)1037 QVariant WorkflowBusItem::itemChange(GraphicsItemChange change, const QVariant &value) {
1038     if (change == ItemSceneChange && (value.value<QGraphicsScene *>()) == nullptr) {
1039         dst->removeDataFlow(this);
1040         src->removeDataFlow(this);
1041         disconnect(dst->getPort(), SIGNAL(bindingChanged()), this, SLOT(sl_update()));
1042 
1043         WorkflowView *ctl = getWorkflowScene()->getController();
1044         if (nullptr != ctl) {
1045             ctl->onBusRemoved(bus);
1046         } else {
1047             delete bus;
1048         }
1049         bus = nullptr;
1050     }
1051 
1052     return QGraphicsItem::itemChange(change, value);
1053 }
1054 
hoverEnterEvent(QGraphicsSceneHoverEvent * event)1055 void WorkflowBusItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) {
1056     Q_UNUSED(event);
1057     setCursor(QCursor(Qt::PointingHandCursor));
1058 }
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)1059 void WorkflowBusItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
1060     Q_UNUSED(event);
1061     unsetCursor();
1062 }
1063 
boundingRect(void) const1064 QRectF WorkflowBusItem::boundingRect(void) const {
1065     QRectF brect(mapFromItem(dst, dst->boundingRect()).boundingRect() | mapFromItem(src, src->boundingRect()).boundingRect());
1066     QRectF trect(text->boundingRect().translated(text->pos()));
1067     if (/*getWorkflowScene()->getRunner()*/ true) {
1068         trect.setTop(trect.top() - trect.height());
1069     }
1070     return brect | trect;
1071 }
1072 
shape() const1073 QPainterPath WorkflowBusItem::shape() const {
1074     QPainterPath p;
1075     QPointF p1 = dst->head(this);
1076     QPointF p2 = src->head(this);
1077     QLineF direct(p2, p1);
1078     QLineF n = direct.normalVector();
1079     n.setLength(A / 2);
1080     p.moveTo(n.p2());
1081     p.lineTo(n.translated(p1 - p2).p2());
1082     QLineF rn(n.p2(), n.p1());
1083     rn.translate(n.p1() - n.p2());
1084     p.lineTo(rn.translated(p1 - p2).p2());
1085     p.lineTo(rn.p2());
1086     p.closeSubpath();
1087     p.addRect(text->boundingRect().translated(text->pos()));
1088     return p;
1089 }
1090 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)1091 void WorkflowBusItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
1092     Q_UNUSED(widget);
1093     painter->setRenderHint(QPainter::Antialiasing);
1094     QColor baseColor(0x66, 0x66, 0x66);
1095     painter->setPen(baseColor);
1096     // painter->fillRect(boundingRect(), QBrush(Qt::blue));
1097     QPointF p1 = dst->head(this);
1098     QPointF p2 = src->head(this);
1099 
1100     QPainterPath path;
1101     path.addEllipse(p2, A / 2 - 2, A / 2 - 2);
1102     path.addEllipse(p1, A / 2 - 2, A / 2 - 2);
1103     painter->fillPath(path, QBrush(baseColor));
1104 
1105     QPen pen = painter->pen();
1106     if (option->state & QStyle::State_Selected) {
1107         pen.setWidthF(1.5);
1108         pen.setStyle(Qt::DashLine);
1109     }
1110     if (!validate()) {
1111         pen.setColor(Qt::red);
1112     }
1113 
1114     drawArrow(painter, pen, p2, p1);
1115     // update();
1116 
1117     painter->setRenderHint(QPainter::NonCosmeticDefaultPen);
1118     QColor yc = QColor(Qt::yellow).lighter();
1119     yc.setAlpha(127);
1120     QRectF textRec = text->boundingRect().translated(text->pos());
1121     painter->fillRect(textRec, QBrush(yc));
1122     painter->drawRect(textRec);
1123 
1124     WorkflowAbstractRunner *rt = getWorkflowScene()->getRunner();
1125     if (rt) {
1126         int msgsInQueue = rt->getMsgNum(this->bus);
1127         int passed = rt->getMsgPassed(this->bus);
1128 
1129         QString rts = QString("%1 in queue, %2 passed").arg(msgsInQueue).arg(passed);
1130         QRectF rtb = textRec.translated(0, -QFontMetricsF(QFont()).height());
1131         qreal shift = (QFontMetricsF(QFont()).width(rts) - rtb.width()) / 2;
1132         rtb.setLeft(rtb.left() - shift);
1133         rtb.setRight(rtb.right() + shift);
1134         painter->drawText(rtb, Qt::AlignHCenter, rts);
1135         if (msgsInQueue == 0) {
1136             return;
1137         }
1138         qreal dx = (p2.x() - p1.x()) / msgsInQueue;
1139         qreal dy = (p2.y() - p1.y()) / msgsInQueue;
1140         QPointF dp(dx, dy);
1141         QColor c1("#AA0404");
1142         painter->setPen(c1);
1143         c1.setAlphaF(0.8);
1144         QColor c2(Qt::white);
1145         c2.setAlphaF(0.8);
1146         while (msgsInQueue-- > 0) {
1147             QPainterPath p;
1148             p.addEllipse(p1, 3, 3);
1149             QRadialGradient rg(p1 + QPointF(1., -1.), 3);
1150 
1151             rg.setColorAt(1, c1);
1152             rg.setColorAt(0, c2);
1153             QBrush br(rg);
1154 
1155             painter->fillPath(p, br);
1156             // painter->drawEllipse(p1, 3,3);
1157             p1 += dp;
1158         }
1159     }
1160 }
1161 
sl_update()1162 void WorkflowBusItem::sl_update() {
1163     update();
1164 }
1165 
validate()1166 bool WorkflowBusItem::validate() {
1167     NotificationsList lst;
1168     return dst->getPort()->validate(lst);
1169 }
1170 
saveState(QDomElement & el) const1171 void WorkflowBusItem::saveState(QDomElement &el) const {
1172     el.setAttribute("hint-pos", QVariantUtils::var2String(text->pos()));
1173 }
loadState(QDomElement & el)1174 void WorkflowBusItem::loadState(QDomElement &el) {
1175     if (el.hasAttribute("hint-pos")) {
1176         QPointF pos = QVariantUtils::String2Var(el.attribute("hint-pos")).toPointF();
1177         if (!pos.isNull()) {
1178             text->setPos(pos);
1179         }
1180     }
1181 }
1182 
getWorkflowScene() const1183 WorkflowScene *StyledItem::getWorkflowScene() const {
1184     return qobject_cast<WorkflowScene *>(scene());
1185 }
1186 
1187 ///////////////// WorkflowHighlightItem /////////////////////////////////////////////////////////
1188 
1189 const quint8 INIT_ANIMATION_STEPS_NUMBER = 50;
1190 const qreal HALVED_MAXIMUM_SIZE_RELATION_TO_PROCESS_ITEM_SIZE = 0.15;
1191 const QColor BORDER_COLOR = QColor(205, 133, 63);
1192 const QPointF INIT_POSITION = QPointF(0.0, 0.0);
1193 
WorkflowHighlightItem(WorkflowProcessItem * owner)1194 WorkflowHighlightItem::WorkflowHighlightItem(WorkflowProcessItem *owner)
1195     : StyledItem(owner), countOfAnimationStepsLeft(0) {
1196     setPos(INIT_POSITION);
1197     setZValue(owner->zValue());
1198     setVisible(false);
1199 }
1200 
boundingRect() const1201 QRectF WorkflowHighlightItem::boundingRect() const {
1202     WorkflowProcessItem *owner = dynamic_cast<WorkflowProcessItem *>(parentItem());
1203     const QRectF parentBoundary = owner->getStyleById(owner->getStyle())->boundingRect();
1204     const qreal sizeFactor = HALVED_MAXIMUM_SIZE_RELATION_TO_PROCESS_ITEM_SIZE * countOfAnimationStepsLeft / INIT_ANIMATION_STEPS_NUMBER;
1205 
1206     return parentBoundary.adjusted(-parentBoundary.width() * sizeFactor, -parentBoundary.height() * sizeFactor, parentBoundary.width() * sizeFactor, parentBoundary.height() * sizeFactor);
1207 }
1208 
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)1209 void WorkflowHighlightItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) {
1210     if (0 != countOfAnimationStepsLeft) {
1211         painter->setPen(BORDER_COLOR);
1212         painter->drawRoundedRect(boundingRect(), 5, 5);
1213         prepareGeometryChange();
1214         --countOfAnimationStepsLeft;
1215         if (0 == countOfAnimationStepsLeft) {
1216             setVisible(false);
1217         }
1218     }
1219 }
1220 
replay()1221 void WorkflowHighlightItem::replay() {
1222     countOfAnimationStepsLeft = INIT_ANIMATION_STEPS_NUMBER;
1223     setVisible(true);
1224     update();
1225 }
1226 
1227 }  // namespace U2
1228