Logo Search packages:      
Sourcecode: ugene version File versions

WorkflowViewItems.cpp

/*****************************************************************
* Unipro UGENE - Integrated Bioinformatics Suite
* Copyright (C) 2008 Unipro, Russia (http://ugene.unipro.ru)
* All Rights Reserved
* 
*     This source code is distributed under the terms of the
*     GNU General Public License. See the files COPYING and LICENSE
*     for details.
*****************************************************************/

#include "WorkflowViewItems.h"
#include "ItemViewStyle.h"

#include <workflow/WorkflowRegistry.h>
#include <workflow/WorkflowModel.h>
#include <workflow/IntegralBusModel.h>

#include <core_api/Log.h>
#include <QBitmap>
#include <QtGui/QPainter>
#include <QtGui/QGraphicsTextItem>
#include <QtGui/QGraphicsSimpleTextItem>
#include <QtGui/QGraphicsSceneMouseEvent>
#include <QtGui/QStyleOptionGraphicsItem>
#include <QtGui/QGraphicsView>
#include <QtGui/QRadialGradient>
#include <QtGui/QTextDocument>
#include <qmath.h>

namespace GB2 {

static LogCategory log(ULOG_CAT_WD);


WorkflowProcessItem::WorkflowProcessItem(Actor* prc) : process(prc) {
    setToolTip(process->getProto()->getDocumentation());
    setFlag(QGraphicsItem::ItemIsMovable, true);
    setFlag(QGraphicsItem::ItemIsSelectable, true);
    styles[ItemStyles::SIMPLE] = currentStyle = new SimpleProcStyle(this);
    styles[ItemStyles::EXTENDED] = new ExtendedProcStyle(this);
}

WorkflowProcessItem::~WorkflowProcessItem()
{
    qDeleteAll(styles.values());
    delete process;
    qDeleteAll(ports);
}


WorkflowPortItem* WorkflowProcessItem::getPort(const QString& id) const {
    foreach(WorkflowPortItem* pit, ports) {
        if (pit->getPort()->getId() == id) {
            return pit;
        }
    }
    return NULL;
}

void WorkflowProcessItem::createPorts() {
    assert(ports.isEmpty());

    int num = process->getInputPorts().size() + 1;
    qreal pie = 180/num;
    int i = 1;
    foreach(Port* port, process->getInputPorts()) {
        WorkflowPortItem* pit = new WorkflowPortItem(this, port);
        scene()->addItem(pit);
        ports << pit;
        pit->setPos(pos());
        pit->setZValue(zValue());
        pit->setOrientation(90 + pie*i++);
    }
    num = process->getOutputPorts().size() + 1;
    pie = 180/num;
    i = 1;
    foreach(Port* port, process->getOutputPorts()) {
        WorkflowPortItem* pit = new WorkflowPortItem(this, port);
        scene()->addItem(pit);
        ports << pit;
        pit->setPos(pos());
        pit->setZValue(zValue());
        pit->setOrientation(270 + pie*i++);
    }
}

void WorkflowProcessItem::sl_update() {
    prepareGeometryChange();
    currentStyle->refresh();
    foreach(WorkflowPortItem* pit, ports) {
        pit->adaptOwnerShape();
    }
    update();
}

void WorkflowProcessItem::setStyle(StyleId s) {
    prepareGeometryChange();
    currentStyle = styles.value(s);
    currentStyle->refresh();
    foreach(WorkflowPortItem* pit, ports) {
        pit->setStyle(s);
    }
    assert(currentStyle);
    update();
}

QRectF WorkflowProcessItem::boundingRect(void) const {
    return currentStyle->boundingRect();
}

QPainterPath WorkflowProcessItem::shape () const {
    return currentStyle->shape();
}

void WorkflowProcessItem::paint(QPainter *painter,
                          const QStyleOptionGraphicsItem *option,
                          QWidget *widget)
{
    currentStyle->paint(painter,option,widget);
}

QVariant WorkflowProcessItem::itemChange ( GraphicsItemChange change, const QVariant & value ) 
{
    switch(change) {
        case ItemSelectedHasChanged:
        {
            currentStyle->update();
        }
        break;
        case ItemZValueHasChanged:
            {
            qreal z = qVariantValue<qreal>(value);
            //currentStyle->setZValue(z);
            //currentStyle->update();
                  foreach(WorkflowPortItem* pit, ports) {
                        pit->setZValue(z);
                  }
            }
            break;
    case ItemPositionChange:
        /*foreach(WorkflowPortItem* pit, ports) {
            foreach(WBusItem*bit, pit->getDataFlows()) {
                bit->prepareGeometryChange();
            }
        }*/
        break;
      case ItemPositionHasChanged:
        foreach(WorkflowPortItem* pit, ports) {
            pit->setPos(pos());
            foreach(WBusItem*bit, pit->getDataFlows()) {
                bit->updatePos();
            }
        }
            break;
      case ItemSceneChange: 
            if (qVariantValue<QGraphicsScene*>(value) == NULL) {
                  foreach(WorkflowPortItem* pit, ports) {
                        scene()->removeItem(pit);
                  }
            }
            break;
    default:
        break;
    }
    return QGraphicsItem::itemChange(change, value);
}

///////////// PIO /////////////

WorkflowPortItem* WorkflowPortItem::findNearbyBindingCandidate(const QPointF& pos) const {
      QPainterPath neighbourhood;
      neighbourhood.addEllipse(pos, R/2, R/2);
      //QRectF neighbourhood(pos.x() - R/2, pos.y() + R/2, R, R);
      WorkflowPortItem* candidate = NULL;
      qreal distance = R*2;
      foreach(QGraphicsItem* it, scene()->items(neighbourhood, Qt::IntersectsItemBoundingRect)) {
        WorkflowPortItem* next = qgraphicsitem_cast<WorkflowPortItem*>(it);
            if (next) {
                  if (bindCandidates.contains(next)) {
                        QLineF l(pos, next->headToScene());
                        qreal len = l.length();                   
                        if (distance > len) {
                              distance = len;
                              candidate = next;
                        }
                  }
            }
      }
      return candidate;
}

//static const QCursor portRotationCursor = QCursor(QBitmap(":workflow_designer/images/rot_cur.png")); //FIXME
static const int portRotationModifier = Qt::AltModifier;
static const int bl = (int) A/4;


WorkflowPortItem::WorkflowPortItem(WorkflowProcessItem* owner, Port* p) 
: /*StyledItem(owner), */ currentStyle(ItemStyles::SIMPLE),port(p),owner(owner),orientation(0), dragging(false), rotating(false),
sticky(false), highlight(false)
{
    setFlag(QGraphicsItem::ItemIsSelectable, true);
    setAcceptHoverEvents(true);
    QString tt = p->isInput() ? "Input port (" : "Output port (";
    tt += p->getDocumentation();
    tt += ").\nDrag it to connect to other process/port."
        "\nHold Alt key while dragging to change port orientation";
    setToolTip(tt);
}

WorkflowPortItem::~WorkflowPortItem()
{
    assert(flows.isEmpty());
}


void WorkflowPortItem::setStyle(StyleId s) {
    Q_UNUSED(s);
    currentStyle = owner->getStyle();
    adaptOwnerShape();
}

void WorkflowPortItem::adaptOwnerShape() {
    setOrientation(orientation);
}

void WorkflowPortItem::setOrientation(qreal angle) {
    orientation = angle;
    /*if (ItemStyles::SIMPLE == currentStyle) {
        qreal x = R*qCos(angle*2*PI/360);
        qreal y = R*qSin(angle*2*PI/360);
        
        resetTransform();
        translate(x, y);
        rotate(angle);    
    } else*/ 
    {
        resetTransform();
        QRectF rec = owner->boundingRect();
        QPolygonF pol(owner->shape().toFillPolygon());
        qreal radius = qMax(rec.width(), rec.height());
        //log.debug(QString("radius=%1 pol=%2").arg(radius).arg(pol.count()));
        QLineF centerLine(0,0,radius, 0);
        assert(pol.containsPoint(centerLine.p1(), Qt::WindingFill));
        assert(!pol.containsPoint(centerLine.p2(), Qt::WindingFill));
        centerLine.setAngle(angle);
        QPointF p1 = pol.first();
        QPointF p2;
        QLineF polyLine;
        QPointF intersectPoint;
        for (int i = 1; i < pol.count(); ++i) {
            p2 = pol.at(i);
            polyLine = QLineF(p1, p2);
            if (QLineF::BoundedIntersection == polyLine.intersect(centerLine, &intersectPoint)) {
                break;
            }
            p1 = p2;
        }
        translate(intersectPoint.x(), intersectPoint.y());
        //qreal polyAngle = polyLine.angle();
        qreal norm = polyLine.normalVector().angle();
        qreal df = qAbs(norm - angle);
        if (df > 90 && df < 270) {
            norm += 180;
        }
        //log.debug(QString("a=%1 pa=%2 pn=%3 n=%4 df=%5").arg(angle).arg(polyAngle).arg(polyLine.normalVector().angle()).arg(norm).arg(df));
        rotate(-norm);
    }
}

WBusItem* WorkflowPortItem::getDataFlow(const WorkflowPortItem* otherPit) const {
      foreach(WBusItem* dit, flows) {
            if ((port->isInput() ? dit->getOutPort() : dit->getInPort()) == otherPit) {
                  return dit;
            }
      }
      return NULL;
}

static bool checkTypes(Port* p1, Port* p2) {
    Port* ip = p1->isInput() ? p1 : p2;
    Port* op = p1->isInput() ? p2 : p1;
    DataTypePtr idt = ip->getType();
    DataTypePtr odt = op->getType();
    if (idt->isSingle() && odt->isMap()) {
        foreach(Descriptor d, odt->getElements()) {
            if (idt == odt->getElement(d)) return true;
        }
    }
    if (idt->isMap() && odt->isMap()) {
        foreach(Descriptor d1, idt->getElements()) {
            foreach(Descriptor d2, odt->getElements()) {
                if (idt->getElement(d1) == odt->getElement(d2)) return true;
            }
        }
    }
    return odt == idt;
}

WorkflowPortItem* WorkflowPortItem::checkBindCandidate(const QGraphicsItem* it) const 
{
      switch (it->type()) {
                  case WorkflowProcessItemType: 
                        {
                              const WorkflowProcessItem* receiver = static_cast<const WorkflowProcessItem*>(it);
                    // try best matches first
                    foreach(WorkflowPortItem* otherPit, receiver->getPortItems()) {
                        if (port->canBind(otherPit->getPort()) && checkTypes(port, otherPit->getPort())) {
                            return otherPit;
                        }
                    }
                              // take first free port
                              foreach(WorkflowPortItem* otherPit, receiver->getPortItems()) {
                                    if (port->canBind(otherPit->getPort())) {
                                          return otherPit;
                                    }
                              }
                        }
                        break;
                  case WorkflowPortItemType:
                        {
                              WorkflowPortItem* otherPit = (WorkflowPortItem*)(it);
                              if (port->canBind(otherPit->getPort())) {
                                    return otherPit;
                              }
                        }
                        break;
      }
      return NULL;
}


WBusItem* WorkflowPortItem::tryBind(WorkflowPortItem* otherPit) {
    WBusItem* dit = NULL;
    if (port->canBind(otherPit->getPort())) {
        dit = new WBusItem(this, otherPit);
        flows << dit;
        otherPit->flows << dit;
        scene()->addItem(dit);
        dit->updatePos();
    }
    return dit;
}

void WorkflowPortItem::removeDataFlow(WBusItem* flow) {
    assert(flows.contains(flow));
    flows.removeOne(flow);
    port->removeLink(flow->getBus());
    assert(!flows.contains(flow));
}

QPointF WorkflowPortItem::head(const QGraphicsItem* item) const {
    return mapToItem(item, A/2 + bl, 0);
}

QPointF WorkflowPortItem::headToScene() const {
      return mapToScene(A, 0);
}

QLineF WorkflowPortItem::arrow(const QGraphicsItem* item) const {
    return QLineF(mapToItem(item, 0,0),mapToItem(item, A,0));
}

QRectF WorkflowPortItem::boundingRect(void) const {
    QRectF rect(0, -A, A+A/2, 2*A);
    if (dragging) {
        rect |= QRectF(QPointF(A, 0), dragPoint);
            //FIXME arrow tip inclusion
    }
    return rect;
}

static void drawArrow(QPainter *painter, const QPen& pen, const QPointF& p1, const QPointF& p2) {
      painter->setPen(pen);
      QLineF l(p1, p2);
      painter->drawLine(l);
      //draw arrow tip
      painter->save();
      painter->translate(p2);
      painter->rotate(-l.angle());
      QRectF rf(-3*A, -A/2, A*1.5, A);
      QPainterPath tip(QPointF(0,0));
      tip.arcTo(rf, -50, 100);
      tip.closeSubpath();
      painter->fillPath(tip, QBrush(pen.color()));
      painter->restore();
}

static QList<WorkflowPortItem*> getCandidates(WorkflowPortItem* port) {
    QList<WorkflowPortItem*> l;
    foreach(QGraphicsItem* it, port->scene()->items()) {
        if (it->type() == WorkflowPortItemType) {
            WorkflowPortItem* next = qgraphicsitem_cast<WorkflowPortItem*>(it);
            if (port->getPort()->canBind(next->getPort()) && checkTypes(next->getPort(), port->getPort())) {
                l.append(next);
            }
        }
    }
    return l;
}

void WorkflowPortItem::paint(QPainter *painter,
                                const QStyleOptionGraphicsItem *option,
                                QWidget *widget)
{
    Q_UNUSED(widget);
    QPointF p1(A/2 + bl, 0);
    QColor greenLight(0,0x99,0x33, 128);
    QColor stickyLight(0,0x77,0x33);

    if (highlight) {
        QPen pen;
        pen.setColor(greenLight);
        painter->setPen(pen);
    }

      //painter->fillRect(boundingRect(), QBrush(Qt::magenta, Qt::Dense4Pattern));
    painter->setRenderHint(QPainter::Antialiasing);
    painter->drawLine(0, 0, bl, 0);
      
    if (port->isInput()) {
        if (highlight) {
            QPainterPath path;
            path.addEllipse(bl, -A/2, A, A);
            painter->fillPath(path, QBrush(greenLight));
        } else {
                    painter->drawArc(QRectF(bl, -A/2, A, A), 90*16, 180*16);
        }
    } else {
        if (highlight) {
            QPainterPath path;
            path.addEllipse(p1, A/2, A/2);
            painter->fillPath(path, QBrush(greenLight));
        } else {
                painter->drawEllipse(p1, A/2, A/2);
        }
    }
    if (dragging) {
            QPen pen;
            //pen.setWidthF(3);
            pen.setStyle(Qt::DotLine);
        if (sticky) {
            pen.setColor(stickyLight);
        }
            if (port->isInput())
                  drawArrow(painter, pen, dragPoint, p1);
            else
                  drawArrow(painter, pen, p1, dragPoint);
    } 
    else if (option->state & QStyle::State_Selected) {
        QPen pen;
        //pen.setWidthF(2);
        pen.setStyle(Qt::DotLine);
        painter->setPen(pen);
        painter->drawRoundedRect(boundingRect(), 30, 30, Qt::RelativeSize);
    }
}

void WorkflowPortItem::mouseMoveEvent( QGraphicsSceneMouseEvent * event ) {
    if (!dragging && !rotating && (event->buttons() & Qt::LeftButton) && !dragPoint.isNull()) {
        //log.debug("port grabbed mouse");
        if ((event->pos().toPoint() - dragPoint.toPoint()).manhattanLength() < 10) return;
        event->accept();
        //grabMouse();
        if (event->modifiers() & portRotationModifier) {
            rotating = true;
            //setCursor(portRotationCursor);
            setCursor(QCursor(QPixmap(":workflow_designer/images/rot_cur.png")));
        } 
        //else 
        {
            dragging = true;
            setCursor(Qt::ClosedHandCursor);
            bindCandidates = getCandidates(this);
            foreach(WorkflowPortItem* it, bindCandidates) {
                it->setHighlight(true);it->update(it->boundingRect());
            }
        }
    } 

    sticky = false;
    if (dragging) {
        rotating = (event->modifiers() & portRotationModifier);
    }

    if (!dragging && !rotating) {
        return;
    }
    event->accept();
    prepareGeometryChange();
    if (rotating) {
        qreal angle = QLineF(owner->pos(), event->scenePos()).angle();
        setOrientation(angle);
    } 
    if (dragging) {
        dragPoint = rotating ? mapFromScene(event->scenePos()) : event->pos();
            WorkflowPortItem* preferable = findNearbyBindingCandidate(event->scenePos());
            if (preferable) {
                  dragPoint = preferable->head(this);
            sticky = true;
            }
    } 
}

void WorkflowPortItem::mousePressEvent ( QGraphicsSceneMouseEvent * event ) {
    dragPoint = QPointF();
    if ((event->buttons() & Qt::LeftButton)) {
        dragPoint = event->pos();
        if (event->modifiers() & portRotationModifier) {
            rotating = true;
            //setCursor(portRotationCursor);
            setCursor(QCursor(QPixmap(":workflow_designer/images/rot_cur.png")));
        } else {
            setCursor(Qt::ClosedHandCursor);
        }
    } 
    else 
    {
        QGraphicsItem::mousePressEvent(event);
    }
}


void WorkflowPortItem::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event ) {
    ungrabMouse();
    unsetCursor();
    QGraphicsItem::mouseReleaseEvent(event);
    rotating = false;
    if (dragging && (event->button() == Qt::LeftButton)) {
        event->accept();
        QList<QGraphicsItem *> li = scene()->items(/*event->scenePos()*/ mapToScene(dragPoint));
        //bool done = false;
        WorkflowPortItem* otherPit = NULL;
        foreach(QGraphicsItem * it, li) {
                  otherPit = checkBindCandidate(it);
            WBusItem* dit;
                  if (otherPit && (dit = tryBind(otherPit))) {
                scene()->clearSelection();
                BusPort* bp = qobject_cast<BusPort*>(dit->getInPort()->getPort());
                if (bp) {
                    bp->setupBusMap();
                }
                dit->getInPort()->setSelected(true);
                        break;
                  }
        }
        prepareGeometryChange();
        dragging = false;dragPoint = QPointF();
        foreach(WorkflowPortItem* it, bindCandidates) {
            it->setHighlight(false);
        }
        scene()->update();
        bindCandidates.clear();
    } 
}

