Vuo  2.1.0
VuoRendererNode.cc
Go to the documentation of this file.
1 
10 #include "VuoRendererNode.hh"
12 #include "VuoRendererSignaler.hh"
13 #include "VuoCompiler.hh"
14 #include "VuoCompilerNodeClass.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 
25 const qreal VuoRendererNode::cornerRadius = 10 /*VuoRendererFonts::thickPenWidth/2.0*/;
26 const qreal VuoRendererNode::outerBorderWidth = 1.;
27 const qreal VuoRendererNode::nodeTitleHeight = 18 /*round(VuoRendererFonts::nodeTitleFontSize + VuoRendererFonts::thickPenWidth*1./8.) + 2*/;
28 const qreal VuoRendererNode::nodeTitleHorizontalMargin = 12 /*VuoRendererFonts::thickPenWidth/2.0 + 2*/;
29 const qreal VuoRendererNode::nodeClassHeight = 12 /*round(VuoRendererFonts::thickPenWidth*3./5.)*/;
30 const qreal VuoRendererNode::nodeHeaderYOffset = -nodeTitleHeight - nodeClassHeight;
31 const 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());
97  layoutPorts();
98 
99  this->signaler = signaler;
100 }
101 
102 void 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 
119 void 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 
214 void 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  drawer->setSelected(this->isSelected());
233 
234  // Update the list's cached bounding rect, so long arms don't disappear when the ports scroll offscreen.
235  auto listDrawer = dynamic_cast<VuoRendererInputListDrawer *>(drawer);
236  if (listDrawer)
237  listDrawer->updateGeometry();
238  }
239 }
240 
244 void VuoRendererNode::drawNodeFrame(QPainter *painter, QRectF nodeInnerFrameRect, VuoRendererColors *colors) const
245 {
246  QPainterPath nodeOuterFrame = this->nodeFrames.first;
247  QPainterPath nodeInnerFrame = this->nodeFrames.second;
248 
249  // Paint the node body background.
250  painter->fillPath(nodeInnerFrame, colors->nodeFill());
251 
252  // Paint the node header background.
253  // This gets painted after the body, so the parts outside the
254  // header's rounded bottom corners show through to the body.
255  painter->fillPath(nodeOuterFrame, colors->nodeFrame());
256 
257  // Paint the subcomposition icon.
258  if (nodeIsSubcomposition)
259  painter->fillPath(this->subcompositionIndicatorPath, QBrush(QColor::fromRgb(255,255,255,128)));
260 }
261 
265 QPair<QPainterPath, QPainterPath> VuoRendererNode::getNodeFrames(QRectF nodeInnerFrameRect)
266 {
267  // Calculate the bounds of the outer frame (header background).
268  QRectF nodeOuterFrameRect;
269  {
270  qreal nodeOuterFrameTopY = nodeInnerFrameRect.top() + VuoRendererNode::nodeHeaderYOffset;
271  nodeOuterFrameRect = QRectF(
272  nodeInnerFrameRect.left() - VuoRendererNode::outerBorderWidth,
273  nodeOuterFrameTopY,
274  nodeInnerFrameRect.width() + 2*VuoRendererNode::outerBorderWidth,
275  -VuoRendererNode::nodeHeaderYOffset// + VuoRendererNode::outerBorderWidth
276  );
277  }
278 
279  // Calculate the outer and inner frames.
280  QPainterPath nodeOuterFrame;
281  qreal nodeHeaderBottomCornerRadius = VuoRendererNode::cornerRadius/4;
282  {
283  // The top of the outer frame (header) is rounded; the bottom is slightly rounded.
284  nodeOuterFrame.moveTo(nodeOuterFrameRect.right(), nodeOuterFrameRect.center().y());
285  addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.bottomRight(), nodeHeaderBottomCornerRadius, false, false);
286  addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.bottomLeft(), nodeHeaderBottomCornerRadius, false, true);
287  addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.topLeft(), VuoRendererNode::cornerRadius, true, true);
288  addRoundedCorner(nodeOuterFrame, true, nodeOuterFrameRect.topRight(), VuoRendererNode::cornerRadius, true, false);
289  }
290 
291  QPainterPath nodeInnerFrame;
292  {
293  // The bottom of the inner frame has rounded corners, to match the outer frame.
294  qreal bw = VuoRendererNode::outerBorderWidth;
295  nodeInnerFrame.moveTo(nodeInnerFrameRect.topLeft() - QPoint(bw, 0) - QPoint(0, nodeHeaderBottomCornerRadius));
296  nodeInnerFrame.lineTo(nodeInnerFrameRect.topRight() + QPoint(bw, 0) - QPoint(0, nodeHeaderBottomCornerRadius));
297  addRoundedCorner(nodeInnerFrame, true, nodeInnerFrameRect.bottomRight() + QPoint( bw,bw), VuoRendererNode::cornerRadius, false, false);
298  addRoundedCorner(nodeInnerFrame, true, nodeInnerFrameRect.bottomLeft() + QPoint(-bw,bw), VuoRendererNode::cornerRadius, false, true);
299  }
300 
301  return qMakePair(nodeOuterFrame, nodeInnerFrame);
302 }
303 
308 QPainterPath VuoRendererNode::getSubcompositionIndicatorPath(QRectF nodeInnerFrameRect, bool isSubcomposition)
309 {
310  if (!isSubcomposition)
311  return QPainterPath();
312 
313  const double rectSideLength = VuoRendererFonts::midPenWidth*2.5;
314 
315  // The icon should start at the node class label's baseline, and extend to match its ascender height.
316  QPainterPath rectPath;
317  rectPath.addRect(QRectF(-0.5 /* pixel-align */ + nodeInnerFrameRect.right() - iconRightOffset,
318  -4. - 1.5*rectSideLength,
319  rectSideLength,
320  rectSideLength));
321 
322  // Stroke the top-left rectangle.
323  QPainterPathStroker rectStroker;
324  QPainterPath topLeftStroke = rectStroker.createStroke(rectPath);
325 
326  // Stroke the bottom-right rectangle.
327  rectPath.translate(-0.5 /* pixel-align */ + rectSideLength/2,
328  -0.5 /* pixel-align */ + rectSideLength/2);
329  QPainterPath bottomRightStroke = rectStroker.createStroke(rectPath);
330 
331  // Unite the two rectangles (rather than overdrawing semitransparent strokes), so the icon is a uniform color.
332  return topLeftStroke.united(bottomRightStroke);
333 }
334 
338 qreal VuoRendererNode::getInputDrawerOffset(unsigned int portIndex) const
339 {
340  qreal maxOffset = 0;
341  vector<VuoPort *> inputPorts = this->getBase()->getInputPorts();
342 
343  for (unsigned int i = inputPorts.size()-1; i > portIndex; --i)
344  {
345  VuoRendererPort *port = inputPorts[i]->getRenderer();
346 
347  if (port->getAttachedInputDrawer())
348  maxOffset += port->getAttachedInputDrawer()->getMaxDrawerChainedLabelWidth() - 5;
349  else if (dynamic_cast<VuoRendererTypecastPort *>(port))
350  {
352  qreal currentTypecastOffset = tp->getPortPath(true, true).boundingRect().width()
353  + (VuoRendererPort::portRadius-2) // Account for typecast child port.
355 
356  if (currentTypecastOffset > maxOffset)
357  maxOffset = currentTypecastOffset;
358  }
359  else if (port->isConstant())
360  {
361  qreal currentConstantOffset = port->getPortPath().boundingRect().width()
364 
365  if (currentConstantOffset > maxOffset)
366  maxOffset = currentConstantOffset;
367  }
368  else if (!port->getBase()->getConnectedCables(true).empty()) // Simple circle port with cable(s) attached (leave room for the cable's rounded corner).
369  maxOffset = fmax(maxOffset, VuoRendererPort::portRadius * 2 + 3);
370  else // Simple circle port with no cables.
371  maxOffset = fmax(maxOffset, VuoRendererPort::portRadius );
372  }
373 
374  qreal targetDrawerOffset = inputPorts[portIndex]->getRenderer()->getAttachedInputDrawer()->getMaxDrawerLabelWidth()
376 
377  return targetDrawerOffset + maxOffset;
378 }
379 
383 vector<VuoRendererInputDrawer *> VuoRendererNode::getAttachedInputDrawers(void) const
384 {
385  vector<VuoRendererInputDrawer *> drawers;
386  vector<VuoPort *> inputPorts = this->getBase()->getInputPorts();
387  for(unsigned int i = 0; i < inputPorts.size(); ++i)
388  {
389  VuoRendererInputDrawer *drawer = inputPorts[i]->getRenderer()->getAttachedInputDrawer();
390  if (drawer)
391  drawers.push_back(drawer);
392  }
393 
394  return drawers;
395 }
396 
403 {
404  QRectF updatedFrameRect;
405  QString nodeTitle = QString::fromUtf8(getBase()->getTitle().c_str());
406 
407  // QPainter::drawText expects strings to be canonically composed,
408  // else it renders diacritics next to (instead of superimposed upon) their base glyphs.
409  nodeTitle = nodeTitle.normalized(QString::NormalizationForm_C);
410 
411  // Width is the longest string or combined input+output port row.
412  qreal maxPortRowWidth=0;
413 
414  size_t inputPortCount = inputPorts.size();
415  size_t outputPortCount = outputPorts.size();
416  size_t portRowCount = max(inputPortCount, outputPortCount);
417  for (int portRow = 0; portRow < portRowCount; ++portRow)
418  {
419  // Skip port rows containing reserved (refresh/function) ports, which don't affect the node's width.
420  int adjustedInputPortRow = portRow + VuoNodeClass::unreservedInputPortStartIndex;
421  int adjustedOutputPortRow = portRow + VuoNodeClass::unreservedOutputPortStartIndex;
422 
423  qreal thisRowWidth = 0;
424  if (adjustedInputPortRow < inputPortCount)
425  {
426  VuoRendererPort *port = inputPorts[adjustedInputPortRow];
427  thisRowWidth += port->getNameRect().x() + port->getNameRect().width();
428 
429  if (port->hasPortAction())
430  thisRowWidth += port->getActionIndicatorRect().width();
431  }
432 
433  if (adjustedOutputPortRow < outputPortCount)
434  thisRowWidth += outputPorts[adjustedOutputPortRow]->getNameRect().width();
435 
436  if(thisRowWidth > maxPortRowWidth)
437  maxPortRowWidth = thisRowWidth;
438  }
439 
440  qreal nodeTitleWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodeTitleFont()).boundingRect(nodeTitle).width();
441  qreal nodeClassWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodeClassFont()).boundingRect(nodeClass).width();
442 
443  if (this->nodeIsSubcomposition)
444  nodeClassWidth += VuoRendererNode::iconRightOffset;
445 
446  bool hasInputPorts = inputPortCount > VuoNodeClass::unreservedInputPortStartIndex;
447  bool hasOutputPorts = outputPortCount > VuoNodeClass::unreservedOutputPortStartIndex;
448  int unalignedFrameWidth =
449  floor(max(
450  max(
451  nodeTitleWidth
452  + nodeTitleHorizontalMargin * 2.
453  // Line up right edge of text with right edge of subcomposition icon
454  - 3,
455 
456  nodeClassWidth
457  // Padding between left edge of node frame and left edge of text
459  // Padding between right edge of text and right edge of node frame
461  // Line up right edge of text with where the right edge of the subcomposition icon would be if it were present
462  - 3
463  ),
464  maxPortRowWidth
465 
466  // Leave some space between the right edge of the input port name and the left edge of the output port name.
467  + ((hasInputPorts && hasOutputPorts) ? (nameDisplayEnabledForInputPorts() && nameDisplayEnabledForOutputPorts()?
470  0.)
471 
472 
473  + 1.
474  ));
475 
476  // Snap to the next-widest horizontal grid position so that node columns can be right-justified.
477  int alignedFrameWidth = VuoRendererComposition::quantizeToNearestGridLine(QPointF(unalignedFrameWidth, 0), VuoRendererComposition::minorGridLineSpacing).x()
478  - 2*VuoRendererNode::outerBorderWidth;
479 
480  if (alignedFrameWidth < unalignedFrameWidth)
482 
483  updatedFrameRect.setWidth(alignedFrameWidth);
484 
485  // Height depends only on the number of input/output ports.
486  updatedFrameRect.setHeight(
487  floor(
488  max(
489  inputPortCount - VuoNodeClass::unreservedInputPortStartIndex, // don't count the refresh port
490  outputPortCount - VuoNodeClass::unreservedOutputPortStartIndex // don't count the function port
493  )
494  );
495 
496  this->frameRect = updatedFrameRect;
497  this->subcompositionIndicatorPath = getSubcompositionIndicatorPath(this->frameRect, this->nodeIsSubcomposition);
498  this->nodeFrames = getNodeFrames(this->frameRect);
499 }
500 
505 {
506  if (paintingDisabled())
507  return QRectF();
508 
509  QRectF r = frameRect;
510 
511  // Header
512  r.adjust(0, nodeHeaderYOffset, 0, 0);
513 
514  // Antialiasing bleed
515  r.adjust(-1,-1,1,1);
516 
517  return r.toAlignedRect();
518 }
519 
524 {
525  return this->proxyNode;
526 }
527 
534 QPointF VuoRendererNode::getPortPoint(VuoRendererPort *port, unsigned int portIndex) const
535 {
536  qreal x = (port->getOutput() ? frameRect.width() + VuoRendererPort::portRadius - 1 : -VuoRendererPort::portRadius);
537  qreal y;
538 
539  if (port->getRefreshPort())
540  {
542  }
543  else if (port->getFunctionPort())
544  {
546  }
547  else
548  {
549  qreal adjustedPortIndex = (qreal)portIndex - (port->getOutput() ?
551  VuoNodeClass::unreservedInputPortStartIndex); // Skip the refresh/function port.
552 
553  qreal portOffset = adjustedPortIndex + 0.5; // Center the port within its space.
555  }
556 
557  return QPointF(x, y);
558 }
559 
564 {
565  if (paintingDisabled())
566  return QRectF();
567 
568  return nodeFrames.first.boundingRect();
569 }
570 
576 void VuoRendererNode::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
577 {
578  if (paintingDisabled())
579  return;
580 
581  drawBoundingRect(painter);
582 
583  VuoRendererColors::SelectionType selectionType = (isSelected()? VuoRendererColors::directSelection : VuoRendererColors::noSelection);
584  qint64 timeOfLastActivity = (getRenderActivity()? timeLastExecutionEnded : VuoRendererItem::notTrackingActivity);
585  VuoRendererColors *colors = new VuoRendererColors(getBase()->getTintColor(), selectionType, false, VuoRendererColors::noHighlight, timeOfLastActivity, nodeIsMissing);
586  drawNodeFrame(painter, frameRect, colors);
587 
588  // Node Title
589  {
590  QString nodeTitle = QString::fromUtf8(getBase()->getTitle().c_str());
591 
592  // QPainter::drawText expects strings to be canonically composed,
593  // else it renders diacritics next to (instead of superimposed upon) their base glyphs.
594  nodeTitle = nodeTitle.normalized(QString::NormalizationForm_C);
595 
597 
598  nodeTitleBoundingRect = QRectF(
599  nodeTitleHorizontalMargin
600  - VuoRendererFonts::getHorizontalOffset(font, nodeTitle),
601  nodeHeaderYOffset + 2,
602  frameRect.width() + 4. - 2. * VuoRendererFonts::thickPenWidth/2., // Leave room for in-event and out-event ports.
603  nodeTitleHeight + 2.
604  );
605  painter->setPen(colors->nodeTitle());
606  painter->setFont(font);
607  painter->drawText(nodeTitleBoundingRect,Qt::AlignLeft,nodeTitle);
608  VuoRendererItem::drawRect(painter, nodeTitleBoundingRect);
609  }
610 
611  // Node Class
612  {
614 
615  QRectF r(
617  + 2.
618  - VuoRendererFonts::getHorizontalOffset(font, nodeClass),
619  -nodeClassHeight - 2.,
620  frameRect.width() + 4. - 2. * VuoRendererFonts::thickPenWidth/2., // Leave room for in-event and out-event ports.
621  nodeClassHeight + 2.
622  );
623  painter->setPen(colors->nodeClass());
624  painter->setFont(font);
625  painter->drawText(r,Qt::AlignLeft,nodeClass);
626  VuoRendererItem::drawRect(painter, r);
627  }
628 
629  if (nodeIsMissing)
630  {
631  bool notAvailable = getBase()->isForbidden();
632 #if VUO_PRO
633  notAvailable |= getBase()->getNodeClass()->isPro();
634 #endif
635  QString text = notAvailable ? "Not Available" : "Not Installed";
637 
638 #if VUO_PRO
639  if (getBase()->getNodeClass()->isPro())
640  {
641  QFontMetrics fm(font);
642  QRect stickerRect;
643  stickerRect.setSize(QSize(16,16));
644  stickerRect.moveCenter(frameRect.center().toPoint());
645  stickerRect.translate(-fm.width(text)/2 - 12, -2);
646  QIcon(":/Icons/menuitem-pro.svg").paint(painter, stickerRect);
647  }
648 #endif
649 
650  painter->setPen(colors->portTitle());
651  painter->setFont(font);
652  painter->drawText(frameRect, Qt::AlignCenter, text);
653  }
654 
655  delete colors;
656 }
657 
661 void VuoRendererNode::setMissingImplementation(bool missingImplementation)
662 {
663  this->nodeIsMissing = missingImplementation;
664 }
665 
670 {
671  return nodeIsMissing;
672 }
673 
678 {
679  this->proxyNode = proxyNode;
680 }
681 
686 {
687  return proxyNode;
688 }
689 
694 {
695  if (!proxyNode)
696  return NULL;
697 
698  foreach (VuoPort *p, proxyNode->getBase()->getInputPorts())
699  {
700  if (p->hasRenderer())
701  {
702  VuoRendererTypecastPort *tp = dynamic_cast<VuoRendererTypecastPort *>(p->getRenderer());
703  if (tp && (tp->getUncollapsedTypecastNode()->getBase() == this->getBase()))
704  return tp;
705  }
706  }
707 
708  return NULL;
709 }
710 
714 QVariant VuoRendererNode::itemChange(GraphicsItemChange change, const QVariant &value)
715 {
716  QVariant newValue = value;
717 
718  if (change == QGraphicsItem::ItemPositionChange)
719  {
720  setCacheModeForConnectedCables(QGraphicsItem::NoCache);
721 
722  if (getSnapToGrid() && !dynamic_cast<VuoRendererInputAttachment *>(this))
723  {
724  // Quantize position to nearest minor gridline.
726  }
727  else
728  {
729  // Quantize position to whole pixels.
730  newValue = value.toPoint();
731  }
732 
734 
735  for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
736  {
737  (*it)->setCacheModeForPortAndChildren(QGraphicsItem::NoCache);
738  (*it)->updateGeometry();
739  (*it)->setCacheModeForPortAndChildren(getCurrentDefaultCacheMode());
740  }
741 
742  for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
743  {
744  (*it)->setCacheModeForPortAndChildren(QGraphicsItem::NoCache);
745  (*it)->updateGeometry();
746  (*it)->setCacheModeForPortAndChildren(getCurrentDefaultCacheMode());
747  }
748 
750  n->updateGeometry();
751 
753  }
754 
755  // Node has moved within its parent
756  if (change == QGraphicsItem::ItemPositionHasChanged)
757  {
758  QPointF newPos = value.toPointF();
759  if ((getBase()->getX() != newPos.x()) || (getBase()->getY() != newPos.y()))
760  {
761  qreal dx = newPos.x() - this->getBase()->getX();
762  qreal dy = newPos.y() - this->getBase()->getY();
763 
764  this->getBase()->setX(newPos.x());
765  this->getBase()->setY(newPos.y());
766 
767  if (signaler)
768  {
769  set<VuoRendererNode *> movedNodes;
770 
771  // Attachments follow their host nodes' movements automatically.
772  // Pushing their moves onto the 'Undo' stack breaks other 'Undo' commands.
773  if (!dynamic_cast<VuoRendererInputAttachment *>(this))
774  {
775  movedNodes.insert(this);
776  signaler->signalNodesMoved(movedNodes, dx, dy, true);
777  }
778  }
779 
781  }
782 
783  return newPos;
784  }
785 
786  if (change == QGraphicsItem::ItemSelectedHasChanged)
787  {
788  setCacheModeForConnectedCables(QGraphicsItem::NoCache);
789 
790  // When the node is (de)selected, repaint all ports and cables (since they also reflect selection status).
791  for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
792  {
793  (*it)->update();
794 
795  auto rtp = dynamic_cast<VuoRendererTypecastPort *>(*it);
796  if (rtp)
797  rtp->getChildPort()->update();
798  }
799  for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
800  (*it)->update();
801 
803 
805  }
806 
807  return QGraphicsItem::itemChange(change, newValue);
808 }
809 
813 void VuoRendererNode::hoverEnterEvent(QGraphicsSceneHoverEvent * event)
814 {
815  hoverMoveEvent(event);
816 }
817 
821 void VuoRendererNode::hoverMoveEvent(QGraphicsSceneHoverEvent * event)
822 {
823  // If the cursor is currently positioned over the node title rectangle,
824  // give the node keyboard focus.
825  if (nodeTitleBoundingRect.contains(event->pos()))
826  setFocus();
827 
828  // Othewise, release focus.
829  else
830  clearFocus();
831 }
832 
836 void VuoRendererNode::hoverLeaveEvent(QGraphicsSceneHoverEvent * event)
837 {
838  clearFocus();
839 }
840 
844 void VuoRendererNode::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
845 {
846  if (event->button() == Qt::LeftButton)
847  {
848  // Display the node popover, as long as the mouse release did not mark the end of a drag.
849  if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton)).length() < QApplication::startDragDistance())
851  }
852 
853  QGraphicsItem::mouseReleaseEvent(event);
854 }
855 
859 void VuoRendererNode::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
860 {
861  // If the double-click occurs on the node title box,
862  // open up a text editor to modify the title.
863  if (nodeTitleBoundingRect.contains(event->pos()))
865 
866  // If the node is editable, open the source for editing.
867  else
868  {
869  VuoNodeClass *nodeClass = getBase()->getNodeClass();
870  if (nodeClass->hasCompiler() && VuoFileUtilities::fileExists(nodeClass->getCompiler()->getSourcePath()))
872  }
873 }
874 
878 void VuoRendererNode::keyPressEvent(QKeyEvent *event)
879 {
880  // If the user presses 'Return' while the node has keyboard focus,
881  // open up a text editor to modify the title.
882  if (event->key() == Qt::Key_Return)
883  {
885  setFocus();
886  }
887 }
888 
895 set<VuoCable *> VuoRendererNode::getConnectedCables(bool includePublishedCables)
896 {
897  set<VuoCable *> connectedInputCables = getConnectedInputCables(includePublishedCables);
898  set<VuoCable *> connectedOutputCables = getConnectedOutputCables(includePublishedCables);
899 
900  set<VuoCable *> connectedCables;
901  connectedCables.insert(connectedInputCables.begin(), connectedInputCables.end());
902  connectedCables.insert(connectedOutputCables.begin(), connectedOutputCables.end());
903 
904  return connectedCables;
905 }
906 
912 set<VuoCable *> VuoRendererNode::getConnectedInputCables(bool includePublishedCables)
913 {
914  set<VuoCable *> connectedInputCables;
915 
916  vector<VuoPort *> inputPorts = this->getBase()->getInputPorts();
917  for(vector<VuoPort *>::iterator inputPort = inputPorts.begin(); inputPort != inputPorts.end(); ++inputPort)
918  {
919  vector<VuoCable *> inputCables = (*inputPort)->getConnectedCables(includePublishedCables);
920  connectedInputCables.insert(inputCables.begin(), inputCables.end());
921 
922  if ((*inputPort)->hasRenderer())
923  {
924  VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>((*inputPort)->getRenderer());
925  if (typecastPort)
926  {
927  vector<VuoCable *> childPortInputCables = typecastPort->getChildPort()->getBase()->getConnectedCables(includePublishedCables);
928  connectedInputCables.insert(childPortInputCables.begin(), childPortInputCables.end());
929  }
930  }
931  }
932 
933  return connectedInputCables;
934 }
935 
940 set<VuoCable *> VuoRendererNode::getConnectedOutputCables(bool includePublishedCables)
941 {
942  set<VuoCable *> connectedOutputCables;
943 
944  vector<VuoPort *> outputPorts = this->getBase()->getOutputPorts();
945  for(vector<VuoPort *>::iterator outputPort = outputPorts.begin(); outputPort != outputPorts.end(); ++outputPort)
946  {
947  vector<VuoCable *> outputCables = (*outputPort)->getConnectedCables(includePublishedCables);
948  connectedOutputCables.insert(outputCables.begin(), outputCables.end());
949  }
950 
951  return connectedOutputCables;
952 }
953 
958 {
959  this->prepareGeometryChange();
961 
962  for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
963  (*it)->updateGeometry();
964 
965  for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
966  (*it)->updateGeometry();
967 }
968 
973 {
974  set<VuoCable *> connectedCables = getConnectedCables(true);
975  for (set<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
976  {
977  if (! (*cable)->hasRenderer())
978  continue; // in case cable has not yet been added to the composition, when constructing a VuoRendererComposition from an existing set of nodes and cables
979 
980  VuoRendererCable *rc = (*cable)->getRenderer();
981  rc->updateGeometry();
982  }
983 }
984 
990 vector<VuoRendererPort *> &VuoRendererNode::getInputPorts(void)
991 {
992  return inputPorts;
993 }
994 
1000 vector<VuoRendererPort *> &VuoRendererNode::getOutputPorts(void)
1001 {
1002  return outputPorts;
1003 }
1004 
1009 vector<pair<QString, json_object *> > VuoRendererNode::getConstantPortValues()
1010 {
1011  vector<pair<QString, json_object *> > portNamesAndValues;
1012 
1013  for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
1014  {
1015  VuoRendererPort *port = *it;
1016  if (port->isConstant())
1017  {
1018  QString name = port->getBase()->getClass()->getName().c_str();
1019  json_object *value = json_tokener_parse(port->getConstantAsString().c_str());
1020  portNamesAndValues.push_back(make_pair(name, value));
1021  }
1022  }
1023 
1024  return portNamesAndValues;
1025 }
1026 
1031 {
1032  vector<VuoRendererPort *>::iterator it = find(inputPorts.begin(), inputPorts.end(), oldPort);
1033  *it = newPort;
1034 
1035  newPort->setParentItem(this);
1036  newPort->stackBefore(oldPort);
1037  oldPort->updateGeometry();
1038  scene()->removeItem(oldPort);
1039  layoutPorts();
1041 }
1042 
1049 {
1050  vector<VuoRendererPort *>::iterator it = find(inputPorts.begin(), inputPorts.end(), newPort);
1051  if (it != inputPorts.end())
1052  inputPorts.erase(it);
1053 
1054  inputPorts.push_back(newPort);
1055  newPort->setParentItem(this);
1056  layoutPorts();
1058 }
1059 
1064 {
1065  foreach (VuoPort *port, getBase()->getInputPorts())
1066  {
1067  if (port->hasCompiler())
1068  {
1069  VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(port->getCompiler());
1070  if (dynamic_cast<VuoGenericType *>(compilerPort->getDataVuoType()))
1071  return true;
1072  }
1073  }
1074 
1075  foreach (VuoPort *port, getBase()->getOutputPorts())
1076  {
1077  if (port->hasCompiler())
1078  {
1079  VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(port->getCompiler());
1080  if (dynamic_cast<VuoGenericType *>(compilerPort->getDataVuoType()))
1081  return true;
1082  }
1083  }
1084 
1085  return false;
1086 }
1087 
1091 void VuoRendererNode::setTitle(string title)
1092 {
1093  updateGeometry();
1094  getBase()->setTitle(title);
1096  layoutPorts();
1097 }
1098 
1104 {
1106 }
1107 
1112 {
1113  this->timeLastExecutionEnded = VuoRendererItem::activityInProgress;
1114 }
1115 
1120 {
1121  this->timeLastExecutionEnded = QDateTime::currentMSecsSinceEpoch();
1122 }
1123 
1129 {
1130  return this->timeLastExecutionEnded;
1131 }
1132 
1136 void VuoRendererNode::setCacheModeForNodeAndPorts(QGraphicsItem::CacheMode mode)
1137 {
1138  // Caching is currently disabled for VuoRendererInputAttachments; see
1139  // https://b33p.net/kosada/node/6286 and https://b33p.net/kosada/node/6064 .
1140  if (dynamic_cast<VuoRendererInputAttachment *>(this))
1141  this->setCacheMode(QGraphicsItem::NoCache);
1142  else
1143  this->setCacheMode(mode);
1144 
1145  for (vector<VuoRendererPort *>::iterator it = inputPorts.begin(); it != inputPorts.end(); ++it)
1146  (*it)->setCacheModeForPortAndChildren(mode);
1147 
1148  for (vector<VuoRendererPort *>::iterator it = outputPorts.begin(); it != outputPorts.end(); ++it)
1149  (*it)->setCacheModeForPortAndChildren(mode);
1150 }
1151 
1155 void VuoRendererNode::setCacheModeForConnectedCables(QGraphicsItem::CacheMode mode)
1156 {
1157  set<VuoCable *> connectedCables = getConnectedCables(true);
1158  for (set<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
1159  {
1160  if (! (*cable)->hasRenderer())
1161  continue; // in case cable has not yet been added to the composition, when constructing a VuoRendererComposition from an existing set of nodes and cables
1162 
1163  VuoRendererCable *rc = (*cable)->getRenderer();
1164  rc->setCacheMode(mode);
1165  }
1166 }
1167 
1175 {
1176  if (this->alwaysDisplayPortNames != displayPortNames)
1177  {
1178  this->alwaysDisplayPortNames = displayPortNames;
1179 
1180  foreach (VuoPort *p, getBase()->getInputPorts())
1181  p->getRenderer()->updateNameRect();
1182 
1183  foreach (VuoPort *p, getBase()->getOutputPorts())
1184  p->getRenderer()->updateNameRect();
1185 
1187  layoutPorts();
1188  }
1189 }
1190 
1195 {
1196  if (port->getOutput())
1198 
1199  else // if (!port->getOutput())
1201 }
1202 
1207 {
1209  return true;
1210 
1212  return true;
1213 
1214  bool nodeHasUnambiguousInput = ((getBase()->getInputPorts().size() == VuoNodeClass::unreservedInputPortStartIndex + 1) &&
1215  (!getBase()->getInputPorts()[VuoNodeClass::unreservedInputPortStartIndex]->getRenderer()->hasPortAction()));
1216  if (!nodeHasUnambiguousInput)
1217  return true;
1218 
1219  // Don't hide port labels if the node contains any trigger ports.
1220  foreach (VuoPort *outputPort, getBase()->getOutputPorts())
1221  {
1222  if (outputPort->getClass()->getPortType() == VuoPortClass::triggerPort)
1223  return true;
1224  }
1225 
1226  return false;
1227 }
1228 
1233 {
1235  return true;
1236 
1237  bool nodeHasUnambiguousOutput = ((getBase()->getOutputPorts().size() == VuoNodeClass::unreservedOutputPortStartIndex + 1) &&
1239 
1240  return !nodeHasUnambiguousOutput;
1241 }
1242 
1248 {
1249  return _eligibilityHighlight;
1250 }
1251 
1257 {
1258  _eligibilityHighlight = eligibility;
1259 }