Vuo 2.4.4
Loading...
Searching...
No Matches
VuoEditor.cc
Go to the documentation of this file.
1
10#include "VuoEditor.hh"
11
12#include "VuoCodeWindow.hh"
13#include "VuoCompiler.hh"
14#include "VuoCompilerDriver.hh"
15#include "VuoCompilerIssue.hh"
16#include "VuoComposition.hh"
19#include "VuoConsoleWindow.hh"
20#include "VuoEditorAboutBox.hh"
22#include "VuoConsole.hh"
23#include "VuoEditorUtilities.hh"
24#include "VuoEditorWindow.hh"
25#include "VuoErrorDialog.hh"
26#include "VuoException.hh"
27#include "VuoModuleCache.hh"
28#include "VuoModuleManager.hh"
29#include "VuoNodeClass.hh"
30#include "VuoNodeSet.hh"
31#include "VuoStringUtilities.hh"
32#include "VuoRendererCommon.hh"
33#include "VuoRendererFonts.hh"
36#include "VuoExampleMenu.hh"
37#include "VuoNodePopover.hh"
38#include "VuoOsStatus.h"
39#include "VuoProtocol.hh"
40#include "VuoRecentFileMenu.hh"
43
44#include <langinfo.h>
45#include <sstream>
46#include <fstream>
47
48#include <Carbon/Carbon.h>
49#include <IOKit/IOKitLib.h>
50#include <dlfcn.h>
51#include <objc/objc-runtime.h>
52
53const QString VuoEditor::recentFileListSettingsKey = "recentFileList";
54const QString VuoEditor::subcompositionPrefixSettingsKey = "subcompositionPrefix";
55const QString VuoEditor::nodeLibraryDisplayModeSettingsKey = "nodeLibraryDisplayMode";
56const QString VuoEditor::nodeLibraryVisibilityStateSettingsKey = "nodeLibraryVisible";
57const QString VuoEditor::nodeLibraryDockingStateSettingsKey = "nodeLibraryDocked";
58const QString VuoEditor::nodeLibraryFloatingPositionSettingsKey = "nodeLibraryFloatingPosition";
59const QString VuoEditor::nodeLibraryWidthSettingsKey = "nodeLibraryWidth";
60const QString VuoEditor::nodeLibraryHeightSettingsKey = "nodeLibraryHeight";
61const QString VuoEditor::nodeDocumentationPanelHeightSettingsKey = "nodeDocumentationPanelHeight";
62const QString VuoEditor::shaderDocumentationVisibilitySettingsKey = "shaderDocumentationVisible";
63const QString VuoEditor::gridTypeSettingsKey = "canvasGridType";
64const QString VuoEditor::gridOpacitySettingsKey = "canvasGridOpacity";
65const qreal VuoEditor::defaultGridOpacity = 1;
66const QString VuoEditor::snapToGridSettingsKey = "canvasGridSnap";
67const QString VuoEditor::darkInterfaceSettingsKey = "darkInterface";
68const QString VuoEditor::canvasOpacitySettingsKey = "canvasOpacity";
69const QString VuoEditor::movieExportWidthSettingsKey = "movieExportWidth";
70const QString VuoEditor::movieExportHeightSettingsKey = "movieExportHeight";
71const QString VuoEditor::movieExportTimeSettingsKey = "movieExportTime";
72const QString VuoEditor::movieExportDurationSettingsKey = "movieExportDuration";
73const QString VuoEditor::movieExportFramerateSettingsKey = "movieExportFramerate";
74const QString VuoEditor::movieExportSpatialSupersampleSettingsKey = "movieExportSpatialSupersample";
75const QString VuoEditor::movieExportTemporalSupersampleSettingsKey = "movieExportTemporalSupersample";
76const QString VuoEditor::movieExportShutterAngleSettingsKey = "movieExportShutterAngle";
77const QString VuoEditor::movieExportImageFormatSettingsKey = "movieExportImageFormat";
78const QString VuoEditor::movieExportQualitySettingsKey = "movieExportQuality";
79
80const QString VuoEditor::vuoHelpBookScheme = "vuo-help";
81const QString VuoEditor::vuoNodeSetDocumentationScheme = "vuo-nodeset";
82const QString VuoEditor::vuoNodeDocumentationScheme = "vuo-node";
83const QString VuoEditor::vuoExampleCompositionScheme = "vuo-example";
85const QString VuoEditor::vuoCompositionFileExtension = "vuo";
86const QString VuoEditor::vuoNodeClassFileExtension = "vuonode";
87const QString VuoEditor::vuoTutorialURL = "https://vuo.org/tutorials";
88
89string VuoEditor::documentationGenerationDirectory = "";
90
98const CFStringRef VuoEditor_helpBundleIdentifier = CFSTR("org.vuo.Editor.help." VUO_VERSION_AND_BUILD_STRING);
99
103VuoEditor::VuoEditor(int &argc, char *argv[])
104 : QApplication(argc,argv)
105{
107
108 compilerQueue = dispatch_queue_create("org.vuo.editor.compiler", DISPATCH_QUEUE_SERIAL);
109
110 // Load stored application settings.
111 QCoreApplication::setOrganizationName("Vuo");
112 QCoreApplication::setOrganizationDomain("vuo.org");
113 QCoreApplication::setApplicationName("Editor");
114 settings = new QSettings();
115
116 loadTranslations();
117
118 VuoEditorWindow::untitledComposition = QObject::tr("Untitled Composition");
119
120
121 // Controls the 2-finger-scroll speed.
122 // Defaults to 3 which (after https://b33p.net/kosada/node/13245) feels a little too fast.
123 // 2 seems to better match Preview.app.
124 setWheelScrollLines(2);
125
126
127 uiInitialized = false;
129
130 setQuitOnLastWindowClosed(false);
131 aboutBox = nullptr;
132 ownedNodeLibrary = NULL;
133 documentationQueue = dispatch_queue_create("org.vuo.editor.documentation", NULL);
134 userSubscriptionLevel = VuoEditor::CommunityUser;
135 reportAbsenceOfUpdates = false;
136 networkManager = NULL;
137 compiler = nullptr;
138 moduleManager = nullptr;
139 subcompositionRouter = new VuoSubcompositionMessageRouter();
140
141 // Initialize default settings.
142 const VuoNodeLibrary::nodeLibraryDisplayMode defaultNodeLibraryDisplayMode = VuoNodeLibrary::displayByName;
143 const bool defaultNodeLibraryVisibilityState = true; // Node library is visible by default.
144 const bool defaultNodeLibraryDockingState = true; // When visible, node library is docked by default.
145 const int defaultNodeLibraryDocumentationPanelHeight = 280;
146
147 qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, false);
148 qApp->setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, false);
149
150 qApp->setAttribute(Qt::AA_UseHighDpiPixmaps);
151 QToolTip::setFont(VuoRendererFonts::getSharedFonts()->portPopoverFont());
152
153 // Disable macOS's restore-open-documents-on-launch feature, since it doesn't work with Qt anyway,
154 // and since it pops up a meaningless "Do you want to try to reopen its windows again?" window after a crash.
155 settings->setValue("ApplePersistenceIgnoreStateQuietly", true);
156
157 QStringList storedRecentFileList = settings->value(recentFileListSettingsKey).toStringList();
158
159 menuOpenRecent = new VuoRecentFileMenu();
160 menuOpenRecent->setRecentFiles(storedRecentFileList);
161
162
163#if VUO_PRO
164 VuoEditor_Pro0();
165#endif
166
167 subcompositionPrefix = settings->contains(subcompositionPrefixSettingsKey)?
168 settings->value(subcompositionPrefixSettingsKey).toString() :
169 "";
170
171 canvasOpacity = settings->contains(canvasOpacitySettingsKey)?
172 settings->value(canvasOpacitySettingsKey).toInt() :
173 255;
174
175 nodeLibraryDisplayMode = settings->contains(nodeLibraryDisplayModeSettingsKey)?
176 (VuoNodeLibrary::nodeLibraryDisplayMode)settings->value(nodeLibraryDisplayModeSettingsKey).toInt() :
177 defaultNodeLibraryDisplayMode;
178
179 nodeLibraryCurrentlyVisible = settings->contains(nodeLibraryVisibilityStateSettingsKey)?
180 settings->value(nodeLibraryVisibilityStateSettingsKey).toBool() :
181 defaultNodeLibraryVisibilityState;
182
183 nodeLibraryCurrentlyDocked = settings->contains(nodeLibraryDockingStateSettingsKey)?
184 settings->value(nodeLibraryDockingStateSettingsKey).toBool() :
185 defaultNodeLibraryDockingState;
186
187 if (settings->contains(nodeLibraryFloatingPositionSettingsKey))
188 {
189 settingsContainedNodeLibraryFloatingPosition = true;
190 nodeLibraryFloatingPosition = settings->value(nodeLibraryFloatingPositionSettingsKey).toPoint();
191 }
192 else
193 {
194 settingsContainedNodeLibraryFloatingPosition = false;
195 nodeLibraryFloatingPosition = QPoint();
196 }
197
198 if (settings->contains(nodeLibraryWidthSettingsKey))
199 nodeLibraryWidth = settings->value(nodeLibraryWidthSettingsKey).toInt();
200 else
201 nodeLibraryWidth = -1;
202
203 if (settings->contains(nodeLibraryHeightSettingsKey))
204 nodeLibraryHeight = settings->value(nodeLibraryHeightSettingsKey).toInt();
205 else
206 nodeLibraryHeight = -1;
207
208 if (settings->contains(nodeDocumentationPanelHeightSettingsKey))
209 nodeDocumentationPanelHeight = settings->value(nodeDocumentationPanelHeightSettingsKey).toInt();
210 else
211 nodeDocumentationPanelHeight = defaultNodeLibraryDocumentationPanelHeight;
212
213 previousVisibleNodeLibraryStateWasDocked = nodeLibraryCurrentlyDocked;
214 currentFloatingNodeLibrary = NULL;
215
216 shaderDocumentationVisible = settings->contains(shaderDocumentationVisibilitySettingsKey)?
217 settings->value(shaderDocumentationVisibilitySettingsKey).toBool() :
218 true;
219
220 applyStoredMovieExportSettings();
221
222 // Disable the Mac OS 10.7+ restore-open-documents-on-launch feature, since it doesn't work with Qt anyway,
223 // and since it pops up a meaningless "Do you want to try to reopen its windows again?" window after a crash.
224 // (Reverted for now, since it it causes "ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to…" console messages.
225// settings->setValue("ApplePersistenceIgnoreState", true);
226
227 compiler = new VuoCompiler();
228 moduleManager = new VuoModuleManager(compiler);
229
230#if VUO_PRO
231 VuoEditor_Pro1();
232#endif
233
234 compiler->prepareModuleCaches();
235
236 builtInDriversQueue = dispatch_queue_create("org.vuo.editor.drivers", NULL);
237 dispatch_async(builtInDriversQueue, ^{
238 initializeBuiltInDrivers();
239 });
240
241 // If operating in documentation-generation mode, generate the node documentation and exit.
242 if (!documentationGenerationDirectory.empty())
243 {
244 menuBar = nullptr;
245 dispatch_async(dispatch_get_main_queue(), ^{
246 generateAllNodeSetHtmlDocumentation(documentationGenerationDirectory);
247 quitCleanly();
248 });
249 return;
250 }
251
252 // Register the URL handler for the "vuo-help" scheme, used for displaying a section of the user manual.
253 QDesktopServices::setUrlHandler(vuoHelpBookScheme, this, "openHelpBookPageFromUrl");
254
255 // Register the URL handler for the "vuo-nodeset" scheme, used for displaying node set documentation.
256 QDesktopServices::setUrlHandler(vuoNodeSetDocumentationScheme, this, "showNodeSetDocumentationFromUrl");
257
258 // Register the URL handler for the "vuo-node" scheme, used for displaying node documentation.
259 QDesktopServices::setUrlHandler(vuoNodeDocumentationScheme, this, "showNodeDocumentationFromUrl");
260
261 // Register the URL handler for the "vuo-example" scheme, used for opening example compositions.
262 QDesktopServices::setUrlHandler(vuoExampleCompositionScheme, this, "openExampleCompositionFromUrl");
263
264 // Construct the basic menu bar here, so that it's visible when no document windows are open.
265 menuBar = new QMenuBar();
266 {
267 menuFile = new QMenu(menuBar);
268 menuFile->setSeparatorsCollapsible(false);
269 menuFile->setTitle(tr("&File"));
270
271 menuFile->addAction(tr("&New Composition"), this, SLOT(newComposition()), QKeySequence("Ctrl+N"));
272
273 menuNewCompositionWithTemplate = new QMenu(tr("New Composition from Template"));
274 menuNewCompositionWithTemplate->setSeparatorsCollapsible(false);
275 populateNewCompositionWithTemplateMenu(menuNewCompositionWithTemplate);
276 menuFile->addMenu(menuNewCompositionWithTemplate);
277
278 QMenu *menuNewShader = new QMenu(tr("New Shader"));
279 menuNewShader->setSeparatorsCollapsible(false);
280 populateNewShaderMenu(menuNewShader);
281 menuFile->addMenu(menuNewShader);
282
283 menuFile->addSeparator();
284 menuFile->addAction(tr("&Open…"), this, SLOT(openFile()), QKeySequence("Ctrl+O"));
285
286 // "Open Recent" menu
287 connect(menuOpenRecent, &VuoRecentFileMenu::recentFileSelected, this, &VuoEditor::openUrl);
289 menuFile->addMenu(menuOpenRecent);
290 connect(menuFile, &QMenu::aboutToShow, this, &VuoEditor::pruneAllOpenRecentFileMenus);
291
292 // "Open Example" menu
293 menuOpenExample = new VuoExampleMenu(menuFile, compiler);
294 connect(menuOpenExample, &VuoExampleMenu::exampleSelected, this, &VuoEditor::openUrl);
295 menuFile->addMenu(menuOpenExample);
296
297 // "About" menu item.
298 // On macOS, this menu item will automatically be moved from the "File" menu to the Application menu by way of `QAction::AboutRole`.
299 QAction *aboutAction = new QAction(nullptr);
300 aboutAction->setText(tr("About Vuo…"));
301 connect(aboutAction, &QAction::triggered, this, &VuoEditor::about);
302 aboutAction->setMenuRole(QAction::AboutRole);
303 menuFile->addAction(aboutAction);
304
305 // Connect the "Quit" menu item action to our customized quit method.
306 // On macOS, this menu item will automatically be moved from the "File" menu to the Application menu by way of `QAction::QuitRole`.
307 QAction *quitAction = new QAction(nullptr);
308 quitAction->setText(tr("&Quit"));
309 quitAction->setShortcut(QKeySequence("Ctrl+Q"));
310 connect(quitAction, &QAction::triggered, this, &VuoEditor::quitCleanly);
311 quitAction->setMenuRole(QAction::QuitRole);
312 menuFile->addAction(quitAction);
313
314 menuBar->addAction(menuFile->menuAction());
315
316#if VUO_PRO
317 enableMenuItems(menuFile, canCloseWelcomeWindow());
318#endif
319 }
320
321 {
322 menuView = new QMenu(menuBar);
323 menuView->setSeparatorsCollapsible(false);
324 menuView->setTitle(tr("&View"));
325
326 // "Show Node Library"
327 showNodeLibraryAction = new QAction(NULL);
328 showNodeLibraryAction->setText(tr("Show Node &Library"));
329 showNodeLibraryAction->setShortcut(QKeySequence("Ctrl+Return"));
330 connect(showNodeLibraryAction, &QAction::triggered, this, &VuoEditor::showNodeLibrary);
331 menuView->addAction(showNodeLibraryAction);
332
333
334 // "Grid" submenu
335 {
336 // "Snap" menu item
337 snapToGridAction = new QAction(NULL);
338 snapToGridAction->setText(tr("Snap"));
339 snapToGridAction->setCheckable(true);
340 bool snapToGrid = (settings->contains(snapToGridSettingsKey)?
341 settings->value(snapToGridSettingsKey).toBool() :
342 true);
344 snapToGridAction->setChecked(snapToGrid);
345 connect(snapToGridAction, &QAction::toggled, this, &VuoEditor::updateSnapToGrid);
346
347 // "Line"/"Points" menu items
348 int gridOpacity;
349 if (settings->contains(gridOpacitySettingsKey))
350 gridOpacity = settings->value(gridOpacitySettingsKey).toInt();
351 else
352 {
353 gridOpacity = VuoEditor::defaultGridOpacity;
354 settings->setValue(gridOpacitySettingsKey, gridOpacity);
355 }
356
358
359 int gridType = (settings->contains(gridTypeSettingsKey)
360 ? settings->value(gridTypeSettingsKey).toInt()
361 : VuoRendererComposition::LineGrid);
363
364 showGridLinesAction = new QAction(NULL);
365 showGridLinesAction->setText(tr("Lines"));
366 showGridLinesAction->setCheckable(true);
367 showGridLinesAction->setChecked(gridType == VuoRendererComposition::LineGrid);
368 connect(showGridLinesAction, &QAction::triggered, this, &VuoEditor::showGridLinesToggled);
369
370 showGridPointsAction = new QAction(NULL);
371 showGridPointsAction->setText(tr("Points"));
372 showGridPointsAction->setCheckable(true);
373 showGridPointsAction->setChecked(gridType == VuoRendererComposition::PointGrid);
374 connect(showGridPointsAction, &QAction::triggered, this, &VuoEditor::showGridPointsToggled);
375 }
376
377
378 // "Canvas Transparency"
379 canvasTransparencyNoneAction = new QAction(NULL);
380 canvasTransparencyNoneAction->setText(tr("None"));
381 canvasTransparencyNoneAction->setData(255);
382 canvasTransparencyNoneAction->setCheckable(true);
383 canvasTransparencyNoneAction->setShortcut(QKeySequence("Ctrl+1"));
384
385 canvasTransparencySlightAction = new QAction(NULL);
386 canvasTransparencySlightAction->setText(tr("Slightly Transparent"));
387 canvasTransparencySlightAction->setData(230);
388 canvasTransparencySlightAction->setCheckable(true);
389 canvasTransparencySlightAction->setShortcut(QKeySequence("Ctrl+2"));
390
391 canvasTransparencyHighAction = new QAction(NULL);
392 canvasTransparencyHighAction->setText(tr("Very Transparent"));
393 canvasTransparencyHighAction->setData(191);
394 canvasTransparencyHighAction->setCheckable(true);
395 canvasTransparencyHighAction->setShortcut(QKeySequence("Ctrl+3"));
396
397 canvasTransparencyOptions = new QActionGroup(this);
401
402 foreach (QAction *action, canvasTransparencyOptions->actions())
403 {
404 if (action->data().toInt() == canvasOpacity)
405 {
406 action->setChecked(true);
407 action->setEnabled(false);
408 break;
409 }
410 }
411
412 // updateCanvasOpacity() needs to happen before canvasOpacityChanged(),
413 // since slots attached to the latter rely on VuoEditor::getCanvasOpacity() which is updated by the former.
414 connect(canvasTransparencyOptions, &QActionGroup::triggered, this, &VuoEditor::updateCanvasOpacity);
415 connect(canvasTransparencyOptions, &QActionGroup::triggered, this, &VuoEditor::canvasOpacityChanged);
416
417 menuBar->addAction(menuView->menuAction());
418 }
419
420 {
421 // @todo: For some reason the following doesn't work; no "Window" menu item is displayed.
422 menuWindow = new QMenu(menuBar);
423 menuWindow->setSeparatorsCollapsible(false);
424 menuWindow->setTitle(tr("&Window"));
425
426 populateWindowMenu(menuWindow, nullptr);
427
428 connect(menuWindow, &QMenu::aboutToShow, this, &VuoEditor::updateUI);
429
430 menuBar->addAction(menuWindow->menuAction());
431 }
432
433 {
434 menuHelp = new QMenu(menuBar);
435 menuHelp->setSeparatorsCollapsible(false);
436 menuHelp->setTitle(tr("&Help"));
437 populateHelpMenu(menuHelp);
438
439 // Prevent "Help > Search" from triggering lookup of all customized example composition titles at once.
440 connect(menuHelp, &QMenu::aboutToShow, menuOpenExample, &VuoExampleMenu::disableExampleTitleLookup);
441 connect(menuHelp, &QMenu::aboutToHide, menuOpenExample, &VuoExampleMenu::enableExampleTitleLookup);
442
443 connect(menuHelp, &QMenu::aboutToShow, [this] { populateHelpMenu(menuHelp); });
444
445 menuBar->addAction(menuHelp->menuAction());
446 }
447
448#ifdef __APPLE__
449 {
450 // Construct an OS X dock context menu.
451 // Items included in this menu will supplement the default OS X dock context menu items.
452 dockContextMenu = new QMenu();
453 dockContextMenu->setSeparatorsCollapsible(false);
454 dockContextMenu->setAsDockMenu();
455
456 connect(dockContextMenu, &QMenu::aboutToShow, this, &VuoEditor::updateUI);
457 }
458#endif
459
460 initializeTopLevelNodeLibrary(compiler,
461 nodeLibraryDisplayMode,
462 settingsContainedNodeLibraryFloatingPosition,
463 nodeLibraryFloatingPosition,
464 nodeLibraryWidth,
465 nodeLibraryHeight);
466 if (!nodeLibraryCurrentlyDocked)
467 designateNewFloatingNodeLibrary(ownedNodeLibrary);
468
469 moduleManager->setNodeLibrary(ownedNodeLibrary);
470
471
472 // @todo https://b33p.net/kosada/node/14299 The following line causes nodes to be listed in the
473 // floating node library immediately on launch (good), but then to be duplicated moments later (bad).
474 //moduleManager->updateWithAlreadyLoadedModules();
475 conformToGlobalNodeLibraryVisibility(
476 getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
477 currentFloatingNodeLibrary);
478 ownedNodeLibrary->displayPopoverForCurrentNodeClass();
479
480 // Update the global node library display mode whenever it is changed for the top-level node library.
481 connect(ownedNodeLibrary, &VuoNodeLibrary::changedIsHumanReadable, static_cast<VuoEditor *>(qApp), &VuoEditor::updateNodeLibraryDisplayMode);
482
483 // Update the "Show/Hide Node Library" menu item when the node library visibility or display mode is changed.
484 connect(ownedNodeLibrary, &VuoNodeLibrary::visibilityChanged, this, &VuoEditor::updateUI);
485 enableGlobalStateConformanceToLibrary(ownedNodeLibrary);
486 connect(this, &VuoEditor::globalNodeLibraryStateChanged, this, &VuoEditor::conformToGlobalNodeLibraryVisibility);
487
488#ifndef VUO_PRO
489 QApplication::processEvents(); // Force processing of any incoming QEvent::FileOpen event, which may populate queuedCompositionsToOpen.
490 if (queuedCompositionsToOpen.empty())
492#endif
493
494 // Initialize a file dialogue with its default directory set to something reasonable, so that
495 // file dialogues initialized subsequently within the same editor session will default to
496 // this location rather than to the executable directory.
497 // See https://b33p.net/kosada/node/7302 .
498 QFileDialog d;
499 if (d.history().isEmpty())
501
502 uiInitialized = true;
504 updateUI();
505}
506
511{
512 dispatch_sync(builtInDriversQueue, ^{});
513
514 if (menuBar)
515 menuBar->deleteLater();
516 delete subcompositionRouter;
517 delete moduleManager; // deletes compiler
518}
519
523QString VuoEditor::qtFindTranslation(const QLocale &locale, const QString &filename, const QString &prefix, const QString &directory, const QString &suffix)
524{
525 QString path;
526 if (QFileInfo(filename).isRelative()) {
527 path = directory;
528 if (!path.isEmpty() && !path.endsWith(QLatin1Char('/')))
529 path += QLatin1Char('/');
530 }
531
532 QFileInfo fi;
533 QString realname;
534 QStringList fuzzyLocales;
535
536 // see http://www.unicode.org/reports/tr35/#LanguageMatching for inspiration
537
538 QStringList languages = locale.uiLanguages();
539 for (int i = languages.size()-1; i >= 0; --i) {
540 QString lang = languages.at(i);
541 QString lowerLang = lang.toLower();
542 if (lang != lowerLang)
543 languages.insert(i+1, lowerLang);
544 }
545
546 // try explicit locales names first
547 foreach (QString localeName, languages) {
548 localeName.replace(QLatin1Char('-'), QLatin1Char('_'));
549
550 realname = path + filename + prefix + localeName + (suffix.isNull() ? QLatin1String(".qm") : suffix);
551 fi.setFile(realname);
552 if (fi.isReadable() && fi.isFile())
553 return realname;
554
555 realname = path + filename + prefix + localeName;
556 fi.setFile(realname);
557 if (fi.isReadable() && fi.isFile())
558 return realname;
559
560 fuzzyLocales.append(localeName);
561 }
562
563 // start guessing
564 foreach (QString localeName, fuzzyLocales) {
565 for (;;) {
566 int rightmost = localeName.lastIndexOf(QLatin1Char('_'));
567 // no truncations? fail
568 if (rightmost <= 0)
569 break;
570 localeName.truncate(rightmost);
571
572 realname = path + filename + prefix + localeName + (suffix.isNull() ? QLatin1String(".qm") : suffix);
573 fi.setFile(realname);
574 if (fi.isReadable() && fi.isFile())
575 return realname;
576
577 realname = path + filename + prefix + localeName;
578 fi.setFile(realname);
579 if (fi.isReadable() && fi.isFile())
580 return realname;
581 }
582 }
583
584 if (!suffix.isNull()) {
585 realname = path + filename + suffix;
586 fi.setFile(realname);
587 if (fi.isReadable() && fi.isFile())
588 return realname;
589 }
590
591 realname = path + filename + prefix;
592 fi.setFile(realname);
593 if (fi.isReadable() && fi.isFile())
594 return realname;
595
596 realname = path + filename;
597 fi.setFile(realname);
598 if (fi.isReadable() && fi.isFile())
599 return realname;
600
601 return QString();
602}
603
607void VuoEditor::loadTranslations()
608{
609 if (settings->value("translation/enable", true).toBool())
610 {
611 QLocale locale = QLocale::system();
612 if (VuoIsDebugEnabled())
613 {
614 VUserLog("C locale:");
615 VUserLog(" NL codeset : %s", nl_langinfo(CODESET));
616 VUserLog(" LANG : %s", getenv("LANG"));
617 VUserLog(" LC_ALL : %s", getenv("LC_ALL"));
618
619 VUserLog("Apple locale:");
620 CFStringRef appleLocale = (CFStringRef)CFPreferencesCopyAppValue(CFSTR("AppleLocale"), kCFPreferencesCurrentApplication);
621 VUserLog(" ISO (pref) : %s", VuoStringUtilities::makeFromCFString(appleLocale).c_str());
622 CFRelease(appleLocale);
623
624 CFLocaleRef localeCF = CFLocaleCopyCurrent();
625 VUserLog(" ISO (CF) : %s", VuoStringUtilities::makeFromCFString(CFLocaleGetIdentifier(localeCF)).c_str());
626 CFRelease(localeCF);
627
628 auto logCFArray = ^(const char *label, CFArrayRef array){
629 CFIndex count = CFArrayGetCount(array);
630 vector<string> s;
631 for (CFIndex i = 0; i < count; ++i)
632 s.push_back(VuoStringUtilities::makeFromCFString(CFArrayGetValueAtIndex(array, i)));
633 VUserLog(" %s: %s", label, VuoStringUtilities::join(s, ',').c_str());
634 CFRelease(array);
635 };
636
637 logCFArray("UI languages (pref)", (CFArrayRef)CFPreferencesCopyAppValue(CFSTR("AppleLanguages"), kCFPreferencesCurrentApplication));
638
639 logCFArray("UI languages (CF) ", CFLocaleCopyPreferredLanguages());
640
641 VUserLog("Qt locale:");
642 VUserLog(" ISO : %s", locale.name().toUtf8().data());
643 VUserLog(" BCP47 : %s", locale.bcp47Name().toUtf8().data());
644 VUserLog(" country : %s (%s)", QLocale::countryToString(locale.country()).toUtf8().data(), locale.nativeCountryName().toUtf8().data());
645 VUserLog(" language : %s (%s)", QLocale::languageToString(locale.language()).toUtf8().data(), locale.nativeLanguageName().toUtf8().data());
646 VUserLog(" UI languages : %s", locale.uiLanguages().join(',').toUtf8().data());
647 VUserLog(" script : %s", QLocale::scriptToString(locale.script()).toUtf8().data());
648 }
649
650 QString translationsPath = QDir(QString::fromStdString(VuoFileUtilities::getVuoFrameworkPath() + "/../../Resources/Translations")).canonicalPath();
651 QList<QString> contexts;
652 contexts << "qtbase" << "vuo";
653 QString prefix("_");
654 QString suffix(".qm");
655 foreach (QString context, contexts)
656 {
657 VUserLog("Context %s:", context.toUtf8().data());
658
659 QTranslator *t = new QTranslator();
660 QString translationFilename = qtFindTranslation(locale, context, prefix, translationsPath, suffix);
661 if (translationFilename.isEmpty())
662 {
663 VUserLog(" No translation file found.");
664 continue;
665 }
666
667 bool loaded = t->load(locale, context, prefix, translationsPath, suffix);
668 VUserLog(" Loading '%s': %s", translationFilename.toUtf8().data(), loaded ? "ok" : "error");
669 if (!loaded)
670 continue;
671
672 bool installed = installTranslator(t);
673 VUserLog(" Installing '%s': %s", translationFilename.toUtf8().data(), installed ? "ok" : "error");
674 }
675 }
676 else
677 VUserLog("Disabling translations since preference `translation.enable` is false.");
678}
679
684{
685 vector<QString> queuedCompositionsToOpenSnapshot(queuedCompositionsToOpen);
686 queuedCompositionsToOpen.clear();
687
688 // Iterate through a copy of the queue rather than the original queue,
689 // because calls to openUrl() might add the file back into the original queue,
690 // so we need the clear() call to have happened before that.
691 foreach (QString queuedUrl, queuedCompositionsToOpenSnapshot)
692 openUrl(queuedUrl);
693}
694
700{
701#if VUO_PRO
702 enableMenuItems(menuFile, canCloseWelcomeWindow());
703
704 if (!queuedCompositionsToOpen.empty())
705 closeWelcomeWindow();
706#endif
707}
708
714void VuoEditor::enableMenuItems(QMenu *menu, bool enable)
715{
716 if (!menu)
717 return;
718
719 foreach (QAction *action, menu->actions())
720 {
721 // Apply recursively to submenus.
722 if (action->menu())
723 {
724 VuoExampleMenu *exampleMenu = dynamic_cast<VuoExampleMenu *>(action->menu());
725 if (exampleMenu)
726 exampleMenu->enableMenuItems(enable);
727 else
728 enableMenuItems(action->menu(), enable);
729 }
730 else
731 {
732 bool isQuitOrHelp = action->text().contains("Quit") || action->text().contains("About");
733 bool isTemplateHeader = (menu == menuNewCompositionWithTemplate) &&
734 action->data().value<QString>().isEmpty() &&
735 !action->data().value<VuoProtocol *>();
736 if (!(isQuitOrHelp || isTemplateHeader))
737 action->setEnabled(enable);
738 }
739 }
740}
741
746{
747 menuOpenRecent->pruneNonexistentFiles();
748 return menuOpenRecent->getRecentFiles();
749}
750
755{
756 VUserLog("Conditionally quit");
757
758 // Write any unsaved settings changes to permanent storage.
759 settings->sync();
760
761 // Prevent node library visibility changes from impacting application-global settings
762 // from this point forward, since we are about to forcibly close them.
763 if (ownedNodeLibrary)
764 disableGlobalStateConformanceToLibrary(ownedNodeLibrary);
765
767 disableGlobalStateConformanceToLibrary(window->getOwnedNodeLibrary());
768
769 // Try to close each window sequentially, in the order they're stacked.
770 windowsRemainingAfterQuitRequested = VuoEditorUtilities::getOpenEditingWindowsStacked();
771 if (windowsRemainingAfterQuitRequested.empty())
772 reallyQuit();
773 else
774 // Start the chain reaction.
775 windowsRemainingAfterQuitRequested[0]->close();
776}
777
785{
786 if (windowsRemainingAfterQuitRequested.empty())
787 // The user hasn't requested to quit, so do nothing.
788 return;
789
790 // Restore node library connections.
791 foreach (QMainWindow *window, windowsRemainingAfterQuitRequested)
792 {
793 VuoEditorWindow *compositionWindow = dynamic_cast<VuoEditorWindow *>(window);
794 if (compositionWindow)
795 enableGlobalStateConformanceToLibrary(compositionWindow->getOwnedNodeLibrary());
796 }
797
798 if (ownedNodeLibrary)
799 enableGlobalStateConformanceToLibrary(ownedNodeLibrary);
800
801 windowsRemainingAfterQuitRequested.clear();
802}
803
810void VuoEditor::continueQuit(QMainWindow *window)
811{
812 if (windowsRemainingAfterQuitRequested.empty())
813 // The user hasn't requested to quit, so do nothing.
814 return;
815
816 windowsRemainingAfterQuitRequested.removeOne(window);
817
818 if (windowsRemainingAfterQuitRequested.empty())
819 reallyQuit();
820 else
821 // Continue the chain reaction.
822 windowsRemainingAfterQuitRequested[0]->close();
823}
824
828void VuoEditor::reallyQuit()
829{
830 VUserLog("Quit");
831
832 try
833 {
835 }
836 catch (...)
837 {
838 // Do nothing; it doesn't matter if this sometimes fails.
839 }
840
841 QApplication::quit();
842}
843
848{
849 return nodeLibraryCurrentlyDocked;
850}
851
857void VuoEditor::enableGlobalStateConformanceToLibrary(VuoNodeLibrary *library)
858{
859 connect(library, &VuoNodeLibrary::nodeLibraryHiddenOrUnhidden, this, &VuoEditor::updateGlobalNodeLibraryVisibilityState);
860 connect(library, &VuoNodeLibrary::topLevelChanged, this, &VuoEditor::updateGlobalNodeLibraryDockedState);
861 connect(library, &VuoNodeLibrary::nodeLibraryMoved, this, &VuoEditor::updateGlobalNodeLibraryFloatingPosition);
862 connect(library, &VuoNodeLibrary::nodeLibraryWidthChanged, this, &VuoEditor::updateGlobalNodeLibraryWidth);
863 connect(library, &VuoNodeLibrary::nodeLibraryHeightChanged, this, &VuoEditor::updateGlobalNodeLibraryHeight);
864 connect(library, &VuoNodeLibrary::nodeDocumentationPanelHeightChanged, this, &VuoEditor::updateGlobalNodeDocumentationPanelHeight);
865}
866
872void VuoEditor::disableGlobalStateConformanceToLibrary(VuoNodeLibrary *library)
873{
874 disconnect(library, &VuoNodeLibrary::nodeLibraryHiddenOrUnhidden, this, &VuoEditor::updateGlobalNodeLibraryVisibilityState);
875 disconnect(library, &VuoNodeLibrary::topLevelChanged, this, &VuoEditor::updateGlobalNodeLibraryDockedState);
876 disconnect(library, &VuoNodeLibrary::nodeLibraryMoved, this, &VuoEditor::updateGlobalNodeLibraryFloatingPosition);
877 disconnect(library, &VuoNodeLibrary::nodeLibraryWidthChanged, this, &VuoEditor::updateGlobalNodeLibraryWidth);
878 disconnect(library, &VuoNodeLibrary::nodeLibraryHeightChanged, this, &VuoEditor::updateGlobalNodeLibraryHeight);
879 disconnect(library, &VuoNodeLibrary::nodeDocumentationPanelHeightChanged, this, &VuoEditor::updateGlobalNodeDocumentationPanelHeight);
880}
881
885void VuoEditor::initializeTopLevelNodeLibrary(VuoCompiler *nodeLibraryCompiler,
886 VuoNodeLibrary::nodeLibraryDisplayMode nodeLibraryDisplayMode,
887 bool setFloatingPosition,
888 QPoint floatingPosition,
889 int nodeLibraryWidth,
890 int nodeLibraryHeight)
891{
892 ownedNodeLibrary = new VuoNodeLibrary(nodeLibraryCompiler, moduleManager, NULL, nodeLibraryDisplayMode);
893 ownedNodeLibrary->setObjectName("Top-level node library");
894
895 ownedNodeLibrary->setFloating(true);
896
897 if (setFloatingPosition)
898 ownedNodeLibrary->move(floatingPosition);
899
900 if (nodeLibraryHeight >= 0)
901 ownedNodeLibrary->resize(ownedNodeLibrary->rect().width(), nodeLibraryHeight);
902}
903
908{
909 if (!aboutBox)
910 aboutBox = new VuoEditorAboutBox();
911
912 aboutBox->showNormal();
913 aboutBox->raise();
914 aboutBox->activateWindow();
915}
916
926
930void VuoEditor::populateWindowMenu(QMenu *m, QMainWindow *currentWindow)
931{
932 if (currentWindow)
933 {
934 m->addAction(tr("Minimize"), currentWindow, &QMainWindow::showMinimized, QKeySequence("Ctrl+M"));
935 m->addAction(tr("Zoom"), currentWindow, &QMainWindow::showMaximized);
936
937 m->addSeparator();
938 }
939
940#if VUO_PRO
941 populateWindowMenu_Pro(m);
942#endif
943
944 QList<QMainWindow *> openWindows = VuoEditorUtilities::getOpenEditingWindows();
945 if (! openWindows.empty())
946 {
947 m->addSeparator();
948
949 for (QMainWindow *openWindow : openWindows)
950 {
951 QAction *raiseDocumentAction = VuoEditorUtilities::getRaiseDocumentActionForWindow(openWindow);
952 m->addAction(raiseDocumentAction);
953
954 if (currentWindow)
955 raiseDocumentAction->setChecked(openWindow == currentWindow);
956 else if (openWindow->isMinimized())
957 raiseDocumentAction->setChecked(false);
958 }
959 }
960}
961
966{
967 m->clear();
968
969 // Vuo Manual
970 QAction *vuoManualAction = new QAction(m);
971 vuoManualAction->setText(tr("Vuo Manual"));
972 vuoManualAction->setData(QUrl(getVuoManualURL()));
973 connect(vuoManualAction, &QAction::triggered, this, &VuoEditor::openHelpBook);
974 m->addAction(vuoManualAction);
975
976 // Vuo Manual (PDF)
977 QAction *vuoManualPDFAction = new QAction(m);
978 vuoManualPDFAction->setText(tr("Vuo Manual (PDF)"));
979 vuoManualPDFAction->setData(QUrl(getVuoManualURL()));
980 connect(vuoManualPDFAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
981 m->addAction(vuoManualPDFAction);
982
983 // Video Tutorials
984 QAction *videoTutorialsAction = new QAction(m);
985 videoTutorialsAction->setText(tr("Video Tutorials"));
986 videoTutorialsAction->setData(QUrl(vuoTutorialURL));
987 connect(videoTutorialsAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
988 m->addAction(videoTutorialsAction);
989
990 m->addSeparator();
991
992 // Search vuo.org
993 QAction *searchVuoOrgAction = new QAction(m);
994 searchVuoOrgAction->setText(tr("Search vuo.org"));
995 searchVuoOrgAction->setData(QUrl("https://vuo.org/search"));
996 connect(searchVuoOrgAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
997 m->addAction(searchVuoOrgAction);
998
999 m->addSeparator();
1000
1001 // View Community Activity
1002 QAction *communityActivityAction = new QAction(m);
1003 communityActivityAction->setText(tr("View Community Activity"));
1004 communityActivityAction->setData(QUrl("https://community.vuo.org"));
1005 connect(communityActivityAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1006 m->addAction(communityActivityAction);
1007
1008 // Share a Composition
1009 QAction *shareCompositionAction = new QAction(m);
1010 shareCompositionAction->setText(tr("Share a Composition"));
1011 shareCompositionAction->setData(QUrl("https://vuo.org/composition"));
1012 connect(shareCompositionAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1013 m->addAction(shareCompositionAction);
1014
1015 // Start a Discussion
1016 QAction *startDiscussionAction = new QAction(m);
1017 startDiscussionAction->setText(tr("Start a Discussion"));
1018 startDiscussionAction->setData(QUrl("https://vuo.org/community/discussion"));
1019 connect(startDiscussionAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1020 m->addAction(startDiscussionAction);
1021
1022 // Report a Bug
1023 QAction *reportBugAction = new QAction(m);
1024 reportBugAction->setText(tr("Report a Bug"));
1025 reportBugAction->setData(QUrl("https://vuo.org/bug"));
1026 connect(reportBugAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1027 m->addAction(reportBugAction);
1028
1029 // Request a Feature
1030 QAction *requestFeatureAction = new QAction(m);
1031 requestFeatureAction->setText(tr("Request a Feature"));
1032 requestFeatureAction->setData(QUrl("https://vuo.org/feature-request"));
1033 connect(requestFeatureAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1034 m->addAction(requestFeatureAction);
1035
1036 m->addSeparator();
1037
1038 // Help Us Improve Vuo
1039 QAction *improveVuoAction = new QAction(m);
1040 improveVuoAction->setText(tr("Help Us Improve Vuo"));
1041 improveVuoAction->setData(QUrl("https://vuo.org/contribute"));
1042 connect(improveVuoAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1043 m->addAction(improveVuoAction);
1044
1045 // Contact Team Vuo
1046 QAction *contactTeamVuoAction = new QAction(m);
1047 contactTeamVuoAction->setText(tr("Contact Team Vuo"));
1048 contactTeamVuoAction->setData(QUrl("https://vuo.org/contact"));
1049 connect(contactTeamVuoAction, &QAction::triggered, this, &VuoEditor::openExternalUrlFromSenderData);
1050 m->addAction(contactTeamVuoAction);
1051
1052#if VUO_PRO
1053 VuoEditor::populateHelpMenu_Pro(m);
1054#endif
1055}
1056
1061{
1062 OSStatus ret = AHGotoPage(VuoEditor_helpBundleIdentifier, NULL, NULL);
1063 if (ret)
1064 {
1065 char *description = VuoOsStatus_getText(ret);
1066 VUserLog("Error: Couldn't open Vuo Manual in HelpViewer.app: %s", description);
1067 free(description);
1068 }
1069}
1070
1076void VuoEditor::openHelpBookPageFromUrl(const QUrl &url)
1077{
1078 CFStringRef relativePath = CFStringCreateWithCString(NULL, url.url(QUrl::RemoveScheme).toUtf8().constData(), kCFStringEncodingUTF8);
1079 OSStatus ret = AHGotoPage(VuoEditor_helpBundleIdentifier, relativePath, NULL);
1080 if (ret)
1081 {
1082 char *description = VuoOsStatus_getText(ret);
1083 VUserLog("Error: Couldn't open Vuo Manual in HelpViewer.app: %s", description);
1084 free(description);
1085 }
1086 CFRelease(relativePath);
1087}
1088
1097VuoEditorWindow * VuoEditor::createEditorWindow(QString filename, bool existingComposition, VuoProtocol *activeProtocol)
1098{
1099 string compositionAsString = (existingComposition ? VuoFileUtilities::readFileToString(filename.toStdString()) : "");
1100 VuoEditorWindow *w = createEditorWindow(filename, filename, compositionAsString, activeProtocol);
1101 return w;
1102}
1103
1113VuoEditorWindow * VuoEditor::createEditorWindow(QString documentIdentifier, QString filename, const string &compositionAsString,
1114 VuoProtocol *activeProtocol, string nodeClassToHighlight)
1115{
1116 VuoEditorWindow *w;
1117 try
1118 {
1119 // @todo Take into account the current node library visibility state in deciding
1120 // whether to dock a node library to the newly opened window.
1121 // See: https://b33p.net/kosada/node/3464, https://b33p.net/kosada/node/3002,
1122 // https://b33p.net/kosada/node/3854, https://b33p.net/kosada/node/3087.
1123 w = new VuoEditorWindow(documentIdentifier, filename,
1124 compositionAsString,
1125 nodeLibraryDisplayMode,
1126 getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
1127 currentFloatingNodeLibrary,
1128 activeProtocol,
1129 nodeClassToHighlight);
1130 }
1131 catch (const VuoCompilerException &e)
1132 {
1133 VuoErrorDialog::show(NULL, tr("%1 can't be opened").arg(QFileInfo(filename).fileName()), e.what());
1134 return NULL;
1135 }
1136
1137 documentIdentifierAssigned[filename.isEmpty() ? documentIdentifier : filename] = w;
1138
1139 const int defaultWindowXOffset = 40;
1140 const int defaultWindowYOffset = 40;
1141
1143 if (!activeWindow)
1144 {
1145 // If this will be the only window…
1146
1147 if (ownedNodeLibrary && nodeLibraryCurrentlyVisible && !nodeLibraryCurrentlyDocked)
1148 {
1149 // If the node library is visible and floating, make sure the new window doesn't cover it up.
1150 int initialWindowXOffset = defaultWindowXOffset;
1151 int initialWindowYOffset = defaultWindowYOffset;
1152
1153 int nodeLibraryLeftPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topLeft()).x();
1154 int nodeLibraryRightPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topRight()).x();
1155 int nodeLibraryCenterPos = 0.5*(nodeLibraryLeftPos + nodeLibraryRightPos);
1156
1157 int availableSpaceLeftBoundary = QApplication::desktop()->availableGeometry(w).left();
1158 int availableSpaceRightBoundary = QApplication::desktop()->availableGeometry(w).right();
1159
1160 bool nodeLibraryCloserToLeft = (nodeLibraryCenterPos - availableSpaceLeftBoundary) <
1161 (availableSpaceRightBoundary - nodeLibraryCenterPos);
1162
1163 const int horizontalBuffer = 10;
1164 bool spaceForWindowToRightOfNodeLibrary = (availableSpaceRightBoundary - nodeLibraryRightPos) >=
1165 (w->geometry().width() + horizontalBuffer);
1166
1167 // Leave enough space to the left of the initial window to display the floating library, if applicable
1168 // and it makes sense to do so.
1169 if ((nodeLibraryCloserToLeft && spaceForWindowToRightOfNodeLibrary))
1170 initialWindowXOffset = nodeLibraryRightPos + horizontalBuffer;
1171
1172 // If the floating node library's top edge is higher than the default top edge position
1173 // for composition windows, vertically align the first composition window with the top edge
1174 // of the node library instead of using the default.
1175 // ownedNodeLibrary->titleBarWidget() doesn't reliably return non-NULL; hard-code title bar height instead of
1176 // actually retrieving it.
1177 const int titleBarHeight = 16;
1178 int nodeLibraryTopPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topLeft()).y() - titleBarHeight;
1179
1180 if (nodeLibraryTopPos < defaultWindowYOffset)
1181 initialWindowYOffset = nodeLibraryTopPos;
1182
1183 w->move(initialWindowXOffset, initialWindowYOffset);
1184 }
1185 else
1186 {
1187 // If the node library is hidden or docked, just let the window system position the new window.
1188 }
1189
1190 yScreenSpaceShortage = qMax(0, (w->geometry().bottom()) - QApplication::desktop()->availableGeometry(w).bottom() + 5);
1191 }
1192 else
1193 {
1194 // If there's already a window open, offset the new window relative to the active window,
1195 // to make it obvious that there are multiple windows.
1196 w->move(activeWindow->pos() + QPoint(defaultWindowXOffset, defaultWindowYOffset));
1197 }
1198
1199 // Workaround for bug that meant that maximizing or unmaximizing a window required a subsequent click to
1200 // the window before further interaction with it was possible, if the window height was limited by
1201 // available screen real estate. https://b33p.net/kosada/node/6914
1202 w->resize(w->width(), w->height()-yScreenSpaceShortage);
1203
1204 // Keep the floating node library's node class list synchronized with the currently active composition window.
1205 // Make sure to set this up before the window's show() call.
1206 connect(w, &VuoEditorWindow::windowActivated, this, &VuoEditor::updateFloatingNodeLibraryModules);
1207 connect(w, &VuoEditorWindow::windowDeactivated, this, &VuoEditor::updateFloatingNodeLibraryModules);
1208
1209 w->show();
1210
1211#if VUO_PRO
1212 createEditorWindow_Pro(w);
1213#endif
1214
1215 // Update the application UI (Open Recent menu, Window menu, macOS dock context menu)
1216 // whenever an editor window is created, destroyed, or modifies its "Document" list.
1218
1219 // Update the global node library display mode whenever it is changed for any node library instance.
1220 connect(w->getOwnedNodeLibrary(), &VuoNodeLibrary::changedIsHumanReadable, static_cast<VuoEditor *>(qApp), &VuoEditor::updateNodeLibraryDisplayMode);
1221
1222 // Keep node library visibility state synchronized among editor windows.
1223 enableGlobalStateConformanceToLibrary(w->getOwnedNodeLibrary());
1224
1225 try
1226 {
1227 VuoCompilerIssues *issues = new VuoCompilerIssues();
1229 delete issues;
1230 }
1231 catch (const VuoCompilerException &e)
1232 {
1233 QString summary = tr("This composition contains nodes that aren't installed.");
1234
1235 QString details = tr("<p>If you save the composition while it contains these nodes, some information will be lost.</p>"
1236 "<p>Try searching for these nodes in the <a href=\"%1\">Node Gallery</a>. "
1237 "Or if you don't need them, just delete them from the composition.</p>")
1238 .arg("https://vuo.org/nodes")
1239 + QString::fromStdString(e.getIssues()->getHint(true));
1240
1241 QString disclosureDetails = QString::fromStdString(e.getIssues()->getShortDescription(false));
1242
1243 VuoErrorDialog::show(w, summary, details, disclosureDetails);
1244 }
1245
1246 return w;
1247}
1248
1252void VuoEditor::registerOpenDocument(QMainWindow *window)
1253{
1254 // Not sure if this is necessary, since menus that display currently-open documents already get updated on QMenu::aboutToShow.
1255 updateUI();
1256
1257 // Ensure that the global menu is restored after closing the last window.
1258 connect(window, &QMainWindow::destroyed, this, &VuoEditor::updateUI);
1259
1260 // Keep the list of recent documents synchronized among the "File > Open Recent" menu of each editor window.
1262 connect(VuoEditorUtilities::getFileMenuForWindow(window), &QMenu::aboutToShow, this, &VuoEditor::pruneAllOpenRecentFileMenus);
1264}
1265
1270{
1271 if (!uiInitialized)
1272 {
1273 dispatch_async(compilerQueue, ^{
1274 dispatch_sync(dispatch_get_main_queue(), ^{
1275
1276 // Don't bother opening the standard new untitled composition during startup
1277 // if another composition has opened in the meantime
1278 // (by dropping a composition file on the Vuo.app icon,
1279 // or double-clicking a composition file in Finder after double-clicking Vuo.app,
1280 // or by passing a filename as a command-line argument).
1282 return;
1283#if VUO_PRO
1284 if (!closeWelcomeWindow())
1285 return;
1286#endif
1287 QString identifier = assignUntitledDocumentIdentifier();
1288 VUserLog("%s: New empty composition", identifier.toUtf8().data());
1289 createEditorWindow(identifier, "", "", NULL);
1290 });
1291 });
1292 return;
1293 }
1294
1295#if VUO_PRO
1296 if (!closeWelcomeWindow())
1297 return;
1298#endif
1299
1300 QString identifier = assignUntitledDocumentIdentifier();
1301 VUserLog("%s: New empty composition", identifier.toUtf8().data());
1302 createEditorWindow(identifier, "", "", NULL);
1303}
1304
1309VuoEditorWindow * VuoEditor::newCompositionWithContent(string content, string compositionDir)
1310{
1311 QString identifier = assignUntitledDocumentIdentifier();
1312 VUserLog("%s: New Composition with content", identifier.toUtf8().data());
1313 return createEditorWindow(identifier, QString::fromStdString(compositionDir), content, NULL);
1314}
1315
1321{
1322 QAction *sender = (QAction *)QObject::sender();
1323 VuoProtocol *selectedProtocol = sender->data().value<VuoProtocol *>();
1324
1325 closeUnmodifiedUntitledComposition();
1326#if VUO_PRO
1327 if (!closeWelcomeWindow())
1328 return;
1329#endif
1330
1331 QString identifier = assignUntitledDocumentIdentifier();
1332 VUserLog("%s: New Composition with %s protocol", identifier.toUtf8().data(), selectedProtocol->getName().c_str());
1333 VuoEditorWindow *w = createEditorWindow(identifier, false, selectedProtocol);
1334
1335 if (selectedProtocol && w && w->getCurrentNodeLibrary())
1336 {
1337 string nodeLibraryFilterText = getFilterTextForTemplate(selectedProtocol->getId());
1338 if (!nodeLibraryFilterText.empty())
1339 {
1340 w->getCurrentNodeLibrary()->searchForText(nodeLibraryFilterText.c_str());
1342 }
1343 }
1344}
1345
1351{
1352 QAction *sender = (QAction *)QObject::sender();
1353 QString selectedTemplate = static_cast<QString>(sender->data().value<QString>());
1354 string templatePath = VuoFileUtilities::getVuoFrameworkPath() + "/Resources/" + selectedTemplate.toUtf8().constData() + ".vuo";
1355
1356 closeUnmodifiedUntitledComposition();
1357#if VUO_PRO
1358 if (!closeWelcomeWindow())
1359 return;
1360#endif
1361
1362 string compositionAsString = VuoFileUtilities::readFileToString(templatePath);
1363 QString identifier = assignUntitledDocumentIdentifier();
1364 VUserLog("%s: New Composition with %s template", identifier.toUtf8().data(), selectedTemplate.toUtf8().data());
1365 VuoEditorWindow *w = createEditorWindow(identifier, "", compositionAsString, NULL);
1366
1367 if (w && w->getCurrentNodeLibrary())
1368 {
1369 string nodeLibraryFilterText = getFilterTextForTemplate(selectedTemplate.toUtf8().constData());
1370 if (!nodeLibraryFilterText.empty())
1371 {
1372 w->getCurrentNodeLibrary()->searchForText(nodeLibraryFilterText.c_str());
1374 }
1375 }
1376}
1377
1385string VuoEditor::getFilterTextForTemplate(string templateID)
1386{
1387 map<string, string> filterTextForTemplate;
1388
1389 // Protocols
1390 filterTextForTemplate[VuoProtocol::imageFilter] = "image";
1391 filterTextForTemplate[VuoProtocol::imageGenerator] = "image";
1392 filterTextForTemplate[VuoProtocol::imageTransition] = "image";
1393
1394 // Window templates
1395 filterTextForTemplate["imageTemplate"] = "vuo.image";
1396 filterTextForTemplate["layersTemplate"] = "vuo.layer shape";
1397 filterTextForTemplate["sceneTemplate"] = "vuo.scene shape";
1398
1399 // Export templates
1400 filterTextForTemplate["movie"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1401 filterTextForTemplate["screensaver"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1402
1403 filterTextForTemplate["FFGLSource"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1404 filterTextForTemplate["FFGLEffect"] = filterTextForTemplate[VuoProtocol::imageFilter];
1405 filterTextForTemplate["FFGLBlendMode"] = filterTextForTemplate[VuoProtocol::imageTransition];
1406
1407 filterTextForTemplate["FxPlugGenerator"] = filterTextForTemplate[VuoProtocol::imageGenerator];
1408 filterTextForTemplate["FxPlugEffect"] = filterTextForTemplate[VuoProtocol::imageFilter];
1409 filterTextForTemplate["FxPlugTransition"] = filterTextForTemplate[VuoProtocol::imageTransition];
1410
1411 map<string, string>::iterator i = filterTextForTemplate.find(templateID);
1412 if (i != filterTextForTemplate.end())
1413 return i->second;
1414 else
1415 return "";
1416}
1417
1421QString VuoEditor::assignUntitledDocumentIdentifier(void)
1422{
1423 QString uniqueDocumentIdentifier = VuoEditorWindow::untitledComposition;
1424 int documentIdentifierInstanceNum = 1;
1425
1426 while(documentIdentifierAssigned[uniqueDocumentIdentifier])
1427 {
1428 std::ostringstream oss;
1429 oss << " " << ++documentIdentifierInstanceNum;
1430 uniqueDocumentIdentifier = VuoEditorWindow::untitledComposition + QString::fromStdString(oss.str());
1431 }
1432
1433 return uniqueDocumentIdentifier;
1434}
1435
1440{
1441 QFileDialog d(NULL, "", "", "Vuo Composition (*.vuo);;Fragment Shader (*.fs)");
1442 d.setFileMode(QFileDialog::ExistingFiles);
1443
1444 // At Qt 5.2.1, the file dialogue's history() always seems to be empty.
1445 // See https://b33p.net/kosada/node/7302 .
1446 //if (d.history().isEmpty())
1447 // d.setDirectory(getDefaultCompositionStorageDirectory());
1448
1449 QStringList fileNames;
1450 if (d.exec() == QDialog::Accepted)
1451 fileNames = d.selectedFiles();
1452
1453 foreach (QString fileName, fileNames)
1454 openFileWithName(fileName);
1455}
1456
1460QMainWindow * VuoEditor::openFileWithName(QString filename, bool addToRecentFileMenu)
1461{
1462 closeUnmodifiedUntitledComposition();
1463#if VUO_PRO
1464 if (!closeWelcomeWindow())
1465 {
1466 QString fileURL = QString("file://").append(filename);
1467 queuedCompositionsToOpen.push_back(QString("file://").append(filename));
1468 return NULL;
1469 }
1470#endif
1471
1472 QMainWindow *existing = VuoEditorUtilities::existingWindowWithFile(filename);
1473 if (existing)
1474 {
1476 return existing;
1477 }
1478
1479 if (! VuoFileUtilities::fileIsReadable(filename.toUtf8().constData()))
1480 {
1481 VuoErrorDialog::show(NULL, tr("You do not have permission to open the document \"%1\".").arg(QFileInfo(filename).fileName()), "");
1482 return NULL;
1483 }
1484
1485 string dir, file, ext;
1486 VuoFileUtilities::splitPath(filename.toStdString(), dir, file, ext);
1487 bool isComposition = VuoFileUtilities::isCompositionExtension(ext);
1488
1489 QMainWindow *window = nullptr;
1490 if (isComposition)
1491 {
1492 VUserLog("%s.%s: Open", file.c_str(), ext.c_str());
1493
1495 {
1496 VuoErrorDialog::show(NULL, tr("\"%1\" can't be opened.").arg(QFileInfo(filename).fileName()), "It is not a valid Vuo composition.");
1497 return NULL;
1498 }
1499
1500 window = createEditorWindow(filename, true);
1501 if (!window)
1502 return nullptr;
1503
1504 dynamic_cast<VuoEditorWindow *>(window)->setIncludeInRecentFileMenu(addToRecentFileMenu);
1505 }
1506 else
1507 {
1508 try
1509 {
1510 window = new VuoCodeWindow(filename.toStdString());
1511 if (!window)
1512 return nullptr;
1513
1514 dynamic_cast<VuoCodeWindow *>(window)->setIncludeInRecentFileMenu(addToRecentFileMenu);
1515 }
1516 catch (VuoException &e)
1517 {
1518 VuoErrorDialog::show(NULL, tr("Couldn't open the shader."), e.what());
1519 return nullptr;
1520 }
1521
1522 window->show();
1523 }
1524
1525 if (addToRecentFileMenu)
1527
1528 return window;
1529}
1530
1538void VuoEditor::openExampleComposition(QString filename, VuoNodeSet *nodeSet, string nodeClassToHighlight)
1539{
1540 VUserLog("%s: Open", filename.toUtf8().data());
1541
1542 string filenameStr = filename.toStdString();
1543 string workingDir = VuoFileUtilities::getTmpDir();
1544 string compositionContents = nodeSet->getExampleCompositionContents(filenameStr);
1545 QString compositionPath = QString::fromStdString(workingDir + "/" + filenameStr);
1546
1547 nodeSet->extractExampleCompositionResources(workingDir);
1548 closeUnmodifiedUntitledComposition();
1549#if VUO_PRO
1550 if (!closeWelcomeWindow())
1551 return;
1552#endif
1553 createEditorWindow(compositionPath, compositionPath, compositionContents, NULL, nodeClassToHighlight);
1554
1555 QString exampleCompositionUrl = QString(VuoEditor::vuoExampleCompositionScheme)
1556 .append("://")
1557 .append(nodeSet->getName().c_str())
1558 .append("/")
1559 .append(filename);
1560
1561 addFileToAllOpenRecentFileMenus(exampleCompositionUrl);
1562}
1563
1569void VuoEditor::closeUnmodifiedUntitledComposition()
1570{
1572 if (untitled)
1573 untitled->close();
1574}
1575
1580{
1581 // If there is an editor window open and the user has somehow accessed the
1582 // application-global menu anyway, trigger the equivalent menu item of the
1583 // topmost editor window instead.
1585 if (topmostWindow)
1586 topmostWindow->on_showNodeLibrary_triggered();
1587
1588 // Unhiding the top-level node class library triggers node-library-floating mode globally.
1589 else
1590 {
1591 if ((ownedNodeLibrary->isHidden()))
1592 {
1593 VUserLog("Show node library");
1594
1595 designateNewFloatingNodeLibrary(ownedNodeLibrary);
1596 updateGlobalNodeLibraryState(true, false);
1597 }
1598
1599 ownedNodeLibrary->focusTextFilterAndSelectAll();
1600 }
1601}
1602
1606void VuoEditor::conformToGlobalNodeLibraryVisibility(VuoNodeLibrary::nodeLibraryState visibility,
1607 VuoNodeLibrary *floater)
1608{
1609 if ((visibility == VuoNodeLibrary::nodeLibraryHidden) ||
1610 (visibility == VuoNodeLibrary::nodeLibraryDocked))
1611 {
1612 ownedNodeLibrary->releaseDocumentationWidget();
1613 ownedNodeLibrary->setVisible(false);
1614 }
1615
1616 else if (visibility == VuoNodeLibrary::nodeLibraryFloating)
1617 {
1618 // If our own node library was the one that initiated global
1619 // floating-node-library mode by being undocked, let it float.
1620 // It is now the single application-wide floating library.
1621 if (ownedNodeLibrary == floater)
1622 {
1623 if (! ownedNodeLibrary->isFloating())
1624 ownedNodeLibrary->setFloating(true);
1625
1626 //ownedNodeLibrary->setVisible(true);
1627 ownedNodeLibrary->prepareAndMakeVisible();
1628 ownedNodeLibrary->setFocus();
1629 ownedNodeLibrary->displayPopoverForCurrentNodeClass();
1630 }
1631 }
1632
1633 updateUI();
1634}
1635
1639void VuoEditor::updateUI()
1640{
1641 if (!uiInitialized)
1642 return;
1643
1644 QList<QMainWindow *> openWindows = VuoEditorUtilities::getOpenEditingWindowsStacked();
1645 QMainWindow *frontWindow = (openWindows.empty() ? nullptr : openWindows.front());
1646
1647 // Update the list of open documents in the "Window" menu, including the checkmarks for the actions.
1648 menuWindow->clear();
1649 populateWindowMenu(menuWindow, frontWindow);
1650
1651#ifdef __APPLE__
1652 // Update the list of open documents in the OS X dock context menu, using the same actions.
1653 dockContextMenu->clear();
1654 for (QMainWindow *openWindow : VuoEditorUtilities::getOpenEditingWindows())
1655 dockContextMenu->addAction(VuoEditorUtilities::getRaiseDocumentActionForWindow(openWindow));
1656
1657 // Ensure that the global menu is restored after closing the last window.
1658 if (openWindows.empty())
1659 {
1660 id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
1661 id currentMainMenu = ((id (*)(id, SEL))objc_msgSend)(*nsAppGlobal, sel_getUid("mainMenu"));
1662 if (menuBar->toNSMenu() != currentMainMenu)
1663 {
1664 // No windows are open, and the currently-visible main menu isn't the global menu, so we need to explicitly restore the global menu.
1665 // QMenuBar::changeEvent calls QMenuBarPrivate::handleReparent, which calls QCocoaMenuBar::handleReparent, which calls QCocoaMenuBar::updateMenuBarImmediately, which calls NSApp::setMainMenu.
1666 QEvent e(QEvent::ParentChange);
1667 sendEvent(menuBar, &e);
1668 }
1669 }
1670#endif
1671
1672 // Update the "enabled" status of the canvas transparency menu items.
1673 foreach (QAction *action, canvasTransparencyOptions->actions())
1674 action->setEnabled(!action->isChecked());
1675}
1676
1680bool VuoEditor::event(QEvent * e)
1681{
1682 switch (e->type())
1683 {
1684 case QEvent::FileOpen:
1685 openUrl(static_cast<QFileOpenEvent *>(e)->url().toString());
1686 return true;
1687
1688 case QEvent::ApplicationActivate:
1690 return true;
1691
1692 case QEvent::ApplicationDeactivate:
1694 return true;
1695
1696 // macOS Dock context menu > Quit
1697 case QEvent::Close:
1698 e->ignore();
1699 quitCleanly();
1700 return true;
1701
1702 default:
1703 return QApplication::event(e);
1704 }
1705}
1706
1712{
1713 QAction *sender = (QAction *)QObject::sender();
1714 QDesktopServices::openUrl(sender->data().toUrl());
1715}
1716
1722{
1723 QAction *sender = (QAction *)QObject::sender();
1724 QString filePath = sender->data().value<QString>();
1725
1726 if (! filePath.startsWith("file://"))
1727 filePath = QString("file://").append(filePath);
1728
1729 openUrl(filePath);
1730}
1731
1737{
1738 QAction *sender = (QAction *)QObject::sender();
1739 QString filePath = sender->data().value<QString>();
1740 moveFileToTrash(filePath);
1741}
1742
1746void VuoEditor::moveFileToTrash(QString filePath)
1747{
1748 try
1749 {
1750 VuoFileUtilities::moveFileToTrash(filePath.toUtf8().constData());
1751 }
1752 catch (const VuoException &e)
1753 {
1754 if (VuoFileUtilities::fileExists(filePath.toUtf8().constData()))
1755 VuoErrorDialog::show(NULL, VuoEditor::tr("Couldn't move file to the Trash"), e.what());
1756 }
1757}
1758
1765void VuoEditor::openUrl(const QString &url)
1766{
1767 QUrl fileUrl(url);
1768 if (fileUrl.isValid() && fileUrl.scheme() == VuoEditor::vuoExampleCompositionScheme)
1769 {
1770 if (!uiInitialized)
1771 queuedCompositionsToOpen.push_back(url);
1772 else
1774 }
1775 else
1776 {
1777 string dir, file, ext;
1778 VuoFileUtilities::splitPath(fileUrl.toLocalFile().toUtf8().constData(), dir, file, ext);
1779
1780 if (QString(ext.c_str()) == VuoEditor::vuoNodeClassFileExtension)
1781 {
1782 string installedNodeDir = VuoFileUtilities::getUserModulesPath();
1783 string sourceNodePath = dir + "/" + file + "." + ext;
1784 string targetNodePath = installedNodeDir + "/" + file + "." + ext;
1785
1786 VuoFileUtilities::makeDir(installedNodeDir);
1787
1788 if (VuoFileUtilities::fileExists(targetNodePath))
1789 {
1790 QMessageBox messageBox;
1791 string errorSummary = "A node named \"" + file + "\" already exists. Do you want to replace it with the one you're installing?";
1792
1793 // On OS X, this combination of flags displays the minimize, maximimize, and close buttons, but all in a disabled state.
1794 messageBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint);
1795
1796 messageBox.setTextFormat(Qt::RichText);
1797 messageBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
1798 messageBox.setButtonText(QMessageBox::Discard, tr("Replace"));
1799 messageBox.setButtonText(QMessageBox::Cancel, tr("Cancel"));
1800 messageBox.setDefaultButton(QMessageBox::Discard);
1801 messageBox.setText(errorSummary.c_str());
1802 messageBox.setStyleSheet("#qt_msgbox_informativelabel, QMessageBoxDetailsText { font-weight: normal; font-size: 11pt; }");
1803
1804 // Give the "Cancel" button keyboard focus (without "Default" status) so that it can be activated by spacebar.
1805 static_cast<QPushButton *>(messageBox.button(QMessageBox::Cancel))->setAutoDefault(false);
1806 messageBox.button(QMessageBox::Cancel)->setFocus();
1807
1808 messageBox.setIconPixmap(VuoEditorUtilities::vuoLogoForDialogs());
1809
1810 if (messageBox.exec() == QMessageBox::Discard)
1811 {
1812 // Move pre-existing module to the trash.
1813 try
1814 {
1815 VuoFileUtilities::moveFileToTrash(targetNodePath);
1816 }
1817 catch (VuoException &e)
1818 {
1819 if (VuoFileUtilities::fileExists(targetNodePath))
1820 {
1821 VuoErrorDialog::show(NULL, tr("There was a problem installing the node."), e.what());
1822 return;
1823 }
1824 }
1825
1826 }
1827 else
1828 return;
1829 }
1830
1831 // Install the node class in the "User Modules" directory.
1832 try
1833 {
1834 VUserLog("Install node %s.%s", file.c_str(), ext.c_str());
1835
1836 VuoFileUtilities::copyFile(sourceNodePath, targetNodePath);
1837 }
1838 catch (VuoException &e)
1839 {
1840 VuoErrorDialog::show(NULL, tr("There was a problem installing the node."), e.what());
1841 return;
1842 }
1843
1844 if (VuoFileUtilities::fileExists(targetNodePath))
1845 {
1846 QMessageBox installationSuccessMessageBox;
1847 installationSuccessMessageBox.setText(tr("Your node has been installed!"));
1848 installationSuccessMessageBox.setIconPixmap(VuoEditorUtilities::vuoLogoForDialogs());
1849 installationSuccessMessageBox.exec();
1850 }
1851
1852#if VUO_PRO
1853 closeWelcomeWindow(true);
1854#endif
1855 }
1856
1858 {
1859 if (!uiInitialized)
1860 queuedCompositionsToOpen.push_back(url);
1861 else
1862 openFileWithName(fileUrl.toLocalFile());
1863 }
1864
1865 else
1866 QDesktopServices::openUrl(url);
1867 }
1868}
1869
1874{
1875 shaderDocumentationVisible = isVisible;
1876 settings->setValue(shaderDocumentationVisibilitySettingsKey, shaderDocumentationVisible);
1877}
1878
1883{
1884 return shaderDocumentationVisible;
1885}
1886
1890void VuoEditor::applyStoredMovieExportSettings()
1891{
1892 movieExportWidth = (settings->contains(movieExportWidthSettingsKey)? settings->value(movieExportWidthSettingsKey).toInt() : 1024);
1893 movieExportHeight = (settings->contains(movieExportHeightSettingsKey)? settings->value(movieExportHeightSettingsKey).toInt() : 768);
1894 movieExportTime = (settings->contains(movieExportTimeSettingsKey)? settings->value(movieExportTimeSettingsKey).toDouble() : 0.);
1895 movieExportDuration = (settings->contains(movieExportDurationSettingsKey)? settings->value(movieExportDurationSettingsKey).toDouble() : 10.);
1896 movieExportFramerate = (settings->contains(movieExportFramerateSettingsKey)? settings->value(movieExportFramerateSettingsKey).toDouble() : 30.);
1897 movieExportSpatialSupersample = (settings->contains(movieExportSpatialSupersampleSettingsKey)? settings->value(movieExportSpatialSupersampleSettingsKey).toInt() : 1.);
1898 movieExportTemporalSupersample = (settings->contains(movieExportTemporalSupersampleSettingsKey)? settings->value(movieExportTemporalSupersampleSettingsKey).toInt() : 1.);
1899 movieExportShutterAngle = (settings->contains(movieExportShutterAngleSettingsKey)? static_cast<float>(settings->value(movieExportShutterAngleSettingsKey).toDouble()) : 360.);
1900 movieExportImageFormat = (settings->contains(movieExportImageFormatSettingsKey)? settings->value(movieExportImageFormatSettingsKey).toString() : "H.264");
1901 movieExportQuality = (settings->contains(movieExportQualitySettingsKey)? settings->value(movieExportQualitySettingsKey).toDouble() : 1.);
1902}
1903
1907void VuoEditor::updateGlobalMovieExportSettings(int width,
1908 int height,
1909 double time,
1910 double duration,
1911 double framerate,
1912 int spatialSupersample,
1913 int temporalSupersample,
1914 float shutterAngle,
1915 QString imageFormat,
1916 double quality)
1917{
1918 if (width != movieExportWidth)
1919 {
1920 movieExportWidth = width;
1921 settings->setValue(movieExportWidthSettingsKey, movieExportWidth);
1922 }
1923
1924 if (height != movieExportHeight)
1925 {
1926 movieExportHeight = height;
1927 settings->setValue(movieExportHeightSettingsKey, movieExportHeight);
1928 }
1929
1930 if (time != movieExportTime)
1931 {
1932 movieExportTime = time;
1933 settings->setValue(movieExportTimeSettingsKey, movieExportTime);
1934 }
1935
1936 if (duration != movieExportDuration)
1937 {
1938 movieExportDuration = duration;
1939 settings->setValue(movieExportDurationSettingsKey, movieExportDuration);
1940 }
1941
1942 if (framerate != movieExportFramerate)
1943 {
1944 movieExportFramerate = framerate;
1945 settings->setValue(movieExportFramerateSettingsKey, movieExportFramerate);
1946 }
1947
1948 if (spatialSupersample != movieExportSpatialSupersample)
1949 {
1950 movieExportSpatialSupersample = spatialSupersample;
1951 settings->setValue(movieExportSpatialSupersampleSettingsKey, movieExportSpatialSupersample);
1952 }
1953
1954 if (temporalSupersample != movieExportTemporalSupersample)
1955 {
1956 movieExportTemporalSupersample = temporalSupersample;
1957 settings->setValue(movieExportTemporalSupersampleSettingsKey, movieExportTemporalSupersample);
1958 }
1959
1960 if (shutterAngle != movieExportShutterAngle)
1961 {
1962 movieExportShutterAngle = shutterAngle;
1963 settings->setValue(movieExportShutterAngleSettingsKey, static_cast<double>(movieExportShutterAngle));
1964 }
1965
1966 if (imageFormat != movieExportImageFormat)
1967 {
1968 movieExportImageFormat = imageFormat;
1969 settings->setValue(movieExportImageFormatSettingsKey, movieExportImageFormat);
1970 }
1971
1972 if (quality != movieExportQuality)
1973 {
1974 movieExportQuality = quality;
1975 settings->setValue(movieExportQualitySettingsKey, movieExportQuality);
1976 }
1977}
1978
1983 int &height,
1984 double &time,
1985 double &duration,
1986 double &framerate,
1987 int &spatialSupersample,
1988 int &temporalSupersample,
1989 float &shutterAngle,
1990 QString &imageFormat,
1991 double &quality)
1992{
1993 width = movieExportWidth;
1994 height = movieExportHeight;
1995 time = movieExportTime;
1996 duration = movieExportDuration;
1997 framerate = movieExportFramerate;
1998 spatialSupersample = movieExportSpatialSupersample;
1999 temporalSupersample = movieExportTemporalSupersample;
2000 shutterAngle = movieExportShutterAngle;
2001 imageFormat = movieExportImageFormat;
2002 quality = movieExportQuality;
2003}
2004
2005
2010VuoNodeLibrary::nodeLibraryState VuoEditor::getGlobalNodeLibraryStateForAttributes(bool visible, bool docked)
2011{
2012 return ((! visible)? VuoNodeLibrary::nodeLibraryHidden :
2013 (docked? VuoNodeLibrary::nodeLibraryDocked :
2014 VuoNodeLibrary::nodeLibraryFloating));
2015}
2016
2022void VuoEditor::updateNodeLibraryDisplayMode(bool humanReadable)
2023{
2024 nodeLibraryDisplayMode = (humanReadable? VuoNodeLibrary::displayByName : VuoNodeLibrary::displayByClass);
2025 settings->setValue(nodeLibraryDisplayModeSettingsKey, nodeLibraryDisplayMode);
2026
2027 // Exception to the non-propagation policy for this setting:
2028 // If the top-level node library is not currently visible, do synchronize its display mode so that
2029 // it is up-to-date if/when it next appears.
2030 if ((ownedNodeLibrary->isHidden()) && (ownedNodeLibrary->getHumanReadable() != humanReadable))
2031 {
2032 ownedNodeLibrary->setHumanReadable(humanReadable);
2033 ownedNodeLibrary->updateUI();
2034 updateUI();
2035 }
2036}
2037
2041void VuoEditor::updateGlobalNodeLibraryFloatingPosition(QPoint newPos)
2042{
2043 nodeLibraryFloatingPosition = newPos;
2044 settings->setValue(nodeLibraryFloatingPositionSettingsKey, nodeLibraryFloatingPosition);
2045}
2046
2050void VuoEditor::updateGlobalNodeLibraryWidth(int newWidth)
2051{
2052 nodeLibraryWidth = newWidth;
2053 settings->setValue(nodeLibraryWidthSettingsKey, nodeLibraryWidth);
2054}
2055
2059void VuoEditor::updateGlobalNodeLibraryHeight(int newHeight)
2060{
2061 nodeLibraryHeight = newHeight;
2062 settings->setValue(nodeLibraryHeightSettingsKey, nodeLibraryHeight);
2063}
2064
2068void VuoEditor::updateGlobalNodeDocumentationPanelHeight(int newSize)
2069{
2070 nodeDocumentationPanelHeight = newSize;
2071 settings->setValue(nodeDocumentationPanelHeightSettingsKey, nodeDocumentationPanelHeight);
2072}
2073
2077void VuoEditor::updateGlobalNodeLibraryVisibilityState(bool visible)
2078{
2079 VUserLog("%s node library", visible ? "Show" : "Hide");
2080
2081 // If transitioning to a visible state, revert to the docking state from the last time the node library was visible.
2082 bool updatedDockingState = ((visible && (! nodeLibraryCurrentlyVisible))? previousVisibleNodeLibraryStateWasDocked : nodeLibraryCurrentlyDocked);
2083 updateGlobalNodeLibraryState(visible, updatedDockingState);
2084}
2085
2089void VuoEditor::updateGlobalNodeLibraryDockedState(bool floating)
2090{
2091 VUserLog("%s node library", floating ? "Detach" : "Attach");
2092
2093 VuoNodeLibrary *sender = (VuoNodeLibrary *)QObject::sender();
2094 VuoNodeLibrary *floater = (floating? sender : NULL);
2095 designateNewFloatingNodeLibrary(floater);
2096 updateGlobalNodeLibraryState(this->nodeLibraryCurrentlyVisible, (! floating));
2097
2098 if (floating)
2099 updateFloatingNodeLibraryModules();
2100 else
2101 updateDockedNodeLibraryModules();
2102}
2103
2109void VuoEditor::updateGlobalNodeLibraryState(bool visible, bool docked)
2110{
2111 VuoNodeLibrary::nodeLibraryState currentNodeLibraryState = getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked);
2112 VuoNodeLibrary::nodeLibraryState updatedNodeLibraryState = getGlobalNodeLibraryStateForAttributes(visible, docked);
2113
2114 if (currentNodeLibraryState != VuoNodeLibrary::nodeLibraryHidden)
2115 previousVisibleNodeLibraryStateWasDocked = nodeLibraryCurrentlyDocked;
2116
2117 nodeLibraryCurrentlyVisible = visible;
2118 nodeLibraryCurrentlyDocked = docked;
2119 if (docked)
2120 currentFloatingNodeLibrary = NULL;
2121
2122 settings->setValue(nodeLibraryVisibilityStateSettingsKey, nodeLibraryCurrentlyVisible);
2123 settings->setValue(nodeLibraryDockingStateSettingsKey, nodeLibraryCurrentlyDocked);
2124
2125 emit globalNodeLibraryStateChanged(updatedNodeLibraryState, currentFloatingNodeLibrary, false);
2126}
2127
2132void VuoEditor::updateFloatingNodeLibraryModules()
2133{
2134 if (!currentFloatingNodeLibrary)
2135 return;
2136
2138 if (activeWindow)
2139 {
2140 VuoModuleManager *activeCompositionModuleManager = activeWindow->getComposition()->getModuleManager();
2141 if (activeCompositionModuleManager->getNodeLibrary() != currentFloatingNodeLibrary)
2142 {
2143 // Tell other open editor windows to stop sending updates to the floating node library, if they were doing so.
2144 QList<VuoEditorWindow *> openWindows = VuoEditorUtilities::getOpenCompositionEditingWindows();
2145 foreach (VuoEditorWindow *window, openWindows)
2146 {
2147 if (window != activeWindow)
2148 {
2149 VuoModuleManager *windowModuleManager = window->getComposition()->getModuleManager();
2150 if (windowModuleManager->getNodeLibrary() == currentFloatingNodeLibrary)
2151 windowModuleManager->setNodeLibrary(NULL);
2152 }
2153 }
2154
2155 // Tell the top-level app to stop sending updates to the floating node library, if it was doing so.
2156 if (moduleManager->getNodeLibrary() == currentFloatingNodeLibrary)
2157 moduleManager->setNodeLibrary(NULL);
2158 }
2159 }
2160
2161 VuoModuleManager *updatedModuleManager = (activeWindow? activeWindow->getComposition()->getModuleManager() : this->moduleManager);
2162 if (updatedModuleManager->getNodeLibrary() != currentFloatingNodeLibrary)
2163 {
2164 // Document the node library's current state (selected and documented items, filter text) before resetting it.
2165 QString origFilterText;
2166 set<string> origSelectedNodeClasses;
2167 string origDocumentedNodeClass;
2168 currentFloatingNodeLibrary->getState(origFilterText, origSelectedNodeClasses, origDocumentedNodeClass);
2169
2170 // Populate the node library with its new content.
2171 currentFloatingNodeLibrary->clearNodeClassList();
2172 updatedModuleManager->setNodeLibrary(currentFloatingNodeLibrary);
2173 updatedModuleManager->updateWithAlreadyLoadedModules();
2174
2175 // Now restore the node library's original state.
2176 currentFloatingNodeLibrary->setState(origFilterText, origSelectedNodeClasses, origDocumentedNodeClass);
2177
2178 // If the documentation pane displayed previously is no longer relevant, display
2179 // whatever documentation is most appropriate now.
2180 QString newFilterText;
2181 set<string> newSelectedNodeClasses;
2182 string newDocumentedNodeClass;
2183 currentFloatingNodeLibrary->getState(newFilterText, newSelectedNodeClasses, newDocumentedNodeClass);
2184 if (newDocumentedNodeClass.empty())
2185 {
2186 if (activeWindow)
2187 activeWindow->displayAppropriateDocumentation();
2188 else
2189 currentFloatingNodeLibrary->displayPopoverForCurrentNodeClass();
2190 }
2191 }
2192}
2193
2199{
2200 if (moduleManager->getNodeLibrary())
2201 {
2202 VuoModuleManager::CallbackType subcompositionCreated = ^void (void) {
2203 moduleManager->getNodeLibrary()->highlightNodeClass(nodeClassName);
2204 };
2205 moduleManager->doNextTimeNodeClassIsLoaded(nodeClassName, subcompositionCreated);
2206 }
2207
2208 QList<VuoEditorWindow *> openWindows = VuoEditorUtilities::getOpenCompositionEditingWindows();
2209 foreach (VuoEditorWindow *window, openWindows)
2210 {
2211 VuoModuleManager *windowModuleManager = window->getComposition()->getModuleManager();
2212 if (windowModuleManager->getNodeLibrary())
2213 {
2214 VuoModuleManager::CallbackType subcompositionCreated = ^void (void) {
2215 windowModuleManager->getNodeLibrary()->highlightNodeClass(nodeClassName);
2216 };
2217 windowModuleManager->doNextTimeNodeClassIsLoaded(nodeClassName, subcompositionCreated);
2218 }
2219 }
2220}
2221
2226void VuoEditor::updateDockedNodeLibraryModules()
2227{
2228 if (currentFloatingNodeLibrary)
2229 return;
2230
2231 // Tell each open editor window to send updates to the node library that it owns and not to any others.
2232 QList<VuoEditorWindow *> openWindows = VuoEditorUtilities::getOpenCompositionEditingWindows();
2233 foreach (VuoEditorWindow *window, openWindows)
2234 {
2235 VuoModuleManager *windowModuleManager = window->getComposition()->getModuleManager();
2236 VuoNodeLibrary *windowOwnedNodeLibrary = window->getOwnedNodeLibrary();
2237
2238 if (windowModuleManager->getNodeLibrary() != windowOwnedNodeLibrary)
2239 {
2240 windowOwnedNodeLibrary->clearNodeClassList();
2241 windowModuleManager->setNodeLibrary(windowOwnedNodeLibrary);
2242 windowModuleManager->updateWithAlreadyLoadedModules();
2243 }
2244 }
2245
2246 // Tell the top-level app to send updates to the node library that it owns and not to any others.
2247 if (moduleManager->getNodeLibrary() != ownedNodeLibrary)
2248 {
2249 ownedNodeLibrary->clearNodeClassList();
2250 moduleManager->setNodeLibrary(ownedNodeLibrary);
2251 moduleManager->updateWithAlreadyLoadedModules();
2252 }
2253}
2254
2259void VuoEditor::designateNewFloatingNodeLibrary(VuoNodeLibrary *library)
2260{
2261 if (currentFloatingNodeLibrary && (currentFloatingNodeLibrary != ownedNodeLibrary))
2262 disconnect(currentFloatingNodeLibrary, &VuoNodeLibrary::aboutToBeDestroyed, this, &VuoEditor::assignTopLevelLibraryAsReplacementFloater);
2263
2264 if (library && (library != ownedNodeLibrary))
2265 connect(library, &VuoNodeLibrary::aboutToBeDestroyed, this, &VuoEditor::assignTopLevelLibraryAsReplacementFloater);
2266
2267 currentFloatingNodeLibrary = library;
2268}
2269
2274void VuoEditor::assignTopLevelLibraryAsReplacementFloater()
2275{
2276 currentFloatingNodeLibrary = ownedNodeLibrary;
2277
2278 emit globalNodeLibraryStateChanged(getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
2279 currentFloatingNodeLibrary, true);
2280}
2281
2288{
2289 menuOpenRecent->addFile(filePath);
2291}
2292
2298{
2299 this->closedFiles.push_back(filePath.toUtf8().constData());
2300}
2301
2312
2323
2329{
2330 for (QMainWindow *openEditingWindow : VuoEditorUtilities::getOpenEditingWindows())
2332
2333 for (QWidget *openWindow : QApplication::topLevelWidgets())
2334 {
2335 VuoConsoleWindow *consoleWindow = dynamic_cast<VuoConsoleWindow *>(openWindow);
2336 if (consoleWindow)
2337 consoleWindow->getRecentFileMenu()->setRecentFiles(menuOpenRecent->getRecentFiles());
2338 }
2339
2340 settings->setValue(recentFileListSettingsKey, menuOpenRecent->getRecentFiles());
2341}
2342
2348{
2349 QString bundledManual = QDir::cleanPath(QApplication::applicationDirPath().append("/../Resources"))
2350 .append(QDir::separator())
2351 .append("vuo-")
2352 .append(VUO_VERSION_AND_BUILD_STRING)
2353 .append("--manual.pdf");
2354
2355 if (QFile(bundledManual).exists())
2356 return QString("file://").append(bundledManual);
2357 else
2358 return "https://vuo.org/manual.pdf";
2359}
2360
2365{
2366 return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2367}
2368
2374void VuoEditor::generateAllNodeSetHtmlDocumentation(string saveDirectory)
2375{
2376 const bool publishInternalVuoLinks = true;
2377 VuoFileUtilities::makeDir(saveDirectory);
2378
2379
2380 QString indexFilename(QString::fromStdString(saveDirectory) + "/index.html");
2381 QFile indexFile(indexFilename);
2382 if (!indexFile.open(QFile::WriteOnly | QFile::Truncate))
2383 throw VuoException(("Couldn't open " + indexFilename).toUtf8().data());
2384
2385 QTextStream indexWriter(&indexFile);
2386 indexWriter << VUO_QSTRINGIFY(
2387 <html>
2388 <head>
2389 <title>Vuo %1 node documentation</title>
2390 <style>
2391 * { font-size: 13pt; font-family: 'PT Sans',Avenir,'Trebuchet MS',Tahoma,sans-serif; }
2392 body { padding: 5em; }
2393 h1 { font-size: 24pt; color: #aaa; font-weight: normal; }
2394 a, a:visited { font-weight: bold; color: #69a; }
2395 p { margin-top: 0; color: #aaa; }
2396 p a, p a:visited { font-weight: normal; color: #aaa; }
2397 li { list-style: none; }
2398 </style>
2399 </head>
2400 <body>
2401 <h1>Vuo %1 node documentation</h1>
2402 <ul>
2403 )
2404 .arg(VUO_VERSION_STRING);
2405
2406 map<string, VuoCompilerNodeClass *> nodeClassesMap = compiler->getNodeClasses();
2407 vector<VuoCompilerNodeClass *> nodeClasses;
2408 for (map<string, VuoCompilerNodeClass *>::iterator i = nodeClassesMap.begin(); i != nodeClassesMap.end(); ++i)
2409 nodeClasses.push_back(i->second);
2411
2412 set<string> nodeSetNames;
2413 foreach (VuoCompilerNodeClass *nodeClass, nodeClasses)
2414 if (nodeClass->getBase()->getNodeSet())
2415 nodeSetNames.insert(nodeClass->getBase()->getNodeSet()->getName());
2416
2417 foreach (string nodeSetName, nodeSetNames)
2418 {
2419 string nodeSetSaveDirectory = saveDirectory + "/" + nodeSetName;
2420 VuoFileUtilities::makeDir(nodeSetSaveDirectory);
2421
2422 string saveNodeSetHtml = nodeSetSaveDirectory + "/index.html";
2423 VuoNodeSet *nodeSet = compiler->getNodeSetForName(nodeSetName);
2424
2425 generateMainHtmlPageForNodeSet(nodeSet, saveNodeSetHtml, publishInternalVuoLinks);
2426 nodeSet->extractDocumentationResources(nodeSetSaveDirectory);
2427 generateNodeClassHtmlPagesForNodeSet(nodeSet, nodeSetSaveDirectory, publishInternalVuoLinks);
2428
2429 string description = nodeSet->getDescription();
2430 string firstLineOfDescription = description.substr(0, description.find('\n') - 1);
2431 string filteredDescription = VuoStringUtilities::generateHtmlFromMarkdownLine(VuoRendererCommon::externalizeVuoNodeLinks(compiler, QString::fromStdString(firstLineOfDescription), false).toStdString());
2432 indexWriter << VUO_QSTRINGIFY(
2433 <li><a href="%1/">%2</a><p>%3</p></li>
2434 )
2435 .arg(QString::fromStdString(nodeSetName))
2436 .arg(VuoEditorComposition::formatNodeSetNameForDisplay(QString::fromStdString(nodeSetName)))
2437 .arg(QString::fromStdString(filteredDescription));
2438 }
2439
2440 indexWriter << VUO_QSTRINGIFY(
2441 </ul>
2442 </body>
2443 </html>
2444 );
2445}
2446
2455{
2456 const bool publishInternalVuoLinks = false;
2457
2458 string nodeSetName = url.host().toUtf8().constData();
2459 VuoNodeSet *nodeSet = compiler->getNodeSetForName(nodeSetName);
2460
2461 if (!nodeSet)
2462 return;
2463
2464 // If the node set resource directory did not already exist, create and populate it now.
2465 string preexistingResourceDir = getResourceDirectoryForNodeSet(nodeSetName);
2466 string tmpSaveDir = (!preexistingResourceDir.empty()? preexistingResourceDir : VuoFileUtilities::makeTmpDir(nodeSetName));
2467 string tmpSaveNodeSetHtml = tmpSaveDir + "/index.html";
2468
2469 if (tmpSaveDir != preexistingResourceDir)
2470 {
2471 generateMainHtmlPageForNodeSet(nodeSet, tmpSaveNodeSetHtml, publishInternalVuoLinks);
2472
2473 // Extract resources referenced by the documentation.
2474 nodeSet->extractDocumentationResources(tmpSaveDir);
2475 }
2476
2477 // Open the node set html file using an external browser.
2478 if (QDesktopServices::openUrl(QString("file://").append(tmpSaveNodeSetHtml.c_str())))
2480
2481 // Save documentation for each member node class to its own html file.
2482 if (tmpSaveDir != preexistingResourceDir)
2483 {
2484 generateNodeClassHtmlPagesForNodeSet(nodeSet, tmpSaveDir, publishInternalVuoLinks);
2485 setResourceDirectoryForNodeSet(nodeSet->getName().c_str(), tmpSaveDir);
2486 }
2487}
2488
2497{
2498#if VUO_PRO
2499 if (!closeWelcomeWindow())
2500 return;
2501#endif
2502
2503 string nodeClassName = url.host().toUtf8().constData();
2504
2505 VuoNodeLibrary *nodeLibraryToUpdate;
2506 if (currentFloatingNodeLibrary)
2507 {
2508 nodeLibraryToUpdate = currentFloatingNodeLibrary;
2510 }
2511 else
2512 {
2514 if (!topmostEditorWindow)
2515 topmostEditorWindow = createEditorWindow(assignUntitledDocumentIdentifier(), "", "", nullptr);
2516
2517 nodeLibraryToUpdate = topmostEditorWindow->getCurrentNodeLibrary();
2518 topmostEditorWindow->on_showNodeLibrary_triggered();
2519 }
2520
2521 if (nodeLibraryToUpdate)
2522 nodeLibraryToUpdate->setState(QString::fromStdString(nodeClassName), { nodeClassName }, nodeClassName);
2523}
2524
2531QString VuoEditor::generateHtmlDocumentationStyles(bool forBrowser, bool isDark)
2532{
2533 return VUO_QSTRINGIFY(<style>
2534 table, th, td {
2535 border: 1px solid #ccc;
2536 border-collapse: collapse;
2537 padding: %1;
2538 }
2539 code, kbd, pre {
2540 font-family: 'Monaco';
2541 font-size: 12px;
2542 background-color: %2;
2543 padding: 0 0.4em;
2544 }
2545 pre {
2546 padding: 1em;
2547 white-space: pre-wrap;
2548 }
2549 </style>)
2550 .arg(forBrowser ? "0.4em" : "0")
2551 .arg(isDark ? "#383838" : "#ececec");
2552}
2553
2558void VuoEditor::generateMainHtmlPageForNodeSet(VuoNodeSet *nodeSet, string saveFileName, bool publishInternalVuoLinks)
2559{
2560 string nodeSetName = nodeSet->getName();
2561 vector<string> nodeSetClassNames = nodeSet->getNodeClassNames();
2562
2563 // Metadata and node set description
2564 //: Appears in the webpage title on node set documentation pages.
2565 QString htmlHeader = "<html><head><meta charset=\"utf-8\"><title>" + tr("Vuo Node Set Documentation") + ": "
2566 + QString::fromStdString(nodeSetName)
2567 + "</title>"
2569 + "</head><body>";
2570
2571 QString nodeSetDisplayName = VuoEditorComposition::formatNodeSetNameForDisplay(QString::fromStdString(nodeSetName));
2572 QString title = QString("<h2>").append(nodeSetDisplayName);
2573 if (nodeSetDisplayName != nodeSetName.c_str())
2574 title.append(" (").append(nodeSetName.c_str()).append(")");
2575 title.append("</h2>");
2576
2577 string nodeSetDocumentationContent = nodeSet->getDescription();
2578 QString filteredNodeSetDocumentationContent = VuoStringUtilities::generateHtmlFromMarkdown(publishInternalVuoLinks?
2579 VuoRendererCommon::externalizeVuoNodeLinks(compiler, QString::fromStdString(nodeSetDocumentationContent), false).toStdString() :
2580 removeVuoLinks(nodeSetDocumentationContent)).c_str();
2581
2582
2583 QString htmlFooter = "</body></html>";
2584
2585 // Example compositions
2586 vector<string> nodeSetExampleCompositionFileNames = nodeSet->getExampleCompositionFileNames();
2587 QString nodeSetExampleCompositionText = "";
2588
2589 foreach (string compositionFileName, nodeSetExampleCompositionFileNames)
2590 {
2591 string compositionAsString = nodeSet->getExampleCompositionContents(compositionFileName);
2592 VuoCompositionMetadata metadata(compositionAsString);
2593
2594 string name = metadata.getCustomizedName();
2595 if (name.empty())
2596 name = VuoEditorComposition::formatCompositionFileNameForDisplay(compositionFileName.c_str()).toUtf8().constData();
2597
2598 string description = metadata.getDescription();
2599 string filteredDescription = (publishInternalVuoLinks? VuoRendererCommon::externalizeVuoNodeLinks(compiler, QString::fromStdString(description), false).toStdString() : removeVuoLinks(description));
2600 QString compositionDescription = VuoStringUtilities::generateHtmlFromMarkdownLine(filteredDescription).c_str();
2601
2602 nodeSetExampleCompositionText.append("<li>")
2603 .append("<a href=\"")
2605 .append("://")
2606 .append(nodeSetName.c_str())
2607 .append("/")
2608 .append(compositionFileName.c_str())
2609 .append("\"><font size=+1>")
2610 .append(name.c_str())
2611 .append("</font></a>");
2612
2613 if (!compositionDescription.isEmpty())
2614 nodeSetExampleCompositionText.append(": ").append(compositionDescription);
2615
2616 nodeSetExampleCompositionText.append("</li>\n");
2617 }
2618
2619 if (nodeSetExampleCompositionText.size() > 0)
2620 //: Appears on node set documentation webpages.
2621 nodeSetExampleCompositionText = "<BR><HR><h3>" + tr("Example composition(s)", "", nodeSetExampleCompositionFileNames.size()) + ":</h3>\n<ul>\n" + nodeSetExampleCompositionText + "</ul>";
2622
2623 // Node classes
2624 QString nodeSetClassesText = "";
2625
2626 // Sort by default node title.
2627 std::sort(nodeSetClassNames.begin(), nodeSetClassNames.end(), [=](const string nodeClassName1, const string nodeClassName2) {
2628 VuoCompilerNodeClass *nodeClass1 = compiler->getNodeClass(nodeClassName1);
2629 VuoCompilerNodeClass *nodeClass2 = compiler->getNodeClass(nodeClassName2);
2630 string nodeClass1Title = nodeClass1? nodeClass1->getBase()->getDefaultTitle() : "";
2631 string nodeClass2Title = nodeClass2? nodeClass2->getBase()->getDefaultTitle() : "";
2632
2633 return nodeClass1Title < nodeClass2Title;
2634 });
2635
2636 foreach (string nodeClassName, nodeSetClassNames)
2637 {
2638 VuoCompilerNodeClass *nodeClass = compiler->getNodeClass(nodeClassName);
2639 if (!nodeClass || nodeClass->getBase()->getDeprecated())
2640 continue;
2641
2642 QString nodeClassTitle = nodeClass->getBase()->getDefaultTitle().c_str();
2643 QString nodeClassDescription = VuoStringUtilities::generateHtmlFromMarkdown(nodeClass->getBase()->getDescription()).c_str();
2644 QString nodeClassProNodeIndicator;
2645#if VUO_PRO
2646 if (nodeClass->getBase()->isPro())
2647 //: Appears on node set documentation webpages.
2648 nodeClassProNodeIndicator = " <b>[<a href=\"https://vuo.org/pro-nodes\">" + tr("Pro node") + "</a>]</b>";
2649#endif
2650
2651 QString nodeClassDocumentationLink = QString((nodeClassName + ".html").c_str());
2652
2653 // Strip HTML and extract the first sentence of the node class description for display.
2654 nodeClassDescription.remove(QRegExp("<[^>]*>"));
2655 nodeClassDescription.replace(QRegExp("\\.\\s.*"), ".");
2656
2657 nodeSetClassesText.append("<li>")
2658 .append("<a href=\"")
2659 .append(nodeClassDocumentationLink)
2660 .append("\">")
2661 .append("<font size=+1>")
2662 .append(nodeClassTitle)
2663 .append("</font>")
2664 .append("</a>")
2665 .append(" (")
2666 .append(nodeClassName.c_str())
2667 .append(")");
2668
2669 nodeSetClassesText.append(nodeClassProNodeIndicator);
2670
2671 if (!nodeClassDescription.isEmpty())
2672 nodeSetClassesText.append(": ").append(nodeClassDescription);
2673
2674 nodeSetClassesText.append("</li>\n");
2675 }
2676
2677 if (nodeSetClassesText.size() > 0)
2678 //: Appears on node set documentation webpages.
2679 nodeSetClassesText = "<BR><HR><h3>" + tr("Node(s)", "", nodeSetClassNames.size()) + ":</h3>\n<ul>\n" + nodeSetClassesText + "</ul>";
2680
2681 // Save the node set documentation to an html file.
2682 ofstream savedNodeSetFile(saveFileName.c_str(), ios::trunc);
2683
2684 savedNodeSetFile << htmlHeader.append("\n\n").toUtf8().constData();
2685 savedNodeSetFile << title.append("\n\n").toUtf8().constData();
2686 savedNodeSetFile << filteredNodeSetDocumentationContent.append("\n\n").toUtf8().constData();
2687 savedNodeSetFile << nodeSetExampleCompositionText.append("\n\n").toUtf8().constData();
2688 savedNodeSetFile << nodeSetClassesText.append("\n\n").toUtf8().constData();
2689 savedNodeSetFile << htmlFooter.append("\n\n").toUtf8().constData();
2690
2691 savedNodeSetFile.close();
2692}
2693
2705void VuoEditor::generateNodeClassHtmlPagesForNodeSet(VuoNodeSet *nodeSet, string saveDir, bool publishInternalVuoLinks)
2706{
2707 string nodeSetName = nodeSet->getName();
2708 vector<string> nodeSetClassNames = nodeSet->getNodeClassNames();
2709 foreach (string nodeClassName, nodeSetClassNames)
2710 {
2711 VuoCompilerNodeClass *nodeClass = compiler->getNodeClass(nodeClassName);
2712 if (!nodeClass || nodeClass->getBase()->getDeprecated())
2713 continue;
2714
2715 string nodeClassTitle = nodeClass->getBase()->getDefaultTitle();
2716 string nodeClassDescription = nodeClass->getBase()->getDescription();
2717 string filteredNodeClassDescription = (publishInternalVuoLinks? VuoRendererCommon::externalizeVuoNodeLinks(compiler, QString::fromStdString(nodeClassDescription), false).toStdString() :
2718 removeVuoLinks(nodeClassDescription));
2719
2720 vector<string> manualKeywords = nodeClass->getBase()->getKeywords();
2721 vector<string> automaticKeywords = nodeClass->getAutomaticKeywords();
2722
2723 set<string> sortedUniqueKeywords;
2724 foreach (string keyword, manualKeywords)
2725 sortedUniqueKeywords.insert(keyword);
2726
2727 foreach (string keyword, automaticKeywords)
2728 sortedUniqueKeywords.insert(keyword);
2729
2730 // Metadata and node class description
2731 //: Appears in the webpage title on node documentation pages.
2732 QString nodeClassHtmlHeader = "<html><head><meta charset=\"utf-8\"><title>" + tr("Vuo Node Documentation") + ": "
2733 + QString::fromStdString(nodeClassTitle)
2734 + " (" + QString::fromStdString(nodeClassName) + ")"
2735 + "</title>"
2737 + "</head><body>";
2738 QString nodeClassDocumentationTitle = QString("<h2>")
2739 .append(nodeClassTitle.c_str())
2740 .append(" (").append(nodeClassName.c_str()).append(")")
2741 .append("</h2>");
2742 QString nodeClassDocumentationContent = VuoStringUtilities::generateHtmlFromMarkdown(filteredNodeClassDescription).c_str();
2743 //: Appears on node documentation webpages.
2744 QString nodeClassKeywordsIntro = "<p><b>" + tr("Keyword(s)", "", sortedUniqueKeywords.size()) + "</b>: ";
2745 QString nodeClassKeywordsText = nodeClassKeywordsIntro;
2746 foreach (string keyword, sortedUniqueKeywords)
2747 {
2748 if (nodeClassKeywordsText != nodeClassKeywordsIntro)
2749 nodeClassKeywordsText.append(", ");
2750
2751 nodeClassKeywordsText.append("<i>").append(keyword.c_str()).append("</i>");
2752 }
2753 nodeClassKeywordsText.append("</p>");
2754
2755 // Rendering of model node
2757 VuoRendererNode *modelNode = new VuoRendererNode(nodeClass->newNode(), NULL);
2758 modelNode->setAlwaysDisplayPortNames(true);
2759 composition->addNode(modelNode->getBase());
2760 composition->createAndConnectInputAttachments(modelNode, compiler);
2761
2762 QSizeF size = composition->itemsBoundingRect().size().toSize();
2763 QSizeF retinaSize(size.width()*2, size.height()*2);
2764 QPixmap pixmap(retinaSize.toSize());
2765 pixmap.fill(Qt::transparent);
2766
2767 QPainter painter(&pixmap);
2768 painter.setRenderHint(QPainter::Antialiasing, true);
2769 painter.setRenderHint(QPainter::HighQualityAntialiasing, true);
2770 painter.setRenderHint(QPainter::TextAntialiasing, true);
2771
2772 composition->render(&painter);
2773
2774 string nodeClassRenderedPreviewFileName = nodeClassName + ".png";
2775 string tmpSaveNodeClassImage = saveDir + "/" + nodeClassRenderedPreviewFileName;
2776 QFile file(tmpSaveNodeClassImage.c_str());
2777 file.open(QIODevice::WriteOnly);
2778 pixmap.save(&file, "PNG");
2779
2780 delete modelNode;
2781 delete composition;
2782
2783 QString nodeClassRenderedPreview = QString("<img src=\"%1\" width=\"%2\" height=\"%3\" />")
2784 .arg(nodeClassRenderedPreviewFileName.c_str())
2785 .arg(size.width())
2786 .arg(size.height());
2787
2788 // Node class example compositions
2789 vector<string> nodeClassExampleCompositionFileNames = nodeClass->getBase()->getExampleCompositionFileNames();
2790 QString nodeClassExampleCompositionText = "";
2791
2792 foreach (string compositionFileName, nodeClassExampleCompositionFileNames)
2793 {
2794 string compositionAsString = nodeSet->getExampleCompositionContents(compositionFileName);
2795 VuoCompositionMetadata metadata(compositionAsString);
2796
2797 string name = metadata.getCustomizedName();
2798 if (name.empty())
2799 name = VuoEditorComposition::formatCompositionFileNameForDisplay(compositionFileName.c_str()).toUtf8().constData();
2800
2801 // Override @c publishInternalVuoLinks here, and always filter out links to node classes from
2802 // example composition descriptions, since those example descriptions will almost always reference the same
2803 // node class whose documentation references the example.
2804 string description = metadata.getDescription();
2805 string filteredDescription = removeVuoLinks(description);
2806 QString compositionDescription = VuoStringUtilities::generateHtmlFromMarkdownLine(filteredDescription).c_str();
2807
2808 nodeClassExampleCompositionText.append("<li>")
2809 .append("<a href=\"")
2811 .append("://")
2812 .append(nodeSetName.c_str())
2813 .append("/")
2814 .append(compositionFileName.c_str())
2815 .append("?")
2817 .append("=")
2818 .append(nodeClass->getBase()->getClassName().c_str())
2819 .append("\"><font size=+1>")
2820 .append(name.c_str())
2821 .append("</font></a>");
2822
2823 if (!compositionDescription.isEmpty())
2824 nodeClassExampleCompositionText.append(": ").append(compositionDescription);
2825
2826 nodeClassExampleCompositionText.append("</li>\n");
2827 }
2828
2829 if (nodeClassExampleCompositionFileNames.size() > 0)
2830 //: Appears on node documentation webpages.
2831 nodeClassExampleCompositionText = "<HR><h3>" + tr("Example composition(s)", "", nodeClassExampleCompositionFileNames.size()) + ":</h3>\n<ul>\n" + nodeClassExampleCompositionText + "</ul>";
2832
2833 // Footer
2834 QString nodeClassProNodeIndicator;
2835#if VUO_PRO
2836 if (nodeClass->getBase()->isPro())
2837 nodeClassProNodeIndicator = QString(VuoNodePopover::installedProNodeText).append("<br><br>");
2838#endif
2839 //: Appears on node documentation webpages.
2840 QString nodeClassSetReference = "<HR>" + nodeClassProNodeIndicator + tr("Back to %1 node set documentation.")
2841 .arg("<a href=\"index.html\">" + QString::fromStdString(nodeSetName) + "</a>");
2842 QString nodeClassHtmlFooter = "</body></html>";
2843
2844 string saveNodeClassHtml = saveDir + "/" + nodeClassName + ".html";
2845 ofstream savedNodeClassFile(saveNodeClassHtml.c_str(), ios::trunc);
2846
2847 savedNodeClassFile << nodeClassHtmlHeader.append("\n\n").toUtf8().constData();
2848 savedNodeClassFile << nodeClassDocumentationTitle.append("\n\n").toUtf8().constData();
2849 savedNodeClassFile << nodeClassRenderedPreview.append("\n\n").toUtf8().constData();
2850 savedNodeClassFile << nodeClassDocumentationContent.append("\n\n").toUtf8().constData();
2851 savedNodeClassFile << nodeClassKeywordsText.append("\n\n").toUtf8().constData();
2852 savedNodeClassFile << nodeClassExampleCompositionText.append("\n\n").toUtf8().constData();
2853 savedNodeClassFile << nodeClassSetReference.append("\n\n").toUtf8().constData();
2854 savedNodeClassFile << nodeClassHtmlFooter.append("\n\n").toUtf8().constData();
2855
2856 savedNodeClassFile.close();
2857 }
2858}
2859
2860
2865string VuoEditor::removeVuoLinks(string markdownText)
2866{
2867 QString filteredText(markdownText.c_str());
2868 QRegularExpression vuoNodeLink("\\[([^\\]]+)\\](\\(vuo-node://([^)]+)\\))");
2869 filteredText.replace(vuoNodeLink, "`\\1`");
2870
2871 QRegularExpression vuoNodeSetLink("\\[([^\\]]+)\\](\\(vuo-nodeset://([^)]+)\\))");
2872 filteredText.replace(vuoNodeSetLink, "`\\1`");
2873
2874 return filteredText.toUtf8().constData();
2875}
2876
2885{
2886 QUrl urlForParsing(url); // We can extract all parts from this except the host, since the QUrl wouldn't preserve its case.
2887
2888 QString hostPathQuery = url;
2889 hostPathQuery.remove(0, (urlForParsing.scheme() + "://").size());
2890 QString nodeSetName = hostPathQuery.left(urlForParsing.host().size());
2891
2892 QString compositionFileName = urlForParsing.path();
2893 compositionFileName.remove(0, 1); // Remove leading "/".
2894
2895 QString nodeClassToHighlight;
2896 QUrlQuery query(urlForParsing.query());
2897 if (query.hasQueryItem(vuoExampleHighlightedNodeClassQueryItem))
2898 nodeClassToHighlight = query.queryItemValue(vuoExampleHighlightedNodeClassQueryItem, QUrl::FullyDecoded);
2899
2900 VuoNodeSet *nodeSet = compiler->getNodeSetForName(nodeSetName.toStdString());
2901 if (!nodeSet)
2902 return;
2903
2904 openExampleComposition(compositionFileName, nodeSet, nodeClassToHighlight.toStdString());
2905}
2906
2912{
2913 QClipboard *clipboard = QApplication::clipboard();
2914 const QMimeData *mimeData = clipboard->mimeData();
2915
2916 if (!mimeData->hasFormat("text/plain") || mimeData->text().isNull())
2917 return "";
2918
2919 return mimeData->text();
2920}
2921
2927{
2928 return this->nodeDocumentationPanelHeight;
2929}
2930
2936{
2937 return this->nodeLibraryWidth;
2938}
2939
2948{
2949 if (resourceDirectoryForNodeSet.find(nodeSetName) != resourceDirectoryForNodeSet.end())
2950 return resourceDirectoryForNodeSet[nodeSetName];
2951 else
2952 return "";
2953}
2954
2959void VuoEditor::setResourceDirectoryForNodeSet(string nodeSetName, string directory)
2960{
2961 resourceDirectoryForNodeSet[nodeSetName] = directory;
2962}
2963
2968{
2969 return subcompositionRouter;
2970}
2971
2975void VuoEditor::initializeBuiltInDrivers()
2976{
2980
2981 // Extract built-in images to be used by protocol drivers.
2982 VuoNodeSet *imageNodeSet = compiler->getNodeSetForName("vuo.image");
2983 if (imageNodeSet)
2985}
2986
2991{
2992 dispatch_sync(builtInDriversQueue, ^{});
2993
2994 map<VuoProtocol *, VuoCompilerDriver *>::iterator driver = builtInDriverForProtocol.find(protocol);
2995 if (driver != builtInDriverForProtocol.end())
2996 return driver->second;
2997 else
2998 return NULL;
2999}
3000
3005{
3006 // Sub-item indentation.
3007 // Use transparent icons instead of whitespace padding,
3008 // so that users can more easily customize the menu item shortcuts via System Settings > Keyboard > Keyboard Shortcuts.
3009 QPixmap emptyPixmap(32, 32);
3010 emptyPixmap.fill(Qt::transparent);
3011 QIcon indentIcon(emptyPixmap);
3012
3013 // "Window" subcategories
3014 QAction *windowHeading = new QAction(this);
3015 windowHeading->setText(tr("Window"));
3016 windowHeading->setEnabled(false);
3017 m->addAction(windowHeading);
3018
3019 // Image template
3020 {
3021 QAction *templateAction = new QAction(this);
3022 templateAction->setText(tr("Image"));
3023 templateAction->setData("imageTemplate");
3024 templateAction->setIcon(indentIcon);
3025 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3026 m->addAction(templateAction);
3027 }
3028
3029 // Layers template
3030 {
3031 QAction *templateAction = new QAction(this);
3032 templateAction->setText(tr("Layers"));
3033 templateAction->setData("layersTemplate");
3034 templateAction->setIcon(indentIcon);
3035 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3036 m->addAction(templateAction);
3037 }
3038
3039 // Scene template
3040 {
3041 QAction *templateAction = new QAction(this);
3042 templateAction->setText(tr("Scene"));
3043 templateAction->setData("sceneTemplate");
3044 templateAction->setIcon(indentIcon);
3045 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3046 m->addAction(templateAction);
3047 }
3048
3049 // "Protocol" subcategories
3050 QAction *protocolHeading = new QAction(this);
3051 protocolHeading->setText(tr("Protocol"));
3052 protocolHeading->setEnabled(false);
3053 m->addSeparator();
3054 m->addAction(protocolHeading);
3055
3056 // Protocols
3057 foreach (VuoProtocol *protocol, VuoProtocol::getProtocols())
3058 {
3059 QAction *protocolAction = new QAction(this);
3060 protocolAction->setText(tr(protocol->getName().c_str()));
3061 protocolAction->setData(QVariant::fromValue(protocol));
3062 protocolAction->setIcon(indentIcon);
3063 connect(protocolAction, &QAction::triggered, this, &VuoEditor::newCompositionWithProtocol);
3064 m->addAction(protocolAction);
3065 }
3066
3067 // "Export" subcategories
3068 {
3069 m->addSeparator();
3070
3071 QAction *heading = new QAction(this);
3072 heading->setText(tr("Export"));
3073 heading->setEnabled(false);
3074 m->addAction(heading);
3075
3076 {
3077 QAction *templateAction = new QAction(this);
3078 templateAction->setText(tr("Movie"));
3079 templateAction->setData("movie");
3080 templateAction->setIcon(indentIcon);
3081 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3082 m->addAction(templateAction);
3083 }
3084
3085 {
3086 QAction *templateAction = new QAction(this);
3087 templateAction->setText(tr("Screen Saver"));
3088 templateAction->setData("screensaver");
3089 templateAction->setIcon(indentIcon);
3090 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3091 m->addAction(templateAction);
3092 }
3093
3094 {
3095 QAction *heading = new QAction(this);
3096 heading->setText(tr("FFGL"));
3097 heading->setIcon(indentIcon);
3098 heading->setEnabled(false);
3099 m->addAction(heading);
3100
3101 {
3102 QAction *templateAction = new QAction(this);
3103 templateAction->setText(" " + tr("Source"));
3104 templateAction->setData("FFGLSource");
3105 templateAction->setIcon(indentIcon);
3106 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3107 m->addAction(templateAction);
3108 }
3109
3110 {
3111 QAction *templateAction = new QAction(this);
3112 templateAction->setText(" " + tr("Effect"));
3113 templateAction->setData("FFGLEffect");
3114 templateAction->setIcon(indentIcon);
3115 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3116 m->addAction(templateAction);
3117 }
3118
3119 {
3120 QAction *templateAction = new QAction(this);
3121 templateAction->setText(" " + tr("Blend Mode"));
3122 templateAction->setData("FFGLBlendMode");
3123 templateAction->setIcon(indentIcon);
3124 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3125 m->addAction(templateAction);
3126 }
3127 }
3128
3129 {
3130 QAction *heading = new QAction(this);
3131 heading->setText(tr("FxPlug"));
3132 heading->setIcon(indentIcon);
3133 heading->setEnabled(false);
3134 m->addAction(heading);
3135
3136 {
3137 QAction *templateAction = new QAction(this);
3138 templateAction->setText(" " + tr("Generator"));
3139 templateAction->setData("FxPlugGenerator");
3140 templateAction->setIcon(indentIcon);
3141 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3142 m->addAction(templateAction);
3143 }
3144
3145 {
3146 QAction *templateAction = new QAction(this);
3147 templateAction->setText(" " + tr("Effect"));
3148 templateAction->setData("FxPlugEffect");
3149 templateAction->setIcon(indentIcon);
3150 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3151 m->addAction(templateAction);
3152 }
3153
3154 {
3155 QAction *templateAction = new QAction(this);
3156 templateAction->setText(" " + tr("Transition"));
3157 templateAction->setData("FxPlugTransition");
3158 templateAction->setIcon(indentIcon);
3159 connect(templateAction, &QAction::triggered, this, &VuoEditor::newCompositionWithTemplate);
3160 m->addAction(templateAction);
3161 }
3162 }
3163 }
3164}
3165
3170{
3171 {
3172 QAction *action = new QAction(m);
3173 action->setText(tr("Image Filter"));
3174 action->setData("GLSLImageFilter");
3175 connect(action, &QAction::triggered, [=] {
3176 closeUnmodifiedUntitledComposition();
3177#if VUO_PRO
3178 if (!closeWelcomeWindow())
3179 return;
3180#endif
3182 });
3183 m->addAction(action);
3184 }
3185
3186 {
3187 QAction *action = new QAction(m);
3188 action->setText(tr("Image Generator"));
3189 action->setData("GLSLImageGenerator");
3190 connect(action, &QAction::triggered, [=] {
3191 closeUnmodifiedUntitledComposition();
3192#if VUO_PRO
3193 if (!closeWelcomeWindow())
3194 return;
3195#endif
3197 });
3198 m->addAction(action);
3199 }
3200
3201 {
3202 QAction *action = new QAction(m);
3203 action->setText(tr("Image Transition"));
3204 action->setData("GLSLImageTransition");
3205 connect(action, &QAction::triggered, [=] {
3206 closeUnmodifiedUntitledComposition();
3207#if VUO_PRO
3208 if (!closeWelcomeWindow())
3209 return;
3210#endif
3212 });
3213 m->addAction(action);
3214 }
3215}
3216
3222{
3223 return documentationQueue;
3224}
3225
3230{
3231#if VUO_PRO
3232 return darkInterfaceAction->isChecked();
3233#else
3234 return false;
3235#endif
3236}
3237
3243{
3244 return this->canvasOpacity;
3245}
3246
3250void VuoEditor::showGridLinesToggled(bool show)
3251{
3253 if (show)
3254 {
3255 showGridPointsAction->setChecked(false);
3256 type = VuoRendererComposition::LineGrid;
3257 }
3258 else
3259 type = VuoRendererComposition::NoGrid;
3260
3261 settings->setValue(gridTypeSettingsKey, type);
3263
3264 emit showGridToggled();
3265}
3266
3270void VuoEditor::showGridPointsToggled(bool show)
3271{
3273 if (show)
3274 {
3275 showGridLinesAction->setChecked(false);
3276 type = VuoRendererComposition::PointGrid;
3277 }
3278 else
3279 type = VuoRendererComposition::NoGrid;
3280
3281 settings->setValue(gridTypeSettingsKey, type);
3283
3284 emit showGridToggled();
3285}
3286
3290void VuoEditor::updateSnapToGrid(bool snap)
3291{
3292 settings->setValue(snapToGridSettingsKey, snap);
3293
3295}
3296
3300void VuoEditor::updateColor(bool isDark)
3301{
3302 settings->setValue(darkInterfaceSettingsKey, isDark);
3303
3305
3306 setStyleSheet(VuoRendererCommon::getStyleSheet(isDark));
3307}
3308
3313void VuoEditor::updateCanvasOpacity(QAction *setOpacityAction)
3314{
3315 updateCanvasOpacityTo(setOpacityAction->data().toInt());
3316 updateUI();
3317}
3318
3323void VuoEditor::updateCanvasOpacityTo(int opacity)
3324{
3325 settings->setValue(canvasOpacitySettingsKey, opacity);
3326 canvasOpacity = opacity;
3327}
3328
3337{
3338 VuoEditor::documentationGenerationDirectory = dir;
3339}
3340
3347{
3348 map<QString, QString> modelExampleCompositionsAndNodeSets;
3349 modelExampleCompositionsAndNodeSets["LaserDiscoball.vuo"] = "vuo.audio";
3350 modelExampleCompositionsAndNodeSets["Tschuri.vuo"] = "vuo.image";
3351 modelExampleCompositionsAndNodeSets["SlitscanMixingInk.vuo"] = "vuo.layer";
3352 modelExampleCompositionsAndNodeSets["DrawInSpace.vuo"] = "vuo.scene";
3353 return modelExampleCompositionsAndNodeSets;
3354}
3355
3361{
3362 map<QString, QString> modelExampleCompositionsAndNodeSets;
3363
3364 if (protocol->getId() == VuoProtocol::imageFilter)
3365 {
3366 modelExampleCompositionsAndNodeSets["ChangeRipplePeriodically.vuo"] = "vuo.time";
3367 modelExampleCompositionsAndNodeSets["PixellateImageRadially.vuo"] = "vuo.image";
3368 }
3369
3370 else if (protocol->getId() == VuoProtocol::imageGenerator)
3371 {
3372 modelExampleCompositionsAndNodeSets["AnimateConcentricCircles.vuo"] = "vuo.image";
3373 modelExampleCompositionsAndNodeSets["DrawRainbowTrail.vuo"] = "vuo.layer";
3374 modelExampleCompositionsAndNodeSets["FlyAtWarpSpeed.vuo"] = "vuo.image";
3375 modelExampleCompositionsAndNodeSets["GenerateCheckerboardImage.vuo"] = "vuo.image";
3376 modelExampleCompositionsAndNodeSets["MakeDriftingClouds.vuo"] = "vuo.image";
3377 modelExampleCompositionsAndNodeSets["MakeOvalPatterns.vuo"] = "vuo.image";
3378 modelExampleCompositionsAndNodeSets["RecordCameraDrags.vuo"] = "vuo.data";
3379 modelExampleCompositionsAndNodeSets["RippleImageGradients.vuo"] = "vuo.image";
3380 modelExampleCompositionsAndNodeSets["RippleImageOfSphere.vuo"] = "vuo.scene";
3381 modelExampleCompositionsAndNodeSets["SpinKaleidoscope.vuo"] = "vuo.event";
3382 modelExampleCompositionsAndNodeSets["Tschuri.vuo"] = "vuo.image";
3383 }
3384
3385 else if (protocol->getId() == VuoProtocol::imageTransition)
3386 {
3387 modelExampleCompositionsAndNodeSets["BlendImages.vuo"] = "vuo.image";
3388 modelExampleCompositionsAndNodeSets["CrossfadeAndZoom.vuo"] = "vuo.image";
3389 modelExampleCompositionsAndNodeSets["CrossfadeWithBlobMask.vuo"] = "vuo.image";
3390 modelExampleCompositionsAndNodeSets["CrossfadeWithMovingTiles.vuo"] = "vuo.image";
3391 }
3392
3393 return modelExampleCompositionsAndNodeSets;
3394}
3395
3399QString VuoEditor::getURLForExampleComposition(QString compositionName, QString nodeSetName)
3400{
3401 QString compositionURL = QString(VuoEditor::vuoExampleCompositionScheme)
3402 .append("://")
3403 .append(nodeSetName)
3404 .append("/")
3405 .append(compositionName);
3406 return compositionURL;
3407}
3408
3413{
3414#if VUO_PRO
3415 return getStoredUserName_Pro();
3416#else
3417 const char *user = getenv("USER");
3418 if (!user)
3419 return string();
3420 return user;
3421#endif
3422}
3423
3428{
3429#if VUO_PRO
3430 return getStoredUserProfileURL_Pro();
3431#else
3432 return "";
3433#endif
3434}
3435
3441{
3442 settings->setValue(subcompositionPrefixSettingsKey, prefix);
3443 subcompositionPrefix = prefix;
3444}
3445
3446
3451{
3452 return (!subcompositionPrefix.isEmpty()? subcompositionPrefix : getDefaultSubcompositionPrefix());
3453}
3454
3460{
3461 return getUserName().c_str();
3462}
3463
3468{
3469 menuOpenRecent->openMostRecentFile();
3470}
3471
3476{
3477 menuOpenExample->openRandomExample();
3478}