void WorkflowPortItem::hoverEnterEvent ( QGraphicsSceneHoverEvent * event ) {
    setCursor((event->modifiers() & portRotationModifier) ? 
//portRotationCursor 
QCursor(QPixmap(":workflow_designer/images/rot_cur.png"))
: QCursor(Qt::OpenHandCursor));
}
void WorkflowPortItem::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ) {
    Q_UNUSED(event);
    unsetCursor();
}

QVariant WorkflowPortItem::itemChange ( GraphicsItemChange change, const QVariant & value ) {
    if (change == ItemPositionChange || change == ItemTransformChange) {
        foreach(WBusItem* dit, flows) {
            dit->prepareGeometryChange();
        }
    } else if (change == ItemPositionHasChanged || change == ItemTransformHasChanged) {
        foreach(WBusItem* dit, flows) {
            //TODO correct update
            //dit->update(dit->boundingRect());
            dit->updatePos();
        }
    } else if (change == ItemSceneChange && qVariantValue<QGraphicsScene*>(value) == NULL) {
        foreach(WBusItem* dit, flows) {
            scene()->removeItem(dit);
            delete dit;
        }
    }

    return QGraphicsItem::itemChange(change, value);
}

////////////////// Flow //////////////

WBusItem::WBusItem(WorkflowPortItem* p1, WorkflowPortItem* p2) 
{     
    if (p1->getPort()->isInput()) {
        assert(!p2->getPort()->isInput());
        dst = p1;
        src = p2;
    } else {
        assert(p2->getPort()->isInput());
        dst = p2;
        src = p1;
    }
    bus = new Link(p1->getPort(), p2->getPort());

    setAcceptHoverEvents(true);
    setFlag(QGraphicsItem::ItemIsSelectable, true);
    setZValue(-1000);

    QGraphicsTextItem* text = new QGraphicsTextItem(src->getPort()->getDisplayName(), this);
    text->setTextWidth(qMin(3*R, text->document()->idealWidth()));
    QRectF tb = text->boundingRect();
    text->setPos(-tb.width()/2, -tb.height()- 3);
    text->setDefaultTextColor(QColor(Qt::gray).darker());
    QFont f = text->font();
    f.setWeight(QFont::Light);
    text->setFont(f);
    this->text = text;
    connect(dst->getPort(), SIGNAL(bindingChanged()), this, SLOT(sl_update()));
}

