PARP Research Group University of Murcia, Spain


src/qvgui/qvdesigner/slate/node.cpp

00001 /*
00002  *      Copyright (C) 2008, 2009. PARP Research Group.
00003  *      <http://perception.inf.um.es>
00004  *      University of Murcia, Spain.
00005  *
00006  *      This file is part of the QVision library.
00007  *
00008  *      QVision is free software: you can redistribute it and/or modify
00009  *      it under the terms of the GNU Lesser General Public License as
00010  *      published by the Free Software Foundation, version 3 of the License.
00011  *
00012  *      QVision is distributed in the hope that it will be useful,
00013  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *      GNU Lesser General Public License for more details.
00016  *
00017  *      You should have received a copy of the GNU Lesser General Public
00018  *      License along with QVision. If not, see <http://www.gnu.org/licenses/>.
00019  */
00020 
00021 #include <QtGui>
00022 
00023 #include "node.h"
00024 #include <QGraphicsScene>
00025 
00026 
00027 Node::Node(QString _name, SlateWindow *wind, QGraphicsItem * parent, QGraphicsScene * scene): QGraphicsItem(parent, scene), name(), type(),
00028         itemProp("Group"), numProp(itemProp.getProperties().size()), window(wind), clickedPoint(-1), externalMarkedPoint(-1), markedPoint(-1)
00029 {
00030     myTextColor = Qt::darkGreen;
00031     myOutlineColor = Qt::darkBlue;
00032     myBackgroundColor = Qt::white;
00033     setFlags(ItemIsMovable | ItemIsSelectable);
00034 
00035         type = "Group";
00036         name = _name;
00037         id = 0;
00038 
00039         CorrectTextChange();
00040         update();
00041 }
00042 
00043 Node::Node(ItemProperties item, QString _name, uint _id, SlateWindow *wind, QGraphicsItem * parent, QGraphicsScene * scene): QGraphicsItem(parent, scene), name(),
00044         type(), itemProp(item), numProp(itemProp.getProperties().size()), window(wind), clickedPoint(-1), externalMarkedPoint(-1), markedPoint(-1)
00045 {
00046     myTextColor = Qt::darkGreen;
00047     myOutlineColor = Qt::darkBlue;
00048     myBackgroundColor = Qt::white;
00049     setFlags(ItemIsMovable | ItemIsSelectable);
00050 
00051         type = itemProp.getType();
00052         name = _name;
00053         id = _id;
00054 
00055         CorrectTextChange();
00056         update();
00057 }
00058 
00059 Node::~Node()
00060 {
00061      foreach (Link *link, getLinks())
00062         delete link;
00063 }
00064 
00065 void Node::addInLink(Link *link)
00066 {
00067     myInLinks.append(link);
00068 }
00069 
00070 void Node::addOutLink(Link *link)
00071 {
00072     myOutLinks.append(link);
00073 }
00074 
00075 void Node::removeLink(Link *link)
00076 {
00077     myInLinks.removeAll(link);
00078         myOutLinks.removeAll(link);
00079 }
00080 
00081 QList<Link *> Node::getLinks() const
00082 {
00083         return (myInLinks + myOutLinks);
00084 }
00085 
00086 QList<Link *> Node::getInLinks() const
00087 {
00088         return myInLinks;
00089 }
00090 
00091 QList<Link *> Node::getOutLinks() const
00092 {
00093         return myOutLinks;
00094 }
00095 
00096 int Node::precursors(QList<Node *> tail)
00097 {
00098         if (myInLinks.isEmpty()) {
00099                 return 0;
00100         }
00101         else if (tail.contains(this)) { // si se ha producido un ciclo
00102                 return 0;
00103         }
00104         else {
00105                 int maxPre = 0;
00106                 tail.append(this);
00107                 foreach(Link *link, myInLinks) {
00108                         if ((link) && (link->fromNode())) {
00109                                 int pre = link->fromNode()->precursors(tail);
00110                                 if (maxPre < pre) maxPre = pre; //actualizo maxPre
00111                         }
00112                 }
00113                 return maxPre + 1;
00114         }
00115 }
00116 
00117 
00118 
00119 void Node::setText(const QString &text)
00120 {
00121     prepareGeometryChange();
00122     myText = text;
00123     update();
00124 }
00125 
00126 QString Node::text() const
00127 {
00128     return myText;
00129 }
00130 
00131 void Node::setTextColor(const QColor &color)
00132 {
00133     myTextColor = color;
00134     update();
00135 }
00136 
00137 QColor Node::textColor() const
00138 {
00139     return myTextColor;
00140 }
00141 
00142 void Node::setOutlineColor(const QColor &color)
00143 {
00144     myOutlineColor = color;
00145     update();
00146 }
00147 
00148 QColor Node::outlineColor() const
00149 {
00150     return myOutlineColor;
00151 }
00152 
00153 void Node::setBackgroundColor(const QColor &color)
00154 {
00155     myBackgroundColor = color;
00156     update();
00157 }
00158 
00159 QColor Node::backgroundColor() const
00160 {
00161     return myBackgroundColor;
00162 }
00163 
00164 QRectF Node::boundingRect() const
00165 {
00166     const int Margin = 1;
00167     return outlineRect().adjusted(-Margin, -Margin, +Margin, +Margin);
00168 }
00169 
00170 QPainterPath Node::shape() const
00171 {
00172     QRectF rect = outlineRect();
00173 
00174     QPainterPath path;
00175     path.addRoundRect(rect, roundness(rect.width()),
00176                       roundness(rect.height()));
00177     return path;
00178 }
00179 
00180 uint Node::getId()
00181 {
00182         return id;
00183 }
00184 
00185 void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * /* widget */)
00186 {
00187     QPen pen(myOutlineColor);
00188     if (option->state & QStyle::State_Selected) {
00189         pen.setStyle(Qt::DotLine);
00190         pen.setWidth(2);
00191     }
00192     painter->setPen(pen);
00193     painter->setBrush(myBackgroundColor);
00194 
00195     QRectF rect = outlineRect();
00196         painter->drawRect(rect);
00197         painter->drawLine((int)rect.left(), (int)(rect.top() + lineSpacing), (int)rect.right(), (int)(rect.top() + lineSpacing));
00198 
00199     painter->setPen(myTextColor);
00200     painter->drawText(rect, Qt::AlignTop | Qt::AlignHCenter, QString("") + QString("(%1) ").arg(getId()) + name);
00201     painter->drawText(rect.adjusted(0.0, lineSpacing, 0.0, 0.0), Qt::AlignTop | Qt::AlignHCenter, myText);
00202 
00203         // y pinta los puntos de unión a ambos lados del texto
00204     painter->setPen(Qt::white);
00205         painter->setBrush(Qt::black);
00206         for (int i = 0; i < numProp; i++)
00207         {
00208                 if (itemProp.isInput(i)) // si tiene enlace de entrada lo pinto
00209                 painter->drawEllipse((int)(rect.left() + lineSpacing*0.25), (int)(rect.top() + lineSpacing*(i + 1.25)), (int)(lineSpacing*0.5), (int)(lineSpacing*0.5));
00210                 if (itemProp.isOutput(i)) // // si tiene enlace de salida lo pinto
00211                         painter->drawEllipse((int)(rect.right() - lineSpacing*0.75), (int)(rect.top() + lineSpacing*(i + 1.25)), (int)(lineSpacing*0.5), (int)(lineSpacing*0.5));
00212         }
00213 
00214         // si uno de los puntos está marcado, lo pinta dependiendo de su validez
00215         if (markedPoint >= 0)
00216         {
00217                 if (markedValidity) painter->setBrush(Qt::green);
00218                 else                            painter->setBrush(Qt::red);
00219 
00220                 if (markedPoint < numProp)
00221                         painter->drawEllipse((int)(rect.left() + lineSpacing*0.25), (int)(rect.top() + lineSpacing*(markedPoint + 1.25)), (int)(lineSpacing*0.5), (int)(lineSpacing*0.5));
00222                 else
00223                         painter->drawEllipse((int)(rect.right() - lineSpacing*0.75), (int)(rect.top() + lineSpacing*(markedPoint - numProp + 1.25)), (int)(lineSpacing*0.5), (int)(lineSpacing*0.5));
00224         }
00225 }
00226 
00227 // devuelve la posición (en cordenadas de la scena) del centro del punto indicado por el índice point, si se sale del rango devuelve la posición del nodo
00228 QPointF Node::scenePointPos(int point) const
00229 {
00230         QRectF rect = outlineRect();
00231 
00232         if (point < 0) return this->scenePos();
00233         if (point < numProp) return mapToScene( QPointF(rect.left() + lineSpacing*0.5, rect.top() + lineSpacing*(point + 1.5)) );
00234         if (point < 2 * numProp) return mapToScene( QPointF(rect.right() - lineSpacing*0.5, rect.top() + lineSpacing*(point-numProp + 1.5)) );
00235         return this->scenePos();
00236 }
00237 
00238 QPointF Node::scenePointPos(QString name, bool in) const
00239 {
00240         return scenePointPos(propPoint(name, in));
00241 }
00242 
00243 
00244 int Node::numProps() const
00245 {
00246         return numProp;
00247 }
00248 
00249 
00250 void Node::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
00251 {
00252     QRectF rect = outlineRect();
00253         QPointF click = mapFromScene(event->scenePos());
00254 
00255         if (click.y() > rect.top() + lineSpacing) {
00256                 window->showProperties(this);
00257         }
00258         else {
00259                 QString text = QInputDialog::getText(event->widget(),
00260                                                         tr("Edit Name"), tr("Enter new name:"),
00261                                                         QLineEdit::Normal, name);
00262                 if (!text.isEmpty()) {
00263                         window->setName(this, text);
00264                 }
00265         }
00266 }
00267 
00268 void Node::setName(QString _name) {
00269         prepareGeometryChange();
00270         name = _name;
00271         CorrectTextChange();
00272         update();
00273 }
00274 
00275 // estas funciones son para conseguir que los puntos se pinten de rojo cunado se pasa sobre ellos
00276 // y que se cree la linea al arrastar y creen los link cuando suelte en una posición correcta
00277 void Node::mousePressEvent(QGraphicsSceneMouseEvent * event)
00278 {
00279         if (!window->isSelected(this)) {
00280                 window->clearSelection();
00281                 setSelected(true);
00282         }
00283 
00284         clickedPoint = pointAt(event->pos());
00285         if (clickedPoint < 0)
00286         {
00287                 // si no coincidía con un punto mueve el item normalmente
00288                 QGraphicsItem::mousePressEvent(event);
00289         }
00290         else
00291         {
00292                 // si coincide con un punto crea una línea indicadora del posible enlace
00293                 line = new QGraphicsLineItem( QLineF(event->scenePos(), event->scenePos()) );
00294                 line->setZValue(1000);
00295                 scene()->addItem(line);
00296         }
00297 }
00298 
00299 void Node::prepareHierarchy()
00300 {
00301         // actualiza a toda la gerarquía de grupos que tiene por encima
00302         QGraphicsItem *ancestor = parentItem();
00303         while (ancestor) {
00304                 if (dynamic_cast<Node *>(ancestor))
00305                         ((Node *)ancestor)->publicPrepareGeometryChange();
00306                 ancestor = ancestor->parentItem();
00307         }
00308 }
00309 
00310 void Node::publicPrepareGeometryChange() {
00311         prepareGeometryChange();
00312 }
00313 
00314 void Node::updateHierarchy()
00315 {
00316         // actualiza a toda la gerarquía de grupos que tiene por encima
00317         QGraphicsItem *ancestor = parentItem();
00318         while (ancestor) {
00319                 ancestor->update();
00320                 if (dynamic_cast<Node *>(ancestor))
00321                         ((Node *)ancestor)->updateLinksPos();
00322                 ancestor = ancestor->parentItem();
00323         }
00324 }
00325 
00326 void Node::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
00327 {
00328         // si no se ha pinchado sobre un punto, muevo el item
00329         if (clickedPoint < 0)
00330         {
00331                 // actualiza a toda la gerarquía de grupos que tiene por encima, por si han de encogerse
00332                 updateHierarchy();
00333 
00334                 QGraphicsItem::mouseMoveEvent(event);
00335 
00336                 // actualiza a toda la gerarquía de grupos que tiene por encima, por si han de ensancharse
00337                 updateHierarchy();
00338         }
00339         else
00340         {
00341                 // desplazo la línea como el mouse, pero un poco (0.5) más corta, ya que sino cuando busquemos el item de justo
00342                 // debajo del cursor nos puede dar la própia línea en vez del que buscamos.
00343                 QLineF shortLine = QLineF(line->line().p1(), event->scenePos());
00344                 shortLine.setLength(shortLine.length() - 0.5);
00345                 line->setLine(shortLine);
00346 
00347                 // si había un punto marcado lo desmarco
00348                 if (externalMarkedPoint >= 0)
00349                 {
00350                         externalMarkedItem->unmarkPoint();
00351                         externalMarkedPoint = -1;
00352                 }
00353 
00354                 // y si está sobre otro punto válido para el enlace, lo marco
00355                 Node *target = dynamic_cast<Node *>(scene()->itemAt(event->scenePos())); // en un futuro tendrá que hacer el cast a Node
00356                 if (target)
00357                 {
00358                         // obtengo el punto concreto a partir de las coordenadas locales al otro item
00359                         int pointPos = target->pointAt(target->mapFromScene(event->scenePos()));
00360                         if (pointPos >= 0)
00361                         { // marco el nuevo punto en función de la validez del enlace que generaría
00362                                 externalMarkedPoint = pointPos;
00363                                 externalMarkedItem = target;
00364                                 externalMarkedItem->markPoint(externalMarkedPoint, isValidLink(this, clickedPoint, target, pointPos));
00365                         }
00366                 }
00367         }
00368 }
00369 
00370 void Node::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
00371 {
00372         // si no se ha arrastrado desde un punto hago un release normal
00373         if (clickedPoint < 0)
00374         {
00375                 QGraphicsItem::mouseReleaseEvent(event);
00376         }
00377         else
00378         {
00379                 // elimino la línea indicadora
00380                 delete(line);
00381 
00382                 // si había un punto marcado lo desmarco
00383                 if (externalMarkedPoint >= 0)
00384                 {
00385                         externalMarkedItem->unmarkPoint();
00386                         externalMarkedPoint = -1;
00387                 }
00388 
00389                 // si está sobre un punto válido creo el enlace
00390                 Node *target = dynamic_cast<Node *>(scene()->itemAt(event->scenePos())); // en un futuro tendrá que hacer el cast a Node
00391                 if (target)
00392                 {
00393                         // obtiene el punto concreto a partir de las coordenadas locales al otro item
00394                         int pointPos = target->pointAt(target->mapFromScene(event->scenePos()));
00395 
00396                         // si es valido crea el enlace entre clickedPoint de this y pointPos de target
00397                         if ( (pointPos >= 0) && isValidLink(this, clickedPoint, target, pointPos) )
00398                         {
00399                                 if (clickedPoint >= numProp) // si clickedPoint es un punto de salida, el enlace parte de this
00400                                         validLinkRelease(this, clickedPoint, target, pointPos);
00401                                 else // si no, parte de target
00402                                         validLinkRelease(target, pointPos, this, clickedPoint);
00403                         }
00404                 }
00405 
00406                 clickedPoint = -1;
00407         }
00408 }
00409 
00410 void Node::validLinkRelease(Node *fromNode, int fromPoint, Node *toNode, int toPoint)
00411 {
00412         window->createLink(fromNode, fromPoint, toNode, toPoint);
00413 }
00414 
00415 void Node::markPoint(int point, bool validity)
00416 {
00417         markedPoint = point;
00418         markedValidity = validity;
00419         update();
00420 }
00421 
00422 void Node::unmarkPoint()
00423 {
00424         markedPoint = -1;
00425         update();
00426 }
00427 
00428 // indica si un enlace sería válido
00430 bool Node::isValidLink(Node *fromNode, int fromPoint, Node *toNode, int toPoint) const
00431 {
00432         int fromNumProp = fromNode->numProp;
00433         int toNumProp = toNode->numProp;
00434 
00435         if (fromNode == toNode) return false; // si son iguales
00436         if (fromNode->parentItem() != toNode->parentItem()) return false; // si no están al mismo nivel
00437         if ( (fromPoint < 0) || (toPoint < 0) ) return false; // si alguno no es un punto
00438         if ( (fromPoint < fromNumProp) && (toPoint < toNumProp) ) return false; // si los dos son de entrada
00439         if ( (fromPoint >= fromNumProp) && (toPoint >= toNumProp) ) return false; // si los dos son de salida
00440         if ( (fromPoint >= 2 * fromNumProp) || (toPoint >= 2 * toNumProp) ) return false; // si alguno no es un punto existente
00441         return true;
00442 }
00443 
00444 int Node::insertPos() const
00445 {
00446         return numProp;
00447 }
00448 
00449 int Node::insertProperty(QString name, int type, bool input, bool output)
00450 {
00451         int pos = insertPos();
00452         insertProperty(pos, name, type, input, output);
00453         return pos;
00454 }
00455 
00456 void Node::insertProperty(int pos, QString name, int type, bool input, bool output)
00457 {
00458         prepareGeometryChange();
00459         itemProp.insertProperty(pos, name, type, input, output);
00460         numProp = itemProp.getProperties().size();
00461 
00462         CorrectTextChange();
00463         update();
00464 }
00465 
00466 void Node::removeProperty(QString name)
00467 {
00468         prepareGeometryChange();
00469         itemProp.deleteProperty(name);
00470         numProp = itemProp.getProperties().size();
00471 
00472         CorrectTextChange();
00473         update();
00474 }
00475 
00476 void Node::deleteProperty(int pos)
00477 {
00478         prepareGeometryChange();
00479         itemProp.deleteProperty(pos);
00480         numProp = itemProp.getProperties().size();
00481 
00482         CorrectTextChange();
00483         update();
00484 }
00485 
00486 void Node::CorrectTextChange()
00487 {
00488         // recalcula el rectángulo exterior
00489     QFontMetricsF metrics = qApp->font();
00490         lineSpacing = metrics.lineSpacing();
00491     outlinerect = metrics.boundingRect(QString("") + QString("(%1) ").arg(getId()) + name);
00492 
00493         const QList<QString> props = itemProp.getProperties();
00494         for (int i = 0; i < props.size(); i++)
00495                 outlinerect |= metrics.boundingRect(props[i]);
00496 
00497     outlinerect.adjust(0.0, 0.0, lineSpacing*4, props.size()*metrics.lineSpacing() /* sin +1, por que ya tiene el alto de una linea */);
00498 
00499 
00500         // recalcula el texto a escribir
00501         QString propsText("");
00502         for (int i = 0; i < props.size(); i++) {
00503                 if (i > 0) propsText += QString("\n");
00504                 propsText += props[i];
00505         }
00506 
00507         setText(propsText);
00508         updateLinksPos();
00509 }
00510 
00511 void Node::delLastProp()
00512 {
00513         prepareGeometryChange();
00514         deleteProperty(numProp - 2);
00515         update();
00516 }
00517 
00518 QVariant Node::itemChange(GraphicsItemChange change,
00519                           const QVariant &value)
00520 {
00521     if (change == ItemPositionHasChanged)
00522                 updateLinksPos();
00523 
00524     return QGraphicsItem::itemChange(change, value);
00525 }
00526 
00527 void Node::updateLinksPos()
00528 {
00529         foreach (Link *link, getLinks())
00530                 link->trackNodes();
00531 }
00532 
00533 QRectF Node::outlineRect() const
00534 {
00535         return outlinerect;
00536 }
00537 
00538 int Node::roundness(double size) const
00539 {
00540     const int Diameter = 12;
00541     return 100 * Diameter / int(size);
00542 }
00543 
00544 // Devuelve el indice del punto de enlace que hay debajo de la posición "pos", en coordenadas del workernode;
00545 // empieza a contar desde 0 y los de los de salida los cuenta a continuación de los de entrada;
00546 // si "pos" no está sobre ningún punto de enlace válido devuelve -1.
00547 int Node::pointAt(QPointF pos) const
00548 {
00549         QRectF rect = outlineRect();
00550         if ((pos.x() >= rect.left() + lineSpacing*0.25) && (pos.x() <= rect.left() + lineSpacing*0.75)) // si está en la columna de entrada
00551         {
00552                 if (pos.y() - rect.top() >= lineSpacing*1.25) // si sestá por debajo del título
00553                 {
00554                         int relativePos = (int)(pos.y() - rect.top() - lineSpacing*1.25);
00555                         if (relativePos % (int)lineSpacing <= lineSpacing*0.5) // si está sobre el lugar del punto
00556                                 if (itemProp.isInput((int)(relativePos / lineSpacing))) return (int)(relativePos / lineSpacing); // si la propiedad tiene enlace de entrada, es valido
00557                 }
00558         }
00559         else if ((pos.x() >= rect.right() - lineSpacing*0.75) && (pos.x() <= rect.right() - lineSpacing*0.25)) // si está en la columna de salida
00560         {
00561                 if (pos.y() - rect.top() >= lineSpacing*1.25) // si está por debajo del título
00562                 {
00563                         int relativePos = (int)(pos.y() - rect.top() - lineSpacing*1.25);
00564                         if (relativePos % (int)lineSpacing <= lineSpacing*0.5) // si está sobre el lugar del punto
00565                                 if (itemProp.isOutput((int)(relativePos / lineSpacing))) return (int)(relativePos / lineSpacing + numProp);  // si la propiedad tiene enlace de salida, es valido
00566                 }
00567         }
00568         return -1;
00569 }
00570 
00571 QString Node::propName(int point) const
00572 {
00573         if ( (point < 0) || (point >= 2 * numProp) ) return QString(); // si point se sale de rango
00574 
00575         if (point >= numProp) point = point - numProp; // ignoro si es de entrada o salida
00576         return itemProp.getProperties()[point];
00577 }
00578 
00579 int Node::propPoint(QString name, bool in) const
00580 {
00581         int pos = itemProp.getProperties().indexOf(name);
00582         if (pos < 0) return pos;
00583         if (!in) return pos += numProp;
00584         return pos;
00585 }
00586 
00587 int Node::propType(int point) const
00588 {
00589         if ( (point < 0) && (point >= 2 * numProp) ) return 0; // si point se sale de rango devuelvo "no type"
00590 
00591         if (point >= numProp) point = point - numProp; // ignoro si es de entrada o salida
00592         return itemProp.propertyType(point);
00593 }
00594 
00595 void Node::setHide(bool hide)
00596 {
00597         foreach(Link *link, getLinks())
00598                 link->setVisible(!hide);
00599 
00600         setVisible(!hide);
00601 }
00602 



QVision framework. PARP research group, copyright 2007, 2008.