Vuo  2.0.2
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  int numPorts = ui->publishedPortList->count();
291  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
292  {
293  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
294  VuoRendererPublishedPort *publishedPortToHighlight = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
295 
296  QGraphicsItem::CacheMode normalCacheMode = publishedPortToHighlight->cacheMode();
297  publishedPortToHighlight->setCacheMode(QGraphicsItem::NoCache);
298  publishedPortToHighlight->updateGeometry();
299 
300  VuoRendererColors::HighlightType highlight = composition->getEligibilityHighlightingForPort(publishedPortToHighlight, internalFixedPort, eventOnlyConnection);
301  publishedPortToHighlight->setEligibilityHighlight(highlight);
302 
303  publishedPortToHighlight->setCacheMode(normalCacheMode);
304  }
305 
306  bool publishedPortWellShouldAcceptDrops = (internalFixedPort && canListPublishedPortAliasFor(internalFixedPort) && internalFixedPort->getPublishable());
307  ui->publishedPortDropBox->setCurrentlyAcceptingDrops(publishedPortWellShouldAcceptDrops);
308  ui->publishedPortDropBox->update();
309 
310  if (!isHidden())
311  {
312  ui->publishedPortList->setFillVerticalSpace(!publishedPortWellShouldAcceptDrops);
313  ui->publishedPortList->viewport()->update();
314  }
315 }
316 
321 {
322  int numPorts = ui->publishedPortList->count();
323  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
324  {
325  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
326  VuoRendererPublishedPort *publishedPort = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
327 
328  QGraphicsItem::CacheMode normalCacheMode = publishedPort->cacheMode();
329  publishedPort->setCacheMode(QGraphicsItem::NoCache);
330  publishedPort->updateGeometry();
332  publishedPort->setCacheMode(normalCacheMode);
333  }
334 
335  ui->publishedPortDropBox->setCurrentlyAcceptingDrops(false);
336  ui->publishedPortDropBox->update();
337 
338  if (!isHidden())
339  {
340  ui->publishedPortList->setFillVerticalSpace(true);
341  ui->publishedPortList->viewport()->update();
342  }
343 }
344 
349 {
350  // Display the name of the active protocol, if any.
351  if (composition->getActiveProtocol())
352  {
353  ui->activeProtocolLabel->setStyleSheet(VUO_QSTRINGIFY(
354  QLabel {
355  background-color: %1;
356  color: rgba(255,255,255,224);
357  }
358  )
359  .arg(getActiveProtocolHeadingColor(0, !isInput).name())
360  );
361 
362  ui->activeProtocolLabel->setText(VuoEditor::tr(composition->getActiveProtocol()->getName().c_str()));
363  ui->activeProtocolLabel->setHidden(false);
364  }
365 
366  else
367  {
368  ui->activeProtocolLabel->setText("");
369  ui->activeProtocolLabel->setHidden(true);
370  }
371 }
372 
377 {
378  return menuChangeProtocol;
379 }
380 
385 {
386  return contextMenuActionRemoveProtocol;
387 }
388 
389 
398 void VuoPublishedPortSidebar::updateHoverHighlighting(QMouseEvent *event, qreal tolerance)
399 {
400  VuoCable *cableInProgress = composition->getCableInProgress();
401  VuoRendererPublishedPort *publishedPortUnderCursor = getPublishedPortUnderCursorForEvent(event, tolerance, !cableInProgress);
402 
403  int numPorts = ui->publishedPortList->count();
404  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
405  {
406  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
407  VuoRendererPublishedPort *publishedPort = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
408 
409  if (publishedPort == publishedPortUnderCursor)
410  publishedPort->extendedHoverMoveEvent(cableInProgress);
411  else
412  publishedPort->extendedHoverLeaveEvent();
413 
414  if (cableInProgress)
415  cableInProgress->getRenderer()->setFloatingEndpointAboveEventPort(publishedPortUnderCursor && !publishedPortUnderCursor->getDataType());
416  }
417 
418  ui->publishedPortDropBox->setHovered(isPublishedPortDropBoxUnderCursorForEvent(event));
419 
420  if (!isHidden())
421  ui->publishedPortList->viewport()->update();
422 }
423 
432 {
433  int numPorts = ui->publishedPortList->count();
434  for (int portIndex = 0; portIndex < numPorts; ++portIndex)
435  {
436  QListWidgetItem *portItem = ui->publishedPortList->item(portIndex);
437  VuoRendererPublishedPort *publishedPort = static_cast<VuoRendererPublishedPort *>(portItem->data(VuoPublishedPortList::publishedPortPointerIndex).value<void *>());
438  publishedPort->extendedHoverLeaveEvent();
439  }
440 
441  ui->publishedPortDropBox->setHovered(false);
442 
443  if (!isHidden())
444  ui->publishedPortList->viewport()->update();
445 }
446 
456 void VuoPublishedPortSidebar::concludePublishedCableDrag(QMouseEvent *event, VuoCable *cableInProgress, bool cableInProgressWasNew)
457 {
458  VuoRendererPort *fixedPort = NULL;
459  if (cableInProgress->getFromNode() && cableInProgress->getFromPort())
460  fixedPort = cableInProgress->getFromPort()->getRenderer();
461  else if (cableInProgress->getToNode() && cableInProgress->getToPort())
462  fixedPort = cableInProgress->getToPort()->getRenderer();
463 
464  if (!fixedPort)
465  return;
466 
467  // Potential side effects of a newly completed cable connection:
468  // - A typecast may need to be automatically inserted.
469  string typecastToInsert = "";
470 
471  // - A generic port involved in the new connection may need to be specialized.
472  VuoRendererPort *portToSpecialize = NULL;
473  string specializedTypeName = "";
474 
475  // Whether the cursor is hovered over the drop box must be determined before the cable drag is cancelled,
476  // because cancelling the cable drag hides the drop box.
477  bool isCursorOverPublishedPortDropBox = isPublishedPortDropBoxUnderCursorForEvent(event);
478  bool forceEventOnlyPublication = !cableInProgress->getRenderer()->effectivelyCarriesData();
479 
480  if (isCursorOverPublishedPortDropBox && canListPublishedPortAliasFor(fixedPort))
481  {
482  emit undoStackMacroBeginRequested("Cable Connection");
483  composition->cancelCableDrag();
485  forceEventOnlyPublication,
486  false); // Avoid nested Undo stack macros.
488  }
489 
490  else
491  {
493  if (publishedPort)
494  {
495  bool isPublishedInput = !publishedPort->getInput();
496  bool cableInProgressExpectedToCarryData = (cableInProgress->getRenderer()->effectivelyCarriesData() &&
497  publishedPort->getDataType());
498 
499  // @todo: Don't assume that the cable is connected on the other end to an internal port (https://b33p.net/node/7756).
500  if ((publishedPort->isCompatibleAliasWithSpecializationForInternalPort(fixedPort, forceEventOnlyPublication, &portToSpecialize, specializedTypeName))
501  || (isPublishedInput? composition->selectBridgingSolution(publishedPort, fixedPort, false, &portToSpecialize, specializedTypeName, typecastToInsert) :
502  composition->selectBridgingSolution(fixedPort, publishedPort, true, &portToSpecialize, specializedTypeName, typecastToInsert)))
503  {
504  // If the port is a published output port and already had a connected data+event cable, displace it.
505  VuoCable *dataCableToDisplace = NULL;
506  if (!forceEventOnlyPublication && !ui->publishedPortList->getInput())
507  {
508  vector<VuoCable *> previousConnectedCables = publishedPort->getBase()->getConnectedCables(true);
509  for (vector<VuoCable *>::iterator cable = previousConnectedCables.begin(); (! dataCableToDisplace) && (cable != previousConnectedCables.end()); ++cable)
510  if ((*cable)->getRenderer()->effectivelyCarriesData())
511  dataCableToDisplace = *cable;
512  }
513 
514  // If replacing a preexisting cable with an identical cable, just cancel the operation
515  // so that it doesn't go onto the Undo stack.
516  VuoCable *cableToReplace = fixedPort->getCableConnectedTo(publishedPort, true);
517  bool cableToReplaceHasMatchingDataCarryingStatus = (cableToReplace?
518  (cableToReplace->getRenderer()->effectivelyCarriesData() ==
519  cableInProgressExpectedToCarryData) :
520  false);
521  if (cableToReplace && cableToReplaceHasMatchingDataCarryingStatus)
522  {
523  composition->cancelCableDrag();
524  return;
525  }
526 
527  // If the user has simply disconnected a cable and reconnected it within the same mouse drag,
528  // don't push the operation onto the Undo stack.
529  bool recreatingSameConnection = ((cableInProgress->getRenderer()->getFloatingEndpointPreviousToPort() ==
530  publishedPort->getBase()) &&
531  (cableInProgress->getRenderer()->getPreviouslyAlwaysEventOnly() ==
532  cableInProgress->getCompiler()->getAlwaysEventOnly()));
533  if (recreatingSameConnection)
534  {
535  composition->revertCableDrag();
536  return;
537  }
538 
539  emit undoStackMacroBeginRequested("Cable Connection");
540  composition->cancelCableDrag();
541 
542  if (dataCableToDisplace)
543  {
544  QList<QGraphicsItem *> removedComponents;
545  removedComponents.append(dataCableToDisplace->getRenderer());
546  emit componentsRemoved(removedComponents, "Delete");
547  }
548 
549  // If this source/target port combination already a cable connecting them, but of a different
550  // data-carrying status, replace the old cable with the new one.
551  if (cableToReplace && !cableToReplaceHasMatchingDataCarryingStatus)
552  {
553  QList<QGraphicsItem *> removedComponents;
554  removedComponents.append(cableToReplace->getRenderer());
555  emit componentsRemoved(removedComponents, "Delete");
556  }
557 
559  dynamic_cast<VuoPublishedPort *>(publishedPort->getBase()),
560  forceEventOnlyPublication,
561  (portToSpecialize? portToSpecialize->getBase() : NULL),
562  specializedTypeName,
563  typecastToInsert,
564  false); // Avoid nested Undo stack macros.
566  }
567  }
568  else // Cable was dropped over empty space within the sidebar
569  composition->cancelCableDrag();
570  }
571 }
572 
576 void VuoPublishedPortSidebar::contextMenuEvent(QContextMenuEvent *event)
577 {
578  // @todo: Account for multiple simultaneous active protocols. https://b33p.net/kosada/node/9585
579  if (isActiveProtocolLabelUnderCursorForEvent(event))
580  {
581  contextMenuActionRemoveProtocol->setData(qVariantFromValue((void *)composition->getActiveProtocol()));
582  contextMenuRemoveProtocol->exec(event->globalPos());
583  }
584  else
585  {
586  contextMenuPortOptions->addMenu(menuAddPort);
587  contextMenuPortOptions->exec(event->globalPos());
588  contextMenuPortOptions->removeAction(menuAddPort->menuAction());
589  }
590 }
591 
596 {
597  // Refrain from hover-highlighting published ports while the cursor is within the sidebar.
599 }
600 
605 {
606  ui->publishedPortList->clearSelection();
607 
608  // Calling QDockWidget::mousePressEvent() here for some reason
609  // increases the width of the sidebar, so don't call it.
610 }
611 
618 {
619  ui->publishedPortList->updatePublishedPortLocs();
620 }
621 
625 void VuoPublishedPortSidebar::dragEnterEvent(QDragEnterEvent *event)
626 {
627  if (event->source() == ui->publishedPortList)
628  event->accept();
629  else
630  event->ignore();
631 }
632 
636 void VuoPublishedPortSidebar::dropEvent(QDropEvent *event)
637 {
638  QDropEvent modifiedEvent(QPoint(10,10),
639  event->dropAction(),
640  event->mimeData(),
641  event->mouseButtons(),
642  event->keyboardModifiers());
643  ui->publishedPortList->adoptDropEvent(&modifiedEvent);
644  return;
645 }
646 
650 void VuoPublishedPortSidebar::closeEvent(QCloseEvent *event)
651 {
652  emit closed();
653  QDockWidget::closeEvent(event);
654 }
655 
660 bool VuoPublishedPortSidebar::canListPublishedPortAliasFor(VuoRendererPort *port)
661 {
662  return ((ui->publishedPortList->getInput() && port->getInput()) || ((! ui->publishedPortList->getInput()) && port->getOutput()));
663 }
664 
669 bool VuoPublishedPortSidebar::isPublishedPortDropBoxUnderCursorForEvent(QMouseEvent *event)
670 {
671  QPoint cursorPosition = ((QMouseEvent *)(event))->globalPos();
672 
673  QRect publishedPortDropBoxRect = ui->publishedPortDropBox->geometry();
674  publishedPortDropBoxRect.moveTopLeft(ui->publishedPortDropBox->parentWidget()->mapToGlobal(publishedPortDropBoxRect.topLeft()));
675 
676  bool dropBoxUnderCursor = (!ui->publishedPortDropBox->isHidden() && publishedPortDropBoxRect.contains(cursorPosition));
677  return dropBoxUnderCursor;
678 }
679 
684 bool VuoPublishedPortSidebar::isActiveProtocolLabelUnderCursorForEvent(QContextMenuEvent *event)
685 {
686  QPoint cursorPosition = ((QContextMenuEvent *)(event))->globalPos();
687 
688  QRect activeProtocolLabelRect = ui->activeProtocolLabel->geometry();
689  activeProtocolLabelRect.moveTopLeft(ui->activeProtocolLabel->parentWidget()->mapToGlobal(activeProtocolLabelRect.topLeft()));
690 
691  bool activeProtocolLabelUnderCursor = (!ui->activeProtocolLabel->isHidden() && activeProtocolLabelRect.contains(cursorPosition));
692  return activeProtocolLabelUnderCursor;
693 }
694 
699 VuoRendererPublishedPort * VuoPublishedPortSidebar::getPublishedPortUnderCursorForEvent(QMouseEvent *event, qreal tolerance, bool limitPortCollisionRange)
700 {
701  QPoint cursorPosition = ((QMouseEvent *)(event))->globalPos();
702  return ui->publishedPortList->getPublishedPortAtGlobalPos(cursorPosition, tolerance, limitPortCollisionRange);
703 }
704 
709 {
710  return ui->publishedPortList->getGlobalPosOfPublishedPort(port);
711 }
712 
717 {
718  return ui->publishedPortList->getMenuSelectionInProgress();
719 }
720 
725 QColor VuoPublishedPortSidebar::getActiveProtocolHeadingColor(int protocolIndex, bool isInput)
726 {
727  VuoRendererColors activeProtocolColor(VuoRendererColors::getActiveProtocolTint(protocolIndex, isInput),
728  VuoRendererColors::noSelection,
729  false,
731  return activeProtocolColor.nodeFrame();
732 }
733 
738 QColor VuoPublishedPortSidebar::getActiveProtocolPortColor(int protocolIndex, bool isInput)
739 {
740  VuoRendererColors activeProtocolColor(VuoRendererColors::getActiveProtocolTint(protocolIndex, isInput),
741  VuoRendererColors::noSelection,
742  false,
744  return activeProtocolColor.nodeFill();
745 }
746 
753 {
754  VuoTitleEditor *nameEditor = new VuoPublishedPortNameEditor();
755  string originalValue = port->getBase()->getClass()->getName();
756 
757  // Position the input editor overtop the published port's list widget item.
758  QPoint portLeftCenterGlobal = getGlobalPosOfPublishedPort(port);
759  int yCenter = portLeftCenterGlobal.y();
760  int xRight = parentWidget()->mapToGlobal( QPoint(x() + width(), 0) ).x();
761  QPoint publishedPortRightCenterGlobal = QPoint(xRight, yCenter);
762 
763  // Adjust the input editor's position and width to keep its focus glow from extending beyond the sidebar boundaries.
764  const int focusGlowWidth = 2;
765  publishedPortRightCenterGlobal = publishedPortRightCenterGlobal - QPoint(focusGlowWidth, 0);
766  nameEditor->setWidth(width() - 2*focusGlowWidth);
767 
768  json_object *details = json_object_new_object();
769  json_object_object_add(details, "isDark", json_object_new_boolean(static_cast<VuoEditor *>(qApp)->isInterfaceDark()));
770 
771  json_object *originalValueAsJson = json_object_new_string(originalValue.c_str());
772  json_object *newValueAsJson = nameEditor->show(publishedPortRightCenterGlobal, originalValueAsJson, details);
773  string newValue = json_object_get_string(newValueAsJson);
774  json_object_put(originalValueAsJson);
775  json_object_put(newValueAsJson);
776 
777  return newValue;
778 }
779 
784 void VuoPublishedPortSidebar::showPublishedPortDetailsEditor(VuoRendererPublishedPort *port)
785 {
786  VuoPublishedPort *publishedPort = dynamic_cast<VuoPublishedPort *>(port->getBase());
787  VuoType *type = port->getDataType();
788  if (publishedPort && type &&
789  ((type->getModuleKey() == "VuoReal") ||
790  (type->getModuleKey() == "VuoInteger") ||
791  (type->getModuleKey() == "VuoPoint2d") ||
792  (type->getModuleKey() == "VuoPoint3d") ||
793  (type->getModuleKey() == "VuoPoint4d")))
794  {
795  QPoint portLeftCenterGlobal = getGlobalPosOfPublishedPort(port);
796 
797  bool isPublishedInput = !port->getInput();
798  json_object *originalDetails = static_cast<VuoCompilerPublishedPort *>(publishedPort->getCompiler())->getDetails(isPublishedInput);
799 
800  composition->disableNondetachedPortPopovers();
801 
802  if (!originalDetails || json_object_get_type(originalDetails) != json_type_object)
803  originalDetails = json_object_new_object();
804  VuoEditor *editor = static_cast<VuoEditor *>(qApp);
805  json_object_object_add(originalDetails, "isDark", json_object_new_boolean(editor->isInterfaceDark()));
806 
807  json_object *newDetails = NULL;
808  if ((type->getModuleKey() == "VuoReal") || (type->getModuleKey() == "VuoInteger"))
809  {
810  VuoDetailsEditorNumeric *numericDetailsEditor = new VuoDetailsEditorNumeric(type);
811  newDetails = numericDetailsEditor->show(portLeftCenterGlobal, originalDetails);
812  numericDetailsEditor->deleteLater();
813  }
814  else if ((type->getModuleKey() == "VuoPoint2d") ||
815  (type->getModuleKey() == "VuoPoint3d") ||
816  (type->getModuleKey() == "VuoPoint4d"))
817  {
818  VuoDetailsEditorPoint *pointDetailsEditor = new VuoDetailsEditorPoint(type);
819  newDetails = pointDetailsEditor->show(portLeftCenterGlobal, originalDetails);
820  pointDetailsEditor->deleteLater();
821  }
822 
823  // Check whether any of the editable values have been changed before creating an Undo stack command.
824  string originalSuggestedMin, newSuggestedMin, originalSuggestedMax, newSuggestedMax, originalSuggestedStep, newSuggestedStep = "";
825 
826  // suggestedMin
827  json_object *originalSuggestedMinValue = NULL;
828  if (json_object_object_get_ex(originalDetails, "suggestedMin", &originalSuggestedMinValue))
829  originalSuggestedMin = json_object_to_json_string(originalSuggestedMinValue);
830 
831  json_object *newSuggestedMinValue = NULL;
832  if (json_object_object_get_ex(newDetails, "suggestedMin", &newSuggestedMinValue))
833  newSuggestedMin = json_object_to_json_string(newSuggestedMinValue);
834  else
835  json_object_object_add(newDetails, "suggestedMin", NULL);
836 
837  // suggestedMax
838  json_object *originalSuggestedMaxValue = NULL;
839  if (json_object_object_get_ex(originalDetails, "suggestedMax", &originalSuggestedMaxValue))
840  originalSuggestedMax = json_object_to_json_string(originalSuggestedMaxValue);
841 
842  json_object *newSuggestedMaxValue = NULL;
843  if (json_object_object_get_ex(newDetails, "suggestedMax", &newSuggestedMaxValue))
844  newSuggestedMax = json_object_to_json_string(newSuggestedMaxValue);
845  else
846  json_object_object_add(newDetails, "suggestedMax", NULL);
847 
848  // suggestedStep
849  json_object *originalSuggestedStepValue = NULL;
850  if (json_object_object_get_ex(originalDetails, "suggestedStep", &originalSuggestedStepValue))
851  originalSuggestedStep = json_object_to_json_string(originalSuggestedStepValue);
852 
853  json_object *newSuggestedStepValue = NULL;
854  if (json_object_object_get_ex(newDetails, "suggestedStep", &newSuggestedStepValue))
855  newSuggestedStep = json_object_to_json_string(newSuggestedStepValue);
856  else
857  json_object_object_add(newDetails, "suggestedStep", NULL);
858 
859  if ((originalSuggestedMin != newSuggestedMin) ||
860  (originalSuggestedMax != newSuggestedMax) ||
861  (originalSuggestedStep != newSuggestedStep))
862  {
863  emit publishedPortDetailsChangeRequested(port, newDetails);
864  }
865 
866  composition->disableNondetachedPortPopovers();
867  }
868 }
869 
873 void VuoPublishedPortSidebar::newPublishedPortTypeSelected()
874 {
875  QAction *sender = (QAction *)QObject::sender();
876  QList<QVariant> actionData = sender->data().toList();
877  QString typeName = actionData[1].toString();
878 
879  emit newPublishedPortRequested(typeName.toUtf8().constData(), isInput);
880 }
881 
886 void VuoPublishedPortSidebar::populatePortTypeMenus()
887 {
888  if (this->portTypeMenusPopulated)
889  return;
890 
891  menuAddPort->clear();
892 
893  // Event-only option
894  QAction *addEventOnlyPortAction = menuAddPort->addAction(tr("Event-Only"));
895  string typeName = "";
896  QList<QVariant> portAndSpecializedType;
897  portAndSpecializedType.append(qVariantFromValue((void *)NULL));
898  portAndSpecializedType.append(typeName.c_str());
899  addEventOnlyPortAction->setData(QVariant(portAndSpecializedType));
900 
901  // Non-list type submenu
902  QMenu *menuNonListType = new QMenu(menuAddPort);
903  menuNonListType->setSeparatorsCollapsible(false);
904  menuNonListType->setTitle(tr("Single Value"));
905  menuNonListType->setToolTipsVisible(true);
906  menuAddPort->addMenu(menuNonListType);
907 
908  // List-type submenu
909  QMenu *menuListType = new QMenu(menuAddPort);
910  menuListType->setSeparatorsCollapsible(false);
911  menuListType->setTitle(tr("List"));
912  menuListType->setToolTipsVisible(true);
913  menuAddPort->addMenu(menuListType);
914 
915  // Non-list type options
916  {
917  vector<string> singleValueTypes = composition->getAllSpecializedTypeOptions(false);
918 
919  set<string> types;
920  if (allowedPortTypes.empty())
921  types.insert(singleValueTypes.begin(), singleValueTypes.end());
922  else
923  {
924  vector<string> sortedAllowedPortTypes(allowedPortTypes.begin(), allowedPortTypes.end());
925  std::sort(sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end());
926  std::sort(singleValueTypes.begin(), singleValueTypes.end());
927 
928  std::set_intersection(singleValueTypes.begin(), singleValueTypes.end(),
929  sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end(),
930  std::inserter(types, types.begin()));
931  }
932 
933  // First list compatible types that don't belong to any specific node set.
934  QList<QAction *> actions = composition->getCompatibleTypesForMenu(NULL, types, types, true, "", menuNonListType);
935  composition->addTypeActionsToMenu(actions, menuNonListType);
936 
937  // Now add a submenu for each node set that contains compatible types.
939  map<string, set<VuoCompilerType *> > loadedTypesForNodeSet = composition->getModuleManager()->getLoadedTypesForNodeSet();
940  QList<QAction *> allNodeSetActionsToAdd;
941  for (map<string, set<VuoCompilerType *> >::iterator i = loadedTypesForNodeSet.begin(); i != loadedTypesForNodeSet.end(); ++i)
942  {
943  string nodeSetName = i->first;
944  if (!nodeSetName.empty())
945  allNodeSetActionsToAdd += composition->getCompatibleTypesForMenu(NULL, types, types, true, nodeSetName, menuNonListType);
946  }
947 
948  if ((menuNonListType->actions().size() > 0) && (allNodeSetActionsToAdd.size() > 0))
949  menuNonListType->addSeparator();
950 
951  composition->addTypeActionsToMenu(allNodeSetActionsToAdd, menuNonListType);
952  }
953 
954  // List type options
955  {
956  vector<string> listTypes = composition->getAllSpecializedTypeOptions(true);
957 
958  set<string> types;
959  if (allowedPortTypes.empty())
960  types.insert(listTypes.begin(), listTypes.end());
961  else
962  {
963  vector<string> sortedAllowedPortTypes(allowedPortTypes.begin(), allowedPortTypes.end());
964  std::sort(sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end());
965  std::sort(listTypes.begin(), listTypes.end());
966 
967  std::set_intersection(listTypes.begin(), listTypes.end(),
968  sortedAllowedPortTypes.begin(), sortedAllowedPortTypes.end(),
969  std::inserter(types, types.begin()));
970  }
971 
972  // First list compatible types that don't belong to any specific node set.
973  QList<QAction *> actions = composition->getCompatibleTypesForMenu(NULL, types, types, true, "", menuListType);
974  composition->addTypeActionsToMenu(actions, menuListType);
975 
976  // Now add a submenu for each node set that contains compatible types.
978  map<string, set<VuoCompilerType *> > loadedTypesForNodeSet = composition->getModuleManager()->getLoadedTypesForNodeSet();
979  QList<QAction *> allNodeSetActionsToAdd;
980  for (map<string, set<VuoCompilerType *> >::iterator i = loadedTypesForNodeSet.begin(); i != loadedTypesForNodeSet.end(); ++i)
981  {
982  string nodeSetName = i->first;
983  if (!nodeSetName.empty())
984  allNodeSetActionsToAdd += composition->getCompatibleTypesForMenu(NULL, types, types, true, nodeSetName, menuListType);
985  }
986 
987  if ((menuListType->actions().size() > 0) && (allNodeSetActionsToAdd.size() > 0))
988  menuListType->addSeparator();
989 
990  composition->addTypeActionsToMenu(allNodeSetActionsToAdd, menuListType);
991  }
992 
993  // Set up context menu item connections.
994  connect(addEventOnlyPortAction, &QAction::triggered, this, &VuoPublishedPortSidebar::newPublishedPortTypeSelected);
995  addEventOnlyPortAction->setCheckable(false);
996 
997  foreach (QAction *setTypeAction, menuAddPort->actions())
998  {
999  QMenu *setTypeSubmenu = setTypeAction->menu();
1000  if (setTypeSubmenu)
1001  {
1002  foreach (QAction *setTypeSubaction, setTypeSubmenu->actions())
1003  {
1004  QMenu *setTypeSubmenu = setTypeSubaction->menu();
1005  if (setTypeSubmenu)
1006  {
1007  foreach (QAction *setTypeSubSubaction, setTypeSubmenu->actions())
1008  {
1009  connect(setTypeSubSubaction, &QAction::triggered, this, &VuoPublishedPortSidebar::newPublishedPortTypeSelected);
1010  setTypeSubSubaction->setCheckable(false);
1011  }
1012  }
1013  else
1014  {
1015  connect(setTypeSubaction, &QAction::triggered, this, &VuoPublishedPortSidebar::newPublishedPortTypeSelected);
1016  setTypeSubaction->setCheckable(false);
1017  }
1018  }
1019  }
1020  }
1021 
1022  this->portTypeMenusPopulated = true;
1023 }
1024 
1030 void VuoPublishedPortSidebar::limitAllowedPortTypes(const set<string> &allowedPortTypes)
1031 {
1032  this->allowedPortTypes = allowedPortTypes;
1033 }
1034 
1038 void VuoPublishedPortSidebar::updateColor(bool isDark)
1039 {
1040  QString titleTextColor = isDark ? "#303030" : "#808080";
1041  QString titleBackgroundColor = isDark ? "#919191" : "#efefef";
1042  QString dockwidgetBackgroundColor = isDark ? "#404040" : "#efefef";
1043  QString buttonTextColor = isDark ? "#a0a0a0" : "#707070";
1044 
1045  setStyleSheet(VUO_QSTRINGIFY(
1046  QDockWidget {
1047  titlebar-close-icon: url(:/Icons/dockwidget-close-%3.png);
1048  font-size: 11px;
1049  border: none;
1050  color: %1;
1051  }
1052  QDockWidget::title {
1053  text-align: left;
1054  padding-left: 6px;
1055  background-color: %2;
1056  }
1057  )
1058  .arg(titleTextColor)
1059  .arg(titleBackgroundColor)
1060  .arg(isDark ? "dark" : "light")
1061  );
1062 
1063  ui->VuoPublishedPortSidebarContents->setStyleSheet(VUO_QSTRINGIFY(
1064  QWidget#VuoPublishedPortSidebarContents {
1065  background-color: %1;
1066  }
1067  )
1068  .arg(dockwidgetBackgroundColor)
1069  );
1070 
1071  ui->publishedPortList->setStyleSheet(
1072  ui->publishedPortList->styleSheet().append(
1073  VUO_QSTRINGIFY(
1075  background-color: %1;
1076  }
1077  )
1078  .arg(dockwidgetBackgroundColor)
1079  )
1080  );
1081 
1082  ui->newPublishedPortButton->setStyleSheet(VUO_QSTRINGIFY(
1083  * {
1084  border-radius: 5px; // Rounded corners
1085  padding: 3px;
1086  margin: 5px 2px;
1087  color: %1;
1088  }
1089  QToolButton::menu-indicator {
1090  image: none;
1091  }
1092  ).arg(buttonTextColor));
1093 }