tuchulcha  0.10.1
Graphical Workflow Configuration Editor
NodeHandler.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2011 Jonathan Wuest
2 
3  This file is part of Tuchulcha.
4 
5  Tuchulcha is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  Tuchulcha is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public License
16  along with Tuchulcha. If not, see <http://www.gnu.org/licenses/>.
17 */
24 #include "Node.h"
25 
26 #include <QFileDialog>
27 #include <QMessageBox>
28 #include <QGraphicsView>
29 #include <QStandardItemModel>
30 #include <QKeyEvent>
31 #include <QPrinter>
32 #include <QMenu>
33 #include <QMimeData>
34 
35 #include "ConnectionLine.h"
36 #include "NodeHandler.h"
37 #include "GraphModel.h"
38 #include "MetaData.h"
39 #include "FileManager.h"
40 #include "QParameterFile.h"
41 #include <stdexcept>
42 
44  QGraphicsScene(pp),
45  _cline(0),
46  _model(mm),
47  _addLine(false)
48 {
49  Q_ASSERT(_model);
50  connect(_model, SIGNAL(graphChanged()), this, SLOT(loadFromModel()));
51  connect(_model, SIGNAL(prefixChanged(QString)),
52  this, SLOT(selectNode(QString)));
53  loadFromModel();
54 }
55 
57 {
58  delete _cline ;
59 }
60 
62  QMapIterator<QString,Node*> iter(_nodeMap);
63  while(iter.hasNext()) {
64  Node* n = iter.next().value();
65  n->setSelectedNode(false);
66  }
67  _selectedNode = 0;
68 }
69 
70 void NodeHandler::selectNode(QString name) {
71  if(_selectedNode) {
72  if (_selectedNode->getInstanceName() == name) {
73  return;
74  }
76  }
77  if (_nodeMap.contains(name)) {
78  _selectedNode = _nodeMap.value(name);
80  }
81  update();
82 }
84  QString oldPrefix = _model->prefix();
85  QMapIterator<QString,Node*> iter(_nodeMap);
86  while(iter.hasNext()) {
87  Node* m = iter.next().value();
88  if (m){
90  m->setActive(_model->active());
91  }
92  }
93  _model->setPrefix(oldPrefix);
94 }
95 
96 void NodeHandler::mousePressEvent(QGraphicsSceneMouseEvent* ev) {
97  QGraphicsScene::mousePressEvent(ev);
98  if (ev->button() == Qt::LeftButton) {
99  QGraphicsItem* itm = itemAt(ev->scenePos(),QTransform());
100  if (!itm) {
101  return;
102  }
103  Node* np = dynamic_cast<Node*>(itm);
104  if (!np) {
105  // if a node property was clicked, use it's parent (node)
106  np = dynamic_cast<Node*>(itm->parentItem());
107  }
108  if (np) {
110  emit nodeTypeSelected(np->getClassName()) ;
111  }
112  NodeProperty* prop = dynamic_cast<NodeProperty*>(itm);
113  if (prop != 0) {
114  _startProp = prop;
115  delete _cline ; _cline = 0 ;
116  _cline = new ConnectionLine();
117  addItem(_cline);
118  QPointF sckPos(prop->pos()+prop->getSocketCenter());
119  QPointF curPos(ev->scenePos().x(),ev->scenePos().y());
120  const bool isIn = prop->isInput();
121  _cline->setStartPoint(isIn ? curPos : sckPos);
122  _cline->setEndPoint (isIn ? sckPos : curPos);
123  _addLine = true;
124  update(_cline->boundingRect());
125  }
126  }
127  setNodeActive();
128 }
129 
130 void NodeHandler::mouseMoveEvent(QGraphicsSceneMouseEvent* ev) {
131  if (_addLine) {
132  if(_startProp->isInput()) {
133  _cline->setStartPoint(ev->scenePos().x(),ev->scenePos().y());
134  } else {
135  _cline->setEndPoint(ev->scenePos().x(),ev->scenePos().y());
136  }
137  }
138  else {
139  QGraphicsScene::mouseMoveEvent(ev);
140  }
141  update();
142 }
143 
144 void NodeHandler::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev) {
145  QGraphicsScene::mouseReleaseEvent(ev);
146  NodeProperty* prop = dynamic_cast<NodeProperty*>(itemAt(ev->scenePos(),QTransform()));
147 
148  delete _cline;
149  _cline = 0;
150 
151  if (_addLine && prop && (prop != _startProp)) {
152  try {
155  prop->getFullName(),true);
156  } catch (const std::runtime_error& err) {
157  emit statusMessage(err.what());
158  }
159  }
160  _addLine = false;
161 }
162 
163 
165  return _model;
166 }
167 
168 bool NodeHandler::load(QString fname) {
169  return _model->load(fname);
170 }
171 
173  clear();
174  _nodeMap.clear();
176  QStringList nodes = _model->nodes();
177  QStringList nodesout,nodesin;
178  QStringList slotout,slotin;
179 
180  for (int ii=0;ii<nodes.size();ii++) {
181  QString name = nodes[ii];
182  QString cname = _model->getClass(nodes[ii], true);
183 
184  Node* node = new Node(_model,name,10*ii,10*ii);
185  node->setClassName(cname);
186  addItem(node);
187  _nodeMap.insert(name,node);
188 
189  QStringList pdata = _model->getValue(name+".editorinfo").split(" ");
190  if (pdata.size() >= 2) {
191  float x = pdata.at(0).toFloat();
192  float y = pdata.at(1).toFloat();
193  node->setPos(x,y);
194  }
195 
196  // set tooltip
197  QString str = _model->getValue(name+".editorcomment");
198  if (str.isEmpty()) {
199  node->setToolTip("");
200  } else {
201  node->setToolTip("<b>" + tr("Comment:") + "</b><br/>" + str);
202  }
203 
204  QStringList ins = _model->getInputs(name);
205  QStringList outs = _model->getOutputs(name);
206  QStringList dins = _model->getInputDisplayNames(name);
207  QStringList douts = _model->getOutputDisplayNames(name);
208  for (int jj=0; jj < ins.size(); jj++) {
209  node->addProperty(ins[jj],dins[jj], true);
210  }
211  for (int jj=0; jj < outs.size(); jj++) {
212  node->addProperty(outs[jj],douts[jj], false);
213  }
214  for (int jj=0; jj < outs.size(); jj++) {
215  QString curO = name+"."+outs[jj];
216  QStringList conns =
217  _model->getValue(curO).split(";",QString::SkipEmptyParts);
218  foreach (const QString& con, conns) {
219  nodesout << name;
220  slotout << outs[jj];
221  slotin << con.section(".",-1,-1);
222  nodesin << con.section(".",0,0);
223  }
224  }
225  node->moveBy(0,0);
226  }
227  for (int ii=0; ii<slotout.size(); ii++) {
228  try {
229  connectNodes(nodesout[ii],slotout[ii],nodesin[ii],slotin[ii]);
230  }
231  catch(const std::exception& e) {
232  if (QMessageBox::question(
233  0, "connection error",
234  QString(
235  "Failed to connect <em>%1.%2</em> "
236  "to <em>%3.%4</em>.<br>"
237  "This may happen after slot renaming<br>"
238  "and/or using old module descriptions.<br><br>"
239  "Exception message:<br>%5<br><br>"
240  "Remove this invalid connection from workflow?")
241  .arg(nodesout[ii]).arg(slotout[ii])
242  .arg(nodesin[ii]).arg(slotin[ii])
243  .arg(e.what()), QMessageBox::Yes, QMessageBox::No)
244  == QMessageBox::Yes) {
246  nodesout[ii]+"."+slotout[ii],nodesin[ii]+"."+slotin[ii]);
247  }
248  }
249  }
250  update();
252  setNodeActive();
253 }
254 
256  QString node0, QString prop0, QString node1, QString prop1) {
257  Node* out = _nodeMap[node0];
258  Node* in = _nodeMap[node1];
259  Q_ASSERT(out && in) ;
260  NodeProperty* outp = out->getProperty(prop0.toLower());
261  NodeProperty* inp = in->getProperty(prop1.toLower());
262 
263  if(!out) {
264  throw std::runtime_error(QString(
265  "Property <em>%1.%2</em> does not exist.")
266  .arg(node0).arg(prop0).toStdString());
267  }
268  if (!inp) {
269  throw std::runtime_error(QString(
270  "Property <em>%1.%2</em> does not exist.")
271  .arg(node1).arg(prop1).toStdString());
272  }
273 
274  Q_ASSERT(outp && inp) ;
275 
277  l->setStartEndProp(outp,inp);
278  outp->addConnection(l);
279  inp->addConnection(l);
280  outp->moveBy(0,0);
281  inp->moveBy(0,0);
282  addItem(l);
283 }
284 
286  QString fname = QFileDialog::getSaveFileName(
287  0, tr("Select File to Write"),
288  "",tr("Images (*.png *.jpg *.bmp *.gif);;"
289  "PDF/Postscript (*.pdf *.ps)"));
290  if (fname.contains(QRegExp("\\.(pdf|ps)\\s*$",Qt::CaseInsensitive))) {
291  QPrinter printer(QPrinter::HighResolution);
292  printer.setPaperSize(sceneRect().size(),QPrinter::Point);
293  printer.setOutputFileName(fname);
294  printer.setFullPage(true);
295  QPainter painter;
296  painter.begin(&printer);
297  render(&painter);
298  painter.end();
299  }
300  else if (!fname.isEmpty()) {
301  QPixmap pixmap(sceneRect().size().toSize());
302  pixmap.fill(Qt::white);
303  QPainter painter(&pixmap);
304  painter.setRenderHint(QPainter::Antialiasing);
305  render(&painter);
306  if(!pixmap.save(fname)) {
307  QMessageBox::warning(
308  0,tr("error writing file"),
309  tr("failed to save the workflow visualization to<br>"
310  "<tt>%1</tt>"
311  "Perhaps the image file format is not supported.")
312  .arg(fname));
313  }
314  }
315 }
316 
317 void NodeHandler::keyPressEvent(QKeyEvent* keyEvent) {
318  switch(keyEvent->key()) {
319  case Qt::Key_Right:
320  _model->selectNext(false);
321  break;
322 
323  case Qt::Key_Left:
324  _model->selectNext(true);
325  break;
326 
327  case Qt::Key_F2:
328  if (_model && !_model->prefix().isEmpty() && _model->prefixValid()) {
330  }
331  break;
332 
333  case Qt::Key_Delete:
334  if (_model && !_model->prefix().isEmpty() && _model->prefixValid()) {
336  }
337  break;
338 
339  case Qt::Key_Escape:
340  if (_model) {
341  _model->setPrefix(QString());
342  }
343 
344  default:
345  break;
346  }
347 
348  QGraphicsScene::keyPressEvent(keyEvent);
349 }
350 
351 void NodeHandler::dragEnterEvent(QGraphicsSceneDragDropEvent* ev) {
352  if (ev->mimeData()->hasFormat("application/x-qstandarditemmodeldatalist"))
353  ev->accept();
354  else
355  QGraphicsScene::dragEnterEvent(ev);
356 }
357 
358 void NodeHandler::dragMoveEvent(QGraphicsSceneDragDropEvent* ev) {
359  if (ev->mimeData()->hasFormat("application/x-qstandarditemmodeldatalist"))
360  ev->accept();
361  else
362  QGraphicsScene::dragMoveEvent(ev);
363 }
364 
365 void NodeHandler::dropEvent(QGraphicsSceneDragDropEvent* ev) {
366  QStandardItemModel m;
367  bool res = m.dropMimeData(ev->mimeData(),Qt::CopyAction,0,0,QModelIndex());
368  if(res && m.rowCount() == 1 && m.columnCount() == 1) {
369  QString className = m.item(0)->text();
370  _selectedNode = 0;
371  QString instName = _model->addNode(className,false);
372 
373  // if plugin instanziation was successfull,
374  // set its coordinates to the drop position
375  if(!instName.isEmpty()) {
376  _model->setPrefix(instName);
377  // classic DropEvent->pos is always zero for this case
378  QPointF pos = ev->scenePos();
379  QString posString = QString("%0 %1").arg(pos.x()).arg(pos.y());
380  // insert editor info
381  _model->setValue(instName+".editorinfo", posString);
382  loadFromModel();
384  ev->accept();
385  return ;
386  }
387  }
388 
389  QGraphicsScene::dropEvent(ev);
390 }
391 
392 void NodeHandler::contextMenuEvent(QGraphicsSceneContextMenuEvent* ev) {
393  QString nodeName, propName;
394 
395  // try to determine node and/or slot name
396  QGraphicsItem* item = itemAt(ev->scenePos(),QTransform());
397  if (item) {
398  Node* node = dynamic_cast<Node*>(item);
399  NodeProperty* prop = dynamic_cast<NodeProperty*>(item);
400 
401  if (prop) {
402  node = dynamic_cast<Node*>(prop->parentItem());
403  propName = prop->getFullName();
404  }
405  if (node) {
406  nodeName = node->getInstanceName();
407  }
408  }
409 
410  if (nodeName.isEmpty()) {
411  QGraphicsScene::contextMenuEvent(ev);
412  }
413  else {
414  // node and prop may not be used after this point because model
415  // changes let them disappear (disconnect/rename/etc.)
416  ev->accept();
417 
418  // node context menu
419  QMenu menu;
420  QIcon disIco(":/icons/disconnect.png");
421  QIcon renIco(":/icons/rename.png");
422  QIcon delIco(":/icons/delete.png");
423 
424  // if context menu was called on a slot, provide option
425  // concerning this slot at a prominent position (top)
426  if (!propName.isEmpty()) {
427  QAction* act = menu.addAction(
428  disIco, tr("disconnect %1").arg(propName.section(".",1)));
429  act->setData(propName);
430  menu.addSeparator();
431  }
432 
433  // common options
434  QAction* delAct = menu.addAction(delIco, tr("delete"));
435  QAction* renAct = menu.addAction(renIco, tr("rename"));
436  QAction* disAct = menu.addAction(disIco, tr("disconnect all slots"));
437 
438  // submenu for slot disconnection (all slots are provided)
439  QStringList slotNames;
440  slotNames << _model->getInputs(nodeName)
441  << _model->getOutputs(nodeName);
442 
443  QMenu* dmenu = menu.addMenu(disIco, tr("disconnect slot"));
444  foreach (const QString& cur, slotNames) {
445  QAction* curAct =
446  dmenu->addAction(disIco, cur);
447  curAct->setData(QString("%1.%2").arg(nodeName).arg(cur));
448  }
449 
450  // handle user selection
451  QAction* selAct = menu.exec(ev->screenPos());
452  if (selAct) {
453  QString data = selAct->data().toString();
454  if (!data.isEmpty()) {
455  Q_ASSERT(data.contains(QRegExp("^[\\w_-]+\\.[\\w_-]+$")));
456  _model->disconnectSlot(data);
457  }
458  else if (selAct == delAct) {
459  _model->deleteNode(nodeName);
460  }
461  else if (selAct == renAct) {
462  _model->renameNode(nodeName);
463  }
464  else if (selAct == disAct) {
465  _model->disconnectAllSlots(nodeName);
466  }
467  }
468  }
469 }
470 
471 void NodeHandler::updateTooltip (QString comment) {
472  // to prevent exceptions, ensure that _selectedNode is not empty
473  if (_selectedNode && !_nodeMap.isEmpty()) {
474  if (comment.isEmpty()) {
475  _selectedNode->setToolTip("");
476  } else {
477  _selectedNode->setToolTip(
478  "<b>" + tr("Comment:") + "</b><br/>" + comment);
479  }
480  }
481 }
void setSelectedNode(bool s)
sets the node selected or not
Definition: Node.cpp:94
Declaration of class FileManager.
This model wraps a ParameterFile instance and provides access to the data interpreted as a (directed)...
Definition: GraphModel.h:32
void setNodeActive()
sets the nodes active or inactive
Definition: NodeHandler.cpp:83
QStringList nodes() const
Get nodes in current graph.
Definition: GraphModel.cpp:343
void _deselectAllNodes()
deselects all nodes
Definition: NodeHandler.cpp:61
QGraphicsItem that contains nodeproperties and draws the node.
Definition: Node.h:36
virtual QString setPrefix(const QString &prefix)
Change prefix.
void selectNode(QString name)
select some node
Definition: NodeHandler.cpp:70
void disconnectAllSlots(QString node, bool draw=true)
disconnect all slots of given node
Definition: GraphModel.cpp:249
QString getValue(QString parName) const
Get a parameter from the underlying parameter file.
Declaration of class Node.
Node * _selectedNode
pointer to currently selected node (zero if none selected)
Definition: NodeHandler.h:135
void loadFromModel()
loads the scene from the set GraphModel
Implementation of class ParameterFileModel.
Declaration of class GraphModel.
void addProperty(QString name, bool input)
adds a property to the node
Definition: Node.cpp:70
virtual void keyPressEvent(QKeyEvent *keyEvent)
handles key press events
QPointF getSocketCenter() const
socket center including node position
QStringList getInputs(QString objName) const
Get input slots of object.
void setClassName(QString modname)
set node class name
Definition: Node.cpp:183
Declaration of class QParameterFile.
QString prefix() const
Get property _prefix.
void updateTooltip(QString comment)
update tooltip of selected node
QStringList getOutputs(QString objName) const
Get output slots of object.
QStringList getOutputDisplayNames(QString objName) const
Get output slots of object.
QRectF boundingRect() const
bounding Rect, always 0 to be not selectable
void addConnection(ConnectionLine *cl)
add a connectionline to the property
QString getClass(QString objName, bool fixCase=false) const
get class of some given object
void selectNext(bool back=false)
select next item
Definition: GraphModel.cpp:362
ConnectionLine * _cline
buffered connection line for slot connection drawing
Definition: NodeHandler.h:129
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
handles mouse movement
QStringList getInputDisplayNames(QString objName) const
Get input slots of object.
bool isInput() const
returns the PropType of the property
NodeHandler(GraphModel *model, QObject *parent=0)
default constructor
Definition: NodeHandler.cpp:43
QString getClassName() const
get class name
Definition: Node.cpp:188
NodeProperty * getProperty(QString propName) const
get node property
Definition: Node.cpp:179
QString addNode(QString className, bool draw=true)
add new node of given kind
Definition: GraphModel.cpp:385
QString getFullName() const
property name including node instance name
void setStartPoint(int x, int y)
sets start point (for drawing)
Declaration of class ConnectionLine.
GraphModel * _model
current set GraphModel
Definition: NodeHandler.h:138
bool prefixValid() const
Check prefix.
void moveBy(qreal dx, qreal dy)
moves the property and if connected all connectionlines
void disconnectSlot(QString source, QString target=QString(), bool draw=true)
disconnect slots
Definition: GraphModel.cpp:222
GraphModel * model()
get the current GraphModel
Line that connects two slots of two different nodes.
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
handles all mousebutton release events
bool deleteNode(QString nodename, bool draw=true)
delete node
Definition: GraphModel.cpp:301
virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
context menu
QMap< QString, Node * > _nodeMap
map for node lookup
Definition: NodeHandler.h:144
void nodeTypeSelected(QString type) const
Send type of currently selected node.
virtual bool load(const QString &fName="")
Load model content from parameterFile.
bool active() const
Returns the bool value of the Active Parameter.
bool _addLine
state of slot connection mode
Definition: NodeHandler.h:141
Property(/Parameter) of a node.
Definition: NodeProperty.h:35
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)
handles mouse press events
Definition: NodeHandler.cpp:96
void saveFlowchart()
save flowchart to file
bool load(QString fname)
load the GraphModel from the given filename and calls loadFromModel to fill the scene ...
void setActive(bool activeStatus)
changes the nodes activity
Definition: Node.cpp:57
QString getInstanceName()
get node name
Definition: Node.cpp:66
void setValue(QString parName, QString value)
Set a parameter in the underlying parameter file.
NodeProperty * _startProp
buffer that stores the slot connection start property
Definition: NodeHandler.h:132
Declaration of class NodeHandler.
void connectSlot(QString source, QString target, bool draw=true)
connect slots
Definition: GraphModel.cpp:175
void renameNode(QString nodename, bool draw=true)
rename node
Definition: GraphModel.cpp:264
void statusMessage(QString msg) const
message to display on status bar
void setStartEndProp(NodeProperty *start, NodeProperty *end)
sets start and end property
virtual ~NodeHandler()
default destructor
Definition: NodeHandler.cpp:56
void connectNodes(QString node0, QString prop0, QString node1, QString prop1)
connects two node with its slots: node0.prop0 to node1.prop1
void setEndPoint(int x, int y)
sets end point (for drawing)