Vuo  2.0.3
VuoPublishedPortSidebar.cc
Go to the documentation of this file.
1 
11 #include "ui_VuoPublishedPortSidebar.h"
12 
14 
15 #include "VuoCompilerCable.hh"
16 #include "VuoCompilerPortClass.hh"
18 #include "VuoComposition.hh"
20 #include "VuoDetailsEditorPoint.hh"
21 #include "VuoEditor.hh"
22 #include "VuoEditorComposition.hh"
23 #include "VuoModuleManager.hh"
24 #include "VuoProtocol.hh"
26 #include "VuoStringUtilities.hh"
27 
31 VuoPublishedPortSidebar::VuoPublishedPortSidebar(QWidget *parent, VuoEditorComposition *composition, bool isInput, bool enableProtocolChanges) :
32  QDockWidget(parent),
33  ui(new Ui::VuoPublishedPortSidebar)
34 {
35  ui->setupUi(this);
36  this->composition = composition;
37  this->isInput = isInput;
38  this->portTypeMenusPopulated = false;
39 
40  setWindowTitle(isInput ? tr("Inputs") : tr("Outputs"));
42 
43  setFocusPolicy(Qt::ClickFocus);
44  setAcceptDrops(true);
45 
46  widget()->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored);
47  widget()->setMinimumWidth(100);
48  widget()->setMaximumWidth(100);
49 
50  ui->newPublishedPortButton->setIcon(QIcon(":/Icons/new.svg"));
51 
52  menuAddPort = new QMenu(this);
53  menuAddPort->setSeparatorsCollapsible(false);
54  menuAddPort->setTitle(tr("New Port"));
55  ui->newPublishedPortButton->setMenu(menuAddPort);
56  connect(menuAddPort, &QMenu::aboutToShow, this, &VuoPublishedPortSidebar::populatePortTypeMenus);
57 
58 #ifdef __APPLE__
59  // Disable standard OS X focus 'glow', since it looks bad when the contents margins are so narrow.
60  ui->publishedPortList->setAttribute(Qt::WA_MacShowFocusRect, false);
61 #endif
62 
63  contextMenuPortOptions = new QMenu(this);
64  contextMenuPortOptions->setSeparatorsCollapsible(false);
65 
66  menuChangeProtocol = new QMenu(contextMenuPortOptions);
67  menuChangeProtocol->setSeparatorsCollapsible(false);
68  menuChangeProtocol->setTitle(tr("Change Protocol"));
69  if (enableProtocolChanges)
70  contextMenuPortOptions->addMenu(menuChangeProtocol);
71 
72  contextMenuRemoveProtocol = new QMenu(this);
73  contextMenuRemoveProtocol->setSeparatorsCollapsible(false);
74  contextMenuActionRemoveProtocol = new QAction(tr("Remove Protocol"), NULL);
75 
76  if (enableProtocolChanges)
77  contextMenuRemoveProtocol->addAction(contextMenuActionRemoveProtocol);
78 
81  connect(ui->publishedPortList, &VuoPublishedPortList::publishedPortDetailsEditorRequested, this, &VuoPublishedPortSidebar::showPublishedPortDetailsEditor);
82  connect(ui->publishedPortList, &VuoPublishedPortList::inputEditorRequested, this, &VuoPublishedPortSidebar::inputEditorRequested, Qt::QueuedConnection);
87 
88  connect(this, &VuoPublishedPortSidebar::visibilityChanged, ui->publishedPortList, &VuoPublishedPortList::setVisible);
89 
90  ui->publishedPortList->setItemDelegate(new VuoPublishedPortListItemDelegate(composition, this));
91  ui->publishedPortList->setInput(isInput);
92  ui->publishedPortList->setComposition(composition);
94 
95  setMouseTracking(true);
96 
97  VuoEditor *editor = (VuoEditor *)qApp;
98  connect(editor, &VuoEditor::darkInterfaceToggled, this, &VuoPublishedPortSidebar::updateColor);
99  connect(editor, SIGNAL(darkInterfaceToggled(bool)), ui->publishedPortList, SLOT(repaint()));
100  updateColor(editor->isInterfaceDark());
101 #ifdef VUO_PRO
102  VuoPublishedPortSidebar_Pro();
103 #endif
104 }
105 
110 {
111  // Remember which ports were previously selected, so that we don't reset the selection whenever we update the port list.
112  set<QString> previouslySelectedPorts;
113  QList<QListWidgetItem *> previouslySelectedPortListItems = ui->publishedPortList->selectedItems();
114  for (QList<QListWidgetItem *>::iterator portListItem = previouslySelectedPortListItems.begin(); portListItem != previouslySelectedPortListItems.end(); ++portListItem)
115  previouslySelectedPorts.insert(((*portListItem)->data(Qt::DisplayRole).value<QString>()));
116 
117  ui->publishedPortList->clear();
118 
119  vector<VuoPublishedPort *> sortedPublishedPorts = composition->getBase()->getProtocolAwarePublishedPortOrder(composition->getActiveProtocol(),
120  ui->publishedPortList->getInput());
121  foreach (VuoPublishedPort *publishedPort, sortedPublishedPorts)
122  {
123  bool portWasSelected = previouslySelectedPorts.find(publishedPort->getClass()->getName().c_str()) != previouslySelectedPorts.end();
124  appendPublishedPortToList(publishedPort, portWasSelected);
125  }
126 
127  // @todo: Fix this to re-enable vertical scrolling.
128  ui->publishedPortList->setFixedHeight(ui->publishedPortList->sizeHint().height());
129  ui->publishedPortList->update();
130  ui->publishedPortList->updatePublishedPortLocs();
131 
132  update();
133 }
134 
139 void VuoPublishedPortSidebar::appendPublishedPortToList(VuoPublishedPort *port, bool select)
140 {
141  string publishedPortName = port->getClass()->getName();
142  VuoType *type = static_cast<VuoCompilerPortClass *>(port->getClass()->getCompiler())->getDataVuoType();
143  string publishedPortType = VuoStringUtilities::transcodeToGraphvizIdentifier(composition->formatTypeNameForDisplay(type).toUtf8().constData());
144  QListWidgetItem *item = new QListWidgetItem(ui->publishedPortList);
145 
146  item->setData(Qt::DisplayRole, publishedPortName.c_str()); // used for sort order
147  item->setData(VuoPublishedPortList::publishedPortPointerIndex, qVariantFromValue((void *)port->getRenderer()));
148 
149  QString formattedPublishedPortName = QString("<b><font size=+2>%1</font></b>").arg(publishedPortName.c_str());
150 
151  //: Appears in the tooltip on published input and output ports.
152  QString formattedPublishedPortDataType = "<font size=+1 color=\"gray\">" + tr("Data type") + ": <b>" + QString::fromStdString(publishedPortType) + "</b></font>";
153 
154  QString description;
155  {
156  string protocol = composition->getActiveProtocol() ? composition->getActiveProtocol()->getId() : "";
157  bool filter = protocol == VuoProtocol::imageFilter;
158  bool generator = protocol == VuoProtocol::imageGenerator;
159  bool transition = protocol == VuoProtocol::imageTransition;
160 
161  // Keep these descriptions in sync with VuoManual.txt's "Making compositions fit a mold with protocols" section.
162  // Protocol published inputs:
163  if (publishedPortName == "time")
164  {
165  if (filter || generator || transition)
166  description = tr("A number that changes over time, used to control animations or other changing effects.");
167  if (transition)
168  description += " " + tr("<code>time</code> is independent of <code>progress</code>.");
169  }
170  else if (publishedPortName == "image")
171  {
172  if (filter || generator || transition)
173  description = tr("The image to be filtered.");
174  }
175  else if (publishedPortName == "width")
176  {
177  if (generator)
178  description = tr("The requested width of the image, in pixels.");
179  }
180  else if (publishedPortName == "height")
181  {
182  if (generator)
183  description = tr("The requested height of the image, in pixels.");
184  }
185  else if (publishedPortName == "startImage")
186  {
187  if (transition)
188  description = tr("The image to transition from.");
189  }
190  else if (publishedPortName == "endImage")
191  {
192  if (transition)
193  description = tr("The image to transition to.");
194  }
195  else if (publishedPortName == "progress")
196  {
197  if (transition)
198  description = tr("A number from 0 to 1 for how far the transition has progressed. At 0, the transition is at the beginning, with only <code>startImage</code> showing. At 0.5, the transition is halfway through. At 1, the transition is complete, with only <code>endImage</code> showing. When previewing the composition in Vuo, the mouse position left to right controls <code>progress</code>.");
199  }
200 
201  // Protocol published outputs:
202  else if (publishedPortName == "outputImage")
203  {
204  if (filter)
205  description = tr("The altered image.");
206  else if (generator)
207  description = tr("The created image. Its width and height should match the <code>width</code> and <code>height</code> published input ports.");
208  else if (transition)
209  description = tr("The resulting image.");
210  }
211 
212  // Export-format-specific published inputs:
213  else if (publishedPortName == "offlineRender")
214  {
215  if (generator)
216  description = tr("For movie export: <code>true</code> if the composition is being exported to a movie and <code>false</code> otherwise.");
217  }
218  else if (publishedPortName == "motionBlur")
219  {
220  if (generator)
221  description = tr("For movie export: The number of frames rendered per output frame. 1 means motion blur is disabled; 2, 4, 8, 16, 32, or 64 means motion blur is enabled.");
222  }
223  else if (publishedPortName == "duration")
224  {
225  if (filter)
226  description = tr("For FxPlug: The length, in seconds, of the clip to be filtered.");
227  else if (generator)
228  description = tr("For movie export and FxPlug: The length, in seconds, of the movie/clip.");
229  else if (transition)
230  description = tr("For FxPlug: The length, in seconds, of the transition.");
231  }
232  else if (publishedPortName == "framerate")
233  {
234  if (filter || transition)
235  description = tr("For FxPlug: The framerate of the project, in frames per second.");
236  else if (generator)
237  description = tr("For movie export and FxPlug: The framerate of the movie/project, in frames per second.");
238  }
239  else if (publishedPortName == "frameNumber")
240  {
241  if (filter)
242  description = tr("For FxPlug: The number of frames since the beginning of the clip, starting at 0.");
243  else if (generator)
244  description = tr("For movie export and FxPlug: The number of frames since the beginning of the movie/clip, starting at 0.");
245  else if (transition)
246  description = tr("For FxPlug: The number of frames since the beginning of the transition, starting at 0.");
247  }
248  else if (publishedPortName == "quality")
249  {
250  if (filter || generator || transition)
251  description = tr("For FxPlug: The rendering quality or level of detail.");
252  }
253  else if (publishedPortName == "screen")
254  {
255  if (generator)
256  description = tr("For screen savers: Which display the screen saver is running on. (macOS runs a separate instance of the composition on each display.)");
257  }
258  else if (publishedPortName == "preview")
259  {
260  if (generator)
261  description = tr("For screen savers: <code>true</code> when the screen saver is running in the System Preferences preview thumbnail.");
262  }
263 
264  if (!description.isEmpty())
265  description = "<p><font size=+1>" + description + "</font></p>";
266  }
267 
268  item->setToolTip(formattedPublishedPortName.append("<BR>")
269  .append(formattedPublishedPortDataType)
270  .append(description));
271 
272  // Disable interaction with published ports that are part of an active protocol.
273  if (port->isProtocolPort())
274  item->setFlags(item->flags() &~ Qt::ItemIsEnabled);
275 
276  ui->publishedPortList->addItem(item);
277  item->setSelected(select);
278 }
279 
288 void VuoPublishedPortSidebar::highlightEligibleDropLocations(VuoRendererPort *internalFixedPort, bool eventOnlyConnection)
289 {
290  auto types = composition->getCompiler()->getTypes();
291 
292  int numPorts = ui->publishedPortList->count();
293  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
294  {
295  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
296  VuoRendererPublishedPort *publishedPortToHighlight = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
297 
298  QGraphicsItem::CacheMode normalCacheMode = publishedPortToHighlight->cacheMode();
299  publishedPortToHighlight->setCacheMode(QGraphicsItem::NoCache);
300  publishedPortToHighlight->updateGeometry();
301 
302  VuoRendererColors::HighlightType highlight = composition->getEligibilityHighlightingForPort(publishedPortToHighlight, internalFixedPort, eventOnlyConnection, types);
303  publishedPortToHighlight->setEligibilityHighlight(highlight);
304 
305  publishedPortToHighlight->setCacheMode(normalCacheMode);
306  }
307 
308  bool publishedPortWellShouldAcceptDrops = (internalFixedPort && canListPublishedPortAliasFor(internalFixedPort) && internalFixedPort->getPublishable());
309  ui->publishedPortDropBox->setCurrentlyAcceptingDrops(publishedPortWellShouldAcceptDrops);
310  ui->publishedPortDropBox->update();
311 
312  if (!isHidden())
313  {
314  ui->publishedPortList->setFillVerticalSpace(!publishedPortWellShouldAcceptDrops);
315  ui->publishedPortList->viewport()->update();
316  }
317 }
318 
323 {
324  int numPorts = ui->publishedPortList->count();
325  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
326  {
327  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
328  VuoRendererPublishedPort *publishedPort = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
329 
330  QGraphicsItem::CacheMode normalCacheMode = publishedPort->cacheMode();
331  publishedPort->setCacheMode(QGraphicsItem::NoCache);
332  publishedPort->updateGeometry();
334  publishedPort->setCacheMode(normalCacheMode);
335  }
336 
337  ui->publishedPortDropBox->setCurrentlyAcceptingDrops(false);
338  ui->publishedPortDropBox->update();
339 
340  if (!isHidden())
341  {
342  ui->publishedPortList->setFillVerticalSpace(true);
343  ui->publishedPortList->viewport()->update();
344  }
345 }
346 
351 {
352  // Display the name of the active protocol, if any.
353  if (composition->getActiveProtocol())
354  {
355  ui->activeProtocolLabel->setStyleSheet(VUO_QSTRINGIFY(
356  QLabel {
357  background-color: %1;
358  color: rgba(255,255,255,224);
359  }
360  )
361  .arg(getActiveProtocolHeadingColor(0, !isInput).name())
362  );
363 
364  ui->activeProtocolLabel->setText(VuoEditor::tr(composition->getActiveProtocol()->getName().c_str()));
365  ui->activeProtocolLabel->setHidden(false);
366  }
367 
368  else
369  {
370  ui->activeProtocolLabel->setText("");
371  ui->activeProtocolLabel->setHidden(true);
372  }
373 }
374 
379 {
380  return menuChangeProtocol;
381 }
382 
387 {
388  return contextMenuActionRemoveProtocol;
389 }
390 
391 
400 void VuoPublishedPortSidebar::updateHoverHighlighting(QMouseEvent *event, qreal tolerance)
401 {
402  VuoCable *cableInProgress = composition->getCableInProgress();
403  VuoRendererPublishedPort *publishedPortUnderCursor = getPublishedPortUnderCursorForEvent(event, tolerance, !cableInProgress);
404 
405  int numPorts = ui->publishedPortList->count();
406  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
407  {
408  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
409  VuoRendererPublishedPort *publishedPort = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
410 
411  if (publishedPort == publishedPortUnderCursor)
412  publishedPort->extendedHoverMoveEvent(cableInProgress);
413  else
414  publishedPort->extendedHoverLeaveEvent();
415 
416  if (cableInProgress)
417  cableInProgress->getRenderer()->setFloatingEndpointAboveEventPort(publishedPortUnderCursor && !publishedPortUnderCursor->getDataType());
418  }
419 
420  ui->publishedPortDropBox->setHovered(isPublishedPortDropBoxUnderCursorForEvent(event));
421 
422  if (!isHidden())
423  ui->publishedPortList->viewport()->update();
424 }
425 
434 {
435  int numPorts = ui->publishedPortList->count();
436  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
437  {
438  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
439  VuoRendererPublishedPort *publishedPort = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
440  publishedPort->extendedHoverLeaveEvent();
441  }
442 
443  ui->publishedPortDropBox->setHovered(false);
444 
445  if (!isHidden())
446  ui->publishedPortList->viewport()->update();
447 }
448 
458 void VuoPublishedPortSidebar::concludePublishedCableDrag(QMouseEvent *event, VuoCable *cableInProgress, bool cableInProgressWasNew)
459 {
460  VuoRendererPort *fixedPort = NULL;
461  if (cableInProgress->getFromNode() && cableInProgress->getFromPort())
462  fixedPort = cableInProgress->getFromPort()->getRenderer();
463  else if (cableInProgress->getToNode() && cableInProgress->getToPort())
464  fixedPort = cableInProgress->getToPort()->getRenderer();
465 
466  if (!fixedPort)
467  return;
468 
469  // Potential side effects of a newly completed cable connection:
470  // - A typecast may need to be automatically inserted.
471  string typecastToInsert = "";
472 
473  // - A generic port involved in the new connection may need to be specialized.
474  VuoRendererPort *portToSpecialize = NULL;
475  string specializedTypeName = "";
476 
477  // Whether the cursor is hovered over the drop box must be determined before the cable drag is cancelled,
478  // because cancelling the cable drag hides the drop box.
479  bool isCursorOverPublishedPortDropBox = isPublishedPortDropBoxUnderCursorForEvent(event);
480  bool forceEventOnlyPublication = !cableInProgress->getRenderer()->effectivelyCarriesData();
481 
482  if (isCursorOverPublishedPortDropBox && canListPublishedPortAliasFor(fixedPort))
483  {
484  emit undoStackMacroBeginRequested("Cable Connection");
485  composition->cancelCableDrag();
487  forceEventOnlyPublication,
488  false); // Avoid nested Undo stack macros.
490  }
491 
492  else
493  {
495  if (publishedPort)
496  {
497  bool isPublishedInput = !publishedPort->getInput();
498  bool cableInProgressExpectedToCarryData = (cableInProgress->getRenderer()->effectivelyCarriesData() &&
499  publishedPort->getDataType());
500 
501  // @todo: Don't assume that the cable is connected on the other end to an internal port (https://b33p.net/node/7756).
502  if ((publishedPort->isCompatibleAliasWithSpecializationForInternalPort(fixedPort, forceEventOnlyPublication, &portToSpecialize, specializedTypeName))
503  || (isPublishedInput? composition->selectBridgingSolution(publishedPort, fixedPort, false, &portToSpecialize, specializedTypeName, typecastToInsert) :
504  composition->selectBridgingSolution(fixedPort, publishedPort, true, &portToSpecialize, specializedTypeName, typecastToInsert)))
505  {
506  // If the port is a published output port and already had a connected data+event cable, displace it.
507  VuoCable *dataCableToDisplace = NULL;
508  if (!forceEventOnlyPublication && !ui->publishedPortList->getInput())
509  {
510  vector<VuoCable *> previousConnectedCables = publishedPort->getBase()->getConnectedCables(true);
511  for (vector<VuoCable *>::iterator cable = previousConnectedCables.begin(); (! dataCableToDisplace) && (cable != previousConnectedCables.end()); ++cable)
512  if ((*cable)->getRenderer()->effectivelyCarriesData())
513  dataCableToDisplace = *cable;
514  }
515 
516  // If replacing a preexisting cable with an identical cable, just cancel the operation
517  // so that it doesn't go onto the Undo stack.
518  VuoCable *cableToReplace = fixedPort->getCableConnectedTo(publishedPort, true);
519  bool cableToReplaceHasMatchingDataCarryingStatus = (cableToReplace?
520  (cableToReplace->getRenderer()->effectivelyCarriesData() ==
521  cableInProgressExpectedToCarryData) :
522  false);
523  if (cableToReplace && cableToReplaceHasMatchingDataCarryingStatus)
524  {
525  composition->cancelCableDrag();
526  return;
527  }
528 
529  // If the user has simply disconnected a cable and reconnected it within the same mouse drag,
530  // don't push the operation onto the Undo stack.
531  bool recreatingSameConnection = ((cableInProgress->getRenderer()->getFloatingEndpointPreviousToPort() ==
532  publishedPort->getBase()) &&
533  (cableInProgress->getRenderer()->getPreviouslyAlwaysEventOnly() ==
534  cableInProgress->getCompiler()->getAlwaysEventOnly()));
535  if (recreatingSameConnection)
536  {
537  composition->revertCableDrag();
538  return;
539  }
540 
541  emit undoStackMacroBeginRequested("Cable Connection");
542  composition->cancelCableDrag();
543 
544  if (dataCableToDisplace)
545  {
546  QList<QGraphicsItem *> removedComponents;
547  removedComponents.append(dataCableToDisplace->getRenderer());
548  emit componentsRemoved(removedComponents, "Delete");
549  }
550 
551  // If this source/target port combination already a cable connecting them, but of a different
552  // data-carrying status, replace the old cable with the new one.
553  if (cableToReplace && !cableToReplaceHasMatchingDataCarryingStatus)
554  {
555  QList<QGraphicsItem *> removedComponents;
556  removedComponents.append(cableToReplace->getRenderer());
557  emit componentsRemoved(removedComponents, "Delete");
558  }
559 
561  dynamic_cast<VuoPublishedPort *>(publishedPort->getBase()),
562  forceEventOnlyPublication,
563  (portToSpecialize? portToSpecialize->getBase() : NULL),
564  specializedTypeName,
565  typecastToInsert,
566  false); // Avoid nested Undo stack macros.
568  }
569  }
570  else // Cable was dropped over empty space within the sidebar
571  composition->cancelCableDrag();
572  }
573 }
574 
578 void VuoPublishedPortSidebar::contextMenuEvent(QContextMenuEvent *event)
579 {
580  // @todo: Account for multiple simultaneous active protocols. https://b33p.net/kosada/node/9585
581  if (isActiveProtocolLabelUnderCursorForEvent(event))
582  {
583  contextMenuActionRemoveProtocol->setData(qVariantFromValue((void *)composition->getActiveProtocol()));
584  contextMenuRemoveProtocol->exec(event->globalPos());
585  }
586  else
587  {
588  contextMenuPortOptions->addMenu(menuAddPort);
589  contextMenuPortOptions->exec(event->globalPos());
590  contextMenuPortOptions->removeAction(menuAddPort->menuAction());
591  }
592 }
593 
598 {
599  // Refrain from hover-highlighting published ports while the cursor is within the sidebar.
601 }
602 
607 {
608  ui->publishedPortList->clearSelection();
609 
610  // Calling QDockWidget::mousePressEvent() here for some reason
611  // increases the width of the sidebar, so don't call it.
612 }
613 
620 {
621  ui->publishedPortList->updatePublishedPortLocs();
622 }
623 
627 void VuoPublishedPortSidebar::dragEnterEvent(QDragEnterEvent *event)
628 {
629  if (event->source() == ui->publishedPortList)
630  event->accept();
631  else
632  event->ignore();
633 }
634 
638 void VuoPublishedPortSidebar::dropEvent(QDropEvent *event)
639 {
640  QDropEvent modifiedEvent(QPoint(10,10),
641  event->dropAction(),
642  event->mimeData(),
643  event->mouseButtons(),
644  event->keyboardModifiers());
645  ui->publishedPortList->adoptDropEvent(&modifiedEvent);
646  return;
647 }
648 
652 void VuoPublishedPortSidebar::closeEvent(QCloseEvent *event)
653 {
654  emit closed();
655  QDockWidget::closeEvent(event);
656 }
657 
662 bool VuoPublishedPortSidebar::canListPublishedPortAliasFor(VuoRendererPort *port)
663 {
664  return ((ui->publishedPortList->getInput() && port->getInput()) || ((! ui->publishedPortList->getInput()) && port->getOutput()));
665 }
666 
671 bool VuoPublishedPortSidebar::isPublishedPortDropBoxUnderCursorForEvent(QMouseEvent *event)
672 {
673  QPoint cursorPosition = ((QMouseEvent *)(event))->globalPos();
674 
675  QRect publishedPortDropBoxRect = ui->publishedPortDropBox->geometry();
676  publishedPortDropBoxRect.moveTopLeft(ui->publishedPortDropBox->parentWidget()->mapToGlobal(publishedPortDropBoxRect.topLeft()));
677 
678  bool dropBoxUnderCursor = (!ui->publishedPortDropBox->isHidden() && publishedPortDropBoxRect.contains(cursorPosition));
679  return dropBoxUnderCursor;
680 }
681 
686 bool VuoPublishedPortSidebar::isActiveProtocolLabelUnderCursorForEvent(QContextMenuEvent *event)
687 {
688  QPoint cursorPosition = ((QContextMenuEvent *)(event))->globalPos();
689 
690  QRect activeProtocolLabelRect = ui->activeProtocolLabel->geometry();
691  activeProtocolLabelRect.moveTopLeft(ui->activeProtocolLabel->parentWidget()->mapToGlobal(activeProtocolLabelRect.topLeft()));
692 
693  bool activeProtocolLabelUnderCursor = (!ui->activeProtocolLabel->isHidden() && activeProtocolLabelRect.contains(cursorPosition));
694  return activeProtocolLabelUnderCursor;
695 }
696 
701 VuoRendererPublishedPort * VuoPublishedPortSidebar::getPublishedPortUnderCursorForEvent(QMouseEvent *event, qreal tolerance, bool limitPortCollisionRange)
702 {
703  QPoint cursorPosition = ((QMouseEvent *)(event))->globalPos();
704  return ui->publishedPortList->getPublishedPortAtGlobalPos(cursorPosition, tolerance, limitPortCollisionRange);
705 }
706 
711 {
712  return ui->publishedPortList->getGlobalPosOfPublishedPort(port);
713 }
714 
719 {
720  return ui->publishedPortList->getMenuSelectionInProgress();
721 }
722 
727 QColor VuoPublishedPortSidebar::getActiveProtocolHeadingColor(int protocolIndex, bool isInput)
728 {
729  VuoRendererColors activeProtocolColor(VuoRendererColors::getActiveProtocolTint(protocolIndex, isInput),
730  VuoRendererColors::noSelection,
731  false,
733  return activeProtocolColor.nodeFrame();
734 }
735 
740 QColor VuoPublishedPortSidebar::getActiveProtocolPortColor(int protocolIndex, bool isInput)
741 {
742  VuoRendererColors activeProtocolColor(VuoRendererColors::getActiveProtocolTint(protocolIndex, isInput),
743  VuoRendererColors::noSelection,
744  false,
746  return activeProtocolColor.nodeFill();
747 }
748 
755 {
756  VuoTitleEditor *nameEditor = new VuoPublishedPortNameEditor();
757  string originalValue = port->getBase()->getClass()->getName();
758 
759  // Position the input editor overtop the published port's list widget item.
760  QPoint portLeftCenterGlobal = getGlobalPosOfPublishedPort(port);
761  int yCenter = portLeftCenterGlobal.y();
762  int xRight = parentWidget()->mapToGlobal( QPoint(x() + width(), 0) ).x();
763  QPoint publishedPortRightCenterGlobal = QPoint(xRight, yCenter);
764 
765  // Adjust the input editor's position and width to keep its focus glow from extending beyond the sidebar boundaries.
766  const int focusGlowWidth = 2;
767  publishedPortRightCenterGlobal = publishedPortRightCenterGlobal - QPoint(focusGlowWidth, 0);
768  nameEditor->setWidth(width() - 2*focusGlowWidth);
769 
770  json_object *details = json_object_new_object();
771  json_object_object_add(details, "isDark", json_object_new_boolean(static_cast<VuoEditor *>(qApp)->isInterfaceDark()));
772 
773  json_object *originalValueAsJson = json_object_new_string(originalValue.c_str());
774  json_object *newValueAsJson = nameEditor->show(publishedPortRightCenterGlobal, originalValueAsJson, details);
775  string newValue = json_object_get_string(newValueAsJson);
776  json_object_put(originalValueAsJson);
777  json_object_put(newValueAsJson);
778 
779  return newValue;
780 }
781 
786 void VuoPublishedPortSidebar::showPublishedPortDetailsEditor(VuoRendererPublishedPort *port)
787 {
788  VuoPublishedPort *publishedPort = dynamic_cast<VuoPublishedPort *>(port->getBase());
789  VuoType *type = port->getDataType();
790  if (publishedPort && type &&
791  ((type->getModuleKey() == "VuoReal") ||
792  (type->getModuleKey() == "VuoInteger") ||
793  (type->getModuleKey() == "VuoPoint2d") ||
794  (type->getModuleKey() == "VuoPoint3d") ||
795  (type->getModuleKey() == "VuoPoint4d")))
796  {
797  QPoint portLeftCenterGlobal = getGlobalPosOfPublishedPort(port);
798 
799  bool isPublishedInput = !port->getInput();
800  json_object *originalDetails = static_cast<VuoCompilerPublishedPort *>(publishedPort->getCompiler())->getDetails(isPublishedInput);
801 
802  composition->disableNondetachedPortPopovers();
803 
804  if (!originalDetails || json_object_get_type(originalDetails) != json_type_object)
805  originalDetails = json_object_new_object();
806  VuoEditor *editor = static_cast<VuoEditor *>(qApp);
807  json_object_object_add(originalDetails, "isDark", json_object_new_boolean(editor->isInterfaceDark()));
808 
809  json_object *newDetails = NULL;
810  if ((type->getModuleKey() == "VuoReal") || (type->getModuleKey() == "VuoInteger"))
811  {
812  VuoDetailsEditorNumeric *numericDetailsEditor = new VuoDetailsEditorNumeric(type);
813  newDetails = numericDetailsEditor->show(portLeftCenterGlobal, originalDetails);
814  numericDetailsEditor->deleteLater();
815  }
816  else if ((type->getModuleKey() == "VuoPoint2d") ||
817  (type->getModuleKey() == "VuoPoint3d") ||
818  (type->getModuleKey() == "VuoPoint4d"))
819  {
820  VuoDetailsEditorPoint *pointDetailsEditor = new VuoDetailsEditorPoint(type);
821  newDetails = pointDetailsEditor->show(portLeftCenterGlobal, originalDetails);
822  pointDetailsEditor->deleteLater();
823  }
824 
825  // Check whether any of the editable values have been changed before creating an Undo stack command.
826  string originalSuggestedMin, newSuggestedMin, originalSuggestedMax, newSuggestedMax, originalSuggestedStep, newSuggestedStep = "";
827 
828  // suggestedMin
829  json_object *originalSuggestedMinValue = NULL;
830  if (json_object_object_get_ex(originalDetails, "suggestedMin", &originalSuggestedMinValue))
831  originalSuggestedMin = json_object_to_json_string(originalSuggestedMinValue);
832 
833  json_object *newSuggestedMinValue = NULL;
834  if (json_object_object_get_ex(newDetails, "suggestedMin", &newSuggestedMinValue))
835  newSuggestedMin = json_object_to_json_string(newSuggestedMinValue);
836  else
837  json_object_object_add(newDetails, "suggestedMin", NULL);
838 
839  // suggestedMax
840  json_object *originalSuggestedMaxValue = NULL;
841  if (json_object_object_get_ex(originalDetails, "suggestedMax", &originalSuggestedMaxValue))
842  originalSuggestedMax = json_object_to_json_string(originalSuggestedMaxValue);
843 
844  json_object *newSuggestedMaxValue = NULL;
845  if (json_object_object_get_ex(newDetails, "suggestedMax", &newSuggestedMaxValue))
846  newSuggestedMax = json_object_to_json_string(newSuggestedMaxValue);
847  else
848  json_object_object_add(newDetails, "suggestedMax", NULL);
849 
850  // suggestedStep
851  json_object *originalSuggestedStepValue = NULL;
852  if (json_object_object_get_ex(originalDetails, "suggestedStep", &originalSuggestedStepValue))
853  originalSuggestedStep = json_object_to_json_string(originalSuggestedStepValue);
854 
855  json_object *newSuggestedStepValue = NULL;
856  if (json_object_object_get_ex(newDetails, "suggestedStep", &newSuggestedStepValue))
857  newSuggestedStep = json_object_to_json_string(newSuggestedStepValue);
858  else
859  json_object_object_add(newDetails, "suggestedStep", NULL);
860 
861  if ((originalSuggestedMin != newSuggestedMin) ||
862  (originalSuggestedMax != newSuggestedMax) ||
863  (originalSuggestedStep != newSuggestedStep))
864  {
865  emit publishedPortDetailsChangeRequested(port, newDetails);
866  }
867 
868  composition->disableNondetachedPortPopovers();
869  }
870 }
871 
875 void VuoPublishedPortSidebar::newPublishedPortTypeSelected()
876 {
877  QAction *sender = (QAction *)QObject::sender();
878  QList<QVariant> actionData = sender->data().toList();
879  QString typeName = actionData[1].toString();
880 
881  emit newPublishedPortRequested(typeName.toUtf8().constData(), isInput);
882 }
883 
888 void VuoPublishedPortSidebar::populatePortTypeMenus()
889 {
890  if (this->portTypeMenusPopulated)
891  return;
892 
893  menuAddPort->clear();
894 
895  // Event-only option
896  QAction *addEventOnlyPortAction = menuAddPort->addAction(tr("Event-Only"));
897  string typeName = "";
898  QList<QVariant> portAndSpecializedType;
899  portAndSpecializedType.append(qVariantFromValue((void *)NULL));
900  portAndSpecializedType.append(typeName.c_str());
901  addEventOnlyPortAction->setData(QVariant(portAndSpecializedType));
902 
903  // Non-list type submenu
904  QMenu *menuNonListType = new QMenu(menuAddPort);
905  menuNonListType->setSeparatorsCollapsible(false);
906  menuNonListType->setTitle(tr("Single Value"));
907  menuNonListType->setToolTipsVisible(true);
908  menuAddPort->addMenu(menuNonListType);
909 
910  // List-type submenu
911  QMenu *menuListType = new QMenu(menuAddPort);
912  menuListType->setSeparatorsCollapsible(false);
913  menuListType->setTitle(tr("List"));
914  menuListType->setToolTipsVisible(true);
915  menuAddPort->addMenu(menuListType);
916 
917  // Non-list type options
918  {
919  vector<string> singleValueTypes = composition->getAllSpecializedTypeOptions(false);
920 
921  set<string> types;
922  if (allowedPortTypes.empty())
923  types.insert(singleValueTypes.begin(), singleValueTypes.end());
924  else
925  {
926  vector<string> sortedAllowedPortTypes(allowedPortTypes.begin(), allowedPortTypes.end());
927  std::sort(sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end());
928  std::sort(singleValueTypes.begin(), singleValueTypes.end());
929 
930  std::set_intersection(singleValueTypes.begin(), singleValueTypes.end(),
931  sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end(),
932  std::inserter(types, types.begin()));
933  }
934 
935  // First list compatible types that don't belong to any specific node set.
936  QList<QAction *> actions = composition->getCompatibleTypesForMenu(NULL, types, types, true, "", menuNonListType);
937  composition->addTypeActionsToMenu(actions, menuNonListType);
938 
939  // Now add a submenu for each node set that contains compatible types.
941  map<string, set<VuoCompilerType *> > loadedTypesForNodeSet = composition->getModuleManager()->getLoadedTypesForNodeSet();
942  QList<QAction *> allNodeSetActionsToAdd;
943  for (map<string, set<VuoCompilerType *> >::iterator i = loadedTypesForNodeSet.begin(); i != loadedTypesForNodeSet.end(); ++i)
944  {
945  string nodeSetName = i->first;
946  if (!nodeSetName.empty())
947  allNodeSetActionsToAdd += composition->getCompatibleTypesForMenu(NULL, types, types, true, nodeSetName, menuNonListType);
948  }
949 
950  if ((menuNonListType->actions().size() > 0) && (allNodeSetActionsToAdd.size() > 0))
951  menuNonListType->addSeparator();
952 
953  composition->addTypeActionsToMenu(allNodeSetActionsToAdd, menuNonListType);
954  }
955 
956  // List type options
957  {
958  vector<string> listTypes = composition->getAllSpecializedTypeOptions(true);
959 
960  set<string> types;
961  if (allowedPortTypes.empty())
962  types.insert(listTypes.begin(), listTypes.end());
963  else
964  {
965  vector<string> sortedAllowedPortTypes(allowedPortTypes.begin(), allowedPortTypes.end());
966  std::sort(sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end());
967  std::sort(listTypes.begin(), listTypes.end());
968 
969  std::set_intersection(listTypes.begin(), listTypes.end(),
970  sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end(),
971  std::inserter(types, types.begin()));
972  }
973 
974  // First list compatible types that don't belong to any specific node set.
975  QList<QAction *> actions = composition->getCompatibleTypesForMenu(NULL, types, types, true, "", menuListType);
976  composition->addTypeActionsToMenu(actions, menuListType);
977 
978  // Now add a submenu for each node set that contains compatible types.
980  map<string, set<VuoCompilerType *> > loadedTypesForNodeSet = composition->getModuleManager()->getLoadedTypesForNodeSet();
981  QList<QAction *> allNodeSetActionsToAdd;
982  for (map<string, set<VuoCompilerType *> >::iterator i = loadedTypesForNodeSet.begin(); i != loadedTypesForNodeSet.end(); ++i)
983  {
984  string nodeSetName = i->first;
985  if (!nodeSetName.empty())
986  allNodeSetActionsToAdd += composition->getCompatibleTypesForMenu(NULL, types, types, true, nodeSetName, menuListType);
987  }
988 
989  if ((menuListType->actions().size() > 0) && (allNodeSetActionsToAdd.size() > 0))
990  menuListType->addSeparator();
991 
992  composition->addTypeActionsToMenu(allNodeSetActionsToAdd, menuListType);
993  }
994 
995  // Set up context menu item connections.
996  connect(addEventOnlyPortAction, &QAction::triggered, this, &VuoPublishedPortSidebar::newPublishedPortTypeSelected);
997  addEventOnlyPortAction->setCheckable(false);
998 
999  foreach (QAction *setTypeAction, menuAddPort->actions())
1000  {
1001  QMenu *setTypeSubmenu = setTypeAction->menu();
1002  if (setTypeSubmenu)
1003  {
1004  foreach (QAction *setTypeSubaction, setTypeSubmenu->actions())
1005  {
1006  QMenu *setTypeSubmenu = setTypeSubaction->menu();
1007  if (setTypeSubmenu)
1008  {
1009  foreach (QAction *setTypeSubSubaction, setTypeSubmenu->actions())
1010  {
1011  connect(setTypeSubSubaction, &QAction::triggered, this, &VuoPublishedPortSidebar::newPublishedPortTypeSelected);
1012  setTypeSubSubaction->setCheckable(false);
1013  }
1014  }
1015  else
1016  {
1017  connect(setTypeSubaction, &QAction::triggered, this, &VuoPublishedPortSidebar::newPublishedPortTypeSelected);
1018  setTypeSubaction->setCheckable(false);
1019  }
1020  }
1021  }
1022  }
1023 
1024  this->portTypeMenusPopulated = true;
1025 }
1026 
1032 void VuoPublishedPortSidebar::limitAllowedPortTypes(const set<string> &allowedPortTypes)
1033 {
1034  this->allowedPortTypes = allowedPortTypes;
1035 }
1036 
1040 void VuoPublishedPortSidebar::updateColor(bool isDark)
1041 {
1042  QString titleTextColor = isDark ? "#303030" : "#808080";
1043  QString titleBackgroundColor = isDark ? "#919191" : "#efefef";
1044  QString dockwidgetBackgroundColor = isDark ? "#404040" : "#efefef";
1045  QString buttonTextColor = isDark ? "#a0a0a0" : "#707070";
1046 
1047  setStyleSheet(VUO_QSTRINGIFY(
1048  QDockWidget {
1049  titlebar-close-icon: url(:/Icons/dockwidget-close-%3.png);
1050  font-size: 11px;
1051  border: none;
1052  color: %1;
1053  }
1054  QDockWidget::title {
1055  text-align: left;
1056  padding-left: 6px;
1057  background-color: %2;
1058  }
1059  )
1060  .arg(titleTextColor)
1061  .arg(titleBackgroundColor)
1062  .arg(isDark ? "dark" : "light")
1063  );
1064 
1065  ui->VuoPublishedPortSidebarContents->setStyleSheet(VUO_QSTRINGIFY(
1066  QWidget#VuoPublishedPortSidebarContents {
1067  background-color: %1;
1068  }
1069  )
1070  .arg(dockwidgetBackgroundColor)
1071  );
1072 
1073  ui->publishedPortList->setStyleSheet(
1074  ui->publishedPortList->styleSheet().append(
1075  VUO_QSTRINGIFY(
1077  background-color: %1;
1078  }
1079  )
1080  .arg(dockwidgetBackgroundColor)
1081  )
1082  );
1083 
1084  ui->newPublishedPortButton->setStyleSheet(VUO_QSTRINGIFY(
1085  * {
1086  border-radius: 5px; // Rounded corners
1087  padding: 3px;
1088  margin: 5px 2px;
1089  color: %1;
1090  }
1091  QToolButton::menu-indicator {
1092  image: none;
1093  }
1094  ).arg(buttonTextColor));
1095 }