Vuo  2.0.2
VuoRendererPort.cc
Go to the documentation of this file.
1 
10 #include "VuoStringUtilities.hh"
11 
13 #include "VuoRendererFonts.hh"
14 #include "VuoRendererPort.hh"
15 #include "VuoRendererSignaler.hh"
17 
21 
22 #include "VuoGenericType.hh"
23 #include "VuoNodeClass.hh"
24 
25 extern "C" {
26 #include "VuoAnchor.h"
27 #include "VuoImage.h"
28 #include "VuoIntegerRange.h"
29 #include "VuoRange.h"
30 #include "VuoScreen.h"
31 #include "VuoTransform.h"
32 #include "VuoUrl.h"
33 #include "VuoSpeechVoice.h"
34 }
35 
36 #pragma clang diagnostic push
37 #pragma clang diagnostic ignored "-Wdocumentation"
38  #include <json-c/json.h>
39 #pragma clang diagnostic pop
40 
41 
42 #include "VuoHeap.h"
43 
44 const qreal VuoRendererPort::portRadius = 8; // VuoRendererFonts::thickPenWidth*8./20.
45 const qreal VuoRendererPort::portSpacing = 15; // VuoRendererFonts::thickPenWidth*3.0/4.0
46 const qreal VuoRendererPort::portContainerMargin = 3.333333; // VuoRendererFonts::thickPenWidth / 6.
47 const qreal VuoRendererPort::portInset = 1.4;
48 const qreal VuoRendererPort::portBarrierWidth = 5.5; // VuoRendererFonts::thickPenWidth*5.5/20.
49 const qreal VuoRendererPort::portConstantTextPadding = 6.5; // VuoRendererFonts::thickPenWidth*6.5/20.
50 
55  bool isOutput, bool isRefreshPort, bool isFunctionPort)
56  : VuoBaseDetail<VuoPort>("VuoRendererPort standard", basePort)
57 {
58  getBase()->setRenderer(this);
59 
60  setZValue(portZValue);
61 
62  this->signaler = signaler;
63 
64  this->isOutput = isOutput;
65  this->isRefreshPort = isRefreshPort;
66  this->isFunctionPort = isFunctionPort;
67  this->_eligibilityHighlight = VuoRendererColors::noHighlight;
68  this->isEligibleForSelection = false;
69  setAnimated(false);
70  this->typecastParentPort = NULL;
72 
74 
75  const int maxAnimationsPerPort = 4;
76 
77  if (getInput() || (getBase()->getClass()->getPortType() == VuoPortClass::triggerPort))
78  {
79  for (int i = 0; i < maxAnimationsPerPort; ++i)
80  {
81  QGraphicsItemAnimation *animation = new QGraphicsItemAnimation();
82  animation->setTimeLine(new QTimeLine(VuoRendererColors::activityAnimationFadeDuration));
83  animations.push_back(animation);
84  }
85  }
86 
87  if (! isHiddenRefreshPort())
88  {
89  setFlag(QGraphicsItem::ItemIsFocusable, true); // allow delivery of key events
90  setAcceptHoverEvents(true); // allow delivery of mouse-hover events
91  }
92 
97 
99  setToolTip(QString("<span></span>") + getConstantAsStringToRender().c_str());
100 }
101 
106 void VuoRendererPort::addRoundedTriangle(QPainterPath &p, QPointF center, qreal radius, qreal cornerRadius)
107 {
108  p.moveTo(center + QPointF(radius,0));
109  for (int theta = 0; theta <= 360; theta += 120)
110  {
111  QRectF rect(center.x() + (radius - cornerRadius)*cos(theta*M_PI/180.) - cornerRadius,
112  center.y() - (radius - cornerRadius)*sin(theta*M_PI/180.) - cornerRadius,
113  cornerRadius*2,
114  cornerRadius*2);
115  bool first = theta==0;
116  bool last = theta==360;
117 
118  p.arcTo(rect,
119  theta - (first ? 0 : 60),
120  (first||last ? 60 : 120));
121  }
122 }
123 
127 QPainterPath VuoRendererPort::getPortPath() const
128 {
129  return cachedPortPath;
130 }
131 
136 {
137  if (isHiddenRefreshPort())
138  {
139  cachedPortPath = QPainterPath();
140  return;
141  }
142 
144  getBase()->getClass()->getPortType(),
145  isConstant() ? QString::fromUtf8(getConstantAsTruncatedStringToRender().c_str()) : "",
146  getInput(),
147  carriesData()
148  );
149 }
150 
155 QPainterPath VuoRendererPort::getPortPath(qreal inset,
156  VuoPortClass::PortType portType,
157  QString constantText,
158  bool isInputPort,
159  bool carriesData
160  )
161 {
162  QPainterPath p;
163  QRectF outerPortRect = getPortRect();
164  QRectF innerPortRect = outerPortRect.adjusted(inset,inset,-inset,-inset);
165 
166  QRectF textRect = getPortConstantTextRectForText(constantText);
167 
168  qreal left = textRect.x() - portConstantTextPadding + inset - VuoRendererPort::portInset;
169  QPointF topLeftCorner(left + .5, innerPortRect.top() - .24);
170  QPointF topRightCorner(innerPortRect.right() + .5, innerPortRect.top() - .24);
171  QPointF bottomRightCorner(innerPortRect.right() + .5, innerPortRect.bottom() - .43);
172  QPointF bottomLeftCorner(left + .5, innerPortRect.bottom() - .43);
173 
174  p.moveTo(innerPortRect.right() + .5, innerPortRect.center().y());
175  qreal adjustedPortRadius = portRadius - 2;
176  addRoundedCorner(p, true, bottomRightCorner, adjustedPortRadius, false, false);
177  addRoundedCorner(p, true, bottomLeftCorner, adjustedPortRadius, false, true);
178  addRoundedCorner(p, true, topLeftCorner, adjustedPortRadius, true, true);
179  addRoundedCorner(p, true, topRightCorner, adjustedPortRadius, true, false);
180 
181  return p;
182 }
183 
188 {
189  return QRectF(
190  -portRadius,
191  -portRadius,
192  portRadius*2.0,
193  portRadius*2.0
194  );
195 }
196 
201 {
202  QRectF barrierRect = QRectF();
203 
204  bool sidebarPaintMode = dynamic_cast<const VuoRendererPublishedPort *>(this);
207 
208  if (!isAnimated &&
209  ((!isOutput && !sidebarPaintMode && eventBlocking != VuoPortClass::EventBlocking_None)
210  || (isOutput && type == VuoPortClass::triggerPort))
211  )
212  {
213  QRectF portRect = getPortRect();
214  if (isOutput)
215  barrierRect = QRectF(portRect.topLeft() + QPointF( 2 - VuoRendererPort::portBarrierWidth, 2), portRect.bottomLeft() + QPointF( 2, -3));
216  else
217  barrierRect = QRectF(portRect.topRight() + QPointF(-1 + VuoRendererPort::portBarrierWidth, 2), portRect.bottomRight() + QPointF(-1, -3));
218  }
219 
220  return barrierRect;
221 }
222 
227 {
228  bool paintDataAntenna = hasConnectedWirelessDataCable(true);
229  bool paintEventAntenna = hasConnectedWirelessEventCable(true);
230  if (!paintDataAntenna && !paintEventAntenna)
231  return QPainterPath();
232 
233  // Mast
234  qreal cableWidth;
235  VuoRendererCable::getCableSpecs(paintDataAntenna, cableWidth);
236 
237  const qreal constantWidth = fmax(0, getPortConstantTextRect().width() - 3);
238  const qreal mastLength = portRadius * 2.;
239  const qreal pixelOffset = -.3;
240  QPointF startPoint = (getInput()? -QPointF(mastLength - (paintDataAntenna ? 0.5 : 0 ) + constantWidth, -pixelOffset) : QPointF(0, pixelOffset));
241  QPointF endPoint = (getInput()? QPointF(-constantWidth, pixelOffset) : QPointF(mastLength + (paintDataAntenna ? 0.5 : 0 ), pixelOffset));
242 
243  VuoCable cableBase(NULL, NULL, NULL, NULL);
244  VuoRendererCable cableRenderer(&cableBase);
245  QPainterPath mastPath = cableRenderer.getCablePathForEndpoints(startPoint, endPoint);
246 
247  QPainterPathStroker mastStroker;
248  mastStroker.setWidth(cableWidth);
249  mastStroker.setCapStyle(Qt::RoundCap);
250  QPainterPath antennaOutline = mastStroker.createStroke(mastPath);
251 
252  // Crossbars
253  qreal outerCrossbarXOffset = (paintDataAntenna? 1 : 0.5);
254  const QPointF outerCrossbarPos = (getInput()? startPoint - QPointF(outerCrossbarXOffset, 0) :
255  endPoint + QPointF(outerCrossbarXOffset, 0));
256  const int crossbarSpacing = 5;
257  const int crossbarHeight = VuoRendererFonts::midPenWidth*5;
258 
259  QPainterPath crossbars;
260  for (int i = 0; i < 2; ++i)
261  {
262  crossbars.moveTo((outerCrossbarPos +
263  QPointF(QPointF(0, -0.5*crossbarHeight) +
264  QPointF((getInput()? 1 : -1)*crossbarSpacing*i, 0))));
265  crossbars.lineTo((outerCrossbarPos +
266  QPointF(QPointF(0, 0.5*crossbarHeight) +
267  QPointF((getInput()? 1 : -1)*crossbarSpacing*i, 0))));
268  }
269 
270  // Union the mast and crossbars.
271  QPainterPathStroker crossbarStroker;
272  crossbarStroker.setWidth(cableWidth*3/5);
273  crossbarStroker.setCapStyle(Qt::RoundCap);
274  antennaOutline += crossbarStroker.createStroke(crossbars);
275 
276  return antennaOutline;
277 }
278 
282 bool VuoRendererPort::hasConnectedWirelessDataCable(bool includePublishedCables) const
283 {
284  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
285  for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
286  if ((*cable)->hasRenderer() && (*cable)->getRenderer()->effectivelyCarriesData() &&
287  (*cable)->getRenderer()->getEffectivelyWireless() &&
288  (*cable)->getRenderer()->paintingDisabled())
289  return true;
290  return false;
291 }
292 
296 bool VuoRendererPort::hasConnectedWirelessEventCable(bool includePublishedCables) const
297 {
298  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
299  for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
300  if ((*cable)->hasRenderer() && !(*cable)->getRenderer()->effectivelyCarriesData() &&
301  (*cable)->getRenderer()->getEffectivelyWireless() &&
302  (*cable)->getRenderer()->paintingDisabled())
303  return true;
304  return false;
305 }
306 
311 {
312  VuoRendererNode *renderedParentNode = getRenderedParentNode();
313  if (renderedParentNode)
314  return renderedParentNode->getBase()->getTintColor();
315  else
316  {
317  // Tint protocol ports the same color as the protocol.
318  if (dynamic_cast<const VuoRendererPublishedPort *>(this) &&
319  (dynamic_cast<VuoPublishedPort *>(this->getBase()))->isProtocolPort())
320  {
321  // @todo: Account for multiple simultaneous active protocols. https://b33p.net/kosada/node/9585
322  return VuoRendererColors::getActiveProtocolTint(0, !isOutput);
323  }
324  }
325 
326  return VuoNode::TintNone;
327 }
328 
336 {
337  if (!getInput())
338  return getPortTint();
339 
340  set<VuoNode::TintColor> connectedPortTints;
341  foreach (VuoRendererPort *port, getPortsConnectedWirelessly(true))
342  {
343  connectedPortTints.insert(port->getPortTint());
344  if (connectedPortTints.size() > 1)
345  return VuoNode::TintNone;
346  }
347 
348  if (connectedPortTints.size() == 1)
349  return *connectedPortTints.begin();
350  else
351  return VuoNode::TintNone;
352 }
353 
357 set<VuoRendererPort *> VuoRendererPort::getPortsConnectedWirelessly(bool includePublishedCables) const
358 {
359  set<VuoRendererPort *> connectedPorts;
360  foreach (VuoCable *cable, getBase()->getConnectedCables(includePublishedCables))
361  {
362  if (cable->hasRenderer() &&
363  cable->getRenderer()->getEffectivelyWireless() &&
364  cable->getRenderer()->paintingDisabled())
365  {
366  VuoPort *connectedPort = (getInput()? cable->getFromPort() : cable->getToPort());
367  if (connectedPort && connectedPort->hasRenderer())
368  connectedPorts.insert(connectedPort->getRenderer());
369  }
370  }
371 
372  return connectedPorts;
373 }
374 
380 {
382 }
383 
389 {
391  if (underlyingAttachment &&
392  underlyingAttachment->getRenderedHostPort() &&
393  underlyingAttachment->getRenderedHostPort()->hasRenderer() &&
394  (underlyingAttachment->getRenderedHostPort()->getRenderer() == targetHostPort) &&
395  (dynamic_cast<VuoRendererInputDrawer *>(underlyingAttachment)))
396  return dynamic_cast<VuoRendererInputDrawer *>(underlyingAttachment);
397 
398  else if (underlyingAttachment)
399  {
400  // The drawer might not be directly connected in the underlying composition. Find it anyway.
401  foreach (VuoPort *port, underlyingAttachment->getBase()->getInputPorts())
402  {
403  VuoRendererInputDrawer *upstreamDrawer = port->getRenderer()->getAttachedInputDrawerRenderedWithHostPort(targetHostPort);
404  if (upstreamDrawer)
405  return upstreamDrawer;
406  }
407  }
408 
409  return NULL;
410 }
411 
417 {
418  if (! getInput())
419  return NULL;
420 
421  vector<VuoCable *> inCables = getBase()->getConnectedCables(false);
422  foreach (VuoCable *cable, inCables)
423  {
424  VuoNode *fromNode = cable->getFromNode();
425  if (fromNode && fromNode->hasRenderer() &&
426  dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer()) &&
427  dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer())->getUnderlyingHostPort()->getRenderer() == this)
428  return dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer());
429  }
430 
431  return NULL;
432 }
433 
439 set<VuoRendererInputAttachment *> VuoRendererPort::getAllUnderlyingUpstreamInputAttachments(void) const
440 {
441  set<VuoRendererInputAttachment *> allUpstreamAttachments;
442  VuoRendererInputAttachment *directUpstreamAttachment = getUnderlyingInputAttachment();
443  if (!directUpstreamAttachment)
444  return allUpstreamAttachments;
445 
446  allUpstreamAttachments.insert(directUpstreamAttachment);
447 
448  vector<VuoPort *> inputPorts = directUpstreamAttachment->getBase()->getInputPorts();
449  foreach (VuoPort *port, inputPorts)
450  {
451  set<VuoRendererInputAttachment *> indirectUpstreamAttachments = port->getRenderer()->getAllUnderlyingUpstreamInputAttachments();
452  allUpstreamAttachments.insert(indirectUpstreamAttachments.begin(), indirectUpstreamAttachments.end());
453  }
454 
455  return allUpstreamAttachments;
456 }
457 
463 {
464  return (isConstant()?
466  QRectF());
467 }
468 
475 {
476  static QHash<QString, int> cachedTextWidths;
477  QHash<QString, int>::iterator i = cachedTextWidths.find(text);
478  if (i != cachedTextWidths.end())
479  return i.value();
480 
481  int textWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodePortConstantFont())
482  .boundingRect(QRectF(0,0,0,0), Qt::TextIncludeTrailingSpaces, text)
483  .width();
484  cachedTextWidths.insert(text, textWidth);
485  return textWidth;
486 }
487 
492 {
493  int textWidth = getTextWidth(text) + 1;
494 
495  QRectF textRect(
496  -textWidth - portConstantTextPadding + 8,
498  textWidth,
499  (VuoRendererPort::portRadius - 1)*2 - 1
500  );
501 
502  return textRect.toAlignedRect();
503 }
504 
505 
510 {
511  return this->nameRect;
512 }
513 
518 {
519  QString text = QString::fromUtf8(getPortNameToRender().c_str());
520  QFont font = getPortNameFont();
521  QSizeF textSize = QFontMetricsF(font).size(0,text);
522 
523  bool isPortOnDrawer = dynamic_cast<VuoRendererInputAttachment *>(getUnderlyingParentNode());
524 
525  this->nameRect = QRectF(
526  (isOutput? -VuoRendererFonts::thickPenWidth/2.0 - textSize.width() - VuoRendererPort::portRadius :
529  + (isPortOnDrawer ? 2 : VuoRendererPort::portRadius) + 2.
530  ),
531  -VuoRendererFonts::thickPenWidth/3.0 - (isPortOnDrawer ? 0 : 1),
532  textSize.width(),
533  textSize.height()
534  );
535 }
536 
546 {
547  VuoRendererNode *node;
548  if (getTypecastParentPort())
549  node = (((VuoRendererTypecastPort *)(getTypecastParentPort()))->getUncollapsedTypecastNode());
550  else
551  node = getRenderedParentNode();
552 
553  return node;
554 }
555 
565 {
566  if (!parentItem())
567  return NULL;
568 
569  if (!parentItem()->parentItem())
570  return (VuoRendererNode *)(parentItem());
571 
572  return (VuoRendererNode *)(parentItem()->parentItem());
573 }
574 
579 {
580  return typecastParentPort;
581 }
582 
587 {
588  this->typecastParentPort = typecastParentPort;
589 }
590 
595 {
596  return cachedBoundingRect;
597 }
598 
603 {
604  VuoRendererNode *renderedParentNode = getRenderedParentNode();
605  if ((renderedParentNode && renderedParentNode->paintingDisabled()) || isHiddenRefreshPort())
606  {
607  cachedBoundingRect = QRectF();
608  return;
609  }
610 
611  QRectF r = getPortPath().boundingRect();
612 
613  r = r.united(getEventBarrierRect());
614 
616  r = r.united(getNameRect());
617 
618  if (hasPortAction())
619  r = r.united(getActionIndicatorRect());
620 
621  r = r.united(getWirelessAntennaPath().boundingRect());
622 
623  // Antialiasing bleed
624  r.adjust(-1,-1,1,1);
625 
626  cachedBoundingRect = r.toAlignedRect();
627 }
628 
633 QPainterPath VuoRendererPort::shape() const
634 {
635  QPainterPath p;
636  p.addRect(boundingRect());
637  return p;
638 }
639 
644 {
645  bool sidebarPaintMode = dynamic_cast<VuoRendererPublishedPort *>(this);
646 
649 
650  if (
651  !isAnimated &&
652  ((!isOutput && !sidebarPaintMode && eventBlocking != VuoPortClass::EventBlocking_None)
653  || (isOutput && type == VuoPortClass::triggerPort))
654  )
655  {
656  QRectF barrierRect = getEventBarrierRect();
657  QColor eventBlockingBarrierColor = (isAnimated? colors->animatedeventBlockingBarrier() : colors->eventBlockingBarrier());
658  painter->setPen(QPen(eventBlockingBarrierColor, VuoRendererPort::portBarrierWidth, Qt::SolidLine, Qt::RoundCap));
659 
660  if (eventBlocking == VuoPortClass::EventBlocking_Wall || type == VuoPortClass::triggerPort)
661  painter->drawLine(barrierRect.center() + QPointF(0, -barrierRect.height()/2. + VuoRendererPort::portBarrierWidth/2. - .83),
662  barrierRect.center() + QPointF(0, barrierRect.height()/2. - VuoRendererPort::portBarrierWidth/2. +1.16));
663  else // VuoPortClass::EventBlocking_Door
664  {
665  painter->drawPoint(barrierRect.center() + QPointF(0, -barrierRect.height()/2. + 1.75 + .17));
666  painter->drawPoint(barrierRect.center() + QPointF(0, barrierRect.height()/2. - 1.75 + .17));
667  }
668  }
669 }
670 
674 QFont VuoRendererPort::getPortNameFont(void) const
675 {
677  // Use a smaller font for port labels on drawers.
679  else
681 }
682 
686 void VuoRendererPort::paintPortName(QPainter *painter, VuoRendererColors *colors)
687 {
689  return;
690 
691  VuoRendererPublishedPort *rpp = dynamic_cast<VuoRendererPublishedPort *>(this);
692 
693  string name = getPortNameToRender();
694 
695  if (rpp)
696  painter->setPen((rpp->isSelected() && rpp->getCurrentlyActive())
697  ? Qt::white
698  : (dynamic_cast<VuoPublishedPort *>(rpp->getBase())->isProtocolPort() ? colors->publishedProtocolPortTitle() : colors->publishedPortTitle()));
699  else
700  painter->setPen(colors->portTitle());
701 
702  painter->setFont(getPortNameFont());
703  painter->drawText(getNameRect(), isOutput? Qt::AlignRight : Qt::AlignLeft, QString::fromStdString(name));
704 }
705 
711 {
712  bool displayPortName = (getRenderedParentNode()? getRenderedParentNode()->nameDisplayEnabledForPort(this) : true);
713 
714  return (!displayPortName? "": getPortNameToRenderWhenDisplayed());
715 }
716 
721 {
722  const VuoRendererPublishedPort *publishedPort = dynamic_cast<const VuoRendererPublishedPort *>(this);
723  return (publishedPort? getBase()->getClass()->getName() :
725  getBase()->getClass()->getName()));
726 }
727 
733 {
734  this->customizedPortName = name;
735  updateNameRect();
736 }
737 
743 {
744  VuoPortClass *pc = getBase()->getClass();
745  if (pc->hasCompiler())
746  return static_cast<VuoCompilerPortClass *>(pc->getCompiler())->getDisplayName();
747  else
748  return "";
749 }
750 
755 {
756  return getBase()->getClass()->hasPortAction();
757 }
758 
763 {
764  QFontMetricsF fontMetrics = QFontMetricsF(getPortNameFont());
765  const qreal marginFromPortName = 4;
766  const qreal triangleSize = 6;
767  qreal triangleLeft = qRound( getNameRect().right() + marginFromPortName );
768  qreal triangleTop = qRound( getNameRect().bottom() - fontMetrics.descent() - fontMetrics.xHeight() );
769 
770  return QRectF(triangleLeft, triangleTop, triangleSize, triangleSize);
771 }
772 
777 {
778  if (hasPortAction())
779  {
780  QRectF rect = getActionIndicatorRect();
781 
782  QPainterPath p;
783  addRoundedTriangle(p, rect.center() + QPointF(-1,-.75), qRound(rect.width()/2.) + .5, VuoRendererNode::cornerRadius/9.);
784 
785  QColor color = colors->actionIndicator();
786  painter->fillPath(p, color);
787  }
788 }
789 
794 {
795  painter->fillPath(getWirelessAntennaPath(), QBrush(colors->cableMain()));
796 }
797 
801 void VuoRendererPort::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
802 {
803  if (isHiddenRefreshPort())
804  return;
805 
806  VuoRendererNode *renderedParentNode = getRenderedParentNode();
807  if (renderedParentNode && renderedParentNode->paintingDisabled())
808  return;
809 
810  VuoRendererPublishedPort *publishedPort = dynamic_cast<VuoRendererPublishedPort *>(this);
811 
812  // Workaround to prevent items that have been removed from the scene from being painted on the scene anyway.
813  // https://b33p.net/kosada/node/7938
814  if (!(scene() || publishedPort))
815  return;
816 
817  painter->setRenderHint(QPainter::Antialiasing, true);
818  drawBoundingRect(painter);
819 
820  bool isColorInverted = isRefreshPort || isFunctionPort;
821 
822  VuoRendererColors::SelectionType selectionType = ((renderedParentNode && renderedParentNode->isSelected())? VuoRendererColors::directSelection :
823  VuoRendererColors::noSelection);
824 
825  bool isHovered = isEligibleForSelection;
826 
828  bool isTriggerPort = (type == VuoPortClass::triggerPort);
829 
830  VuoRendererComposition *composition = dynamic_cast<VuoRendererComposition *>(scene());
831  bool renderNodeActivity = composition && composition->getRenderNodeActivity();
832  bool renderPortActivity = composition && composition->getRenderPortActivity();
833 
834  qint64 timeOfLastActivity = ((! renderNodeActivity)? VuoRendererItem::notTrackingActivity :
835  ((isTriggerPort && renderPortActivity)? timeLastEventFired :
836  (getTypecastParentPort()? static_cast<VuoRendererTypecastPort *>(getTypecastParentPort())->getUncollapsedTypecastNode()->getTimeLastExecutionEnded() :
837  (renderedParentNode? renderedParentNode->getTimeLastExecutionEnded() :
838  VuoRendererItem::notTrackingActivity))));
839 
840  // If an attached drawer does have eligible ports, ensure this host port isn't faded out, so the port name is legible.
841  VuoRendererColors::HighlightType effectiveHighlight = _eligibilityHighlight;
842  bool effectivelyHovered = isHovered;
844  if (drawer)
845  {
846  VuoRendererColors::HighlightType drawerHighlight = drawer->getEligibilityHighlight();
847  if (drawerHighlight < _eligibilityHighlight)
848  {
849  effectiveHighlight = drawerHighlight;
850 
851  if (_eligibilityHighlight == VuoRendererColors::ineligibleHighlight)
852  effectivelyHovered = false;
853  }
854  }
855 
857  selectionType,
858  effectivelyHovered,
859  effectiveHighlight,
860  timeOfLastActivity);
862  selectionType,
863  isHovered,
865  timeOfLastActivity);
866 
867  // Draw the port circle / constant flag
868  QPainterPath portPath = getPortPath();
869 
870  QBrush portBrush;
871 
872  if (isColorInverted)
873  portBrush = colors->portTitlebarFill();
874  else if (isAnimated)
875  portBrush = colors->animatedPortFill();
876  else if (publishedPort && dynamic_cast<VuoPublishedPort *>(publishedPort->getBase())->isProtocolPort())
877  portBrush = colors->portTitlebarFill();
878  else if (publishedPort)
879  portBrush = colors->publishedPortFill();
880  else
881  portBrush = colors->portFill();
882 
883 
884  if (!isConstant())
885  {
887  VuoRendererPort *drawerChildPort = (drawer && (drawer->getInputPorts().size() >= VuoNodeClass::unreservedInputPortStartIndex+1)?
889  NULL);
890 
891  // Prevent neighboring semi-transparent highlights from overlapping in a misleading way
892  bool showRightHalfOnly = !isHovered && drawerChildPort &&
895  if (showRightHalfOnly)
896  painter->setClipRect(QRectF(-0.39, -portRadius, portRadius, portRadius*2.));
897 
898  painter->fillPath(portPath, portBrush);
899 
900  if (showRightHalfOnly)
901  painter->setClipping(false);
902  }
903  else
904  {
905  // Display a color swatch for VuoColor data.
907  bool isColorPort = getDataType() && getDataType()->getModuleKey()=="VuoColor";
908  if (isColorPort)
909  {
910  string colorRGBAJsonString = getConstantAsString();
911  VuoColor c = VuoColor_makeFromString(colorRGBAJsonString.c_str());
912  QColor swatchColor = QColor(c.r*255, c.g*255, c.b*255, c.a*255 * portBrush.color().alphaF());
913  VuoReal h,s,l,a;
914  VuoColor_getHSLA(c, &h, &s, &l, &a);
915 
916  bool isDark = colors->isDark();
917 
918  // Two possible swatches:
919 
920  // 1. Semitransparent color, or solid color that matches the canvas background: Draw a background+border, then draw the swatch.
921  if ((a < 1) || (isDark ? l < .25 : l > .75))
922  {
923  // Fill the entire background with a color distinct from the canvas.
924  QColor topLeftColor = colors->nodeFrame();
925  painter->fillPath(portPath, topLeftColor);
926 
927  // Fill the bottom right of the background with the opposite color.
928  // Use a slightly smaller circle, so the topLeftColor acts as a border.
929  QColor bottomRightColor = isDark ? Qt::black : Qt::white;
930  QTransform transform;
931  transform.scale(0.87, 0.87);
932  QPainterPath smallerCircle = portPath * transform;
933  {
934  QRectF r = portPath.boundingRect();
935  QPainterPath bottomRight;
936  bottomRight.moveTo(r.bottomLeft());
937  bottomRight.lineTo(r.topRight());
938  bottomRight.lineTo(r.bottomRight());
939  painter->setClipPath(smallerCircle);
940  painter->fillPath(bottomRight, bottomRightColor);
941  painter->setClipping(false);
942  }
943 
944  // Draw the swatch.
945  painter->fillPath(smallerCircle, swatchColor);
946  }
947 
948  // 2. Solid color that's distinct from the canvas background: Just draw the swatch.
949  else
950  painter->fillPath(portPath, swatchColor);
951  }
952  else
953  {
954  painter->fillPath(portPath, portBrush);
955 
956  QString constantText = QString::fromUtf8(getConstantAsTruncatedStringToRender().c_str());
957  QBrush constantFlagBackgroundBrush = colors->constantFill();
958 
959  // Constant string
960  QRectF textRect = getPortConstantTextRectForText(constantText);
961  painter->setPen(colors->constantText());
962  painter->setFont(VuoRendererFonts::getSharedFonts()->nodePortConstantFont());
963  painter->drawText(textRect, Qt::AlignLeft, constantText);
964  }
965  }
966 
967  if (! carriesData())
968  {
969  QRectF r = getPortRect();
970  QPainterPath p;
971  addRoundedTriangle(p, r.center() + QPointF(.5, -.3), (r.width() - VuoRendererPort::portInset*2)/2.-2, VuoRendererNode::cornerRadius/6.);
972  painter->fillPath(p, colors->portIcon());
973  }
974 
975  paintEventBarrier(painter, colors);
976  paintPortName(painter, colors);
977  paintActionIndicator(painter, colors);
978  paintWirelessAntenna(painter, antennaColors);
979 
980  delete colors;
981  delete antennaColors;
982 }
983 
989 {
990  return isEligibleForSelection;
991 }
992 
998 {
999  return _eligibilityHighlight == VuoRendererColors::standardHighlight
1000  || _eligibilityHighlight == VuoRendererColors::subtleHighlight;
1001 }
1002 
1007 {
1008  _eligibilityHighlight = eligibility;
1009 }
1010 
1015 {
1016  return _eligibilityHighlight;
1017 }
1018 
1022 void VuoRendererPort::extendedHoverEnterEvent(bool cableDragUnderway, bool disableConnectedCableHighlight)
1023 {
1024  extendedHoverMoveEvent(cableDragUnderway, disableConnectedCableHighlight);
1025 }
1026 
1032 void VuoRendererPort::extendedHoverMoveEvent(bool cableDragUnderway, bool disableConnectedCableHighlight)
1033 {
1034  QGraphicsItem::CacheMode normalCacheMode = cacheMode();
1035  setCacheMode(QGraphicsItem::NoCache);
1036 
1037  prepareGeometryChange();
1038  isEligibleForSelection = (cableDragUnderway? isEligibleForConnection() : true);
1039 
1040  setCacheMode(normalCacheMode);
1041 
1042  setFocus();
1043 
1044  if (!cableDragUnderway && !disableConnectedCableHighlight)
1045  {
1046  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(false);
1047  if (supportsDisconnectionByDragging() && (! connectedCables.empty()))
1048  {
1049  VuoRendererCable *cableToDisconnect = connectedCables.back()->getRenderer();
1050  cableToDisconnect->updateGeometry();
1051  cableToDisconnect->setHovered(true);
1052  }
1053  }
1054 }
1055 
1060 {
1061  QGraphicsItem::CacheMode normalCacheMode = cacheMode();
1062  setCacheMode(QGraphicsItem::NoCache);
1063 
1064  prepareGeometryChange();
1065  isEligibleForSelection = false;
1066 
1067  setCacheMode(normalCacheMode);
1068 
1069  clearFocus();
1070 
1071  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(false);
1072  if (supportsDisconnectionByDragging() && (! connectedCables.empty()))
1073  {
1074  VuoRendererCable *cableToDisconnect = connectedCables.back()->getRenderer();
1075  cableToDisconnect->updateGeometry();
1076  cableToDisconnect->setHovered(false);
1077  }
1078 }
1079 
1093 {
1094  bool fromPortIsEnabledOutput = (this->getOutput() && this->isEnabled());
1095  bool toPortIsEnabledInput = (toPort && toPort->getInput() && toPort->isEnabled());
1096 
1097  if (!(fromPortIsEnabledOutput && toPortIsEnabledInput))
1098  return false;
1099 
1100  // OK: Any connection made using an event-only cable.
1101  if (eventOnlyConnection)
1102  return true;
1103 
1104  VuoType *fromDataType = this->getDataType();
1105  VuoType *toDataType = toPort->getDataType();
1106 
1107  // OK: Event-only to event+data.
1108  // OK: Event-only to event-only.
1109  // OK: Event+data to event-only.
1110  if (!fromDataType || !toDataType)
1111  return true;
1112 
1113  // OK: Event+data to event+data, if types are non-generic and identical.
1114  if (! dynamic_cast<VuoGenericType *>(fromDataType) && (fromDataType == toDataType))
1115  return true;
1116 
1117  // OK: Event+data to event+data, if types are generic and compatible.
1118  if (dynamic_cast<VuoGenericType *>(fromDataType) && dynamic_cast<VuoGenericType *>(toDataType))
1119  {
1121  if (VuoType::isListTypeName(fromDataType->getModuleKey()) != VuoType::isListTypeName(toDataType->getModuleKey()))
1122  return false;
1123 
1124  return (dynamic_cast<VuoGenericType *>(fromDataType)->isGenericTypeCompatible(dynamic_cast<VuoGenericType *>(toDataType)));
1125  }
1126 
1127  return false;
1128 }
1129 
1146 {
1147  VuoRendererPort *portToSpecialize = NULL;
1148  string specializedTypeName = "";
1149 
1150  return (this->canConnectDirectlyWithSpecializationTo(toPort, eventOnlyConnection, &portToSpecialize, specializedTypeName));
1151 }
1152 
1166 bool VuoRendererPort::canConnectDirectlyWithSpecializationTo(VuoRendererPort *toPort, bool eventOnlyConnection, VuoRendererPort **portToSpecialize, string &specializedTypeName)
1167 {
1168  *portToSpecialize = NULL;
1169  specializedTypeName = "";
1170 
1171  if (this->canConnectDirectlyWithoutSpecializationTo(toPort, eventOnlyConnection))
1172  return true;
1173 
1174  bool fromPortIsEnabledOutput = (this->getOutput() && this->isEnabled());
1175  bool toPortIsEnabledInput = (toPort && toPort->getInput() && toPort->isEnabled());
1176 
1177  if (!(fromPortIsEnabledOutput && toPortIsEnabledInput))
1178  return false;
1179 
1180  VuoType *originalFromDataType = ((VuoCompilerPortClass *)(this->getBase()->getClass()->getCompiler()))->getDataVuoType();
1181  VuoType *originalToDataType = ((VuoCompilerPortClass *)(toPort->getBase()->getClass()->getCompiler()))->getDataVuoType();
1182 
1183  VuoType *currentFromDataType = this->getDataType();
1184  VuoType *currentToDataType = toPort->getDataType();
1185 
1186  if (!(originalFromDataType && originalToDataType && currentFromDataType && currentToDataType))
1187  return false;
1188 
1189  VuoGenericType *currentFromGenericType = dynamic_cast<VuoGenericType *>(currentFromDataType);
1190  VuoGenericType *currentToGenericType = dynamic_cast<VuoGenericType *>(currentToDataType);
1191 
1193  if (VuoType::isListTypeName(currentFromDataType->getModuleKey()) != VuoType::isListTypeName(currentToDataType->getModuleKey()))
1194  return false;
1195 
1196  // Case: The 'From' port is generic and can be specialized to match the concrete type of the 'To' port.
1197  if (currentFromGenericType && currentFromGenericType->isSpecializedTypeCompatible(originalToDataType->getModuleKey()))
1198  {
1199  *portToSpecialize = this;
1200  specializedTypeName = originalToDataType->getModuleKey();
1201 
1202  return true;
1203  }
1204 
1205  // Case: The 'To' port is generic and can be specialized to match the concrete type of the 'From' port.
1206  else if (currentToGenericType && currentToGenericType->isSpecializedTypeCompatible(originalFromDataType->getModuleKey()))
1207  {
1208  *portToSpecialize = toPort;
1209  specializedTypeName = originalFromDataType->getModuleKey();
1210 
1211  return true;
1212  }
1213 
1214  return false;
1215 }
1216 
1220 VuoCable * VuoRendererPort::getCableConnectedTo(VuoRendererPort *toPort, bool includePublishedCables)
1221 {
1222  vector<VuoCable *> cables = this->getBase()->getConnectedCables(includePublishedCables);
1223  for (vector<VuoCable *>::iterator cable = cables.begin(); cable != cables.end(); ++cable)
1224  if ((*cable)->getToPort() == toPort->getBase())
1225  return (*cable);
1226 
1227  return NULL;
1228 }
1229 
1233 void VuoRendererPort::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
1234 {
1235  if (isConstant())
1236  {
1238  }
1239 }
1240 
1244 void VuoRendererPort::keyPressEvent(QKeyEvent *event)
1245 {
1246  if (isConstant() &&
1247  (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter))
1248  {
1250  }
1251 }
1252 
1258 {
1259  return (getInput() &&
1260  (! dynamic_cast<VuoRendererTypecastPort *>(this)) &&
1261  (! getAttachedInputDrawer()));
1262 }
1263 
1268 {
1269  return ((! isOutput) && (! isFunctionPort));
1270 }
1271 
1276 {
1277  return isOutput;
1278 }
1279 
1284 {
1285  return isRefreshPort;
1286 }
1287 
1292 {
1293  return isFunctionPort;
1294 }
1295 
1300 {
1301  return isRefreshPort && getBase()->getConnectedCables(true).empty();
1302 }
1303 
1308 {
1309  return (getBase()->getClass()->getPortType() == VuoPortClass::dataAndEventPort ||
1310  (getBase()->getClass()->hasCompiler() &&
1311  static_cast<VuoCompilerPortClass *>(getBase()->getClass()->getCompiler())->getDataVuoType()));
1312 }
1313 
1318 {
1320  this->prepareGeometryChange();
1322 }
1323 
1327 QVariant VuoRendererPort::itemChange(GraphicsItemChange change, const QVariant &value)
1328 {
1329  // Port has moved relative to its parent
1330  if (change == QGraphicsItem::ItemPositionHasChanged)
1331  {
1332  VuoRendererNode *parentNode = getRenderedParentNode();
1333  if (parentNode)
1335  }
1336 
1337  return QGraphicsItem::itemChange(change, value);
1338 }
1339 
1345 {
1346  if (!(getBase() && getBase()->hasCompiler()))
1347  return NULL;
1348 
1349  VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(getBase()->getCompiler());
1350  return compilerPort->getDataVuoType();
1351 }
1352 
1359 {
1360  // For now, ports with URLs are expected to be of type "VuoText".
1361  if ( !(getDataType() && getDataType()->getModuleKey() == "VuoText") )
1362  return false;
1363 
1364  string portName = getBase()->getClass()->getName();
1365 
1366  // Case: Port is titled "url"
1367  if (portName == "url")
1368  return true;
1369 
1370  // Case: Port is titled "folder"
1371  // Relevant for vuo.file.list node.
1372  if (portName == "folder")
1373  return true;
1374 
1375  // Case: Port is an input port on a drawer attached to a port titled "urls"
1376  // Relevant for vuo.image.fetch.list, vuo.scene.fetch.list nodes.
1378  if (drawer)
1379  {
1380  VuoPort *hostPort = drawer->getRenderedHostPort();
1381  if (hostPort && (hostPort->getClass()->getName() == "urls"))
1382  return true;
1383  }
1384 
1385  return false;
1386 }
1387 
1396 {
1397  if ( !(isConstant() && hasURLType()) )
1398  return false;
1399 
1400  json_object *details = static_cast<VuoCompilerInputEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1401  json_object *isSaveValue = NULL;
1402  if (details && json_object_object_get_ex(details, "isSave", &isSaveValue) && json_object_get_boolean(isSaveValue))
1403  return false;
1404 
1406  VuoRetain(url);
1407  bool isRelativePath = VuoUrl_isRelativePath(url);
1408  VuoRelease(url);
1409  return isRelativePath;
1410 }
1411 
1416 {
1417  return ((getInput() && getDataType()) && // input port with data...
1418  (!effectivelyHasConnectedDataCable(true))); // ... that has no incoming data cable (published or unpublished).
1419 }
1420 
1425 bool VuoRendererPort::effectivelyHasConnectedDataCable(bool includePublishedCables) const
1426 {
1427  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
1428  for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
1429  if ((*cable)->hasRenderer() && (*cable)->getRenderer()->effectivelyCarriesData())
1430  return true;
1431  return false;
1432 }
1433 
1437 string VuoRendererPort::format(const char *format, ...)
1438 {
1439  va_list args;
1440 
1441  va_start(args, format);
1442  int size = vsnprintf(NULL, 0, format, args);
1443  va_end(args);
1444 
1445  char *formattedString = (char *)malloc(size+1);
1446  va_start(args, format);
1447  vsnprintf(formattedString, size+1, format, args);
1448  va_end(args);
1449 
1450  string s(formattedString);
1451  free(formattedString);
1452  return s;
1453 }
1454 
1459 {
1460  string s(strz);
1461  free(strz);
1462  return s;
1463 }
1464 
1469 {
1470  if (!(getInput() && getDataType()))
1471  return "";
1472 
1473  VuoCompilerInputEventPort *compilerEventPort = dynamic_cast<VuoCompilerInputEventPort *>(getBase()->getCompiler());
1474  if (! compilerEventPort)
1475  return "";
1476 
1477  return compilerEventPort->getData()->getInitialValue();
1478 }
1479 
1485 {
1486  VuoText fullString = VuoText_make(getConstantAsStringToRender().c_str());
1487  VuoLocal(fullString);
1488 
1489  if (getDataType() && (getDataType()->getModuleKey() == "VuoColor"))
1490  return "";
1491 
1492  bool truncateFromBeginning = (getDataType() &&
1493  (getDataType()->getModuleKey()=="VuoArtNetInputDevice" ||
1494  getDataType()->getModuleKey()=="VuoArtNetOutputDevice" ||
1495  hasURLType()));
1496 
1497  size_t maxLength = strlen("Matches wildcard (not case-sensitive)");
1498  VuoText t = VuoText_truncateWithEllipsis(fullString, maxLength, truncateFromBeginning?
1499  VuoTextTruncation_Beginning :
1500  VuoTextTruncation_End);
1501  VuoLocal(t);
1502  return string(t);
1503 }
1504 
1510 {
1511  if (!(getInput() && getDataType()))
1512  return "";
1513 
1515  if (getDataType())
1516  {
1517  // Don't display constant input values for generic ports.
1518  if (dynamic_cast<VuoGenericType *>(getDataType()))
1519  return "";
1520 
1521  // Don't display constant input values for list ports.
1522  if (VuoType::isListTypeName(getDataType()->getModuleKey()))
1523  return "";
1524 
1525  if (getDataType()->getModuleKey()=="VuoColor")
1526  {
1527  string colorRGBAJsonString = getConstantAsString();
1528  VuoColor c = VuoColor_makeFromString(colorRGBAJsonString.c_str());
1530  }
1531  if (getDataType()->getModuleKey()=="VuoBoolean")
1533  if (getDataType()->getModuleKey()=="VuoReal")
1534  {
1535  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1536  double real = json_object_get_double(js);
1537  json_object_put(js);
1538 
1539  if (getBase()->getClass()->hasCompiler() && !dynamic_cast<VuoPublishedPort *>(getBase()))
1540  {
1541  json_object *portDetails = static_cast<VuoCompilerEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1542  json_object *autoObject = NULL;
1543  if (json_object_object_get_ex(portDetails, "auto", &autoObject))
1544  if (real == json_object_get_double(autoObject))
1545  return "Auto";
1546  }
1547 
1548  return getStringForRealValue(real);
1549  }
1550  if (getDataType()->getModuleKey()=="VuoInteger")
1551  {
1552  // Retrieve the port's JSON details object.
1553  json_object *details = NULL;
1555  if (portClass)
1556  details = portClass->getDataClass()->getDetails();
1557 
1558  // Case: Port type is named enum
1559  json_object *menuItemsValue = NULL;
1560  if (details && json_object_object_get_ex(details, "menuItems", &menuItemsValue))
1561  {
1562  string portValue = getConstantAsString();
1563  // Support upgrading a VuoBoolean port to a named enum.
1564  if (portValue == "false")
1565  portValue = "0";
1566  else if (portValue == "true")
1567  portValue = "1";
1568 
1569  int len = json_object_array_length(menuItemsValue);
1570  for (int i = 0; i < len; ++i)
1571  {
1572  json_object *menuItem = json_object_array_get_idx(menuItemsValue, i);
1573  if (json_object_is_type(menuItem, json_type_object))
1574  {
1575  json_object *value = NULL;
1576  if (json_object_object_get_ex(menuItem, "value", &value))
1577  if ((json_object_is_type(value, json_type_string) && portValue == json_object_get_string(value))
1578  || (json_object_is_type(value, json_type_int ) && atol(portValue.c_str()) == json_object_get_int64(value)))
1579  {
1580  json_object *name = NULL;
1581  if (json_object_object_get_ex(menuItem, "name", &name))
1582  {
1583  VuoText t = VuoText_makeFromJson(name);
1584  VuoLocal(t);
1585  VuoText tr = VuoText_trim(t); // Trim off leading indentation, if any.
1586  VuoLocal(tr);
1587  return string(tr);
1588  }
1589  }
1590  }
1591  }
1592  }
1593 
1594  // Case: Port type is a regular VuoInteger
1595  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1596  VuoInteger i = json_object_get_int64(js);
1597  json_object_put(js);
1598 
1599  if (getBase()->getClass()->hasCompiler() && !dynamic_cast<VuoPublishedPort *>(getBase()))
1600  {
1601  json_object *portDetails = static_cast<VuoCompilerEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1602  json_object *autoObject = NULL;
1603  if (json_object_object_get_ex(portDetails, "auto", &autoObject))
1604  if (i == json_object_get_int64(autoObject))
1605  return "Auto";
1606  }
1607 
1608  return format("%lld", i);
1609  }
1610  if (getDataType()->getModuleKey()=="VuoPoint2d")
1611  {
1612  VuoPoint2d p = VuoPoint2d_makeFromString(getConstantAsString().c_str());
1613  QList<float> pointList = QList<float>() << p.x << p.y;
1614  return getPointStringForCoords(pointList);
1615  }
1616  if (getDataType()->getModuleKey()=="VuoPoint3d")
1617  {
1618  VuoPoint3d p = VuoPoint3d_makeFromString(getConstantAsString().c_str());
1619  QList<float> pointList = QList<float>() << p.x << p.y << p.z;
1620  return getPointStringForCoords(pointList);
1621  }
1622  if (getDataType()->getModuleKey()=="VuoPoint4d")
1623  {
1624  VuoPoint4d p = VuoPoint4d_makeFromString(getConstantAsString().c_str());
1625  QList<float> pointList = QList<float>() << p.x << p.y << p.z << p.w;
1626  return getPointStringForCoords(pointList);
1627  }
1628  if (getDataType()->getModuleKey()=="VuoFont")
1629  {
1630  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1631  json_object *o = NULL;
1632 
1633  const char *fontName = NULL;
1634  if (json_object_object_get_ex(js, "fontName", &o))
1635  fontName = json_object_get_string(o);
1636 
1637  double pointSize = 0;
1638  if (json_object_object_get_ex(js, "pointSize", &o))
1639  pointSize = json_object_get_double(o);
1640 
1641  bool underline = false;
1642  if (json_object_object_get_ex(js, "underline", &o))
1643  underline = json_object_get_boolean(o);
1644  const char *underlineString = underline ? " [U]" : "";
1645 
1646  string outputString;
1647  if (fontName)
1648  outputString = format("%s %gpt%s", fontName, pointSize, underlineString);
1649 
1650  json_object_put(js);
1651 
1652  return outputString;
1653  }
1654  if (getDataType()->getModuleKey()=="VuoMathExpressionList")
1655  {
1656  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1657  json_object *expressionsObject = NULL;
1658 
1659  string expression;
1660  if (json_object_object_get_ex(js, "expressions", &expressionsObject))
1661  {
1662  if (json_object_get_type(expressionsObject) == json_type_array)
1663  {
1664  int itemCount = json_object_array_length(expressionsObject);
1665  if (itemCount > 0)
1666  {
1667  json_object *itemObject = json_object_array_get_idx(expressionsObject, 0);
1668  if (json_object_get_type(itemObject) == json_type_string)
1669  {
1670  expression = json_object_get_string(itemObject);
1671  json_object_put(itemObject);
1672  }
1673  }
1674  }
1675  }
1676 
1677  json_object_put(js);
1678 
1679  return expression;
1680  }
1681  if (getDataType()->getModuleKey()=="VuoRealRegulation")
1682  {
1683  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1684  json_object *o = NULL;
1685 
1686  string outputString;
1687  if (json_object_object_get_ex(js, "name", &o))
1688  outputString = json_object_get_string(o);
1689 
1690  json_object_put(js);
1691 
1692  return outputString;
1693  }
1694  if (getDataType()->getModuleKey()=="VuoImage")
1695  {
1697  if (!value)
1698  return "";
1699 
1700  VuoLocal(value);
1701  return format("%lu×%lu", value->pixelsWide, value->pixelsHigh);
1702  }
1703  if (getDataType()->getModuleKey()=="VuoTransform")
1704  {
1706 
1707  if (VuoTransform_isIdentity(value))
1708  return "≡";
1709 
1710  if (value.type == VuoTransformTypeTargeted)
1711  return format("(%g,%g,%g) toward (%g,%g,%g)",
1712  value.translation.x, value.translation.y, value.translation.z, value.rotationSource.target.x, value.rotationSource.target.y, value.rotationSource.target.z);
1713 
1714  string rotation;
1715  if (value.type == VuoTransformTypeQuaternion)
1716  rotation = format("‹%g,%g,%g,%g›",
1717  value.rotationSource.quaternion.x, value.rotationSource.quaternion.y, value.rotationSource.quaternion.z, value.rotationSource.quaternion.w);
1718  else
1719  {
1720  VuoPoint3d r = VuoPoint3d_multiply(value.rotationSource.euler, 180./M_PI);
1721  rotation = format("(%g°,%g°,%g°)",
1722  r.x, r.y, r.z);
1723  }
1724 
1725  return format("(%g,%g,%g) %s %g×%g×%g",
1726  value.translation.x, value.translation.y, value.translation.z, rotation.c_str(), value.scale.x, value.scale.y, value.scale.z);
1727  }
1728  if (getDataType()->getModuleKey()=="VuoTransform2d")
1729  {
1731 
1732  if (VuoTransform2d_isIdentity(value))
1733  return "≡";
1734 
1735  VuoReal rotationInDegrees = value.rotation * 180./M_PI;
1736  return format("(%g,%g) %g° %g×%g",
1737  value.translation.x, value.translation.y, rotationInDegrees, value.scale.x, value.scale.y);
1738  }
1739  if (getDataType()->getModuleKey()=="VuoMovieFormat")
1740  {
1741  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1742  json_object *o = NULL;
1743 
1744  const char *imageEncoding = NULL;
1745  if (json_object_object_get_ex(js, "imageEncoding", &o))
1746  {
1747  imageEncoding = json_object_get_string(o);
1748  if (strcasecmp(imageEncoding, "jpeg") == 0)
1749  imageEncoding = "JPEG";
1750  else if (strcasecmp(imageEncoding, "h264") == 0)
1751  imageEncoding = "H.264";
1752  else if (strcasecmp(imageEncoding, "prores4444") == 0)
1753  imageEncoding = "ProRes 4444";
1754  else if (strcasecmp(imageEncoding, "prores422") == 0)
1755  imageEncoding = "ProRes 422";
1756  else if (strcasecmp(imageEncoding, "prores422-hq") == 0)
1757  imageEncoding = "ProRes 422 HQ";
1758  else if (strcasecmp(imageEncoding, "prores422-lt") == 0)
1759  imageEncoding = "ProRes 422 LT";
1760  else if (strcasecmp(imageEncoding, "prores422-proxy") == 0)
1761  imageEncoding = "ProRes 422 Proxy";
1762  else if (strcmp(imageEncoding, "hevc") == 0)
1763  imageEncoding = "HEVC";
1764  else if (strcmp(imageEncoding, "hevc-alpha") == 0)
1765  imageEncoding = "HEVC+Alpha";
1766  }
1767 
1768  const char *audioEncoding = NULL;
1769  if (json_object_object_get_ex(js, "audioEncoding", &o))
1770  {
1771  audioEncoding = json_object_get_string(o);
1772  if (strcmp(audioEncoding, "LinearPCM") == 0)
1773  audioEncoding = "Linear PCM";
1774  }
1775 
1776  string outputString;
1777  if (imageEncoding && audioEncoding)
1778  outputString = format("%s, %s", imageEncoding, audioEncoding);
1779 
1780  json_object_put(js);
1781 
1782  return outputString;
1783  }
1784  if (getDataType()->getModuleKey()=="VuoScreen")
1785  {
1786  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1787  json_object *o = NULL;
1788 
1789  string label;
1790  if (json_object_object_get_ex(js, "type", &o))
1791  {
1792  VuoScreenType type = VuoScreen_typeFromCString(json_object_get_string(o));
1793 
1794  if (type == VuoScreenType_Active)
1795  label = "Active";
1796  else if (type == VuoScreenType_Primary)
1797  label = "Primary";
1798  else if (type == VuoScreenType_Secondary)
1799  label = "Secondary";
1800  else if (type == VuoScreenType_MatchName)
1801  {
1802  if (json_object_object_get_ex(js, "name", &o))
1803  label = json_object_get_string(o);
1804  }
1805  else if (type == VuoScreenType_MatchId)
1806  {
1807  VuoScreen screen = VuoScreen_makeFromJson(js);
1808  VuoScreen realizedScreen;
1809  if (VuoScreen_realize(screen, &realizedScreen))
1810  label = realizedScreen.name;
1811  }
1812  }
1813  json_object_put(js);
1814 
1815  return label;
1816  }
1817  if (getDataType()->getModuleKey()=="VuoSerialDevice")
1818  {
1819  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1820  json_object *o = NULL;
1821 
1822  const char *name = NULL;
1823  if (json_object_object_get_ex(js, "name", &o))
1824  name = json_object_get_string(o);
1825  else if (json_object_object_get_ex(js, "path", &o))
1826  name = json_object_get_string(o);
1827 
1828  string outputString = "First";
1829  if (name && strlen(name))
1830  outputString = name;
1831 
1832  json_object_put(js);
1833 
1834  return outputString;
1835  }
1836  if (getDataType()->getModuleKey()=="VuoMidiInputDevice")
1837  {
1838  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1839  json_object *o = NULL;
1840 
1841  const char *name = NULL;
1842  if (json_object_object_get_ex(js, "name", &o))
1843  name = json_object_get_string(o);
1844 
1845  string outputString = "First";
1846  if (name && strlen(name))
1847  outputString = name;
1848 
1849  json_object_put(js);
1850 
1851  return outputString;
1852  }
1853  if (getDataType()->getModuleKey()=="VuoMidiOutputDevice")
1854  {
1855  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1856  json_object *o = NULL;
1857 
1858  const char *name = NULL;
1859  if (json_object_object_get_ex(js, "name", &o))
1860  name = json_object_get_string(o);
1861 
1862  string outputString = "First";
1863  if (name && strlen(name))
1864  outputString = name;
1865 
1866  json_object_put(js);
1867 
1868  return outputString;
1869  }
1870  if (getDataType()->getModuleKey()=="VuoSyphonServerDescription")
1871  {
1872  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1873  json_object *o = NULL;
1874 
1875  const char *name = NULL;
1876  if (json_object_object_get_ex(js, "serverName", &o))
1877  {
1878  const char *n = json_object_get_string(o);
1879  if (strcmp(n, "*"))
1880  name = n;
1881  }
1882  if (!name && json_object_object_get_ex(js, "applicationName", &o))
1883  {
1884  const char *n = json_object_get_string(o);
1885  if (strcmp(n, "*"))
1886  name = n;
1887  }
1888 
1889  string outputString = "First";
1890  if (name && strlen(name))
1891  outputString = name;
1892 
1893  json_object_put(js);
1894 
1895  return outputString;
1896  }
1897  if (getDataType()->getModuleKey()=="VuoVideoInputDevice")
1898  {
1899  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1900  json_object *o = NULL;
1901 
1902  const char *name = NULL;
1903  if (json_object_object_get_ex(js, "name", &o))
1904  name = json_object_get_string(o);
1905  else if (json_object_object_get_ex(js, "id", &o))
1906  name = json_object_get_string(o);
1907 
1908  string outputString = "Default";
1909  if (name && strlen(name))
1910  outputString = name;
1911 
1912  json_object_put(js);
1913 
1914  return outputString;
1915  }
1916  if (getDataType()->getModuleKey()=="VuoAudioInputDevice")
1917  {
1918  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1919  json_object *o = NULL;
1920 
1921  const char *name = NULL;
1922  if (json_object_object_get_ex(js, "name", &o))
1923  name = json_object_get_string(o);
1924 
1925  string outputString = "Default";
1926  if (name && strlen(name))
1927  outputString = name;
1928 
1929  json_object_put(js);
1930 
1931  return outputString;
1932  }
1933  if (getDataType()->getModuleKey()=="VuoAudioOutputDevice")
1934  {
1935  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1936  json_object *o = NULL;
1937 
1938  const char *name = NULL;
1939  if (json_object_object_get_ex(js, "name", &o))
1940  name = json_object_get_string(o);
1941 
1942  string outputString = "Default";
1943  if (name && strlen(name))
1944  outputString = name;
1945 
1946  json_object_put(js);
1947 
1948  return outputString;
1949  }
1950  if (getDataType()->getModuleKey()=="VuoHidDevice")
1951  {
1952  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1953  json_object *o = NULL;
1954 
1955  QString outputString;
1956  if (json_object_object_get_ex(js, "name", &o))
1957  {
1958  outputString = json_object_get_string(o);
1959 
1960  // Trim off the parenthetical vendor/class.
1961  outputString = outputString.section(" (", 0, 0);
1962  }
1963  json_object_put(js);
1964 
1965  return outputString.toStdString();
1966  }
1967  if (getDataType()->getModuleKey()=="VuoOscInputDevice"
1968  || getDataType()->getModuleKey()=="VuoOscOutputDevice")
1969  {
1970  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1971  json_object *o = NULL;
1972 
1973  const char *name = NULL;
1974  if (json_object_object_get_ex(js, "name", &o))
1975  name = json_object_get_string(o);
1976 
1977  string outputString = "Auto";
1978  if (name && strlen(name))
1979  outputString = name;
1980 
1981  json_object_put(js);
1982 
1983  return outputString;
1984  }
1985  if (getDataType()->getModuleKey()=="VuoArtNetInputDevice"
1986  || getDataType()->getModuleKey()=="VuoArtNetOutputDevice")
1987  {
1988  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1989  json_object *o = NULL;
1990 
1991  const char *name = NULL;
1992  if (json_object_object_get_ex(js, "name", &o))
1993  name = json_object_get_string(o);
1994  else
1995  {
1996  if (getDataType()->getModuleKey()=="VuoArtNetInputDevice")
1997  name = "Any";
1998  else
1999  name = "Broadcast";
2000  }
2001 
2002  VuoInteger net=0, subNet=0, universe=0;
2003  if (json_object_object_get_ex(js, "net", &o))
2004  net = json_object_get_int64(o);
2005  if (json_object_object_get_ex(js, "subNet", &o))
2006  subNet = json_object_get_int64(o);
2007  if (json_object_object_get_ex(js, "universe", &o))
2008  universe = json_object_get_int64(o);
2009 
2010  string outputString = format("%s (%lld:%lld:%lld)", name, net, subNet, universe);
2011 
2012  json_object_put(js);
2013 
2014  return outputString;
2015  }
2016  if (getDataType()->getModuleKey()=="VuoTempoRange")
2017  {
2018  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2019  const char *tempoRange = json_object_get_string(js);
2020  if (!tempoRange)
2021  return "Unknown";
2022  if (strcmp(tempoRange, "andante") == 0)
2023  return "70–110 BPM";
2024  else if (strcmp(tempoRange, "moderato") == 0)
2025  return "100–140 BPM";
2026  else if (strcmp(tempoRange, "allegro") == 0)
2027  return "120–180 BPM";
2028  else if (strcmp(tempoRange, "presto") == 0)
2029  return "170–250 BPM";
2030  else if (strcmp(tempoRange, "prestissimo") == 0)
2031  return "220–320 BPM";
2032  }
2033  if (getDataType()->getModuleKey()=="VuoEdgeBlend")
2034  {
2035  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2036 
2037  double cutoff = 0, gamma = 0, crop = 0;
2038  json_object *o = NULL;
2039 
2040  if (json_object_object_get_ex(js, "cutoff", &o))
2041  cutoff = json_object_get_double(o);
2042 
2043  if (json_object_object_get_ex(js, "gamma", &o))
2044  gamma = json_object_get_double(o);
2045 
2046  if (json_object_object_get_ex(js, "crop", &o))
2047  crop = json_object_get_double(o);
2048 
2049  json_object_put(js);
2050 
2051  double cropPercent = -crop * 100;
2052  double cutoffPercent = cutoff * 100;
2053  if (VuoReal_areEqual(crop, 0) && VuoReal_areEqual(cutoff, 0))
2054  return "≡";
2055  else if (VuoReal_areEqual(cutoff, 0))
2056  return format("%.0f%%", cropPercent);
2057  else if (VuoReal_areEqual(gamma, 1))
2058  return format("%.0f%% %.0f%%", cropPercent, cutoffPercent);
2059  else
2060  return format("%.0f%% %.0f%% @ %.2gγ", cropPercent, cutoffPercent, gamma);
2061  }
2062  if (getDataType()->getModuleKey()=="VuoRange")
2063  {
2064  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2065 
2066  double minimum = VuoRange_NoMinimum, maximum = VuoRange_NoMaximum;
2067 
2068  json_object *o = NULL;
2069 
2070  if (json_object_object_get_ex(js, "minimum", &o))
2071  minimum = json_object_get_double(o);
2072 
2073  if (json_object_object_get_ex(js, "maximum", &o))
2074  maximum = json_object_get_double(o);
2075 
2076  json_object_put(js);
2077 
2078  if (minimum != VuoRange_NoMinimum && maximum != VuoRange_NoMaximum)
2079  return format("%.4g to %.4g", minimum, maximum);
2080  else if (minimum != VuoRange_NoMinimum)
2081  return format("%.4g to ∞", minimum);
2082  else if (maximum != VuoRange_NoMaximum)
2083  return format("-∞ to %.4g", maximum);
2084  else
2085  return format("-∞ to ∞");
2086  }
2087  if (getDataType()->getModuleKey()=="VuoIntegerRange")
2088  {
2089  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2090 
2092 
2093  json_object *o = NULL;
2094 
2095  if (json_object_object_get_ex(js, "minimum", &o))
2096  minimum = json_object_get_int64(o);
2097 
2098  if (json_object_object_get_ex(js, "maximum", &o))
2099  maximum = json_object_get_int64(o);
2100 
2101  json_object_put(js);
2102 
2103  if (minimum != VuoIntegerRange_NoMinimum && maximum != VuoIntegerRange_NoMaximum)
2104  return format("%lld to %lld", minimum, maximum);
2105  else if (minimum != VuoIntegerRange_NoMinimum)
2106  return format("%lld to ∞", minimum);
2107  else if (maximum != VuoIntegerRange_NoMaximum)
2108  return format("-∞ to %lld", maximum);
2109  else
2110  return format("-∞ to ∞");
2111  }
2112  if (getDataType()->getModuleKey() == "VuoAnchor")
2113  {
2115  return stringAndFree(VuoAnchor_getSummary(value));
2116  }
2117  if (getDataType()->getModuleKey() == "VuoTextComparison")
2118  {
2121  }
2122  if (getDataType()->getModuleKey() == "VuoBlackmagicInputDevice"
2123  || getDataType()->getModuleKey() == "VuoBlackmagicOutputDevice")
2124  {
2125  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2126  json_object *o;
2127  if (json_object_object_get_ex(js, "name", &o))
2128  return json_object_get_string(o);
2129  else
2130  return "First";
2131  }
2132  if (getDataType()->getModuleKey() == "VuoSpeechVoice")
2133  {
2136  }
2137  if (getDataType()->getModuleKey() == "VuoRectangle")
2138  {
2140  return stringAndFree(VuoRectangle_getSummary(value));
2141  }
2142  }
2143 
2144  // If it's a JSON string (e.g., VuoText or an enum identifier), unescape and optionally capitalize it.
2145  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2146  if (json_object_get_type(js) == json_type_string)
2147  {
2148  string textWithoutQuotes = json_object_get_string(js);
2149  json_object_put(js);
2150 
2151  string type;
2152  if (getDataType())
2153  type = getDataType()->getModuleKey();
2154 
2155  // Leave text as-is.
2156  if (type == "VuoText"
2157  || type == "VuoImageFormat"
2158  || type == "VuoAudioEncoding"
2159  || type == "VuoBlackmagicConnection"
2160  || type == "VuoBlackmagicVideoMode"
2161  || type == "VuoMovieImageEncoding")
2162  return textWithoutQuotes;
2163 
2164  // All-caps.
2165  if (type == "VuoTableFormat")
2166  {
2167  std::transform(textWithoutQuotes.begin(), textWithoutQuotes.end(), textWithoutQuotes.begin(), ::toupper);
2168  return textWithoutQuotes;
2169  }
2170 
2171  // Convert hyphenations to camelcase.
2172  // Example: VuoTimeFormat
2173  for (auto it = textWithoutQuotes.begin(); it != textWithoutQuotes.end();)
2174  if (*it == '-')
2175  {
2176  textWithoutQuotes.erase(it);
2177  *it = toupper(*it);
2178  }
2179  else
2180  ++it;
2181 
2182  return VuoStringUtilities::expandCamelCase(textWithoutQuotes);
2183  }
2184  json_object_put(js);
2185 
2186  return getConstantAsString();
2187 }
2188 
2192 void VuoRendererPort::setConstant(string constantValue)
2193 {
2194  VuoCompilerInputEventPort *eventPort = dynamic_cast<VuoCompilerInputEventPort *>(getBase()->getCompiler());
2195  if (eventPort)
2196  {
2197  QGraphicsItem::CacheMode normalCacheMode = cacheMode();
2198  setCacheMode(QGraphicsItem::NoCache);
2199  updateGeometry();
2200 
2201  eventPort->getData()->setInitialValue(constantValue);
2202 
2204  setToolTip(QString("<span></span>") + getConstantAsStringToRender().c_str());
2205  else
2206  setToolTip("");
2207 
2208  setCacheMode(normalCacheMode);
2212 
2213  // Ensure this node's cable paths are updated to escape the new constant's flag.
2214  set<VuoCable *> cables = getRenderedParentNode()->getConnectedInputCables(true);
2215  for (set<VuoCable *>::iterator i = cables.begin(); i != cables.end(); ++i)
2216  {
2217  (*i)->getRenderer()->setPortConstantsChanged();
2218  (*i)->getRenderer()->updateGeometry();
2219  }
2220  }
2221 }
2222 
2229 {
2230  bool sidebarPaintMode = dynamic_cast<const VuoRendererPublishedPort *>(this);
2231  string name = getPortNameToRender();
2233 
2234  if (name.empty() || isAnimated)
2235  return false;
2236  else if (parent && parent->isMissingImplementation())
2237  return false;
2238  else if (sidebarPaintMode)
2239  return true;
2240  else if (isRefreshPort || isFunctionPort || typecastParentPort)
2241  return false;
2242 
2243  return true;
2244 }
2245 
2253 string VuoRendererPort::getPointStringForCoords(QList<float> coordList) const
2254 {
2255  const QString coordSeparator = QString(QLocale::system().decimalPoint() != ','? QChar(',') : QChar(';')).append(" ");
2256  QStringList coordStringList;
2257 
2258  foreach (float coord, coordList)
2259  coordStringList.append(getStringForRealValue(coord).c_str());
2260 
2261  QString pointString = QString("(").append(coordStringList.join(coordSeparator).append(")"));
2262  return pointString.toStdString();
2263 }
2264 
2269 string VuoRendererPort::getStringForRealValue(double value) const
2270 {
2271  // See VuoDoubleSpinBox::textFromValue.
2272  QString valueAsStringInUserLocale = QLocale::system().toString(value, 'g', 11);
2273  if (qAbs(value) >= 1000.0)
2274  valueAsStringInUserLocale.remove(QLocale::system().groupSeparator());
2275 
2276  return valueAsStringInUserLocale.toStdString();
2277 }
2278 
2284 {
2285  // Like the `double` version, but reduced to 7 so we don't display bogus precision when rendering VuoPoint*d (whose components are `float`, not `double` like VuoReal).
2286  QString valueAsStringInUserLocale = QLocale::system().toString(value, 'g', 7);
2287  if (qAbs(value) >= 1000.0)
2288  valueAsStringInUserLocale.remove(QLocale::system().groupSeparator());
2289 
2290  return valueAsStringInUserLocale.toStdString();
2291 }
2292 
2297 {
2298  // @todo: Allow generic published ports (https://b33p.net/kosada/node/7655).
2299  bool isGeneric = bool(dynamic_cast<VuoGenericType *>(this->getDataType()));
2300 
2301  // @todo: Allow published dictionary ports (https://b33p.net/kosada/node/8524).
2302  bool hasDictionaryType = (this->getDataType() && VuoStringUtilities::beginsWith(this->getDataType()->getModuleKey(), "VuoDictionary_"));
2303 
2304  // @todo: Allow published math expression ports for "Calculate" nodes (https://b33p.net/kosada/node/8550).
2305  bool isMathExpressionInputToCalculateNode = (this->getDataType() &&
2306  (this->getDataType()->getModuleKey() == "VuoMathExpressionList") &&
2307  this->getUnderlyingParentNode() &&
2308  VuoStringUtilities::beginsWith(this->getUnderlyingParentNode()->getBase()->getNodeClass()->getClassName(), "vuo.math.calculate"));
2309 
2310 
2311  // @todo: Allow direct connections between external published inputs and external published outputs
2312  // (https://b33p.net/kosada/node/7756).
2313  return (!isGeneric && !hasDictionaryType && !isMathExpressionInputToCalculateNode && !dynamic_cast<const VuoRendererPublishedPort *>(this));
2314 }
2315 
2320 vector<VuoRendererPublishedPort *> VuoRendererPort::getPublishedPorts(void) const
2321 {
2322  vector <VuoRendererPublishedPort *> publishedPorts;
2323  foreach (VuoCable *cable, getBase()->getConnectedCables(true))
2324  {
2325  if (getInput() && cable->isPublishedInputCable())
2326  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getFromPort()->getRenderer()));
2327  else if (getOutput() && cable->isPublishedOutputCable())
2328  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getToPort()->getRenderer()));
2329  }
2330 
2331  return publishedPorts;
2332 }
2333 
2338 vector<VuoRendererPublishedPort *> VuoRendererPort::getPublishedPortsConnectedByDataCarryingCables(void) const
2339 {
2340  vector <VuoRendererPublishedPort *> publishedPorts;
2341  foreach (VuoCable *cable, getBase()->getConnectedCables(true))
2342  {
2343  if (getInput() && cable->isPublishedInputCable() && cable->getRenderer()->effectivelyCarriesData())
2344  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getFromPort()->getRenderer()));
2345  else if (getOutput() && cable->isPublishedOutputCable() && cable->getRenderer()->effectivelyCarriesData())
2346  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getToPort()->getRenderer()));
2347  }
2348 
2349  return publishedPorts;
2350 }
2351 
2357 {
2358  this->timeLastEventFired = VuoRendererColors::getVirtualFiredEventOrigin();
2359 }
2360 
2365 {
2366  this->timeLastEventFired = QDateTime::currentMSecsSinceEpoch();
2367 }
2368 
2374 {
2375  this->timeLastEventFired = VuoRendererColors::getVirtualFiredEventOriginForAnimationFadePercentage(percentage);
2376 }
2377 
2381 vector<QGraphicsItemAnimation *> VuoRendererPort::getAnimations()
2382 {
2383  return this->animations;
2384 }
2385 
2390 void VuoRendererPort::setAnimated(bool animated)
2391 {
2392  this->isAnimated = animated;
2394 }
2395 
2399 void VuoRendererPort::setCacheModeForPortAndChildren(QGraphicsItem::CacheMode mode)
2400 {
2401  this->setCacheMode(mode);
2402 
2403  VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>(this);
2404  if (typecastPort)
2405  typecastPort->getChildPort()->setCacheMode(mode);
2406 }
2407 
2413 {
2414  // Port animations, and ports without compilers, shouldn't accept mouse events.
2415  setEnabled(!isAnimated &&
2416  ((getBase()->hasCompiler() && getBase()->getClass()->hasCompiler()) ||
2417  dynamic_cast<VuoRendererPublishedPort *>(this)) &&
2418  !isHiddenRefreshPort());
2419 }
2420 
2428 {
2429  // A published port name must:
2430  // - Contain only alphanumeric characters; and
2431  // - Either be entirely numeric or begin with an alphabetic character; and
2432  // - Have a total length of 1-31 characters.
2433  return QString("[A-Za-z][A-Za-z0-9]{0,30}")
2434  .append("|")
2435  .append("[0-9]{1,31}");
2436 }
2437 
2447 {
2448  // Remove non-alphanumeric characters.
2449  portID.remove(QRegExp("[^A-Za-z0-9]"));
2450 
2451  // Unless the identifier is purely numeric, remove non-alphabetic first characters.
2452  if (!portID.contains(QRegExp("^[0-9]+$")))
2453  {
2454  while (!portID.isEmpty() && !portID.contains(QRegExp("^[A-Za-z]")))
2455  portID = portID.right(portID.size()-1);
2456  }
2457 
2458  // Remove characters beyond the 31st.
2459  portID = portID.left(31);
2460 
2461  return portID;
2462 }
2463 
2464 VuoRendererPort::~VuoRendererPort()
2465 {
2466  foreach (QGraphicsItemAnimation *animation, animations)
2467  animation->clear();
2468 
2469  animations.clear();
2470 }