Vuo  2.3.2
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 
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 
243 void 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 
264 QPair<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 
307 QPainterPath 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 
337 qreal 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 
382 vector<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 
544 QPointF 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 
586 void 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),
611  nodeHeaderYOffset + 2,
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.width(text)/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 
671 void 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  {
712  VuoRendererTypecastPort *tp = dynamic_cast<VuoRendererTypecastPort *>(p->getRenderer());
713  if (tp && (tp->getUncollapsedTypecastNode()->getBase() == this->getBase()))
714  return tp;
715  }
716  }
717 
718  return NULL;
719 }
720 
724 QVariant 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 
837 void VuoRendererNode::hoverEnterEvent(QGraphicsSceneHoverEvent * event)
838 {
839  hoverMoveEvent(event);
840 }
841 
845 void 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 
860 void VuoRendererNode::hoverLeaveEvent(QGraphicsSceneHoverEvent * event)
861 {
862  clearFocus();
863 }
864 
868 void 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 
883 void 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 
902 void 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 
919 set<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 
936 set<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 
964 set<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 
1014 vector<VuoRendererPort *> &VuoRendererNode::getInputPorts(void)
1015 {
1016  return inputPorts;
1017 }
1018 
1024 vector<VuoRendererPort *> &VuoRendererNode::getOutputPorts(void)
1025 {
1026  return outputPorts;
1027 }
1028 
1033 vector<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 
1115 void VuoRendererNode::setTitle(string title)
1116 {
1117  updateGeometry();
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 
1160 void 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 
1179 void 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())
1205  p->getRenderer()->updateNameRect();
1206 
1207  foreach (VuoPort *p, getBase()->getOutputPorts())
1208  p->getRenderer()->updateNameRect();
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 }