tuchulcha  0.10.1
Graphical Workflow Configuration Editor
GraphModel.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2009 Jens-Malte Gottfried
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 */
26 #include "GraphModel.h"
27 #include "QParameterFile.h"
28 #include "MetaData.h"
29 #include <QMessageBox>
30 #include <QtDebug>
31 #include <sstream>
32 #include <set>
33 #include <algorithm>
34 #include "QTextInputDialog.h"
35 #include <stdexcept>
36 #include <QRegExpValidator>
37 
39 
42 inline std::string _toLower(std::string input) {
43  std::transform(
44  input.begin(), input.end(), input.begin(),
45  (int(*)(int)) tolower);
46  return input;
47 }
48 
49 GraphModel::GraphModel(QString fName, QObject* myParent, QString metaFile) :
50  ParameterFileModel(fName, myParent, metaFile) {
51 
52  // here we need some metaFile
53  if (metaFile.isEmpty()) {
54  qFatal("No metaFile in GraphModel given! Pleas specify one!");
55  }
56  if (!useMetaInfo()) {
57  qFatal("MetaInfo not usable!");
58  }
59 
60  connect(
61  this, SIGNAL(dynamicUpdate()),
62  this, SIGNAL(graphChanged()),
63  Qt::QueuedConnection);
64 }
65 
66 void GraphModel::clear(bool draw) {
67  setPrefix("");
69  if(draw)
70  emit graphChanged();
71 }
72 
74  clear(true);
75 }
76 
78  emit graphChanged();
79 }
80 
83  return false;
84  }
85 
86  Q_ASSERT(useMetaInfo());
87 
88  // check which classes are used in the workflow
89  QSet<QString> wClasses;
90  const QStringList& nds = nodes();
91  foreach (const QString& cur, nds) {
92  wClasses << getClass(cur);
93  }
94 
95  // collect unknown classes
96  QStringList uClasses;
97  const QStringList& knownClasses = getClasses();
98  foreach(const QString& s, wClasses) {
99  if (!knownClasses.contains(s,Qt::CaseInsensitive)) {
100  uClasses << s;
101  }
102  }
103 
104  if (!uClasses.isEmpty()) {
105  QMessageBox::warning(
106  0,tr("missing modules"),
107  tr("The following classes are used in the workflow "
108  "but are unknown to Tuchulcha:")
109  +QString("<ul><li>%1</li></ul>").arg(uClasses.join("</li><li>"))
110  +tr("Please check your plugin path settings, "
111  "then update plugin informations "
112  "and look if these plugins are found at all "
113  "or error messages related with tese plugins occur."
114  ));
115  return false;
116  }
117 
118  emit graphChanged();
119  selectNext();
120 
121  return true;
122 }
123 
124 bool GraphModel::nodeValid(const QString& name) const {
125  return !getClass(name).isEmpty();
126 }
127 
129  QString source, QString target) const {
130  // assert that source is an input slot
131  if (!isInputSlot(source)) {
132  qSwap(source,target);
133  }
134  if (!isInputSlot(source)) {
135  throw std::runtime_error(
136  tr("At least one of (%1,%2) has to be an input slot!")
137  .arg(source).arg(target).toStdString());
138  }
139 
140  // check if target is of the corresponding slot type (input <-> output)
141  if (!isOutputSlot(target)) {
142  throw std::runtime_error(
143  tr("%1 has to be an output slot!").arg(target).toStdString());
144  }
145 
146  // check slot types
147  QString inSlotType = getType(target);
148  QString outSlotType = getType(source);
149  if ((inSlotType.toLower() != outSlotType.toLower())&&!(inSlotType.toLower()=="virtual"||outSlotType.toLower()=="virtual"))
150  throw std::runtime_error(
151  tr("Type of \"%1\" (%2) does not match type of \"%3\" (%4)")
152  .arg(target).arg(inSlotType).arg(source).arg(outSlotType)
153  .toStdString());
154 
155  bool established =
156  getValue(source).contains(QRegExp(target,Qt::CaseInsensitive));
157 
158  // check if target node is in input/ouput list of source
159  if (established &&
160  !(getValue(target).contains(QRegExp(source,Qt::CaseInsensitive)))) {
161  throw std::runtime_error(
162  tr("Node %1 missing in List %2!")
163  .arg(source).arg(target).toStdString());
164  }
165 
166  return established;
167 }
168 
169 void GraphModel::loadMetaFile(QString fName) {
170  if(fName.isEmpty())
171  qFatal("Tried to set emtpy metaFile in GraphModel!");
173 }
174 
175 void GraphModel::connectSlot(QString source, QString target, bool draw) {
176  // check for valid connection and return, if this connection already
177  // exists.
178  if (connected(source, target))
179  return;
180 
181  // identify input and output slot
182  if(!isInputSlot(source)) {
183  // swap source and target
184  qSwap(source,target);
185  }
186 
187  // disconnect input slot, if assigned and not multi slot
188  if (!isMultiSlot(source)) {
189  QString val = getValue(source);
190  if (!val.isEmpty())
191  disconnectSlot(source, val, false);
192  }
193 
194  // add target to source
195  QString content = getValue(source);
196  Q_ASSERT(content.indexOf(target,Qt::CaseInsensitive) < 0);
197  QStringList targetList = content.split(";", QString::SkipEmptyParts);
198  // add new target
199  targetList << target;
200  setValue(source, targetList.join(";"));
201 
202  // add source to target
203  content = getValue(target);
204  // this if instead of a strict Q_ASSERT
205  // is a workaround to accept some old (buggy)
206  // parameter files with left-over entries
207  // within the output slot connections
208  if (content.indexOf(source,Qt::CaseInsensitive) < 0) {
209  QStringList sourceList = content.split(";", QString::SkipEmptyParts);
210  // add new target
211  sourceList << source;
212  setValue(target, sourceList.join(";"));
213  }
214 
215  if(draw)
216  emit graphChanged();
217 
218  emit statusMessage(
219  tr("connect slot %1 with %2").arg(source).arg(target));
220 }
221 
222 void GraphModel::disconnectSlot(QString source, QString target, bool draw) {
223  QString content = getValue(source).toLower();
224  QStringList targets = content.split(";", QString::SkipEmptyParts);
225  if (target.isEmpty()) {
226  foreach (const QString& tar, targets) {
227  setValue(source,QString());
228  disconnectSlot(tar,source,false);
229  }
230  }
231  else {
232  int pos = targets.indexOf(target.toLower(),0);
233  if (pos >= 0) {
234  targets.removeAt(pos);
235  setValue(source, targets.join(";"));
236  disconnectSlot(target,source,false);
237  }
238  }
239 
240  if(draw)
241  emit graphChanged();
242 
243  emit statusMessage(
244  target.isEmpty() ?
245  tr("disconnected slot %1 from all targets").arg(source) :
246  tr("disconnected slot %1 from %2").arg(source).arg(target));
247 }
248 
249 void GraphModel::disconnectAllSlots(QString node, bool draw) {
250  node = node.section(".",0,0).toLower(); // get base name
251  foreach (const QString& slot, getInputs(node)) {
252  disconnectSlot(QString("%1.%2").arg(node).arg(slot),QString(),false);
253  }
254  foreach (const QString& slot, getOutputs(node)) {
255  disconnectSlot(QString("%1.%2").arg(node).arg(slot),QString(),false);
256  }
257 
258  if(draw)
259  emit graphChanged();
260 
261  emit statusMessage(tr("disconnected all slots of node %1").arg(node));
262 }
263 
264 void GraphModel::renameNode(QString nodename, bool draw) {
265  nodename = nodename.section(".",0,0).toLower();
266  bool ok;
267  QRegExpValidator validator(QParameterFile::prefixCheck);
268  QString newName = QTextInputDialog::getText(
269  0, tr("rename node"),
270  tr("Enter new name for node \"%1\":").arg(nodename),
271  QLineEdit::Normal, nodename, &ok,0,Qt::ImhNone,
272  &validator);
273  newName = newName.toLower() ;
274  if (ok) {
275  if (newName.compare(nodename,Qt::CaseInsensitive)==0) {
276  // nothing to do
277  return;
278  }
279  if(nodeValid(newName)) {
280  QMessageBox::warning(
281  0, tr("node exists"),
282  tr("A node named <i>%1</i> does already exist.<br/>"
283  "Please choose another name.").arg(newName));
284  return;
285  }
286  if (rename(nodename,newName)) {
287  if(draw) {
288  emit graphChanged();
289  }
290  emit statusMessage(
291  tr("renamed node %1 to %2").arg(nodename).arg(newName));
292  }
293  else {
294  qWarning("%s",
295  tr("renaming failed! (%1 to %2)").arg(nodename).arg(newName)
296  .toLocal8Bit().constData());
297  }
298  }
299 }
300 
301 bool GraphModel::deleteNode(QString nodename, bool draw) {
302  if(nodeValid(nodename) && QMessageBox::question(
303  0, tr("confirm delete"),
304  tr("Do you really want to delete node \"%1\"?").arg(nodename),
305  QMessageBox::No | QMessageBox::Yes, QMessageBox::No)
306  == QMessageBox::Yes) {
307  disconnectAllSlots(nodename, false);
308 
309  // select next node, so that erase just touches the parameter file
310  // which is the most efficient way of deleting values
311  if (QString::compare(nodename,prefix(),Qt::CaseInsensitive) == 0) {
312  selectNext();
313  }
314  if (QString::compare(nodename,prefix(),Qt::CaseInsensitive) == 0) {
315  // still same prefix
316  // this is the case, if only one node left, so use old style code
317  setOnlyParams(false);
318  removeRows(0, rowCount());
319  setOnlyParams(true);
320  setPrefix("");
321  }
322  else {
323  // erase all keys of this node.
324  // since the next code has been selected, all of the
325  // parameters are currently not visible in the model, so
326  // erase can just remove them without data change in the view
327  QStringList toDelete = parameterFile().getKeyList(nodename+".");
328  foreach (const QString& cur, toDelete) {
329  erase(cur);
330  }
331  }
332 
333  if(draw)
334  emit graphChanged();
335 
336  emit statusMessage(tr("delete node %1").arg(nodename));
337  return true;
338  }
339  return false;
340 }
341 
342 
343 QStringList GraphModel::nodes() const {
344  QSet<QString> nodeSet;
345 
346  // detect objects
347  QStringList keys = parameterFile().getKeyList();
348  foreach (const QString& ckey, keys) {
349  nodeSet << ckey.section(".",0,0);
350  }
351 
352  QStringList result;
353  foreach(const QString& node, nodeSet) {
354  if(nodeValid(node))
355  result << node;
356  }
357  result.sort();
358 
359  return result;
360 }
361 
362 void GraphModel::selectNext(bool back) {
363  const QStringList& curNodes = nodes();
364 
365  // skip, if no nodes to select avaiable
366  if (!curNodes.size())
367  return;
368 
369  int pos = 0;
370 
371  if(!prefix().isEmpty() && prefixValid()) {
372  pos = curNodes.indexOf(prefix());
373  Q_ASSERT(pos >= 0);
374  if (back) {
375  pos = (pos - 1 + curNodes.size()) % curNodes.size();
376  }
377  else {
378  pos = (pos + 1) % curNodes.size();
379  }
380  }
381 
382  setPrefix(curNodes[pos]);
383 }
384 
385 QString GraphModel::addNode(QString className, bool draw) {
386  QString info, newName, baseName = className.toLower();
387  // cNameCheck matches input with dots and/or spaces (invalid)
388  // the captured part (cap(1)) is the valid part before the spaces/dots
389  QRegExp cNameCheck("([\\w]+)[\\s\\.]+.*");
390  if (cNameCheck.exactMatch(baseName)) {
391  baseName = cNameCheck.cap(1).toLower();
392  Q_ASSERT(!baseName.isEmpty());
393  }
394  bool retry = false;
395  QRegExpValidator validator(QParameterFile::prefixCheck);
396 
397  // loop until a valid name is found or add node canceled
398  do {
399  retry = false;
400  // generate new unique name based on the class name
401  int nameNr = 0;
402  do {
403  newName = QString("%1%2").arg(baseName).arg(++nameNr);
404  } while (nodeValid(newName));
405 
406  // ask user
407  newName = QTextInputDialog::getText(
408  0, tr("add new node"),
409  info + tr("Enter a name for the new node:"),
410  QLineEdit::Normal, newName,
411  0,0,Qt::ImhNone,&validator).trimmed();
412 
413  if (newName.isEmpty()) {
414  return QString(); // cancel adding node
415  }
416 
417  // convert to lowercase to prevent problems later
418  newName = newName.toLower() ;
419 
420  // check instance name rules
421  if( nodeValid(newName) ) {
422  retry = true;
423  info = tr("This name is already in use.") + "\n"
424  + tr("Please use another name.") + "\n";
425  }
426  if( cNameCheck.exactMatch(newName) ) {
427  retry = true;
428  info = tr("Whitespace and dots in names are not allowed.") + "\n"
429  + tr("Please use a valid name.") + "\n";
430  baseName = cNameCheck.cap(1).toLower();
431  }
432  } while (retry);
433 
434  setValue(newName+".type", className);
435 
436  emit statusMessage(
437  tr("add node %1 of class %2").arg(newName).arg(className));
438  if(draw)
439  emit graphChanged();
440 
441  return newName;
442 }
443 
445  const QModelIndex& ind, const QVariant& value, int role) {
446  QString param = data(index(ind.row(),0)).toString();
447  if (!prefix().isEmpty()) {
448  Q_ASSERT(prefixValid());
449  param = prefix() + "." + param;
450  }
451  QRegExp ttype("(.*\\.)?templatetype",Qt::CaseInsensitive);
452  if (
453  (role == Qt::DisplayRole || role == Qt::EditRole) &&
454  ind.column() == 1 &&
455  value != data(ind) &&
456  ttype.exactMatch(param)) {
457 
458  // disconnect slots on template type change, if neccessary
459  QString node = param.section(".",0,0).toLower();
460  QStringList allSlots;
461  allSlots << getInputs(node) << getOutputs(node);
462  foreach (const QString& slot, allSlots) {
463  QString slotName = QString("%1.%2").arg(node).arg(slot);
464  QString slotTypeR = getType(slotName, true); // tmpltype applied
465  QString slotTypeT = getType(slotName, false); // plain type
466  if (slotTypeR != slotTypeT) {
467  // slot type depends on template type
468  disconnectSlot(slotName, QString(), false);
469  }
470  }
471  emit graphChanged();
472  }
473  return ParameterFileModel::setData(ind, value, role);
474 }
475 
476 bool GraphModel::removeRows(int row, int count,
477  const QModelIndex& parentInd) {
478  QRegExp ttype("(.*\\.)?templatetype",Qt::CaseInsensitive);
479 
480  // Check if template type is about to be removed
481  // and reset it to it's default value before deletion.
482  // This handles proper disconnection on reset.
483  for (int i = row; i < row+count; i++) {
484  QString cur = data(index(row,0)).toString();
485  if (!prefix().isEmpty()) {
486  cur = prefix() + "." + cur;
487  }
488  if (ttype.exactMatch(cur)) {
489  setData(index(row,1),getDefault(cur));
490  }
491  }
492 
493  return ParameterFileModel::removeRows(row, count, parentInd);
494 }
495 
void setOnlyParams(bool value)
Set property _onlyparams.
virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole)
Set item content.
Definition: GraphModel.cpp:444
void statusMessage(const QString &msg)
send status message
void erase(QString parName)
Delete a parameter from the the underlying parameter file.
QStringList nodes() const
Get nodes in current graph.
Definition: GraphModel.cpp:343
virtual QString setPrefix(const QString &prefix)
Change prefix.
bool rename(QString oldPrefix, QString newPrefix)
rename prefix
void disconnectAllSlots(QString node, bool draw=true)
disconnect all slots of given node
Definition: GraphModel.cpp:249
void dynamicUpdate()
inform about dynamic plugin update
QStringList getClasses() const
get classes contained in metadata file
QString getValue(QString parName) const
Get a parameter from the underlying parameter file.
GraphModel(QString fileName="", QObject *parent=0, QString metaFile="")
Constructor initializing the model with the given file.
Definition: GraphModel.cpp:49
void graphChanged()
Initiate graph rebuild and Display refresh.
Implementation of class ParameterFileModel.
Declaration of class GraphModel.
const QParameterFile & parameterFile() const
Get const pointer of ParameterFile.
QString getDefault(QString parName) const
pass to metaInfo, use dynamic metadata if needed
QStringList getInputs(QString objName) const
Get input slots of object.
Declaration of class QParameterFile.
QString prefix() const
Get property _prefix.
void reDraw()
emit graph changed signal to update displays
Definition: GraphModel.cpp:77
QStringList getOutputs(QString objName) const
Get output slots of object.
virtual void loadMetaInfo(const QString &fileName)
load metaFile
QString getClass(QString objName, bool fixCase=false) const
get class of some given object
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
Return number of table rows.
void selectNext(bool back=false)
select next item
Definition: GraphModel.cpp:362
bool isMultiSlot(QString name) const
Check if some slot is a multi slot.
This model serves to provide a model frontend to access a ParameterFile instance. ...
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
Access to item content.
QString addNode(QString className, bool draw=true)
add new node of given kind
Definition: GraphModel.cpp:385
bool prefixValid() const
Check prefix.
void disconnectSlot(QString source, QString target=QString(), bool draw=true)
disconnect slots
Definition: GraphModel.cpp:222
bool connected(QString source, QString target) const
Check if given connection is valid and established.
Definition: GraphModel.cpp:128
bool deleteNode(QString nodename, bool draw=true)
delete node
Definition: GraphModel.cpp:301
bool isOutputSlot(QString name) const
Check if some parameter/slot is an output slot.
void setValue(QString parName, QString value)
Set a parameter in the underlying parameter file.
bool useMetaInfo() const
Get property _useMetadata;.
QStringList getKeyList(QString beginsWith="") const
Look for parameters beginning with a given string.
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
virtual bool _load()
Load data without showing OpenFile Dialog.
bool nodeValid(const QString &name) const
Checks if the given node is a valid object in the ParameterFile.
Definition: GraphModel.cpp:124
virtual void clear()
Clear ParameterFile content.
virtual void clear()
Clear ParameterFile content with redraw.
Definition: GraphModel.cpp:73
QString getType(QString parName, bool applyTmplType=true) const
Get type of some parameter or slot.
virtual bool _load()
Load data without showing OpenFile Dialog.
Definition: GraphModel.cpp:81
Declaration of class QTextInputDialog.
static QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode=QLineEdit::Normal, const QString &text=QString(), bool *ok=0, Qt::WindowFlags flags=0, Qt::InputMethodHints iHints=Qt::ImhNone, QValidator *val=0)
get text
virtual bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex())
Remove data from the model.
std::string _toLower(std::string input)
transform std::string into lowercase
Definition: GraphModel.cpp:42
bool isInputSlot(QString name) const
Check if some parameter/slot is an input slot.
virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole)
Set item content.
virtual void loadMetaFile(QString fileName)
load metaFile
Definition: GraphModel.cpp:169
virtual bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex())
Remove data from the model.
Definition: GraphModel.cpp:476
static const QRegExp prefixCheck
instance name or prefix check regex