Vuo  2.4.0
VuoRendererNode.cc
Go to the documentation of this file.
1
10#include "VuoRendererNode.hh"
13#include "VuoCompiler.hh"
15#include "VuoCompilerPort.hh"
16#include "VuoFileUtilities.hh"
17#include "VuoGenericType.hh"
18#include "VuoNodeClass.hh"
21#include "VuoRendererFonts.hh"
22#include "VuoStringUtilities.hh"
24
25const qreal VuoRendererNode::cornerRadius = 10 /*VuoRendererFonts::thickPenWidth/2.0*/;
26const qreal VuoRendererNode::outerBorderWidth = 1.;
27const qreal VuoRendererNode::nodeTitleHeight = 18 /*round(VuoRendererFonts::nodeTitleFontSize + VuoRendererFonts::thickPenWidth*1./8.) + 2*/;
28const qreal VuoRendererNode::nodeTitleHorizontalMargin = 12 /*VuoRendererFonts::thickPenWidth/2.0 + 2*/;
29const qreal VuoRendererNode::nodeClassHeight = 12 /*round(VuoRendererFonts::thickPenWidth*3./5.)*/;
30const qreal VuoRendererNode::nodeHeaderYOffset = -nodeTitleHeight - nodeClassHeight;
31const qreal VuoRendererNode::iconRightOffset = 11.;
32
37 : VuoBaseDetail<VuoNode>("VuoRendererNode from VuoCompilerNode", baseNode)
38{
39 getBase()->setRenderer(this);
40
41 setZValue(nodeZValue);
42
43 // Set up signaler after node has been positioned to avoid sending a spurious nodeMoved signal.
44 this->signaler = NULL;
45
46 VuoNodeClass * nodeClass = baseNode->getNodeClass();
47 this->proxyNode = NULL;
48 this->nodeType = VuoRendererNode::node;
49
50 if (nodeClass->hasCompiler() && dynamic_cast<VuoCompilerSpecializedNodeClass *>(nodeClass->getCompiler()))
51 this->nodeClass = QString::fromUtf8(dynamic_cast<VuoCompilerSpecializedNodeClass *>(nodeClass->getCompiler())->getOriginalGenericNodeClassName().c_str());
52 else
53 this->nodeClass = QString::fromUtf8(nodeClass->getClassName().c_str());
54
55 // QPainter::drawText expects strings to be canonically composed,
56 // else it renders diacritics next to (instead of superimposed upon) their base glyphs.
57 this->nodeClass = this->nodeClass.normalized(QString::NormalizationForm_C);
58
59 this->nodeIsSubcomposition = nodeClass->hasCompiler() && nodeClass->getCompiler()->isSubcomposition();
60 this->nodeIsMissing = !nodeClass->hasCompiler();
61 this->alwaysDisplayPortNames = false;
62 this->_eligibilityHighlight = VuoRendererColors::noHighlight;
63
65
66 this->setFlags(QGraphicsItem::ItemIsMovable |
67 QGraphicsItem::ItemIsSelectable |
68 QGraphicsItem::ItemIsFocusable |
69 QGraphicsItem::ItemSendsGeometryChanges);
70
71 this->setAcceptHoverEvents(true);
72
73 {
74 vector<VuoRendererPort *> inputs;
75 vector<VuoPort *> inputPorts = baseNode->getInputPorts();
76 for(vector<VuoPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
77 {
78 bool isRefreshPort = (*it == baseNode->getRefreshPort());
79 inputs.push_back(new VuoRendererPort(*it, signaler, false, isRefreshPort, false));
80 }
81 setInputPorts(inputs);
82 }
83
84 {
85 vector<VuoRendererPort *> outputs;
86 vector<VuoPort *> outputPorts = baseNode->getOutputPorts();
87 for(vector<VuoPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
88 {
89 bool isFunctionPort = false;
90 outputs.push_back(new VuoRendererPort(*it, signaler, true, false, isFunctionPort));
91 }
92 setOutputPorts(outputs);
93 }
94
95 setPos(baseNode->getX(), baseNode->getY());
98
99 this->signaler = signaler;
100}
101
102void VuoRendererNode::setInputPorts(vector<VuoRendererPort *> inputPorts)
103{
104 for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
105 {
106 this->inputPorts.push_back(*it);
107 (*it)->setParentItem(this);
108
109 if ((*it)->isHiddenRefreshPort())
110 continue;
111
112 // Now that the port knows what node it belongs to, it may need to update
113 // its cached display name and related structures to reflect the fact that
114 // the port name may be unambiguous within the node, and therefore not displayed.
115 (*it)->updateNameRect();
116 }
117}
118
119void VuoRendererNode::setOutputPorts(vector<VuoRendererPort *> outputPorts)
120{
121 // @@@ remove when functionPort is implemented and part of the outputPorts list.
122 // At that time, make sure that VuoNodeClass::unreservedOutputPortStartIndex is updated accordingly.
123 functionPort = new VuoRendererPort(new VuoPort(new VuoPortClass("fake function port", VuoPortClass::notAPort))
124 , signaler, false, false, true);
125
126 // We're not rendering the function port at the moment anyway, so don't add it to the outputPorts list --
127 // the discrepancy in the unreserved output port start index between the renderer and base makes things messy.
128 //this->outputPorts->addToGroup(functionPort);
129
130 for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
131 {
132 this->outputPorts.push_back(*it);
133
134 (*it)->setParentItem(this);
135
136 // Now that the port knows what node it belongs to, it may need to update
137 // its cached display name and related structures to reflect the fact that
138 // the port name may be unambiguous within the node, and therefore not displayed.
139 (*it)->updateNameRect();
140 }
141}
142
147{
148 int i=0;
149 foreach (VuoRendererPort *p, this->inputPorts)
150 {
151 QPointF point = getPortPoint(p, i++);
152 p->setPos(point);
153
154 VuoRendererTypecastPort * tp = dynamic_cast<VuoRendererTypecastPort *>(p);
155 if (tp)
156 tp->getChildPort()->setPos(point - QPointF(tp->getChildPortXOffset(),0));
157 }
158
159 i=0;
160 foreach (VuoRendererPort *p, this->outputPorts)
161 p->setPos(getPortPoint(p, i++));
162
163 this->nodeFrames = getNodeFrames(this->frameRect);
164}
165
170{
171 vector<VuoPort *> inputPorts = getBase()->getInputPorts();
172 if (inputPorts.size() < 1)
173 return;
174
175 VuoPort *finalInputPort = inputPorts[inputPorts.size()-1];
176
177 if (finalInputPort->hasRenderer())
179}
180
187{
188 bool reachedTargetPort = false;
189
190 vector<VuoPort *> inputPorts = getBase()->getInputPorts();
191 for (int i = inputPorts.size()-1; i >= 0; --i)
192 {
193 if (inputPorts[i] == port->getBase())
194 reachedTargetPort = true;
195
196 if (reachedTargetPort)
197 layoutConnectedInputDrawer(i);
198 }
199
200 VuoRendererInputDrawer *thisDrawer = dynamic_cast<VuoRendererInputDrawer *>(this);
201 if (thisDrawer)
202 {
203 VuoNode *hostNode = thisDrawer->getRenderedHostNode();
204 VuoPort *hostPort = thisDrawer->getRenderedHostPort();
205 if (hostNode && hostNode->hasRenderer() && hostPort && hostPort->hasRenderer())
207 }
208}
209
214void VuoRendererNode::layoutConnectedInputDrawer(unsigned int i)
215{
216 vector<VuoPort *> inputPorts = getBase()->getInputPorts();
217
218 if (i >= inputPorts.size())
219 return;
220
221 VuoRendererPort *p = inputPorts[i]->getRenderer();
223 if (drawer)
224 {
225 QPointF point = getPortPoint(p, i);
226 drawer->updateGeometry();
227
228 QPoint inputDrawerOffset(getInputDrawerOffset(i) + 0.5 - 2, 0); // Add 0.5 in order to round, not truncate.
229 drawer->setHorizontalDrawerOffset(inputDrawerOffset.x());
230 drawer->setPos(mapToScene(point-inputDrawerOffset));
231 drawer->getBase()->setTintColor(this->getBase()->getTintColor());
232
233 // Update the list's cached bounding rect, so long arms don't disappear when the ports scroll offscreen.
234 auto listDrawer = dynamic_cast<VuoRendererInputListDrawer *>(drawer);
235 if (listDrawer)
236 listDrawer->updateGeometry();
237 }
238}
239
243void VuoRendererNode::drawNodeFrame(QPainter *painter, QRectF nodeInnerFrameRect, VuoRendererColors *colors) const
244{
245 QPainterPath nodeOuterFrame = this->nodeFrames.first;
246 QPainterPath nodeInnerFrame = this->nodeFrames.second;
247
248 // Paint the node body background.
249 painter->fillPath(nodeInnerFrame, colors->nodeFill());
250
251 // Paint the node header background.
252 // This gets painted after the body, so the parts outside the
253 // header's rounded bottom corners show through to the body.
254 painter->fillPath(nodeOuterFrame, colors->nodeFrame());
255
256 // Paint the subcomposition icon.
257 if (nodeIsSubcomposition)
258 painter->fillPath(this->subcompositionIndicatorPath, QBrush(QColor::fromRgb(255,255,255,128)));
259}
260
264QPair<QPainterPath, QPainterPath> VuoRendererNode::getNodeFrames(QRectF nodeInnerFrameRect)
265{
266 // Calculate the bounds of the outer frame (header background).
267 QRectF nodeOuterFrameRect;
268 {
269 qreal nodeOuterFrameTopY = nodeInnerFrameRect.top() + VuoRendererNode::nodeHeaderYOffset;
270 nodeOuterFrameRect = QRectF(
271 nodeInnerFrameRect.left() - VuoRendererNode::outerBorderWidth,
272 nodeOuterFrameTopY,
273 nodeInnerFrameRect.width() + 2*VuoRendererNode::outerBorderWidth,
274 -VuoRendererNode::nodeHeaderYOffset// + VuoRendererNode::outerBorderWidth
275 );
276 }
277
278 // Calculate the outer and inner frames.
279 QPainterPath nodeOuterFrame;
280 qreal nodeHeaderBottomCornerRadius = VuoRendererNode::cornerRadius/4;
281 {
282 // The top of the outer frame (header) is rounded; the bottom is slightly rounded.
283 nodeOuterFrame.moveTo(nodeOuterFrameRect.right(), nodeOuterFrameRect.center().y());
284 addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.bottomRight(), nodeHeaderBottomCornerRadius, false, false);
285 addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.bottomLeft(), nodeHeaderBottomCornerRadius, false, true);
286 addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.topLeft(), VuoRendererNode::cornerRadius, true, true);
287 addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.topRight(), VuoRendererNode::cornerRadius, true, false);
288 }
289
290 QPainterPath nodeInnerFrame;
291 {
292 // The bottom of the inner frame has rounded corners, to match the outer frame.
293 qreal bw = VuoRendererNode::outerBorderWidth;
294 nodeInnerFrame.moveTo(nodeInnerFrameRect.topLeft() - QPoint(bw, 0) - QPoint(0, nodeHeaderBottomCornerRadius));
295 nodeInnerFrame.lineTo(nodeInnerFrameRect.topRight() + QPoint(bw, 0) - QPoint(0, nodeHeaderBottomCornerRadius));
296 addRoundedCorner(nodeInnerFrame, true, nodeInnerFrameRect.bottomRight() + QPoint( bw,bw), VuoRendererNode::cornerRadius, false, false);
297 addRoundedCorner(nodeInnerFrame, true, nodeInnerFrameRect.bottomLeft() + QPoint(-bw,bw), VuoRendererNode::cornerRadius, false, true);
298 }
299
300 return qMakePair(nodeOuterFrame, nodeInnerFrame);
301}
302
307QPainterPath VuoRendererNode::getSubcompositionIndicatorPath(QRectF nodeInnerFrameRect, bool isSubcomposition)
308{
309 if (!isSubcomposition)
310 return QPainterPath();
311
312 const double rectSideLength = VuoRendererFonts::midPenWidth*2.5;
313
314 // The icon should start at the node class label's baseline, and extend to match its ascender height.
315 QPainterPath rectPath;
316 rectPath.addRect(QRectF(-0.5 /* pixel-align */ + nodeInnerFrameRect.right() - iconRightOffset,
317 -4. - 1.5*rectSideLength,
318 rectSideLength,
319 rectSideLength));
320
321 // Stroke the top-left rectangle.
322 QPainterPathStroker rectStroker;
323 QPainterPath topLeftStroke = rectStroker.createStroke(rectPath);
324
325 // Stroke the bottom-right rectangle.
326 rectPath.translate(-0.5 /* pixel-align */ + rectSideLength/2,
327 -0.5 /* pixel-align */ + rectSideLength/2);
328 QPainterPath bottomRightStroke = rectStroker.createStroke(rectPath);
329
330 // Unite the two rectangles (rather than overdrawing semitransparent strokes), so the icon is a uniform color.
331 return topLeftStroke.united(bottomRightStroke);
332}
333
337qreal VuoRendererNode::getInputDrawerOffset(unsigned int portIndex) const
338{
339 qreal maxOffset = 0;
340 vector<VuoPort *> inputPorts = this->getBase()->getInputPorts();
341
342 for (unsigned int i = inputPorts.size()-1; i > portIndex; --i)
343 {
344 VuoRendererPort *port = inputPorts[i]->getRenderer();
345
346 if (port->getAttachedInputDrawer())
347 maxOffset += port->getAttachedInputDrawer()->getMaxDrawerChainedLabelWidth() - 5;
348 else if (dynamic_cast<VuoRendererTypecastPort *>(port))
349 {
351 qreal currentTypecastOffset = tp->getPortPath(true, true).boundingRect().width()
352 + (VuoRendererPort::portRadius-2) // Account for typecast child port.
354
355 if (currentTypecastOffset > maxOffset)
356 maxOffset = currentTypecastOffset;
357 }
358 else if (port->isConstant())
359 {
360 qreal currentConstantOffset = port->getPortPath().boundingRect().width()
363
364 if (currentConstantOffset > maxOffset)
365 maxOffset = currentConstantOffset;
366 }
367 else if (!port->getBase()->getConnectedCables(true).empty()) // Simple circle port with cable(s) attached (leave room for the cable's rounded corner).
368 maxOffset = fmax(maxOffset, VuoRendererPort::portRadius * 2 + 3);
369 else // Simple circle port with no cables.
370 maxOffset = fmax(maxOffset, VuoRendererPort::portRadius );
371 }
372
373 qreal targetDrawerOffset = inputPorts[portIndex]->getRenderer()->getAttachedInputDrawer()->getMaxDrawerLabelWidth()
375
376 return targetDrawerOffset + maxOffset;
377}
378
382vector<VuoRendererInputDrawer *> VuoRendererNode::getAttachedInputDrawers(void) const
383{
384 vector<VuoRendererInputDrawer *> drawers;
385 vector<VuoPort *> inputPorts = this->getBase()->getInputPorts();
386 for(unsigned int i = 0; i < inputPorts.size(); ++i)
387 {
388 VuoRendererInputDrawer *drawer = inputPorts[i]->getRenderer()->getAttachedInputDrawer();
389 if (drawer)
390 drawers.push_back(drawer);
391 }
392
393 return drawers;
394}
395
402{
403 QRectF updatedFrameRect;
404 QString nodeTitle = QString::fromUtf8(getBase()->getTitle().c_str());
405
406 // QPainter::drawText expects strings to be canonically composed,
407 // else it renders diacritics next to (instead of superimposed upon) their base glyphs.
408 nodeTitle = nodeTitle.normalized(QString::NormalizationForm_C);
409
410 // Width is the longest string or combined input+output port row.
411 qreal maxPortRowWidth=0;
412
413 size_t inputPortCount = inputPorts.size();
414 size_t outputPortCount = outputPorts.size();
415 size_t portRowCount = max(inputPortCount, outputPortCount);
416 for (int portRow = 0; portRow < portRowCount; ++portRow)
417 {
418 // Skip port rows containing reserved (refresh/function) ports, which don't affect the node's width.
419 int adjustedInputPortRow = portRow + VuoNodeClass::unreservedInputPortStartIndex;
420 int adjustedOutputPortRow = portRow + VuoNodeClass::unreservedOutputPortStartIndex;
421
422 qreal thisRowWidth = 0;
423 if (adjustedInputPortRow < inputPortCount)
424 {
425 VuoRendererPort *port = inputPorts[adjustedInputPortRow];
426 thisRowWidth += port->getNameRect().x() + port->getNameRect().width();
427
428 if (port->hasPortAction())
429 thisRowWidth += port->getActionIndicatorRect().width();
430 }
431
432 if (adjustedOutputPortRow < outputPortCount)
433 thisRowWidth += outputPorts[adjustedOutputPortRow]->getNameRect().width();
434
435 if(thisRowWidth > maxPortRowWidth)
436 maxPortRowWidth = thisRowWidth;
437 }
438
439 qreal nodeTitleWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodeTitleFont()).boundingRect(nodeTitle).width();
440 qreal nodeClassWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodeClassFont()).boundingRect(nodeClass).width();
441
442 if (this->nodeIsSubcomposition)
443 nodeClassWidth += VuoRendererNode::iconRightOffset;
444
445 bool hasInputPorts = inputPortCount > VuoNodeClass::unreservedInputPortStartIndex;
446 bool hasOutputPorts = outputPortCount > VuoNodeClass::unreservedOutputPortStartIndex;
447 int unalignedFrameWidth =
448 floor(max(
449 max(
450 nodeTitleWidth
451 + nodeTitleHorizontalMargin * 2.
452 // Line up right edge of text with right edge of subcomposition icon
453 - 3,
454
455 nodeClassWidth
456 // Padding between left edge of node frame and left edge of text
458 // Padding between right edge of text and right edge of node frame
460 // Line up right edge of text with where the right edge of the subcomposition icon would be if it were present
461 - 3
462 ),
463 maxPortRowWidth
464
465 // Leave some space between the right edge of the input port name and the left edge of the output port name.
466 + ((hasInputPorts && hasOutputPorts) ? (nameDisplayEnabledForInputPorts() && nameDisplayEnabledForOutputPorts()?
469 0.)
470
471
472 + 1.
473 ));
474
475 // Snap to the next-widest horizontal grid position so that node columns can be right-justified.
476 int alignedFrameWidth = VuoRendererComposition::quantizeToNearestGridLine(QPointF(unalignedFrameWidth, 0), VuoRendererComposition::minorGridLineSpacing).x()
477 - 2*VuoRendererNode::outerBorderWidth;
478
479 if (alignedFrameWidth < unalignedFrameWidth)
481
482 updatedFrameRect.setWidth(alignedFrameWidth);
483
484 // Height depends only on the number of input/output ports.
485 updatedFrameRect.setHeight(
486 floor(
487 max(
488 inputPortCount - VuoNodeClass::unreservedInputPortStartIndex, // don't count the refresh port
489 outputPortCount - VuoNodeClass::unreservedOutputPortStartIndex // don't count the function port
492 )
493 );
494
495 this->frameRect = updatedFrameRect;
496 this->subcompositionIndicatorPath = getSubcompositionIndicatorPath(this->frameRect, this->nodeIsSubcomposition);
497 this->nodeFrames = getNodeFrames(this->frameRect);
498}
499
504{
505 if (paintingDisabled())
506 return QRectF();
507
508 QRectF r = frameRect;
509
510 // Header
511 r.adjust(0, nodeHeaderYOffset, 0, 0);
512
513 // Antialiasing bleed
514 r.adjust(-1,-1,1,1);
515
516 return r.toAlignedRect();
517}
518
523{
524 return this->proxyNode;
525}
526
534{
535 return isSelected();
536}
537
544QPointF VuoRendererNode::getPortPoint(VuoRendererPort *port, unsigned int portIndex) const
545{
546 qreal x = (port->getOutput() ? frameRect.width() + VuoRendererPort::portRadius - 1 : -VuoRendererPort::portRadius);
547 qreal y;
548
549 if (port->getRefreshPort())
550 {
552 }
553 else if (port->getFunctionPort())
554 {
556 }
557 else
558 {
559 qreal adjustedPortIndex = (qreal)portIndex - (port->getOutput() ?
561 VuoNodeClass::unreservedInputPortStartIndex); // Skip the refresh/function port.
562
563 qreal portOffset = adjustedPortIndex + 0.5; // Center the port within its space.
565 }
566
567 return QPointF(x, y);
568}
569
574{
575 if (paintingDisabled())
576 return QRectF();
577
578 return nodeFrames.first.boundingRect();
579}
580
586void VuoRendererNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
587{
588 if (paintingDisabled())
589 return;
590
591 drawBoundingRect(painter);
592
593 VuoRendererColors::SelectionType selectionType = (isSelected()? VuoRendererColors::directSelection : VuoRendererColors::noSelection);
594 qint64 timeOfLastActivity = (getRenderActivity()? timeLastExecutionEnded : VuoRendererItem::notTrackingActivity);
595 VuoRendererColors *colors = new VuoRendererColors(getBase()->getTintColor(), selectionType, false, VuoRendererColors::noHighlight, timeOfLastActivity, nodeIsMissing);
596 drawNodeFrame(painter, frameRect, colors);
597
598 // Node Title
599 {
600 QString nodeTitle = QString::fromUtf8(getBase()->getTitle().c_str());
601
602 // QPainter::drawText expects strings to be canonically composed,
603 // else it renders diacritics next to (instead of superimposed upon) their base glyphs.
604 nodeTitle = nodeTitle.normalized(QString::NormalizationForm_C);
605
607
608 nodeTitleBoundingRect = QRectF(
609 nodeTitleHorizontalMargin
610 - VuoRendererFonts::getHorizontalOffset(font, nodeTitle),
612 frameRect.width() + 4. - 2. * VuoRendererFonts::thickPenWidth/2., // Leave room for in-event and out-event ports.
613 nodeTitleHeight + 2.
614 );
615 painter->setPen(colors->nodeTitle());
616 painter->setFont(font);
617 painter->drawText(nodeTitleBoundingRect,Qt::AlignLeft,nodeTitle);
618 VuoRendererItem::drawRect(painter, nodeTitleBoundingRect);
619 }
620
621 // Node Class
622 {
624
625 QRectF r(
627 + 2.
628 - VuoRendererFonts::getHorizontalOffset(font, nodeClass),
629 -nodeClassHeight - 2.,
630 frameRect.width() + 4. - 2. * VuoRendererFonts::thickPenWidth/2., // Leave room for in-event and out-event ports.
631 nodeClassHeight + 2.
632 );
633 painter->setPen(colors->nodeClass());
634 painter->setFont(font);
635 painter->drawText(r,Qt::AlignLeft,nodeClass);
636 VuoRendererItem::drawRect(painter, r);
637 }
638
639 if (nodeIsMissing)
640 {
641 bool notAvailable = getBase()->isForbidden();
642#if VUO_PRO
643 notAvailable |= getBase()->getNodeClass()->isPro();
644#endif
645 QString text = notAvailable ? "Not Available" : "Not Installed";
647
648#if VUO_PRO
649 if (getBase()->getNodeClass()->isPro())
650 {
651 QFontMetrics fm(font);
652 QRect stickerRect;
653 stickerRect.setSize(QSize(16,16));
654 stickerRect.moveCenter(frameRect.center().toPoint());
655 stickerRect.translate(-fm.boundingRect(text).width()/2 - 12, -2);
656 QIcon(":/Icons/menuitem-pro.svg").paint(painter, stickerRect);
657 }
658#endif
659
660 painter->setPen(colors->portTitle());
661 painter->setFont(font);
662 painter->drawText(frameRect, Qt::AlignCenter, text);
663 }
664
665 delete colors;
666}
667
671void VuoRendererNode::setMissingImplementation(bool missingImplementation)
672{
673 this->nodeIsMissing = missingImplementation;
674}
675
680{
681 return nodeIsMissing;
682}
683
688{
689 this->proxyNode = proxyNode;
690}
691
696{
697 return proxyNode;
698}
699
704{
705 if (!proxyNode)
706 return NULL;
707
708 foreach (VuoPort *p, proxyNode->getBase()->getInputPorts())
709 {
710 if (p->hasRenderer())
711 {
713 if (tp && (tp->getUncollapsedTypecastNode()->getBase() == this->getBase()))
714 return tp;
715 }
716 }
717
718 return NULL;
719}
720
724QVariant VuoRendererNode::itemChange(GraphicsItemChange change, const QVariant &value)
725{
726 QVariant newValue = value;
727
728 if (change == QGraphicsItem::ItemPositionChange)
729 {
730 setCacheModeForConnectedCables(QGraphicsItem::NoCache);
731
732 if (getSnapToGrid() && !dynamic_cast<VuoRendererInputAttachment *>(this))
733 {
734 // Quantize position to nearest minor gridline.
736 }
737 else
738 {
739 // Quantize position to whole pixels.
740 newValue = value.toPoint();
741 }
742
744
745 for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
746 {
747 (*it)->setCacheModeForPortAndChildren(QGraphicsItem::NoCache);
748 (*it)->updateGeometry();
749 (*it)->setCacheModeForPortAndChildren(getCurrentDefaultCacheMode());
750 }
751
752 for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
753 {
754 (*it)->setCacheModeForPortAndChildren(QGraphicsItem::NoCache);
755 (*it)->updateGeometry();
756 (*it)->setCacheModeForPortAndChildren(getCurrentDefaultCacheMode());
757 }
758
760 n->updateGeometry();
761
763 }
764
765 // Node has moved within its parent
766 if (change == QGraphicsItem::ItemPositionHasChanged)
767 {
768 QPointF newPos = value.toPointF();
769 if ((getBase()->getX() != newPos.x()) || (getBase()->getY() != newPos.y()))
770 {
771 qreal dx = newPos.x() - this->getBase()->getX();
772 qreal dy = newPos.y() - this->getBase()->getY();
773
774 this->getBase()->setX(newPos.x());
775 this->getBase()->setY(newPos.y());
776
777 if (signaler)
778 {
779 set<VuoRendererNode *> movedNodes;
780
781 // Attachments follow their host nodes' movements automatically.
782 // Pushing their moves onto the 'Undo' stack breaks other 'Undo' commands.
783 if (!dynamic_cast<VuoRendererInputAttachment *>(this))
784 {
785 movedNodes.insert(this);
786 signaler->signalNodesMoved(movedNodes, dx, dy, true);
787 }
788 }
789
791 }
792
793 return newPos;
794 }
795
796 if (change == QGraphicsItem::ItemSelectedHasChanged)
797 {
798 setCacheModeForConnectedCables(QGraphicsItem::NoCache);
799
800 // When the node is (de)selected, repaint all ports, attachments, and cables (since they also reflect selection status).
801 for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
802 {
803 (*it)->update();
804 foreach (VuoRendererInputAttachment *attachment, (*it)->getAllUnderlyingUpstreamInputAttachments())
805 {
806 attachment->setCacheModeForConnectedCables(QGraphicsItem::NoCache);
807 attachment->update();
808 foreach (VuoRendererPort *attachmentInputPort, attachment->getInputPorts())
809 {
810 attachmentInputPort->update();
811 auto rtp = dynamic_cast<VuoRendererTypecastPort *>(attachmentInputPort);
812 if (rtp)
813 rtp->getChildPort()->update();
814 }
815
816 attachment->updateConnectedCableGeometry();
818 }
819
820 auto rtp = dynamic_cast<VuoRendererTypecastPort *>(*it);
821 if (rtp)
822 rtp->getChildPort()->update();
823 }
824 for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
825 (*it)->update();
826
829 }
830
831 return QGraphicsItem::itemChange(change, newValue);
832}
833
837void VuoRendererNode::hoverEnterEvent(QGraphicsSceneHoverEvent * event)
838{
839 hoverMoveEvent(event);
840}
841
845void VuoRendererNode::hoverMoveEvent(QGraphicsSceneHoverEvent * event)
846{
847 // If the cursor is currently positioned over the node title rectangle,
848 // give the node keyboard focus.
849 if (nodeTitleBoundingRect.contains(event->pos()))
850 setFocus();
851
852 // Othewise, release focus.
853 else
854 clearFocus();
855}
856
860void VuoRendererNode::hoverLeaveEvent(QGraphicsSceneHoverEvent * event)
861{
862 clearFocus();
863}
864
868void VuoRendererNode::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
869{
870 if (event->button() == Qt::LeftButton)
871 {
872 // Display the node popover, as long as the mouse release did not mark the end of a drag.
873 if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton)).length() < QApplication::startDragDistance())
875 }
876
877 QGraphicsItem::mouseReleaseEvent(event);
878}
879
883void VuoRendererNode::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
884{
885 // If the double-click occurs on the node title box,
886 // open up a text editor to modify the title.
887 if (nodeTitleBoundingRect.contains(event->pos()))
889
890 // If the node is editable, open the source for editing.
891 else
892 {
893 VuoNodeClass *nodeClass = getBase()->getNodeClass();
894 if (nodeClass->hasCompiler() && VuoFileUtilities::fileExists(nodeClass->getCompiler()->getSourcePath()))
896 }
897}
898
902void VuoRendererNode::keyPressEvent(QKeyEvent *event)
903{
904 // If the user presses 'Return' while the node has keyboard focus,
905 // open up a text editor to modify the title.
906 if (event->key() == Qt::Key_Return)
907 {
909 setFocus();
910 }
911}
912
919set<VuoCable *> VuoRendererNode::getConnectedCables(bool includePublishedCables)
920{
921 set<VuoCable *> connectedInputCables = getConnectedInputCables(includePublishedCables);
922 set<VuoCable *> connectedOutputCables = getConnectedOutputCables(includePublishedCables);
923
924 set<VuoCable *> connectedCables;
925 connectedCables.insert(connectedInputCables.begin(), connectedInputCables.end());
926 connectedCables.insert(connectedOutputCables.begin(), connectedOutputCables.end());
927
928 return connectedCables;
929}
930
936set<VuoCable *> VuoRendererNode::getConnectedInputCables(bool includePublishedCables)
937{
938 set<VuoCable *> connectedInputCables;
939
940 vector<VuoPort *> inputPorts = this->getBase()->getInputPorts();
941 for(vector<VuoPort *>::iterator inputPort = inputPorts.begin(); inputPort != inputPorts.end(); ++inputPort)
942 {
943 vector<VuoCable *> inputCables = (*inputPort)->getConnectedCables(includePublishedCables);
944 connectedInputCables.insert(inputCables.begin(), inputCables.end());
945
946 if ((*inputPort)->hasRenderer())
947 {
948 VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>((*inputPort)->getRenderer());
949 if (typecastPort)
950 {
951 vector<VuoCable *> childPortInputCables = typecastPort->getChildPort()->getBase()->getConnectedCables(includePublishedCables);
952 connectedInputCables.insert(childPortInputCables.begin(), childPortInputCables.end());
953 }
954 }
955 }
956
957 return connectedInputCables;
958}
959
964set<VuoCable *> VuoRendererNode::getConnectedOutputCables(bool includePublishedCables)
965{
966 set<VuoCable *> connectedOutputCables;
967
968 vector<VuoPort *> outputPorts = this->getBase()->getOutputPorts();
969 for(vector<VuoPort *>::iterator outputPort = outputPorts.begin(); outputPort != outputPorts.end(); ++outputPort)
970 {
971 vector<VuoCable *> outputCables = (*outputPort)->getConnectedCables(includePublishedCables);
972 connectedOutputCables.insert(outputCables.begin(), outputCables.end());
973 }
974
975 return connectedOutputCables;
976}
977
982{
983 this->prepareGeometryChange();
985
986 for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
987 (*it)->updateGeometry();
988
989 for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
990 (*it)->updateGeometry();
991}
992
997{
998 set<VuoCable *> connectedCables = getConnectedCables(true);
999 for (set<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
1000 {
1001 if (! (*cable)->hasRenderer())
1002 continue; // in case cable has not yet been added to the composition, when constructing a VuoRendererComposition from an existing set of nodes and cables
1003
1004 VuoRendererCable *rc = (*cable)->getRenderer();
1005 rc->updateGeometry();
1006 }
1007}
1008
1014vector<VuoRendererPort *> &VuoRendererNode::getInputPorts(void)
1015{
1016 return inputPorts;
1017}
1018
1024vector<VuoRendererPort *> &VuoRendererNode::getOutputPorts(void)
1025{
1026 return outputPorts;
1027}
1028
1033vector<pair<QString, json_object *> > VuoRendererNode::getConstantPortValues()
1034{
1035 vector<pair<QString, json_object *> > portNamesAndValues;
1036
1037 for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
1038 {
1039 VuoRendererPort *port = *it;
1040 if (port->isConstant())
1041 {
1042 QString name = port->getBase()->getClass()->getName().c_str();
1043 json_object *value = json_tokener_parse(port->getConstantAsString().c_str());
1044 portNamesAndValues.push_back(make_pair(name, value));
1045 }
1046 }
1047
1048 return portNamesAndValues;
1049}
1050
1055{
1056 vector<VuoRendererPort *>::iterator it = find(inputPorts.begin(), inputPorts.end(), oldPort);
1057 *it = newPort;
1058
1059 newPort->setParentItem(this);
1060 newPort->stackBefore(oldPort);
1061 oldPort->updateGeometry();
1062 scene()->removeItem(oldPort);
1063 layoutPorts();
1065}
1066
1073{
1074 vector<VuoRendererPort *>::iterator it = find(inputPorts.begin(), inputPorts.end(), newPort);
1075 if (it != inputPorts.end())
1076 inputPorts.erase(it);
1077
1078 inputPorts.push_back(newPort);
1079 newPort->setParentItem(this);
1080 layoutPorts();
1082}
1083
1088{
1089 foreach (VuoPort *port, getBase()->getInputPorts())
1090 {
1091 if (port->hasCompiler())
1092 {
1093 VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(port->getCompiler());
1094 if (dynamic_cast<VuoGenericType *>(compilerPort->getDataVuoType()))
1095 return true;
1096 }
1097 }
1098
1099 foreach (VuoPort *port, getBase()->getOutputPorts())
1100 {
1101 if (port->hasCompiler())
1102 {
1103 VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(port->getCompiler());
1104 if (dynamic_cast<VuoGenericType *>(compilerPort->getDataVuoType()))
1105 return true;
1106 }
1107 }
1108
1109 return false;
1110}
1111
1116{
1118 getBase()->setTitle(title);
1120 layoutPorts();
1121}
1122
1128{
1130}
1131
1136{
1137 this->timeLastExecutionEnded = VuoRendererItem::activityInProgress;
1138}
1139
1144{
1145 this->timeLastExecutionEnded = QDateTime::currentMSecsSinceEpoch();
1146}
1147
1153{
1154 return this->timeLastExecutionEnded;
1155}
1156
1160void VuoRendererNode::setCacheModeForNodeAndPorts(QGraphicsItem::CacheMode mode)
1161{
1162 // Caching is currently disabled for VuoRendererInputAttachments; see
1163 // https://b33p.net/kosada/node/6286 and https://b33p.net/kosada/node/6064 .
1164 if (dynamic_cast<VuoRendererInputAttachment *>(this))
1165 this->setCacheMode(QGraphicsItem::NoCache);
1166 else
1167 this->setCacheMode(mode);
1168
1169 for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
1170 (*it)->setCacheModeForPortAndChildren(mode);
1171
1172 for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
1173 (*it)->setCacheModeForPortAndChildren(mode);
1174}
1175
1179void VuoRendererNode::setCacheModeForConnectedCables(QGraphicsItem::CacheMode mode)
1180{
1181 set<VuoCable *> connectedCables = getConnectedCables(true);
1182 for (set<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
1183 {
1184 if (! (*cable)->hasRenderer())
1185 continue; // in case cable has not yet been added to the composition, when constructing a VuoRendererComposition from an existing set of nodes and cables
1186
1187 VuoRendererCable *rc = (*cable)->getRenderer();
1188 rc->setCacheMode(mode);
1189 }
1190}
1191
1199{
1200 if (this->alwaysDisplayPortNames != displayPortNames)
1201 {
1202 this->alwaysDisplayPortNames = displayPortNames;
1203
1204 foreach (VuoPort *p, getBase()->getInputPorts())
1206
1207 foreach (VuoPort *p, getBase()->getOutputPorts())
1209
1211 layoutPorts();
1212 }
1213}
1214
1219{
1220 if (port->getOutput())
1222
1223 else // if (!port->getOutput())
1225}
1226
1231{
1233 return true;
1234
1236 return true;
1237
1238 bool nodeHasUnambiguousInput = ((getBase()->getInputPorts().size() == VuoNodeClass::unreservedInputPortStartIndex + 1) &&
1239 (!getBase()->getInputPorts()[VuoNodeClass::unreservedInputPortStartIndex]->getRenderer()->hasPortAction()));
1240 if (!nodeHasUnambiguousInput)
1241 return true;
1242
1243 // Don't hide port labels if the node contains any trigger ports.
1244 foreach (VuoPort *outputPort, getBase()->getOutputPorts())
1245 {
1246 if (outputPort->getClass()->getPortType() == VuoPortClass::triggerPort)
1247 return true;
1248 }
1249
1250 return false;
1251}
1252
1257{
1259 return true;
1260
1261 bool nodeHasUnambiguousOutput = ((getBase()->getOutputPorts().size() == VuoNodeClass::unreservedOutputPortStartIndex + 1) &&
1263
1264 return !nodeHasUnambiguousOutput;
1265}
1266
1272{
1273 return _eligibilityHighlight;
1274}
1275
1281{
1282 _eligibilityHighlight = eligibility;
1283}