WBusItem::~WBusItem()
{
    assert(bus == NULL);
    //delete text;
}


void WBusItem::updatePos() {
//     QPointF p1 = dst->pos();
//     QPointF p2 = src->pos();
    QPointF p1 = dst->headToScene();
    QPointF p2 = src->headToScene();

    setPos((p1.x() + p2.x())/2, (p1.y() + p2.y())/2);
}

QVariant WBusItem::itemChange ( GraphicsItemChange change, const QVariant & value ) {
    if (change == ItemSceneChange && qVariantValue<QGraphicsScene*>(value) == NULL) {
        dst->removeDataFlow(this);
        src->removeDataFlow(this);
        disconnect(dst->getPort(), SIGNAL(bindingChanged()), this, SLOT(sl_update()));
        //dst = src = NULL;
        delete bus; bus = NULL;
    }

    return QGraphicsItem::itemChange(change, value);
}

void WBusItem::hoverEnterEvent ( QGraphicsSceneHoverEvent * event ) {
    Q_UNUSED(event);
    setCursor(QCursor(Qt::PointingHandCursor));
}
void WBusItem::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ) {
    Q_UNUSED(event);
    unsetCursor();
}

QRectF WBusItem::boundingRect(void) const {
    return (mapFromItem(dst, dst->boundingRect()).boundingRect() 
        | mapFromItem(src, src->boundingRect()).boundingRect() 
        | text->boundingRect().translated(text->pos()));
}

