Vuo  2.0.2
VuoEditor.cc
Go to the documentation of this file.
1 
10 #include "VuoEditor.hh"
11 
12 #include "VuoCodeWindow.hh"
13 #include "VuoCompilerDriver.hh"
14 #include "VuoCompilerIssue.hh"
15 #include "VuoComposition.hh"
18 #include "VuoEditorAboutBox.hh"
19 #include "VuoEditorComposition.hh"
20 #include "VuoEditorUtilities.hh"
21 #include "VuoEditorWindow.hh"
22 #include "VuoErrorDialog.hh"
23 #include "VuoException.hh"
24 #include "VuoModuleManager.hh"
25 #include "VuoNodeClass.hh"
26 #include "VuoNodeSet.hh"
27 #include "VuoStringUtilities.hh"
28 #include "VuoRendererFonts.hh"
29 #include "VuoCompilerException.hh"
30 #include "VuoCompilerNodeClass.hh"
31 #include "VuoExampleMenu.hh"
32 #include "VuoNodePopover.hh"
33 #include "VuoOsStatus.h"
34 #include "VuoProtocol.hh"
35 #include "VuoRecentFileMenu.hh"
38 
39 #include <sstream>
40 #include <fstream>
41 
42 #include <Carbon/Carbon.h>
43 #include <IOKit/IOKitLib.h>
44 
45 const QString VuoEditor::recentFileListSettingsKey = "recentFileList";
46 const QString VuoEditor::subcompositionPrefixSettingsKey = "subcompositionPrefix";
47 const QString VuoEditor::nodeLibraryDisplayModeSettingsKey = "nodeLibraryDisplayMode";
48 const QString VuoEditor::nodeLibraryVisibilityStateSettingsKey = "nodeLibraryVisible";
49 const QString VuoEditor::nodeLibraryDockingStateSettingsKey = "nodeLibraryDocked";
50 const QString VuoEditor::nodeLibraryFloatingPositionSettingsKey = "nodeLibraryFloatingPosition";
51 const QString VuoEditor::nodeLibraryWidthSettingsKey = "nodeLibraryWidth";
52 const QString VuoEditor::nodeLibraryHeightSettingsKey = "nodeLibraryHeight";
53 const QString VuoEditor::nodeDocumentationPanelHeightSettingsKey = "nodeDocumentationPanelHeight";
54 const QString VuoEditor::shaderDocumentationVisibilitySettingsKey = "shaderDocumentationVisible";
55 const QString VuoEditor::gridTypeSettingsKey = "canvasGridType";
56 const QString VuoEditor::gridOpacitySettingsKey = "canvasGridOpacity";
57 const qreal VuoEditor::defaultGridOpacity = 1;
58 const QString VuoEditor::snapToGridSettingsKey = "canvasGridSnap";
59 const QString VuoEditor::darkInterfaceSettingsKey = "darkInterface";
60 const QString VuoEditor::canvasOpacitySettingsKey = "canvasOpacity";
61 const QString VuoEditor::movieExportWidthSettingsKey = "movieExportWidth";
62 const QString VuoEditor::movieExportHeightSettingsKey = "movieExportHeight";
63 const QString VuoEditor::movieExportTimeSettingsKey = "movieExportTime";
64 const QString VuoEditor::movieExportDurationSettingsKey = "movieExportDuration";
65 const QString VuoEditor::movieExportFramerateSettingsKey = "movieExportFramerate";
66 const QString VuoEditor::movieExportSpatialSupersampleSettingsKey = "movieExportSpatialSupersample";
67 const QString VuoEditor::movieExportTemporalSupersampleSettingsKey = "movieExportTemporalSupersample";
68 const QString VuoEditor::movieExportShutterAngleSettingsKey = "movieExportShutterAngle";
69 const QString VuoEditor::movieExportImageFormatSettingsKey = "movieExportImageFormat";
70 const QString VuoEditor::movieExportQualitySettingsKey = "movieExportQuality";
71 
72 const QString VuoEditor::vuoHelpBookScheme = "vuo-help";
73 const QString VuoEditor::vuoNodeSetDocumentationScheme = "vuo-nodeset";
74 const QString VuoEditor::vuoNodeDocumentationScheme = "vuo-node";
75 const QString VuoEditor::vuoExampleCompositionScheme = "vuo-example";
76 const QString VuoEditor::vuoExampleHighlightedNodeClassQueryItem = "nodeClass";
77 const QString VuoEditor::vuoCompositionFileExtension = "vuo";
78 const QString VuoEditor::vuoNodeClassFileExtension = "vuonode";
79 const QString VuoEditor::vuoTutorialURL = "https://vuo.org/tutorials";
80 
81 string VuoEditor::documentationGenerationDirectory = "";
82 
86 VuoEditor::VuoEditor(int &argc, char *argv[])
87  : QApplication(argc,argv)
88 {
89  // Load stored application settings.
90  QCoreApplication::setOrganizationName("Vuo");
91  QCoreApplication::setOrganizationDomain("vuo.org");
92  QCoreApplication::setApplicationName("Editor");
93  settings = new QSettings();
94 
95 
96  // Load translations
97  if (settings->value("translation/enable", true).toBool())
98  {
99  QLocale locale = QLocale::system();
100  QString translationsPath = QString::fromStdString(VuoFileUtilities::getVuoFrameworkPath() + "/../../Resources/Translations");
101  QList<QString> contexts;
102  contexts << "qtbase" << "vuo";
103  foreach (QString context, contexts)
104  {
105  QTranslator *t = new QTranslator();
106  t->load(locale, context, "_", translationsPath, ".qm");
107  installTranslator(t);
108  }
109  }
110 
111  VuoEditorWindow::untitledComposition = QObject::tr("Untitled Composition");
112 
113 
114  // Controls the 2-finger-scroll speed.
115  // Defaults to 3 which (after https://b33p.net/kosada/node/13245) feels a little too fast.
116  // 2 seems to better match Preview.app.
117  setWheelScrollLines(2);
118 
119 
120  uiInitialized = false;
122 
123  setQuitOnLastWindowClosed(false);
124  aboutBox = nullptr;
125  ownedNodeLibrary = NULL;
126  documentationQueue = dispatch_queue_create("org.vuo.editor.documentation", NULL);
127  userSubscriptionLevel = VuoEditor::CommunityUser;
128  reportAbsenceOfUpdates = false;
129  networkManager = NULL;
130  compiler = nullptr;
131  moduleManager = nullptr;
132  subcompositionRouter = new VuoSubcompositionMessageRouter();
133 
134  // Initialize default settings.
135  const VuoNodeLibrary::nodeLibraryDisplayMode defaultNodeLibraryDisplayMode = VuoNodeLibrary::displayByName;
136  const bool defaultNodeLibraryVisibilityState = true; // Node library is visible by default.
137  const bool defaultNodeLibraryDockingState = true; // When visible, node library is docked by default.
138  const int defaultNodeLibraryDocumentationPanelHeight = 280;
139 
140  qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
141  qApp->setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false);
142 
143  qApp->setAttribute(Qt::AA_UseHighDpiPixmaps);
144  QToolTip::setFont(VuoRendererFonts::getSharedFonts()->portPopoverFont());
145 
146  // Disable macOS's restore-open-documents-on-launch feature, since it doesn't work with Qt anyway,
147  // and since it pops up a meaningless "Do you want to try to reopen its windows again?" window after a crash.
148  settings->setValue("ApplePersistenceIgnoreStateQuietly", true);
149 
150  QStringList storedRecentFileList = settings->value(recentFileListSettingsKey).toStringList();
151 
152  menuOpenRecent = new VuoRecentFileMenu();
153  menuOpenRecent->setRecentFiles(storedRecentFileList);
154 
155 
156 #if VUO_PRO
157  VuoEditor_Pro0();
158 #endif
159 
160  subcompositionPrefix = settings->contains(subcompositionPrefixSettingsKey)?
161  settings->value(subcompositionPrefixSettingsKey).toString() :
162  "";
163 
164  canvasOpacity = settings->contains(canvasOpacitySettingsKey)?
165  settings->value(canvasOpacitySettingsKey).toInt() :
166  255;
167 
168  nodeLibraryDisplayMode = settings->contains(nodeLibraryDisplayModeSettingsKey)?
169  (VuoNodeLibrary::nodeLibraryDisplayMode)settings->value(nodeLibraryDisplayModeSettingsKey).toInt() :
170  defaultNodeLibraryDisplayMode;
171 
172  nodeLibraryCurrentlyVisible = settings->contains(nodeLibraryVisibilityStateSettingsKey)?
173  settings->value(nodeLibraryVisibilityStateSettingsKey).toBool() :
174  defaultNodeLibraryVisibilityState;
175 
176  nodeLibraryCurrentlyDocked = settings->contains(nodeLibraryDockingStateSettingsKey)?
177  settings->value(nodeLibraryDockingStateSettingsKey).toBool() :
178  defaultNodeLibraryDockingState;
179 
180  if (settings->contains(nodeLibraryFloatingPositionSettingsKey))
181  {
182  settingsContainedNodeLibraryFloatingPosition = true;
183  nodeLibraryFloatingPosition = settings->value(nodeLibraryFloatingPositionSettingsKey).toPoint();
184  }
185  else
186  {
187  settingsContainedNodeLibraryFloatingPosition = false;
188  nodeLibraryFloatingPosition = QPoint();
189  }
190 
191  if (settings->contains(nodeLibraryWidthSettingsKey))
192  nodeLibraryWidth = settings->value(nodeLibraryWidthSettingsKey).toInt();
193  else
194  nodeLibraryWidth = -1;
195 
196  if (settings->contains(nodeLibraryHeightSettingsKey))
197  nodeLibraryHeight = settings->value(nodeLibraryHeightSettingsKey).toInt();
198  else
199  nodeLibraryHeight = -1;
200 
201  if (settings->contains(nodeDocumentationPanelHeightSettingsKey))
202  nodeDocumentationPanelHeight = settings->value(nodeDocumentationPanelHeightSettingsKey).toInt();
203  else
204  nodeDocumentationPanelHeight = defaultNodeLibraryDocumentationPanelHeight;
205 
206  previousVisibleNodeLibraryStateWasDocked = nodeLibraryCurrentlyDocked;
207  currentFloatingNodeLibrary = NULL;
208 
209  shaderDocumentationVisible = settings->contains(shaderDocumentationVisibilitySettingsKey)?
210  settings->value(shaderDocumentationVisibilitySettingsKey).toBool() :
211  true;
212 
213  applyStoredMovieExportSettings();
214 
215  // Disable the Mac OS 10.7+ restore-open-documents-on-launch feature, since it doesn't work with Qt anyway,
216  // and since it pops up a meaningless "Do you want to try to reopen its windows again?" window after a crash.
217  // (Reverted for now, since it it causes "ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to…" console messages.
218 // settings->setValue("ApplePersistenceIgnoreState", true);
219 
220  compiler = new VuoCompiler();
221  moduleManager = new VuoModuleManager(compiler);
222 
223 #if VUO_PRO
224  VuoEditor_Pro1();
225 #endif
226 
227  builtInDriversQueue = dispatch_queue_create("org.vuo.editor.drivers", NULL);
228  dispatch_async(builtInDriversQueue, ^{
229  initializeBuiltInDrivers();
230  });
231 
232  compiler->prepareForFastBuild();
233 
234  // If operating in documentation-generation mode, generate the node documentation and exit.
235  if (!documentationGenerationDirectory.empty())
236  {
237  dispatch_async(dispatch_get_main_queue(), ^{
238  generateAllNodeSetHtmlDocumentation(documentationGenerationDirectory);
239  quitCleanly();
240  });
241  return;
242  }
243 
244  // Register the URL handler for the "vuo-help" scheme, used for displaying a section of the user manual.
245  QDesktopServices::setUrlHandler(vuoHelpBookScheme, this, "openHelpBookPageFromUrl");
246 
247  // Register the URL handler for the "vuo-nodeset" scheme, used for displaying node set documentation.
248  QDesktopServices::setUrlHandler(vuoNodeSetDocumentationScheme, this, "showNodeSetDocumentationFromUrl");
249 
250  // Register the URL handler for the "vuo-node" scheme, used for displaying node documentation.
251  QDesktopServices::setUrlHandler(vuoNodeDocumentationScheme, this, "showNodeDocumentationFromUrl");
252 
253  // Register the URL handler for the "vuo-example" scheme, used for opening example compositions.
254  QDesktopServices::setUrlHandler(vuoExampleCompositionScheme, this, "openExampleCompositionFromUrl");
255 
256  // Construct the basic menu bar here, so that it's visible when no document windows are open.
257  menuBar = new QMenuBar();
258  {
259  menuFile = new QMenu(menuBar);
260  menuFile->setSeparatorsCollapsible(false);
261  menuFile->setTitle(tr("&File"));
262 
263  menuFile->addAction(tr("&New Composition"), this, SLOT(newComposition()), QKeySequence("Ctrl+N"));
264 
265  menuNewCompositionWithTemplate = new QMenu(tr("New Composition from Template"));
266  menuNewCompositionWithTemplate->setSeparatorsCollapsible(false);
267  populateNewCompositionWithTemplateMenu(menuNewCompositionWithTemplate);
268  menuFile->addMenu(menuNewCompositionWithTemplate);
269 
270  QMenu *menuNewShader = new QMenu(tr("New Shader"));
271  menuNewShader->setSeparatorsCollapsible(false);
272  populateNewShaderMenu(menuNewShader);
273  menuFile->addMenu(menuNewShader);
274 
275  menuFile->addSeparator();
276  menuFile->addAction(tr("&Open…"), this, SLOT(openFile()), QKeySequence("Ctrl+O"));
277 
278  // "Open Recent" menu
279  connect(menuOpenRecent, &VuoRecentFileMenu::recentFileSelected, this, &VuoEditor::openUrl);
281  menuFile->addMenu(menuOpenRecent);
282  connect(menuFile, &QMenu::aboutToShow, this, &VuoEditor::pruneAllOpenRecentFileMenus);
283 
284  // "Open Example" menu
285  menuOpenExample = new VuoExampleMenu(menuFile, compiler);
286  connect(menuOpenExample, &VuoExampleMenu::exampleSelected, this, &VuoEditor::openUrl);
287  menuFile->addMenu(menuOpenExample);
288 
289  // Connect the "Quit" menu item action to our customized quit method. On Mac OS X, this menu
290  // item will automatically be moved from the "File" menu to the Application menu.
291  menuFile->addAction(tr("Quit"), this, SLOT(quitCleanly()), QKeySequence("Ctrl+Q"));
292 
293  // "About" menu item
294  menuFile->addAction(tr("About Vuo…"), this, SLOT(about()));
295 
296  // Workaround for bug preventing the "Quit" and "About" menu items from appearing within the Application menu:
297  // Add them again to the "File" menu, this time with Qt's automatic menu merging behavior disabled;
298  // this at least gains us a functional keyboard shortcut for the "Quit" item. (@todo: Fix properly; see
299  // https://b33p.net/kosada/node/5260 ).
300  QAction *aboutAction = new QAction(NULL);
301  aboutAction->setText(tr("About Vuo…"));
302  connect(aboutAction, &QAction::triggered, this, &VuoEditor::about);
303  aboutAction->setMenuRole(QAction::NoRole); // Disable automatic menu merging for this item.
304  menuFile->addSeparator();
305  menuFile->addAction(aboutAction);
306 
307  QAction *quitAction = new QAction(NULL);
308  quitAction->setText(tr("&Quit Vuo"));
309  quitAction->setShortcut(QKeySequence("Ctrl+Q"));
310  connect(quitAction, &QAction::triggered, this, &VuoEditor::quitCleanly);
311  quitAction->setMenuRole(QAction::NoRole); // Disable automatic menu merging for this item.
312  menuFile->addSeparator();
313  menuFile->addAction(quitAction);
314 
315  menuBar->addAction(menuFile->menuAction());
316 
317 #if VUO_PRO
318  enableMenuItems(menuFile, canCloseWelcomeWindow());
319 #endif
320  }
321 
322  {
323  menuView = new QMenu(menuBar);
324  menuView->setSeparatorsCollapsible(false);
325  menuView->setTitle(tr("&View"));
326 
327  // "Show Node Library"
328  showNodeLibraryAction = new QAction(NULL);
329  showNodeLibraryAction->setText(tr("Show Node &Library"));
330  showNodeLibraryAction->setShortcut(QKeySequence("Ctrl+Return"));
331  connect(showNodeLibraryAction, &QAction::triggered, this, &VuoEditor::showNodeLibrary);
332  menuView->addAction(showNodeLibraryAction);
333 
334 
335  // "Grid" submenu
336  {
337  // "Snap" menu item
338  snapToGridAction = new QAction(NULL);
339  snapToGridAction->setText(tr("Snap"));
340  snapToGridAction->setCheckable(true);
341  bool snapToGrid = (settings->contains(snapToGridSettingsKey)?
342  settings->value(snapToGridSettingsKey).toBool() :
343  true);
344  VuoRendererItem::setSnapToGrid(snapToGrid);
345  snapToGridAction->setChecked(snapToGrid);
346  connect(snapToGridAction, &QAction::toggled, this, &VuoEditor::updateSnapToGrid);
347 
348  // "Line"/"Points" menu items
349  int gridOpacity;
350  if (settings->contains(gridOpacitySettingsKey))
351  gridOpacity = settings->value(gridOpacitySettingsKey).toInt();
352  else
353  {
354  gridOpacity = VuoEditor::defaultGridOpacity;
355  settings->setValue(gridOpacitySettingsKey, gridOpacity);
356  }
357 
359 
360  int gridType = (settings->contains(gridTypeSettingsKey)
361  ? settings->value(gridTypeSettingsKey).toInt()
362  : VuoRendererComposition::LineGrid);
364 
365  showGridLinesAction = new QAction(NULL);
366  showGridLinesAction->setText(tr("Lines"));
367  showGridLinesAction->setCheckable(true);
368  showGridLinesAction->setChecked(gridType == VuoRendererComposition::LineGrid);
369  connect(showGridLinesAction, &QAction::triggered, this, &VuoEditor::showGridLinesToggled);
370 
371  showGridPointsAction = new QAction(NULL);
372  showGridPointsAction->setText(tr("Points"));
373  showGridPointsAction->setCheckable(true);
374  showGridPointsAction->setChecked(gridType == VuoRendererComposition::PointGrid);
375  connect(showGridPointsAction, &QAction::triggered, this, &VuoEditor::showGridPointsToggled);
376  }
377 
378 
379  // "Canvas Transparency"
380  canvasTransparencyNoneAction = new QAction(NULL);
381  canvasTransparencyNoneAction->setText(tr("None"));
382  canvasTransparencyNoneAction->setData(255);
383  canvasTransparencyNoneAction->setCheckable(true);
384  canvasTransparencyNoneAction->setShortcut(QKeySequence("Ctrl+1"));
385 
386  canvasTransparencySlightAction = new QAction(NULL);
387  canvasTransparencySlightAction->setText(tr("Slightly Transparent"));
388  canvasTransparencySlightAction->setData(230);
389  canvasTransparencySlightAction->setCheckable(true);
390  canvasTransparencySlightAction->setShortcut(QKeySequence("Ctrl+2"));
391 
392  canvasTransparencyHighAction = new QAction(NULL);
393  canvasTransparencyHighAction->setText(tr("Very Transparent"));
394  canvasTransparencyHighAction->setData(191);
395  canvasTransparencyHighAction->setCheckable(true);
396  canvasTransparencyHighAction->setShortcut(QKeySequence("Ctrl+3"));
397 
398  canvasTransparencyOptions = new QActionGroup(this);
402 
403  foreach (QAction *action, canvasTransparencyOptions->actions())
404  {
405  if (action->data().toInt() == canvasOpacity)
406  {
407  action->setChecked(true);
408  action->setEnabled(false);
409  break;
410  }
411  }
412 
413  // updateCanvasOpacity() needs to happen before canvasOpacityChanged(),
414  // since slots attached to the latter rely on VuoEditor::getCanvasOpacity() which is updated by the former.
415  connect(canvasTransparencyOptions, &QActionGroup::triggered, this, &VuoEditor::updateCanvasOpacity);
416  connect(canvasTransparencyOptions, &QActionGroup::triggered, this, &VuoEditor::canvasOpacityChanged);
417 
418  menuBar->addAction(menuView->menuAction());
419  }
420 
421  {
422  // @todo: For some reason the following doesn't work; no "Window" menu item is displayed.
423  menuWindow = new QMenu(menuBar);
424  menuWindow->setSeparatorsCollapsible(false);
425  menuWindow->setTitle(tr("&Window"));
426 
427  populateWindowMenu(menuWindow, nullptr);
428 
429  connect(menuWindow, &QMenu::aboutToShow, this, &VuoEditor::updateUI);
430 
431  menuBar->addAction(menuWindow->menuAction());
432  }
433 
434  {
435  menuHelp = new QMenu(menuBar);
436  menuHelp->setSeparatorsCollapsible(false);
437  menuHelp->setTitle(tr("&Help"));
438  populateHelpMenu(menuHelp);
439 
440  // Prevent "Help > Search" from triggering lookup of all customized example composition titles at once.
441  connect(menuHelp, &QMenu::aboutToShow, menuOpenExample, &VuoExampleMenu::disableExampleTitleLookup);
442  connect(menuHelp, &QMenu::aboutToHide, menuOpenExample, &VuoExampleMenu::enableExampleTitleLookup);
443 
444  connect(menuHelp, &QMenu::aboutToShow, [this] { populateHelpMenu(menuHelp); });
445 
446  menuBar->addAction(menuHelp->menuAction());
447  }
448 
449 #ifdef __APPLE__
450  {
451  // Construct an OS X dock context menu.
452  // Items included in this menu will supplement the default OS X dock context menu items.
453  dockContextMenu = new QMenu();
454  dockContextMenu->setSeparatorsCollapsible(false);
455  dockContextMenu->setAsDockMenu();
456 
457  connect(dockContextMenu, &QMenu::aboutToShow, this, &VuoEditor::updateUI);
458  }
459 #endif
460 
461  initializeTopLevelNodeLibrary(compiler,
462  nodeLibraryDisplayMode,
463  settingsContainedNodeLibraryFloatingPosition,
464  nodeLibraryFloatingPosition,
465  nodeLibraryWidth,
466  nodeLibraryHeight);
467  if (!nodeLibraryCurrentlyDocked)
468  designateNewFloatingNodeLibrary(ownedNodeLibrary);
469 
470  moduleManager->setNodeLibrary(ownedNodeLibrary);
471 
472 
473  // @todo https://b33p.net/kosada/node/14299 The following line causes nodes to be listed in the
474  // floating node library immediately on launch (good), but then to be duplicated moments later (bad).
475  //moduleManager->updateWithAlreadyLoadedModules();
476  conformToGlobalNodeLibraryVisibility(
477  getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
478  currentFloatingNodeLibrary);
479  ownedNodeLibrary->displayPopoverForCurrentNodeClass();
480 
481  // Update the global node library display mode whenever it is changed for the top-level node library.
482  connect(ownedNodeLibrary, &VuoNodeLibrary::changedIsHumanReadable, static_cast<VuoEditor *>(qApp), &VuoEditor::updateNodeLibraryDisplayMode);
483 
484  // Update the "Show/Hide Node Library" menu item when the node library visibility or display mode is changed.
485  connect(ownedNodeLibrary, &VuoNodeLibrary::visibilityChanged, this, &VuoEditor::updateUI);
486  enableGlobalStateConformanceToLibrary(ownedNodeLibrary);
487  connect(this, &VuoEditor::globalNodeLibraryStateChanged, this, &VuoEditor::conformToGlobalNodeLibraryVisibility);
488 
489 #ifndef VUO_PRO
490  QApplication::processEvents(); // Force processing of any incoming QEvent::FileOpen event, which may populate queuedCompositionsToOpen.
491  if (queuedCompositionsToOpen.empty())
492  newComposition();
493 #endif
494 
495  // Initialize a file dialogue with its default directory set to something reasonable, so that
496  // file dialogues initialized subsequently within the same editor session will default to
497  // this location rather than to the executable directory.
498  // See https://b33p.net/kosada/node/7302 .
499  QFileDialog d;
500  if (d.history().isEmpty())
501  d.setDirectory(getDefaultCompositionStorageDirectory());
502 
503  uiInitialized = true;
505  updateUI();
506 }
507 
512 {
513  dispatch_sync(builtInDriversQueue, ^{});
514 
515  menuBar->deleteLater();
516  delete subcompositionRouter;
517  moduleManager->deleteWhenReady(); // deletes compiler
518 }
519 
524  {
525  vector<QString> queuedCompositionsToOpenSnapshot(queuedCompositionsToOpen);
526  queuedCompositionsToOpen.clear();
527 
528  // Iterate through a copy of the queue rather than the original queue,
529  // because calls to openUrl() might add the file back into the original queue,
530  // so we need the clear() call to have happened before that.
531  foreach (QString queuedUrl, queuedCompositionsToOpenSnapshot)
532  openUrl(queuedUrl);
533  }
534 
540  {
541 #if VUO_PRO
542  enableMenuItems(menuFile, canCloseWelcomeWindow());
543 
544  if (!queuedCompositionsToOpen.empty())
545  closeWelcomeWindow();
546 #endif
547  }
548 
554  void VuoEditor::enableMenuItems(QMenu *menu, bool enable)
555  {
556  if (!menu)
557  return;
558 
559  foreach (QAction *action, menu->actions())
560  {
561  // Apply recursively to submenus.
562  if (action->menu())
563  {
564  VuoExampleMenu *exampleMenu = dynamic_cast<VuoExampleMenu *>(action->menu());
565  if (exampleMenu)
566  exampleMenu->enableMenuItems(enable);
567  else
568  enableMenuItems(action->menu(), enable);
569  }
570  else
571  {
572  bool isQuitOrHelp = action->text().contains("Quit") || action->text().contains("About");
573  bool isTemplateHeader = (menu == menuNewCompositionWithTemplate) &&
574  action->data().value<QString>().isEmpty() &&
575  !action->data().value<void *>();
576  if (!(isQuitOrHelp || isTemplateHeader))
577  action->setEnabled(enable);
578  }
579  }
580  }
581 
586 {
587  menuOpenRecent->pruneNonexistentFiles();
588  return menuOpenRecent->getRecentFiles();
589 }
590 
595 {
596  VUserLog("Conditionally quit");
597 
598  // Write any unsaved settings changes to permanent storage.
599  settings->sync();
600 
601  // Prevent node library visibility changes from impacting application-global settings
602  // from this point forward, since we are about to forcibly close them.
603  if (ownedNodeLibrary)
604  disableGlobalStateConformanceToLibrary(ownedNodeLibrary);
605 
607  disableGlobalStateConformanceToLibrary(window->getOwnedNodeLibrary());
608 
609  // Try to close each window sequentially, in the order they're stacked.
610  windowsRemainingAfterQuitRequested = VuoEditorUtilities::getOpenEditingWindowsStacked();
611  if (windowsRemainingAfterQuitRequested.empty())
612  reallyQuit();
613  else
614  // Start the chain reaction.
615  windowsRemainingAfterQuitRequested[0]->close();
616 }
617 
625 {
626  if (windowsRemainingAfterQuitRequested.empty())
627  // The user hasn't requested to quit, so do nothing.
628  return;
629 
630  // Restore node library connections.
631  foreach (QMainWindow *window, windowsRemainingAfterQuitRequested)
632  {
633  VuoEditorWindow *compositionWindow = dynamic_cast<VuoEditorWindow *>(window);
634  if (compositionWindow)
635  enableGlobalStateConformanceToLibrary(compositionWindow->getOwnedNodeLibrary());
636  }
637 
638  if (ownedNodeLibrary)
639  enableGlobalStateConformanceToLibrary(ownedNodeLibrary);
640 
641  windowsRemainingAfterQuitRequested.clear();
642 }
643 
650 void VuoEditor::continueQuit(QMainWindow *window)
651 {
652  if (windowsRemainingAfterQuitRequested.empty())
653  // The user hasn't requested to quit, so do nothing.
654  return;
655 
656  windowsRemainingAfterQuitRequested.removeOne(window);
657 
658  if (windowsRemainingAfterQuitRequested.empty())
659  reallyQuit();
660  else
661  // Continue the chain reaction.
662  windowsRemainingAfterQuitRequested[0]->close();
663 }
664 
668 void VuoEditor::reallyQuit()
669 {
670  VUserLog("Quit");
671 
672  try
673  {
675  }
676  catch (...)
677  {
678  // Do nothing; it doesn't matter if this sometimes fails.
679  }
680 
681  QApplication::quit();
682 }
683 
688 {
689  return nodeLibraryCurrentlyDocked;
690 }
691 
697 void VuoEditor::enableGlobalStateConformanceToLibrary(VuoNodeLibrary *library)
698 {
699  connect(library, &VuoNodeLibrary::nodeLibraryHiddenOrUnhidden, this, &VuoEditor::updateGlobalNodeLibraryVisibilityState);
700  connect(library, &VuoNodeLibrary::topLevelChanged, this, &VuoEditor::updateGlobalNodeLibraryDockedState);
701  connect(library, &VuoNodeLibrary::nodeLibraryMoved, this, &VuoEditor::updateGlobalNodeLibraryFloatingPosition);
702  connect(library, &VuoNodeLibrary::nodeLibraryWidthChanged, this, &VuoEditor::updateGlobalNodeLibraryWidth);
703  connect(library, &VuoNodeLibrary::nodeLibraryHeightChanged, this, &VuoEditor::updateGlobalNodeLibraryHeight);
704  connect(library, &VuoNodeLibrary::nodeDocumentationPanelHeightChanged, this, &VuoEditor::updateGlobalNodeDocumentationPanelHeight);
705 }
706 
712 void VuoEditor::disableGlobalStateConformanceToLibrary(VuoNodeLibrary *library)
713 {
714  disconnect(library, &VuoNodeLibrary::nodeLibraryHiddenOrUnhidden, this, &VuoEditor::updateGlobalNodeLibraryVisibilityState);
715  disconnect(library, &VuoNodeLibrary::topLevelChanged, this, &VuoEditor::updateGlobalNodeLibraryDockedState);
716  disconnect(library, &VuoNodeLibrary::nodeLibraryMoved, this, &VuoEditor::updateGlobalNodeLibraryFloatingPosition);
717  disconnect(library, &VuoNodeLibrary::nodeLibraryWidthChanged, this, &VuoEditor::updateGlobalNodeLibraryWidth);
718  disconnect(library, &VuoNodeLibrary::nodeLibraryHeightChanged, this, &VuoEditor::updateGlobalNodeLibraryHeight);
719  disconnect(library, &VuoNodeLibrary::nodeDocumentationPanelHeightChanged, this, &VuoEditor::updateGlobalNodeDocumentationPanelHeight);
720 }
721 
725 void VuoEditor::initializeTopLevelNodeLibrary(VuoCompiler *nodeLibraryCompiler,
726  VuoNodeLibrary::nodeLibraryDisplayMode nodeLibraryDisplayMode,
727  bool setFloatingPosition,
728  QPoint floatingPosition,
729  int nodeLibraryWidth,
730  int nodeLibraryHeight)
731 {
732  ownedNodeLibrary = new VuoNodeLibrary(nodeLibraryCompiler, NULL, nodeLibraryDisplayMode);
733  ownedNodeLibrary->setObjectName("Top-level node library");
734 
735  ownedNodeLibrary->setFloating(true);
736 
737  if (setFloatingPosition)
738  ownedNodeLibrary->move(floatingPosition);
739 
740  if (nodeLibraryHeight >= 0)
741  ownedNodeLibrary->resize(ownedNodeLibrary->rect().width(), nodeLibraryHeight);
742 }
743 
748 {
749  if (!aboutBox)
750  aboutBox = new VuoEditorAboutBox();
751 
752  aboutBox->showNormal();
753  aboutBox->raise();
754  aboutBox->activateWindow();
755 }
756 
761 {
762  m->addAction(canvasTransparencyNoneAction);
763  m->addAction(canvasTransparencySlightAction);
764  m->addAction(canvasTransparencyHighAction);
765 }
766 
770 void VuoEditor::populateWindowMenu(QMenu *m, QMainWindow *currentWindow)
771 {
772  if (currentWindow)
773  {
774  m->addAction(tr("Minimize"), currentWindow, &QMainWindow::showMinimized, QKeySequence("Ctrl+M"));
775  m->addAction(tr("Zoom"), currentWindow, &QMainWindow::showMaximized);
776 
777  m->addSeparator();
778  }
779 
780 #if VUO_PRO
781  populateWindowMenu_Pro(m);
782 #endif
783 
784  QList<QMainWindow *> openWindows = VuoEditorUtilities::getOpenEditingWindows();
785  if (! openWindows.empty())
786  {
787  m->addSeparator();
788 
789  for (QMainWindow *openWindow : openWindows)
790  {
791  QAction *raiseDocumentAction = VuoEditorUtilities::getRaiseDocumentActionForWindow(openWindow);
792  m->addAction(raiseDocumentAction);
793 
794  if (currentWindow)
795  raiseDocumentAction->setChecked(openWindow == currentWindow);
796  else if (openWindow->isMinimized())
797  raiseDocumentAction->setChecked(false);
798  }
799  }
800 }
801 
806 {
807  m->clear();
808 
809  // Vuo Manual
810  QAction *vuoManualAction = new QAction(m);
811  vuoManualAction->setText(tr("Vuo Manual"));
812  vuoManualAction->setData(QUrl(getVuoManualURL()));
813  connect(vuoManualAction, &QAction::triggered, this, &VuoEditor::openHelpBook);
814  m->addAction(vuoManualAction);
815 
816  // Vuo Manual (PDF)
817  QAction *vuoManualPDFAction = new QAction(m);
818  vuoManualPDFAction->setText(tr("Vuo Manual (PDF)"));
819  vuoManualPDFAction->setData(QUrl(getVuoManualURL()));
820  connect(vuoManualPDFAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
821  m->addAction(vuoManualPDFAction);
822 
823  // Video Tutorials
824  QAction *videoTutorialsAction = new QAction(m);
825  videoTutorialsAction->setText(tr("Video Tutorials"));
826  videoTutorialsAction->setData(QUrl(vuoTutorialURL));
827  connect(videoTutorialsAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
828  m->addAction(videoTutorialsAction);
829 
830  m->addSeparator();
831 
832  // Search vuo.org
833  QAction *searchVuoOrgAction = new QAction(m);
834  searchVuoOrgAction->setText(tr("Search vuo.org"));
835  searchVuoOrgAction->setData(QUrl("https://vuo.org/search"));
836  connect(searchVuoOrgAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
837  m->addAction(searchVuoOrgAction);
838 
839  m->addSeparator();
840 
841  // View Community Activity
842  QAction *communityActivityAction = new QAction(m);
843  communityActivityAction->setText(tr("View Community Activity"));
844  communityActivityAction->setData(QUrl("https://vuo.org/community"));
845  connect(communityActivityAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
846  m->addAction(communityActivityAction);
847 
848  // Share a Composition
849  QAction *shareCompositionAction = new QAction(m);
850  shareCompositionAction->setText(tr("Share a Composition"));
851  shareCompositionAction->setData(QUrl("https://vuo.org/node/add/vuo-composition"));
852  connect(shareCompositionAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
853  m->addAction(shareCompositionAction);
854 
855  // Start a Discussion
856  QAction *startDiscussionAction = new QAction(m);
857  startDiscussionAction->setText(tr("Start a Discussion"));
858  startDiscussionAction->setData(QUrl("https://vuo.org/node/add/discussion"));
859  connect(startDiscussionAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
860  m->addAction(startDiscussionAction);
861 
862  // Report a Bug
863  QAction *reportBugAction = new QAction(m);
864  reportBugAction->setText(tr("Report a Bug"));
865  reportBugAction->setData(QUrl("https://vuo.org/bug"));
866  connect(reportBugAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
867  m->addAction(reportBugAction);
868 
869  // Request a Feature
870  QAction *requestFeatureAction = new QAction(m);
871  requestFeatureAction->setText(tr("Request a Feature"));
872  requestFeatureAction->setData(QUrl("https://vuo.org/feature-request"));
873  connect(requestFeatureAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
874  m->addAction(requestFeatureAction);
875 
876  m->addSeparator();
877 
878  // Help Us Improve Vuo
879  QAction *improveVuoAction = new QAction(m);
880  improveVuoAction->setText(tr("Help Us Improve Vuo"));
881  improveVuoAction->setData(QUrl("https://vuo.org/community-edition"));
882  connect(improveVuoAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
883  m->addAction(improveVuoAction);
884 
885  // Contact Team Vuo
886  QAction *contactTeamVuoAction = new QAction(m);
887  contactTeamVuoAction->setText(tr("Contact Team Vuo"));
888  contactTeamVuoAction->setData(QUrl("https://vuo.org/contact"));
889  connect(contactTeamVuoAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
890  m->addAction(contactTeamVuoAction);
891 
892 #if VUO_PRO
893  VuoEditor::populateHelpMenu_Pro(m);
894 #endif
895 }
896 
901 {
902  OSStatus ret = AHGotoPage(CFSTR("org.vuo.Editor.help"), NULL, NULL);
903  if (ret)
904  {
905  char *description = VuoOsStatus_getText(ret);
906  VUserLog("Error: Couldn't open Vuo Manual in HelpViewer.app: %s", description);
907  free(description);
908  }
909 }
910 
916 void VuoEditor::openHelpBookPageFromUrl(const QUrl &url)
917 {
918  CFStringRef relativePath = CFStringCreateWithCString(NULL, url.url(QUrl::RemoveScheme).toUtf8().constData(), kCFStringEncodingUTF8);
919  OSStatus ret = AHGotoPage(CFSTR("org.vuo.Editor.help"), relativePath, NULL);
920  if (ret)
921  {
922  char *description = VuoOsStatus_getText(ret);
923  VUserLog("Error: Couldn't open Vuo Manual in HelpViewer.app: %s", description);
924  free(description);
925  }
926  CFRelease(relativePath);
927 }
928 
937 VuoEditorWindow * VuoEditor::createEditorWindow(QString filename, bool existingComposition, VuoProtocol *activeProtocol)
938 {
939  string compositionAsString = (existingComposition ? VuoFileUtilities::readFileToString(filename.toStdString()) : "");
940  VuoEditorWindow *w = createEditorWindow(filename, filename, compositionAsString, activeProtocol);
941  return w;
942 }
943 
953 VuoEditorWindow * VuoEditor::createEditorWindow(QString documentIdentifier, QString filename, const string &compositionAsString,
954  VuoProtocol *activeProtocol, string nodeClassToHighlight)
955 {
956  VuoEditorWindow *w;
957  try
958  {
959  // @todo Take into account the current node library visibility state in deciding
960  // whether to dock a node library to the newly opened window.
961  // See: https://b33p.net/kosada/node/3464, https://b33p.net/kosada/node/3002,
962  // https://b33p.net/kosada/node/3854, https://b33p.net/kosada/node/3087.
963  w = new VuoEditorWindow(documentIdentifier, filename,
964  compositionAsString,
965  nodeLibraryDisplayMode,
966  getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
967  currentFloatingNodeLibrary,
968  activeProtocol,
969  nodeClassToHighlight);
970  }
971  catch (const VuoCompilerException &e)
972  {
973  VuoErrorDialog::show(NULL, tr("%1 can't be opened").arg(QFileInfo(filename).fileName()), e.what());
974  return NULL;
975  }
976 
977  documentIdentifierAssigned[filename.isEmpty() ? documentIdentifier : filename] = w;
978 
979  const int defaultWindowXOffset = 40;
980  const int defaultWindowYOffset = 40;
981 
983  if (!activeWindow)
984  {
985  // If this will be the only window…
986 
987  if (ownedNodeLibrary && nodeLibraryCurrentlyVisible && !nodeLibraryCurrentlyDocked)
988  {
989  // If the node library is visible and floating, make sure the new window doesn't cover it up.
990  int initialWindowXOffset = defaultWindowXOffset;
991  int initialWindowYOffset = defaultWindowYOffset;
992 
993  int nodeLibraryLeftPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topLeft()).x();
994  int nodeLibraryRightPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topRight()).x();
995  int nodeLibraryCenterPos = 0.5*(nodeLibraryLeftPos + nodeLibraryRightPos);
996 
997  int availableSpaceLeftBoundary = QApplication::desktop()->availableGeometry(w).left();
998  int availableSpaceRightBoundary = QApplication::desktop()->availableGeometry(w).right();
999 
1000  bool nodeLibraryCloserToLeft = (nodeLibraryCenterPos - availableSpaceLeftBoundary) <
1001  (availableSpaceRightBoundary - nodeLibraryCenterPos);
1002 
1003  const int horizontalBuffer = 10;
1004  bool spaceForWindowToRightOfNodeLibrary = (availableSpaceRightBoundary - nodeLibraryRightPos) >=
1005  (w->geometry().width() + horizontalBuffer);
1006 
1007  // Leave enough space to the left of the initial window to display the floating library, if applicable
1008  // and it makes sense to do so.
1009  if ((nodeLibraryCloserToLeft && spaceForWindowToRightOfNodeLibrary))
1010  initialWindowXOffset = nodeLibraryRightPos + horizontalBuffer;
1011 
1012  // If the floating node library's top edge is higher than the default top edge position
1013  // for composition windows, vertically align the first composition window with the top edge
1014  // of the node library instead of using the default.
1015  // ownedNodeLibrary->titleBarWidget() doesn't reliably return non-NULL; hard-code title bar height instead of
1016  // actually retrieving it.
1017  const int titleBarHeight = 16;
1018  int nodeLibraryTopPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topLeft()).y() - titleBarHeight;
1019 
1020  if (nodeLibraryTopPos < defaultWindowYOffset)
1021  initialWindowYOffset = nodeLibraryTopPos;
1022 
1023  w->move(initialWindowXOffset, initialWindowYOffset);
1024  }
1025  else
1026  {
1027  // If the node library is hidden or docked, just let the window system position the new window.
1028  }
1029 
1030  yScreenSpaceShortage = qMax(0, (w->geometry().bottom()) - QApplication::desktop()->availableGeometry(w).bottom() + 5);
1031  }
1032  else
1033  {
1034  // If there's already a window open, offset the new window relative to the active window,
1035  // to make it obvious that there are multiple windows.
1036  w->move(activeWindow->pos() + QPoint(defaultWindowXOffset, defaultWindowYOffset));
1037  }
1038 
1039  // Workaround for bug that meant that maximizing or unmaximizing a window required a subsequent click to
1040  // the window before further interaction with it was possible, if the window height was limited by
1041  // available screen real estate. https://b33p.net/kosada/node/6914
1042  w->resize(w->width(), w->height()-yScreenSpaceShortage);
1043 
1044  // Keep the floating node library's node class list synchronized with the currently active composition window.
1045  // Make sure to set this up before the window's show() call.
1046  connect(w, &VuoEditorWindow::windowActivated, this, &VuoEditor::updateFloatingNodeLibraryModules);
1047  connect(w, &VuoEditorWindow::windowDeactivated, this, &VuoEditor::updateFloatingNodeLibraryModules);
1048 
1049  w->show();
1050 
1051 #if VUO_PRO
1052  createEditorWindow_Pro(w);
1053 #endif
1054 
1055  // Update the application UI (Open Recent menu, Window menu, macOS dock context menu)
1056  // whenever an editor window is created, destroyed, or modifies its "Document" list.
1058 
1059  // Update the global node library display mode whenever it is changed for any node library instance.
1060  connect(w->getOwnedNodeLibrary(), &VuoNodeLibrary::changedIsHumanReadable, static_cast<VuoEditor *>(qApp), &VuoEditor::updateNodeLibraryDisplayMode);
1061 
1062  // Keep node library visibility state synchronized among editor windows.
1063  enableGlobalStateConformanceToLibrary(w->getOwnedNodeLibrary());
1064 
1065  try
1066  {
1067  VuoCompilerIssues *issues = new VuoCompilerIssues();
1069  delete issues;
1070  }
1071  catch (const VuoCompilerException &e)
1072  {
1073  QString summary = tr("This composition contains nodes that aren't installed.");
1074 
1075  QString details = tr("<p>If you save the composition while it contains these nodes, some information will be lost.</p>"
1076  "<p>Try searching for these nodes in the <a href=\"%1\">Node Gallery</a>. "
1077  "Or if you don't need them, just delete them from the composition.</p>")
1078  .arg("https://vuo.org/nodes")
1079  + QString::fromStdString(e.getIssues()->getHint(true));
1080 
1081  QString disclosureDetails = QString::fromStdString(e.getIssues()->getShortDescription(false));
1082 
1083  VuoErrorDialog::show(w, summary, details, disclosureDetails);
1084  }
1085 
1086  return w;
1087 }
1088 
1092 void VuoEditor::registerOpenDocument(QMainWindow *window)
1093 {
1094  // Not sure if this is necessary, since menus that display currently-open documents already get updated on QMenu::aboutToShow.
1095  updateUI();
1096  connect(window, &QMainWindow::destroyed, this, &VuoEditor::updateUI);
1097 
1098  // Keep the list of recent documents synchronized among the "File > Open Recent" menu of each editor window.
1100  connect(VuoEditorUtilities::getFileMenuForWindow(window), &QMenu::aboutToShow, this, &VuoEditor::pruneAllOpenRecentFileMenus);
1102 }
1103 
1108 {
1109  if (!compiler)
1110  {
1111  // Wait for the constructor to finish.
1112  dispatch_async(dispatch_get_main_queue(), ^{
1113  // Don't bother opening the standard new untitled composition during startup
1114  // if another composition has opened in the meantime
1115  // (by dropping a composition file on the Vuo.app icon,
1116  // or double-clicking a composition file in Finder after double-clicking Vuo.app,
1117  // or by passing a filename as a command-line argument).
1119  return;
1120 #if VUO_PRO
1121  if (!closeWelcomeWindow())
1122  return;
1123 #endif
1124  QString identifier = assignUntitledDocumentIdentifier();
1125  VUserLog("%s: New empty composition", identifier.toUtf8().data());
1126  createEditorWindow(identifier, "", "", NULL);
1127  });
1128  return;
1129  }
1130 
1131 #if VUO_PRO
1132  if (!closeWelcomeWindow())
1133  return;
1134 #endif
1135 
1136  QString identifier = assignUntitledDocumentIdentifier();
1137  VUserLog("%s: New empty composition", identifier.toUtf8().data());
1138  createEditorWindow(identifier, "", "", NULL);
1139 }
1140 
1145 VuoEditorWindow * VuoEditor::newCompositionWithContent(string content, string compositionDir)
1146 {
1147  QString identifier = assignUntitledDocumentIdentifier();
1148  VUserLog("%s: New Composition with content", identifier.toUtf8().data());
1149  return createEditorWindow(identifier, QString::fromStdString(compositionDir), content, NULL);
1150 }
1151 
1157 {
1158  QAction *sender = (QAction *)QObject::sender();
1159  VuoProtocol *selectedProtocol = static_cast<VuoProtocol *>(sender->data().value<void *>());
1160 
1161  closeUnmodifiedUntitledComposition();
1162 #if VUO_PRO
1163  if (!closeWelcomeWindow())
1164  return;
1165 #endif
1166 
1167  QString identifier = assignUntitledDocumentIdentifier();
1168  VUserLog("%s: New Composition with %s protocol", identifier.toUtf8().data(), selectedProtocol->getName().c_str());
1169  VuoEditorWindow *w = createEditorWindow(identifier, false, selectedProtocol);
1170 
1171  if (selectedProtocol && w && w->getCurrentNodeLibrary())
1172  {
1173  string nodeLibraryFilterText = getFilterTextForTemplate(selectedProtocol->getId());
1174  if (!nodeLibraryFilterText.empty())
1175  {
1176  w->getCurrentNodeLibrary()->searchForText(nodeLibraryFilterText.c_str());
1178  }
1179  }
1180 }
1181 
1187 {
1188  QAction *sender = (QAction *)QObject::sender();
1189  QString selectedTemplate = static_cast<QString>(sender->data().value<QString>());
1190  string templatePath = VuoFileUtilities::getVuoFrameworkPath() + "/Resources/" + selectedTemplate.toUtf8().constData() + ".vuo";
1191 
1192  closeUnmodifiedUntitledComposition();
1193 #if VUO_PRO
1194  if (!closeWelcomeWindow())
1195  return;
1196 #endif
1197 
1198  string compositionAsString = VuoFileUtilities::readFileToString(templatePath);
1199  QString identifier = assignUntitledDocumentIdentifier();
1200  VUserLog("%s: New Composition with %s template", identifier.toUtf8().data(), selectedTemplate.toUtf8().data());
1201  VuoEditorWindow *w = createEditorWindow(identifier, "", compositionAsString, NULL);
1202 
1203  if (w && w->getCurrentNodeLibrary())
1204  {
1205  string nodeLibraryFilterText = getFilterTextForTemplate(selectedTemplate.toUtf8().constData());
1206  if (!nodeLibraryFilterText.empty())
1207  {
1208  w->getCurrentNodeLibrary()->searchForText(nodeLibraryFilterText.c_str());
1210  }
1211  }
1212 }
1213 
1221 string VuoEditor::getFilterTextForTemplate(string templateID)
1222 {
1223  map<string, string> filterTextForTemplate;
1224 
1225  // Protocols
1226  filterTextForTemplate[VuoProtocol::imageFilter] = "image";
1227  filterTextForTemplate[VuoProtocol::imageGenerator] = "image";
1228  filterTextForTemplate[VuoProtocol::imageTransition] = "image";
1229 
1230  // Window templates
1231  filterTextForTemplate["imageTemplate"] = "vuo.image";
1232  filterTextForTemplate["layersTemplate"] = "vuo.layer shape";
1233  filterTextForTemplate["sceneTemplate"] = "vuo.scene shape";
1234 
1235  // Export templates
1236  filterTextForTemplate["movie"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1237  filterTextForTemplate["screensaver"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1238 
1239  filterTextForTemplate["FFGLSource"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1240  filterTextForTemplate["FFGLEffect"] = filterTextForTemplate[VuoProtocol::imageFilter];
1241  filterTextForTemplate["FFGLBlendMode"] = filterTextForTemplate[VuoProtocol::imageTransition];
1242 
1243  filterTextForTemplate["FxPlugGenerator"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1244  filterTextForTemplate["FxPlugEffect"] = filterTextForTemplate[VuoProtocol::imageFilter];
1245  filterTextForTemplate["FxPlugTransition"] = filterTextForTemplate[VuoProtocol::imageTransition];
1246 
1247  map<string, string>::iterator i = filterTextForTemplate.find(templateID);
1248  if (i != filterTextForTemplate.end())
1249  return i->second;
1250  else
1251  return "";
1252 }
1253 
1257 QString VuoEditor::assignUntitledDocumentIdentifier(void)
1258 {
1259  QString uniqueDocumentIdentifier = VuoEditorWindow::untitledComposition;
1260  int documentIdentifierInstanceNum = 1;
1261 
1262  while(documentIdentifierAssigned[uniqueDocumentIdentifier])
1263  {
1264  std::ostringstream oss;
1265  oss << " " << ++documentIdentifierInstanceNum;
1266  uniqueDocumentIdentifier = VuoEditorWindow::untitledComposition + QString::fromStdString(oss.str());
1267  }
1268 
1269  return uniqueDocumentIdentifier;
1270 }
1271 
1276 {
1277  QFileDialog d(NULL, "", "", "Vuo Composition (*.vuo);;Fragment Shader (*.fs)");
1278  d.setFileMode(QFileDialog::ExistingFiles);
1279 
1280  // At Qt 5.2.1, the file dialogue's history() always seems to be empty.
1281  // See https://b33p.net/kosada/node/7302 .
1282  //if (d.history().isEmpty())
1283  // d.setDirectory(getDefaultCompositionStorageDirectory());
1284 
1285  QStringList fileNames;
1286  if (d.exec() == QDialog::Accepted)
1287  fileNames = d.selectedFiles();
1288 
1289  foreach (QString fileName, fileNames)
1290  openFileWithName(fileName);
1291 }
1292 
1296 QMainWindow * VuoEditor::openFileWithName(QString filename, bool addToRecentFileMenu)
1297 {
1298  closeUnmodifiedUntitledComposition();
1299 #if VUO_PRO
1300  if (!closeWelcomeWindow())
1301  {
1302  QString fileURL = QString("file://").append(filename);
1303  queuedCompositionsToOpen.push_back(QString("file://").append(filename));
1304  return NULL;
1305  }
1306 #endif
1307 
1308  QMainWindow *existing = VuoEditorUtilities::existingWindowWithFile(filename);
1309  if (existing)
1310  {
1312  return existing;
1313  }
1314 
1315  if (! VuoFileUtilities::fileIsReadable(filename.toUtf8().constData()))
1316  {
1317  VuoErrorDialog::show(NULL, tr("You do not have permission to open the document \"%1\".").arg(QFileInfo(filename).fileName()), "");
1318  return NULL;
1319  }
1320 
1321  string dir, file, ext;
1322  VuoFileUtilities::splitPath(filename.toStdString(), dir, file, ext);
1323  bool isComposition = VuoFileUtilities::isCompositionExtension(ext);
1324 
1325  QMainWindow *window = nullptr;
1326  if (isComposition)
1327  {
1328  VUserLog("%s.%s: Open", file.c_str(), ext.c_str());
1329  window = createEditorWindow(filename, true);
1330  dynamic_cast<VuoEditorWindow *>(window)->setIncludeInRecentFileMenu(addToRecentFileMenu);
1331  }
1332  else
1333  {
1334  try
1335  {
1336  window = new VuoCodeWindow(filename.toStdString());
1337  dynamic_cast<VuoCodeWindow *>(window)->setIncludeInRecentFileMenu(addToRecentFileMenu);
1338  }
1339  catch (VuoException &e)
1340  {
1341  VuoErrorDialog::show(NULL, tr("Couldn't open the shader."), e.what());
1342  return nullptr;
1343  }
1344 
1345  window->show();
1346  }
1347 
1348  if (addToRecentFileMenu)
1350 
1351  return window;
1352 }
1353 
1361 void VuoEditor::openExampleComposition(QString filename, VuoNodeSet *nodeSet, string nodeClassToHighlight)
1362 {
1363  VUserLog("%s: Open", filename.toUtf8().data());
1364 
1365  string filenameStr = filename.toStdString();
1366  string workingDir = VuoFileUtilities::getTmpDir();
1367  string compositionContents = nodeSet->getExampleCompositionContents(filenameStr);
1368  QString compositionPath = QString::fromStdString(workingDir + "/" + filenameStr);
1369 
1370  nodeSet->extractExampleCompositionResources(workingDir);
1371  closeUnmodifiedUntitledComposition();
1372 #if VUO_PRO
1373  if (!closeWelcomeWindow())
1374  return;
1375 #endif
1376  createEditorWindow(compositionPath, compositionPath, compositionContents, NULL, nodeClassToHighlight);
1377 
1378  QString exampleCompositionUrl = QString(VuoEditor::vuoExampleCompositionScheme)
1379  .append("://")
1380  .append(nodeSet->getName().c_str())
1381  .append("/")
1382  .append(filename);
1383 
1384  addFileToAllOpenRecentFileMenus(exampleCompositionUrl);
1385 }
1386 
1392 void VuoEditor::closeUnmodifiedUntitledComposition()
1393 {
1395  if (untitled)
1396  untitled->close();
1397 }
1398 
1403 {
1404  // If there is an editor window open and the user has somehow accessed the
1405  // application-global menu anyway, trigger the equivalent menu item of the
1406  // topmost editor window instead.
1408  if (topmostWindow)
1409  topmostWindow->on_showNodeLibrary_triggered();
1410 
1411  // Unhiding the top-level node class library triggers node-library-floating mode globally.
1412  else
1413  {
1414  if ((ownedNodeLibrary->isHidden()))
1415  {
1416  VUserLog("Show node library");
1417 
1418  designateNewFloatingNodeLibrary(ownedNodeLibrary);
1419  updateGlobalNodeLibraryState(true, false);
1420  }
1421 
1422  ownedNodeLibrary->focusTextFilter();
1423  }
1424 }
1425 
1429 void VuoEditor::conformToGlobalNodeLibraryVisibility(VuoNodeLibrary::nodeLibraryState visibility,
1430  VuoNodeLibrary *floater)
1431 {
1432  if ((visibility == VuoNodeLibrary::nodeLibraryHidden) ||
1433  (visibility == VuoNodeLibrary::nodeLibraryDocked))
1434  {
1435  ownedNodeLibrary->releaseDocumentationWidget();
1436  ownedNodeLibrary->setVisible(false);
1437  }
1438 
1439  else if (visibility == VuoNodeLibrary::nodeLibraryFloating)
1440  {
1441  // If our own node library was the one that initiated global
1442  // floating-node-library mode by being undocked, let it float.
1443  // It is now the single application-wide floating library.
1444  if (ownedNodeLibrary == floater)
1445  {
1446  if (! ownedNodeLibrary->isFloating())
1447  ownedNodeLibrary->setFloating(true);
1448 
1449  //ownedNodeLibrary->setVisible(true);
1450  ownedNodeLibrary->prepareAndMakeVisible();
1451  ownedNodeLibrary->setFocus();
1452  ownedNodeLibrary->displayPopoverForCurrentNodeClass();
1453  }
1454  }
1455 
1456  updateUI();
1457 }
1458 
1462 void VuoEditor::updateUI()
1463 {
1464  if (!uiInitialized)
1465  return;
1466 
1467  QList<QMainWindow *> openWindows = VuoEditorUtilities::getOpenEditingWindowsStacked();
1468  QMainWindow *frontWindow = (openWindows.empty() ? nullptr : openWindows.front());
1469 
1470  // Update the list of open documents in the "Window" menu, including the checkmarks for the actions.
1471  menuWindow->clear();
1472  populateWindowMenu(menuWindow, frontWindow);
1473 
1474 #ifdef __APPLE__
1475  // Update the list of open documents in the OS X dock context menu, using the same actions.
1476  dockContextMenu->clear();
1477  for (QMainWindow *openWindow : VuoEditorUtilities::getOpenEditingWindows())
1478  dockContextMenu->addAction(VuoEditorUtilities::getRaiseDocumentActionForWindow(openWindow));
1479 #endif
1480 
1481  // Update the "enabled" status of the canvas transparency menu items.
1482  foreach (QAction *action, canvasTransparencyOptions->actions())
1483  action->setEnabled(!action->isChecked());
1484 }
1485 
1489 bool VuoEditor::event(QEvent * e)
1490 {
1491  switch (e->type())
1492  {
1493  case QEvent::FileOpen:
1494  openUrl(static_cast<QFileOpenEvent *>(e)->url().toString());
1495  return true;
1496 
1497  case QEvent::ApplicationActivate:
1498  emit activeApplicationStateChanged(true);
1499  return true;
1500 
1501  case QEvent::ApplicationDeactivate:
1502  emit activeApplicationStateChanged(false);
1503  return true;
1504 
1505  // macOS Dock context menu > Quit
1506  case QEvent::Close:
1507  e->ignore();
1508  quitCleanly();
1509  return true;
1510 
1511  default:
1512  return QApplication::event(e);
1513  }
1514 }
1515 
1521 {
1522  QAction *sender = (QAction *)QObject::sender();
1523  QDesktopServices::openUrl(sender->data().toUrl());
1524 }
1525 
1531 {
1532  QAction *sender = (QAction *)QObject::sender();
1533  QString filePath = sender->data().value<QString>();
1534 
1535  if (! filePath.startsWith("file://"))
1536  filePath = QString("file://").append(filePath);
1537 
1538  openUrl(filePath);
1539 }
1540 
1546 {
1547  QAction *sender = (QAction *)QObject::sender();
1548  QString filePath = sender->data().value<QString>();
1549  moveFileToTrash(filePath);
1550 }
1551 
1555 void VuoEditor::moveFileToTrash(QString filePath)
1556 {
1557  try
1558  {
1559  VuoFileUtilities::moveFileToTrash(filePath.toUtf8().constData());
1560  }
1561  catch (const VuoException &e)
1562  {
1563  if (VuoFileUtilities::fileExists(filePath.toUtf8().constData()))
1564  VuoErrorDialog::show(NULL, VuoEditor::tr("Couldn't move file to the Trash"), e.what());
1565  }
1566 }
1567 
1574 void VuoEditor::openUrl(const QString &url)
1575 {
1576  QUrl fileUrl(url);
1577 
1578  if (fileUrl.isValid() && fileUrl.scheme() == VuoEditor::vuoExampleCompositionScheme)
1579  {
1580  if (!uiInitialized)
1581  queuedCompositionsToOpen.push_back(url);
1582  else
1584  }
1585  else
1586  {
1587  string dir, file, ext;
1588  VuoFileUtilities::splitPath(fileUrl.toLocalFile().toUtf8().constData(), dir, file, ext);
1589 
1590  if (QString(ext.c_str()) == VuoEditor::vuoNodeClassFileExtension)
1591  {
1592  string installedNodeDir = VuoFileUtilities::getUserModulesPath();
1593  string sourceNodePath = dir + "/" + file + "." + ext;
1594  string targetNodePath = installedNodeDir + "/" + file + "." + ext;
1595 
1596  VuoFileUtilities::makeDir(installedNodeDir);
1597 
1598  if (VuoFileUtilities::fileExists(targetNodePath))
1599  {
1600  QMessageBox messageBox;
1601  string errorSummary = "A node named \"" + file + "\" already exists. Do you want to replace it with the one you're installing?";
1602 
1603  // On OS X, this combination of flags displays the minimize, maximimize, and close buttons, but all in a disabled state.
1604  messageBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint);
1605 
1606  messageBox.setTextFormat(Qt::RichText);
1607  messageBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
1608  messageBox.setButtonText(QMessageBox::Discard, tr("Replace"));
1609  messageBox.setButtonText(QMessageBox::Cancel, tr("Cancel"));
1610  messageBox.setDefaultButton(QMessageBox::Discard);
1611  messageBox.setText(errorSummary.c_str());
1612  messageBox.setStyleSheet("#qt_msgbox_informativelabel, QMessageBoxDetailsText { font-weight: normal; font-size: 11pt; }");
1613 
1614  // Give the "Cancel" button keyboard focus (without "Default" status) so that it can be activated by spacebar.
1615  static_cast<QPushButton *>(messageBox.button(QMessageBox::Cancel))->setAutoDefault(false);
1616  messageBox.button(QMessageBox::Cancel)->setFocus();
1617 
1618  messageBox.setIconPixmap(VuoEditorUtilities::vuoLogoForDialogs());
1619 
1620  if (messageBox.exec() == QMessageBox::Discard)
1621  {
1622  // Move pre-existing module to the trash.
1623  try
1624  {
1625  VuoFileUtilities::moveFileToTrash(targetNodePath);
1626  }
1627  catch (VuoException &e)
1628  {
1629  if (VuoFileUtilities::fileExists(targetNodePath))
1630  {
1631  VuoErrorDialog::show(NULL, tr("There was a problem installing the node."), e.what());
1632  return;
1633  }
1634  }
1635 
1636  }
1637  else
1638  return;
1639  }
1640 
1641  // Install the node class in the "User Modules" directory.
1642  try
1643  {
1644  VUserLog("Install node %s.%s", file.c_str(), ext.c_str());
1645 
1646  VuoFileUtilities::copyFile(sourceNodePath, targetNodePath);
1647  }
1648  catch (VuoException &e)
1649  {
1650  VuoErrorDialog::show(NULL, tr("There was a problem installing the node."), e.what());
1651  return;
1652  }
1653 
1654  if (VuoFileUtilities::fileExists(targetNodePath))
1655  {
1656  QMessageBox installationSuccessMessageBox;
1657  installationSuccessMessageBox.setText(tr("Your node has been installed!"));
1658  installationSuccessMessageBox.setIconPixmap(VuoEditorUtilities::vuoLogoForDialogs());
1659  installationSuccessMessageBox.exec();
1660  }
1661  }
1662 
1664  {
1665  if (!uiInitialized)
1666  queuedCompositionsToOpen.push_back(url);
1667  else
1668  openFileWithName(fileUrl.toLocalFile());
1669  }
1670 
1671  else
1672  QDesktopServices::openUrl(url);
1673  }
1674 }
1675 
1680 {
1681  shaderDocumentationVisible = isVisible;
1682  settings->setValue(shaderDocumentationVisibilitySettingsKey, shaderDocumentationVisible);
1683 }
1684 
1689 {
1690  return shaderDocumentationVisible;
1691 }
1692 
1696 void VuoEditor::applyStoredMovieExportSettings()
1697 {
1698  movieExportWidth = (settings->contains(movieExportWidthSettingsKey)? settings->value(movieExportWidthSettingsKey).toInt() : 1024);
1699  movieExportHeight = (settings->contains(movieExportHeightSettingsKey)? settings->value(movieExportHeightSettingsKey).toInt() : 768);
1700  movieExportTime = (settings->contains(movieExportTimeSettingsKey)? settings->value(movieExportTimeSettingsKey).toDouble() : 0.);
1701  movieExportDuration = (settings->contains(movieExportDurationSettingsKey)? settings->value(movieExportDurationSettingsKey).toDouble() : 10.);
1702  movieExportFramerate = (settings->contains(movieExportFramerateSettingsKey)? settings->value(movieExportFramerateSettingsKey).toDouble() : 30.);
1703  movieExportSpatialSupersample = (settings->contains(movieExportSpatialSupersampleSettingsKey)? settings->value(movieExportSpatialSupersampleSettingsKey).toInt() : 1.);
1704  movieExportTemporalSupersample = (settings->contains(movieExportTemporalSupersampleSettingsKey)? settings->value(movieExportTemporalSupersampleSettingsKey).toInt() : 1.);
1705  movieExportShutterAngle = (settings->contains(movieExportShutterAngleSettingsKey)? static_cast<float>(settings->value(movieExportShutterAngleSettingsKey).toDouble()) : 360.);
1706  movieExportImageFormat = (settings->contains(movieExportImageFormatSettingsKey)? settings->value(movieExportImageFormatSettingsKey).toString() : "H.264");
1707  movieExportQuality = (settings->contains(movieExportQualitySettingsKey)? settings->value(movieExportQualitySettingsKey).toDouble() : 1.);
1708 }
1709 
1713 void VuoEditor::updateGlobalMovieExportSettings(int width,
1714  int height,
1715  double time,
1716  double duration,
1717  double framerate,
1718  int spatialSupersample,
1719  int temporalSupersample,
1720  float shutterAngle,
1721  QString imageFormat,
1722  double quality)
1723 {
1724  if (width != movieExportWidth)
1725  {
1726  movieExportWidth = width;
1727  settings->setValue(movieExportWidthSettingsKey, movieExportWidth);
1728  }
1729 
1730  if (height != movieExportHeight)
1731  {
1732  movieExportHeight = height;
1733  settings->setValue(movieExportHeightSettingsKey, movieExportHeight);
1734  }
1735 
1736  if (time != movieExportTime)
1737  {
1738  movieExportTime = time;
1739  settings->setValue(movieExportTimeSettingsKey, movieExportTime);
1740  }
1741 
1742  if (duration != movieExportDuration)
1743  {
1744  movieExportDuration = duration;
1745  settings->setValue(movieExportDurationSettingsKey, movieExportDuration);
1746  }
1747 
1748  if (framerate != movieExportFramerate)
1749  {
1750  movieExportFramerate = framerate;
1751  settings->setValue(movieExportFramerateSettingsKey, movieExportFramerate);
1752  }
1753 
1754  if (spatialSupersample != movieExportSpatialSupersample)
1755  {
1756  movieExportSpatialSupersample = spatialSupersample;
1757  settings->setValue(movieExportSpatialSupersampleSettingsKey, movieExportSpatialSupersample);
1758  }
1759 
1760  if (temporalSupersample != movieExportTemporalSupersample)
1761  {
1762  movieExportTemporalSupersample = temporalSupersample;
1763  settings->setValue(movieExportTemporalSupersampleSettingsKey, movieExportTemporalSupersample);
1764  }
1765 
1766  if (shutterAngle != movieExportShutterAngle)
1767  {
1768  movieExportShutterAngle = shutterAngle;
1769  settings->setValue(movieExportShutterAngleSettingsKey, static_cast<double>(movieExportShutterAngle));
1770  }
1771 
1772  if (imageFormat != movieExportImageFormat)
1773  {
1774  movieExportImageFormat = imageFormat;
1775  settings->setValue(movieExportImageFormatSettingsKey, movieExportImageFormat);
1776  }
1777 
1778  if (quality != movieExportQuality)
1779  {
1780  movieExportQuality = quality;
1781  settings->setValue(movieExportQualitySettingsKey, movieExportQuality);
1782  }
1783 }
1784 
1789  int &height,
1790  double &time,
1791  double &duration,
1792  double &framerate,
1793  int &spatialSupersample,
1794  int &temporalSupersample,
1795  float &shutterAngle,
1796  QString &imageFormat,
1797  double &quality)
1798 {
1799  width = movieExportWidth;
1800  height = movieExportHeight;
1801  time = movieExportTime;
1802  duration = movieExportDuration;
1803  framerate = movieExportFramerate;
1804  spatialSupersample = movieExportSpatialSupersample;
1805  temporalSupersample = movieExportTemporalSupersample;
1806  shutterAngle = movieExportShutterAngle;
1807  imageFormat = movieExportImageFormat;
1808  quality = movieExportQuality;
1809 }
1810 
1811 
1816 VuoNodeLibrary::nodeLibraryState VuoEditor::getGlobalNodeLibraryStateForAttributes(bool visible, bool docked)
1817 {
1818  return ((! visible)? VuoNodeLibrary::nodeLibraryHidden :
1819  (docked? VuoNodeLibrary::nodeLibraryDocked :
1820  VuoNodeLibrary::nodeLibraryFloating));
1821 }
1822 
1828 void VuoEditor::updateNodeLibraryDisplayMode(bool humanReadable)
1829 {
1830  nodeLibraryDisplayMode = (humanReadable? VuoNodeLibrary::displayByName : VuoNodeLibrary::displayByClass);
1831  settings->setValue(nodeLibraryDisplayModeSettingsKey, nodeLibraryDisplayMode);
1832 
1833  // Exception to the non-propagation policy for this setting:
1834  // If the top-level node library is not currently visible, do synchronize its display mode so that
1835  // it is up-to-date if/when it next appears.
1836  if ((ownedNodeLibrary->isHidden()) && (ownedNodeLibrary->getHumanReadable() != humanReadable))
1837  {
1838  ownedNodeLibrary->setHumanReadable(humanReadable);
1839  ownedNodeLibrary->updateUI();
1840  updateUI();
1841  }
1842 }
1843 
1847 void VuoEditor::updateGlobalNodeLibraryFloatingPosition(QPoint newPos)
1848 {
1849  nodeLibraryFloatingPosition = newPos;
1850  settings->setValue(nodeLibraryFloatingPositionSettingsKey, nodeLibraryFloatingPosition);
1851 }
1852 
1856 void VuoEditor::updateGlobalNodeLibraryWidth(int newWidth)
1857 {
1858  nodeLibraryWidth = newWidth;
1859  settings->setValue(nodeLibraryWidthSettingsKey, nodeLibraryWidth);
1860 }
1861 
1865 void VuoEditor::updateGlobalNodeLibraryHeight(int newHeight)
1866 {
1867  nodeLibraryHeight = newHeight;
1868  settings->setValue(nodeLibraryHeightSettingsKey, nodeLibraryHeight);
1869 }
1870 
1874 void VuoEditor::updateGlobalNodeDocumentationPanelHeight(int newSize)
1875 {
1876  nodeDocumentationPanelHeight = newSize;
1877  settings->setValue(nodeDocumentationPanelHeightSettingsKey, nodeDocumentationPanelHeight);
1878 }
1879 
1883 void VuoEditor::updateGlobalNodeLibraryVisibilityState(bool visible)
1884 {
1885  VUserLog("%s node library", visible ? "Show" : "Hide");
1886 
1887  // If transitioning to a visible state, revert to the docking state from the last time the node library was visible.
1888  bool updatedDockingState = ((visible && (! nodeLibraryCurrentlyVisible))? previousVisibleNodeLibraryStateWasDocked : nodeLibraryCurrentlyDocked);
1889  updateGlobalNodeLibraryState(visible, updatedDockingState);
1890 }
1891 
1895 void VuoEditor::updateGlobalNodeLibraryDockedState(bool floating)
1896 {
1897  VUserLog("%s node library", floating ? "Detach" : "Attach");
1898 
1899  VuoNodeLibrary *sender = (VuoNodeLibrary *)QObject::sender();
1900  VuoNodeLibrary *floater = (floating? sender : NULL);
1901  designateNewFloatingNodeLibrary(floater);
1902  updateGlobalNodeLibraryState(this->nodeLibraryCurrentlyVisible, (! floating));
1903 
1904  if (floating)
1905  updateFloatingNodeLibraryModules();
1906  else
1907  updateDockedNodeLibraryModules();
1908 }
1909 
1915 void VuoEditor::updateGlobalNodeLibraryState(bool visible, bool docked)
1916 {
1917  VuoNodeLibrary::nodeLibraryState currentNodeLibraryState = getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked);
1918  VuoNodeLibrary::nodeLibraryState updatedNodeLibraryState = getGlobalNodeLibraryStateForAttributes(visible, docked);
1919 
1920  if (currentNodeLibraryState != VuoNodeLibrary::nodeLibraryHidden)
1921  previousVisibleNodeLibraryStateWasDocked = nodeLibraryCurrentlyDocked;
1922 
1923  nodeLibraryCurrentlyVisible = visible;
1924  nodeLibraryCurrentlyDocked = docked;
1925  if (docked)
1926  currentFloatingNodeLibrary = NULL;
1927 
1928  settings->setValue(nodeLibraryVisibilityStateSettingsKey, nodeLibraryCurrentlyVisible);
1929  settings->setValue(nodeLibraryDockingStateSettingsKey, nodeLibraryCurrentlyDocked);
1930 
1931  emit globalNodeLibraryStateChanged(updatedNodeLibraryState, currentFloatingNodeLibrary, false);
1932 }
1933 
1938 void VuoEditor::updateFloatingNodeLibraryModules()
1939 {
1940  if (!currentFloatingNodeLibrary)
1941  return;
1942 
1944  if (activeWindow)
1945  {
1946  VuoModuleManager *activeCompositionModuleManager = activeWindow->getComposition()->getModuleManager();
1947  if (activeCompositionModuleManager->getNodeLibrary() != currentFloatingNodeLibrary)
1948  {
1949  // Tell other open editor windows to stop sending updates to the floating node library, if they were doing so.
1950  QList<VuoEditorWindow *> openWindows = VuoEditorUtilities::getOpenCompositionEditingWindows();
1951  foreach (VuoEditorWindow *window, openWindows)
1952  {
1953  if (window != activeWindow)
1954  {
1955  VuoModuleManager *windowModuleManager = window->getComposition()->getModuleManager();
1956  if (windowModuleManager->getNodeLibrary() == currentFloatingNodeLibrary)
1957  windowModuleManager->setNodeLibrary(NULL);
1958  }
1959  }
1960 
1961  // Tell the top-level app to stop sending updates to the floating node library, if it was doing so.
1962  if (moduleManager->getNodeLibrary() == currentFloatingNodeLibrary)
1963  moduleManager->setNodeLibrary(NULL);
1964  }
1965  }
1966 
1967  VuoModuleManager *updatedModuleManager = (activeWindow? activeWindow->getComposition()->getModuleManager() : this->moduleManager);
1968  if (updatedModuleManager->getNodeLibrary() != currentFloatingNodeLibrary)
1969  {
1970  // Document the node library's current state (selected and documented items, filter text) before resetting it.
1971  QString origFilterText;
1972  set<string> origSelectedNodeClasses;
1973  string origDocumentedNodeClass;
1974  currentFloatingNodeLibrary->getState(origFilterText, origSelectedNodeClasses, origDocumentedNodeClass);
1975 
1976  // Populate the node library with its new content.
1977  currentFloatingNodeLibrary->clearNodeClassList();
1978  updatedModuleManager->setNodeLibrary(currentFloatingNodeLibrary);
1979  updatedModuleManager->updateWithAlreadyLoadedModules();
1980 
1981  // Now restore the node library's original state.
1982  currentFloatingNodeLibrary->setState(origFilterText, origSelectedNodeClasses, origDocumentedNodeClass);
1983 
1984  // If the documentation pane displayed previously is no longer relevant, display
1985  // whatever documentation is most appropriate now.
1986  QString newFilterText;
1987  set<string> newSelectedNodeClasses;
1988  string newDocumentedNodeClass;
1989  currentFloatingNodeLibrary->getState(newFilterText, newSelectedNodeClasses, newDocumentedNodeClass);
1990  if (newDocumentedNodeClass.empty())
1991  {
1992  if (activeWindow)
1993  activeWindow->displayAppropriateDocumentation();
1994  else
1995  currentFloatingNodeLibrary->displayPopoverForCurrentNodeClass();
1996  }
1997  }
1998 }
1999 
2005 {
2006  if (moduleManager->getNodeLibrary())
2007  {
2008  VuoModuleManager::CallbackType subcompositionCreated = ^void (void) {
2009  moduleManager->getNodeLibrary()->highlightNodeClass(nodeClassName);
2010  };
2011  moduleManager->doNextTimeNodeClassIsLoaded(nodeClassName, subcompositionCreated);
2012  }
2013 
2014  QList<VuoEditorWindow *> openWindows = VuoEditorUtilities::getOpenCompositionEditingWindows();
2015  foreach (VuoEditorWindow *window, openWindows)
2016  {
2017  VuoModuleManager *windowModuleManager = window->getComposition()->getModuleManager();
2018  if (windowModuleManager->getNodeLibrary())
2019  {
2020  VuoModuleManager::CallbackType subcompositionCreated = ^void (void) {
2021  windowModuleManager->getNodeLibrary()->highlightNodeClass(nodeClassName);
2022  };
2023  windowModuleManager->doNextTimeNodeClassIsLoaded(nodeClassName, subcompositionCreated);
2024  }
2025  }
2026 }
2027 
2032 void VuoEditor::updateDockedNodeLibraryModules()
2033 {
2034  if (currentFloatingNodeLibrary)
2035  return;
2036 
2037  // Tell each open editor window to send updates to the node library that it owns and not to any others.
2038  QList<VuoEditorWindow *> openWindows = VuoEditorUtilities::getOpenCompositionEditingWindows();
2039  foreach (VuoEditorWindow *window, openWindows)
2040  {
2041  VuoModuleManager *windowModuleManager = window->getComposition()->getModuleManager();
2042  VuoNodeLibrary *windowOwnedNodeLibrary = window->getOwnedNodeLibrary();
2043 
2044  if (windowModuleManager->getNodeLibrary() != windowOwnedNodeLibrary)
2045  {
2046  windowOwnedNodeLibrary->clearNodeClassList();
2047  windowModuleManager->setNodeLibrary(windowOwnedNodeLibrary);
2048  windowModuleManager->updateWithAlreadyLoadedModules();
2049  }
2050  }
2051 
2052  // Tell the top-level app to send updates to the node library that it owns and not to any others.
2053  if (moduleManager->getNodeLibrary() != ownedNodeLibrary)
2054  {
2055  ownedNodeLibrary->clearNodeClassList();
2056  moduleManager->setNodeLibrary(ownedNodeLibrary);
2057  moduleManager->updateWithAlreadyLoadedModules();
2058  }
2059 }
2060 
2065 void VuoEditor::designateNewFloatingNodeLibrary(VuoNodeLibrary *library)
2066 {
2067  if (currentFloatingNodeLibrary && (currentFloatingNodeLibrary != ownedNodeLibrary))
2068  disconnect(currentFloatingNodeLibrary, &VuoNodeLibrary::aboutToBeDestroyed, this, &VuoEditor::assignTopLevelLibraryAsReplacementFloater);
2069 
2070  if (library && (library != ownedNodeLibrary))
2071  connect(library, &VuoNodeLibrary::aboutToBeDestroyed, this, &VuoEditor::assignTopLevelLibraryAsReplacementFloater);
2072 
2073  currentFloatingNodeLibrary = library;
2074 }
2075 
2080 void VuoEditor::assignTopLevelLibraryAsReplacementFloater()
2081 {
2082  currentFloatingNodeLibrary = ownedNodeLibrary;
2083 
2084  emit globalNodeLibraryStateChanged(getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
2085  currentFloatingNodeLibrary, true);
2086 }
2087 
2094 {
2095  menuOpenRecent->addFile(filePath);
2097 }
2098 
2104 {
2105  this->closedFiles.push_back(filePath.toUtf8().constData());
2106 }
2107 
2114 {
2115  menuOpenRecent->clearRecentFileListActionTriggered();
2117 }
2118 
2125 {
2126  menuOpenRecent->pruneNonexistentFiles();
2128 }
2129 
2135 {
2136  for (QMainWindow *openWindow : VuoEditorUtilities::getOpenEditingWindows())
2138 
2139  settings->setValue(recentFileListSettingsKey, menuOpenRecent->getRecentFiles());
2140 }
2141 
2147 {
2148  QString bundledManual = QDir::cleanPath(QApplication::applicationDirPath().append("/../Resources"))
2149  .append(QDir::separator())
2150  .append("vuo-")
2151  .append(VUO_VERSION_STRING)
2152  .append("-manual.pdf");
2153 
2154  if (QFile(bundledManual).exists())
2155  return QString("file://").append(bundledManual);
2156  else
2157  return "https://vuo.org/manual.pdf";
2158 }
2159 
2164 {
2165  return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2166 }
2167 
2173 void VuoEditor::generateAllNodeSetHtmlDocumentation(string saveDirectory)
2174 {
2175  const bool publishInternalVuoLinks = true;
2176  VuoFileUtilities::makeDir(saveDirectory);
2177 
2178 
2179  QString indexFilename(QString::fromStdString(saveDirectory) + "/index.html");
2180  QFile indexFile(indexFilename);
2181  if (!indexFile.open(QFile::WriteOnly | QFile::Truncate))
2182  throw VuoException(("Couldn't open " + indexFilename).toUtf8().data());
2183 
2184  QTextStream indexWriter(&indexFile);
2185  indexWriter << VUO_QSTRINGIFY(
2186  <html>
2187  <head>
2188  <title>Vuo %1 node documentation</title>
2189  <style>
2190  * { font-size: 13pt; font-family: 'PT Sans',Avenir,'Trebuchet MS',Tahoma,sans-serif; }
2191  body { padding: 5em; }
2192  h1 { font-size: 24pt; color: #aaa; font-weight: normal; }
2193  a, a:visited { font-weight: bold; color: #69a; }
2194  p { margin-top: 0; color: #aaa; }
2195  p a, p a:visited { font-weight: normal; color: #aaa; }
2196  li { list-style: none; }
2197  </style>
2198  </head>
2199  <body>
2200  <h1>Vuo %1 node documentation</h1>
2201  <ul>
2202  )
2203  .arg(VUO_VERSION_STRING);
2204 
2205  map<string, VuoCompilerNodeClass *> nodeClassesMap = compiler->getNodeClasses();
2206  vector<VuoCompilerNodeClass *> nodeClasses;
2207  for (map<string, VuoCompilerNodeClass *>::iterator i = nodeClassesMap.begin(); i != nodeClassesMap.end(); ++i)
2208  nodeClasses.push_back(i->second);
2210 
2211  set<string> nodeSetNames;
2212  foreach (VuoCompilerNodeClass *nodeClass, nodeClasses)
2213  if (nodeClass->getBase()->getNodeSet())
2214  nodeSetNames.insert(nodeClass->getBase()->getNodeSet()->getName());
2215 
2216  foreach (string nodeSetName, nodeSetNames)
2217  {
2218  string nodeSetSaveDirectory = saveDirectory + "/" + nodeSetName;
2219  VuoFileUtilities::makeDir(nodeSetSaveDirectory);
2220 
2221  string saveNodeSetHtml = nodeSetSaveDirectory + "/index.html";
2222  VuoNodeSet *nodeSet = compiler->getNodeSetForName(nodeSetName);
2223 
2224  generateMainHtmlPageForNodeSet(nodeSet, saveNodeSetHtml, publishInternalVuoLinks);
2225  nodeSet->extractDocumentationResources(nodeSetSaveDirectory);
2226  generateNodeClassHtmlPagesForNodeSet(nodeSet, nodeSetSaveDirectory, publishInternalVuoLinks);
2227 
2228  string description = nodeSet->getDescription();
2229  string firstLineOfDescription = description.substr(0, description.find('\n') - 1);
2230  string filteredDescription = VuoStringUtilities::generateHtmlFromMarkdownLine(externalizeVuoLinks(firstLineOfDescription));
2231  indexWriter << VUO_QSTRINGIFY(
2232  <li><a href="%1/">%2</a><p>%3</p></li>
2233  )
2234  .arg(QString::fromStdString(nodeSetName))
2235  .arg(VuoEditorComposition::formatNodeSetNameForDisplay(QString::fromStdString(nodeSetName)))
2236  .arg(QString::fromStdString(filteredDescription));
2237  }
2238 
2239  indexWriter << VUO_QSTRINGIFY(
2240  </ul>
2241  </body>
2242  </html>
2243  );
2244 }
2245 
2254 {
2255  const bool publishInternalVuoLinks = false;
2256 
2257  string nodeSetName = url.host().toUtf8().constData();
2258  VuoNodeSet *nodeSet = compiler->getNodeSetForName(nodeSetName);
2259 
2260  if (!nodeSet)
2261  return;
2262 
2263  // If the node set resource directory did not already exist, create and populate it now.
2264  string preexistingResourceDir = getResourceDirectoryForNodeSet(nodeSetName);
2265  string tmpSaveDir = (!preexistingResourceDir.empty()? preexistingResourceDir : VuoFileUtilities::makeTmpDir(nodeSetName));
2266  string tmpSaveNodeSetHtml = tmpSaveDir + "/index.html";
2267 
2268  if (tmpSaveDir != preexistingResourceDir)
2269  {
2270  generateMainHtmlPageForNodeSet(nodeSet, tmpSaveNodeSetHtml, publishInternalVuoLinks);
2271 
2272  // Extract resources referenced by the documentation.
2273  nodeSet->extractDocumentationResources(tmpSaveDir);
2274  }
2275 
2276  // Open the node set html file using an external browser.
2277  if (QDesktopServices::openUrl(QString("file://").append(tmpSaveNodeSetHtml.c_str())))
2278  emit activeApplicationStateChanged(false);
2279 
2280  // Save documentation for each member node class to its own html file.
2281  if (tmpSaveDir != preexistingResourceDir)
2282  {
2283  generateNodeClassHtmlPagesForNodeSet(nodeSet, tmpSaveDir, publishInternalVuoLinks);
2284  setResourceDirectoryForNodeSet(nodeSet->getName().c_str(), tmpSaveDir);
2285  }
2286 }
2287 
2296 {
2297  string nodeClassName = url.host().toUtf8().constData();
2300  NULL);
2301 
2302  if (topmostNodeLibrary)
2303  topmostNodeLibrary->prepareAndDisplayNodePopoverForClass(nodeClassName);
2304 }
2305 
2312 QString VuoEditor::generateHtmlDocumentationStyles(bool forBrowser, bool isDark)
2313 {
2314  return VUO_QSTRINGIFY(<style>
2315  table, th, td {
2316  border: 1px solid #ccc;
2317  border-collapse: collapse;
2318  padding: %1;
2319  }
2320  code, kbd, pre {
2321  font-family: 'Monaco';
2322  font-size: 12px;
2323  background-color: %2;
2324  padding: 0 0.4em;
2325  }
2326  pre {
2327  padding: 1em;
2328  white-space: pre-wrap;
2329  }
2330  </style>)
2331  .arg(forBrowser ? "0.4em" : "0")
2332  .arg(isDark ? "#383838" : "#ececec");
2333 }
2334 
2339 void VuoEditor::generateMainHtmlPageForNodeSet(VuoNodeSet *nodeSet, string saveFileName, bool publishInternalVuoLinks)
2340 {
2341  string nodeSetName = nodeSet->getName();
2342  vector<string> nodeSetClassNames = nodeSet->getNodeClassNames();
2343 
2344  // Metadata and node set description
2345  //: Appears in the webpage title on node set documentation pages.
2346  QString htmlHeader = "<html><head><meta charset=\"utf-8\"><title>" + tr("Vuo Node Set Documentation") + ": "
2347  + QString::fromStdString(nodeSetName)
2348  + "</title>"
2350  + "</head><body>";
2351 
2352  QString nodeSetDisplayName = VuoEditorComposition::formatNodeSetNameForDisplay(QString::fromStdString(nodeSetName));
2353  QString title = QString("<h2>").append(nodeSetDisplayName);
2354  if (nodeSetDisplayName != nodeSetName.c_str())
2355  title.append(" (").append(nodeSetName.c_str()).append(")");
2356  title.append("</h2>");
2357 
2358  string nodeSetDocumentationContent = nodeSet->getDescription();
2359  QString filteredNodeSetDocumentationContent = VuoStringUtilities::generateHtmlFromMarkdown(publishInternalVuoLinks?
2360  externalizeVuoLinks(nodeSetDocumentationContent) :
2361  removeVuoLinks(nodeSetDocumentationContent)).c_str();
2362 
2363 
2364  QString htmlFooter = "</body></html>";
2365 
2366  // Example compositions
2367  vector<string> nodeSetExampleCompositionFileNames = nodeSet->getExampleCompositionFileNames();
2368  QString nodeSetExampleCompositionText = "";
2369 
2370  foreach (string compositionFileName, nodeSetExampleCompositionFileNames)
2371  {
2372  string compositionAsString = nodeSet->getExampleCompositionContents(compositionFileName);
2373  VuoCompositionMetadata metadata(compositionAsString);
2374 
2375  string name = metadata.getCustomizedName();
2376  if (name.empty())
2377  name = VuoEditorComposition::formatCompositionFileNameForDisplay(compositionFileName.c_str()).toUtf8().constData();
2378 
2379  string description = metadata.getDescription();
2380  string filteredDescription = (publishInternalVuoLinks? externalizeVuoLinks(description) : removeVuoLinks(description));
2381  QString compositionDescription = VuoStringUtilities::generateHtmlFromMarkdownLine(filteredDescription).c_str();
2382 
2383  nodeSetExampleCompositionText.append("<li>")
2384  .append("<a href=\"")
2386  .append("://")
2387  .append(nodeSetName.c_str())
2388  .append("/")
2389  .append(compositionFileName.c_str())
2390  .append("\"><font size=+1>")
2391  .append(name.c_str())
2392  .append("</font></a>");
2393 
2394  if (!compositionDescription.isEmpty())
2395  nodeSetExampleCompositionText.append(": ").append(compositionDescription);
2396 
2397  nodeSetExampleCompositionText.append("</li>\n");
2398  }
2399 
2400  if (nodeSetExampleCompositionText.size() > 0)
2401  //: Appears on node set documentation webpages.
2402  nodeSetExampleCompositionText = "<BR><HR><h3>" + tr("Example composition(s)", "", nodeSetExampleCompositionFileNames.size()) + ":</h3>\n<ul>\n" + nodeSetExampleCompositionText + "</ul>";
2403 
2404  // Node classes
2405  QString nodeSetClassesText = "";
2406 
2407  // Sort by default node title.
2408  std::sort(nodeSetClassNames.begin(), nodeSetClassNames.end(), [=](const string nodeClassName1, const string nodeClassName2) {
2409  VuoCompilerNodeClass *nodeClass1 = compiler->getNodeClass(nodeClassName1);
2410  VuoCompilerNodeClass *nodeClass2 = compiler->getNodeClass(nodeClassName2);
2411  string nodeClass1Title = nodeClass1? nodeClass1->getBase()->getDefaultTitle() : "";
2412  string nodeClass2Title = nodeClass2? nodeClass2->getBase()->getDefaultTitle() : "";
2413 
2414  return nodeClass1Title < nodeClass2Title;
2415  });
2416 
2417  foreach (string nodeClassName, nodeSetClassNames)
2418  {
2419  VuoCompilerNodeClass *nodeClass = compiler->getNodeClass(nodeClassName);
2420  if (!nodeClass || nodeClass->getBase()->getDeprecated())
2421  continue;
2422 
2423  QString nodeClassTitle = nodeClass->getBase()->getDefaultTitle().c_str();
2424  QString nodeClassDescription = VuoStringUtilities::generateHtmlFromMarkdown(nodeClass->getBase()->getDescription()).c_str();
2425  QString nodeClassProNodeIndicator;
2426 #if VUO_PRO
2427  if (nodeClass->getBase()->isPro())
2428  //: Appears on node set documentation webpages.
2429  nodeClassProNodeIndicator = " <b>[<a href=\"https://vuo.org/pro-nodes\">" + tr("Pro node") + "</a>]</b>";
2430 #endif
2431 
2432  QString nodeClassDocumentationLink = QString((nodeClassName + ".html").c_str());
2433 
2434  // Strip HTML and extract the first sentence of the node class description for display.
2435  nodeClassDescription.remove(QRegExp("<[^>]*>"));
2436  nodeClassDescription.replace(QRegExp("\\.\\s.*"), ".");
2437 
2438  nodeSetClassesText.append("<li>")
2439  .append("<a href=\"")
2440  .append(nodeClassDocumentationLink)
2441  .append("\">")
2442  .append("<font size=+1>")
2443  .append(nodeClassTitle)
2444  .append("</font>")
2445  .append("</a>")
2446  .append(" (")
2447  .append(nodeClassName.c_str())
2448  .append(")");
2449 
2450  nodeSetClassesText.append(nodeClassProNodeIndicator);
2451 
2452  if (!nodeClassDescription.isEmpty())
2453  nodeSetClassesText.append(": ").append(nodeClassDescription);
2454 
2455  nodeSetClassesText.append("</li>\n");
2456  }
2457 
2458  if (nodeSetClassesText.size() > 0)
2459  //: Appears on node set documentation webpages.
2460  nodeSetClassesText = "<BR><HR><h3>" + tr("Node(s)", "", nodeSetClassNames.size()) + ":</h3>\n<ul>\n" + nodeSetClassesText + "</ul>";
2461 
2462  // Save the node set documentation to an html file.
2463  ofstream savedNodeSetFile(saveFileName.c_str(), ios::trunc);
2464 
2465  savedNodeSetFile << htmlHeader.append("\n\n").toUtf8().constData();
2466  savedNodeSetFile << title.append("\n\n").toUtf8().constData();
2467  savedNodeSetFile << filteredNodeSetDocumentationContent.append("\n\n").toUtf8().constData();
2468  savedNodeSetFile << nodeSetExampleCompositionText.append("\n\n").toUtf8().constData();
2469  savedNodeSetFile << nodeSetClassesText.append("\n\n").toUtf8().constData();
2470  savedNodeSetFile << htmlFooter.append("\n\n").toUtf8().constData();
2471 
2472  savedNodeSetFile.close();
2473 }
2474 
2486 void VuoEditor::generateNodeClassHtmlPagesForNodeSet(VuoNodeSet *nodeSet, string saveDir, bool publishInternalVuoLinks)
2487 {
2488  string nodeSetName = nodeSet->getName();
2489  vector<string> nodeSetClassNames = nodeSet->getNodeClassNames();
2490  foreach (string nodeClassName, nodeSetClassNames)
2491  {
2492  VuoCompilerNodeClass *nodeClass = compiler->getNodeClass(nodeClassName);
2493  if (!nodeClass || nodeClass->getBase()->getDeprecated())
2494  continue;
2495 
2496  string nodeClassTitle = nodeClass->getBase()->getDefaultTitle();
2497  string nodeClassDescription = nodeClass->getBase()->getDescription();
2498  string filteredNodeClassDescription = (publishInternalVuoLinks? externalizeVuoLinks(nodeClassDescription) :
2499  removeVuoLinks(nodeClassDescription));
2500 
2501  vector<string> manualKeywords = nodeClass->getBase()->getKeywords();
2502  vector<string> automaticKeywords = nodeClass->getAutomaticKeywords();
2503 
2504  set<string> sortedUniqueKeywords;
2505  foreach (string keyword, manualKeywords)
2506  sortedUniqueKeywords.insert(keyword);
2507 
2508  foreach (string keyword, automaticKeywords)
2509  sortedUniqueKeywords.insert(keyword);
2510 
2511  // Metadata and node class description
2512  //: Appears in the webpage title on node documentation pages.
2513  QString nodeClassHtmlHeader = "<html><head><meta charset=\"utf-8\"><title>" + tr("Vuo Node Documentation") + ": "
2514  + QString::fromStdString(nodeClassTitle)
2515  + " (" + QString::fromStdString(nodeClassName) + ")"
2516  + "</title>"
2518  + "</head><body>";
2519  QString nodeClassDocumentationTitle = QString("<h2>")
2520  .append(nodeClassTitle.c_str())
2521  .append(" (").append(nodeClassName.c_str()).append(")")
2522  .append("</h2>");
2523  QString nodeClassDocumentationContent = VuoStringUtilities::generateHtmlFromMarkdown(filteredNodeClassDescription).c_str();
2524  //: Appears on node documentation webpages.
2525  QString nodeClassKeywordsIntro = "<p><b>" + tr("Keyword(s)", "", sortedUniqueKeywords.size()) + "</b>: ";
2526  QString nodeClassKeywordsText = nodeClassKeywordsIntro;
2527  foreach (string keyword, sortedUniqueKeywords)
2528  {
2529  if (nodeClassKeywordsText != nodeClassKeywordsIntro)
2530  nodeClassKeywordsText.append(", ");
2531 
2532  nodeClassKeywordsText.append("<i>").append(keyword.c_str()).append("</i>");
2533  }
2534  nodeClassKeywordsText.append("</p>");
2535 
2536  // Rendering of model node
2538  VuoRendererNode *modelNode = new VuoRendererNode(nodeClass->newNode(), NULL);
2539  modelNode->setAlwaysDisplayPortNames(true);
2540  composition->addNode(modelNode->getBase());
2541  composition->createAndConnectInputAttachments(modelNode, compiler);
2542 
2543  QSizeF size = composition->itemsBoundingRect().size().toSize();
2544  QSizeF retinaSize(size.width()*2, size.height()*2);
2545  QPixmap pixmap(retinaSize.toSize());
2546  pixmap.fill(Qt::transparent);
2547 
2548  QPainter painter(&pixmap);
2549  painter.setRenderHint(QPainter::Antialiasing, true);
2550  painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
2551  painter.setRenderHint(QPainter::TextAntialiasing, true);
2552 
2553  composition->render(&painter);
2554 
2555  string nodeClassRenderedPreviewFileName = nodeClassName + ".png";
2556  string tmpSaveNodeClassImage = saveDir + "/" + nodeClassRenderedPreviewFileName;
2557  QFile file(tmpSaveNodeClassImage.c_str());
2558  file.open(QIODevice::WriteOnly);
2559  pixmap.save(&file, "PNG");
2560 
2561  delete modelNode;
2562  delete composition;
2563 
2564  QString nodeClassRenderedPreview = QString("<img src=\"%1\" width=\"%2\" height=\"%3\" />")
2565  .arg(nodeClassRenderedPreviewFileName.c_str())
2566  .arg(size.width())
2567  .arg(size.height());
2568 
2569  // Node class example compositions
2570  vector<string> nodeClassExampleCompositionFileNames = nodeClass->getBase()->getExampleCompositionFileNames();
2571  QString nodeClassExampleCompositionText = "";
2572 
2573  foreach (string compositionFileName, nodeClassExampleCompositionFileNames)
2574  {
2575  string compositionAsString = nodeSet->getExampleCompositionContents(compositionFileName);
2576  VuoCompositionMetadata metadata(compositionAsString);
2577 
2578  string name = metadata.getCustomizedName();
2579  if (name.empty())
2580  name = VuoEditorComposition::formatCompositionFileNameForDisplay(compositionFileName.c_str()).toUtf8().constData();
2581 
2582  // Override @c publishInternalVuoLinks here, and always filter out links to node classes from
2583  // example composition descriptions, since those example descriptions will almost always reference the same
2584  // node class whose documentation references the example.
2585  string description = metadata.getDescription();
2586  string filteredDescription = removeVuoLinks(description);
2587  QString compositionDescription = VuoStringUtilities::generateHtmlFromMarkdownLine(filteredDescription).c_str();
2588 
2589  nodeClassExampleCompositionText.append("<li>")
2590  .append("<a href=\"")
2592  .append("://")
2593  .append(nodeSetName.c_str())
2594  .append("/")
2595  .append(compositionFileName.c_str())
2596  .append("?")
2598  .append("=")
2599  .append(nodeClass->getBase()->getClassName().c_str())
2600  .append("\"><font size=+1>")
2601  .append(name.c_str())
2602  .append("</font></a>");
2603 
2604  if (!compositionDescription.isEmpty())
2605  nodeClassExampleCompositionText.append(": ").append(compositionDescription);
2606 
2607  nodeClassExampleCompositionText.append("</li>\n");
2608  }
2609 
2610  if (nodeClassExampleCompositionFileNames.size() > 0)
2611  //: Appears on node documentation webpages.
2612  nodeClassExampleCompositionText = "<HR><h3>" + tr("Example composition(s)", "", nodeClassExampleCompositionFileNames.size()) + ":</h3>\n<ul>\n" + nodeClassExampleCompositionText + "</ul>";
2613 
2614  // Footer
2615  QString nodeClassProNodeIndicator;
2616 #if VUO_PRO
2617  if (nodeClass->getBase()->isPro())
2618  nodeClassProNodeIndicator = QString(VuoNodePopover::installedProNodeText).append("<br><br>");
2619 #endif
2620  //: Appears on node documentation webpages.
2621  QString nodeClassSetReference = "<HR>" + nodeClassProNodeIndicator + tr("Back to %1 node set documentation.")
2622  .arg("<a href=\"index.html\">" + QString::fromStdString(nodeSetName) + "</a>");
2623  QString nodeClassHtmlFooter = "</body></html>";
2624 
2625  string saveNodeClassHtml = saveDir + "/" + nodeClassName + ".html";
2626  ofstream savedNodeClassFile(saveNodeClassHtml.c_str(), ios::trunc);
2627 
2628  savedNodeClassFile << nodeClassHtmlHeader.append("\n\n").toUtf8().constData();
2629  savedNodeClassFile << nodeClassDocumentationTitle.append("\n\n").toUtf8().constData();
2630  savedNodeClassFile << nodeClassRenderedPreview.append("\n\n").toUtf8().constData();
2631  savedNodeClassFile << nodeClassDocumentationContent.append("\n\n").toUtf8().constData();
2632  savedNodeClassFile << nodeClassKeywordsText.append("\n\n").toUtf8().constData();
2633  savedNodeClassFile << nodeClassExampleCompositionText.append("\n\n").toUtf8().constData();
2634  savedNodeClassFile << nodeClassSetReference.append("\n\n").toUtf8().constData();
2635  savedNodeClassFile << nodeClassHtmlFooter.append("\n\n").toUtf8().constData();
2636 
2637  savedNodeClassFile.close();
2638  }
2639 }
2640 
2645 string VuoEditor::externalizeVuoLinks(string markdownText)
2646 {
2647  QString filteredText(markdownText.c_str());
2648  QRegularExpression vuoNodeLink("\\[(.*)\\](\\(vuo-node://(.*)\\))", QRegularExpression::InvertedGreedinessOption);
2649  QRegularExpression vuoNodeSetLink("\\[(.*)\\](\\(vuo-nodeset://(.*)\\))", QRegularExpression::InvertedGreedinessOption);
2650 
2651  // Map node class links.
2652  size_t startPos = 0;
2653  QRegularExpressionMatch match = vuoNodeLink.match(filteredText, startPos);
2654  while (match.hasMatch()) {
2655  QString nodeClassDisplayTitle = match.captured(1);
2656  QString nodeClassName = match.captured(3);
2657 
2658  VuoCompilerNodeClass *nodeClass = compiler->getNodeClass(nodeClassName.toUtf8().constData());
2659  QString nodeSetName = (nodeClass? nodeClass->getBase()->getNodeSet()->getName().c_str() : "");
2660  QString mappedLink = QString("[")
2661  .append(nodeClassDisplayTitle)
2662  .append("](../")
2663  .append(nodeSetName)
2664  .append("/")
2665  .append(nodeClassName)
2666  .append(".html)");
2667 
2668  filteredText.replace(match.capturedStart(), match.capturedLength(), mappedLink);
2669  startPos = (match.capturedStart() + mappedLink.length());
2670  match = vuoNodeLink.match(filteredText, startPos);
2671  }
2672 
2673  // Map node set links.
2674  startPos = 0;
2675  match = vuoNodeSetLink.match(filteredText, startPos);
2676  while (match.hasMatch()) {
2677  QString nodeSetDisplayTitle = match.captured(1);
2678  QString nodeSetName = match.captured(3);
2679 
2680  QString mappedLink = QString("[")
2681  .append(nodeSetDisplayTitle)
2682  .append("](../")
2683  .append(nodeSetName)
2684  .append("/index.html)");
2685 
2686  filteredText.replace(match.capturedStart(), match.capturedLength(), mappedLink);
2687  startPos = (match.capturedStart() + mappedLink.length());
2688  match = vuoNodeSetLink.match(filteredText, startPos);
2689  }
2690 
2691  return filteredText.toUtf8().constData();
2692 }
2693 
2698 string VuoEditor::removeVuoLinks(string markdownText)
2699 {
2700  QString filteredText(markdownText.c_str());
2701  QRegularExpression vuoNodeLink("\\[(.*)\\](\\(vuo-node://(.*)\\))", QRegularExpression::InvertedGreedinessOption);
2702  filteredText.replace(vuoNodeLink, "`\\1`");
2703 
2704  QRegularExpression vuoNodeSetLink("\\[(.*)\\](\\(vuo-nodeset://(.*)\\))", QRegularExpression::InvertedGreedinessOption);
2705  filteredText.replace(vuoNodeSetLink, "`\\1`");
2706 
2707  return filteredText.toUtf8().constData();
2708 }
2709 
2718 {
2719  string nodeSetName = url.host().toUtf8().constData();
2720  string nodeClassToHighlight = "";
2721 
2722  QUrlQuery query(url.query());
2723  // Check whether the url contains the expected query parameters.
2724  if (query.hasQueryItem(vuoExampleHighlightedNodeClassQueryItem))
2725  nodeClassToHighlight = query.queryItemValue(vuoExampleHighlightedNodeClassQueryItem, QUrl::FullyDecoded).toUtf8().constData();
2726 
2727  string compositionFileName = VuoStringUtilities::substrAfter(url.path().toUtf8().constData(), "/");
2728 
2729  VuoNodeSet *nodeSet = compiler->getNodeSetForName(nodeSetName);
2730  if (!nodeSet)
2731  return;
2732 
2733  // Open the composition.
2734  openExampleComposition(compositionFileName.c_str(), nodeSet, nodeClassToHighlight);
2735 }
2736 
2742 {
2743  QClipboard *clipboard = QApplication::clipboard();
2744  const QMimeData *mimeData = clipboard->mimeData();
2745 
2746  if (!mimeData->hasFormat("text/plain") || mimeData->text().isNull())
2747  return "";
2748 
2749  return mimeData->text();
2750 }
2751 
2757 {
2758  return this->nodeDocumentationPanelHeight;
2759 }
2760 
2766 {
2767  return this->nodeLibraryWidth;
2768 }
2769 
2778 {
2779  if (resourceDirectoryForNodeSet.find(nodeSetName) != resourceDirectoryForNodeSet.end())
2780  return resourceDirectoryForNodeSet[nodeSetName];
2781  else
2782  return "";
2783 }
2784 
2789 void VuoEditor::setResourceDirectoryForNodeSet(string nodeSetName, string directory)
2790 {
2791  resourceDirectoryForNodeSet[nodeSetName] = directory;
2792 }
2793 
2798 {
2799  return subcompositionRouter;
2800 }
2801 
2805 void VuoEditor::initializeBuiltInDrivers()
2806 {
2807  // Image filter driver
2809  string imageFilterDriverPath = VuoFileUtilities::getVuoFrameworkPath() + "/Resources/" + "imageFilterDriver.vuo";
2810  string imageFilterDriverAsString = (VuoFileUtilities::fileExists(imageFilterDriverPath)?
2811  VuoFileUtilities::readFileToString(imageFilterDriverPath) :
2812  "");
2813 
2814  if (!imageFilterDriverAsString.empty())
2815  {
2816  VuoCompilerDriver *imageFilterDriver = new VuoCompilerDriver(compiler, imageFilterDriverAsString);
2817  if (imageFilterDriver->isValidDriverForProtocol(imageFilterProtocol))
2818  builtInDriverForProtocol[imageFilterProtocol] = imageFilterDriver;
2819  }
2820 
2821  // Image generator driver
2823  string imageGeneratorDriverPath = VuoFileUtilities::getVuoFrameworkPath() + "/Resources/" + "imageGeneratorDriver.vuo";
2824  string imageGeneratorDriverAsString = (VuoFileUtilities::fileExists(imageGeneratorDriverPath)?
2825  VuoFileUtilities::readFileToString(imageGeneratorDriverPath) :
2826  "");
2827 
2828  if (!imageGeneratorDriverAsString.empty())
2829  {
2830  VuoCompilerDriver *imageGeneratorDriver = new VuoCompilerDriver(compiler, imageGeneratorDriverAsString);
2831  if (imageGeneratorDriver->isValidDriverForProtocol(imageGeneratorProtocol))
2832  builtInDriverForProtocol[imageGeneratorProtocol] = imageGeneratorDriver;
2833  }
2834 
2835  // Image transition driver
2837  string imageTransitionDriverPath = VuoFileUtilities::getVuoFrameworkPath() + "/Resources/" + "imageTransitionDriver.vuo";
2838  string imageTransitionDriverAsString = (VuoFileUtilities::fileExists(imageTransitionDriverPath)?
2839  VuoFileUtilities::readFileToString(imageTransitionDriverPath) :
2840  "");
2841 
2842  if (!imageTransitionDriverAsString.empty())
2843  {
2844  VuoCompilerDriver *imageTransitionDriver = new VuoCompilerDriver(compiler, imageTransitionDriverAsString);
2845  if (imageTransitionDriver->isValidDriverForProtocol(imageTransitionProtocol))
2846  builtInDriverForProtocol[imageTransitionProtocol] = imageTransitionDriver;
2847  }
2848 
2849  // Extract built-in images to be used by protocol drivers.
2850  VuoNodeSet *imageNodeSet = compiler->getNodeSetForName("vuo.image");
2851  if (imageNodeSet)
2853 }
2854 
2859 {
2860  dispatch_sync(builtInDriversQueue, ^{});
2861 
2862  map<VuoProtocol *, VuoCompilerDriver *>::iterator driver = builtInDriverForProtocol.find(protocol);
2863  if (driver != builtInDriverForProtocol.end())
2864  return driver->second;
2865  else
2866  return NULL;
2867 }
2868 
2873 {
2874  // Sub-item indentation.
2875  // Use transparent icons instead of whitespace padding,
2876  // so that users can more easily customize the menu item shortcuts via System Preferences.
2877  QPixmap emptyPixmap(32, 32);
2878  emptyPixmap.fill(Qt::transparent);
2879  QIcon indentIcon(emptyPixmap);
2880 
2881  // "Window" subcategories
2882  QAction *windowHeading = new QAction(this);
2883  windowHeading->setText(tr("Window"));
2884  windowHeading->setEnabled(false);
2885  m->addAction(windowHeading);
2886 
2887  // Image template
2888  {
2889  QAction *templateAction = new QAction(this);
2890  templateAction->setText(tr("Image"));
2891  templateAction->setData("imageTemplate");
2892  templateAction->setIcon(indentIcon);
2893  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2894  m->addAction(templateAction);
2895  }
2896 
2897  // Layers template
2898  {
2899  QAction *templateAction = new QAction(this);
2900  templateAction->setText(tr("Layers"));
2901  templateAction->setData("layersTemplate");
2902  templateAction->setIcon(indentIcon);
2903  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2904  m->addAction(templateAction);
2905  }
2906 
2907  // Scene template
2908  {
2909  QAction *templateAction = new QAction(this);
2910  templateAction->setText(tr("Scene"));
2911  templateAction->setData("sceneTemplate");
2912  templateAction->setIcon(indentIcon);
2913  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2914  m->addAction(templateAction);
2915  }
2916 
2917  // "Protocol" subcategories
2918  QAction *protocolHeading = new QAction(this);
2919  protocolHeading->setText(tr("Protocol"));
2920  protocolHeading->setEnabled(false);
2921  m->addSeparator();
2922  m->addAction(protocolHeading);
2923 
2924  // Protocols
2925  foreach (VuoProtocol *protocol, VuoProtocol::getProtocols())
2926  {
2927  QAction *protocolAction = new QAction(this);
2928  protocolAction->setText(tr(protocol->getName().c_str()));
2929  protocolAction->setData(qVariantFromValue(static_cast<void *>(protocol)));
2930  protocolAction->setIcon(indentIcon);
2931  connect(protocolAction, &QAction::triggered, this, &VuoEditor::newCompositionWithProtocol);
2932  m->addAction(protocolAction);
2933  }
2934 
2935  // "Export" subcategories
2936  {
2937  m->addSeparator();
2938 
2939  QAction *heading = new QAction(this);
2940  heading->setText(tr("Export"));
2941  heading->setEnabled(false);
2942  m->addAction(heading);
2943 
2944  {
2945  QAction *templateAction = new QAction(this);
2946  templateAction->setText(tr("Movie"));
2947  templateAction->setData("movie");
2948  templateAction->setIcon(indentIcon);
2949  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2950  m->addAction(templateAction);
2951  }
2952 
2953  {
2954  QAction *templateAction = new QAction(this);
2955  templateAction->setText(tr("Screen Saver"));
2956  templateAction->setData("screensaver");
2957  templateAction->setIcon(indentIcon);
2958  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2959  m->addAction(templateAction);
2960  }
2961 
2962  {
2963  QAction *heading = new QAction(this);
2964  heading->setText(tr("FFGL"));
2965  heading->setIcon(indentIcon);
2966  heading->setEnabled(false);
2967  m->addAction(heading);
2968 
2969  {
2970  QAction *templateAction = new QAction(this);
2971  templateAction->setText(" " + tr("Source"));
2972  templateAction->setData("FFGLSource");
2973  templateAction->setIcon(indentIcon);
2974  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2975  m->addAction(templateAction);
2976  }
2977 
2978  {
2979  QAction *templateAction = new QAction(this);
2980  templateAction->setText(" " + tr("Effect"));
2981  templateAction->setData("FFGLEffect");
2982  templateAction->setIcon(indentIcon);
2983  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2984  m->addAction(templateAction);
2985  }
2986 
2987  {
2988  QAction *templateAction = new QAction(this);
2989  templateAction->setText(" " + tr("Blend Mode"));
2990  templateAction->setData("FFGLBlendMode");
2991  templateAction->setIcon(indentIcon);
2992  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
2993  m->addAction(templateAction);
2994  }
2995  }
2996 
2997  {
2998  QAction *heading = new QAction(this);
2999  heading->setText(tr("FxPlug"));
3000  heading->setIcon(indentIcon);
3001  heading->setEnabled(false);
3002  m->addAction(heading);
3003 
3004  {
3005  QAction *templateAction = new QAction(this);
3006  templateAction->setText(" " + tr("Generator"));
3007  templateAction->setData("FxPlugGenerator");
3008  templateAction->setIcon(indentIcon);
3009  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3010  m->addAction(templateAction);
3011  }
3012 
3013  {
3014  QAction *templateAction = new QAction(this);
3015  templateAction->setText(" " + tr("Effect"));
3016  templateAction->setData("FxPlugEffect");
3017  templateAction->setIcon(indentIcon);
3018  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3019  m->addAction(templateAction);
3020  }
3021 
3022  {
3023  QAction *templateAction = new QAction(this);
3024  templateAction->setText(" " + tr("Transition"));
3025  templateAction->setData("FxPlugTransition");
3026  templateAction->setIcon(indentIcon);
3027  connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3028  m->addAction(templateAction);
3029  }
3030  }
3031  }
3032 }
3033 
3038 {
3039  {
3040  QAction *action = new QAction(m);
3041  action->setText(tr("Image Filter"));
3042  action->setData("GLSLImageFilter");
3043  connect(action, &QAction::triggered, [=] {
3044  closeUnmodifiedUntitledComposition();
3045 #if VUO_PRO
3046  if (!closeWelcomeWindow())
3047  return;
3048 #endif
3050  });
3051  m->addAction(action);
3052  }
3053 
3054  {
3055  QAction *action = new QAction(m);
3056  action->setText(tr("Image Generator"));
3057  action->setData("GLSLImageGenerator");
3058  connect(action, &QAction::triggered, [=] {
3059  closeUnmodifiedUntitledComposition();
3060 #if VUO_PRO
3061  if (!closeWelcomeWindow())
3062  return;
3063 #endif
3065  });
3066  m->addAction(action);
3067  }
3068 
3069  {
3070  QAction *action = new QAction(m);
3071  action->setText(tr("Image Transition"));
3072  action->setData("GLSLImageTransition");
3073  connect(action, &QAction::triggered, [=] {
3074  closeUnmodifiedUntitledComposition();
3075 #if VUO_PRO
3076  if (!closeWelcomeWindow())
3077  return;
3078 #endif
3080  });
3081  m->addAction(action);
3082  }
3083 }
3084 
3090 {
3091  return documentationQueue;
3092 }
3093 
3098 {
3099 #if VUO_PRO
3100  return darkInterfaceAction->isChecked();
3101 #else
3102  return false;
3103 #endif
3104 }
3105 
3111 {
3112  return this->canvasOpacity;
3113 }
3114 
3118 void VuoEditor::showGridLinesToggled(bool show)
3119 {
3121  if (show)
3122  {
3123  showGridPointsAction->setChecked(false);
3124  type = VuoRendererComposition::LineGrid;
3125  }
3126  else
3127  type = VuoRendererComposition::NoGrid;
3128 
3129  settings->setValue(gridTypeSettingsKey, type);
3131 
3132  emit showGridToggled();
3133 }
3134 
3138 void VuoEditor::showGridPointsToggled(bool show)
3139 {
3141  if (show)
3142  {
3143  showGridLinesAction->setChecked(false);
3144  type = VuoRendererComposition::PointGrid;
3145  }
3146  else
3147  type = VuoRendererComposition::NoGrid;
3148 
3149  settings->setValue(gridTypeSettingsKey, type);
3151 
3152  emit showGridToggled();
3153 }
3154 
3158 void VuoEditor::updateSnapToGrid(bool snap)
3159 {
3160  settings->setValue(snapToGridSettingsKey, snap);
3161 
3163 }
3164 
3168 void VuoEditor::updateColor(bool isDark)
3169 {
3170  settings->setValue(darkInterfaceSettingsKey, isDark);
3171 
3173 }
3174 
3179 void VuoEditor::updateCanvasOpacity(QAction *setOpacityAction)
3180 {
3181  updateCanvasOpacityTo(setOpacityAction->data().toInt());
3182  updateUI();
3183 }
3184 
3189 void VuoEditor::updateCanvasOpacityTo(int opacity)
3190 {
3191  settings->setValue(canvasOpacitySettingsKey, opacity);
3192  canvasOpacity = opacity;
3193 }
3194 
3203 {
3204  VuoEditor::documentationGenerationDirectory = dir;
3205 }
3206 
3213 {
3214  map<QString, QString> modelExampleCompositionsAndNodeSets;
3215  modelExampleCompositionsAndNodeSets["LaserDiscoball.vuo"] = "vuo.audio";
3216  modelExampleCompositionsAndNodeSets["Tschuri.vuo"] = "vuo.image";
3217  modelExampleCompositionsAndNodeSets["SlitscanMixingInk.vuo"] = "vuo.layer";
3218  modelExampleCompositionsAndNodeSets["DrawInSpace.vuo"] = "vuo.scene";
3219  return modelExampleCompositionsAndNodeSets;
3220 }
3221 
3227 {
3228  map<QString, QString> modelExampleCompositionsAndNodeSets;
3229 
3230  if (protocol->getId() == VuoProtocol::imageFilter)
3231  {
3232  modelExampleCompositionsAndNodeSets["PixellateImageRadially.vuo"] = "vuo.image";
3233  }
3234 
3235  else if (protocol->getId() == VuoProtocol::imageGenerator)
3236  {
3237  modelExampleCompositionsAndNodeSets["GenerateCheckerboardImage.vuo"] = "vuo.image";
3238  modelExampleCompositionsAndNodeSets["MakeDriftingClouds.vuo"] = "vuo.image";
3239  modelExampleCompositionsAndNodeSets["MakeOvalPatterns.vuo"] = "vuo.image";
3240  modelExampleCompositionsAndNodeSets["RippleImageGradients.vuo"] = "vuo.image";
3241  modelExampleCompositionsAndNodeSets["SpinKaleidoscope.vuo"] = "vuo.event";
3242  }
3243 
3244  else if (protocol->getId() == VuoProtocol::imageTransition)
3245  modelExampleCompositionsAndNodeSets["BlendImages.vuo"] = "vuo.image";
3246 
3247  return modelExampleCompositionsAndNodeSets;
3248 }
3249 
3253 QString VuoEditor::getURLForExampleComposition(QString compositionName, QString nodeSetName)
3254 {
3255  QString compositionURL = QString(VuoEditor::vuoExampleCompositionScheme)
3256  .append("://")
3257  .append(nodeSetName)
3258  .append("/")
3259  .append(compositionName);
3260  return compositionURL;
3261 }
3262 
3267 {
3268 #if VUO_PRO
3269  return getStoredUserName_Pro();
3270 #else
3271  return getenv("USER");
3272 #endif
3273 }
3274 
3279 {
3280 #if VUO_PRO
3281  return getStoredUserProfileURL_Pro();
3282 #else
3283  return "";
3284 #endif
3285 }
3286 
3292 {
3293  settings->setValue(subcompositionPrefixSettingsKey, prefix);
3294  subcompositionPrefix = prefix;
3295 }
3296 
3297 
3302 {
3303  return (!subcompositionPrefix.isEmpty()? subcompositionPrefix : getDefaultSubcompositionPrefix());
3304 }
3305 
3311 {
3312  return getUserName().c_str();
3313 }
3314 
3319 {
3320  menuOpenRecent->openMostRecentFile();
3321 }
3322 
3327 {
3328  menuOpenExample->openRandomExample();
3329 }