Developer Documentation
context.cc
1/*===========================================================================*\
2* *
3* OpenFlipper *
4 * Copyright (c) 2001-2015, RWTH-Aachen University *
5 * Department of Computer Graphics and Multimedia *
6 * All rights reserved. *
7 * www.openflipper.org *
8 * *
9 *---------------------------------------------------------------------------*
10 * This file is part of OpenFlipper. *
11 *---------------------------------------------------------------------------*
12 * *
13 * Redistribution and use in source and binary forms, with or without *
14 * modification, are permitted provided that the following conditions *
15 * are met: *
16 * *
17 * 1. Redistributions of source code must retain the above copyright notice, *
18 * this list of conditions and the following disclaimer. *
19 * *
20 * 2. Redistributions in binary form must reproduce the above copyright *
21 * notice, this list of conditions and the following disclaimer in the *
22 * documentation and/or other materials provided with the distribution. *
23 * *
24 * 3. Neither the name of the copyright holder nor the names of its *
25 * contributors may be used to endorse or promote products derived from *
26 * this software without specific prior written permission. *
27 * *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
30 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
31 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
32 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
33 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
34 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
35 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
36 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
37 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
38 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
39* *
40\*===========================================================================*/
41
42
43
44//== INCLUDES =================================================================
45#include "context.hh"
46#include "input.hh"
47#include "output.hh"
48#include "type.hh"
49#include "function.hh"
50
51#include "types/number.hh"
52#include "types/string.hh"
53#include "types/bool.hh"
54#include "types/filename.hh"
55#include "types/selection.hh"
56#include "types/vec3d.hh"
57#include "types/vec4d.hh"
58#include "types/matrix4x4.hh"
59#include "types/objectId/objectId.hh"
60#include "types/any.hh"
61
62#include <QRegularExpression>
63
64#define DATA_NAME "DataFlow"
65
66//== NAMESPACES ===============================================================
67namespace VSI {
68
69//=============================================================================
70//
71// CLASS VSI::Context - IMPLEMENTATION
72//
73//=============================================================================
74
76Context::Context (LoggingInterface *_loggingInterface, PythonInterface *_pythonInterface) :
77 loggingInterface_(_loggingInterface), pythonInterface_(_pythonInterface)
78{
79 // add start element
80 Element *e = new Element (this, "start");
81 elements_.append (e);
82
83 e->category_ = "Undefined";
84
85 e->shortDesc_ = "Start";
86 e->longDesc_ = "Start";
87
88 e->dataOut_ = new Output (e);
89 e->dataOut_->name_ = "data";
90 e->dataOut_->type_ = "data";
91
92 e->dataOut_->shortDesc_ = DATA_NAME;
93 e->dataOut_->longDesc_ = DATA_NAME;
94
95 e->flags_ = ELEMENT_FLAG_NO_DELETE | ELEMENT_FLAG_SKIP_TOOLBOX;
96
97 // add end element
98 e = new Element (this, "end");
99 elements_.append (e);
100
101 e->category_ = "Undefined";
102
103 e->shortDesc_ = "End";
104 e->longDesc_ = "End";
105
106 e->dataIn_ = new Input (e);
107 e->dataIn_->name_ = "data";
108 e->dataIn_->type_ = "data";
109
110 e->dataIn_->shortDesc_ = DATA_NAME;
111 e->dataIn_->longDesc_ = DATA_NAME;
112
113 e->flags_ = ELEMENT_FLAG_NO_DELETE | ELEMENT_FLAG_SKIP_TOOLBOX;
114
115
116 // Register default types
117 registerType (new TypeNumber ());
118 registerType (new TypeBool ());
119 registerType (new TypeString ());
120 registerType (new TypeFilename ());
122 registerType (new TypeVec3D ());
123 registerType (new TypeVec4D ());
125 registerType (new TypeObjectId ());
126 registerType (new TypeAny ());
127}
128
129//------------------------------------------------------------------------------
130
133{
134 foreach (Element *e, elements_)
135 delete e;
136 foreach (Type *t, types_)
137 delete t;
138}
139
140//------------------------------------------------------------------------------
141
143QVector <Element *> Context::elements (const QString& _category)
144{
145 QVector <Element *> rv;
146 foreach (Element *e, elements_)
147 if (e->category () == _category)
148 rv.append (e);
149 return rv;
150}
151
152//------------------------------------------------------------------------------
153
155Element * Context::element (const QString& _name)
156{
157 foreach (Element *e, elements_)
158 if (e->name () == _name)
159 return e;
160 return NULL;
161}
162
163//------------------------------------------------------------------------------
164
167{
168 QStringList sl;
169
170 foreach (Element *e, elements_)
171 if (!sl.contains (e->category ()) && !(e->flags () & ELEMENT_FLAG_SKIP_TOOLBOX))
172 sl << e->category ();
173
174 return sl;
175}
176
177//------------------------------------------------------------------------------
178
180void Context::parse (QFile& _xml)
181{
182
183 QDomDocument doc("OpenFlipper");
184 if (!doc.setContent(&_xml)) {
185 return;
186 }
187
188 // Iterate over all elements in the file (One Level below the OpenFlipper Tag
189 QDomElement docElem = doc.documentElement();
190
191 QDomNode n = docElem.firstChild();
192 while(!n.isNull()) {
193 QDomElement e = n.toElement(); // try to convert the node to an element.
194 if(!e.isNull()) {
195 parseElement(e);
196 }
197 n = n.nextSibling();
198 }
199}
200
201//------------------------------------------------------------------------------
202
204void Context::parseElement (QDomElement &_domElement)
205{
206
207 // If the element has no name, something is wrong here!
208 if ( !_domElement.hasAttribute("name"))
209 return;
210
211 // Create element in the database with the new elements name
212 Element *e = new Element (this, _domElement.attribute("name"));
213 elements_.append (e);
214
215 e->category_ = getXmlString (_domElement, "category", "Undefined");
216
217 e->shortDesc_ = getXmlString (_domElement, "short", e->name ());
218 e->longDesc_ = getXmlString (_domElement, "long", e->shortDesc_);
219
220 // scene graph in/output for scenegraph elements
221 if (strToBool (getXmlString (_domElement, "dataflow")))
222 {
223 e->dataIn_ = new Input (e);
224 e->dataIn_->name_ = "data";
225 e->dataIn_->type_ = "---";
226
227 e->dataIn_->shortDesc_ = DATA_NAME;
228 e->dataIn_->longDesc_ = DATA_NAME;
229 e->dataIn_->setState (Input::NoRuntimeUserInput | Input::NoUserInput);
230
231 e->dataOut_ = new Output (e);
232 e->dataOut_->name_ = "data";
233 e->dataOut_->type_ = "---";
234
235 e->dataOut_->shortDesc_ = DATA_NAME;
236 e->dataOut_->longDesc_ = DATA_NAME;
237 }
238
239 // Search through all children of the current element if we have inputs, outputs or functions
240 for(QDomElement n = _domElement.firstChildElement(); !n.isNull(); n = n.nextSiblingElement() )
241 {
242 // Found an input Tag!
243 if (n.tagName() == "inputs") {
244
245 // Iterate over all inputs inside it
246 for(QDomElement inputElement = n.firstChildElement(); !inputElement.isNull(); inputElement = inputElement.nextSiblingElement() )
247 {
248 Input* i =parseInput(inputElement,e);
249
250 if (i) {
251 e->inputs_.append (i);
252 }
253 }
254 }
255
256 // Found an input Tag!
257 if (n.tagName() == "outputs") {
258
259 // Iterate over all outputs inside it
260 for(QDomElement outputElement = n.firstChildElement(); !outputElement.isNull(); outputElement = outputElement.nextSiblingElement() )
261 {
262 Output *o = parseOutput(outputElement,e);
263
264 if (o) {
265 o->element_ = e;
266 e->outputs_.append (o);
267 }
268 }
269 }
270
271
272 // Found an input Tag!
273 if (n.tagName() == "functions") {
274
275 // Iterate over all outputs inside it
276 for(QDomElement functionElement = n.firstChildElement(); !functionElement.isNull(); functionElement = functionElement.nextSiblingElement() )
277 {
278 Function *f = parseFunction (functionElement, e);
279 if (f)
280 {
281 e->functions_.append (f);
282 }
283 }
284
285 }
286
287 }
288
289
290 // get code & precode segment
291 e->precode_ = getXmlString (_domElement, "precode", "");
292 e->code_ = getXmlString (_domElement, "code", "");
293
294 if (e->precode_.contains(QRegularExpression("^\\s*\\t"))) {
295 // precode contains tab symbol
296 emit loggingInterface_->log(LOGWARN, "Precode block of "+e->name()+" contains tab identation");
297 }
298 if (e->code_.contains(QRegularExpression("^\\s*\\t"))) {
299 // precode contains tab symbol
300 emit loggingInterface_->log(LOGWARN, "Code block of "+e->name()+" contains tab identation");
301 }
302
303 // remove spaces at line begin
304 e->precode_ = removeCommonTrailingSpaces(e->precode_);
305 e->code_ = removeCommonTrailingSpaces(e->code_);
306
307}
308
310
312Input* Context::parseInput(QDomElement& _domElement, Element *_e)
313{
314 Input *i = new Input (_e);
315
316 // common part
317 if (!parseInOutBase (_domElement, i))
318 {
319 delete i;
320 return NULL;
321 }
322
323 // input states
324 QString stateStr = _domElement.attribute("external");
325 unsigned int state = 0;
326
327 if (!stateStr.isEmpty () && !strToBool (stateStr))
328 state |= Input::NoExternalInput;
329
330 stateStr = _domElement.attribute("user");
331
332 if (!stateStr.isEmpty () && !strToBool (stateStr))
333 state |= Input::NoUserInput;
334
335 stateStr = _domElement.attribute("runtime");
336
337 if (!stateStr.isEmpty () && !strToBool (stateStr))
338 state |= Input::NoRuntimeUserInput;
339
340 stateStr = _domElement.attribute("optional");
341
342 if (!stateStr.isEmpty () && strToBool (stateStr))
343 state |= Input::Optional;
344
345 i->state_ = state;
346
347 return i;
348}
349
351
353Output* Context::parseOutput (QDomElement& _domElement, Element *_e)
354{
355 Output *o = new Output (_e);
356
357 // common part
358 if (!parseInOutBase (_domElement, o))
359 {
360 delete o;
361 return NULL;
362 }
363
364 return o;
365}
366
368
370bool Context::parseInOutBase (QDomElement &_domElement, InOut *_io)
371{
372 QString type = _domElement.attribute("type","");
373
374 if ( !_domElement.hasAttribute("name") || type.isEmpty() )
375 return false;
376
377 _io->name_ = _domElement.attribute("name");
378 _io->type_ = type;
379
380 _io->shortDesc_ = getXmlString (_domElement,"short", _io->name ());
381 _io->longDesc_ = getXmlString (_domElement, "long", _io->shortDesc_);
382
383 // get type hints for supported types
384 if (typeSupported (type))
385 {
386 foreach (QString hint, supportedTypes_[type]->supportedHints ())
387 {
388 QString value = getXmlString (_domElement, hint , "");
389 if (!value.isEmpty ())
390 _io->hints_[hint] = value;
391 }
392 }
393
394 return true;
395}
396
398
400Function* Context::parseFunction (QDomElement& _domElement, Element *_e)
401{
402 QString name = _domElement.attribute("name");
403 if (name.isEmpty ())
404 return NULL;
405
406 Function *f = new Function (_e, name);
407
408 f->shortDesc_ = getXmlString (_domElement, "short", f->name ());
409 f->longDesc_ = getXmlString (_domElement, "long", f->shortDesc_);
410
411 // add start element
412 f->start_ = new Element (_e->context (), "start_" + _e->name () + "_" + name);
413
414 f->start_->category_ = "Undefined";
415
416 f->start_->shortDesc_ = "Start";
417 f->start_->longDesc_ = "Start";
418
419 f->start_->dataOut_ = new Output (f->start_);
420 f->start_->dataOut_->name_ = "data";
421 f->start_->dataOut_->type_ = "data";
422
423 f->start_->dataOut_->shortDesc_ = DATA_NAME;
424 f->start_->dataOut_->longDesc_ = DATA_NAME;
425
426 f->start_->flags_ = ELEMENT_FLAG_NO_DELETE | ELEMENT_FLAG_SKIP_TOOLBOX;
427
428 elements_.append (f->start_);
429
430 // add end element
431 f->end_ = new Element (_e->context (), "end_" + _e->name () + "_" + name);
432
433 f->end_->category_ = "Undefined";
434
435 f->end_->shortDesc_ = "End";
436 f->end_->longDesc_ = "End";
437
438 f->end_->dataIn_ = new Input (f->end_);
439 f->end_->dataIn_->name_ = "data";
440 f->end_->dataIn_->type_ = "data";
441
442 f->end_->dataIn_->shortDesc_ = DATA_NAME;
443 f->end_->dataIn_->longDesc_ = DATA_NAME;
444
445 f->end_->flags_ = ELEMENT_FLAG_NO_DELETE | ELEMENT_FLAG_SKIP_TOOLBOX;
446
447 elements_.append (f->end_);
448
449
450
451 // Search through all children of the current element if we have inputs, outputs or functions
452 for(QDomElement n = _domElement.firstChildElement(); !n.isNull(); n = n.nextSiblingElement() )
453 {
454 // Found an input Tag!
455 if (n.tagName() == "inputs") {
456
457 // Iterate over all inputs inside it
458 for(QDomElement inputElement = n.firstChildElement(); !inputElement.isNull(); inputElement = inputElement.nextSiblingElement() )
459 {
460 Output *o = new Output (f->start_);
461 if (parseInOutBase(inputElement, o))
462 {
463 f->start_->outputs_.append (o);
464 }
465 else
466 delete o;
467 }
468
469 // Only one input element allowed
470 break;
471 }
472
473 // Found an input Tag!
474 if (n.tagName() == "outputs") {
475
476 // Iterate over all outputs inside it
477 for(QDomElement outputElement = n.firstChildElement(); !outputElement.isNull(); outputElement = outputElement.nextSiblingElement() )
478 {
479 Input *i = new Input (f->end_);
480 if (parseInOutBase(outputElement, i))
481 {
482 i->state_ = Input::NoUserInput | Input::NoRuntimeUserInput;
483 f->end_->inputs_.append (i);
484 }
485 else
486 delete i;
487 }
488
489 // Only one output element allowed
490 break;
491 }
492 }
493
494 // add end node only if we have outputs
495 if (!f->end_->inputs ().isEmpty ())
496 {
497 // Generate end node return code
498
499 QString endCode = "return { ";
500 foreach (Input *i, f->end_->inputs ())
501 endCode += "\"" + i->name () + "\" : [input=\"" + i->name () + "\"], ";
502 endCode.remove (endCode.length () - 2, 2);
503 endCode += " };\n";
504
505 f->end_->code_ = endCode;
506 }
507
508 return f;
509}
510
511//------------------------------------------------------------------------------
512
514QString Context::getXmlString (QDomElement &_domElement, const QString& _tag, QString _default)
515{
516
517 // Secrh through all children of the current element if it matches the given tag
518 for(QDomElement n = _domElement.firstChildElement(); !n.isNull(); n = n.nextSiblingElement() )
519 {
520 if (n.tagName() == _tag)
521 return n.text();
522 }
523
524 return _default;
525}
526
527//------------------------------------------------------------------------------
528
530bool Context::strToBool (const QString& _str)
531{
532 if (_str == "1" || _str == "true" || _str == "True" || _str == "TRUE" ||
533 _str == "yes" || _str == "Yes" || _str == "YES")
534 return true;
535 return false;
536}
537
538//------------------------------------------------------------------------------
539
542{
543 types_.append (_type);
544
545 foreach (const QString &s, _type->supportedTypes())
546 supportedTypes_.insert (s, _type);
547}
548
549//------------------------------------------------------------------------------
550
552bool Context::typeSupported(const QString& _type)
553{
554 return supportedTypes_.contains (_type);
555}
556
557//------------------------------------------------------------------------------
558
560bool Context::canConvert(const QString& _type1, const QString& _type2)
561{
562 if (!typeSupported (_type1) || !typeSupported (_type2))
563 return false;
564 return supportedTypes_[_type1]->canConvertTo (_type2) ||
565 supportedTypes_[_type2]->canConvertTo (_type1);
566}
567
568//------------------------------------------------------------------------------
569
571Type * Context::getType(const QString& _type)
572{
573 if (typeSupported (_type))
574 return supportedTypes_[_type];
575 return NULL;
576}
577
578//------------------------------------------------------------------------------
579
581// test
582// abcd
583// qwertz
584// is transformed to
585// test
586// abcd
587// qwertz
588QString Context::removeCommonTrailingSpaces(const QString& in) {
589 QStringList lines = in.split("\n");
590
591 const int INF = 1e6;
592 int commonTrailingSpaces = INF;
593
594 for (int line=0;line<lines.length();line++) {
595 int lenWithoutTrailingSpaces = QString(lines[line]).replace(QRegularExpression ("^\\s*"), "").length();
596
597 if (lenWithoutTrailingSpaces > 0) {
598 // line not empty
599 int trailingSpaces = lines[line].length() - lenWithoutTrailingSpaces;
600
601 if (trailingSpaces < commonTrailingSpaces) {
602 // here are fewer trailing spaces than before discovered
603 commonTrailingSpaces = trailingSpaces;
604 }
605 } else {
606 // line empty
607 }
608 }
609
610 if (commonTrailingSpaces >= INF) {
611 // no content
612 return QString("");
613 } else {
614
615 for (int line=0;line<lines.length();line++) {
616 int subLength = lines[line].length() - commonTrailingSpaces;
617
618 if (subLength < 0) {
619 // line is empty
620 lines[line] = QString("");
621 } else {
622 // remove common trailing whitespaces
623 lines[line] = lines[line].right(subLength);
624 }
625 }
626
627 QString output = lines.join("\n");
628 return output;
629 }
630}
631
632void Context::executeScript(const QString& _script) {
633 emit pythonInterface_->executePythonScript(_script);
634}
635
636void Context::openScriptInEditor(const QString& _script) {
637 emit pythonInterface_->openPythonScriptInEditor(_script);
638}
639
640}
@ LOGWARN
Interface for all Plugins which do logging to the logging window of the framework.
virtual void log(Logtype _type, QString _message)=0
Interface class for exporting functions to python.
virtual void openPythonScriptInEditor(QString _script)
virtual void executePythonScript(QString _script)
Context(LoggingInterface *_loggingInterface, PythonInterface *_pythonInterface)
Constructor.
Definition: context.cc:76
static bool strToBool(const QString &_str)
Converts the given string to bool.
Definition: context.cc:530
void registerType(Type *_type)
Registers a supported datatype.
Definition: context.cc:541
bool typeSupported(const QString &_type)
Is the given type supported.
Definition: context.cc:552
void parseElement(QDomElement &_element)
parse element from xml
Definition: context.cc:204
static QString getXmlString(QDomElement &_element, const QString &_tag, QString _default="")
Gets the string of a xml query.
Definition: context.cc:514
Type * getType(const QString &_type)
Get type object for given type name.
Definition: context.cc:571
bool canConvert(const QString &_type1, const QString &_type2)
Can the given types be converted to each other.
Definition: context.cc:560
const QVector< Element * > & elements() const
Returns all available elements.
Definition: context.hh:84
~Context()
Destructor.
Definition: context.cc:132
static QString removeCommonTrailingSpaces(const QString &in)
Removes trailing spaces from string which are present in each line - relative trailing spaces are not...
Definition: context.cc:588
Element * element(const QString &_name)
Returns the element with a given name.
Definition: context.cc:155
void parse(QFile &_xml)
Parse xml content.
Definition: context.cc:180
QStringList categories()
List of categories.
Definition: context.cc:166
QString name() const
Element name.
Definition: element.hh:82
const QString & category() const
Element category.
Definition: element.hh:85
unsigned int flags() const
Flags.
Definition: element.hh:109
void setState(unsigned int _state)
Sets the state.
Definition: input.hh:79
virtual QStringList supportedTypes()
Names of Types.
Definition: type.cc:71