]> Creatis software - cpPlugins.git/blobdiff - appli/cpPipelineEditor/Node.cxx
More on graph editor
[cpPlugins.git] / appli / cpPipelineEditor / Node.cxx
index 2ecb9e5b1f8a6e8eaace212844d1fb8efceb1ea9..34c4d96fddc1f314503331b54f1dc7a0d0303fdb 100644 (file)
-/****************************************************************************
-**
-** Copyright (C) 2015 The Qt Company Ltd.
-** Contact: http://www.qt.io/licensing/
-**
-** This file is part of the examples of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:BSD$
-** You may use this file under the terms of the BSD license as follows:
-**
-** "Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions are
-** met:
-**   * Redistributions of source code must retain the above copyright
-**     notice, this list of conditions and the following disclaimer.
-**   * Redistributions in binary form must reproduce the above copyright
-**     notice, this list of conditions and the following disclaimer in
-**     the documentation and/or other materials provided with the
-**     distribution.
-**   * Neither the name of The Qt Company Ltd nor the names of its
-**     contributors may be used to endorse or promote products derived
-**     from this software without specific prior written permission.
-**
-**
-** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QGraphicsScene>
-#include <QGraphicsSceneMouseEvent>
-#include <QPainter>
-#include <QStyleOption>
-
-#include "Edge.h"
 #include "Node.h"
-#include "GraphWidget.h"
+#include "Edge.h"
+#include "GraphCanvas.h"
+
+#include <QFontMetricsF>
+#include <QGraphicsWidget>
+#include <QGraphicsSceneHoverEvent>
 
-Node::Node(GraphWidget *graphWidget, const std::string& label)
-  : graph(graphWidget),
-    m_Label( label )
+#include <cpPlugins/Interface/Object.h>
+#include <cpPlugins/Interface/DataObject.h>
+#include <cpPlugins/Interface/ProcessObject.h>
+
+#define PORT_SIZE 15
+
+// -------------------------------------------------------------------------
+PipelineEditor::Node::
+Node( GraphCanvas* canvas, cpPlugins::Interface::Object* object )
+  : QGraphicsItem( NULL ),
+    m_Canvas( canvas ),
+    m_Object( object ),
+    m_SelectedPort( NULL ),
+    m_DraggingPort( false )
 {
-    setFlag(ItemIsMovable);
-    setFlag(ItemSendsGeometryChanges);
-    setCacheMode(DeviceCoordinateCache);
-    setZValue(-1);
-    this->setToolTip( this->m_Label.c_str( ) );
+  this->setFlag( QGraphicsItem::ItemIsMovable, true );
+  this->setFlag( QGraphicsItem::ItemSendsGeometryChanges, true );
+  this->setCacheMode( QGraphicsItem::DeviceCoordinateCache );
+  this->setAcceptHoverEvents( true );
+  this->setAcceptDrops( true );
+  this->setZValue( -1 );
+  this->updateRepresentation( );
 }
 
-void Node::addEdge(Edge *edge)
+// -------------------------------------------------------------------------
+PipelineEditor::Node::
+~Node( )
 {
-    edgeList << edge;
-    edge->adjust();
 }
 
-QList<Edge *> Node::edges() const
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+addEdge( PipelineEditor::Edge* edge )
 {
-    return edgeList;
+  this->m_Edges << edge;
+  edge->adjust( );
 }
 
-void Node::calculateForces()
+// -------------------------------------------------------------------------
+QList< PipelineEditor::Edge* > PipelineEditor::Node::
+edges( ) const
 {
-    if (!scene() || scene()->mouseGrabberItem() == this) {
-        newPos = pos();
-        return;
-    }
+  return( this->m_Edges );
+}
+
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+updateRepresentation( )
+{
+  typedef cpPlugins::Interface::DataObject    _TData;
+  typedef cpPlugins::Interface::ProcessObject _TFilter;
+
+  if( this->m_Object == NULL )
+    return;
+
+  // Try to infere type
+  _TData* d = dynamic_cast< _TData* >( this->m_Object );
+  _TFilter* f = dynamic_cast< _TFilter* >( this->m_Object );
+  if( d == NULL && f == NULL )
+    return;
+
+  // Label and its bounds
+  QFontMetricsF fm( this->m_Canvas->font( ) );
+  this->m_Label  = this->m_Object->GetName( );
+  this->m_Label += "\n";
+  this->m_Label += this->m_Object->GetClassName( ).c_str( );
+  this->m_Bounds = fm.boundingRect( this->m_Label );
+
+  // Create ports representation
+  this->m_Inputs.clear( );
+  this->m_Outputs.clear( );
+  this->m_InputPorts.clear( );
+  this->m_OutputPorts.clear( );
+  if( f != NULL )
+  {
+    // Get filter's inputs and outputs
+    f->GetInputsNames( this->m_Inputs );
+    f->GetOutputsNames( this->m_Outputs );
 
-    // Sum up all forces pushing this item away
-    qreal xvel = 0;
-    qreal yvel = 0;
-    foreach (QGraphicsItem *item, scene()->items()) {
-        Node *node = qgraphicsitem_cast<Node *>(item);
-        if (!node)
-            continue;
+    // Correct height
+    unsigned int nIn = this->m_Inputs.size( );
+    unsigned int nOut = this->m_Outputs.size( );
+    qreal n =
+      qreal( ( ( ( ( nIn > nOut )? nIn: nOut ) << 1 ) + 1 ) * PORT_SIZE );
+    qreal h = this->m_Bounds.height( );
+    if( n > h )
+      this->m_Bounds.setHeight( n );
 
-        QPointF vec = mapToItem(node, 0, 0);
-        qreal dx = vec.x();
-        qreal dy = vec.y();
-        double l = 2.0 * (dx * dx + dy * dy);
-        if (l > 0) {
-            xvel += (dx * 150.0) / l;
-            yvel += (dy * 150.0) / l;
-        }
-    }
+    // Get bounds values
+    qreal rt = this->m_Bounds.top( ) - qreal( PORT_SIZE );
+    qreal rb = this->m_Bounds.bottom( ) + qreal( PORT_SIZE );
+    qreal rl = this->m_Bounds.left( ) - qreal( PORT_SIZE );
+    qreal rr = this->m_Bounds.right( ) + qreal( PORT_SIZE );
 
-    // Now subtract all forces pulling items together
-    double weight = (edgeList.size() + 1) * 10;
-    foreach (Edge *edge, edgeList) {
-        QPointF vec;
-        if (edge->sourceNode() == this)
-            vec = mapToItem(edge->destNode(), 0, 0);
+    // Add some space to the geometry
+    this->m_Bounds.setTop( rt );
+    this->m_Bounds.setBottom( rb );
+    this->m_Bounds.setLeft( rl );
+    this->m_Bounds.setRight( rr );
+    qreal rh = this->m_Bounds.height( );
+
+    // Create ports
+    QSizeF ps( qreal( PORT_SIZE ), qreal( PORT_SIZE ) );
+    std::set< std::string >* ports[] =
+      { &( this->m_Inputs ), &( this->m_Outputs ) };
+    for( unsigned int pId = 0; pId < 2; ++pId )
+    {
+      qreal h = qreal( ( ( ports[ pId ]->size( ) << 1 ) + 1 ) * PORT_SIZE );
+      qreal off = qreal( PORT_SIZE );
+      if( rh > h )
+        off += ( rh - h ) / qreal( 2 );
+      for( auto i = ports[ pId ]->begin( ); i != ports[ pId ]->end( ); ++i )
+      {
+        if( pId == 0 )
+          this->m_InputPorts[ *i ] =
+            QRectF( QPointF( rl, rt + off ), ps );
         else
-            vec = mapToItem(edge->sourceNode(), 0, 0);
-        xvel -= vec.x() / weight;
-        yvel -= vec.y() / weight;
-    }
+          this->m_OutputPorts[ *i ] =
+            QRectF( QPointF( rr - qreal( PORT_SIZE ), rt + off ), ps );
+        off += qreal( PORT_SIZE < 1 );
+
+      } // rof
 
-    if (qAbs(xvel) < 0.1 && qAbs(yvel) < 0.1)
-        xvel = yvel = 0;
+    } // rof
 
-    QRectF sceneRect = scene()->sceneRect();
-    newPos = pos() + QPointF(xvel, yvel);
-    newPos.setX(qMin(qMax(newPos.x(), sceneRect.left() + 10), sceneRect.right() - 10));
-    newPos.setY(qMin(qMax(newPos.y(), sceneRect.top() + 10), sceneRect.bottom() - 10));
+  } // fi
 
-    newPos = pos(); // + QPointF(xvel, yvel);
+  // Some other initializations
+  this->m_SelectedPort = NULL;
 }
 
-bool Node::advance()
+// -------------------------------------------------------------------------
+QRectF PipelineEditor::Node::
+boundingRect( ) const
 {
-    if (newPos == pos())
-        return false;
+  return( this->m_Bounds );
+}
 
-    setPos(newPos);
-    return true;
+// -------------------------------------------------------------------------
+QPainterPath PipelineEditor::Node::
+shape( ) const
+{
+  QPainterPath path;
+  path.addRect( this->m_Bounds );
+  return( path );
 }
 
-QRectF Node::boundingRect() const
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+paint(
+  QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget
+  )
 {
-#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5)
-    // Add some extra space around the circle for easier touching with finger
-    qreal adjust = 30;
-    return QRectF( -10 - adjust, -10 - adjust,
-                  20 + adjust * 2, 20 + adjust * 2);
-#else
-    qreal adjust = 2;
-    return QRectF( -10 - adjust, -10 - adjust,
-                  23 + adjust, 23 + adjust);
-#endif
+  // Draw main box
+  QRectF rect = this->boundingRect( );
+  painter->drawRect( rect );
+  painter->drawText( rect, Qt::AlignCenter, this->m_Label );
+
+  // Draw ports
+  std::map< std::string, QRectF >* ports[] =
+    { &( this->m_InputPorts ), &( this->m_OutputPorts ) };
+  for( unsigned int pId = 0; pId < 2; ++pId )
+    for( auto i = ports[ pId ]->begin( ); i != ports[ pId ]->end( ); ++i )
+      painter->drawRect( i->second );
+
+  // Draw clicked port
+  if( this->m_SelectedPort != NULL )
+  {
+    painter->setBrush( Qt::green );
+    painter->drawEllipse( *( this->m_SelectedPort ) );
+
+  } // fi
 }
 
-QPainterPath Node::shape() const
+// -------------------------------------------------------------------------
+QVariant PipelineEditor::Node::
+itemChange( GraphicsItemChange change, const QVariant& value )
 {
-    QPainterPath path;
-#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5)
-    // Add some extra space around the circle for easier touching with finger
-    path.addEllipse( -40, -40, 80, 80);
-#else
-    path.addEllipse(-10, -10, 20, 20);
-#endif
-    return path;
+  switch( change )
+  {
+  case QGraphicsItem::ItemPositionHasChanged:
+    foreach( Edge* edge, this->m_Edges )
+      edge->update( );
+    this->m_Canvas->itemMoved( );
+    break;
+  default:
+    break;
+  } // hctiws
+  return( this->QGraphicsItem::itemChange( change, value ) );
 }
 
-void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+mousePressEvent( QGraphicsSceneMouseEvent* event )
 {
-    painter->setPen(Qt::NoPen);
-    painter->setBrush(Qt::darkGray);
-    painter->drawEllipse(-7, -7, 20, 20);
+  if( this->m_SelectedPort != NULL && this->m_Canvas != NULL )
+  {
+    if( event->button( ) == Qt::LeftButton )
+    {
+      QDrag* drag = new QDrag( this->m_Canvas );
+      QMimeData* mimeData = new QMimeData( );
 
-    QPointF text_point = this->boundingRect( ).bottomRight( ) + this->pos( );
-    painter->drawText( text_point, this->m_Label.c_str( ) );
-    std::cout << text_point.x( ) << " " << text_point.y( ) << std::endl;
+      // mimeData->setText( "drag_data" );
+      qulonglong address = reinterpret_cast< qulonglong >( this );
+      QByteArray ba;
+      ba.setNum( address );
+      mimeData->setData( "source_node", ba );
+      drag->setMimeData( mimeData );
+      // TODO: drag->setPixmap( iconPixmap );
 
-    QRadialGradient gradient(-3, -3, 10);
-    if (option->state & QStyle::State_Sunken) {
-        gradient.setCenter(3, 3);
-        gradient.setFocalPoint(3, 3);
-        gradient.setColorAt(1, QColor(Qt::yellow).light(120));
-        gradient.setColorAt(0, QColor(Qt::darkYellow).light(120));
-    } else {
-        gradient.setColorAt(0, Qt::yellow);
-        gradient.setColorAt(1, Qt::darkYellow);
-    }
-    painter->setBrush(gradient);
+      this->m_DraggingPort = true;
+      Qt::DropAction dropAction = drag->exec( );
+      this->m_DraggingPort = false;
 
-    painter->setPen(QPen(Qt::black, 0));
-    painter->drawEllipse(-10, -10, 20, 20);
+    } // fi
+  }
+  else
+  {
+  } // fi
+
+  /* TODO
+     Qt::MouseButton btn = event->button( );
+     if( btn == Qt::LeftButton )
+     {
+     std::string name = this->toolTip( ).toStdString( );
+     if( name != this->m_Object->GetName( ) )
+     {
+     // Get clicked port, if any
+     QPointF pos = event->buttonDownPos( btn );
+     auto iIt = this->m_InputPorts.find( name );
+     auto oIt = this->m_OutputPorts.find( name );
+     this->m_SelectedPort = NULL;
+     if( iIt != this->m_InputPorts.end( ) )
+     if( iIt->second.contains( pos ) )
+     this->m_SelectedPort = &( iIt->second );
+      if( this->m_SelectedPort == NULL && oIt != this->m_OutputPorts.end( ) )
+      if( oIt->second.contains( pos ) )
+      this->m_SelectedPort = &( oIt->second );
+
+      } // fi
+
+      } // fi
+  */
+  this->update( );
+  this->QGraphicsItem::mousePressEvent( event );
 }
 
-QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value)
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
 {
-    switch (change) {
-    case ItemPositionHasChanged:
-        foreach (Edge *edge, edgeList)
-            edge->adjust();
-        graph->itemMoved();
-        break;
-    default:
-        break;
-    };
+  this->update( );
+  this->QGraphicsItem::mouseReleaseEvent( event );
+}
 
-    return QGraphicsItem::itemChange(change, value);
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event )
+{
 }
 
-void Node::mousePressEvent(QGraphicsSceneMouseEvent *event)
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+hoverMoveEvent( QGraphicsSceneHoverEvent* event )
 {
-    update();
-    QGraphicsItem::mousePressEvent(event);
+  this->_selectPort( event->pos( ) );
 }
 
-void Node::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+hoverLeaveEvent( QGraphicsSceneHoverEvent* event )
 {
-    update();
-    QGraphicsItem::mouseReleaseEvent(event);
+  this->_deselectPort( );
+}
+
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+dragMoveEvent( QGraphicsSceneDragDropEvent* event )
+{
+  this->_selectPort( event->pos( ) );
+}
+
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+dragLeaveEvent( QGraphicsSceneDragDropEvent* event )
+{
+  this->_deselectPort( );
+}
+
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+dropEvent( QGraphicsSceneDragDropEvent* event )
+{
+  // Get vertices and directionality
+  bool ok;
+  qulonglong address =
+    event->mimeData( )->data( "source_node" ).toULongLong( &ok );
+  Node* src = reinterpret_cast< Node* >( address );
+  if( src == NULL )
+    return;
+  Node* des = this;
+  if( src->m_SelectedPortIsInput )
+  {
+    des = src;
+    src = this;
+
+  } // fi
+
+  // Discard if a loop is detected
+  if( src == des )
+    return;
+
+  // Get edge data
+  const QRectF* srcPort = src->m_SelectedPort;
+  const QRectF* desPort = des->m_SelectedPort;
+  std::string srcName = src->m_Object->GetName( );
+  std::string desName = des->m_Object->GetName( );
+  std::string srcPortName = src->toolTip( ).toStdString( );
+  std::string desPortName = des->toolTip( ).toStdString( );
+
+  Edge* e = new Edge( src, des, srcPort, desPort );
+  src->addEdge( e );
+  des->addEdge( e );
+  if( this->m_Canvas != NULL )
+    this->m_Canvas->scene( )->addItem( e );
+}
+
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+_selectPort( const QPointF& pos )
+{
+  if( this->m_DraggingPort )
+    return;
+
+  const QRectF* prevPort = this->m_SelectedPort;
+
+  // Check ports
+  std::map< std::string, QRectF >* ports[] =
+    { &( this->m_InputPorts ), &( this->m_OutputPorts ) };
+  bool found = false;
+  for( unsigned int pId = 0; pId < 2 && !found; ++pId )
+  {
+    for(
+      auto i = ports[ pId ]->begin( );
+      i != ports[ pId ]->end( ) && !found;
+      ++i
+      )
+    {
+      if( i->second.contains( pos ) )
+      {
+        this->setToolTip( i->first.c_str( ) );
+        this->m_SelectedPort = &( i->second );
+        this->m_SelectedPortIsInput = ( pId == 0 );
+        found = true;
+
+      } // fi
+
+    } // rof
+
+  } // rof
+  if( !found )
+  {
+    this->setToolTip( this->m_Object->GetName( ) );
+    this->m_SelectedPort = NULL;
+
+  } // fi
+  if( prevPort != this->m_SelectedPort )
+    this->update( );
+}
+
+// -------------------------------------------------------------------------
+void PipelineEditor::Node::
+_deselectPort( )
+{
+  if( !( this->m_DraggingPort ) )
+  {
+    this->m_SelectedPort = NULL;
+    this->update( );
+
+  } // fi
 }
 
+// eof - $RCSfile$