Vuo 2.4.4
Loading...
Searching...
No Matches
VuoRendererComposition.cc
Go to the documentation of this file.
1
10#include "VuoFileType.h"
11
13
14#include "VuoCompiler.hh"
15#include "VuoCompilerCable.hh"
17#include "VuoCompilerDriver.hh"
22#include "VuoCompilerIssue.hh"
24#include "VuoCompilerType.hh"
25#include "VuoComment.hh"
26#include "VuoComposition.hh"
28#include "VuoNodeClass.hh"
29#include "VuoRendererComment.hh"
35#include "VuoRendererFonts.hh"
37
38#include "VuoStringUtilities.hh"
39
40#include "VuoHeap.h"
41#include "type.h"
42
43#include "VuoTextHtml.h"
44#include "VuoUrl.h"
45
46#ifdef __APPLE__
47#include <objc/message.h>
48#endif
49
50int VuoRendererComposition::gridOpacity = 0;
51VuoRendererComposition::GridType VuoRendererComposition::gridType = VuoRendererComposition::NoGrid;
52const int VuoRendererComposition::minorGridLineSpacing = 15; // VuoRendererPort::portSpacing
54const string VuoRendererComposition::deprecatedDefaultDescription = "This composition does...";
55
66VuoRendererComposition::VuoRendererComposition(VuoComposition *baseComposition, bool renderMissingAsPresent, bool enableCaching)
67 : VuoBaseDetail<VuoComposition>("VuoRendererComposition", baseComposition)
68{
69 getBase()->setRenderer(this);
70
71 VuoRendererFonts::getSharedFonts(); // Load the fonts now to avoid a delay later when rendering the first item in the composition.
72
74 this->renderMissingAsPresent = renderMissingAsPresent;
75 this->renderNodeActivity = false;
76 this->renderPortActivity = false;
77 this->renderHiddenCables = false;
78 this->cachingEnabled = enableCaching;
79 this->publishedInputNode = createPublishedInputNode();
80 this->publishedOutputNode = createPublishedOutputNode();
81
82 parser = NULL;
83
85
87}
88
93{
94 set<VuoNode *> nodes = getBase()->getNodes();
95 foreach (VuoNode *node, nodes)
96 addNodeInCompositionToCanvas(node);
97
98 vector<VuoPublishedPort *> publishedInputPorts = getBase()->getPublishedInputPorts();
99 foreach (VuoPublishedPort *publishedPort, publishedInputPorts)
101
102 vector<VuoPublishedPort *> publishedOutputPorts = getBase()->getPublishedOutputPorts();
103 foreach (VuoPublishedPort *publishedPort, publishedOutputPorts)
105
106 set<VuoCable *> cables = getBase()->getCables();
107 foreach (VuoCable *cable, cables)
108 {
109 addCableInCompositionToCanvas(cable);
110
111 if (cable->isPublishedInputCable())
112 cable->setFrom(publishedInputNode, cable->getFromPort());
113 if (cable->isPublishedOutputCable())
114 cable->setTo(publishedOutputNode, cable->getToPort());
115 }
116
117 foreach (VuoComment *comment, getBase()->getComments())
118 addCommentInCompositionToCanvas(comment);
119
121
122 // Now that all renderer components have been created, calculate
123 // the final positions of collapsed "Make List" drawers.
124 foreach (VuoNode *node, nodes)
126}
127
132{
133 if (transparent)
134 setBackgroundBrush(Qt::transparent);
135 else
136 setBackgroundBrush(VuoRendererColors::getSharedColors()->canvasFill());
137}
138
163
174void VuoRendererComposition::addNode(VuoNode *n, bool nodeShouldBeRendered, bool nodeShouldBeGivenUniqueIdentifier)
175{
176 if (getBase()->hasCompiler() && nodeShouldBeGivenUniqueIdentifier)
178
179 getBase()->addNode(n);
180
181 if (nodeShouldBeRendered)
182 addNodeInCompositionToCanvas(n);
183}
184
193void VuoRendererComposition::addNodeInCompositionToCanvas(VuoNode *n)
194{
196
197 if (renderMissingAsPresent)
198 rn->setMissingImplementation(false);
199
201 addItem(rn);
202
204}
205
212{
213 getBase()->addCable(c);
214 addCableInCompositionToCanvas(c);
215}
216
222void VuoRendererComposition::addCableInCompositionToCanvas(VuoCable *c)
223{
224 VuoRendererCable *rc = (c->hasRenderer() ? c->getRenderer() : new VuoRendererCable(c));
225 addItem(rc);
226
227 // The following VuoRendererCable::setFrom()/setTo() calls are unnecessary as far as the
228 // base cable is concerned, but forces the connected component renderings to update appropriately.
229 rc->setFrom(rc->getBase()->getFromNode(), rc->getBase()->getFromPort());
230 rc->setTo(rc->getBase()->getToNode(), rc->getBase()->getToPort());
231
232 // Performance optimizations
233 rc->setCacheMode(getCurrentDefaultCacheMode());
234}
235
243
250{
251 if (getBase()->hasCompiler())
253
254 getBase()->addComment(c);
255 addCommentInCompositionToCanvas(c);
256}
257
263void VuoRendererComposition::addCommentInCompositionToCanvas(VuoComment *c)
264{
266 addItem(rc);
267
268 // Performance optimizations
269 rc->setCacheMode(getCurrentDefaultCacheMode());
270}
271
276{
277 rn->updateGeometry();
278 removeItem(rn);
279
280 getBase()->removeNode(rn->getBase());
281}
282
287{
288 rc->setFrom(NULL, NULL);
289 rc->setTo(NULL, NULL);
290
291 rc->updateGeometry();
292 rc->removeFromScene();
293
294 getBase()->removeCable(rc->getBase());
295}
296
301{
302 rc->updateGeometry();
303 removeItem(rc);
304
305 getBase()->removeComment(rc->getBase());
306}
307
311QList<QGraphicsItem *> VuoRendererComposition::createAndConnectInputAttachments(VuoRendererNode *node, VuoCompiler *compiler, bool createButDoNotAdd)
312{
313 QList<QGraphicsItem *> componentsAttached;
314 if (VuoStringUtilities::beginsWith(node->getBase()->getNodeClass()->getClassName(), "vuo.math.calculate"))
315 {
316 VuoPort *valuesPort = node->getBase()->getInputPortWithName("values");
317 set<VuoRendererInputAttachment *> attachments = valuesPort->getRenderer()->getAllUnderlyingUpstreamInputAttachments();
318 if (attachments.empty())
319 componentsAttached.append(createAndConnectDrawersToReadOnlyDictionaryInputPorts(node, compiler, createButDoNotAdd));
320 }
321
322 componentsAttached.append(createAndConnectDrawersToListInputPorts(node, compiler, createButDoNotAdd));
323
324 return componentsAttached;
325}
326
330QList<QGraphicsItem *> VuoRendererComposition::createAndConnectDrawersToListInputPorts(VuoRendererNode *node, VuoCompiler *compiler, bool createButDoNotAdd)
331{
332 QList<QGraphicsItem *> componentsAttached;
333 foreach (VuoPort *port, node->getBase()->getInputPorts())
334 {
335 VuoCompilerInputEventPort *inputEventPort = (port->hasCompiler()? dynamic_cast<VuoCompilerInputEventPort *>(port->getCompiler()) : NULL);
336 if (inputEventPort && VuoCompilerType::isListType(inputEventPort->getDataType()))
337 {
339 {
340 VuoRendererCable *cable = NULL;
341 VuoRendererNode *makeListNode = createAndConnectMakeListNode(node->getBase(), port, compiler, cable);
342
343 if (cable)
344 componentsAttached.append(cable);
345
346 if (makeListNode)
347 componentsAttached.append(makeListNode);
348
349 if (!createButDoNotAdd)
350 {
351 addNode(makeListNode->getBase());
352 addCable(cable->getBase());
353 }
354 }
355 }
356 }
357
358 return componentsAttached;
359}
360
364QList<QGraphicsItem *> VuoRendererComposition::createAndConnectDrawersToReadOnlyDictionaryInputPorts(VuoRendererNode *node, VuoCompiler *compiler, bool createButDoNotAdd)
365{
366 set<VuoRendererNode *> nodesToAdd;
367 set<VuoRendererCable *> cablesToAdd;
368 createAndConnectDictionaryAttachmentsForNode(node->getBase(), compiler, nodesToAdd, cablesToAdd);
369
370 QList<QGraphicsItem *> componentsAttached;
371 foreach (VuoRendererNode *node, nodesToAdd)
372 componentsAttached.append(node);
373 foreach (VuoRendererCable *cable, cablesToAdd)
374 componentsAttached.append(cable);
375
376 if (!createButDoNotAdd)
377 {
378 foreach (VuoRendererNode *node, nodesToAdd)
379 addNode(node->getBase());
380
381 foreach (VuoRendererCable *cable, cablesToAdd)
382 addCable(cable->getBase());
383 }
384
385 return componentsAttached;
386}
387
398 VuoRendererCable *&rendererCable)
399{
400 VuoRendererNode *makeListRendererNode = NULL;
401 rendererCable = NULL;
402
403 VuoCompilerInputEventPort *inputEventPort = static_cast<VuoCompilerInputEventPort *>(toPort->getCompiler());
404 VuoCompilerType *type = inputEventPort->getDataType();
405
406 vector<string> itemInitialValues;
407 if (inputEventPort->getData())
408 {
409 string listInitialValue = inputEventPort->getData()->getInitialValue();
410 json_object *js = json_tokener_parse(listInitialValue.c_str());
411 if (json_object_get_type(js) == json_type_array)
412 {
413 int itemCount = json_object_array_length(js);
414 for (int i = 0; i < itemCount; ++i)
415 {
416 json_object *itemObject = json_object_array_get_idx(js, i);
417 string itemString = json_object_to_json_string_ext(itemObject, JSON_C_TO_STRING_PLAIN);
418 itemInitialValues.push_back(itemString);
419 }
420 }
421 json_object_put(js);
422 }
423
424 unsigned long itemCount = (itemInitialValues.empty() ? 2 : itemInitialValues.size());
425 string nodeClassName = VuoCompilerMakeListNodeClass::getNodeClassName(itemCount, type);
426 VuoCompilerNodeClass *makeListNodeClass = compiler->getNodeClass(nodeClassName);
427
428 VuoNode *makeListNode = makeListNodeClass->newNode();
429 makeListRendererNode = createRendererNode(makeListNode);
430
431 vector<VuoPort *> itemPorts = makeListNode->getInputPorts();
432 for (size_t i = 0; i < itemInitialValues.size(); ++i)
433 {
435 VuoRendererPort *itemPort = itemPorts[portIndex]->getRenderer();
436 itemPort->setConstant(itemInitialValues[i]);
437 }
438
439 VuoCompilerPort *fromCompilerPort = static_cast<VuoCompilerPort *>(makeListNode->getOutputPorts().back()->getCompiler());
440 VuoCompilerPort *toCompilerPort = static_cast<VuoCompilerPort *>(toPort->getCompiler());
441 VuoCompilerCable *compilerCable = new VuoCompilerCable(makeListNode->getCompiler(), fromCompilerPort,
442 toNode->getCompiler(), toCompilerPort);
443 rendererCable = new VuoRendererCable(compilerCable->getBase());
444
445 return makeListRendererNode;
446}
447
458 VuoCompiler *compiler,
459 set<VuoRendererNode *> &createdNodes,
460 set<VuoRendererCable *> &createdCables)
461{
462 createdNodes.clear();
463 createdCables.clear();
464
465 VuoPort *expressionInputPort = node->getInputPortWithName("expression");
466 VuoPort *valuesInputPort = node->getInputPortWithName("values");
467 if (!(expressionInputPort && valuesInputPort))
468 {
469 VUserLog("Error: Cannot create dictionary attachments for a node without 'expression' and 'values' input ports.");
470 return;
471 }
472
473 // Assume for now that the dictionary should map strings to reals for use as variables in a VuoMathExpressionList.
474 const string dictionaryTypeName = "VuoDictionary_VuoText_VuoReal";
475 const string dictionaryClassName = "vuo.dictionary.make.VuoText.VuoReal";
476 const string dictionaryKeySourceTypeName = "VuoMathExpressionList";
477
478 VuoCompilerPort *valuesInputPortCompiler = static_cast<VuoCompilerPort *>(valuesInputPort->getCompiler());
479 if (valuesInputPortCompiler->getDataVuoType()->getModuleKey() != dictionaryTypeName)
480 {
481 VUserLog("Error: Unexpected dictionary type required: %s", valuesInputPortCompiler->getDataVuoType()->getModuleKey().c_str());
482 return;
483 }
484
485 VuoCompilerPort *expressionInputPortCompiler = static_cast<VuoCompilerPort *>(expressionInputPort->getCompiler());
486 if (expressionInputPortCompiler->getDataVuoType()->getModuleKey() != dictionaryKeySourceTypeName)
487 {
488 VUserLog("Error: Unexpected key source type encountered: %s", expressionInputPortCompiler->getDataVuoType()->getModuleKey().c_str());
489 return;
490 }
491
492 // Extract the variable names from the math expressions.
493 VuoCompilerInputEventPort *expressionInputEventPort = static_cast<VuoCompilerInputEventPort *>(expressionInputPortCompiler);
494 string expressionConstant = expressionInputEventPort->getData()->getInitialValue();
495 vector<string> inputVariables = extractInputVariableListFromExpressionsConstant(expressionConstant, node->getNodeClass()->getClassName());
496 unsigned long itemCount = inputVariables.size();
497
498 string keyListClassName = VuoCompilerMakeListNodeClass::buildNodeClassName(itemCount, "VuoText");
499 string valueListClassName = VuoCompilerMakeListNodeClass::buildNodeClassName(itemCount, "VuoReal");
500
501 VuoCompilerNodeClass *keyListNodeClass = compiler->getNodeClass(keyListClassName);
502 VuoCompilerNodeClass *valueListNodeClass = compiler->getNodeClass(valueListClassName);
503 VuoCompilerNodeClass *dictionaryNodeClass = compiler->getNodeClass(dictionaryClassName);
504
505 if (keyListNodeClass && valueListNodeClass && dictionaryNodeClass)
506 {
507 // Create and connect all base components before creating any renderer components so that createRendererNode()
508 // has all of the information it needs to create the appropriate renderer form for each node.
509 QPoint offset(-220, 50);
510 VuoNode *dictionaryNode = compiler->createNode(dictionaryNodeClass, "", node->getX()+offset.x(), node->getY()+offset.y());
511 VuoNode *keyListNode = compiler->createNode(keyListNodeClass, "", node->getX()+offset.x(), node->getY()+offset.y());
512 VuoNode *valueListNode = compiler->createNode(valueListNodeClass, "", node->getX()+offset.x(), node->getY()+offset.y());
513
514 VuoCable *cableCarryingDictionary = (new VuoCompilerCable(dictionaryNode->getCompiler(),
515 static_cast<VuoCompilerPort *>(dictionaryNode->getOutputPortWithName("dictionary")->getCompiler()),
516 node->getCompiler(),
517 static_cast<VuoCompilerPort *>(valuesInputPort->getCompiler())))->getBase();
518
519 VuoCable *cableCarryingKeys = (new VuoCompilerCable(keyListNode->getCompiler(),
520 static_cast<VuoCompilerPort *>(keyListNode->getOutputPortWithName("list")->getCompiler()),
521 dictionaryNode->getCompiler(),
522 static_cast<VuoCompilerPort *>(dictionaryNode->getInputPortWithName("keys")->getCompiler())))->getBase();
523
524 VuoCable *cableCarryingValues = (new VuoCompilerCable(valueListNode->getCompiler(),
525 static_cast<VuoCompilerPort *>(valueListNode->getOutputPortWithName("list")->getCompiler()),
526 dictionaryNode->getCompiler(),
527 static_cast<VuoCompilerPort *>(dictionaryNode->getInputPortWithName("values")->getCompiler())))->getBase();
528
529 // Set the variable names extracted from the math expressions.
530 vector<VuoPort *> keyPorts = keyListNode->getInputPorts();
531 for (size_t i = 0; i < itemCount; ++i)
532 {
534 string key = "\"" + inputVariables[i] + "\"";
535
536 VuoCompilerInputEventPort *keyEventPort = static_cast<VuoCompilerInputEventPort *>(keyPort->getCompiler());
537 keyEventPort->getData()->setInitialValue(key);
538 }
539
540 createdNodes.insert(createRendererNode(dictionaryNode));
541 createdNodes.insert(createRendererNode(keyListNode));
542 createdNodes.insert(createRendererNode(valueListNode));
543
544 createdCables.insert(new VuoRendererCable(cableCarryingDictionary));
545 createdCables.insert(new VuoRendererCable(cableCarryingKeys));
546 createdCables.insert(new VuoRendererCable(cableCarryingValues));
547 }
548}
549
556vector<string> VuoRendererComposition::extractInputVariableListFromExpressionsConstant(string constant, string nodeClassName)
557{
558 vector<string> inputVariables;
559
560 json_object *js = json_tokener_parse(constant.c_str());
561 json_object *expressionsObject = NULL;
562
563 if (json_object_object_get_ex(js, "inputVariables", &expressionsObject))
564 {
565 if (json_object_get_type(expressionsObject) == json_type_array)
566 {
567 int variableCount = json_object_array_length(expressionsObject);
568 for (int i = 0; i < variableCount; ++i)
569 {
570 json_object *itemObject = json_object_array_get_idx(expressionsObject, i);
571 if (json_object_get_type(itemObject) == json_type_string)
572 {
573 const char *variableName = json_object_get_string(itemObject);
574 if (nodeClassName == "vuo.math.calculate.list"
575 && (strcasecmp(variableName, "x") == 0
576 || strcasecmp(variableName, "i") == 0))
577 continue;
578
579 inputVariables.push_back(json_object_get_string(itemObject));
580 }
581 }
582 }
583 }
584 json_object_put(js);
585
586 return inputVariables;
587}
588
594{
595 if (! publishedPort->hasCompiler())
596 return NULL;
597
598 return new VuoRendererPublishedPort(publishedPort, !isPublishedInput);
599}
600
604void VuoRendererComposition::addPublishedPort(VuoPublishedPort *publishedPort, bool isPublishedInput, VuoCompiler *compiler)
605{
606 string name = publishedPort->getClass()->getName();
607 if (isPublishedInput)
608 {
610 if (! existingPort)
611 {
612 int index = getBase()->getPublishedInputPorts().size();
613 getBase()->addPublishedInputPort(publishedPort, index);
614 }
615 else if (publishedPort != existingPort)
616 VUserLog("Error: Unhandled published port name conflict.");
617 }
618 else // if (! isPublishedInput)
619 {
621 if (! existingPort)
622 {
623 int index = getBase()->getPublishedOutputPorts().size();
624 getBase()->addPublishedOutputPort(publishedPort, index);
625 }
626 else if (publishedPort != existingPort)
627 VUserLog("Error: Unhandled published port name conflict.");
628 }
629}
630
637int VuoRendererComposition::removePublishedPort(VuoPublishedPort *publishedPort, bool isPublishedInput, VuoCompiler *compiler)
638{
639 if (isPublishedInput)
640 {
641 int index = getBase()->getIndexOfPublishedPort(publishedPort, isPublishedInput);
642 if (index != -1)
644
645 return index;
646 }
647 else
648 {
649 int index = getBase()->getIndexOfPublishedPort(publishedPort, isPublishedInput);
650 if (index != -1)
652
653 return index;
654 }
655}
656
662{
663 publishedPort->setName(getUniquePublishedPortName(name));
664}
665
671VuoNode * VuoRendererComposition::createPublishedInputNode()
672{
673 VuoNodeClass *dummyPublishedInputNodeClass = new VuoNodeClass(VuoNodeClass::publishedInputNodeClassName, vector<string>(), vector<string>());
674 return dummyPublishedInputNodeClass->newNode(VuoNodeClass::publishedInputNodeIdentifier);
675}
676
682VuoNode * VuoRendererComposition::createPublishedOutputNode()
683{
684 VuoNodeClass *dummyPublishedOutputNodeClass = new VuoNodeClass(VuoNodeClass::publishedOutputNodeClassName, vector<string>(), vector<string>());
685 return dummyPublishedOutputNodeClass->newNode(VuoNodeClass::publishedOutputNodeIdentifier);
686}
687
693{
694 auto isNameAvailable = [this] (const string &name)
695 {
696 return (! name.empty() &&
697 name != "refresh" &&
698 getBase()->getPublishedInputPortWithName(name) == nullptr &&
699 getBase()->getPublishedOutputPortWithName(name) == nullptr);
700 };
701
702 return VuoStringUtilities::formUniqueIdentifier(isNameAvailable, baseName);
703}
704
709{
710 return (!port->getPublishedPorts().empty());
711}
712
718{
719 vector<VuoRendererNode *> typecastsCollapsed;
720
721 foreach (VuoNode *node, getBase()->getNodes())
722 {
723 if (node->isTypecastNode())
724 {
726 if (collapsedPort)
727 typecastsCollapsed.push_back(node->getRenderer());
728 }
729 }
730
731 return typecastsCollapsed;
732}
733
739{
740 // Don't try to collapse nodes that don't qualify as typecasts.
741 if (!rn || !rn->getBase()->isTypecastNode())
742 return NULL;
743
746 return NULL;
747
750
751 // Don't try to re-collapse typecasts that are already collapsed.
752 VuoRendererPort *typecastParent = typecastInPort->getRenderer()->getTypecastParentPort();
753 if (typecastParent)
754 return NULL;
755
756 vector<VuoCable *> outCables = typecastOutPort->getConnectedCables(true);
757 vector<VuoCable *> inCables = typecastInPort->getConnectedCables(true);
758
759 // Don't try to collapse typecast nodes with incoming cables to the "refresh" port.
760 if ( ! rn->getBase()->getRefreshPort()->getConnectedCables(true).empty() )
761 return NULL;
762
763 // Don't try to collapse typecast nodes outputting to multiple nodes, or without any output cables.
764 if (outCables.size() != 1)
765 return NULL;
766
767 VuoCable *outCable = *(outCables.begin());
768
769 // Don't try to collapse typecast nodes whose outgoing cable is event-only.
770 if (!(outCable->hasRenderer() && outCable->getRenderer()->effectivelyCarriesData()))
771 return NULL;
772
773 // Don't try to collapse typecast nodes without any incoming data+event cables (including published ones).
774 VuoCable *incomingDataCable = NULL;
775 for (vector<VuoCable *>::iterator i = inCables.begin(); !incomingDataCable && (i != inCables.end()); ++i)
776 {
777 if ((*i)->hasRenderer() && (*i)->getRenderer()->effectivelyCarriesData())
778 incomingDataCable = *i;
779 }
780 if (! incomingDataCable)
781 return NULL;
782
783 // Don't try to collapse typecast nodes that have published output ports.
784 if (outCable->isPublished())
785 return NULL;
786
787 VuoNode *fromNode = incomingDataCable->getFromNode();
788 VuoPort *fromPort = incomingDataCable->getFromPort();
789
790 VuoNode * toNode = outCable->getToNode();
791 VuoPort * toPort = outCable->getToPort();
792
793 // Don't try to collapse typecast nodes with input or output cables that are not currently connected at both ends.
794 if (! (fromNode && fromPort && toNode && toPort))
795 return NULL;
796
797 // Don't try to collapse typecast nodes with attached input drawers.
798 if (rn->getAttachedInputDrawers().size() > 0)
799 return NULL;
800
801 // Don't try to collapse typecast nodes outputting to other typecasts.
802 if (toNode->isTypecastNode())
803 return NULL;
804
805 // Don't try to collapse typecast nodes outputting to ports with multiple input cables.
806 if (toPort->getConnectedCables(false).size() > 1)
807 return NULL;
808
809 // Don't try to collapse typecast nodes outputting to internal ports that have been published.
810 if (isPortPublished(toPort->getRenderer()))
811 return NULL;
812
813 // Hide the typecast node.
814 VuoRendererNode * toRN = toNode->getRenderer();
815 rn->updateGeometry();
816 rn->setProxyNode(toRN);
817
818 // Replace the target node's input port with a new typecast port.
819 VuoRendererPort * oldToRP = toPort->getRenderer();
821 oldToRP,
822 signaler);
823
824 QGraphicsItem::CacheMode defaultCacheMode = getCurrentDefaultCacheMode();
825 foreach (VuoCable *cable, inCables)
826 {
827 if (cable->hasRenderer())
828 {
829 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
830 cable->getRenderer()->updateGeometry();
831 cable->getRenderer()->setCacheMode(defaultCacheMode);
832 }
833 }
834
835 foreach (VuoCable *cable, outCables)
836 {
837 if (cable->hasRenderer())
838 {
839 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
840 cable->getRenderer()->updateGeometry();
841 cable->getRenderer()->setCacheMode(defaultCacheMode);
842 }
843 }
844
845 tp->updateGeometry();
846 toRN->replaceInputPort(oldToRP, tp);
848 typecastInPort->getRenderer()->setTypecastParentPort(tp);
849 typecastInPort->getRenderer()->setParentItem(toRN);
850
851 // Notify the base port of the change in renderer port, to reverse
852 // the change made within the VuoRendererPort constructor on behalf of any
853 // renderer port previously initialized for this base port.
854 tp->getBase()->setRenderer(tp);
855
856 // The typecast port may have been added to a drawer,
857 // and may thus need to change the horizontal position of its name.
858 tp->updateNameRect();
859
861
862 return tp;
863}
864
869{
870 foreach (VuoNode *node, getBase()->getNodes())
871 {
872 if (node->isTypecastNode())
873 {
876 }
877 }
878}
879
885{
886 VuoPort * typecastInPort = typecastNode->getBase()->getInputPorts()[VuoNodeClass::unreservedInputPortStartIndex];
887 VuoRendererPort *typecastParent = typecastInPort->getRenderer()->getTypecastParentPort();
888
889 if (typecastParent)
891}
892
897{
898 VuoRendererNode *uncollapsedNode = typecast->getUncollapsedTypecastNode();
899 VuoPort *typecastInPort = uncollapsedNode->getBase()->getInputPorts()[VuoNodeClass::unreservedInputPortStartIndex];
900 VuoPort *typecastOutPort = uncollapsedNode->getBase()->getOutputPorts()[VuoNodeClass::unreservedOutputPortStartIndex];
901 vector<VuoCable *> outCables = typecastOutPort->getConnectedCables(false);
902 VuoCable * outCable = *(outCables.begin());
903 VuoRendererPort *uncollapsedToRP = typecast->getReplacedPort();
904 VuoRendererNode *toRN = outCable->getToNode()->getRenderer();
905
906 QGraphicsItem::CacheMode defaultCacheMode = getCurrentDefaultCacheMode();
907 foreach (VuoCable *cable, typecastInPort->getConnectedCables(true))
908 {
909 if (cable->hasRenderer())
910 {
911 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
912 cable->getRenderer()->updateGeometry();
913 cable->getRenderer()->setCacheMode(defaultCacheMode);
914 }
915 }
916
917 foreach (VuoCable *cable, typecastOutPort->getConnectedCables(true))
918 {
919 if (cable->hasRenderer())
920 {
921 cable->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
922 cable->getRenderer()->updateGeometry();
923 cable->getRenderer()->setCacheMode(defaultCacheMode);
924 }
925 }
926
927 typecast->updateGeometry();
928 uncollapsedNode->updateGeometry();
929
930 typecastInPort->getRenderer()->setParentItem(uncollapsedNode);
931 uncollapsedNode->addInputPort(typecastInPort->getRenderer());
932
933 typecastInPort->getRenderer()->setTypecastParentPort(NULL);
934 uncollapsedNode->setProxyNode(NULL);
935
936 toRN->updateGeometry();
937 toRN->replaceInputPort(typecast, uncollapsedToRP);
939
940 // Notify the base port of the change in renderer port, to reverse
941 // the change made within the VuoRendererPort constructor on behalf of any
942 // renderer port previously initialized for this base port.
943 uncollapsedToRP->getBase()->setRenderer(uncollapsedToRP);
944
945 toRN->layoutConnectedInputDrawersAtAndAbovePort(uncollapsedToRP);
946
947 return uncollapsedNode;
948}
949
957
965
970{
971 foreach (VuoNode *node, getBase()->getNodes())
972 {
973 vector<VuoPort *> inputPorts = node->getInputPorts();
974 for(vector<VuoPort *>::iterator inputPort = inputPorts.begin(); inputPort != inputPorts.end(); ++inputPort)
975 {
976 QGraphicsItem::CacheMode normalCacheMode = (*inputPort)->getRenderer()->cacheMode();
977 (*inputPort)->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
978
979 (*inputPort)->getRenderer()->updateGeometry();
980 (*inputPort)->getRenderer()->setEligibilityHighlight(VuoRendererColors::noHighlight);
981
982 (*inputPort)->getRenderer()->setCacheMode(normalCacheMode);
983
984 VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>((*inputPort)->getRenderer());
985 if (typecastPort)
986 {
987 QGraphicsItem::CacheMode normalCacheMode = typecastPort->getChildPort()->cacheMode();
988 typecastPort->getChildPort()->setCacheMode(QGraphicsItem::NoCache);
989
990 typecastPort->getChildPort()->updateGeometry();
992
993 typecastPort->getChildPort()->setCacheMode(normalCacheMode);
994 }
995 }
996
997 vector<VuoPort *> outputPorts = node->getOutputPorts();
998 for(vector<VuoPort *>::iterator outputPort = outputPorts.begin(); outputPort != outputPorts.end(); ++outputPort)
999 {
1000 QGraphicsItem::CacheMode normalCacheMode = (*outputPort)->getRenderer()->cacheMode();
1001 (*outputPort)->getRenderer()->setCacheMode(QGraphicsItem::NoCache);
1002
1003 (*outputPort)->getRenderer()->updateGeometry();
1004 (*outputPort)->getRenderer()->setEligibilityHighlight(VuoRendererColors::noHighlight);
1005
1006 (*outputPort)->getRenderer()->setCacheMode(normalCacheMode);
1007 }
1008
1009
1010 VuoRendererNode *rn = node->getRenderer();
1011 QGraphicsItem::CacheMode normalCacheMode = rn->cacheMode();
1012 rn->setCacheMode(QGraphicsItem::NoCache);
1013
1014 rn->updateGeometry();
1016
1017 rn->setCacheMode(normalCacheMode);
1018 }
1019
1020 foreach (VuoCable *cable, getBase()->getCables())
1021 {
1022 VuoRendererCable *rc = cable->getRenderer();
1023 QGraphicsItem::CacheMode normalCacheMode = rc->cacheMode();
1024 rc->setCacheMode(QGraphicsItem::NoCache);
1025 rc->updateGeometry();
1026
1028
1029 rc->setCacheMode(normalCacheMode);
1030 }
1031}
1032
1037{
1038 foreach (VuoNode *node, getBase()->getNodes())
1039 {
1040 node->getRenderer()->updateGeometry();
1041
1042 foreach (VuoPort *port, node->getInputPorts())
1043 port->getRenderer()->updateGeometry();
1044
1045 foreach (VuoPort *port, node->getOutputPorts())
1046 port->getRenderer()->updateGeometry();
1047 }
1048
1049 foreach (VuoCable *cable, getBase()->getCables())
1050 cable->getRenderer()->updateGeometry();
1051}
1052
1059{
1060 return this->renderNodeActivity;
1061}
1062
1069{
1070 return (this->renderNodeActivity && this->renderPortActivity);
1071}
1072
1079void VuoRendererComposition::drawBackground(QPainter *painter, const QRectF &rect)
1080{
1081 QGraphicsScene::drawBackground(painter, rect);
1082
1084 {
1085 painter->setRenderHint(QPainter::Antialiasing, true);
1086 painter->setPen(QPen(QColor(255,0,0,128),5));
1087 painter->drawRect(sceneRect());
1088 QVector<QPointF> points;
1089 points << QPointF(0,0);
1090 points << sceneRect().topLeft();
1091 points << sceneRect().center();
1092 points << sceneRect().bottomRight();
1093 foreach (QPointF p, points)
1094 {
1095 painter->setPen(QPen(QColor(255,0,0,128),5));
1096 painter->drawEllipse(p, 5, 5);
1097 painter->setPen(QColor(255,0,0,128));
1098 painter->drawText(p + QPointF(5,15) - (p == sceneRect().bottomRight() ? QPointF(70,20) : QPointF(0,0)), QString("(%1,%2)").arg(p.x()).arg(p.y()));
1099 }
1100 painter->setRenderHint(QPainter::Antialiasing, false);
1101 }
1102
1103 // Draw grid.
1104 if (gridType != NoGrid)
1105 {
1107
1108 qreal leftmostGridLine = quantizeToNearestGridLine(rect.topLeft(), gridSpacing).x();
1109 if (leftmostGridLine < rect.left())
1110 leftmostGridLine += gridSpacing;
1111 qreal topmostGridLine = quantizeToNearestGridLine(rect.topLeft(), gridSpacing).y();
1112 if (topmostGridLine < rect.top())
1113 topmostGridLine += gridSpacing;
1114
1115 // Correct for the fact that VuoRendererNode::paint() starts painting at (-1,0) rather than (0,0).
1116 // @todo: Eliminate this correction after modifying VuoRendererNode::paint()
1117 // for https://b33p.net/kosada/node/10210 .
1118 const int nodeXAlignmentCorrection = -1;
1119
1120 if (gridType == LineGrid)
1121 {
1122 QVector<QLineF> gridLines;
1123 for (qreal x = leftmostGridLine; x < rect.right(); x += gridSpacing)
1124 gridLines.append(QLineF(x + nodeXAlignmentCorrection, rect.top(), x + nodeXAlignmentCorrection, rect.bottom()));
1125 for (qreal y = topmostGridLine; y < rect.bottom(); y += gridSpacing)
1126 gridLines.append(QLineF(rect.left(), y, rect.right(), y));
1127
1128 painter->setPen(QColor(128, 128, 128, VuoRendererComposition::gridOpacity * 32));
1129 painter->drawLines(gridLines);
1130 }
1131 else if (gridType == PointGrid)
1132 {
1133 painter->setRenderHint(QPainter::Antialiasing, true);
1134 painter->setPen(Qt::NoPen);
1135 painter->setBrush(QColor(128, 128, 128, VuoRendererComposition::gridOpacity * 128));
1136
1137 for (qreal y = topmostGridLine; y < rect.bottom(); y += gridSpacing)
1138 for (qreal x = leftmostGridLine; x < rect.right(); x += gridSpacing)
1139 // Offset by a half-pixel to render a softer plus-like point (instead of a sharp pixel-aligned square).
1140 painter->drawEllipse(QPointF(x + nodeXAlignmentCorrection + .5, y + .5), 1.5,1.5);
1141 }
1142 }
1143}
1144
1151void VuoRendererComposition::setRenderActivity(bool render, bool includePortActivity)
1152{
1153 if ((this->renderNodeActivity == render) && (this->renderPortActivity == includePortActivity))
1154 return;
1155
1156 this->renderNodeActivity = render;
1157 this->renderPortActivity = includePortActivity;
1158
1159 if (render)
1160 {
1161 foreach (VuoNode *node, getBase()->getNodes())
1162 {
1164
1165 if (includePortActivity)
1166 {
1167 foreach (VuoPort *port, node->getInputPorts())
1169
1170 foreach (VuoPort *port, node->getOutputPorts())
1172 }
1173 }
1174
1175 foreach (VuoCable *cable, getBase()->getCables())
1177 }
1178
1181}
1182
1190
1201
1206void VuoRendererComposition::setComponentCaching(QGraphicsItem::CacheMode cacheMode)
1207{
1208 // Nodes and ports
1209 foreach (VuoNode *node, getBase()->getNodes())
1210 {
1211 VuoRendererNode *rn = node->getRenderer();
1212 if (rn)
1213 rn->setCacheModeForNodeAndPorts(cacheMode);
1214 }
1215
1216 // Cables
1217 set<VuoCable *> allCables = getBase()->getCables();
1218 foreach (VuoCable *cable, allCables)
1219 {
1220 VuoRendererCable *rc = cable->getRenderer();
1221 if (rc)
1222 rc->setCacheMode(cacheMode);
1223 }
1224}
1225
1232{
1233 return ((cachingEnabled && !renderNodeActivity)? QGraphicsItem::DeviceCoordinateCache : QGraphicsItem::NoCache);
1234}
1235
1241{
1242#ifdef __APPLE__
1243 // [NSAutoreleasePool new];
1244 Class poolClass = objc_getClass("NSAutoreleasePool");
1245 ((void (*)(id, SEL))objc_msgSend)((id)poolClass, sel_getUid("new"));
1246#endif
1247}
1248
1255{
1256 foreach (VuoNode *node, getBase()->getNodes())
1257 {
1258 foreach (VuoPort *port, node->getInputPorts())
1259 {
1260 if (!port->hasRenderer())
1261 continue;
1262
1263 VuoRendererPort *rp = port->getRenderer();
1265 {
1267 string modifiedRelativeResourcePath = modifyResourcePathForBundle(url);
1268 VuoRelease(url);
1269 rp->setConstant("\"" + modifiedRelativeResourcePath + "\"");
1270 }
1271 }
1272 }
1273}
1274
1281{
1282 map<VuoPort *, string> modifiedPathForPort;
1283 foreach (VuoNode *node, getBase()->getNodes())
1284 {
1285 foreach (VuoPort *port, node->getInputPorts())
1286 {
1287 if (!port->hasRenderer())
1288 continue;
1289
1290 VuoRendererPort *rp = port->getRenderer();
1292 {
1294 QString origRelativeResourcePath = url;
1295 VuoRelease(url);
1296 string modifiedRelativeResourcePath = modifyResourcePathForNewDir(origRelativeResourcePath.toUtf8().constData(), newDir);
1297
1298 if (modifiedRelativeResourcePath != origRelativeResourcePath.toUtf8().constData())
1299 modifiedPathForPort[port] = "\"" + modifiedRelativeResourcePath + "\"";
1300 }
1301 }
1302 }
1303
1304 return modifiedPathForPort;
1305}
1306
1312{
1313 string iconURL = getBase()->getMetadata()->getIconURL();
1314 string quotedIconURL = "\"" + iconURL + "\"";
1315
1316 VuoUrl url = VuoMakeRetainedFromString(quotedIconURL.c_str(), VuoUrl);
1317 bool isRelativePath = VuoUrl_isRelativePath(url);
1318 QString origRelativeResourcePath = url;
1319 VuoRelease(url);
1320
1321 if (!isRelativePath)
1322 return iconURL;
1323
1324 return modifyResourcePathForNewDir(origRelativeResourcePath.toUtf8().constData(), newDir);
1325}
1326
1337void VuoRendererComposition::bundleResourceFiles(string targetResourceDir, bool tmpFilesOnly, QString bundledIconPath)
1338{
1339 // Update relative URLs referenced within port constants.
1340 foreach (VuoNode *node, getBase()->getNodes())
1341 {
1342 foreach (VuoPort *port, node->getInputPorts())
1343 {
1344 if (!port->hasRenderer())
1345 continue;
1346
1347 VuoRendererPort *rp = port->getRenderer();
1349 {
1351 QString origRelativeResourcePath = url;
1352 QString modifiedRelativeResourcePath = modifyResourcePathForBundle(origRelativeResourcePath.toUtf8().constData()).c_str();
1353
1354 string origRelativeDir, modifiedRelativeDir, file, ext;
1355 VuoFileUtilities::splitPath(origRelativeResourcePath.toUtf8().constData(), origRelativeDir, file, ext);
1356 VuoFileUtilities::splitPath(modifiedRelativeResourcePath.toUtf8().constData(), modifiedRelativeDir, file, ext);
1357 string resourceFileName = file;
1358 if (!ext.empty())
1359 {
1360 resourceFileName += ".";
1361 resourceFileName += ext;
1362 }
1363
1364 QDir compositionDir(QDir(getBase()->getDirectory().c_str()).canonicalPath());
1365 QDir appDir(QDir(targetResourceDir.c_str()).canonicalPath());
1366
1367 QString sourceFilePath = compositionDir.filePath(QDir(origRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1368 if (!tmpFilesOnly || isTmpFile(sourceFilePath.toUtf8().constData()))
1369 {
1370 if (!modifiedRelativeDir.empty())
1371 appDir.mkpath(modifiedRelativeDir.c_str());
1372
1373 QString targetFilePath = appDir.filePath(QDir(modifiedRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1374
1375 VDebugLog("Copying \"%s\" (from %s:%s)", url, node->getTitle().c_str(), port->getClass()->getName().c_str());
1376 VuoFileUtilities::copyDirectory(sourceFilePath.toStdString(), targetFilePath.toStdString());
1377
1378 if (VuoFileType_isFileOfType(sourceFilePath.toUtf8().constData(), VuoFileType_Scene))
1379 bundleAuxiliaryFilesForSceneFile(sourceFilePath, targetFilePath);
1380 }
1381 VuoRelease(url);
1382 }
1383
1384 }
1385 }
1386
1387 // Update relative path to custom icon.
1388 string iconURL = getBase()->getMetadata()->getIconURL();
1389 string quotedIconURL = "\"" + iconURL + "\"";
1390
1391 VuoUrl url = VuoMakeRetainedFromString(quotedIconURL.c_str(), VuoUrl);
1392 bool iconHasRelativePath = VuoUrl_isRelativePath(url);
1393 QString origRelativeResourcePath = url;
1394
1395 if (iconHasRelativePath)
1396 {
1397 QString modifiedRelativeResourcePath = modifyResourcePathForBundle(origRelativeResourcePath.toUtf8().constData()).c_str();
1398
1399 string origRelativeDir, modifiedRelativeDir, file, ext;
1400 VuoFileUtilities::splitPath(origRelativeResourcePath.toUtf8().constData(), origRelativeDir, file, ext);
1401 VuoFileUtilities::splitPath(modifiedRelativeResourcePath.toUtf8().constData(), modifiedRelativeDir, file, ext);
1402 string resourceFileName = file;
1403 if (!ext.empty())
1404 {
1405 resourceFileName += ".";
1406 resourceFileName += ext;
1407 }
1408
1409 QDir compositionDir(QDir(getBase()->getDirectory().c_str()).canonicalPath());
1410 QDir appDir(QDir(targetResourceDir.c_str()).canonicalPath());
1411
1412 QString sourceFilePath = compositionDir.filePath(QDir(origRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1413 if (!tmpFilesOnly || isTmpFile(sourceFilePath.toUtf8().constData()))
1414 {
1415 if (!modifiedRelativeDir.empty())
1416 appDir.mkpath(modifiedRelativeDir.c_str());
1417
1418 QString targetFilePath = (!bundledIconPath.isEmpty()? bundledIconPath :
1419 appDir.filePath(QDir(modifiedRelativeDir.c_str()).filePath(resourceFileName.c_str())));
1420
1421 // Case: Icon is already in .icns format; copy it directly.
1422 if (QString(ext.c_str()).toLower() == "icns")
1423 {
1424 VDebugLog("Copying \"%s\" (app icon)", url);
1425 VuoFileUtilities::copyDirectory(sourceFilePath.toStdString(), targetFilePath.toStdString());
1426 }
1427 // Case: Icon is in some other format; convert it to an ".icns" file.
1428 else
1429 {
1430 VDebugLog("Converting \"%s\" (app icon)", url);
1431 QPixmap icon(sourceFilePath);
1432 QFile file(targetFilePath);
1433 file.open(QIODevice::WriteOnly);
1434 icon.save(&file, "ICNS");
1435 }
1436 }
1437 }
1438 VuoRelease(url);
1439
1440 // @todo https://b33p.net/kosada/node/9205 : Published input port constants?
1441}
1442
1448{
1449 const QString tmpDirPath = QDir(VuoFileUtilities::getTmpDir().c_str()).canonicalPath();
1450 QDir parentDir(QDir(QFileInfo(filePath.c_str()).dir()).canonicalPath());
1451
1452 do
1453 {
1454 if (parentDir.canonicalPath() == tmpDirPath)
1455 return true;
1456 } while (parentDir.cdUp());
1457
1458 return false;
1459}
1460
1465string VuoRendererComposition::modifyResourcePathForBundle(string path)
1466{
1467 if (VuoUrl_isRelativePath(path.c_str()))
1468 {
1469 // Replace parent-directory indicators ("../") so that resources
1470 // located within the parent (or ancestor) directory of the original composition will
1471 // still be copied into the exported app's "Resources" directory or a subdirectory thereof.
1472 const QString originalParentDirIndicator = "/../";
1473 const QString appBundleParentDirIndicator = "/VuoParentDir/";
1474
1475 QString modifiedPath = QString("/").append(path.c_str());
1476
1477 while (modifiedPath.contains(originalParentDirIndicator))
1478 modifiedPath.replace(originalParentDirIndicator, appBundleParentDirIndicator);
1479
1480 modifiedPath.remove(0, 1); // Remove leading '/' added earlier
1481
1482 return modifiedPath.toUtf8().constData();
1483 }
1484
1485 else
1486 return path;
1487}
1488
1493string VuoRendererComposition::modifyResourcePathForNewDir(string path, QDir newDir)
1494{
1495 if (VuoUrl_isRelativePath(path.c_str()))
1496 {
1497 QDir compositionDir(QDir(getBase()->getDirectory().c_str()).canonicalPath());
1498 if (compositionDir.canonicalPath() == newDir.canonicalPath())
1499 return path;
1500
1501 string origRelativeDir, file, ext;
1502 VuoFileUtilities::splitPath(path, origRelativeDir, file, ext);
1503 string resourceFileName = file;
1504 if (!ext.empty())
1505 {
1506 resourceFileName += ".";
1507 resourceFileName += ext;
1508 }
1509
1510 QString sourceFilePath = compositionDir.filePath(QDir(origRelativeDir.c_str()).filePath(resourceFileName.c_str()));
1511 string modifiedPath = newDir.relativeFilePath(sourceFilePath).toUtf8().constData();
1512
1513 return modifiedPath;
1514 }
1515
1516 else
1517 return path;
1518}
1519
1524void VuoRendererComposition::bundleAuxiliaryFilesForSceneFile(QString sourceFilePath, QString targetFilePath)
1525{
1526 string sourceDirName, targetDirName, file, ext;
1527 VuoFileUtilities::splitPath(sourceFilePath.toUtf8().constData(), sourceDirName, file, ext);
1528 VuoFileUtilities::splitPath(targetFilePath.toUtf8().constData(), targetDirName, file, ext);
1529
1530 // Bundle any file or folder in the same directory as the mesh file
1531 // whose name begins with the mesh file's basename.
1532 QDir sourceDir(sourceDirName.c_str());
1533 QStringList filesWithMatchingBaseName = QStringList() << QString(file.c_str()).append("*");
1534 sourceDir.setNameFilters(filesWithMatchingBaseName);
1535
1536 foreach (QString auxiliaryFile, sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot))
1537 {
1538 QString sourceFile = QString(sourceDirName.c_str()) + QDir::separator() + auxiliaryFile;
1539 QString targetFile = QString(targetDirName.c_str()) + QDir::separator() + auxiliaryFile;
1540 if (!QFileInfo(targetFile).exists())
1541 {
1542 VDebugLog("Copying \"%s\"", QFileInfo(targetFile).fileName().toUtf8().constData());
1543 VuoFileUtilities::copyDirectory(sourceFile.toStdString(), targetFile.toStdString());
1544 }
1545 }
1546
1547 // Bundle texture folders in the same directory as the mesh file.
1548 QStringList textureFolderNames = QStringList() << "Textures" << "_Textures";
1549 sourceDir.setNameFilters(textureFolderNames);
1550 foreach (QString textureFolderName, sourceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
1551 {
1552 QString sourceTextureDir = QString(sourceDirName.c_str()) + QDir::separator() + textureFolderName;
1553 QString targetTextureDir = QString(targetDirName.c_str()) + QDir::separator() + textureFolderName;
1554 if (!QFileInfo(targetTextureDir).exists())
1555 {
1556 VDebugLog("Copying \"%s\"", QFileInfo(targetTextureDir).fileName().toUtf8().constData());
1557 VuoFileUtilities::copyDirectory(sourceTextureDir.toStdString(), targetTextureDir.toStdString());
1558 }
1559 }
1560
1561 // @todo: Bundle texture folders in the parent directory of the mesh file.
1562 // See https://b33p.net/kosada/node/9390, https://b33p.net/kosada/node/9391.
1563
1564 return;
1565}
1566
1572{
1573 QDir dir(path.c_str());
1574 return dir.exists() && !VuoFileType_isFileOfType(path.c_str(), VuoFileType_App);
1575}
1576
1581{
1582 VuoRendererComposition::gridOpacity = opacity;
1583}
1584
1589{
1590 return VuoRendererComposition::gridOpacity;
1591}
1592
1597{
1598 VuoRendererComposition::gridType = type;
1599}
1600
1605QPoint VuoRendererComposition::quantizeToNearestGridLine(QPointF point, int gridSpacing)
1606{
1607 return QPoint(floor((point.x()/(1.0*gridSpacing))+0.5)*gridSpacing,
1608 floor((point.y()/(1.0*gridSpacing))+0.5)*gridSpacing);
1609}