Vuo 2.4.4
Loading...
Searching...
No Matches
VuoRendererPort.cc
Go to the documentation of this file.
1
10#include "VuoStringUtilities.hh"
11
13#include "VuoRendererFonts.hh"
14#include "VuoRendererPort.hh"
17
21
22#include "VuoGenericType.hh"
23#include "VuoNodeClass.hh"
24
25#include "VuoAnchor.h"
26#include "VuoAudioInputDevice.h"
28#include "VuoImage.h"
29#include "VuoIntegerRange.h"
30#include "VuoRange.h"
31#include "VuoScreen.h"
32#include "VuoTransform.h"
33#include "VuoUrl.h"
34#include "VuoSpeechVoice.h"
35
36extern "C" {
37char *VuoHid_getUsageText(uint32_t usagePage, uint32_t usage);
38}
39
40#pragma clang diagnostic push
41#pragma clang diagnostic ignored "-Wdocumentation"
42 #include <json-c/json.h>
43#pragma clang diagnostic pop
44
45
46#include "VuoHeap.h"
47#include "type.h"
48
49const qreal VuoRendererPort::portRadius = 8; // VuoRendererFonts::thickPenWidth*8./20.
50const qreal VuoRendererPort::portSpacing = 15; // VuoRendererFonts::thickPenWidth*3.0/4.0
51const qreal VuoRendererPort::portContainerMargin = 3.333333; // VuoRendererFonts::thickPenWidth / 6.
52const qreal VuoRendererPort::portInset = 1.4;
53const qreal VuoRendererPort::portBarrierWidth = 5.5; // VuoRendererFonts::thickPenWidth*5.5/20.
54const qreal VuoRendererPort::portConstantTextPadding = 6.5; // VuoRendererFonts::thickPenWidth*6.5/20.
55
61#define RETURN_SUMMARY(type) \
62 if (getDataType()->getModuleKey() == #type) \
63 { \
64 type value = VuoMakeRetainedFromString(getConstantAsString().c_str(), type); \
65 string s = stringAndFree(type ## _getSummary(value)); \
66 type ## _release(value); \
67 return s; \
68 }
69
75#define RETURN_SHORT_SUMMARY(type) \
76 if (getDataType()->getModuleKey() == #type) \
77 { \
78 type value = VuoMakeRetainedFromString(getConstantAsString().c_str(), type); \
79 string s = stringAndFree(type ## _getShortSummary(value)); \
80 type ## _release(value); \
81 return s; \
82 }
83
88 bool isOutput, bool isRefreshPort, bool isFunctionPort)
89 : VuoBaseDetail<VuoPort>("VuoRendererPort standard", basePort)
90{
91 getBase()->setRenderer(this);
92
93 setZValue(portZValue);
94
95 this->signaler = signaler;
96
97 this->isOutput = isOutput;
98 this->isRefreshPort = isRefreshPort;
99 this->isFunctionPort = isFunctionPort;
100 this->_eligibilityHighlight = VuoRendererColors::noHighlight;
101 this->isEligibleForSelection = false;
102 setAnimated(false);
103 this->typecastParentPort = NULL;
105
107
108 const int maxAnimationsPerPort = 4;
109
110 if (getInput() || (getBase()->getClass()->getPortType() == VuoPortClass::triggerPort))
111 {
112 for (int i = 0; i < maxAnimationsPerPort; ++i)
113 {
114 QGraphicsItemAnimation *animation = new QGraphicsItemAnimation();
115 animation->setTimeLine(new QTimeLine(VuoRendererColors::activityAnimationFadeDuration));
116 animations.push_back(animation);
117 }
118 }
119
120 if (! isHiddenRefreshPort())
121 {
122 setFlag(QGraphicsItem::ItemIsFocusable, true); // allow delivery of key events
123 setAcceptHoverEvents(true); // allow delivery of mouse-hover events
124 }
125
130 updateToolTip();
131}
132
137void VuoRendererPort::addRoundedTriangle(QPainterPath &p, QPointF center, qreal radius, qreal cornerRadius)
138{
139 p.moveTo(center + QPointF(radius,0));
140 for (int theta = 0; theta <= 360; theta += 120)
141 {
142 QRectF rect(center.x() + (radius - cornerRadius)*cos(theta*M_PI/180.) - cornerRadius,
143 center.y() - (radius - cornerRadius)*sin(theta*M_PI/180.) - cornerRadius,
144 cornerRadius*2,
145 cornerRadius*2);
146 bool first = theta==0;
147 bool last = theta==360;
148
149 p.arcTo(rect,
150 theta - (first ? 0 : 60),
151 (first||last ? 60 : 120));
152 }
153}
154
159void VuoRendererPort::updateToolTip()
160{
162 {
163 QString constantString = QString::fromStdString(getConstantAsStringToRender());
164
165 if (!constantString.startsWith("<style>")) // Don't trim the specially-formatted VuoColor tooltips.
166 {
167 // Trim it down to about 1/2 screenful of text
168 // on the smallest display dimensions sold recently by Apple (1280x768).
169 int maxColumns = 100;
170 int lineHeight = 19;
171 int screenHeight = 768;
172 int menuBarHeight = 22;
173 int dockHeight = 50;
174 int maxRows = (screenHeight - menuBarHeight - dockHeight) / lineHeight;
175 int maxLength = maxColumns * maxRows;
176 if (constantString.length() > maxLength)
177 {
178 constantString.truncate(maxLength);
179 constantString += "…";
180 }
181
182 // Periodically insert spaces to ensure the text can word-wrap.
183 for (int i = maxColumns; i < constantString.length(); i += maxColumns)
184 constantString.insert(i, "\u200b"); // Unicode zero-width space
185 }
186
187 // <span></span> forces Qt to render the tooltip as rich text, enabling word-wrapping.
188 setToolTip("<span></span>" + constantString);
189 }
190 else
191 setToolTip("");
192}
193
198{
199 return cachedPortPath;
200}
201
206{
208 {
209 cachedPortPath = QPainterPath();
210 return;
211 }
212
214 getBase()->getClass()->getPortType(),
215 isConstant() ? QString::fromUtf8(getConstantAsTruncatedStringToRender().c_str()) : "",
216 getInput(),
218 );
219}
220
225QPainterPath VuoRendererPort::getPortPath(qreal inset,
226 VuoPortClass::PortType portType,
227 QString constantText,
228 bool isInputPort,
229 bool carriesData
230 )
231{
232 QPainterPath p;
233 QRectF outerPortRect = getPortRect();
234 QRectF innerPortRect = outerPortRect.adjusted(inset,inset,-inset,-inset);
235
236 QRectF textRect = getPortConstantTextRectForText(constantText);
237
238 qreal left = textRect.x() - portConstantTextPadding + inset - VuoRendererPort::portInset;
239 QPointF topLeftCorner(left + .5, innerPortRect.top() - .24);
240 QPointF topRightCorner(innerPortRect.right() + .5, innerPortRect.top() - .24);
241 QPointF bottomRightCorner(innerPortRect.right() + .5, innerPortRect.bottom() - .43);
242 QPointF bottomLeftCorner(left + .5, innerPortRect.bottom() - .43);
243
244 p.moveTo(innerPortRect.right() + .5, innerPortRect.center().y());
245 qreal adjustedPortRadius = portRadius - 2;
246 addRoundedCorner(p, true, bottomRightCorner, adjustedPortRadius, false, false);
247 addRoundedCorner(p, true, bottomLeftCorner, adjustedPortRadius, false, true);
248 addRoundedCorner(p, true, topLeftCorner, adjustedPortRadius, true, true);
249 addRoundedCorner(p, true, topRightCorner, adjustedPortRadius, true, false);
250
251 return p;
252}
253
258{
259 return QRectF(
260 -portRadius,
261 -portRadius,
262 portRadius*2.0,
263 portRadius*2.0
264 );
265}
266
271{
272 QRectF barrierRect = QRectF();
273
274 bool sidebarPaintMode = dynamic_cast<const VuoRendererPublishedPort *>(this);
277
278 if (!isAnimated &&
279 ((!isOutput && !sidebarPaintMode && eventBlocking != VuoPortClass::EventBlocking_None)
280 || (isOutput && type == VuoPortClass::triggerPort))
281 )
282 {
283 QRectF portRect = getPortRect();
284 if (isOutput)
285 barrierRect = QRectF(portRect.topLeft() + QPointF( 2 - VuoRendererPort::portBarrierWidth, 2), portRect.bottomLeft() + QPointF( 2, -3));
286 else
287 barrierRect = QRectF(portRect.topRight() + QPointF(-1 + VuoRendererPort::portBarrierWidth, 2), portRect.bottomRight() + QPointF(-1, -3));
288 }
289
290 return barrierRect;
291}
292
297{
298 bool paintDataAntenna = hasConnectedWirelessDataCable(true);
299 bool paintEventAntenna = hasConnectedWirelessEventCable(true);
300 if (!paintDataAntenna && !paintEventAntenna)
301 return QPainterPath();
302
303 // Mast
304 qreal cableWidth;
305 VuoRendererCable::getCableSpecs(paintDataAntenna, cableWidth);
306
307 const qreal constantWidth = fmax(0, getPortConstantTextRect().width() - 3);
308 const qreal mastLength = portRadius * 2.;
309 const qreal pixelOffset = -.3;
310 QPointF startPoint = (getInput()? -QPointF(mastLength - (paintDataAntenna ? 0.5 : 0 ) + constantWidth, -pixelOffset) : QPointF(0, pixelOffset));
311 QPointF endPoint = (getInput()? QPointF(-constantWidth, pixelOffset) : QPointF(mastLength + (paintDataAntenna ? 0.5 : 0 ), pixelOffset));
312
313 VuoCable cableBase(NULL, NULL, NULL, NULL);
314 VuoRendererCable cableRenderer(&cableBase);
315 QPainterPath mastPath = cableRenderer.getCablePathForEndpoints(startPoint, endPoint);
316
317 QPainterPathStroker mastStroker;
318 mastStroker.setWidth(cableWidth);
319 mastStroker.setCapStyle(Qt::RoundCap);
320 QPainterPath antennaOutline = mastStroker.createStroke(mastPath);
321
322 // Crossbars
323 qreal outerCrossbarXOffset = (paintDataAntenna? 1 : 0.5);
324 const QPointF outerCrossbarPos = (getInput()? startPoint - QPointF(outerCrossbarXOffset, 0) :
325 endPoint + QPointF(outerCrossbarXOffset, 0));
326 const int crossbarSpacing = 5;
327 const int crossbarHeight = VuoRendererFonts::midPenWidth*5;
328
329 QPainterPath crossbars;
330 for (int i = 0; i < 2; ++i)
331 {
332 crossbars.moveTo((outerCrossbarPos +
333 QPointF(QPointF(0, -0.5*crossbarHeight) +
334 QPointF((getInput()? 1 : -1)*crossbarSpacing*i, 0))));
335 crossbars.lineTo((outerCrossbarPos +
336 QPointF(QPointF(0, 0.5*crossbarHeight) +
337 QPointF((getInput()? 1 : -1)*crossbarSpacing*i, 0))));
338 }
339
340 // Union the mast and crossbars.
341 QPainterPathStroker crossbarStroker;
342 crossbarStroker.setWidth(cableWidth*3/5);
343 crossbarStroker.setCapStyle(Qt::RoundCap);
344 antennaOutline += crossbarStroker.createStroke(crossbars);
345
346 return antennaOutline;
347}
348
352bool VuoRendererPort::hasConnectedWirelessDataCable(bool includePublishedCables) const
353{
354 vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
355 for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
356 if ((*cable)->hasRenderer() && (*cable)->getRenderer()->effectivelyCarriesData() &&
357 (*cable)->getRenderer()->getEffectivelyWireless() &&
358 (*cable)->getRenderer()->paintingDisabled())
359 return true;
360 return false;
361}
362
366bool VuoRendererPort::hasConnectedWirelessEventCable(bool includePublishedCables) const
367{
368 vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
369 for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
370 if ((*cable)->hasRenderer() && !(*cable)->getRenderer()->effectivelyCarriesData() &&
371 (*cable)->getRenderer()->getEffectivelyWireless() &&
372 (*cable)->getRenderer()->paintingDisabled())
373 return true;
374 return false;
375}
376
381{
382 VuoRendererNode *renderedParentNode = getRenderedParentNode();
383 if (renderedParentNode)
384 return renderedParentNode->getBase()->getTintColor();
385 else
386 {
387 // Tint protocol ports the same color as the protocol.
388 if (dynamic_cast<const VuoRendererPublishedPort *>(this) &&
389 (static_cast<VuoPublishedPort *>(this->getBase()))->isProtocolPort())
390 {
391 // @todo: Account for multiple simultaneous active protocols. https://b33p.net/kosada/node/9585
392 return VuoRendererColors::getActiveProtocolTint(0, !isOutput);
393 }
394 }
395
396 return VuoNode::TintNone;
397}
398
406{
407 if (!getInput())
408 return getPortTint();
409
410 set<VuoNode::TintColor> connectedPortTints;
411 foreach (VuoRendererPort *port, getPortsConnectedWirelessly(true))
412 {
413 connectedPortTints.insert(port->getPortTint());
414 if (connectedPortTints.size() > 1)
415 return VuoNode::TintNone;
416 }
417
418 if (connectedPortTints.size() == 1)
419 return *connectedPortTints.begin();
420 else
421 return VuoNode::TintNone;
422}
423
427set<VuoRendererPort *> VuoRendererPort::getPortsConnectedWirelessly(bool includePublishedCables) const
428{
429 set<VuoRendererPort *> connectedPorts;
430 foreach (VuoCable *cable, getBase()->getConnectedCables(includePublishedCables))
431 {
432 if (cable->hasRenderer() &&
434 cable->getRenderer()->paintingDisabled())
435 {
436 VuoPort *connectedPort = (getInput()? cable->getFromPort() : cable->getToPort());
437 if (connectedPort && connectedPort->hasRenderer())
438 connectedPorts.insert(connectedPort->getRenderer());
439 }
440 }
441
442 return connectedPorts;
443}
444
453
459{
461 if (underlyingAttachment &&
462 underlyingAttachment->getRenderedHostPort() &&
463 underlyingAttachment->getRenderedHostPort()->hasRenderer() &&
464 (underlyingAttachment->getRenderedHostPort()->getRenderer() == targetHostPort) &&
465 (dynamic_cast<VuoRendererInputDrawer *>(underlyingAttachment)))
466 return dynamic_cast<VuoRendererInputDrawer *>(underlyingAttachment);
467
468 else if (underlyingAttachment)
469 {
470 // The drawer might not be directly connected in the underlying composition. Find it anyway.
471 foreach (VuoPort *port, underlyingAttachment->getBase()->getInputPorts())
472 {
473 VuoRendererInputDrawer *upstreamDrawer = port->getRenderer()->getAttachedInputDrawerRenderedWithHostPort(targetHostPort);
474 if (upstreamDrawer)
475 return upstreamDrawer;
476 }
477 }
478
479 return NULL;
480}
481
487{
488 if (! getInput())
489 return NULL;
490
491 vector<VuoCable *> inCables = getBase()->getConnectedCables(false);
492 foreach (VuoCable *cable, inCables)
493 {
494 VuoNode *fromNode = cable->getFromNode();
495 if (fromNode && fromNode->hasRenderer() &&
496 dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer()) &&
497 dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer())->getUnderlyingHostPort()->getRenderer() == this)
498 return dynamic_cast<VuoRendererInputAttachment *>(fromNode->getRenderer());
499 }
500
501 return NULL;
502}
503
509set<VuoRendererInputAttachment *> VuoRendererPort::getAllUnderlyingUpstreamInputAttachments(void) const
510{
511 set<VuoRendererInputAttachment *> allUpstreamAttachments;
512 VuoRendererInputAttachment *directUpstreamAttachment = getUnderlyingInputAttachment();
513 if (!directUpstreamAttachment)
514 return allUpstreamAttachments;
515
516 allUpstreamAttachments.insert(directUpstreamAttachment);
517
518 vector<VuoPort *> inputPorts = directUpstreamAttachment->getBase()->getInputPorts();
519 foreach (VuoPort *port, inputPorts)
520 {
521 set<VuoRendererInputAttachment *> indirectUpstreamAttachments = port->getRenderer()->getAllUnderlyingUpstreamInputAttachments();
522 allUpstreamAttachments.insert(indirectUpstreamAttachments.begin(), indirectUpstreamAttachments.end());
523 }
524
525 return allUpstreamAttachments;
526}
527
533{
534 return (isConstant()?
536 QRectF());
537}
538
545{
546 static QHash<QString, int> cachedTextWidths;
547 QHash<QString, int>::iterator i = cachedTextWidths.find(text);
548 if (i != cachedTextWidths.end())
549 return i.value();
550
551 int textWidth = QFontMetricsF(VuoRendererFonts::getSharedFonts()->nodePortConstantFont())
552 .boundingRect(QRectF(0,0,0,0), Qt::TextIncludeTrailingSpaces, text)
553 .width();
554 cachedTextWidths.insert(text, textWidth);
555 return textWidth;
556}
557
562{
563 int textWidth = getTextWidth(text) + 1;
564
565 QRectF textRect(
566 -textWidth - portConstantTextPadding + 8,
568 textWidth,
570 );
571
572 return textRect.toAlignedRect();
573}
574
575
580{
581 return this->nameRect;
582}
583
588{
589 QString text = QString::fromUtf8(getPortNameToRender().c_str());
590 QFont font = getPortNameFont();
591 QSizeF textSize = QFontMetricsF(font).size(0,text);
592
593 bool isPortOnDrawer = dynamic_cast<VuoRendererInputAttachment *>(getUnderlyingParentNode());
594
595 this->nameRect = QRectF(
596 (isOutput? -VuoRendererFonts::thickPenWidth/2.0 - textSize.width() - VuoRendererPort::portRadius :
599 + (isPortOnDrawer ? 2 : VuoRendererPort::portRadius) + 2.
600 ),
601 -VuoRendererFonts::thickPenWidth/3.0 - (isPortOnDrawer ? 0 : 1),
602 textSize.width(),
603 textSize.height()
604 );
605}
606
616{
617 VuoRendererNode *node;
619 node = (((VuoRendererTypecastPort *)(getTypecastParentPort()))->getUncollapsedTypecastNode());
620 else
621 node = getRenderedParentNode();
622
623 return node;
624}
625
635{
636 if (!parentItem())
637 return NULL;
638
639 if (!parentItem()->parentItem())
640 return (VuoRendererNode *)(parentItem());
641
642 return (VuoRendererNode *)(parentItem()->parentItem());
643}
644
649{
650 return typecastParentPort;
651}
652
657{
658 this->typecastParentPort = typecastParentPort;
659}
660
665{
666 return cachedBoundingRect;
667}
668
673{
674 VuoRendererNode *renderedParentNode = getRenderedParentNode();
675 if ((renderedParentNode && renderedParentNode->paintingDisabled()) || isHiddenRefreshPort())
676 {
677 cachedBoundingRect = QRectF();
678 return;
679 }
680
681 QRectF r = getPortPath().boundingRect();
682
683 r = r.united(getEventBarrierRect());
684
686 r = r.united(getNameRect());
687
688 if (hasPortAction())
689 r = r.united(getActionIndicatorRect());
690
691 r = r.united(getWirelessAntennaPath().boundingRect());
692
693 // Antialiasing bleed
694 r.adjust(-1,-1,1,1);
695
696 cachedBoundingRect = r.toAlignedRect();
697}
698
703QPainterPath VuoRendererPort::shape() const
704{
705 QPainterPath p;
706 p.addRect(boundingRect());
707 return p;
708}
709
714{
715 bool sidebarPaintMode = dynamic_cast<VuoRendererPublishedPort *>(this);
716
719
720 if (
721 !isAnimated &&
722 ((!isOutput && !sidebarPaintMode && eventBlocking != VuoPortClass::EventBlocking_None)
723 || (isOutput && type == VuoPortClass::triggerPort))
724 )
725 {
726 QRectF barrierRect = getEventBarrierRect();
727 QColor eventBlockingBarrierColor = (isAnimated? colors->animatedeventBlockingBarrier() : colors->eventBlockingBarrier());
728 painter->setPen(QPen(eventBlockingBarrierColor, VuoRendererPort::portBarrierWidth, Qt::SolidLine, Qt::RoundCap));
729
730 if (eventBlocking == VuoPortClass::EventBlocking_Wall || type == VuoPortClass::triggerPort)
731 painter->drawLine(barrierRect.center() + QPointF(0, -barrierRect.height()/2. + VuoRendererPort::portBarrierWidth/2. - .83),
732 barrierRect.center() + QPointF(0, barrierRect.height()/2. - VuoRendererPort::portBarrierWidth/2. +1.16));
733 else // VuoPortClass::EventBlocking_Door
734 {
735 painter->drawPoint(barrierRect.center() + QPointF(0, -barrierRect.height()/2. + 1.75 + .17));
736 painter->drawPoint(barrierRect.center() + QPointF(0, barrierRect.height()/2. - 1.75 + .17));
737 }
738 }
739}
740
744QFont VuoRendererPort::getPortNameFont(void) const
745{
747 // Use a smaller font for port labels on drawers.
749 else
751}
752
756void VuoRendererPort::paintPortName(QPainter *painter, VuoRendererColors *colors)
757{
759 return;
760
761 VuoRendererPublishedPort *rpp = dynamic_cast<VuoRendererPublishedPort *>(this);
762
763 string name = getPortNameToRender();
764
765 if (rpp)
766 painter->setPen((rpp->isSelected() && rpp->getCurrentlyActive())
767 ? Qt::white
768 : (static_cast<VuoPublishedPort *>(rpp->getBase())->isProtocolPort() ? colors->publishedProtocolPortTitle() : colors->publishedPortTitle()));
769 else
770 painter->setPen(colors->portTitle());
771
772 painter->setFont(getPortNameFont());
773 painter->drawText(getNameRect(), isOutput? Qt::AlignRight : Qt::AlignLeft, QString::fromStdString(name));
774}
775
781{
782 bool displayPortName = (getRenderedParentNode()? getRenderedParentNode()->nameDisplayEnabledForPort(this) : true);
783
784 return (!displayPortName? "": getPortNameToRenderWhenDisplayed());
785}
786
791{
792 const VuoRendererPublishedPort *publishedPort = dynamic_cast<const VuoRendererPublishedPort *>(this);
793 return (publishedPort? getBase()->getClass()->getName() :
795 getBase()->getClass()->getName()));
796}
797
803{
804 this->customizedPortName = name;
806}
807
813{
814 VuoPortClass *pc = getBase()->getClass();
815 if (pc->hasCompiler())
816 return static_cast<VuoCompilerPortClass *>(pc->getCompiler())->getDisplayName();
817 else
818 return "";
819}
820
825{
826 return getBase()->getClass()->hasPortAction();
827}
828
833{
834 QFontMetricsF fontMetrics = QFontMetricsF(getPortNameFont());
835 const qreal marginFromPortName = 4;
836 const qreal triangleSize = 6;
837 qreal triangleLeft = qRound( getNameRect().right() + marginFromPortName );
838 qreal triangleTop = qRound( getNameRect().bottom() - fontMetrics.descent() - fontMetrics.xHeight() );
839
840 return QRectF(triangleLeft, triangleTop, triangleSize, triangleSize);
841}
842
847{
848 if (hasPortAction())
849 {
850 QRectF rect = getActionIndicatorRect();
851
852 QPainterPath p;
853 addRoundedTriangle(p, rect.center() + QPointF(-1,-.75), qRound(rect.width()/2.) + .5, VuoRendererNode::cornerRadius/9.);
854
855 QColor color = colors->actionIndicator();
856 painter->fillPath(p, color);
857 }
858}
859
864{
865 painter->fillPath(getWirelessAntennaPath(), QBrush(colors->cableMain()));
866}
867
871void VuoRendererPort::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
872{
874 return;
875
876 VuoRendererNode *renderedParentNode = getRenderedParentNode();
877 if (renderedParentNode && renderedParentNode->paintingDisabled())
878 return;
879
880 VuoRendererPublishedPort *publishedPort = dynamic_cast<VuoRendererPublishedPort *>(this);
881
882 // Workaround to prevent items that have been removed from the scene from being painted on the scene anyway.
883 // https://b33p.net/kosada/node/7938
884 if (!(scene() || publishedPort))
885 return;
886
887 painter->setRenderHint(QPainter::Antialiasing, true);
888 drawBoundingRect(painter);
889
890 bool isColorInverted = isRefreshPort || isFunctionPort;
891
892 VuoRendererColors::SelectionType selectionType = ((renderedParentNode && renderedParentNode->isEffectivelySelected())? VuoRendererColors::directSelection :
893 VuoRendererColors::noSelection);
894
895 bool isHovered = isEligibleForSelection;
896 qint64 timeOfLastActivity = getTimeOfLastActivity();
897
898 // If an attached drawer does have eligible ports, ensure this host port isn't faded out, so the port name is legible.
899 VuoRendererColors::HighlightType effectiveHighlight = _eligibilityHighlight;
900 bool effectivelyHovered = isHovered;
902 if (drawer)
903 {
905 if (drawerHighlight < _eligibilityHighlight)
906 {
907 effectiveHighlight = drawerHighlight;
908
909 if (_eligibilityHighlight == VuoRendererColors::ineligibleHighlight)
910 effectivelyHovered = false;
911 }
912 }
913
915 selectionType,
916 effectivelyHovered,
917 effectiveHighlight,
918 timeOfLastActivity);
920 selectionType,
921 isHovered,
923 timeOfLastActivity);
924
925 // Draw the port circle / constant flag
926 QPainterPath portPath = getPortPath();
927
928 QBrush portBrush;
929
930 if (isColorInverted)
931 portBrush = colors->portTitlebarFill();
932 else if (isAnimated)
933 portBrush = colors->animatedPortFill();
934 else if (publishedPort && static_cast<VuoPublishedPort *>(publishedPort->getBase())->isProtocolPort())
935 portBrush = colors->portTitlebarFill();
936 else if (publishedPort)
937 portBrush = colors->publishedPortFill();
938 else
939 portBrush = colors->portFill();
940
941
942 if (!isConstant())
943 {
944 bool showRightHalfOnly = false;
946 if (drawer)
947 {
948 VuoRendererPort *drawerChildPort = (drawer && (drawer->getInputPorts().size() >= VuoNodeClass::unreservedInputPortStartIndex+1)?
950 NULL);
951
952 // Prevent neighboring semi-transparent highlights from overlapping in a misleading way
953 // Essentially: Paint the whole circle if the circle is meant to be more opaque than the drawer handle it intersects.
954 qint64 timeNow = QDateTime::currentMSecsSinceEpoch();
955 const double fadeThreshold = 0.3; // Tuned visually.
956 qint64 childTimeOfLastActivity = drawerChildPort? drawerChildPort->getTimeOfLastActivity() : VuoRendererItem::notTrackingActivity;
957 bool showingActiveEvent = (timeOfLastActivity != VuoRendererItem::notTrackingActivity) &&
958 (((timeNow - timeOfLastActivity) < VuoRendererColors::activityAnimationFadeDuration*fadeThreshold) ||
959 (timeOfLastActivity == VuoRendererItem::activityInProgress));
960 bool childShowingActiveEvent = (childTimeOfLastActivity != VuoRendererItem::notTrackingActivity) &&
961 (((timeNow - childTimeOfLastActivity) < VuoRendererColors::activityAnimationFadeDuration*fadeThreshold) ||
962 (childTimeOfLastActivity == VuoRendererItem::activityInProgress));
963 showRightHalfOnly = !effectivelyHovered && drawerChildPort &&
964 (drawerChildPort->eligibilityHighlight() <= effectiveHighlight) &&
965 !(showingActiveEvent && !childShowingActiveEvent);
966 }
967
968 if (showRightHalfOnly)
969 painter->setClipRect(QRectF(-0.39, -portRadius, portRadius, portRadius*2.));
970
971 painter->fillPath(portPath, portBrush);
972
973 if (showRightHalfOnly)
974 painter->setClipping(false);
975 }
976 else
977 {
978 // Display a color swatch for VuoColor data.
980 bool isColorPort = getDataType() && getDataType()->getModuleKey()=="VuoColor";
981 if (isColorPort)
982 {
984 QColor swatchColor = QColor(c.r*255, c.g*255, c.b*255, c.a*255 * portBrush.color().alphaF());
985 VuoReal h,s,l,a;
986 VuoColor_getHSLA(c, &h, &s, &l, &a);
987
988 bool isDark = colors->isDark();
989
990 // Two possible swatches:
991
992 // 1. Semitransparent color, or solid color that matches the canvas background: Draw a background+border, then draw the swatch.
993 if ((a < 1) || (isDark ? l < .25 : l > .75))
994 {
995 // Fill the entire background with a color distinct from the canvas.
996 QColor topLeftColor = colors->nodeFrame();
997 painter->fillPath(portPath, topLeftColor);
998
999 // Fill the bottom right of the background with the opposite color.
1000 // Use a slightly smaller circle, so the topLeftColor acts as a border.
1001 QColor bottomRightColor = isDark ? Qt::black : Qt::white;
1002 QTransform transform;
1003 transform.scale(0.87, 0.87);
1004 QPainterPath smallerCircle = portPath * transform;
1005 {
1006 QRectF r = portPath.boundingRect();
1007 QPainterPath bottomRight;
1008 bottomRight.moveTo(r.bottomLeft());
1009 bottomRight.lineTo(r.topRight());
1010 bottomRight.lineTo(r.bottomRight());
1011 painter->setClipPath(smallerCircle);
1012 painter->fillPath(bottomRight, bottomRightColor);
1013 painter->setClipping(false);
1014 }
1015
1016 // Draw the swatch.
1017 painter->fillPath(smallerCircle, swatchColor);
1018 }
1019
1020 // 2. Solid color that's distinct from the canvas background: Just draw the swatch.
1021 else
1022 painter->fillPath(portPath, swatchColor);
1023 }
1024 else
1025 {
1026 painter->fillPath(portPath, portBrush);
1027
1028 QString constantText = QString::fromUtf8(getConstantAsTruncatedStringToRender().c_str());
1029 QBrush constantFlagBackgroundBrush = colors->constantFill();
1030
1031 // Constant string
1032 QRectF textRect = getPortConstantTextRectForText(constantText);
1033 painter->setPen(colors->constantText());
1034 painter->setFont(VuoRendererFonts::getSharedFonts()->nodePortConstantFont());
1035 painter->drawText(textRect, Qt::AlignLeft, constantText);
1036 }
1037 }
1038
1039 if (! carriesData())
1040 {
1041 QRectF r = getPortRect();
1042 QPainterPath p;
1043 addRoundedTriangle(p, r.center() + QPointF(.5, -.3), (r.width() - VuoRendererPort::portInset*2)/2.-2, VuoRendererNode::cornerRadius/6.);
1044 painter->fillPath(p, colors->portIcon());
1045 }
1046
1047 paintEventBarrier(painter, colors);
1048 paintPortName(painter, colors);
1049 paintActionIndicator(painter, colors);
1050 paintWirelessAntenna(painter, antennaColors);
1051
1052 delete colors;
1053 delete antennaColors;
1054}
1055
1060{
1062 bool isTriggerPort = (type == VuoPortClass::triggerPort);
1063
1064 VuoRendererComposition *composition = dynamic_cast<VuoRendererComposition *>(scene());
1065 bool renderNodeActivity = composition && composition->getRenderNodeActivity();
1066 bool renderPortActivity = composition && composition->getRenderPortActivity();
1067 VuoRendererNode *renderedParentNode = getRenderedParentNode();
1068
1069 return ((! renderNodeActivity)? VuoRendererItem::notTrackingActivity :
1070 ((isTriggerPort && renderPortActivity)? timeLastEventFired :
1071 (getTypecastParentPort()? static_cast<VuoRendererTypecastPort *>(getTypecastParentPort())->getUncollapsedTypecastNode()->getTimeLastExecutionEnded() :
1072 (renderedParentNode? renderedParentNode->getTimeLastExecutionEnded() :
1073 VuoRendererItem::notTrackingActivity))));
1074}
1075
1081{
1082 return isEligibleForSelection;
1083}
1084
1090{
1091 return _eligibilityHighlight == VuoRendererColors::standardHighlight
1092 || _eligibilityHighlight == VuoRendererColors::subtleHighlight;
1093}
1094
1099{
1100 _eligibilityHighlight = eligibility;
1101}
1102
1107{
1108 return _eligibilityHighlight;
1109}
1110
1114void VuoRendererPort::extendedHoverEnterEvent(bool cableDragUnderway, bool disableConnectedCableHighlight)
1115{
1116 extendedHoverMoveEvent(cableDragUnderway, disableConnectedCableHighlight);
1117}
1118
1124void VuoRendererPort::extendedHoverMoveEvent(bool cableDragUnderway, bool disableConnectedCableHighlight)
1125{
1126 QGraphicsItem::CacheMode normalCacheMode = cacheMode();
1127 setCacheMode(QGraphicsItem::NoCache);
1128
1129 prepareGeometryChange();
1130 isEligibleForSelection = (cableDragUnderway? isEligibleForConnection() : true);
1131
1132 setCacheMode(normalCacheMode);
1133
1134 setFocus();
1135
1136 if (!cableDragUnderway && !disableConnectedCableHighlight)
1137 {
1138 vector<VuoCable *> connectedCables = getBase()->getConnectedCables(false);
1139 if (supportsDisconnectionByDragging() && (! connectedCables.empty()))
1140 {
1141 VuoRendererCable *cableToDisconnect = connectedCables.back()->getRenderer();
1142 cableToDisconnect->updateGeometry();
1143 cableToDisconnect->setHovered(true);
1144 }
1145 }
1146}
1147
1152{
1153 QGraphicsItem::CacheMode normalCacheMode = cacheMode();
1154 setCacheMode(QGraphicsItem::NoCache);
1155
1156 prepareGeometryChange();
1157 isEligibleForSelection = false;
1158
1159 setCacheMode(normalCacheMode);
1160
1161 clearFocus();
1162
1163 vector<VuoCable *> connectedCables = getBase()->getConnectedCables(false);
1164 if (supportsDisconnectionByDragging() && (! connectedCables.empty()))
1165 {
1166 VuoRendererCable *cableToDisconnect = connectedCables.back()->getRenderer();
1167 cableToDisconnect->updateGeometry();
1168 cableToDisconnect->setHovered(false);
1169 }
1170}
1171
1185{
1186 bool fromPortIsEnabledOutput = (this->getOutput() && this->isEnabled());
1187 bool toPortIsEnabledInput = (toPort && toPort->getInput() && toPort->isEnabled());
1188
1189 if (!(fromPortIsEnabledOutput && toPortIsEnabledInput))
1190 return false;
1191
1192 // OK: Any connection made using an event-only cable.
1193 if (eventOnlyConnection)
1194 return true;
1195
1196 VuoType *fromDataType = this->getDataType();
1197 VuoType *toDataType = toPort->getDataType();
1198
1199 // OK: Event-only to event+data.
1200 // OK: Event-only to event-only.
1201 // OK: Event+data to event-only.
1202 if (!fromDataType || !toDataType)
1203 return true;
1204
1205 // OK: Event+data to event+data, if types are non-generic and identical.
1206 if (! dynamic_cast<VuoGenericType *>(fromDataType) && (fromDataType == toDataType))
1207 return true;
1208
1209 // OK: Event+data to event+data, if types are generic and compatible.
1210 if (dynamic_cast<VuoGenericType *>(fromDataType) && dynamic_cast<VuoGenericType *>(toDataType) &&
1211 dynamic_cast<VuoGenericType *>(fromDataType)->isGenericTypeCompatible(dynamic_cast<VuoGenericType *>(toDataType)))
1212 return true;
1213
1214 return false;
1215}
1216
1233{
1234 VuoRendererPort *portToSpecialize = NULL;
1235 string specializedTypeName = "";
1236
1237 return (this->canConnectDirectlyWithSpecializationTo(toPort, eventOnlyConnection, &portToSpecialize, specializedTypeName));
1238}
1239
1253bool VuoRendererPort::canConnectDirectlyWithSpecializationTo(VuoRendererPort *toPort, bool eventOnlyConnection, VuoRendererPort **portToSpecialize, string &specializedTypeName)
1254{
1255 *portToSpecialize = NULL;
1256 specializedTypeName = "";
1257
1258 if (this->canConnectDirectlyWithoutSpecializationTo(toPort, eventOnlyConnection))
1259 return true;
1260
1261 bool fromPortIsEnabledOutput = (this->getOutput() && this->isEnabled());
1262 bool toPortIsEnabledInput = (toPort && toPort->getInput() && toPort->isEnabled());
1263
1264 if (!(fromPortIsEnabledOutput && toPortIsEnabledInput))
1265 return false;
1266
1267 VuoType *originalFromDataType = ((VuoCompilerPortClass *)(this->getBase()->getClass()->getCompiler()))->getDataVuoType();
1268 VuoType *originalToDataType = ((VuoCompilerPortClass *)(toPort->getBase()->getClass()->getCompiler()))->getDataVuoType();
1269
1270 VuoType *currentFromDataType = this->getDataType();
1271 VuoType *currentToDataType = toPort->getDataType();
1272
1273 if (!(originalFromDataType && originalToDataType && currentFromDataType && currentToDataType))
1274 return false;
1275
1276 VuoGenericType *currentFromGenericType = dynamic_cast<VuoGenericType *>(currentFromDataType);
1277 VuoGenericType *currentToGenericType = dynamic_cast<VuoGenericType *>(currentToDataType);
1278
1280 if (VuoType::isListTypeName(currentFromDataType->getModuleKey()) != VuoType::isListTypeName(currentToDataType->getModuleKey()))
1281 return false;
1282
1283 // Case: The 'From' port is generic and can be specialized to match the concrete type of the 'To' port.
1284 if (currentFromGenericType && currentFromGenericType->isSpecializedTypeCompatible(originalToDataType->getModuleKey())
1285 && isSpecializationImplementedForType(originalToDataType->getModuleKey()))
1286 {
1287 *portToSpecialize = this;
1288 specializedTypeName = originalToDataType->getModuleKey();
1289
1290 return true;
1291 }
1292
1293 // Case: The 'To' port is generic and can be specialized to match the concrete type of the 'From' port.
1294 else if (currentToGenericType && currentToGenericType->isSpecializedTypeCompatible(originalFromDataType->getModuleKey())
1295 && isSpecializationImplementedForType(originalFromDataType->getModuleKey()))
1296 {
1297 *portToSpecialize = toPort;
1298 specializedTypeName = originalFromDataType->getModuleKey();
1299
1300 return true;
1301 }
1302
1303 return false;
1304}
1305
1312{
1313 return !VuoType::isDictionaryTypeName(typeName) &&
1314 (typeName != "VuoMathExpressionList");
1315}
1316
1321{
1322 vector<VuoCable *> cables = this->getBase()->getConnectedCables(includePublishedCables);
1323 for (vector<VuoCable *>::iterator cable = cables.begin(); cable != cables.end(); ++cable)
1324 if ((*cable)->getToPort() == toPort->getBase())
1325 return (*cable);
1326
1327 return NULL;
1328}
1329
1333void VuoRendererPort::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
1334{
1335 if (isConstant())
1336 {
1338 }
1339}
1340
1345{
1346 if (isConstant() &&
1347 (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter))
1348 {
1350 }
1351}
1352
1358{
1359 return (getInput() &&
1360 (! dynamic_cast<VuoRendererTypecastPort *>(this)) &&
1362}
1363
1368{
1369 return ((! isOutput) && (! isFunctionPort));
1370}
1371
1376{
1377 return isOutput;
1378}
1379
1384{
1385 return isRefreshPort;
1386}
1387
1392{
1393 return isFunctionPort;
1394}
1395
1400{
1401 return isRefreshPort && getBase()->getConnectedCables(true).empty();
1402}
1403
1408{
1409 return (getBase()->getClass()->getPortType() == VuoPortClass::dataAndEventPort ||
1410 (getBase()->getClass()->hasCompiler() &&
1411 static_cast<VuoCompilerPortClass *>(getBase()->getClass()->getCompiler())->getDataVuoType()));
1412}
1413
1418{
1420 this->prepareGeometryChange();
1422}
1423
1427QVariant VuoRendererPort::itemChange(GraphicsItemChange change, const QVariant &value)
1428{
1429 // Port has moved relative to its parent
1430 if (change == QGraphicsItem::ItemPositionHasChanged)
1431 {
1432 VuoRendererNode *parentNode = getRenderedParentNode();
1433 if (parentNode)
1435 }
1436
1437 return QGraphicsItem::itemChange(change, value);
1438}
1439
1445{
1446 if (!(getBase() && getBase()->hasCompiler()))
1447 return NULL;
1448
1449 VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(getBase()->getCompiler());
1450 return compilerPort->getDataVuoType();
1451}
1452
1459{
1460 // For now, ports with URLs are expected to be of type "VuoText".
1461 if ( !(getDataType() && getDataType()->getModuleKey() == "VuoText") )
1462 return false;
1463
1464 string portName = getBase()->getClass()->getName();
1465
1466 // Case: Port is titled "url"
1467 if (portName == "url")
1468 return true;
1469
1470 // Case: Port is titled "folder"
1471 // Relevant for vuo.file.list node.
1472 if (portName == "folder")
1473 return true;
1474
1475 // Case: Port is an input port on a drawer attached to a port titled "urls"
1476 // Relevant for vuo.image.fetch.list, vuo.scene.fetch.list nodes.
1478 if (drawer)
1479 {
1480 VuoPort *hostPort = drawer->getRenderedHostPort();
1481 if (hostPort && (hostPort->getClass()->getName() == "urls"))
1482 return true;
1483 }
1484
1485 return false;
1486}
1487
1496{
1497 if ( !(isConstant() && hasURLType()) )
1498 return false;
1499
1500 json_object *details = static_cast<VuoCompilerInputEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1501 json_object *isSaveValue = NULL;
1502 if (details && json_object_object_get_ex(details, "isSave", &isSaveValue) && json_object_get_boolean(isSaveValue))
1503 return false;
1504
1506 bool isRelativePath = VuoUrl_isRelativePath(url);
1507 VuoRelease(url);
1508 return isRelativePath;
1509}
1510
1515{
1516 return ((getInput() && getDataType()) && // input port with data...
1517 (!effectivelyHasConnectedDataCable(true))); // ... that has no incoming data cable (published or unpublished).
1518}
1519
1524bool VuoRendererPort::effectivelyHasConnectedDataCable(bool includePublishedCables) const
1525{
1526 vector<VuoCable *> connectedCables = getBase()->getConnectedCables(includePublishedCables);
1527 for (vector<VuoCable *>::iterator cable = connectedCables.begin(); cable != connectedCables.end(); ++cable)
1528 if ((*cable)->hasRenderer() && (*cable)->getRenderer()->effectivelyCarriesData())
1529 return true;
1530 return false;
1531}
1532
1536string VuoRendererPort::format(const char *format, ...)
1537{
1538 va_list args;
1539
1540 va_start(args, format);
1541 int size = vsnprintf(NULL, 0, format, args);
1542 va_end(args);
1543
1544 char *formattedString = (char *)malloc(size+1);
1545 va_start(args, format);
1546 vsnprintf(formattedString, size+1, format, args);
1547 va_end(args);
1548
1549 string s(formattedString);
1550 free(formattedString);
1551 return s;
1552}
1553
1558{
1559 string s(strz);
1560 free(strz);
1561 return s;
1562}
1563
1568{
1569 if (!(getInput() && getDataType()))
1570 return "";
1571
1572 VuoCompilerInputEventPort *compilerEventPort = dynamic_cast<VuoCompilerInputEventPort *>(getBase()->getCompiler());
1573 if (! compilerEventPort)
1574 return "";
1575
1576 return compilerEventPort->getData()->getInitialValue();
1577}
1578
1584{
1585 VuoText fullString = VuoText_make(getConstantAsStringToRender().c_str());
1586 VuoLocal(fullString);
1587
1588 if (getDataType() && (getDataType()->getModuleKey() == "VuoColor"))
1589 return "";
1590
1591 bool truncateFromBeginning = (getDataType() &&
1592 (getDataType()->getModuleKey()=="VuoArtNetInputDevice" ||
1593 getDataType()->getModuleKey()=="VuoArtNetOutputDevice" ||
1594 hasURLType()));
1595
1596 size_t maxLength = strlen("Matches wildcard (not case-sensitive)");
1597 VuoText t = VuoText_truncateWithEllipsis(fullString, maxLength, truncateFromBeginning?
1598 VuoTextTruncation_Beginning :
1599 VuoTextTruncation_End);
1600 VuoLocal(t);
1601 return string(t);
1602}
1603
1609{
1610 if (!(getInput() && getDataType()))
1611 return "";
1612
1614 if (getDataType())
1615 {
1616 // Don't display constant input values for generic ports.
1617 if (dynamic_cast<VuoGenericType *>(getDataType()))
1618 return "";
1619
1620 string typeName = getDataType()->getModuleKey();
1621
1622 // Don't display constant input values for list ports.
1623 if (VuoType::isListTypeName(typeName))
1624 return "";
1625
1626 // Don't display constant input values for types that don't have input editors.
1627 if (typeName == "VuoData")
1628 return "";
1629
1632 if (getDataType()->getModuleKey()=="VuoReal")
1633 {
1634 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1635 double real = json_object_get_double(js);
1636 json_object_put(js);
1637
1638 if (getBase()->getClass()->hasCompiler() && !dynamic_cast<VuoPublishedPort *>(getBase()))
1639 {
1640 json_object *portDetails = static_cast<VuoCompilerEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1641 json_object *autoObject = NULL;
1642 if (json_object_object_get_ex(portDetails, "auto", &autoObject))
1643 if (real == json_object_get_double(autoObject))
1644 return "Auto";
1645 }
1646
1647 return getStringForRealValue(real);
1648 }
1649 if (getDataType()->getModuleKey()=="VuoInteger")
1650 {
1651 // Retrieve the port's JSON details object.
1652 json_object *details = NULL;
1654 if (portClass)
1655 details = portClass->getDataClass()->getDetails();
1656
1657 // Case: Port type is named enum
1658 json_object *menuItemsValue = NULL;
1659 if (details && json_object_object_get_ex(details, "menuItems", &menuItemsValue))
1660 {
1661 string portValue = getConstantAsString();
1662 // Support upgrading a VuoBoolean port to a named enum.
1663 if (portValue == "false")
1664 portValue = "0";
1665 else if (portValue == "true")
1666 portValue = "1";
1667
1668 int len = json_object_array_length(menuItemsValue);
1669 for (int i = 0; i < len; ++i)
1670 {
1671 json_object *menuItem = json_object_array_get_idx(menuItemsValue, i);
1672 if (json_object_is_type(menuItem, json_type_object))
1673 {
1674 json_object *value = NULL;
1675 if (json_object_object_get_ex(menuItem, "value", &value))
1676 if ((json_object_is_type(value, json_type_string) && portValue == json_object_get_string(value))
1677 || (json_object_is_type(value, json_type_int ) && atol(portValue.c_str()) == json_object_get_int64(value)))
1678 {
1679 json_object *name = NULL;
1680 if (json_object_object_get_ex(menuItem, "name", &name))
1681 {
1682 VuoText t = VuoText_makeFromJson(name);
1683 VuoLocal(t);
1684 VuoText tr = VuoText_trim(t); // Trim off leading indentation, if any.
1685 VuoLocal(tr);
1686 return string(tr);
1687 }
1688 }
1689 }
1690 }
1691 }
1692
1693 // Case: Port type is a regular VuoInteger
1694 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1696 json_object_put(js);
1697
1698 if (getBase()->getClass()->hasCompiler() && !dynamic_cast<VuoPublishedPort *>(getBase()))
1699 {
1700 json_object *portDetails = static_cast<VuoCompilerEventPortClass *>(getBase()->getClass()->getCompiler())->getDataClass()->getDetails();
1701 json_object *autoObject = NULL;
1702 if (json_object_object_get_ex(portDetails, "auto", &autoObject))
1703 if (i == VuoInteger_makeFromJson(autoObject))
1704 return "Auto";
1705 }
1706
1707 return format("%lld", i);
1708 }
1709 if (getDataType()->getModuleKey()=="VuoPoint2d")
1710 {
1711 VuoPoint2d p = VuoMakeRetainedFromString(getConstantAsString().c_str(), VuoPoint2d);
1712 QList<float> pointList = QList<float>() << p.x << p.y;
1713 return getPointStringForCoords(pointList);
1714 }
1715 if (getDataType()->getModuleKey()=="VuoPoint3d")
1716 {
1717 VuoPoint3d p = VuoMakeRetainedFromString(getConstantAsString().c_str(), VuoPoint3d);
1718 QList<float> pointList = QList<float>() << p.x << p.y << p.z;
1719 return getPointStringForCoords(pointList);
1720 }
1721 if (getDataType()->getModuleKey()=="VuoPoint4d")
1722 {
1723 VuoPoint4d p = VuoMakeRetainedFromString(getConstantAsString().c_str(), VuoPoint4d);
1724 QList<float> pointList = QList<float>() << p.x << p.y << p.z << p.w;
1725 return getPointStringForCoords(pointList);
1726 }
1727 if (getDataType()->getModuleKey()=="VuoFont")
1728 {
1729 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1730 json_object *o = NULL;
1731
1732 const char *fontName = NULL;
1733 if (json_object_object_get_ex(js, "fontName", &o))
1734 fontName = json_object_get_string(o);
1735
1736 double pointSize = 0;
1737 if (json_object_object_get_ex(js, "pointSize", &o))
1738 pointSize = json_object_get_double(o);
1739
1740 bool underline = false;
1741 if (json_object_object_get_ex(js, "underline", &o))
1742 underline = json_object_get_boolean(o);
1743 const char *underlineString = underline ? " [U]" : "";
1744
1745 string outputString;
1746 if (fontName)
1747 outputString = format("%s %gpt%s", fontName, pointSize, underlineString);
1748
1749 json_object_put(js);
1750
1751 return outputString;
1752 }
1753 if (getDataType()->getModuleKey()=="VuoMathExpressionList")
1754 {
1755 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1756 json_object *expressionsObject = NULL;
1757
1758 string expression;
1759 if (json_object_object_get_ex(js, "expressions", &expressionsObject))
1760 {
1761 if (json_object_get_type(expressionsObject) == json_type_array)
1762 {
1763 int itemCount = json_object_array_length(expressionsObject);
1764 if (itemCount > 0)
1765 {
1766 json_object *itemObject = json_object_array_get_idx(expressionsObject, 0);
1767 if (json_object_get_type(itemObject) == json_type_string)
1768 expression = json_object_get_string(itemObject);
1769 }
1770 }
1771 }
1772
1773 json_object_put(js);
1774
1775 return expression;
1776 }
1777 if (getDataType()->getModuleKey()=="VuoRealRegulation")
1778 {
1779 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1780 json_object *o = NULL;
1781
1782 string outputString;
1783 if (json_object_object_get_ex(js, "name", &o))
1784 outputString = json_object_get_string(o);
1785
1786 json_object_put(js);
1787
1788 return outputString;
1789 }
1790 if (getDataType()->getModuleKey()=="VuoImage")
1791 {
1793 if (!value)
1794 return "";
1795
1796 string s = format("%lu×%lu", value->pixelsWide, value->pixelsHigh);
1797 VuoRelease(value);
1798 return s;
1799 }
1800 if (getDataType()->getModuleKey()=="VuoTransform")
1801 {
1803
1804 if (VuoTransform_isIdentity(value))
1805 return "≡";
1806
1807 if (value.type == VuoTransformTypeTargeted)
1808 return format("(%g,%g,%g) toward (%g,%g,%g)",
1809 value.translation.x, value.translation.y, value.translation.z, value.rotationSource.target.x, value.rotationSource.target.y, value.rotationSource.target.z);
1810
1811 string rotation;
1812 if (value.type == VuoTransformTypeQuaternion)
1813 rotation = format("‹%g,%g,%g,%g›",
1814 value.rotationSource.quaternion.x, value.rotationSource.quaternion.y, value.rotationSource.quaternion.z, value.rotationSource.quaternion.w);
1815 else
1816 {
1817 VuoPoint3d r = VuoPoint3d_multiply(value.rotationSource.euler, 180./M_PI);
1818 rotation = format("(%g°,%g°,%g°)",
1819 r.x, r.y, r.z);
1820 }
1821
1822 return format("(%g,%g,%g) %s %g×%g×%g",
1823 value.translation.x, value.translation.y, value.translation.z, rotation.c_str(), value.scale.x, value.scale.y, value.scale.z);
1824 }
1825 if (getDataType()->getModuleKey()=="VuoTransform2d")
1826 {
1828
1829 if (VuoTransform2d_isIdentity(value))
1830 return "≡";
1831
1832 VuoReal rotationInDegrees = value.rotation * 180./M_PI;
1833 return format("(%g,%g) %g° %g×%g",
1834 value.translation.x, value.translation.y, rotationInDegrees, value.scale.x, value.scale.y);
1835 }
1836 if (getDataType()->getModuleKey()=="VuoMovieFormat")
1837 {
1838 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1839 json_object *o = NULL;
1840
1841 const char *imageEncoding = NULL;
1842 if (json_object_object_get_ex(js, "imageEncoding", &o))
1843 {
1844 imageEncoding = json_object_get_string(o);
1845 if (strcasecmp(imageEncoding, "jpeg") == 0)
1846 imageEncoding = "JPEG";
1847 else if (strcasecmp(imageEncoding, "h264") == 0)
1848 imageEncoding = "H.264";
1849 else if (strcasecmp(imageEncoding, "prores4444") == 0)
1850 imageEncoding = "ProRes 4444";
1851 else if (strcasecmp(imageEncoding, "prores422") == 0)
1852 imageEncoding = "ProRes 422";
1853 else if (strcasecmp(imageEncoding, "prores422-hq") == 0)
1854 imageEncoding = "ProRes 422 HQ";
1855 else if (strcasecmp(imageEncoding, "prores422-lt") == 0)
1856 imageEncoding = "ProRes 422 LT";
1857 else if (strcasecmp(imageEncoding, "prores422-proxy") == 0)
1858 imageEncoding = "ProRes 422 Proxy";
1859 else if (strcmp(imageEncoding, "hevc") == 0)
1860 imageEncoding = "HEVC";
1861 else if (strcmp(imageEncoding, "hevc-alpha") == 0)
1862 imageEncoding = "HEVC+Alpha";
1863 }
1864
1865 const char *audioEncoding = NULL;
1866 if (json_object_object_get_ex(js, "audioEncoding", &o))
1867 {
1868 audioEncoding = json_object_get_string(o);
1869 if (strcmp(audioEncoding, "LinearPCM") == 0)
1870 audioEncoding = "Linear PCM";
1871 }
1872
1873 string outputString;
1874 if (imageEncoding && audioEncoding)
1875 outputString = format("%s, %s", imageEncoding, audioEncoding);
1876
1877 json_object_put(js);
1878
1879 return outputString;
1880 }
1881 if (getDataType()->getModuleKey()=="VuoScreen")
1882 {
1883 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1884 json_object *o = NULL;
1885
1886 string label;
1887 if (json_object_object_get_ex(js, "type", &o))
1888 {
1889 VuoScreenType type = VuoScreen_typeFromCString(json_object_get_string(o));
1890
1891 if (type == VuoScreenType_Active)
1892 label = "Active";
1893 else if (type == VuoScreenType_Primary)
1894 label = "Primary";
1895 else if (type == VuoScreenType_Secondary)
1896 label = "Secondary";
1897 else if (type == VuoScreenType_MatchName)
1898 {
1899 if (json_object_object_get_ex(js, "name", &o))
1900 label = json_object_get_string(o);
1901 }
1902 else if (type == VuoScreenType_MatchId)
1903 {
1904 VuoScreen screen = VuoScreen_makeFromJson(js);
1905 VuoScreen realizedScreen;
1906 if (VuoScreen_realize(screen, &realizedScreen))
1907 label = realizedScreen.name;
1908 }
1909 }
1910 json_object_put(js);
1911
1912 return label;
1913 }
1914 if (getDataType()->getModuleKey()=="VuoSerialDevice")
1915 {
1916 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1917 json_object *o = NULL;
1918
1919 const char *name = NULL;
1920 if (json_object_object_get_ex(js, "name", &o))
1921 name = json_object_get_string(o);
1922 else if (json_object_object_get_ex(js, "path", &o))
1923 name = json_object_get_string(o);
1924
1925 string outputString = "First";
1926 if (name && strlen(name))
1927 outputString = name;
1928
1929 json_object_put(js);
1930
1931 return outputString;
1932 }
1933 if (getDataType()->getModuleKey()=="VuoMidiInputDevice")
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 = "First";
1943 if (name && strlen(name))
1944 outputString = name;
1945
1946 json_object_put(js);
1947
1948 return outputString;
1949 }
1950 if (getDataType()->getModuleKey()=="VuoMidiOutputDevice")
1951 {
1952 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1953 json_object *o = NULL;
1954
1955 const char *name = NULL;
1956 if (json_object_object_get_ex(js, "name", &o))
1957 name = json_object_get_string(o);
1958
1959 string outputString = "First";
1960 if (name && strlen(name))
1961 outputString = name;
1962
1963 json_object_put(js);
1964
1965 return outputString;
1966 }
1967 if (getDataType()->getModuleKey()=="VuoSyphonServerDescription")
1968 {
1969 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1970 json_object *o = NULL;
1971
1972 const char *name = NULL;
1973 if (json_object_object_get_ex(js, "serverName", &o))
1974 {
1975 const char *n = json_object_get_string(o);
1976 if (strcmp(n, "*"))
1977 name = n;
1978 }
1979 if (!name && json_object_object_get_ex(js, "applicationName", &o))
1980 {
1981 const char *n = json_object_get_string(o);
1982 if (strcmp(n, "*"))
1983 name = n;
1984 }
1985
1986 string outputString = "First";
1987 if (name && strlen(name))
1988 outputString = name;
1989
1990 json_object_put(js);
1991
1992 return outputString;
1993 }
1994 if (getDataType()->getModuleKey()=="VuoVideoInputDevice")
1995 {
1996 json_object *js = json_tokener_parse(getConstantAsString().c_str());
1997 json_object *o = NULL;
1998
1999 const char *name = NULL;
2000 if (json_object_object_get_ex(js, "name", &o))
2001 name = json_object_get_string(o);
2002 else if (json_object_object_get_ex(js, "id", &o))
2003 name = json_object_get_string(o);
2004
2005 string outputString = "Default";
2006 if (name && strlen(name))
2007 outputString = name;
2008
2009 json_object_put(js);
2010
2011 return outputString;
2012 }
2015 if (getDataType()->getModuleKey()=="VuoHidDevice")
2016 {
2017 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2018 json_object *o = NULL;
2019
2020 QString outputString;
2021 if (json_object_object_get_ex(js, "name", &o))
2022 {
2023 outputString = json_object_get_string(o);
2024
2025 // Trim off the parenthetical vendor/class.
2026 outputString = outputString.section(" (", 0, 0);
2027 }
2028 else if (json_object_object_get_ex(js, "matchType", &o))
2029 {
2030 const char *matchType = json_object_get_string(o);
2031 if (strcmp(matchType, "usage") == 0)
2032 {
2033 int usagePage = 0;
2034 if (json_object_object_get_ex(js, "usagePage", &o))
2035 usagePage = json_object_get_int(o);
2036 int usage = 0;
2037 if (json_object_object_get_ex(js, "usage", &o))
2038 usage = json_object_get_int(o);
2039 json_object_put(js);
2040 return stringAndFree(VuoHid_getUsageText(usagePage, usage));
2041 }
2042 }
2043 json_object_put(js);
2044
2045 return outputString.toStdString();
2046 }
2047 if (getDataType()->getModuleKey()=="VuoOscInputDevice"
2048 || getDataType()->getModuleKey()=="VuoOscOutputDevice")
2049 {
2050 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2051 json_object *o = NULL;
2052
2053 const char *name = NULL;
2054 if (json_object_object_get_ex(js, "name", &o))
2055 name = json_object_get_string(o);
2056
2057 string outputString = "Auto";
2058 if (name && strlen(name))
2059 outputString = name;
2060
2061 json_object_put(js);
2062
2063 return outputString;
2064 }
2065 if (getDataType()->getModuleKey()=="VuoArtNetInputDevice"
2066 || getDataType()->getModuleKey()=="VuoArtNetOutputDevice")
2067 {
2068 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2069 json_object *o = NULL;
2070
2071 const char *name = NULL;
2072 if (json_object_object_get_ex(js, "name", &o))
2073 name = json_object_get_string(o);
2074 else
2075 {
2076 if (getDataType()->getModuleKey()=="VuoArtNetInputDevice")
2077 name = "Any";
2078 else
2079 name = "Broadcast";
2080 }
2081
2082 VuoInteger net=0, subNet=0, universe=0;
2083 if (json_object_object_get_ex(js, "net", &o))
2084 net = json_object_get_int64(o);
2085 if (json_object_object_get_ex(js, "subNet", &o))
2086 subNet = json_object_get_int64(o);
2087 if (json_object_object_get_ex(js, "universe", &o))
2088 universe = json_object_get_int64(o);
2089
2090 string outputString = format("%s (%lld:%lld:%lld)", name, net, subNet, universe);
2091
2092 json_object_put(js);
2093
2094 return outputString;
2095 }
2096 if (getDataType()->getModuleKey()=="VuoTempoRange")
2097 {
2098 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2099 const char *tempoRange = json_object_get_string(js);
2100 if (!tempoRange)
2101 return "Unknown";
2102 if (strcmp(tempoRange, "andante") == 0)
2103 return "70–110 BPM";
2104 else if (strcmp(tempoRange, "moderato") == 0)
2105 return "100–140 BPM";
2106 else if (strcmp(tempoRange, "allegro") == 0)
2107 return "120–180 BPM";
2108 else if (strcmp(tempoRange, "presto") == 0)
2109 return "170–250 BPM";
2110 else if (strcmp(tempoRange, "prestissimo") == 0)
2111 return "220–320 BPM";
2112 }
2113 if (getDataType()->getModuleKey()=="VuoEdgeBlend")
2114 {
2115 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2116
2117 double cutoff = 0, gamma = 0, crop = 0;
2118 json_object *o = NULL;
2119
2120 if (json_object_object_get_ex(js, "cutoff", &o))
2121 cutoff = json_object_get_double(o);
2122
2123 if (json_object_object_get_ex(js, "gamma", &o))
2124 gamma = json_object_get_double(o);
2125
2126 if (json_object_object_get_ex(js, "crop", &o))
2127 crop = json_object_get_double(o);
2128
2129 json_object_put(js);
2130
2131 double cropPercent = -crop * 100;
2132 double cutoffPercent = cutoff * 100;
2133 if (VuoReal_areEqual(crop, 0) && VuoReal_areEqual(cutoff, 0))
2134 return "≡";
2135 else if (VuoReal_areEqual(cutoff, 0))
2136 return format("%.0f%%", cropPercent);
2137 else if (VuoReal_areEqual(gamma, 1))
2138 return format("%.0f%% %.0f%%", cropPercent, cutoffPercent);
2139 else
2140 return format("%.0f%% %.0f%% @ %.2gγ", cropPercent, cutoffPercent, gamma);
2141 }
2142 if (getDataType()->getModuleKey()=="VuoRange")
2143 {
2144 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2145
2146 double minimum = VuoRange_NoMinimum, maximum = VuoRange_NoMaximum;
2147
2148 json_object *o = NULL;
2149
2150 if (json_object_object_get_ex(js, "minimum", &o))
2151 minimum = json_object_get_double(o);
2152
2153 if (json_object_object_get_ex(js, "maximum", &o))
2154 maximum = json_object_get_double(o);
2155
2156 json_object_put(js);
2157
2158 if (minimum != VuoRange_NoMinimum && maximum != VuoRange_NoMaximum)
2159 return format("%.4g to %.4g", minimum, maximum);
2160 else if (minimum != VuoRange_NoMinimum)
2161 return format("%.4g to ∞", minimum);
2162 else if (maximum != VuoRange_NoMaximum)
2163 return format("-∞ to %.4g", maximum);
2164 else
2165 return format("-∞ to ∞");
2166 }
2167 if (getDataType()->getModuleKey()=="VuoIntegerRange")
2168 {
2169 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2170
2172
2173 json_object *o = NULL;
2174
2175 if (json_object_object_get_ex(js, "minimum", &o))
2176 minimum = json_object_get_int64(o);
2177
2178 if (json_object_object_get_ex(js, "maximum", &o))
2179 maximum = json_object_get_int64(o);
2180
2181 json_object_put(js);
2182
2183 if (minimum != VuoIntegerRange_NoMinimum && maximum != VuoIntegerRange_NoMaximum)
2184 return format("%lld to %lld", minimum, maximum);
2185 else if (minimum != VuoIntegerRange_NoMinimum)
2186 return format("%lld to ∞", minimum);
2187 else if (maximum != VuoIntegerRange_NoMaximum)
2188 return format("-∞ to %lld", maximum);
2189 else
2190 return format("-∞ to ∞");
2191 }
2194 if (getDataType()->getModuleKey() == "VuoBlackmagicInputDevice"
2195 || getDataType()->getModuleKey() == "VuoBlackmagicOutputDevice")
2196 {
2197 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2198 json_object *o;
2199 if (json_object_object_get_ex(js, "name", &o))
2200 return json_object_get_string(o);
2201 else
2202 return "First";
2203 }
2206 if (getDataType()->getModuleKey() == "VuoNdiSource")
2207 {
2208 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2209 json_object *o, *o2;
2210 if (json_object_object_get_ex(js, "name", &o))
2211 return json_object_get_string(o);
2212 else if (json_object_object_get_ex(js, "ipAddress", &o)
2213 && json_object_object_get_ex(js, "port", &o2))
2214 return format("%s:%lld", json_object_get_string(o), json_object_get_int(o2));
2215 else if (json_object_object_get_ex(js, "ipAddress", &o))
2216 return json_object_get_string(o);
2217 else
2218 return "First";
2219 }
2220 }
2221
2222 // If it's a JSON string (e.g., VuoText or an enum identifier), unescape and optionally capitalize it.
2223 json_object *js = json_tokener_parse(getConstantAsString().c_str());
2224 if (json_object_get_type(js) == json_type_string)
2225 {
2226 string textWithoutQuotes = json_object_get_string(js);
2227 json_object_put(js);
2228
2229 // Show linebreaks as a glyph (rather than causing the following text to move to the next line, which gets cut off).
2230 VuoStringUtilities::replaceAll(textWithoutQuotes, "\n", "⏎");
2231
2232 string type;
2233 if (getDataType())
2234 type = getDataType()->getModuleKey();
2235
2236 // Leave text as-is.
2237 if (type == "VuoText"
2238 || type == "VuoImageFormat"
2239 || type == "VuoAudioEncoding"
2240 || type == "VuoBlackmagicConnection"
2241 || type == "VuoBlackmagicVideoMode"
2242 || type == "VuoMovieImageEncoding")
2243 return textWithoutQuotes;
2244
2245 // All-caps.
2246 if (type == "VuoTableFormat")
2247 {
2248 std::transform(textWithoutQuotes.begin(), textWithoutQuotes.end(), textWithoutQuotes.begin(), ::toupper);
2249 return textWithoutQuotes;
2250 }
2251
2252 // Convert hyphenations to camelcase.
2253 // Example: VuoTimeFormat
2254 for (auto it = textWithoutQuotes.begin(); it != textWithoutQuotes.end();)
2255 if (*it == '-')
2256 {
2257 textWithoutQuotes.erase(it);
2258 *it = toupper(*it);
2259 }
2260 else
2261 ++it;
2262
2263 return VuoStringUtilities::expandCamelCase(textWithoutQuotes);
2264 }
2265 json_object_put(js);
2266
2267 return getConstantAsString();
2268}
2269
2273void VuoRendererPort::setConstant(string constantValue)
2274{
2276 if (eventPort)
2277 {
2278 QGraphicsItem::CacheMode normalCacheMode = cacheMode();
2279 setCacheMode(QGraphicsItem::NoCache);
2281
2282 eventPort->getData()->setInitialValue(constantValue);
2283
2284 updateToolTip();
2285
2286 setCacheMode(normalCacheMode);
2290
2291 // Ensure this node's cable paths are updated to escape the new constant's flag.
2292 set<VuoCable *> cables = getRenderedParentNode()->getConnectedInputCables(true);
2293 for (set<VuoCable *>::iterator i = cables.begin(); i != cables.end(); ++i)
2294 {
2295 (*i)->getRenderer()->setPortConstantsChanged();
2296 (*i)->getRenderer()->updateGeometry();
2297 }
2298 }
2299}
2300
2307{
2308 bool sidebarPaintMode = dynamic_cast<const VuoRendererPublishedPort *>(this);
2309 string name = getPortNameToRender();
2311
2312 if (name.empty() || isAnimated)
2313 return false;
2314 else if (parent && parent->isMissingImplementation())
2315 return false;
2316 else if (sidebarPaintMode)
2317 return true;
2318 else if (isRefreshPort || isFunctionPort || typecastParentPort)
2319 return false;
2320
2321 return true;
2322}
2323
2331string VuoRendererPort::getPointStringForCoords(QList<float> coordList) const
2332{
2333 const QString coordSeparator = QString(QLocale::system().decimalPoint() != ','? QChar(',') : QChar(';')).append(" ");
2334 QStringList coordStringList;
2335
2336 foreach (float coord, coordList)
2337 coordStringList.append(getStringForRealValue(coord).c_str());
2338
2339 QString pointString = QString("(").append(coordStringList.join(coordSeparator).append(")"));
2340 return pointString.toStdString();
2341}
2342
2348{
2349 // See VuoDoubleSpinBox::textFromValue.
2350 QString valueAsStringInUserLocale = QLocale::system().toString(value, 'g', 11);
2351 if (qAbs(value) >= 1000.0)
2352 valueAsStringInUserLocale.remove(QLocale::system().groupSeparator());
2353
2354 return valueAsStringInUserLocale.toStdString();
2355}
2356
2362{
2363 // 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).
2364 QString valueAsStringInUserLocale = QLocale::system().toString(value, 'g', 7);
2365 if (qAbs(value) >= 1000.0)
2366 valueAsStringInUserLocale.remove(QLocale::system().groupSeparator());
2367
2368 return valueAsStringInUserLocale.toStdString();
2369}
2370
2375{
2376 // @todo: Allow generic published ports (https://b33p.net/kosada/node/7655).
2377 bool isGeneric = bool(dynamic_cast<VuoGenericType *>(this->getDataType()));
2378
2379 // @todo: Allow published dictionary ports (https://b33p.net/kosada/node/8524).
2380 bool hasDictionaryType = (this->getDataType() && VuoStringUtilities::beginsWith(this->getDataType()->getModuleKey(), "VuoDictionary_"));
2381
2382 // @todo: Allow published math expression ports for "Calculate" nodes (https://b33p.net/kosada/node/8550).
2383 bool isMathExpressionInputToCalculateNode = (this->getDataType() &&
2384 (this->getDataType()->getModuleKey() == "VuoMathExpressionList") &&
2385 this->getUnderlyingParentNode() &&
2386 VuoStringUtilities::beginsWith(this->getUnderlyingParentNode()->getBase()->getNodeClass()->getClassName(), "vuo.math.calculate"));
2387
2388
2389 // @todo: Allow direct connections between external published inputs and external published outputs
2390 // (https://b33p.net/kosada/node/7756).
2391 return (!isGeneric && !hasDictionaryType && !isMathExpressionInputToCalculateNode && !dynamic_cast<const VuoRendererPublishedPort *>(this));
2392}
2393
2398vector<VuoRendererPublishedPort *> VuoRendererPort::getPublishedPorts(void) const
2399{
2400 vector <VuoRendererPublishedPort *> publishedPorts;
2401 foreach (VuoCable *cable, getBase()->getConnectedCables(true))
2402 {
2403 if (getInput() && cable->isPublishedInputCable())
2404 publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getFromPort()->getRenderer()));
2405 else if (getOutput() && cable->isPublishedOutputCable())
2406 publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getToPort()->getRenderer()));
2407 }
2408
2409 return publishedPorts;
2410}
2411
2416vector<VuoRendererPublishedPort *> VuoRendererPort::getPublishedPortsConnectedByDataCarryingCables(void) const
2417{
2418 vector <VuoRendererPublishedPort *> publishedPorts;
2419 foreach (VuoCable *cable, getBase()->getConnectedCables(true))
2420 {
2421 if (getInput() && cable->isPublishedInputCable() && cable->getRenderer()->effectivelyCarriesData())
2422 publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getFromPort()->getRenderer()));
2423 else if (getOutput() && cable->isPublishedOutputCable() && cable->getRenderer()->effectivelyCarriesData())
2424 publishedPorts.push_back(dynamic_cast<VuoRendererPublishedPort *>(cable->getToPort()->getRenderer()));
2425 }
2426
2427 return publishedPorts;
2428}
2429
2438
2443{
2444 this->timeLastEventFired = QDateTime::currentMSecsSinceEpoch();
2445}
2446
2455
2459vector<QGraphicsItemAnimation *> VuoRendererPort::getAnimations()
2460{
2461 return this->animations;
2462}
2463
2469{
2470 this->isAnimated = animated;
2472}
2473
2477void VuoRendererPort::setCacheModeForPortAndChildren(QGraphicsItem::CacheMode mode)
2478{
2479 this->setCacheMode(mode);
2480
2481 VuoRendererTypecastPort *typecastPort = dynamic_cast<VuoRendererTypecastPort *>(this);
2482 if (typecastPort)
2483 typecastPort->getChildPort()->setCacheMode(mode);
2484}
2485
2491{
2492 // Port animations, and ports without compilers, shouldn't accept mouse events.
2493 setEnabled(!isAnimated &&
2494 ((getBase()->hasCompiler() && getBase()->getClass()->hasCompiler()) ||
2495 dynamic_cast<VuoRendererPublishedPort *>(this)) &&
2497}
2498
2506{
2507 // A published port name must:
2508 // - Contain only alphanumeric characters; and
2509 // - Either be entirely numeric or begin with an alphabetic character; and
2510 // - Have a total length of 1-31 characters.
2511 return QString("[A-Za-z][A-Za-z0-9]{0,30}")
2512 .append("|")
2513 .append("[0-9]{1,31}");
2514}
2515
2524QString VuoRendererPort::sanitizePortName(QString portName)
2525{
2526 // Remove non-alphanumeric characters.
2527 portName.remove(QRegExp("[^A-Za-z0-9]"));
2528
2529 // Unless the name is purely numeric, remove non-alphabetic first characters.
2530 if (!portName.contains(QRegExp("^[0-9]+$")))
2531 {
2532 while (!portName.isEmpty() && !portName.contains(QRegExp("^[A-Za-z]")))
2533 portName = portName.right(portName.size()-1);
2534 }
2535
2536 // Remove characters beyond the 31st.
2537 portName = portName.left(31);
2538
2539 return portName;
2540}
2541
2542VuoRendererPort::~VuoRendererPort()
2543{
2544 foreach (QGraphicsItemAnimation *animation, animations)
2545 animation->clear();
2546
2547 animations.clear();
2548}