Vuo  2.1.0
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 "VuoAudioInputDevice.h"
28 #include "VuoAudioOutputDevice.h"
29 #include "VuoImage.h"
30 #include "VuoIntegerRange.h"
31 #include "VuoRange.h"
32 #include "VuoScreen.h"
33 #include "VuoTransform.h"
34 #include "VuoUrl.h"
35 #include "VuoSpeechVoice.h"
36 }
37 
38 #pragma clang diagnostic push
39 #pragma clang diagnostic ignored "-Wdocumentation"
40  #include <json-c/json.h>
41 #pragma clang diagnostic pop
42 
43 
44 #include "VuoHeap.h"
45 
46 const qreal VuoRendererPort::portRadius = 8; // VuoRendererFonts::thickPenWidth*8./20.
47 const qreal VuoRendererPort::portSpacing = 15; // VuoRendererFonts::thickPenWidth*3.0/4.0
48 const qreal VuoRendererPort::portContainerMargin = 3.333333; // VuoRendererFonts::thickPenWidth / 6.
49 const qreal VuoRendererPort::portInset = 1.4;
50 const qreal VuoRendererPort::portBarrierWidth = 5.5; // VuoRendererFonts::thickPenWidth*5.5/20.
51 const qreal VuoRendererPort::portConstantTextPadding = 6.5; // VuoRendererFonts::thickPenWidth*6.5/20.
52 
57  bool isOutput, bool isRefreshPort, bool isFunctionPort)
58  : VuoBaseDetail<VuoPort>("VuoRendererPort standard", basePort)
59 {
60  getBase()->setRenderer(this);
61 
62  setZValue(portZValue);
63 
64  this->signaler = signaler;
65 
66  this->isOutput = isOutput;
67  this->isRefreshPort = isRefreshPort;
68  this->isFunctionPort = isFunctionPort;
69  this->_eligibilityHighlight = VuoRendererColors::noHighlight;
70  this->isEligibleForSelection = false;
71  setAnimated(false);
72  this->typecastParentPort = NULL;
74 
76 
77  const int maxAnimationsPerPort = 4;
78 
79  if (getInput() || (getBase()->getClass()->getPortType() == VuoPortClass::triggerPort))
80  {
81  for (int i = 0; i < maxAnimationsPerPort; ++i)
82  {
83  QGraphicsItemAnimation *animation = new QGraphicsItemAnimation();
84  animation->setTimeLine(new QTimeLine(VuoRendererColors::activityAnimationFadeDuration));
85  animations.push_back(animation);
86  }
87  }
88 
89  if (! isHiddenRefreshPort())
90  {
91  setFlag(QGraphicsItem::ItemIsFocusable, true); // allow delivery of key events
92  setAcceptHoverEvents(true); // allow delivery of mouse-hover events
93  }
94 
99 
101  setToolTip(QString("<span></span>") + getConstantAsStringToRender().c_str());
102 }
103 
108 void VuoRendererPort::addRoundedTriangle(QPainterPath &p, QPointF center, qreal radius, qreal cornerRadius)
109 {
110  p.moveTo(center + QPointF(radius,0));
111  for (int theta = 0; theta <= 360; theta += 120)
112  {
113  QRectF rect(center.x() + (radius - cornerRadius)*cos(theta*M_PI/180.) - cornerRadius,
114  center.y() - (radius - cornerRadius)*sin(theta*M_PI/180.) - cornerRadius,
115  cornerRadius*2,
116  cornerRadius*2);
117  bool first = theta==0;
118  bool last = theta==360;
119 
120  p.arcTo(rect,
121  theta - (first ? 0 : 60),
122  (first||last ? 60 : 120));
123  }
124 }
125 
129 QPainterPath VuoRendererPort::getPortPath() const
130 {
131  return cachedPortPath;
132 }
133 
138 {
139  if (isHiddenRefreshPort())
140  {
141  cachedPortPath = QPainterPath();
142  return;
143  }
144 
146  getBase()->getClass()->getPortType(),
147  isConstant() ? QString::fromUtf8(getConstantAsTruncatedStringToRender().c_str()) : "",
148  getInput(),
149  carriesData()
150  );
151 }
152 
157 QPainterPath VuoRendererPort::getPortPath(qreal inset,
158  VuoPortClass::PortType portType,
159  QString constantText,
160  bool isInputPort,
161  bool carriesData
162  )
163 {
164  QPainterPath p;
165  QRectF outerPortRect = getPortRect();
166  QRectF innerPortRect = outerPortRect.adjusted(inset,inset,-inset,-inset);
167 
168  QRectF textRect = getPortConstantTextRectForText(constantText);
169 
170  qreal left = textRect.x() - portConstantTextPadding + inset - VuoRendererPort::portInset;
171  QPointF topLeftCorner(left + .5, innerPortRect.top() - .24);
172  QPointF topRightCorner(innerPortRect.right() + .5, innerPortRect.top() - .24);
173  QPointF bottomRightCorner(innerPortRect.right() + .5, innerPortRect.bottom() - .43);
174  QPointF bottomLeftCorner(left + .5, innerPortRect.bottom() - .43);
175 
176  p.moveTo(innerPortRect.right() + .5, innerPortRect.center().y());
177  qreal adjustedPortRadius = portRadius - 2;
178  addRoundedCorner(p, true, bottomRightCorner, adjustedPortRadius, false, false);
179  addRoundedCorner(p, true, bottomLeftCorner, adjustedPortRadius, false, true);
180  addRoundedCorner(p, true, topLeftCorner, adjustedPortRadius, true, true);
181  addRoundedCorner(p, true, topRightCorner, adjustedPortRadius, true, false);
182 
183  return p;
184 }
185 
190 {
191  return QRectF(
192  -portRadius,
193  -portRadius,
194  portRadius*2.0,
195  portRadius*2.0
196  );
197 }
198 
203 {
204  QRectF barrierRect = QRectF();
205 
206  bool sidebarPaintMode = dynamic_cast<const VuoRendererPublishedPort *>(this);
209 
210  if (!isAnimated &&
211  ((!isOutput && !sidebarPaintMode && eventBlocking != VuoPortClass::EventBlocking_None)
212  || (isOutput && type == VuoPortClass::triggerPort))
213  )
214  {
215  QRectF portRect = getPortRect();
216  if (isOutput)
217  barrierRect = QRectF(portRect.topLeft() + QPointF( 2 - VuoRendererPort::portBarrierWidth, 2), portRect.bottomLeft() + QPointF( 2, -3));
218  else
219  barrierRect = QRectF(portRect.topRight() + QPointF(-1 + VuoRendererPort::portBarrierWidth, 2), portRect.bottomRight() + QPointF(-1, -3));
220  }
221 
222  return barrierRect;
223 }
224 
229 {
230  bool paintDataAntenna = hasConnectedWirelessDataCable(true);
231  bool paintEventAntenna = hasConnectedWirelessEventCable(true);
232  if (!paintDataAntenna && !paintEventAntenna)
233  return QPainterPath();
234 
235  // Mast
236  qreal cableWidth;
237  VuoRendererCable::getCableSpecs(paintDataAntenna, cableWidth);
238 
239  const qreal constantWidth = fmax(0, getPortConstantTextRect().width() - 3);
240  const qreal mastLength = portRadius * 2.;
241  const qreal pixelOffset = -.3;
242  QPointF startPoint = (getInput()? -QPointF(mastLength - (paintDataAntenna ? 0.5 : 0 ) + constantWidth, -pixelOffset) : QPointF(0, pixelOffset));
243  QPointF endPoint = (getInput()? QPointF(-constantWidth, pixelOffset) : QPointF(mastLength + (paintDataAntenna ? 0.5 : 0 ), pixelOffset));
244 
245  VuoCable cableBase(NULL, NULL, NULL, NULL);
246  VuoRendererCable cableRenderer(&cableBase);
247  QPainterPath mastPath = cableRenderer.getCablePathForEndpoints(startPoint, endPoint);
248 
249  QPainterPathStroker mastStroker;
250  mastStroker.setWidth(cableWidth);
251  mastStroker.setCapStyle(Qt::RoundCap);
252  QPainterPath antennaOutline = mastStroker.createStroke(mastPath);
253 
254  // Crossbars
255  qreal outerCrossbarXOffset = (paintDataAntenna? 1 : 0.5);
256  const QPointF outerCrossbarPos = (getInput()? startPoint - QPointF(outerCrossbarXOffset, 0) :
257  endPoint + QPointF(outerCrossbarXOffset, 0));
258  const int crossbarSpacing = 5;
259  const int crossbarHeight = VuoRendererFonts::midPenWidth*5;
260 
261  QPainterPath crossbars;
262  for (int i = 0; i < 2; ++i)
263  {
264  crossbars.moveTo((outerCrossbarPos +
265  QPointF(QPointF(0, -0.5*crossbarHeight) +
266  QPointF((getInput()? 1 : -1)*crossbarSpacing*i, 0))));
267  crossbars.lineTo((outerCrossbarPos +
268  QPointF(QPointF(0, 0.5*crossbarHeight) +
269  QPointF((getInput()? 1 : -1)*crossbarSpacing*i, 0))));
270  }
271 
272  // Union the mast and crossbars.
273  QPainterPathStroker crossbarStroker;
274  crossbarStroker.setWidth(cableWidth*3/5);
275  crossbarStroker.setCapStyle(Qt::RoundCap);
276  antennaOutline += crossbarStroker.createStroke(crossbars);
277 
278  return antennaOutline;
279 }
280 
284 bool VuoRendererPort::hasConnectedWirelessDataCable(bool includePublishedCables) const
285 {
286  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
287  for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
288  if ((*cable)->hasRenderer() && (*cable)->getRenderer()->effectivelyCarriesData() &&
289  (*cable)->getRenderer()->getEffectivelyWireless() &&
290  (*cable)->getRenderer()->paintingDisabled())
291  return true;
292  return false;
293 }
294 
298 bool VuoRendererPort::hasConnectedWirelessEventCable(bool includePublishedCables) const
299 {
300  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
301  for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
302  if ((*cable)->hasRenderer() && !(*cable)->getRenderer()->effectivelyCarriesData() &&
303  (*cable)->getRenderer()->getEffectivelyWireless() &&
304  (*cable)->getRenderer()->paintingDisabled())
305  return true;
306  return false;
307 }
308 
313 {
314  VuoRendererNode *renderedParentNode = getRenderedParentNode();
315  if (renderedParentNode)
316  return renderedParentNode->getBase()->getTintColor();
317  else
318  {
319  // Tint protocol ports the same color as the protocol.
320  if (dynamic_cast<const VuoRendererPublishedPort *>(this) &&
321  (dynamic_cast<VuoPublishedPort *>(this->getBase()))->isProtocolPort())
322  {
323  // @todo: Account for multiple simultaneous active protocols. https://b33p.net/kosada/node/9585
324  return VuoRendererColors::getActiveProtocolTint(0, !isOutput);
325  }
326  }
327 
328  return VuoNode::TintNone;
329 }
330 
338 {
339  if (!getInput())
340  return getPortTint();
341 
342  set<VuoNode::TintColor> connectedPortTints;
343  foreach (VuoRendererPort *port, getPortsConnectedWirelessly(true))
344  {
345  connectedPortTints.insert(port->getPortTint());
346  if (connectedPortTints.size() > 1)
347  return VuoNode::TintNone;
348  }
349 
350  if (connectedPortTints.size() == 1)
351  return *connectedPortTints.begin();
352  else
353  return VuoNode::TintNone;
354 }
355 
359 set<VuoRendererPort *> VuoRendererPort::getPortsConnectedWirelessly(bool includePublishedCables) const
360 {
361  set<VuoRendererPort *> connectedPorts;
362  foreach (VuoCable *cable, getBase()->getConnectedCables(includePublishedCables))
363  {
364  if (cable->hasRenderer() &&
365  cable->getRenderer()->getEffectivelyWireless() &&
366  cable->getRenderer()->paintingDisabled())
367  {
368  VuoPort *connectedPort = (getInput()? cable->getFromPort() : cable->getToPort());
369  if (connectedPort && connectedPort->hasRenderer())
370  connectedPorts.insert(connectedPort->getRenderer());
371  }
372  }
373 
374  return connectedPorts;
375 }
376 
382 {
384 }
385 
391 {
393  if (underlyingAttachment &&
394  underlyingAttachment->getRenderedHostPort() &&
395  underlyingAttachment->getRenderedHostPort()->hasRenderer() &&
396  (underlyingAttachment->getRenderedHostPort()->getRenderer() == targetHostPort) &&
397  (dynamic_cast<VuoRendererInputDrawer *>(underlyingAttachment)))
398  return dynamic_cast<VuoRendererInputDrawer *>(underlyingAttachment);
399 
400  else if (underlyingAttachment)
401  {
402  // The drawer might not be directly connected in the underlying composition. Find it anyway.
403  foreach (VuoPort *port, underlyingAttachment->getBase()->getInputPorts())
404  {
405  VuoRendererInputDrawer *upstreamDrawer = port->getRenderer()->getAttachedInputDrawerRenderedWithHostPort(targetHostPort);
406  if (upstreamDrawer)
407  return upstreamDrawer;
408  }
409  }
410 
411  return NULL;
412 }
413 
419 {
420  if (! getInput())
421  return NULL;
422 
423  vector<VuoCable *> inCables = getBase()->getConnectedCables(false);
424  foreach (VuoCable *cable, inCables)
425  {
426  VuoNode *fromNode = cable->getFromNode();
427  if (fromNode && fromNode->hasRenderer() &&
428  dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer()) &&
429  dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer())->getUnderlyingHostPort()->getRenderer() == this)
430  return dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer());
431  }
432 
433  return NULL;
434 }
435 
441 set<VuoRendererInputAttachment *> VuoRendererPort::getAllUnderlyingUpstreamInputAttachments(void) const
442 {
443  set<VuoRendererInputAttachment *> allUpstreamAttachments;
444  VuoRendererInputAttachment *directUpstreamAttachment = getUnderlyingInputAttachment();
445  if (!directUpstreamAttachment)
446  return allUpstreamAttachments;
447 
448  allUpstreamAttachments.insert(directUpstreamAttachment);
449 
450  vector<VuoPort *> inputPorts = directUpstreamAttachment->getBase()->getInputPorts();
451  foreach (VuoPort *port, inputPorts)
452  {
453  set<VuoRendererInputAttachment *> indirectUpstreamAttachments = port->getRenderer()->getAllUnderlyingUpstreamInputAttachments();
454  allUpstreamAttachments.insert(indirectUpstreamAttachments.begin(), indirectUpstreamAttachments.end());
455  }
456 
457  return allUpstreamAttachments;
458 }
459 
465 {
466  return (isConstant()?
468  QRectF());
469 }
470 
477 {
478  static QHash<QString, int> cachedTextWidths;
479  QHash<QString, int>::iterator i = cachedTextWidths.find(text);
480  if (i != cachedTextWidths.end())
481  return i.value();
482 
483  int textWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodePortConstantFont())
484  .boundingRect(QRectF(0,0,0,0), Qt::TextIncludeTrailingSpaces, text)
485  .width();
486  cachedTextWidths.insert(text, textWidth);
487  return textWidth;
488 }
489 
494 {
495  int textWidth = getTextWidth(text) + 1;
496 
497  QRectF textRect(
498  -textWidth - portConstantTextPadding + 8,
500  textWidth,
501  (VuoRendererPort::portRadius - 1)*2 - 1
502  );
503 
504  return textRect.toAlignedRect();
505 }
506 
507 
512 {
513  return this->nameRect;
514 }
515 
520 {
521  QString text = QString::fromUtf8(getPortNameToRender().c_str());
522  QFont font = getPortNameFont();
523  QSizeF textSize = QFontMetricsF(font).size(0,text);
524 
525  bool isPortOnDrawer = dynamic_cast<VuoRendererInputAttachment *>(getUnderlyingParentNode());
526 
527  this->nameRect = QRectF(
528  (isOutput? -VuoRendererFonts::thickPenWidth/2.0 - textSize.width() - VuoRendererPort::portRadius :
531  + (isPortOnDrawer ? 2 : VuoRendererPort::portRadius) + 2.
532  ),
533  -VuoRendererFonts::thickPenWidth/3.0 - (isPortOnDrawer ? 0 : 1),
534  textSize.width(),
535  textSize.height()
536  );
537 }
538 
548 {
549  VuoRendererNode *node;
550  if (getTypecastParentPort())
551  node = (((VuoRendererTypecastPort *)(getTypecastParentPort()))->getUncollapsedTypecastNode());
552  else
553  node = getRenderedParentNode();
554 
555  return node;
556 }
557 
567 {
568  if (!parentItem())
569  return NULL;
570 
571  if (!parentItem()->parentItem())
572  return (VuoRendererNode *)(parentItem());
573 
574  return (VuoRendererNode *)(parentItem()->parentItem());
575 }
576 
581 {
582  return typecastParentPort;
583 }
584 
589 {
590  this->typecastParentPort = typecastParentPort;
591 }
592 
597 {
598  return cachedBoundingRect;
599 }
600 
605 {
606  VuoRendererNode *renderedParentNode = getRenderedParentNode();
607  if ((renderedParentNode && renderedParentNode->paintingDisabled()) || isHiddenRefreshPort())
608  {
609  cachedBoundingRect = QRectF();
610  return;
611  }
612 
613  QRectF r = getPortPath().boundingRect();
614 
615  r = r.united(getEventBarrierRect());
616 
618  r = r.united(getNameRect());
619 
620  if (hasPortAction())
621  r = r.united(getActionIndicatorRect());
622 
623  r = r.united(getWirelessAntennaPath().boundingRect());
624 
625  // Antialiasing bleed
626  r.adjust(-1,-1,1,1);
627 
628  cachedBoundingRect = r.toAlignedRect();
629 }
630 
635 QPainterPath VuoRendererPort::shape() const
636 {
637  QPainterPath p;
638  p.addRect(boundingRect());
639  return p;
640 }
641 
646 {
647  bool sidebarPaintMode = dynamic_cast<VuoRendererPublishedPort *>(this);
648 
651 
652  if (
653  !isAnimated &&
654  ((!isOutput && !sidebarPaintMode && eventBlocking != VuoPortClass::EventBlocking_None)
655  || (isOutput && type == VuoPortClass::triggerPort))
656  )
657  {
658  QRectF barrierRect = getEventBarrierRect();
659  QColor eventBlockingBarrierColor = (isAnimated? colors->animatedeventBlockingBarrier() : colors->eventBlockingBarrier());
660  painter->setPen(QPen(eventBlockingBarrierColor, VuoRendererPort::portBarrierWidth, Qt::SolidLine, Qt::RoundCap));
661 
662  if (eventBlocking == VuoPortClass::EventBlocking_Wall || type == VuoPortClass::triggerPort)
663  painter->drawLine(barrierRect.center() + QPointF(0, -barrierRect.height()/2. + VuoRendererPort::portBarrierWidth/2. - .83),
664  barrierRect.center() + QPointF(0, barrierRect.height()/2. - VuoRendererPort::portBarrierWidth/2. +1.16));
665  else // VuoPortClass::EventBlocking_Door
666  {
667  painter->drawPoint(barrierRect.center() + QPointF(0, -barrierRect.height()/2. + 1.75 + .17));
668  painter->drawPoint(barrierRect.center() + QPointF(0, barrierRect.height()/2. - 1.75 + .17));
669  }
670  }
671 }
672 
676 QFont VuoRendererPort::getPortNameFont(void) const
677 {
679  // Use a smaller font for port labels on drawers.
681  else
683 }
684 
688 void VuoRendererPort::paintPortName(QPainter *painter, VuoRendererColors *colors)
689 {
691  return;
692 
693  VuoRendererPublishedPort *rpp = dynamic_cast<VuoRendererPublishedPort *>(this);
694 
695  string name = getPortNameToRender();
696 
697  if (rpp)
698  painter->setPen((rpp->isSelected() && rpp->getCurrentlyActive())
699  ? Qt::white
700  : (dynamic_cast<VuoPublishedPort *>(rpp->getBase())->isProtocolPort() ? colors->publishedProtocolPortTitle() : colors->publishedPortTitle()));
701  else
702  painter->setPen(colors->portTitle());
703 
704  painter->setFont(getPortNameFont());
705  painter->drawText(getNameRect(), isOutput? Qt::AlignRight : Qt::AlignLeft, QString::fromStdString(name));
706 }
707 
713 {
714  bool displayPortName = (getRenderedParentNode()? getRenderedParentNode()->nameDisplayEnabledForPort(this) : true);
715 
716  return (!displayPortName? "": getPortNameToRenderWhenDisplayed());
717 }
718 
723 {
724  const VuoRendererPublishedPort *publishedPort = dynamic_cast<const VuoRendererPublishedPort *>(this);
725  return (publishedPort? getBase()->getClass()->getName() :
727  getBase()->getClass()->getName()));
728 }
729 
735 {
736  this->customizedPortName = name;
737  updateNameRect();
738 }
739 
745 {
746  VuoPortClass *pc = getBase()->getClass();
747  if (pc->hasCompiler())
748  return static_cast<VuoCompilerPortClass *>(pc->getCompiler())->getDisplayName();
749  else
750  return "";
751 }
752 
757 {
758  return getBase()->getClass()->hasPortAction();
759 }
760 
765 {
766  QFontMetricsF fontMetrics = QFontMetricsF(getPortNameFont());
767  const qreal marginFromPortName = 4;
768  const qreal triangleSize = 6;
769  qreal triangleLeft = qRound( getNameRect().right() + marginFromPortName );
770  qreal triangleTop = qRound( getNameRect().bottom() - fontMetrics.descent() - fontMetrics.xHeight() );
771 
772  return QRectF(triangleLeft, triangleTop, triangleSize, triangleSize);
773 }
774 
779 {
780  if (hasPortAction())
781  {
782  QRectF rect = getActionIndicatorRect();
783 
784  QPainterPath p;
785  addRoundedTriangle(p, rect.center() + QPointF(-1,-.75), qRound(rect.width()/2.) + .5, VuoRendererNode::cornerRadius/9.);
786 
787  QColor color = colors->actionIndicator();
788  painter->fillPath(p, color);
789  }
790 }
791 
796 {
797  painter->fillPath(getWirelessAntennaPath(), QBrush(colors->cableMain()));
798 }
799 
803 void VuoRendererPort::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
804 {
805  if (isHiddenRefreshPort())
806  return;
807 
808  VuoRendererNode *renderedParentNode = getRenderedParentNode();
809  if (renderedParentNode && renderedParentNode->paintingDisabled())
810  return;
811 
812  VuoRendererPublishedPort *publishedPort = dynamic_cast<VuoRendererPublishedPort *>(this);
813 
814  // Workaround to prevent items that have been removed from the scene from being painted on the scene anyway.
815  // https://b33p.net/kosada/node/7938
816  if (!(scene() || publishedPort))
817  return;
818 
819  painter->setRenderHint(QPainter::Antialiasing, true);
820  drawBoundingRect(painter);
821 
822  bool isColorInverted = isRefreshPort || isFunctionPort;
823 
824  VuoRendererColors::SelectionType selectionType = ((renderedParentNode && renderedParentNode->isSelected())? VuoRendererColors::directSelection :
825  VuoRendererColors::noSelection);
826 
827  bool isHovered = isEligibleForSelection;
828  qint64 timeOfLastActivity = getTimeOfLastActivity();
829 
830  // If an attached drawer does have eligible ports, ensure this host port isn't faded out, so the port name is legible.
831  VuoRendererColors::HighlightType effectiveHighlight = _eligibilityHighlight;
832  bool effectivelyHovered = isHovered;
834  if (drawer)
835  {
836  VuoRendererColors::HighlightType drawerHighlight = drawer->getEligibilityHighlight();
837  if (drawerHighlight < _eligibilityHighlight)
838  {
839  effectiveHighlight = drawerHighlight;
840 
841  if (_eligibilityHighlight == VuoRendererColors::ineligibleHighlight)
842  effectivelyHovered = false;
843  }
844  }
845 
847  selectionType,
848  effectivelyHovered,
849  effectiveHighlight,
850  timeOfLastActivity);
852  selectionType,
853  isHovered,
855  timeOfLastActivity);
856 
857  // Draw the port circle / constant flag
858  QPainterPath portPath = getPortPath();
859 
860  QBrush portBrush;
861 
862  if (isColorInverted)
863  portBrush = colors->portTitlebarFill();
864  else if (isAnimated)
865  portBrush = colors->animatedPortFill();
866  else if (publishedPort && dynamic_cast<VuoPublishedPort *>(publishedPort->getBase())->isProtocolPort())
867  portBrush = colors->portTitlebarFill();
868  else if (publishedPort)
869  portBrush = colors->publishedPortFill();
870  else
871  portBrush = colors->portFill();
872 
873 
874  if (!isConstant())
875  {
876  bool showRightHalfOnly = false;
878  if (drawer)
879  {
880  VuoRendererPort *drawerChildPort = (drawer && (drawer->getInputPorts().size() >= VuoNodeClass::unreservedInputPortStartIndex+1)?
882  NULL);
883 
884  // Prevent neighboring semi-transparent highlights from overlapping in a misleading way
885  // Essentially: Paint the whole circle if the circle is meant to be more opaque than the drawer handle it intersects.
886  qint64 timeNow = QDateTime::currentMSecsSinceEpoch();
887  const double fadeThreshold = 0.3; // Tuned visually.
888  qint64 childTimeOfLastActivity = drawerChildPort? drawerChildPort->getTimeOfLastActivity() : VuoRendererItem::notTrackingActivity;
889  bool showingActiveEvent = (timeOfLastActivity != VuoRendererItem::notTrackingActivity) &&
890  (((timeNow - timeOfLastActivity) < VuoRendererColors::activityAnimationFadeDuration*fadeThreshold) ||
891  (timeOfLastActivity == VuoRendererItem::activityInProgress));
892  bool childShowingActiveEvent = (childTimeOfLastActivity != VuoRendererItem::notTrackingActivity) &&
893  (((timeNow - childTimeOfLastActivity) < VuoRendererColors::activityAnimationFadeDuration*fadeThreshold) ||
894  (childTimeOfLastActivity == VuoRendererItem::activityInProgress));
895  showRightHalfOnly = !effectivelyHovered && drawerChildPort &&
896  (drawerChildPort->eligibilityHighlight() <= effectiveHighlight) &&
897  !(showingActiveEvent && !childShowingActiveEvent);
898  }
899 
900  if (showRightHalfOnly)
901  painter->setClipRect(QRectF(-0.39, -portRadius, portRadius, portRadius*2.));
902 
903  painter->fillPath(portPath, portBrush);
904 
905  if (showRightHalfOnly)
906  painter->setClipping(false);
907  }
908  else
909  {
910  // Display a color swatch for VuoColor data.
912  bool isColorPort = getDataType() && getDataType()->getModuleKey()=="VuoColor";
913  if (isColorPort)
914  {
915  string colorRGBAJsonString = getConstantAsString();
916  VuoColor c = VuoColor_makeFromString(colorRGBAJsonString.c_str());
917  QColor swatchColor = QColor(c.r*255, c.g*255, c.b*255, c.a*255 * portBrush.color().alphaF());
918  VuoReal h,s,l,a;
919  VuoColor_getHSLA(c, &h, &s, &l, &a);
920 
921  bool isDark = colors->isDark();
922 
923  // Two possible swatches:
924 
925  // 1. Semitransparent color, or solid color that matches the canvas background: Draw a background+border, then draw the swatch.
926  if ((a < 1) || (isDark ? l < .25 : l > .75))
927  {
928  // Fill the entire background with a color distinct from the canvas.
929  QColor topLeftColor = colors->nodeFrame();
930  painter->fillPath(portPath, topLeftColor);
931 
932  // Fill the bottom right of the background with the opposite color.
933  // Use a slightly smaller circle, so the topLeftColor acts as a border.
934  QColor bottomRightColor = isDark ? Qt::black : Qt::white;
935  QTransform transform;
936  transform.scale(0.87, 0.87);
937  QPainterPath smallerCircle = portPath * transform;
938  {
939  QRectF r = portPath.boundingRect();
940  QPainterPath bottomRight;
941  bottomRight.moveTo(r.bottomLeft());
942  bottomRight.lineTo(r.topRight());
943  bottomRight.lineTo(r.bottomRight());
944  painter->setClipPath(smallerCircle);
945  painter->fillPath(bottomRight, bottomRightColor);
946  painter->setClipping(false);
947  }
948 
949  // Draw the swatch.
950  painter->fillPath(smallerCircle, swatchColor);
951  }
952 
953  // 2. Solid color that's distinct from the canvas background: Just draw the swatch.
954  else
955  painter->fillPath(portPath, swatchColor);
956  }
957  else
958  {
959  painter->fillPath(portPath, portBrush);
960 
961  QString constantText = QString::fromUtf8(getConstantAsTruncatedStringToRender().c_str());
962  QBrush constantFlagBackgroundBrush = colors->constantFill();
963 
964  // Constant string
965  QRectF textRect = getPortConstantTextRectForText(constantText);
966  painter->setPen(colors->constantText());
967  painter->setFont(VuoRendererFonts::getSharedFonts()->nodePortConstantFont());
968  painter->drawText(textRect, Qt::AlignLeft, constantText);
969  }
970  }
971 
972  if (! carriesData())
973  {
974  QRectF r = getPortRect();
975  QPainterPath p;
976  addRoundedTriangle(p, r.center() + QPointF(.5, -.3), (r.width() - VuoRendererPort::portInset*2)/2.-2, VuoRendererNode::cornerRadius/6.);
977  painter->fillPath(p, colors->portIcon());
978  }
979 
980  paintEventBarrier(painter, colors);
981  paintPortName(painter, colors);
982  paintActionIndicator(painter, colors);
983  paintWirelessAntenna(painter, antennaColors);
984 
985  delete colors;
986  delete antennaColors;
987 }
988 
993 {
995  bool isTriggerPort = (type == VuoPortClass::triggerPort);
996 
997  VuoRendererComposition *composition = dynamic_cast<VuoRendererComposition *>(scene());
998  bool renderNodeActivity = composition && composition->getRenderNodeActivity();
999  bool renderPortActivity = composition && composition->getRenderPortActivity();
1000  VuoRendererNode *renderedParentNode = getRenderedParentNode();
1001 
1002  return ((! renderNodeActivity)? VuoRendererItem::notTrackingActivity :
1003  ((isTriggerPort && renderPortActivity)? timeLastEventFired :
1004  (getTypecastParentPort()? static_cast<VuoRendererTypecastPort *>(getTypecastParentPort())->getUncollapsedTypecastNode()->getTimeLastExecutionEnded() :
1005  (renderedParentNode? renderedParentNode->getTimeLastExecutionEnded() :
1006  VuoRendererItem::notTrackingActivity))));
1007 }
1008 
1014 {
1015  return isEligibleForSelection;
1016 }
1017 
1023 {
1024  return _eligibilityHighlight == VuoRendererColors::standardHighlight
1025  || _eligibilityHighlight == VuoRendererColors::subtleHighlight;
1026 }
1027 
1032 {
1033  _eligibilityHighlight = eligibility;
1034 }
1035 
1040 {
1041  return _eligibilityHighlight;
1042 }
1043 
1047 void VuoRendererPort::extendedHoverEnterEvent(bool cableDragUnderway, bool disableConnectedCableHighlight)
1048 {
1049  extendedHoverMoveEvent(cableDragUnderway, disableConnectedCableHighlight);
1050 }
1051 
1057 void VuoRendererPort::extendedHoverMoveEvent(bool cableDragUnderway, bool disableConnectedCableHighlight)
1058 {
1059  QGraphicsItem::CacheMode normalCacheMode = cacheMode();
1060  setCacheMode(QGraphicsItem::NoCache);
1061 
1062  prepareGeometryChange();
1063  isEligibleForSelection = (cableDragUnderway? isEligibleForConnection() : true);
1064 
1065  setCacheMode(normalCacheMode);
1066 
1067  setFocus();
1068 
1069  if (!cableDragUnderway && !disableConnectedCableHighlight)
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(true);
1077  }
1078  }
1079 }
1080 
1085 {
1086  QGraphicsItem::CacheMode normalCacheMode = cacheMode();
1087  setCacheMode(QGraphicsItem::NoCache);
1088 
1089  prepareGeometryChange();
1090  isEligibleForSelection = false;
1091 
1092  setCacheMode(normalCacheMode);
1093 
1094  clearFocus();
1095 
1096  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(false);
1097  if (supportsDisconnectionByDragging() && (! connectedCables.empty()))
1098  {
1099  VuoRendererCable *cableToDisconnect = connectedCables.back()->getRenderer();
1100  cableToDisconnect->updateGeometry();
1101  cableToDisconnect->setHovered(false);
1102  }
1103 }
1104 
1118 {
1119  bool fromPortIsEnabledOutput = (this->getOutput() && this->isEnabled());
1120  bool toPortIsEnabledInput = (toPort && toPort->getInput() && toPort->isEnabled());
1121 
1122  if (!(fromPortIsEnabledOutput && toPortIsEnabledInput))
1123  return false;
1124 
1125  // OK: Any connection made using an event-only cable.
1126  if (eventOnlyConnection)
1127  return true;
1128 
1129  VuoType *fromDataType = this->getDataType();
1130  VuoType *toDataType = toPort->getDataType();
1131 
1132  // OK: Event-only to event+data.
1133  // OK: Event-only to event-only.
1134  // OK: Event+data to event-only.
1135  if (!fromDataType || !toDataType)
1136  return true;
1137 
1138  // OK: Event+data to event+data, if types are non-generic and identical.
1139  if (! dynamic_cast<VuoGenericType *>(fromDataType) && (fromDataType == toDataType))
1140  return true;
1141 
1142  // OK: Event+data to event+data, if types are generic and compatible.
1143  if (dynamic_cast<VuoGenericType *>(fromDataType) && dynamic_cast<VuoGenericType *>(toDataType))
1144  {
1146  if (VuoType::isListTypeName(fromDataType->getModuleKey()) != VuoType::isListTypeName(toDataType->getModuleKey()))
1147  return false;
1148 
1149  return (dynamic_cast<VuoGenericType *>(fromDataType)->isGenericTypeCompatible(dynamic_cast<VuoGenericType *>(toDataType)));
1150  }
1151 
1152  return false;
1153 }
1154 
1171 {
1172  VuoRendererPort *portToSpecialize = NULL;
1173  string specializedTypeName = "";
1174 
1175  return (this->canConnectDirectlyWithSpecializationTo(toPort, eventOnlyConnection, &portToSpecialize, specializedTypeName));
1176 }
1177 
1191 bool VuoRendererPort::canConnectDirectlyWithSpecializationTo(VuoRendererPort *toPort, bool eventOnlyConnection, VuoRendererPort **portToSpecialize, string &specializedTypeName)
1192 {
1193  *portToSpecialize = NULL;
1194  specializedTypeName = "";
1195 
1196  if (this->canConnectDirectlyWithoutSpecializationTo(toPort, eventOnlyConnection))
1197  return true;
1198 
1199  bool fromPortIsEnabledOutput = (this->getOutput() && this->isEnabled());
1200  bool toPortIsEnabledInput = (toPort && toPort->getInput() && toPort->isEnabled());
1201 
1202  if (!(fromPortIsEnabledOutput && toPortIsEnabledInput))
1203  return false;
1204 
1205  VuoType *originalFromDataType = ((VuoCompilerPortClass *)(this->getBase()->getClass()->getCompiler()))->getDataVuoType();
1206  VuoType *originalToDataType = ((VuoCompilerPortClass *)(toPort->getBase()->getClass()->getCompiler()))->getDataVuoType();
1207 
1208  VuoType *currentFromDataType = this->getDataType();
1209  VuoType *currentToDataType = toPort->getDataType();
1210 
1211  if (!(originalFromDataType && originalToDataType && currentFromDataType && currentToDataType))
1212  return false;
1213 
1214  VuoGenericType *currentFromGenericType = dynamic_cast<VuoGenericType *>(currentFromDataType);
1215  VuoGenericType *currentToGenericType = dynamic_cast<VuoGenericType *>(currentToDataType);
1216 
1218  if (VuoType::isListTypeName(currentFromDataType->getModuleKey()) != VuoType::isListTypeName(currentToDataType->getModuleKey()))
1219  return false;
1220 
1221  // Case: The 'From' port is generic and can be specialized to match the concrete type of the 'To' port.
1222  if (currentFromGenericType && currentFromGenericType->isSpecializedTypeCompatible(originalToDataType->getModuleKey()))
1223  {
1224  *portToSpecialize = this;
1225  specializedTypeName = originalToDataType->getModuleKey();
1226 
1227  return true;
1228  }
1229 
1230  // Case: The 'To' port is generic and can be specialized to match the concrete type of the 'From' port.
1231  else if (currentToGenericType && currentToGenericType->isSpecializedTypeCompatible(originalFromDataType->getModuleKey()))
1232  {
1233  *portToSpecialize = toPort;
1234  specializedTypeName = originalFromDataType->getModuleKey();
1235 
1236  return true;
1237  }
1238 
1239  return false;
1240 }
1241 
1245 VuoCable * VuoRendererPort::getCableConnectedTo(VuoRendererPort *toPort, bool includePublishedCables)
1246 {
1247  vector<VuoCable *> cables = this->getBase()->getConnectedCables(includePublishedCables);
1248  for (vector<VuoCable *>::iterator cable = cables.begin(); cable != cables.end(); ++cable)
1249  if ((*cable)->getToPort() == toPort->getBase())
1250  return (*cable);
1251 
1252  return NULL;
1253 }
1254 
1258 void VuoRendererPort::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
1259 {
1260  if (isConstant())
1261  {
1263  }
1264 }
1265 
1269 void VuoRendererPort::keyPressEvent(QKeyEvent *event)
1270 {
1271  if (isConstant() &&
1272  (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter))
1273  {
1275  }
1276 }
1277 
1283 {
1284  return (getInput() &&
1285  (! dynamic_cast<VuoRendererTypecastPort *>(this)) &&
1286  (! getAttachedInputDrawer()));
1287 }
1288 
1293 {
1294  return ((! isOutput) && (! isFunctionPort));
1295 }
1296 
1301 {
1302  return isOutput;
1303 }
1304 
1309 {
1310  return isRefreshPort;
1311 }
1312 
1317 {
1318  return isFunctionPort;
1319 }
1320 
1325 {
1326  return isRefreshPort && getBase()->getConnectedCables(true).empty();
1327 }
1328 
1333 {
1334  return (getBase()->getClass()->getPortType() == VuoPortClass::dataAndEventPort ||
1335  (getBase()->getClass()->hasCompiler() &&
1336  static_cast<VuoCompilerPortClass *>(getBase()->getClass()->getCompiler())->getDataVuoType()));
1337 }
1338 
1343 {
1345  this->prepareGeometryChange();
1347 }
1348 
1352 QVariant VuoRendererPort::itemChange(GraphicsItemChange change, const QVariant &value)
1353 {
1354  // Port has moved relative to its parent
1355  if (change == QGraphicsItem::ItemPositionHasChanged)
1356  {
1357  VuoRendererNode *parentNode = getRenderedParentNode();
1358  if (parentNode)
1360  }
1361 
1362  return QGraphicsItem::itemChange(change, value);
1363 }
1364 
1370 {
1371  if (!(getBase() && getBase()->hasCompiler()))
1372  return NULL;
1373 
1374  VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(getBase()->getCompiler());
1375  return compilerPort->getDataVuoType();
1376 }
1377 
1384 {
1385  // For now, ports with URLs are expected to be of type "VuoText".
1386  if ( !(getDataType() && getDataType()->getModuleKey() == "VuoText") )
1387  return false;
1388 
1389  string portName = getBase()->getClass()->getName();
1390 
1391  // Case: Port is titled "url"
1392  if (portName == "url")
1393  return true;
1394 
1395  // Case: Port is titled "folder"
1396  // Relevant for vuo.file.list node.
1397  if (portName == "folder")
1398  return true;
1399 
1400  // Case: Port is an input port on a drawer attached to a port titled "urls"
1401  // Relevant for vuo.image.fetch.list, vuo.scene.fetch.list nodes.
1403  if (drawer)
1404  {
1405  VuoPort *hostPort = drawer->getRenderedHostPort();
1406  if (hostPort && (hostPort->getClass()->getName() == "urls"))
1407  return true;
1408  }
1409 
1410  return false;
1411 }
1412 
1421 {
1422  if ( !(isConstant() && hasURLType()) )
1423  return false;
1424 
1425  json_object *details = static_cast<VuoCompilerInputEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1426  json_object *isSaveValue = NULL;
1427  if (details && json_object_object_get_ex(details, "isSave", &isSaveValue) && json_object_get_boolean(isSaveValue))
1428  return false;
1429 
1431  VuoRetain(url);
1432  bool isRelativePath = VuoUrl_isRelativePath(url);
1433  VuoRelease(url);
1434  return isRelativePath;
1435 }
1436 
1441 {
1442  return ((getInput() && getDataType()) && // input port with data...
1443  (!effectivelyHasConnectedDataCable(true))); // ... that has no incoming data cable (published or unpublished).
1444 }
1445 
1450 bool VuoRendererPort::effectivelyHasConnectedDataCable(bool includePublishedCables) const
1451 {
1452  vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
1453  for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
1454  if ((*cable)->hasRenderer() && (*cable)->getRenderer()->effectivelyCarriesData())
1455  return true;
1456  return false;
1457 }
1458 
1462 string VuoRendererPort::format(const char *format, ...)
1463 {
1464  va_list args;
1465 
1466  va_start(args, format);
1467  int size = vsnprintf(NULL, 0, format, args);
1468  va_end(args);
1469 
1470  char *formattedString = (char *)malloc(size+1);
1471  va_start(args, format);
1472  vsnprintf(formattedString, size+1, format, args);
1473  va_end(args);
1474 
1475  string s(formattedString);
1476  free(formattedString);
1477  return s;
1478 }
1479 
1484 {
1485  string s(strz);
1486  free(strz);
1487  return s;
1488 }
1489 
1494 {
1495  if (!(getInput() && getDataType()))
1496  return "";
1497 
1498  VuoCompilerInputEventPort *compilerEventPort = dynamic_cast<VuoCompilerInputEventPort *>(getBase()->getCompiler());
1499  if (! compilerEventPort)
1500  return "";
1501 
1502  return compilerEventPort->getData()->getInitialValue();
1503 }
1504 
1510 {
1511  VuoText fullString = VuoText_make(getConstantAsStringToRender().c_str());
1512  VuoLocal(fullString);
1513 
1514  if (getDataType() && (getDataType()->getModuleKey() == "VuoColor"))
1515  return "";
1516 
1517  bool truncateFromBeginning = (getDataType() &&
1518  (getDataType()->getModuleKey()=="VuoArtNetInputDevice" ||
1519  getDataType()->getModuleKey()=="VuoArtNetOutputDevice" ||
1520  hasURLType()));
1521 
1522  size_t maxLength = strlen("Matches wildcard (not case-sensitive)");
1523  VuoText t = VuoText_truncateWithEllipsis(fullString, maxLength, truncateFromBeginning?
1524  VuoTextTruncation_Beginning :
1525  VuoTextTruncation_End);
1526  VuoLocal(t);
1527  return string(t);
1528 }
1529 
1535 #define RETURN_SHORT_SUMMARY(type) \
1536  if (getDataType()->getModuleKey() == #type) \
1537  { \
1538  type value = type ## _makeFromString(getConstantAsString().c_str()); \
1539  type ## _retain(value); \
1540  string s = stringAndFree(type ## _getShortSummary(value)); \
1541  type ## _release(value); \
1542  return s; \
1543  }
1544 
1550 {
1551  if (!(getInput() && getDataType()))
1552  return "";
1553 
1555  if (getDataType())
1556  {
1557  // Don't display constant input values for generic ports.
1558  if (dynamic_cast<VuoGenericType *>(getDataType()))
1559  return "";
1560 
1561  string typeName = getDataType()->getModuleKey();
1562 
1563  // Don't display constant input values for list ports.
1564  if (VuoType::isListTypeName(typeName))
1565  return "";
1566 
1567  // Don't display constant input values for types that don't have input editors.
1568  if (typeName == "VuoData")
1569  return "";
1570 
1571  if (getDataType()->getModuleKey()=="VuoColor")
1572  {
1573  string colorRGBAJsonString = getConstantAsString();
1574  VuoColor c = VuoColor_makeFromString(colorRGBAJsonString.c_str());
1576  }
1577  if (getDataType()->getModuleKey()=="VuoBoolean")
1579  if (getDataType()->getModuleKey()=="VuoReal")
1580  {
1581  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1582  double real = json_object_get_double(js);
1583  json_object_put(js);
1584 
1585  if (getBase()->getClass()->hasCompiler() && !dynamic_cast<VuoPublishedPort *>(getBase()))
1586  {
1587  json_object *portDetails = static_cast<VuoCompilerEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1588  json_object *autoObject = NULL;
1589  if (json_object_object_get_ex(portDetails, "auto", &autoObject))
1590  if (real == json_object_get_double(autoObject))
1591  return "Auto";
1592  }
1593 
1594  return getStringForRealValue(real);
1595  }
1596  if (getDataType()->getModuleKey()=="VuoInteger")
1597  {
1598  // Retrieve the port's JSON details object.
1599  json_object *details = NULL;
1601  if (portClass)
1602  details = portClass->getDataClass()->getDetails();
1603 
1604  // Case: Port type is named enum
1605  json_object *menuItemsValue = NULL;
1606  if (details && json_object_object_get_ex(details, "menuItems", &menuItemsValue))
1607  {
1608  string portValue = getConstantAsString();
1609  // Support upgrading a VuoBoolean port to a named enum.
1610  if (portValue == "false")
1611  portValue = "0";
1612  else if (portValue == "true")
1613  portValue = "1";
1614 
1615  int len = json_object_array_length(menuItemsValue);
1616  for (int i = 0; i < len; ++i)
1617  {
1618  json_object *menuItem = json_object_array_get_idx(menuItemsValue, i);
1619  if (json_object_is_type(menuItem, json_type_object))
1620  {
1621  json_object *value = NULL;
1622  if (json_object_object_get_ex(menuItem, "value", &value))
1623  if ((json_object_is_type(value, json_type_string) && portValue == json_object_get_string(value))
1624  || (json_object_is_type(value, json_type_int ) && atol(portValue.c_str()) == json_object_get_int64(value)))
1625  {
1626  json_object *name = NULL;
1627  if (json_object_object_get_ex(menuItem, "name", &name))
1628  {
1629  VuoText t = VuoText_makeFromJson(name);
1630  VuoLocal(t);
1631  VuoText tr = VuoText_trim(t); // Trim off leading indentation, if any.
1632  VuoLocal(tr);
1633  return string(tr);
1634  }
1635  }
1636  }
1637  }
1638  }
1639 
1640  // Case: Port type is a regular VuoInteger
1641  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1642  VuoInteger i = json_object_get_int64(js);
1643  json_object_put(js);
1644 
1645  if (getBase()->getClass()->hasCompiler() && !dynamic_cast<VuoPublishedPort *>(getBase()))
1646  {
1647  json_object *portDetails = static_cast<VuoCompilerEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1648  json_object *autoObject = NULL;
1649  if (json_object_object_get_ex(portDetails, "auto", &autoObject))
1650  if (i == json_object_get_int64(autoObject))
1651  return "Auto";
1652  }
1653 
1654  return format("%lld", i);
1655  }
1656  if (getDataType()->getModuleKey()=="VuoPoint2d")
1657  {
1658  VuoPoint2d p = VuoPoint2d_makeFromString(getConstantAsString().c_str());
1659  QList<float> pointList = QList<float>() << p.x << p.y;
1660  return getPointStringForCoords(pointList);
1661  }
1662  if (getDataType()->getModuleKey()=="VuoPoint3d")
1663  {
1664  VuoPoint3d p = VuoPoint3d_makeFromString(getConstantAsString().c_str());
1665  QList<float> pointList = QList<float>() << p.x << p.y << p.z;
1666  return getPointStringForCoords(pointList);
1667  }
1668  if (getDataType()->getModuleKey()=="VuoPoint4d")
1669  {
1670  VuoPoint4d p = VuoPoint4d_makeFromString(getConstantAsString().c_str());
1671  QList<float> pointList = QList<float>() << p.x << p.y << p.z << p.w;
1672  return getPointStringForCoords(pointList);
1673  }
1674  if (getDataType()->getModuleKey()=="VuoFont")
1675  {
1676  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1677  json_object *o = NULL;
1678 
1679  const char *fontName = NULL;
1680  if (json_object_object_get_ex(js, "fontName", &o))
1681  fontName = json_object_get_string(o);
1682 
1683  double pointSize = 0;
1684  if (json_object_object_get_ex(js, "pointSize", &o))
1685  pointSize = json_object_get_double(o);
1686 
1687  bool underline = false;
1688  if (json_object_object_get_ex(js, "underline", &o))
1689  underline = json_object_get_boolean(o);
1690  const char *underlineString = underline ? " [U]" : "";
1691 
1692  string outputString;
1693  if (fontName)
1694  outputString = format("%s %gpt%s", fontName, pointSize, underlineString);
1695 
1696  json_object_put(js);
1697 
1698  return outputString;
1699  }
1700  if (getDataType()->getModuleKey()=="VuoMathExpressionList")
1701  {
1702  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1703  json_object *expressionsObject = NULL;
1704 
1705  string expression;
1706  if (json_object_object_get_ex(js, "expressions", &expressionsObject))
1707  {
1708  if (json_object_get_type(expressionsObject) == json_type_array)
1709  {
1710  int itemCount = json_object_array_length(expressionsObject);
1711  if (itemCount > 0)
1712  {
1713  json_object *itemObject = json_object_array_get_idx(expressionsObject, 0);
1714  if (json_object_get_type(itemObject) == json_type_string)
1715  {
1716  expression = json_object_get_string(itemObject);
1717  json_object_put(itemObject);
1718  }
1719  }
1720  }
1721  }
1722 
1723  json_object_put(js);
1724 
1725  return expression;
1726  }
1727  if (getDataType()->getModuleKey()=="VuoRealRegulation")
1728  {
1729  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1730  json_object *o = NULL;
1731 
1732  string outputString;
1733  if (json_object_object_get_ex(js, "name", &o))
1734  outputString = json_object_get_string(o);
1735 
1736  json_object_put(js);
1737 
1738  return outputString;
1739  }
1740  if (getDataType()->getModuleKey()=="VuoImage")
1741  {
1743  if (!value)
1744  return "";
1745 
1746  VuoLocal(value);
1747  return format("%lu×%lu", value->pixelsWide, value->pixelsHigh);
1748  }
1749  if (getDataType()->getModuleKey()=="VuoTransform")
1750  {
1752 
1753  if (VuoTransform_isIdentity(value))
1754  return "≡";
1755 
1756  if (value.type == VuoTransformTypeTargeted)
1757  return format("(%g,%g,%g) toward (%g,%g,%g)",
1758  value.translation.x, value.translation.y, value.translation.z, value.rotationSource.target.x, value.rotationSource.target.y, value.rotationSource.target.z);
1759 
1760  string rotation;
1761  if (value.type == VuoTransformTypeQuaternion)
1762  rotation = format("‹%g,%g,%g,%g›",
1763  value.rotationSource.quaternion.x, value.rotationSource.quaternion.y, value.rotationSource.quaternion.z, value.rotationSource.quaternion.w);
1764  else
1765  {
1766  VuoPoint3d r = VuoPoint3d_multiply(value.rotationSource.euler, 180./M_PI);
1767  rotation = format("(%g°,%g°,%g°)",
1768  r.x, r.y, r.z);
1769  }
1770 
1771  return format("(%g,%g,%g) %s %g×%g×%g",
1772  value.translation.x, value.translation.y, value.translation.z, rotation.c_str(), value.scale.x, value.scale.y, value.scale.z);
1773  }
1774  if (getDataType()->getModuleKey()=="VuoTransform2d")
1775  {
1777 
1778  if (VuoTransform2d_isIdentity(value))
1779  return "≡";
1780 
1781  VuoReal rotationInDegrees = value.rotation * 180./M_PI;
1782  return format("(%g,%g) %g° %g×%g",
1783  value.translation.x, value.translation.y, rotationInDegrees, value.scale.x, value.scale.y);
1784  }
1785  if (getDataType()->getModuleKey()=="VuoMovieFormat")
1786  {
1787  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1788  json_object *o = NULL;
1789 
1790  const char *imageEncoding = NULL;
1791  if (json_object_object_get_ex(js, "imageEncoding", &o))
1792  {
1793  imageEncoding = json_object_get_string(o);
1794  if (strcasecmp(imageEncoding, "jpeg") == 0)
1795  imageEncoding = "JPEG";
1796  else if (strcasecmp(imageEncoding, "h264") == 0)
1797  imageEncoding = "H.264";
1798  else if (strcasecmp(imageEncoding, "prores4444") == 0)
1799  imageEncoding = "ProRes 4444";
1800  else if (strcasecmp(imageEncoding, "prores422") == 0)
1801  imageEncoding = "ProRes 422";
1802  else if (strcasecmp(imageEncoding, "prores422-hq") == 0)
1803  imageEncoding = "ProRes 422 HQ";
1804  else if (strcasecmp(imageEncoding, "prores422-lt") == 0)
1805  imageEncoding = "ProRes 422 LT";
1806  else if (strcasecmp(imageEncoding, "prores422-proxy") == 0)
1807  imageEncoding = "ProRes 422 Proxy";
1808  else if (strcmp(imageEncoding, "hevc") == 0)
1809  imageEncoding = "HEVC";
1810  else if (strcmp(imageEncoding, "hevc-alpha") == 0)
1811  imageEncoding = "HEVC+Alpha";
1812  }
1813 
1814  const char *audioEncoding = NULL;
1815  if (json_object_object_get_ex(js, "audioEncoding", &o))
1816  {
1817  audioEncoding = json_object_get_string(o);
1818  if (strcmp(audioEncoding, "LinearPCM") == 0)
1819  audioEncoding = "Linear PCM";
1820  }
1821 
1822  string outputString;
1823  if (imageEncoding && audioEncoding)
1824  outputString = format("%s, %s", imageEncoding, audioEncoding);
1825 
1826  json_object_put(js);
1827 
1828  return outputString;
1829  }
1830  if (getDataType()->getModuleKey()=="VuoScreen")
1831  {
1832  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1833  json_object *o = NULL;
1834 
1835  string label;
1836  if (json_object_object_get_ex(js, "type", &o))
1837  {
1838  VuoScreenType type = VuoScreen_typeFromCString(json_object_get_string(o));
1839 
1840  if (type == VuoScreenType_Active)
1841  label = "Active";
1842  else if (type == VuoScreenType_Primary)
1843  label = "Primary";
1844  else if (type == VuoScreenType_Secondary)
1845  label = "Secondary";
1846  else if (type == VuoScreenType_MatchName)
1847  {
1848  if (json_object_object_get_ex(js, "name", &o))
1849  label = json_object_get_string(o);
1850  }
1851  else if (type == VuoScreenType_MatchId)
1852  {
1853  VuoScreen screen = VuoScreen_makeFromJson(js);
1854  VuoScreen realizedScreen;
1855  if (VuoScreen_realize(screen, &realizedScreen))
1856  label = realizedScreen.name;
1857  }
1858  }
1859  json_object_put(js);
1860 
1861  return label;
1862  }
1863  if (getDataType()->getModuleKey()=="VuoSerialDevice")
1864  {
1865  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1866  json_object *o = NULL;
1867 
1868  const char *name = NULL;
1869  if (json_object_object_get_ex(js, "name", &o))
1870  name = json_object_get_string(o);
1871  else if (json_object_object_get_ex(js, "path", &o))
1872  name = json_object_get_string(o);
1873 
1874  string outputString = "First";
1875  if (name && strlen(name))
1876  outputString = name;
1877 
1878  json_object_put(js);
1879 
1880  return outputString;
1881  }
1882  if (getDataType()->getModuleKey()=="VuoMidiInputDevice")
1883  {
1884  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1885  json_object *o = NULL;
1886 
1887  const char *name = NULL;
1888  if (json_object_object_get_ex(js, "name", &o))
1889  name = json_object_get_string(o);
1890 
1891  string outputString = "First";
1892  if (name && strlen(name))
1893  outputString = name;
1894 
1895  json_object_put(js);
1896 
1897  return outputString;
1898  }
1899  if (getDataType()->getModuleKey()=="VuoMidiOutputDevice")
1900  {
1901  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1902  json_object *o = NULL;
1903 
1904  const char *name = NULL;
1905  if (json_object_object_get_ex(js, "name", &o))
1906  name = json_object_get_string(o);
1907 
1908  string outputString = "First";
1909  if (name && strlen(name))
1910  outputString = name;
1911 
1912  json_object_put(js);
1913 
1914  return outputString;
1915  }
1916  if (getDataType()->getModuleKey()=="VuoSyphonServerDescription")
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, "serverName", &o))
1923  {
1924  const char *n = json_object_get_string(o);
1925  if (strcmp(n, "*"))
1926  name = n;
1927  }
1928  if (!name && json_object_object_get_ex(js, "applicationName", &o))
1929  {
1930  const char *n = json_object_get_string(o);
1931  if (strcmp(n, "*"))
1932  name = n;
1933  }
1934 
1935  string outputString = "First";
1936  if (name && strlen(name))
1937  outputString = name;
1938 
1939  json_object_put(js);
1940 
1941  return outputString;
1942  }
1943  if (getDataType()->getModuleKey()=="VuoVideoInputDevice")
1944  {
1945  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1946  json_object *o = NULL;
1947 
1948  const char *name = NULL;
1949  if (json_object_object_get_ex(js, "name", &o))
1950  name = json_object_get_string(o);
1951  else if (json_object_object_get_ex(js, "id", &o))
1952  name = json_object_get_string(o);
1953 
1954  string outputString = "Default";
1955  if (name && strlen(name))
1956  outputString = name;
1957 
1958  json_object_put(js);
1959 
1960  return outputString;
1961  }
1964  if (getDataType()->getModuleKey()=="VuoHidDevice")
1965  {
1966  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1967  json_object *o = NULL;
1968 
1969  QString outputString;
1970  if (json_object_object_get_ex(js, "name", &o))
1971  {
1972  outputString = json_object_get_string(o);
1973 
1974  // Trim off the parenthetical vendor/class.
1975  outputString = outputString.section(" (", 0, 0);
1976  }
1977  json_object_put(js);
1978 
1979  return outputString.toStdString();
1980  }
1981  if (getDataType()->getModuleKey()=="VuoOscInputDevice"
1982  || getDataType()->getModuleKey()=="VuoOscOutputDevice")
1983  {
1984  json_object *js = json_tokener_parse(getConstantAsString().c_str());
1985  json_object *o = NULL;
1986 
1987  const char *name = NULL;
1988  if (json_object_object_get_ex(js, "name", &o))
1989  name = json_object_get_string(o);
1990 
1991  string outputString = "Auto";
1992  if (name && strlen(name))
1993  outputString = name;
1994 
1995  json_object_put(js);
1996 
1997  return outputString;
1998  }
1999  if (getDataType()->getModuleKey()=="VuoArtNetInputDevice"
2000  || getDataType()->getModuleKey()=="VuoArtNetOutputDevice")
2001  {
2002  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2003  json_object *o = NULL;
2004 
2005  const char *name = NULL;
2006  if (json_object_object_get_ex(js, "name", &o))
2007  name = json_object_get_string(o);
2008  else
2009  {
2010  if (getDataType()->getModuleKey()=="VuoArtNetInputDevice")
2011  name = "Any";
2012  else
2013  name = "Broadcast";
2014  }
2015 
2016  VuoInteger net=0, subNet=0, universe=0;
2017  if (json_object_object_get_ex(js, "net", &o))
2018  net = json_object_get_int64(o);
2019  if (json_object_object_get_ex(js, "subNet", &o))
2020  subNet = json_object_get_int64(o);
2021  if (json_object_object_get_ex(js, "universe", &o))
2022  universe = json_object_get_int64(o);
2023 
2024  string outputString = format("%s (%lld:%lld:%lld)", name, net, subNet, universe);
2025 
2026  json_object_put(js);
2027 
2028  return outputString;
2029  }
2030  if (getDataType()->getModuleKey()=="VuoTempoRange")
2031  {
2032  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2033  const char *tempoRange = json_object_get_string(js);
2034  if (!tempoRange)
2035  return "Unknown";
2036  if (strcmp(tempoRange, "andante") == 0)
2037  return "70–110 BPM";
2038  else if (strcmp(tempoRange, "moderato") == 0)
2039  return "100–140 BPM";
2040  else if (strcmp(tempoRange, "allegro") == 0)
2041  return "120–180 BPM";
2042  else if (strcmp(tempoRange, "presto") == 0)
2043  return "170–250 BPM";
2044  else if (strcmp(tempoRange, "prestissimo") == 0)
2045  return "220–320 BPM";
2046  }
2047  if (getDataType()->getModuleKey()=="VuoEdgeBlend")
2048  {
2049  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2050 
2051  double cutoff = 0, gamma = 0, crop = 0;
2052  json_object *o = NULL;
2053 
2054  if (json_object_object_get_ex(js, "cutoff", &o))
2055  cutoff = json_object_get_double(o);
2056 
2057  if (json_object_object_get_ex(js, "gamma", &o))
2058  gamma = json_object_get_double(o);
2059 
2060  if (json_object_object_get_ex(js, "crop", &o))
2061  crop = json_object_get_double(o);
2062 
2063  json_object_put(js);
2064 
2065  double cropPercent = -crop * 100;
2066  double cutoffPercent = cutoff * 100;
2067  if (VuoReal_areEqual(crop, 0) && VuoReal_areEqual(cutoff, 0))
2068  return "≡";
2069  else if (VuoReal_areEqual(cutoff, 0))
2070  return format("%.0f%%", cropPercent);
2071  else if (VuoReal_areEqual(gamma, 1))
2072  return format("%.0f%% %.0f%%", cropPercent, cutoffPercent);
2073  else
2074  return format("%.0f%% %.0f%% @ %.2gγ", cropPercent, cutoffPercent, gamma);
2075  }
2076  if (getDataType()->getModuleKey()=="VuoRange")
2077  {
2078  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2079 
2080  double minimum = VuoRange_NoMinimum, maximum = VuoRange_NoMaximum;
2081 
2082  json_object *o = NULL;
2083 
2084  if (json_object_object_get_ex(js, "minimum", &o))
2085  minimum = json_object_get_double(o);
2086 
2087  if (json_object_object_get_ex(js, "maximum", &o))
2088  maximum = json_object_get_double(o);
2089 
2090  json_object_put(js);
2091 
2092  if (minimum != VuoRange_NoMinimum && maximum != VuoRange_NoMaximum)
2093  return format("%.4g to %.4g", minimum, maximum);
2094  else if (minimum != VuoRange_NoMinimum)
2095  return format("%.4g to ∞", minimum);
2096  else if (maximum != VuoRange_NoMaximum)
2097  return format("-∞ to %.4g", maximum);
2098  else
2099  return format("-∞ to ∞");
2100  }
2101  if (getDataType()->getModuleKey()=="VuoIntegerRange")
2102  {
2103  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2104 
2106 
2107  json_object *o = NULL;
2108 
2109  if (json_object_object_get_ex(js, "minimum", &o))
2110  minimum = json_object_get_int64(o);
2111 
2112  if (json_object_object_get_ex(js, "maximum", &o))
2113  maximum = json_object_get_int64(o);
2114 
2115  json_object_put(js);
2116 
2117  if (minimum != VuoIntegerRange_NoMinimum && maximum != VuoIntegerRange_NoMaximum)
2118  return format("%lld to %lld", minimum, maximum);
2119  else if (minimum != VuoIntegerRange_NoMinimum)
2120  return format("%lld to ∞", minimum);
2121  else if (maximum != VuoIntegerRange_NoMaximum)
2122  return format("-∞ to %lld", maximum);
2123  else
2124  return format("-∞ to ∞");
2125  }
2126  if (getDataType()->getModuleKey() == "VuoAnchor")
2127  {
2129  return stringAndFree(VuoAnchor_getSummary(value));
2130  }
2131  if (getDataType()->getModuleKey() == "VuoTextComparison")
2132  {
2135  }
2136  if (getDataType()->getModuleKey() == "VuoBlackmagicInputDevice"
2137  || getDataType()->getModuleKey() == "VuoBlackmagicOutputDevice")
2138  {
2139  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2140  json_object *o;
2141  if (json_object_object_get_ex(js, "name", &o))
2142  return json_object_get_string(o);
2143  else
2144  return "First";
2145  }
2146  if (getDataType()->getModuleKey() == "VuoSpeechVoice")
2147  {
2150  }
2151  if (getDataType()->getModuleKey() == "VuoRectangle")
2152  {
2154  return stringAndFree(VuoRectangle_getSummary(value));
2155  }
2156  if (getDataType()->getModuleKey() == "VuoNdiSource")
2157  {
2158  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2159  json_object *o, *o2;
2160  if (json_object_object_get_ex(js, "name", &o))
2161  return json_object_get_string(o);
2162  else if (json_object_object_get_ex(js, "ipAddress", &o)
2163  && json_object_object_get_ex(js, "port", &o2))
2164  return format("%s:%lld", json_object_get_string(o), json_object_get_int(o2));
2165  else if (json_object_object_get_ex(js, "ipAddress", &o))
2166  return json_object_get_string(o);
2167  else
2168  return "First";
2169  }
2170  }
2171 
2172  // If it's a JSON string (e.g., VuoText or an enum identifier), unescape and optionally capitalize it.
2173  json_object *js = json_tokener_parse(getConstantAsString().c_str());
2174  if (json_object_get_type(js) == json_type_string)
2175  {
2176  string textWithoutQuotes = json_object_get_string(js);
2177  json_object_put(js);
2178 
2179  // Show linebreaks as a glyph (rather than causing the following text to move to the next line, which gets cut off).
2180  VuoStringUtilities::replaceAll(textWithoutQuotes, "\n", "⏎");
2181 
2182  string type;
2183  if (getDataType())
2184  type = getDataType()->getModuleKey();
2185 
2186  // Leave text as-is.
2187  if (type == "VuoText"
2188  || type == "VuoImageFormat"
2189  || type == "VuoAudioEncoding"
2190  || type == "VuoBlackmagicConnection"
2191  || type == "VuoBlackmagicVideoMode"
2192  || type == "VuoMovieImageEncoding")
2193  return textWithoutQuotes;
2194 
2195  // All-caps.
2196  if (type == "VuoTableFormat")
2197  {
2198  std::transform(textWithoutQuotes.begin(), textWithoutQuotes.end(), textWithoutQuotes.begin(), ::toupper);
2199  return textWithoutQuotes;
2200  }
2201 
2202  // Convert hyphenations to camelcase.
2203  // Example: VuoTimeFormat
2204  for (auto it = textWithoutQuotes.begin(); it != textWithoutQuotes.end();)
2205  if (*it == '-')
2206  {
2207  textWithoutQuotes.erase(it);
2208  *it = toupper(*it);
2209  }
2210  else
2211  ++it;
2212 
2213  return VuoStringUtilities::expandCamelCase(textWithoutQuotes);
2214  }
2215  json_object_put(js);
2216 
2217  return getConstantAsString();
2218 }
2219 
2223 void VuoRendererPort::setConstant(string constantValue)
2224 {
2225  VuoCompilerInputEventPort *eventPort = dynamic_cast<VuoCompilerInputEventPort *>(getBase()->getCompiler());
2226  if (eventPort)
2227  {
2228  QGraphicsItem::CacheMode normalCacheMode = cacheMode();
2229  setCacheMode(QGraphicsItem::NoCache);
2230  updateGeometry();
2231 
2232  eventPort->getData()->setInitialValue(constantValue);
2233 
2235  setToolTip(QString("<span></span>") + getConstantAsStringToRender().c_str());
2236  else
2237  setToolTip("");
2238 
2239  setCacheMode(normalCacheMode);
2243 
2244  // Ensure this node's cable paths are updated to escape the new constant's flag.
2245  set<VuoCable *> cables = getRenderedParentNode()->getConnectedInputCables(true);
2246  for (set<VuoCable *>::iterator i = cables.begin(); i != cables.end(); ++i)
2247  {
2248  (*i)->getRenderer()->setPortConstantsChanged();
2249  (*i)->getRenderer()->updateGeometry();
2250  }
2251  }
2252 }
2253 
2260 {
2261  bool sidebarPaintMode = dynamic_cast<const VuoRendererPublishedPort *>(this);
2262  string name = getPortNameToRender();
2264 
2265  if (name.empty() || isAnimated)
2266  return false;
2267  else if (parent && parent->isMissingImplementation())
2268  return false;
2269  else if (sidebarPaintMode)
2270  return true;
2271  else if (isRefreshPort || isFunctionPort || typecastParentPort)
2272  return false;
2273 
2274  return true;
2275 }
2276 
2284 string VuoRendererPort::getPointStringForCoords(QList<float> coordList) const
2285 {
2286  const QString coordSeparator = QString(QLocale::system().decimalPoint() != ','? QChar(',') : QChar(';')).append(" ");
2287  QStringList coordStringList;
2288 
2289  foreach (float coord, coordList)
2290  coordStringList.append(getStringForRealValue(coord).c_str());
2291 
2292  QString pointString = QString("(").append(coordStringList.join(coordSeparator).append(")"));
2293  return pointString.toStdString();
2294 }
2295 
2300 string VuoRendererPort::getStringForRealValue(double value) const
2301 {
2302  // See VuoDoubleSpinBox::textFromValue.
2303  QString valueAsStringInUserLocale = QLocale::system().toString(value, 'g', 11);
2304  if (qAbs(value) >= 1000.0)
2305  valueAsStringInUserLocale.remove(QLocale::system().groupSeparator());
2306 
2307  return valueAsStringInUserLocale.toStdString();
2308 }
2309 
2315 {
2316  // 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).
2317  QString valueAsStringInUserLocale = QLocale::system().toString(value, 'g', 7);
2318  if (qAbs(value) >= 1000.0)
2319  valueAsStringInUserLocale.remove(QLocale::system().groupSeparator());
2320 
2321  return valueAsStringInUserLocale.toStdString();
2322 }
2323 
2328 {
2329  // @todo: Allow generic published ports (https://b33p.net/kosada/node/7655).
2330  bool isGeneric = bool(dynamic_cast<VuoGenericType *>(this->getDataType()));
2331 
2332  // @todo: Allow published dictionary ports (https://b33p.net/kosada/node/8524).
2333  bool hasDictionaryType = (this->getDataType() && VuoStringUtilities::beginsWith(this->getDataType()->getModuleKey(), "VuoDictionary_"));
2334 
2335  // @todo: Allow published math expression ports for "Calculate" nodes (https://b33p.net/kosada/node/8550).
2336  bool isMathExpressionInputToCalculateNode = (this->getDataType() &&
2337  (this->getDataType()->getModuleKey() == "VuoMathExpressionList") &&
2338  this->getUnderlyingParentNode() &&
2339  VuoStringUtilities::beginsWith(this->getUnderlyingParentNode()->getBase()->getNodeClass()->getClassName(), "vuo.math.calculate"));
2340 
2341 
2342  // @todo: Allow direct connections between external published inputs and external published outputs
2343  // (https://b33p.net/kosada/node/7756).
2344  return (!isGeneric && !hasDictionaryType && !isMathExpressionInputToCalculateNode && !dynamic_cast<const VuoRendererPublishedPort *>(this));
2345 }
2346 
2351 vector<VuoRendererPublishedPort *> VuoRendererPort::getPublishedPorts(void) const
2352 {
2353  vector <VuoRendererPublishedPort *> publishedPorts;
2354  foreach (VuoCable *cable, getBase()->getConnectedCables(true))
2355  {
2356  if (getInput() && cable->isPublishedInputCable())
2357  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getFromPort()->getRenderer()));
2358  else if (getOutput() && cable->isPublishedOutputCable())
2359  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getToPort()->getRenderer()));
2360  }
2361 
2362  return publishedPorts;
2363 }
2364 
2369 vector<VuoRendererPublishedPort *> VuoRendererPort::getPublishedPortsConnectedByDataCarryingCables(void) const
2370 {
2371  vector <VuoRendererPublishedPort *> publishedPorts;
2372  foreach (VuoCable *cable, getBase()->getConnectedCables(true))
2373  {
2374  if (getInput() && cable->isPublishedInputCable() && cable->getRenderer()->effectivelyCarriesData())
2375  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getFromPort()->getRenderer()));
2376  else if (getOutput() && cable->isPublishedOutputCable() && cable->getRenderer()->effectivelyCarriesData())
2377  publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getToPort()->getRenderer()));
2378  }
2379 
2380  return publishedPorts;
2381 }
2382 
2388 {
2389  this->timeLastEventFired = VuoRendererColors::getVirtualFiredEventOrigin();
2390 }
2391 
2396 {
2397  this->timeLastEventFired = QDateTime::currentMSecsSinceEpoch();
2398 }
2399 
2405 {
2406  this->timeLastEventFired = VuoRendererColors::getVirtualFiredEventOriginForAnimationFadePercentage(percentage);
2407 }
2408 
2412 vector<QGraphicsItemAnimation *> VuoRendererPort::getAnimations()
2413 {
2414  return this->animations;
2415 }
2416 
2421 void VuoRendererPort::setAnimated(bool animated)
2422 {
2423  this->isAnimated = animated;
2425 }
2426 
2430 void VuoRendererPort::setCacheModeForPortAndChildren(QGraphicsItem::CacheMode mode)
2431 {
2432  this->setCacheMode(mode);
2433 
2434  VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>(this);
2435  if (typecastPort)
2436  typecastPort->getChildPort()->setCacheMode(mode);
2437 }
2438 
2444 {
2445  // Port animations, and ports without compilers, shouldn't accept mouse events.
2446  setEnabled(!isAnimated &&
2447  ((getBase()->hasCompiler() && getBase()->getClass()->hasCompiler()) ||
2448  dynamic_cast<VuoRendererPublishedPort *>(this)) &&
2449  !isHiddenRefreshPort());
2450 }
2451 
2459 {
2460  // A published port name must:
2461  // - Contain only alphanumeric characters; and
2462  // - Either be entirely numeric or begin with an alphabetic character; and
2463  // - Have a total length of 1-31 characters.
2464  return QString("[A-Za-z][A-Za-z0-9]{0,30}")
2465  .append("|")
2466  .append("[0-9]{1,31}");
2467 }
2468 
2478 {
2479  // Remove non-alphanumeric characters.
2480  portID.remove(QRegExp("[^A-Za-z0-9]"));
2481 
2482  // Unless the identifier is purely numeric, remove non-alphabetic first characters.
2483  if (!portID.contains(QRegExp("^[0-9]+$")))
2484  {
2485  while (!portID.isEmpty() && !portID.contains(QRegExp("^[A-Za-z]")))
2486  portID = portID.right(portID.size()-1);
2487  }
2488 
2489  // Remove characters beyond the 31st.
2490  portID = portID.left(31);
2491 
2492  return portID;
2493 }
2494 
2495 VuoRendererPort::~VuoRendererPort()
2496 {
2497  foreach (QGraphicsItemAnimation *animation, animations)
2498  animation->clear();
2499 
2500  animations.clear();
2501 }