QPainterPath WBusItem::shape () const {
      QPainterPath p;
      QPointF p1 = dst->head(this);
      QPointF p2 = src->head(this);
      QLineF direct(p2,p1);
      QLineF n = direct.normalVector();
      n.setLength(A/2);
      p.moveTo(n.p2());
      p.lineTo(n.translated(p1-p2).p2());
      QLineF rn(n.p2(), n.p1());
      rn.translate(n.p1()-n.p2());
      p.lineTo(rn.translated(p1-p2).p2());
      p.lineTo(rn.p2());
      p.closeSubpath();
      p.addRect(text->boundingRect().translated(text->pos()));
      return p;
}

void WBusItem::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) {
    Q_UNUSED(widget);
    painter->setRenderHint(QPainter::Antialiasing);
    QColor baseColor(0x66, 0x66, 0x66);
    painter->setPen(baseColor);
    //painter->fillRect(boundingRect(), QBrush(Qt::blue));
    QPointF p1 = dst->head(this);
    QPointF p2 = src->head(this);

    QPainterPath path;
      path.addEllipse(p2, A/2 - 2, A/2 - 2);
      path.addEllipse(p1, A/2 - 2, A/2 - 2);
      painter->fillPath(path, QBrush(baseColor));

    QPen pen = painter->pen();
    if (option->state & QStyle::State_Selected) {
        pen.setWidthF(1.5);
        pen.setStyle(Qt::DashLine);
    }
    if (!validate()) {
        pen.setColor(Qt::red);
    }
      drawArrow(painter, pen, p2, p1);

    painter->setRenderHint(QPainter::NonCosmeticDefaultPen);
    QColor yc = QColor(Qt::yellow).lighter();yc.setAlpha(127);
    QRectF textRec = text->boundingRect().translated(text->pos());
    painter->fillRect(textRec, QBrush(yc));
    painter->drawRect(textRec);
}

void WBusItem::sl_update() {
    update();
}

bool WBusItem::validate() {
    QStringList lst;
    return dst->getPort()->validate(lst);
}

}//namespace

Generated by  Doxygen 1.6.0   Back to index