48#include <Carbon/Carbon.h>
49#include <IOKit/IOKitLib.h>
51#include <objc/objc-runtime.h>
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";
89string VuoEditor::documentationGenerationDirectory =
"";
104 : QApplication(argc,argv)
108 compilerQueue = dispatch_queue_create(
"org.vuo.editor.compiler", DISPATCH_QUEUE_SERIAL);
111 QCoreApplication::setOrganizationName(
"Vuo");
112 QCoreApplication::setOrganizationDomain(
"vuo.org");
113 QCoreApplication::setApplicationName(
"Editor");
114 settings =
new QSettings();
124 setWheelScrollLines(2);
127 uiInitialized =
false;
130 setQuitOnLastWindowClosed(
false);
132 ownedNodeLibrary = NULL;
133 documentationQueue = dispatch_queue_create(
"org.vuo.editor.documentation", NULL);
134 userSubscriptionLevel = VuoEditor::CommunityUser;
135 reportAbsenceOfUpdates =
false;
136 networkManager = NULL;
138 moduleManager =
nullptr;
143 const bool defaultNodeLibraryVisibilityState =
true;
144 const bool defaultNodeLibraryDockingState =
true;
145 const int defaultNodeLibraryDocumentationPanelHeight = 280;
147 qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents,
false);
148 qApp->setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents,
false);
150 qApp->setAttribute(Qt::AA_UseHighDpiPixmaps);
155 settings->setValue(
"ApplePersistenceIgnoreStateQuietly",
true);
157 QStringList storedRecentFileList = settings->value(recentFileListSettingsKey).toStringList();
167 subcompositionPrefix = settings->contains(subcompositionPrefixSettingsKey)?
168 settings->value(subcompositionPrefixSettingsKey).toString() :
171 canvasOpacity = settings->contains(canvasOpacitySettingsKey)?
172 settings->value(canvasOpacitySettingsKey).toInt() :
175 nodeLibraryDisplayMode = settings->contains(nodeLibraryDisplayModeSettingsKey)?
177 defaultNodeLibraryDisplayMode;
179 nodeLibraryCurrentlyVisible = settings->contains(nodeLibraryVisibilityStateSettingsKey)?
180 settings->value(nodeLibraryVisibilityStateSettingsKey).toBool() :
181 defaultNodeLibraryVisibilityState;
183 nodeLibraryCurrentlyDocked = settings->contains(nodeLibraryDockingStateSettingsKey)?
184 settings->value(nodeLibraryDockingStateSettingsKey).toBool() :
185 defaultNodeLibraryDockingState;
187 if (settings->contains(nodeLibraryFloatingPositionSettingsKey))
189 settingsContainedNodeLibraryFloatingPosition =
true;
190 nodeLibraryFloatingPosition = settings->value(nodeLibraryFloatingPositionSettingsKey).toPoint();
194 settingsContainedNodeLibraryFloatingPosition =
false;
195 nodeLibraryFloatingPosition = QPoint();
198 if (settings->contains(nodeLibraryWidthSettingsKey))
199 nodeLibraryWidth = settings->value(nodeLibraryWidthSettingsKey).toInt();
201 nodeLibraryWidth = -1;
203 if (settings->contains(nodeLibraryHeightSettingsKey))
204 nodeLibraryHeight = settings->value(nodeLibraryHeightSettingsKey).toInt();
206 nodeLibraryHeight = -1;
208 if (settings->contains(nodeDocumentationPanelHeightSettingsKey))
209 nodeDocumentationPanelHeight = settings->value(nodeDocumentationPanelHeightSettingsKey).toInt();
211 nodeDocumentationPanelHeight = defaultNodeLibraryDocumentationPanelHeight;
213 previousVisibleNodeLibraryStateWasDocked = nodeLibraryCurrentlyDocked;
214 currentFloatingNodeLibrary = NULL;
216 shaderDocumentationVisible = settings->contains(shaderDocumentationVisibilitySettingsKey)?
217 settings->value(shaderDocumentationVisibilitySettingsKey).toBool() :
220 applyStoredMovieExportSettings();
236 builtInDriversQueue = dispatch_queue_create(
"org.vuo.editor.drivers", NULL);
237 dispatch_async(builtInDriversQueue, ^{
238 initializeBuiltInDrivers();
242 if (!documentationGenerationDirectory.empty())
245 dispatch_async(dispatch_get_main_queue(), ^{
246 generateAllNodeSetHtmlDocumentation(documentationGenerationDirectory);
253 QDesktopServices::setUrlHandler(
vuoHelpBookScheme,
this,
"openHelpBookPageFromUrl");
265 menuBar =
new QMenuBar();
267 menuFile =
new QMenu(menuBar);
268 menuFile->setSeparatorsCollapsible(
false);
269 menuFile->setTitle(tr(
"&File"));
271 menuFile->addAction(tr(
"&New Composition"),
this, SLOT(
newComposition()), QKeySequence(
"Ctrl+N"));
273 menuNewCompositionWithTemplate =
new QMenu(tr(
"New Composition from Template"));
274 menuNewCompositionWithTemplate->setSeparatorsCollapsible(
false);
276 menuFile->addMenu(menuNewCompositionWithTemplate);
278 QMenu *menuNewShader =
new QMenu(tr(
"New Shader"));
279 menuNewShader->setSeparatorsCollapsible(
false);
281 menuFile->addMenu(menuNewShader);
283 menuFile->addSeparator();
284 menuFile->addAction(tr(
"&Open…"),
this, SLOT(
openFile()), QKeySequence(
"Ctrl+O"));
289 menuFile->addMenu(menuOpenRecent);
295 menuFile->addMenu(menuOpenExample);
299 QAction *aboutAction =
new QAction(
nullptr);
300 aboutAction->setText(tr(
"About Vuo…"));
302 aboutAction->setMenuRole(QAction::AboutRole);
303 menuFile->addAction(aboutAction);
307 QAction *quitAction =
new QAction(
nullptr);
308 quitAction->setText(tr(
"&Quit"));
309 quitAction->setShortcut(QKeySequence(
"Ctrl+Q"));
311 quitAction->setMenuRole(QAction::QuitRole);
312 menuFile->addAction(quitAction);
314 menuBar->addAction(menuFile->menuAction());
317 enableMenuItems(menuFile, canCloseWelcomeWindow());
322 menuView =
new QMenu(menuBar);
323 menuView->setSeparatorsCollapsible(
false);
324 menuView->setTitle(tr(
"&View"));
327 showNodeLibraryAction =
new QAction(NULL);
328 showNodeLibraryAction->setText(tr(
"Show Node &Library"));
329 showNodeLibraryAction->setShortcut(QKeySequence(
"Ctrl+Return"));
331 menuView->addAction(showNodeLibraryAction);
340 bool snapToGrid = (settings->contains(snapToGridSettingsKey)?
341 settings->value(snapToGridSettingsKey).toBool() :
345 connect(
snapToGridAction, &QAction::toggled,
this, &VuoEditor::updateSnapToGrid);
349 if (settings->contains(gridOpacitySettingsKey))
350 gridOpacity = settings->value(gridOpacitySettingsKey).toInt();
353 gridOpacity = VuoEditor::defaultGridOpacity;
354 settings->setValue(gridOpacitySettingsKey, gridOpacity);
359 int gridType = (settings->contains(gridTypeSettingsKey)
360 ? settings->value(gridTypeSettingsKey).toInt()
361 : VuoRendererComposition::LineGrid);
404 if (action->data().toInt() == canvasOpacity)
406 action->setChecked(
true);
407 action->setEnabled(
false);
417 menuBar->addAction(menuView->menuAction());
422 menuWindow =
new QMenu(menuBar);
423 menuWindow->setSeparatorsCollapsible(
false);
424 menuWindow->setTitle(tr(
"&Window"));
428 connect(menuWindow, &QMenu::aboutToShow,
this, &VuoEditor::updateUI);
430 menuBar->addAction(menuWindow->menuAction());
434 menuHelp =
new QMenu(menuBar);
435 menuHelp->setSeparatorsCollapsible(
false);
436 menuHelp->setTitle(tr(
"&Help"));
443 connect(menuHelp, &QMenu::aboutToShow, [
this] {
populateHelpMenu(menuHelp); });
445 menuBar->addAction(menuHelp->menuAction());
452 dockContextMenu =
new QMenu();
453 dockContextMenu->setSeparatorsCollapsible(
false);
454 dockContextMenu->setAsDockMenu();
456 connect(dockContextMenu, &QMenu::aboutToShow,
this, &VuoEditor::updateUI);
460 initializeTopLevelNodeLibrary(compiler,
461 nodeLibraryDisplayMode,
462 settingsContainedNodeLibraryFloatingPosition,
463 nodeLibraryFloatingPosition,
466 if (!nodeLibraryCurrentlyDocked)
467 designateNewFloatingNodeLibrary(ownedNodeLibrary);
475 conformToGlobalNodeLibraryVisibility(
476 getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
477 currentFloatingNodeLibrary);
484 connect(ownedNodeLibrary, &VuoNodeLibrary::visibilityChanged,
this, &VuoEditor::updateUI);
485 enableGlobalStateConformanceToLibrary(ownedNodeLibrary);
489 QApplication::processEvents();
490 if (queuedCompositionsToOpen.empty())
499 if (d.history().isEmpty())
502 uiInitialized =
true;
512 dispatch_sync(builtInDriversQueue, ^{});
515 menuBar->deleteLater();
516 delete subcompositionRouter;
517 delete moduleManager;
523QString VuoEditor::qtFindTranslation(
const QLocale &locale,
const QString &filename,
const QString &prefix,
const QString &directory,
const QString &suffix)
526 if (QFileInfo(filename).isRelative()) {
528 if (!path.isEmpty() && !path.endsWith(QLatin1Char(
'/')))
529 path += QLatin1Char(
'/');
534 QStringList fuzzyLocales;
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);
547 foreach (QString localeName, languages) {
548 localeName.replace(QLatin1Char(
'-'), QLatin1Char(
'_'));
550 realname = path + filename + prefix + localeName + (suffix.isNull() ? QLatin1String(
".qm") : suffix);
551 fi.setFile(realname);
552 if (fi.isReadable() && fi.isFile())
555 realname = path + filename + prefix + localeName;
556 fi.setFile(realname);
557 if (fi.isReadable() && fi.isFile())
560 fuzzyLocales.append(localeName);
564 foreach (QString localeName, fuzzyLocales) {
566 int rightmost = localeName.lastIndexOf(QLatin1Char(
'_'));
570 localeName.truncate(rightmost);
572 realname = path + filename + prefix + localeName + (suffix.isNull() ? QLatin1String(
".qm") : suffix);
573 fi.setFile(realname);
574 if (fi.isReadable() && fi.isFile())
577 realname = path + filename + prefix + localeName;
578 fi.setFile(realname);
579 if (fi.isReadable() && fi.isFile())
584 if (!suffix.isNull()) {
585 realname = path + filename + suffix;
586 fi.setFile(realname);
587 if (fi.isReadable() && fi.isFile())
591 realname = path + filename + prefix;
592 fi.setFile(realname);
593 if (fi.isReadable() && fi.isFile())
596 realname = path + filename;
597 fi.setFile(realname);
598 if (fi.isReadable() && fi.isFile())
607void VuoEditor::loadTranslations()
609 if (settings->value(
"translation/enable",
true).toBool())
611 QLocale locale = QLocale::system();
615 VUserLog(
" NL codeset : %s", nl_langinfo(CODESET));
616 VUserLog(
" LANG : %s", getenv(
"LANG"));
617 VUserLog(
" LC_ALL : %s", getenv(
"LC_ALL"));
620 CFStringRef appleLocale = (CFStringRef)CFPreferencesCopyAppValue(CFSTR(
"AppleLocale"), kCFPreferencesCurrentApplication);
622 CFRelease(appleLocale);
624 CFLocaleRef localeCF = CFLocaleCopyCurrent();
628 auto logCFArray = ^(
const char *label, CFArrayRef array){
629 CFIndex count = CFArrayGetCount(array);
631 for (CFIndex i = 0; i < count; ++i)
637 logCFArray(
"UI languages (pref)", (CFArrayRef)CFPreferencesCopyAppValue(CFSTR(
"AppleLanguages"), kCFPreferencesCurrentApplication));
639 logCFArray(
"UI languages (CF) ", CFLocaleCopyPreferredLanguages());
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());
651 QList<QString> contexts;
652 contexts <<
"qtbase" <<
"vuo";
654 QString suffix(
".qm");
655 foreach (QString context, contexts)
657 VUserLog(
"Context %s:", context.toUtf8().data());
659 QTranslator *t =
new QTranslator();
660 QString translationFilename = qtFindTranslation(locale, context, prefix, translationsPath, suffix);
661 if (translationFilename.isEmpty())
663 VUserLog(
" No translation file found.");
667 bool loaded = t->load(locale, context, prefix, translationsPath, suffix);
668 VUserLog(
" Loading '%s': %s", translationFilename.toUtf8().data(), loaded ?
"ok" :
"error");
672 bool installed = installTranslator(t);
673 VUserLog(
" Installing '%s': %s", translationFilename.toUtf8().data(), installed ?
"ok" :
"error");
677 VUserLog(
"Disabling translations since preference `translation.enable` is false.");
685 vector<QString> queuedCompositionsToOpenSnapshot(queuedCompositionsToOpen);
686 queuedCompositionsToOpen.clear();
691 foreach (QString queuedUrl, queuedCompositionsToOpenSnapshot)
702 enableMenuItems(menuFile, canCloseWelcomeWindow());
704 if (!queuedCompositionsToOpen.empty())
705 closeWelcomeWindow();
714void VuoEditor::enableMenuItems(QMenu *menu,
bool enable)
719 foreach (QAction *action, menu->actions())
728 enableMenuItems(action->menu(), enable);
732 bool isQuitOrHelp = action->text().contains(
"Quit") || action->text().contains(
"About");
733 bool isTemplateHeader = (menu == menuNewCompositionWithTemplate) &&
734 action->data().value<QString>().isEmpty() &&
736 if (!(isQuitOrHelp || isTemplateHeader))
737 action->setEnabled(enable);
763 if (ownedNodeLibrary)
764 disableGlobalStateConformanceToLibrary(ownedNodeLibrary);
771 if (windowsRemainingAfterQuitRequested.empty())
775 windowsRemainingAfterQuitRequested[0]->close();
786 if (windowsRemainingAfterQuitRequested.empty())
791 foreach (QMainWindow *window, windowsRemainingAfterQuitRequested)
794 if (compositionWindow)
798 if (ownedNodeLibrary)
799 enableGlobalStateConformanceToLibrary(ownedNodeLibrary);
801 windowsRemainingAfterQuitRequested.clear();
812 if (windowsRemainingAfterQuitRequested.empty())
816 windowsRemainingAfterQuitRequested.removeOne(window);
818 if (windowsRemainingAfterQuitRequested.empty())
822 windowsRemainingAfterQuitRequested[0]->close();
828void VuoEditor::reallyQuit()
841 QApplication::quit();
849 return nodeLibraryCurrentlyDocked;
857void VuoEditor::enableGlobalStateConformanceToLibrary(
VuoNodeLibrary *library)
860 connect(library, &VuoNodeLibrary::topLevelChanged,
this, &VuoEditor::updateGlobalNodeLibraryDockedState);
872void VuoEditor::disableGlobalStateConformanceToLibrary(
VuoNodeLibrary *library)
875 disconnect(library, &VuoNodeLibrary::topLevelChanged,
this, &VuoEditor::updateGlobalNodeLibraryDockedState);
885void VuoEditor::initializeTopLevelNodeLibrary(
VuoCompiler *nodeLibraryCompiler,
887 bool setFloatingPosition,
888 QPoint floatingPosition,
889 int nodeLibraryWidth,
890 int nodeLibraryHeight)
892 ownedNodeLibrary =
new VuoNodeLibrary(nodeLibraryCompiler, moduleManager, NULL, nodeLibraryDisplayMode);
893 ownedNodeLibrary->setObjectName(
"Top-level node library");
895 ownedNodeLibrary->setFloating(
true);
897 if (setFloatingPosition)
898 ownedNodeLibrary->move(floatingPosition);
900 if (nodeLibraryHeight >= 0)
901 ownedNodeLibrary->resize(ownedNodeLibrary->rect().width(), nodeLibraryHeight);
912 aboutBox->showNormal();
914 aboutBox->activateWindow();
934 m->addAction(tr(
"Minimize"), currentWindow, &QMainWindow::showMinimized, QKeySequence(
"Ctrl+M"));
935 m->addAction(tr(
"Zoom"), currentWindow, &QMainWindow::showMaximized);
941 populateWindowMenu_Pro(m);
945 if (! openWindows.empty())
949 for (QMainWindow *openWindow : openWindows)
952 m->addAction(raiseDocumentAction);
955 raiseDocumentAction->setChecked(openWindow == currentWindow);
956 else if (openWindow->isMinimized())
957 raiseDocumentAction->setChecked(
false);
970 QAction *vuoManualAction =
new QAction(m);
971 vuoManualAction->setText(tr(
"Vuo Manual"));
974 m->addAction(vuoManualAction);
977 QAction *vuoManualPDFAction =
new QAction(m);
978 vuoManualPDFAction->setText(tr(
"Vuo Manual (PDF)"));
981 m->addAction(vuoManualPDFAction);
984 QAction *videoTutorialsAction =
new QAction(m);
985 videoTutorialsAction->setText(tr(
"Video Tutorials"));
988 m->addAction(videoTutorialsAction);
993 QAction *searchVuoOrgAction =
new QAction(m);
994 searchVuoOrgAction->setText(tr(
"Search vuo.org"));
995 searchVuoOrgAction->setData(QUrl(
"https://vuo.org/search"));
997 m->addAction(searchVuoOrgAction);
1002 QAction *communityActivityAction =
new QAction(m);
1003 communityActivityAction->setText(tr(
"View Community Activity"));
1004 communityActivityAction->setData(QUrl(
"https://community.vuo.org"));
1006 m->addAction(communityActivityAction);
1009 QAction *shareCompositionAction =
new QAction(m);
1010 shareCompositionAction->setText(tr(
"Share a Composition"));
1011 shareCompositionAction->setData(QUrl(
"https://vuo.org/composition"));
1013 m->addAction(shareCompositionAction);
1016 QAction *startDiscussionAction =
new QAction(m);
1017 startDiscussionAction->setText(tr(
"Start a Discussion"));
1018 startDiscussionAction->setData(QUrl(
"https://vuo.org/community/discussion"));
1020 m->addAction(startDiscussionAction);
1023 QAction *reportBugAction =
new QAction(m);
1024 reportBugAction->setText(tr(
"Report a Bug"));
1025 reportBugAction->setData(QUrl(
"https://vuo.org/bug"));
1027 m->addAction(reportBugAction);
1030 QAction *requestFeatureAction =
new QAction(m);
1031 requestFeatureAction->setText(tr(
"Request a Feature"));
1032 requestFeatureAction->setData(QUrl(
"https://vuo.org/feature-request"));
1034 m->addAction(requestFeatureAction);
1039 QAction *improveVuoAction =
new QAction(m);
1040 improveVuoAction->setText(tr(
"Help Us Improve Vuo"));
1041 improveVuoAction->setData(QUrl(
"https://vuo.org/contribute"));
1043 m->addAction(improveVuoAction);
1046 QAction *contactTeamVuoAction =
new QAction(m);
1047 contactTeamVuoAction->setText(tr(
"Contact Team Vuo"));
1048 contactTeamVuoAction->setData(QUrl(
"https://vuo.org/contact"));
1050 m->addAction(contactTeamVuoAction);
1053 VuoEditor::populateHelpMenu_Pro(m);
1066 VUserLog(
"Error: Couldn't open Vuo Manual in HelpViewer.app: %s", description);
1076void VuoEditor::openHelpBookPageFromUrl(
const QUrl &url)
1078 CFStringRef relativePath = CFStringCreateWithCString(NULL, url.url(QUrl::RemoveScheme).toUtf8().constData(), kCFStringEncodingUTF8);
1083 VUserLog(
"Error: Couldn't open Vuo Manual in HelpViewer.app: %s", description);
1086 CFRelease(relativePath);
1100 VuoEditorWindow *w = createEditorWindow(filename, filename, compositionAsString, activeProtocol);
1113VuoEditorWindow * VuoEditor::createEditorWindow(QString documentIdentifier, QString filename,
const string &compositionAsString,
1114 VuoProtocol *activeProtocol,
string nodeClassToHighlight)
1124 compositionAsString,
1125 nodeLibraryDisplayMode,
1126 getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked),
1127 currentFloatingNodeLibrary,
1129 nodeClassToHighlight);
1137 documentIdentifierAssigned[filename.isEmpty() ? documentIdentifier : filename] = w;
1139 const int defaultWindowXOffset = 40;
1140 const int defaultWindowYOffset = 40;
1147 if (ownedNodeLibrary && nodeLibraryCurrentlyVisible && !nodeLibraryCurrentlyDocked)
1150 int initialWindowXOffset = defaultWindowXOffset;
1151 int initialWindowYOffset = defaultWindowYOffset;
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);
1157 int availableSpaceLeftBoundary = QApplication::desktop()->availableGeometry(w).left();
1158 int availableSpaceRightBoundary = QApplication::desktop()->availableGeometry(w).right();
1160 bool nodeLibraryCloserToLeft = (nodeLibraryCenterPos - availableSpaceLeftBoundary) <
1161 (availableSpaceRightBoundary - nodeLibraryCenterPos);
1163 const int horizontalBuffer = 10;
1164 bool spaceForWindowToRightOfNodeLibrary = (availableSpaceRightBoundary - nodeLibraryRightPos) >=
1165 (w->geometry().width() + horizontalBuffer);
1169 if ((nodeLibraryCloserToLeft && spaceForWindowToRightOfNodeLibrary))
1170 initialWindowXOffset = nodeLibraryRightPos + horizontalBuffer;
1177 const int titleBarHeight = 16;
1178 int nodeLibraryTopPos = ownedNodeLibrary->mapToGlobal(ownedNodeLibrary->rect().topLeft()).y() - titleBarHeight;
1180 if (nodeLibraryTopPos < defaultWindowYOffset)
1181 initialWindowYOffset = nodeLibraryTopPos;
1183 w->move(initialWindowXOffset, initialWindowYOffset);
1190 yScreenSpaceShortage = qMax(0, (w->geometry().bottom()) - QApplication::desktop()->availableGeometry(w).bottom() + 5);
1196 w->move(activeWindow->pos() + QPoint(defaultWindowXOffset, defaultWindowYOffset));
1202 w->resize(w->width(), w->height()-yScreenSpaceShortage);
1212 createEditorWindow_Pro(w);
1233 QString summary = tr(
"This composition contains nodes that aren't installed.");
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")
1258 connect(window, &QMainWindow::destroyed,
this, &VuoEditor::updateUI);
1273 dispatch_async(compilerQueue, ^{
1274 dispatch_sync(dispatch_get_main_queue(), ^{
1284 if (!closeWelcomeWindow())
1287 QString identifier = assignUntitledDocumentIdentifier();
1288 VUserLog(
"%s: New empty composition", identifier.toUtf8().data());
1289 createEditorWindow(identifier,
"",
"", NULL);
1296 if (!closeWelcomeWindow())
1300 QString identifier = assignUntitledDocumentIdentifier();
1301 VUserLog(
"%s: New empty composition", identifier.toUtf8().data());
1302 createEditorWindow(identifier,
"",
"", NULL);
1311 QString identifier = assignUntitledDocumentIdentifier();
1312 VUserLog(
"%s: New Composition with content", identifier.toUtf8().data());
1313 return createEditorWindow(identifier, QString::fromStdString(compositionDir), content, NULL);
1322 QAction *sender = (QAction *)QObject::sender();
1325 closeUnmodifiedUntitledComposition();
1327 if (!closeWelcomeWindow())
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);
1337 string nodeLibraryFilterText = getFilterTextForTemplate(selectedProtocol->
getId());
1338 if (!nodeLibraryFilterText.empty())
1352 QAction *sender = (QAction *)QObject::sender();
1353 QString selectedTemplate =
static_cast<QString
>(sender->data().value<QString>());
1356 closeUnmodifiedUntitledComposition();
1358 if (!closeWelcomeWindow())
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);
1369 string nodeLibraryFilterText = getFilterTextForTemplate(selectedTemplate.toUtf8().constData());
1370 if (!nodeLibraryFilterText.empty())
1385string VuoEditor::getFilterTextForTemplate(
string templateID)
1387 map<string, string> filterTextForTemplate;
1395 filterTextForTemplate[
"imageTemplate"] =
"vuo.image";
1396 filterTextForTemplate[
"layersTemplate"] =
"vuo.layer shape";
1397 filterTextForTemplate[
"sceneTemplate"] =
"vuo.scene shape";
1411 map<string, string>::iterator i = filterTextForTemplate.find(templateID);
1412 if (i != filterTextForTemplate.end())
1421QString VuoEditor::assignUntitledDocumentIdentifier(
void)
1424 int documentIdentifierInstanceNum = 1;
1426 while(documentIdentifierAssigned[uniqueDocumentIdentifier])
1428 std::ostringstream oss;
1429 oss <<
" " << ++documentIdentifierInstanceNum;
1433 return uniqueDocumentIdentifier;
1441 QFileDialog d(NULL,
"",
"",
"Vuo Composition (*.vuo);;Fragment Shader (*.fs)");
1442 d.setFileMode(QFileDialog::ExistingFiles);
1449 QStringList fileNames;
1450 if (d.exec() == QDialog::Accepted)
1451 fileNames = d.selectedFiles();
1453 foreach (QString fileName, fileNames)
1462 closeUnmodifiedUntitledComposition();
1464 if (!closeWelcomeWindow())
1466 QString fileURL = QString(
"file://").append(filename);
1467 queuedCompositionsToOpen.push_back(QString(
"file://").append(filename));
1481 VuoErrorDialog::show(NULL, tr(
"You do not have permission to open the document \"%1\".").arg(QFileInfo(filename).fileName()),
"");
1485 string dir, file, ext;
1489 QMainWindow *window =
nullptr;
1492 VUserLog(
"%s.%s: Open", file.c_str(), ext.c_str());
1496 VuoErrorDialog::show(NULL, tr(
"\"%1\" can't be opened.").arg(QFileInfo(filename).fileName()),
"It is not a valid Vuo composition.");
1500 window = createEditorWindow(filename,
true);
1504 dynamic_cast<VuoEditorWindow *
>(window)->setIncludeInRecentFileMenu(addToRecentFileMenu);
1514 dynamic_cast<VuoCodeWindow *
>(window)->setIncludeInRecentFileMenu(addToRecentFileMenu);
1525 if (addToRecentFileMenu)
1540 VUserLog(
"%s: Open", filename.toUtf8().data());
1542 string filenameStr = filename.toStdString();
1545 QString compositionPath = QString::fromStdString(workingDir +
"/" + filenameStr);
1548 closeUnmodifiedUntitledComposition();
1550 if (!closeWelcomeWindow())
1553 createEditorWindow(compositionPath, compositionPath, compositionContents, NULL, nodeClassToHighlight);
1557 .append(nodeSet->
getName().c_str())
1569void VuoEditor::closeUnmodifiedUntitledComposition()
1591 if ((ownedNodeLibrary->isHidden()))
1595 designateNewFloatingNodeLibrary(ownedNodeLibrary);
1596 updateGlobalNodeLibraryState(
true,
false);
1609 if ((visibility == VuoNodeLibrary::nodeLibraryHidden) ||
1610 (visibility == VuoNodeLibrary::nodeLibraryDocked))
1613 ownedNodeLibrary->setVisible(
false);
1616 else if (visibility == VuoNodeLibrary::nodeLibraryFloating)
1621 if (ownedNodeLibrary == floater)
1623 if (! ownedNodeLibrary->isFloating())
1624 ownedNodeLibrary->setFloating(
true);
1628 ownedNodeLibrary->setFocus();
1639void VuoEditor::updateUI()
1645 QMainWindow *frontWindow = (openWindows.empty() ? nullptr : openWindows.front());
1648 menuWindow->clear();
1653 dockContextMenu->clear();
1655 dockContextMenu->addAction(
VuoEditorUtilities::getRaiseDocumentActionForWindow(openWindow));
1658 if (openWindows.empty())
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)
1666 QEvent e(QEvent::ParentChange);
1667 sendEvent(menuBar, &e);
1674 action->setEnabled(!action->isChecked());
1684 case QEvent::FileOpen:
1685 openUrl(
static_cast<QFileOpenEvent *
>(e)->url().toString());
1688 case QEvent::ApplicationActivate:
1692 case QEvent::ApplicationDeactivate:
1703 return QApplication::event(e);
1713 QAction *sender = (QAction *)QObject::sender();
1714 QDesktopServices::openUrl(sender->data().toUrl());
1723 QAction *sender = (QAction *)QObject::sender();
1724 QString filePath = sender->data().value<QString>();
1726 if (! filePath.startsWith(
"file://"))
1727 filePath = QString(
"file://").append(filePath);
1738 QAction *sender = (QAction *)QObject::sender();
1739 QString filePath = sender->data().value<QString>();
1740 moveFileToTrash(filePath);
1746void VuoEditor::moveFileToTrash(QString filePath)
1771 queuedCompositionsToOpen.push_back(url);
1777 string dir, file, ext;
1783 string sourceNodePath = dir +
"/" + file +
"." + ext;
1784 string targetNodePath = installedNodeDir +
"/" + file +
"." + ext;
1790 QMessageBox messageBox;
1791 string errorSummary =
"A node named \"" + file +
"\" already exists. Do you want to replace it with the one you're installing?";
1794 messageBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint);
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; }");
1805 static_cast<QPushButton *
>(messageBox.button(QMessageBox::Cancel))->setAutoDefault(
false);
1806 messageBox.button(QMessageBox::Cancel)->setFocus();
1810 if (messageBox.exec() == QMessageBox::Discard)
1834 VUserLog(
"Install node %s.%s", file.c_str(), ext.c_str());
1846 QMessageBox installationSuccessMessageBox;
1847 installationSuccessMessageBox.setText(tr(
"Your node has been installed!"));
1849 installationSuccessMessageBox.exec();
1853 closeWelcomeWindow(
true);
1860 queuedCompositionsToOpen.push_back(url);
1866 QDesktopServices::openUrl(url);
1875 shaderDocumentationVisible = isVisible;
1876 settings->setValue(shaderDocumentationVisibilitySettingsKey, shaderDocumentationVisible);
1884 return shaderDocumentationVisible;
1890void VuoEditor::applyStoredMovieExportSettings()
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.);
1907void VuoEditor::updateGlobalMovieExportSettings(
int width,
1912 int spatialSupersample,
1913 int temporalSupersample,
1915 QString imageFormat,
1918 if (width != movieExportWidth)
1920 movieExportWidth = width;
1921 settings->setValue(movieExportWidthSettingsKey, movieExportWidth);
1924 if (height != movieExportHeight)
1926 movieExportHeight = height;
1927 settings->setValue(movieExportHeightSettingsKey, movieExportHeight);
1930 if (time != movieExportTime)
1932 movieExportTime = time;
1933 settings->setValue(movieExportTimeSettingsKey, movieExportTime);
1936 if (duration != movieExportDuration)
1938 movieExportDuration = duration;
1939 settings->setValue(movieExportDurationSettingsKey, movieExportDuration);
1942 if (framerate != movieExportFramerate)
1944 movieExportFramerate = framerate;
1945 settings->setValue(movieExportFramerateSettingsKey, movieExportFramerate);
1948 if (spatialSupersample != movieExportSpatialSupersample)
1950 movieExportSpatialSupersample = spatialSupersample;
1951 settings->setValue(movieExportSpatialSupersampleSettingsKey, movieExportSpatialSupersample);
1954 if (temporalSupersample != movieExportTemporalSupersample)
1956 movieExportTemporalSupersample = temporalSupersample;
1957 settings->setValue(movieExportTemporalSupersampleSettingsKey, movieExportTemporalSupersample);
1960 if (shutterAngle != movieExportShutterAngle)
1962 movieExportShutterAngle = shutterAngle;
1963 settings->setValue(movieExportShutterAngleSettingsKey,
static_cast<double>(movieExportShutterAngle));
1966 if (imageFormat != movieExportImageFormat)
1968 movieExportImageFormat = imageFormat;
1969 settings->setValue(movieExportImageFormatSettingsKey, movieExportImageFormat);
1972 if (quality != movieExportQuality)
1974 movieExportQuality = quality;
1975 settings->setValue(movieExportQualitySettingsKey, movieExportQuality);
1987 int &spatialSupersample,
1988 int &temporalSupersample,
1989 float &shutterAngle,
1990 QString &imageFormat,
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;
2012 return ((! visible)? VuoNodeLibrary::nodeLibraryHidden :
2022void VuoEditor::updateNodeLibraryDisplayMode(
bool humanReadable)
2024 nodeLibraryDisplayMode = (humanReadable? VuoNodeLibrary::displayByName : VuoNodeLibrary::displayByClass);
2025 settings->setValue(nodeLibraryDisplayModeSettingsKey, nodeLibraryDisplayMode);
2030 if ((ownedNodeLibrary->isHidden()) && (ownedNodeLibrary->
getHumanReadable() != humanReadable))
2041void VuoEditor::updateGlobalNodeLibraryFloatingPosition(QPoint newPos)
2043 nodeLibraryFloatingPosition = newPos;
2044 settings->setValue(nodeLibraryFloatingPositionSettingsKey, nodeLibraryFloatingPosition);
2050void VuoEditor::updateGlobalNodeLibraryWidth(
int newWidth)
2052 nodeLibraryWidth = newWidth;
2053 settings->setValue(nodeLibraryWidthSettingsKey, nodeLibraryWidth);
2059void VuoEditor::updateGlobalNodeLibraryHeight(
int newHeight)
2061 nodeLibraryHeight = newHeight;
2062 settings->setValue(nodeLibraryHeightSettingsKey, nodeLibraryHeight);
2068void VuoEditor::updateGlobalNodeDocumentationPanelHeight(
int newSize)
2070 nodeDocumentationPanelHeight = newSize;
2071 settings->setValue(nodeDocumentationPanelHeightSettingsKey, nodeDocumentationPanelHeight);
2077void VuoEditor::updateGlobalNodeLibraryVisibilityState(
bool visible)
2079 VUserLog(
"%s node library", visible ?
"Show" :
"Hide");
2082 bool updatedDockingState = ((visible && (! nodeLibraryCurrentlyVisible))? previousVisibleNodeLibraryStateWasDocked : nodeLibraryCurrentlyDocked);
2083 updateGlobalNodeLibraryState(visible, updatedDockingState);
2089void VuoEditor::updateGlobalNodeLibraryDockedState(
bool floating)
2091 VUserLog(
"%s node library", floating ?
"Detach" :
"Attach");
2095 designateNewFloatingNodeLibrary(floater);
2096 updateGlobalNodeLibraryState(this->nodeLibraryCurrentlyVisible, (! floating));
2099 updateFloatingNodeLibraryModules();
2101 updateDockedNodeLibraryModules();
2109void VuoEditor::updateGlobalNodeLibraryState(
bool visible,
bool docked)
2111 VuoNodeLibrary::nodeLibraryState currentNodeLibraryState = getGlobalNodeLibraryStateForAttributes(nodeLibraryCurrentlyVisible, nodeLibraryCurrentlyDocked);
2114 if (currentNodeLibraryState != VuoNodeLibrary::nodeLibraryHidden)
2115 previousVisibleNodeLibraryStateWasDocked = nodeLibraryCurrentlyDocked;
2117 nodeLibraryCurrentlyVisible = visible;
2118 nodeLibraryCurrentlyDocked = docked;
2120 currentFloatingNodeLibrary = NULL;
2122 settings->setValue(nodeLibraryVisibilityStateSettingsKey, nodeLibraryCurrentlyVisible);
2123 settings->setValue(nodeLibraryDockingStateSettingsKey, nodeLibraryCurrentlyDocked);
2132void VuoEditor::updateFloatingNodeLibraryModules()
2134 if (!currentFloatingNodeLibrary)
2141 if (activeCompositionModuleManager->
getNodeLibrary() != currentFloatingNodeLibrary)
2147 if (window != activeWindow)
2150 if (windowModuleManager->
getNodeLibrary() == currentFloatingNodeLibrary)
2156 if (moduleManager->
getNodeLibrary() == currentFloatingNodeLibrary)
2162 if (updatedModuleManager->
getNodeLibrary() != currentFloatingNodeLibrary)
2165 QString origFilterText;
2166 set<string> origSelectedNodeClasses;
2167 string origDocumentedNodeClass;
2168 currentFloatingNodeLibrary->
getState(origFilterText, origSelectedNodeClasses, origDocumentedNodeClass);
2172 updatedModuleManager->
setNodeLibrary(currentFloatingNodeLibrary);
2176 currentFloatingNodeLibrary->
setState(origFilterText, origSelectedNodeClasses, origDocumentedNodeClass);
2180 QString newFilterText;
2181 set<string> newSelectedNodeClasses;
2182 string newDocumentedNodeClass;
2183 currentFloatingNodeLibrary->
getState(newFilterText, newSelectedNodeClasses, newDocumentedNodeClass);
2184 if (newDocumentedNodeClass.empty())
2226void VuoEditor::updateDockedNodeLibraryModules()
2228 if (currentFloatingNodeLibrary)
2238 if (windowModuleManager->
getNodeLibrary() != windowOwnedNodeLibrary)
2259void VuoEditor::designateNewFloatingNodeLibrary(
VuoNodeLibrary *library)
2261 if (currentFloatingNodeLibrary && (currentFloatingNodeLibrary != ownedNodeLibrary))
2264 if (library && (library != ownedNodeLibrary))
2267 currentFloatingNodeLibrary = library;
2274void VuoEditor::assignTopLevelLibraryAsReplacementFloater()
2276 currentFloatingNodeLibrary = ownedNodeLibrary;
2279 currentFloatingNodeLibrary,
true);
2289 menuOpenRecent->
addFile(filePath);
2299 this->closedFiles.push_back(filePath.toUtf8().constData());
2333 for (QWidget *openWindow : QApplication::topLevelWidgets())
2340 settings->setValue(recentFileListSettingsKey, menuOpenRecent->
getRecentFiles());
2349 QString bundledManual = QDir::cleanPath(QApplication::applicationDirPath().append(
"/../Resources"))
2350 .append(QDir::separator())
2352 .append(VUO_VERSION_AND_BUILD_STRING)
2353 .append(
"--manual.pdf");
2355 if (QFile(bundledManual).exists())
2356 return QString(
"file://").append(bundledManual);
2358 return "https://vuo.org/manual.pdf";
2366 return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
2374void VuoEditor::generateAllNodeSetHtmlDocumentation(
string saveDirectory)
2376 const bool publishInternalVuoLinks =
true;
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());
2385 QTextStream indexWriter(&indexFile);
2386 indexWriter << VUO_QSTRINGIFY(
2389 <title>Vuo %1 node documentation</title>
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; }
2401 <h1>Vuo %1 node documentation</h1>
2404 .arg(VUO_VERSION_STRING);
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);
2412 set<string> nodeSetNames;
2417 foreach (
string nodeSetName, nodeSetNames)
2419 string nodeSetSaveDirectory = saveDirectory +
"/" + nodeSetName;
2422 string saveNodeSetHtml = nodeSetSaveDirectory +
"/index.html";
2430 string firstLineOfDescription = description.substr(0, description.find(
'\n') - 1);
2432 indexWriter << VUO_QSTRINGIFY(
2433 <li><a href=
"%1/">%2</a><p>%3</p></li>
2435 .arg(QString::fromStdString(nodeSetName))
2437 .arg(QString::fromStdString(filteredDescription));
2440 indexWriter << VUO_QSTRINGIFY(
2456 const bool publishInternalVuoLinks =
false;
2458 string nodeSetName = url.host().toUtf8().constData();
2467 string tmpSaveNodeSetHtml = tmpSaveDir +
"/index.html";
2469 if (tmpSaveDir != preexistingResourceDir)
2478 if (QDesktopServices::openUrl(QString(
"file://").append(tmpSaveNodeSetHtml.c_str())))
2482 if (tmpSaveDir != preexistingResourceDir)
2499 if (!closeWelcomeWindow())
2503 string nodeClassName = url.host().toUtf8().constData();
2506 if (currentFloatingNodeLibrary)
2508 nodeLibraryToUpdate = currentFloatingNodeLibrary;
2514 if (!topmostEditorWindow)
2515 topmostEditorWindow = createEditorWindow(assignUntitledDocumentIdentifier(),
"",
"",
nullptr);
2521 if (nodeLibraryToUpdate)
2522 nodeLibraryToUpdate->
setState(QString::fromStdString(nodeClassName), { nodeClassName }, nodeClassName);
2533 return VUO_QSTRINGIFY(<style>
2535 border: 1px solid #ccc;
2536 border-collapse: collapse;
2540 font-family:
'Monaco';
2542 background-color: %2;
2547 white-space: pre-wrap;
2550 .arg(forBrowser ?
"0.4em" :
"0")
2551 .arg(isDark ?
"#383838" :
"#ececec");
2560 string nodeSetName = nodeSet->
getName();
2565 QString htmlHeader =
"<html><head><meta charset=\"utf-8\"><title>" + tr(
"Vuo Node Set Documentation") +
": "
2566 + 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>");
2583 QString htmlFooter =
"</body></html>";
2587 QString nodeSetExampleCompositionText =
"";
2589 foreach (
string compositionFileName, nodeSetExampleCompositionFileNames)
2602 nodeSetExampleCompositionText.append(
"<li>")
2603 .append(
"<a href=\"")
2606 .append(nodeSetName.c_str())
2608 .append(compositionFileName.c_str())
2609 .append(
"\"><font size=+1>")
2610 .append(name.c_str())
2611 .append(
"</font></a>");
2613 if (!compositionDescription.isEmpty())
2614 nodeSetExampleCompositionText.append(
": ").append(compositionDescription);
2616 nodeSetExampleCompositionText.append(
"</li>\n");
2619 if (nodeSetExampleCompositionText.size() > 0)
2621 nodeSetExampleCompositionText =
"<BR><HR><h3>" + tr(
"Example composition(s)",
"", nodeSetExampleCompositionFileNames.size()) +
":</h3>\n<ul>\n" + nodeSetExampleCompositionText +
"</ul>";
2624 QString nodeSetClassesText =
"";
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() :
"";
2633 return nodeClass1Title < nodeClass2Title;
2636 foreach (
string nodeClassName, nodeSetClassNames)
2644 QString nodeClassProNodeIndicator;
2646 if (nodeClass->
getBase()->isPro())
2648 nodeClassProNodeIndicator =
" <b>[<a href=\"https://vuo.org/pro-nodes\">" + tr(
"Pro node") +
"</a>]</b>";
2651 QString nodeClassDocumentationLink = QString((nodeClassName +
".html").c_str());
2654 nodeClassDescription.remove(QRegExp(
"<[^>]*>"));
2655 nodeClassDescription.replace(QRegExp(
"\\.\\s.*"),
".");
2657 nodeSetClassesText.append(
"<li>")
2658 .append(
"<a href=\"")
2659 .append(nodeClassDocumentationLink)
2661 .append(
"<font size=+1>")
2662 .append(nodeClassTitle)
2666 .append(nodeClassName.c_str())
2669 nodeSetClassesText.append(nodeClassProNodeIndicator);
2671 if (!nodeClassDescription.isEmpty())
2672 nodeSetClassesText.append(
": ").append(nodeClassDescription);
2674 nodeSetClassesText.append(
"</li>\n");
2677 if (nodeSetClassesText.size() > 0)
2679 nodeSetClassesText =
"<BR><HR><h3>" + tr(
"Node(s)",
"", nodeSetClassNames.size()) +
":</h3>\n<ul>\n" + nodeSetClassesText +
"</ul>";
2682 ofstream savedNodeSetFile(saveFileName.c_str(), ios::trunc);
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();
2691 savedNodeSetFile.close();
2707 string nodeSetName = nodeSet->
getName();
2709 foreach (
string nodeClassName, nodeSetClassNames)
2723 set<string> sortedUniqueKeywords;
2724 foreach (
string keyword, manualKeywords)
2725 sortedUniqueKeywords.insert(keyword);
2727 foreach (
string keyword, automaticKeywords)
2728 sortedUniqueKeywords.insert(keyword);
2732 QString nodeClassHtmlHeader =
"<html><head><meta charset=\"utf-8\"><title>" + tr(
"Vuo Node Documentation") +
": "
2733 + QString::fromStdString(nodeClassTitle)
2734 +
" (" + QString::fromStdString(nodeClassName) +
")"
2738 QString nodeClassDocumentationTitle = QString(
"<h2>")
2739 .append(nodeClassTitle.c_str())
2740 .append(
" (").append(nodeClassName.c_str()).append(
")")
2744 QString nodeClassKeywordsIntro =
"<p><b>" + tr(
"Keyword(s)",
"", sortedUniqueKeywords.size()) +
"</b>: ";
2745 QString nodeClassKeywordsText = nodeClassKeywordsIntro;
2746 foreach (
string keyword, sortedUniqueKeywords)
2748 if (nodeClassKeywordsText != nodeClassKeywordsIntro)
2749 nodeClassKeywordsText.append(
", ");
2751 nodeClassKeywordsText.append(
"<i>").append(keyword.c_str()).append(
"</i>");
2753 nodeClassKeywordsText.append(
"</p>");
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);
2767 QPainter painter(&pixmap);
2768 painter.setRenderHint(QPainter::Antialiasing,
true);
2769 painter.setRenderHint(QPainter::HighQualityAntialiasing,
true);
2770 painter.setRenderHint(QPainter::TextAntialiasing,
true);
2772 composition->render(&painter);
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");
2783 QString nodeClassRenderedPreview = QString(
"<img src=\"%1\" width=\"%2\" height=\"%3\" />")
2784 .arg(nodeClassRenderedPreviewFileName.c_str())
2786 .arg(size.height());
2790 QString nodeClassExampleCompositionText =
"";
2792 foreach (
string compositionFileName, nodeClassExampleCompositionFileNames)
2808 nodeClassExampleCompositionText.append(
"<li>")
2809 .append(
"<a href=\"")
2812 .append(nodeSetName.c_str())
2814 .append(compositionFileName.c_str())
2819 .append(
"\"><font size=+1>")
2820 .append(name.c_str())
2821 .append(
"</font></a>");
2823 if (!compositionDescription.isEmpty())
2824 nodeClassExampleCompositionText.append(
": ").append(compositionDescription);
2826 nodeClassExampleCompositionText.append(
"</li>\n");
2829 if (nodeClassExampleCompositionFileNames.size() > 0)
2831 nodeClassExampleCompositionText =
"<HR><h3>" + tr(
"Example composition(s)",
"", nodeClassExampleCompositionFileNames.size()) +
":</h3>\n<ul>\n" + nodeClassExampleCompositionText +
"</ul>";
2834 QString nodeClassProNodeIndicator;
2836 if (nodeClass->
getBase()->isPro())
2837 nodeClassProNodeIndicator = QString(VuoNodePopover::installedProNodeText).append(
"<br><br>");
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>";
2844 string saveNodeClassHtml = saveDir +
"/" + nodeClassName +
".html";
2845 ofstream savedNodeClassFile(saveNodeClassHtml.c_str(), ios::trunc);
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();
2856 savedNodeClassFile.close();
2867 QString filteredText(markdownText.c_str());
2868 QRegularExpression vuoNodeLink(
"\\[([^\\]]+)\\](\\(vuo-node://([^)]+)\\))");
2869 filteredText.replace(vuoNodeLink,
"`\\1`");
2871 QRegularExpression vuoNodeSetLink(
"\\[([^\\]]+)\\](\\(vuo-nodeset://([^)]+)\\))");
2872 filteredText.replace(vuoNodeSetLink,
"`\\1`");
2874 return filteredText.toUtf8().constData();
2886 QUrl urlForParsing(url);
2888 QString hostPathQuery = url;
2889 hostPathQuery.remove(0, (urlForParsing.scheme() +
"://").size());
2890 QString nodeSetName = hostPathQuery.left(urlForParsing.host().size());
2892 QString compositionFileName = urlForParsing.path();
2893 compositionFileName.remove(0, 1);
2895 QString nodeClassToHighlight;
2896 QUrlQuery query(urlForParsing.query());
2913 QClipboard *clipboard = QApplication::clipboard();
2914 const QMimeData *mimeData = clipboard->mimeData();
2916 if (!mimeData->hasFormat(
"text/plain") || mimeData->text().isNull())
2919 return mimeData->text();
2928 return this->nodeDocumentationPanelHeight;
2937 return this->nodeLibraryWidth;
2949 if (resourceDirectoryForNodeSet.find(nodeSetName) != resourceDirectoryForNodeSet.end())
2950 return resourceDirectoryForNodeSet[nodeSetName];
2961 resourceDirectoryForNodeSet[nodeSetName] = directory;
2969 return subcompositionRouter;
2975void VuoEditor::initializeBuiltInDrivers()
2992 dispatch_sync(builtInDriversQueue, ^{});
2994 map<VuoProtocol *, VuoCompilerDriver *>::iterator driver = builtInDriverForProtocol.find(protocol);
2995 if (driver != builtInDriverForProtocol.end())
2996 return driver->second;
3009 QPixmap emptyPixmap(32, 32);
3010 emptyPixmap.fill(Qt::transparent);
3011 QIcon indentIcon(emptyPixmap);
3014 QAction *windowHeading =
new QAction(
this);
3015 windowHeading->setText(tr(
"Window"));
3016 windowHeading->setEnabled(
false);
3017 m->addAction(windowHeading);
3021 QAction *templateAction =
new QAction(
this);
3022 templateAction->setText(tr(
"Image"));
3023 templateAction->setData(
"imageTemplate");
3024 templateAction->setIcon(indentIcon);
3026 m->addAction(templateAction);
3031 QAction *templateAction =
new QAction(
this);
3032 templateAction->setText(tr(
"Layers"));
3033 templateAction->setData(
"layersTemplate");
3034 templateAction->setIcon(indentIcon);
3036 m->addAction(templateAction);
3041 QAction *templateAction =
new QAction(
this);
3042 templateAction->setText(tr(
"Scene"));
3043 templateAction->setData(
"sceneTemplate");
3044 templateAction->setIcon(indentIcon);
3046 m->addAction(templateAction);
3050 QAction *protocolHeading =
new QAction(
this);
3051 protocolHeading->setText(tr(
"Protocol"));
3052 protocolHeading->setEnabled(
false);
3054 m->addAction(protocolHeading);
3059 QAction *protocolAction =
new QAction(
this);
3060 protocolAction->setText(tr(protocol->
getName().c_str()));
3061 protocolAction->setData(QVariant::fromValue(protocol));
3062 protocolAction->setIcon(indentIcon);
3064 m->addAction(protocolAction);
3071 QAction *heading =
new QAction(
this);
3072 heading->setText(tr(
"Export"));
3073 heading->setEnabled(
false);
3074 m->addAction(heading);
3077 QAction *templateAction =
new QAction(
this);
3078 templateAction->setText(tr(
"Movie"));
3079 templateAction->setData(
"movie");
3080 templateAction->setIcon(indentIcon);
3082 m->addAction(templateAction);
3086 QAction *templateAction =
new QAction(
this);
3087 templateAction->setText(tr(
"Screen Saver"));
3088 templateAction->setData(
"screensaver");
3089 templateAction->setIcon(indentIcon);
3091 m->addAction(templateAction);
3095 QAction *heading =
new QAction(
this);
3096 heading->setText(tr(
"FFGL"));
3097 heading->setIcon(indentIcon);
3098 heading->setEnabled(
false);
3099 m->addAction(heading);
3102 QAction *templateAction =
new QAction(
this);
3103 templateAction->setText(
" " + tr(
"Source"));
3104 templateAction->setData(
"FFGLSource");
3105 templateAction->setIcon(indentIcon);
3107 m->addAction(templateAction);
3111 QAction *templateAction =
new QAction(
this);
3112 templateAction->setText(
" " + tr(
"Effect"));
3113 templateAction->setData(
"FFGLEffect");
3114 templateAction->setIcon(indentIcon);
3116 m->addAction(templateAction);
3120 QAction *templateAction =
new QAction(
this);
3121 templateAction->setText(
" " + tr(
"Blend Mode"));
3122 templateAction->setData(
"FFGLBlendMode");
3123 templateAction->setIcon(indentIcon);
3125 m->addAction(templateAction);
3130 QAction *heading =
new QAction(
this);
3131 heading->setText(tr(
"FxPlug"));
3132 heading->setIcon(indentIcon);
3133 heading->setEnabled(
false);
3134 m->addAction(heading);
3137 QAction *templateAction =
new QAction(
this);
3138 templateAction->setText(
" " + tr(
"Generator"));
3139 templateAction->setData(
"FxPlugGenerator");
3140 templateAction->setIcon(indentIcon);
3142 m->addAction(templateAction);
3146 QAction *templateAction =
new QAction(
this);
3147 templateAction->setText(
" " + tr(
"Effect"));
3148 templateAction->setData(
"FxPlugEffect");
3149 templateAction->setIcon(indentIcon);
3151 m->addAction(templateAction);
3155 QAction *templateAction =
new QAction(
this);
3156 templateAction->setText(
" " + tr(
"Transition"));
3157 templateAction->setData(
"FxPlugTransition");
3158 templateAction->setIcon(indentIcon);
3160 m->addAction(templateAction);
3172 QAction *action =
new QAction(m);
3173 action->setText(tr(
"Image Filter"));
3174 action->setData(
"GLSLImageFilter");
3175 connect(action, &QAction::triggered, [=] {
3176 closeUnmodifiedUntitledComposition();
3178 if (!closeWelcomeWindow())
3183 m->addAction(action);
3187 QAction *action =
new QAction(m);
3188 action->setText(tr(
"Image Generator"));
3189 action->setData(
"GLSLImageGenerator");
3190 connect(action, &QAction::triggered, [=] {
3191 closeUnmodifiedUntitledComposition();
3193 if (!closeWelcomeWindow())
3198 m->addAction(action);
3202 QAction *action =
new QAction(m);
3203 action->setText(tr(
"Image Transition"));
3204 action->setData(
"GLSLImageTransition");
3205 connect(action, &QAction::triggered, [=] {
3206 closeUnmodifiedUntitledComposition();
3208 if (!closeWelcomeWindow())
3213 m->addAction(action);
3223 return documentationQueue;
3232 return darkInterfaceAction->isChecked();
3244 return this->canvasOpacity;
3250void VuoEditor::showGridLinesToggled(
bool show)
3256 type = VuoRendererComposition::LineGrid;
3259 type = VuoRendererComposition::NoGrid;
3261 settings->setValue(gridTypeSettingsKey, type);
3270void VuoEditor::showGridPointsToggled(
bool show)
3276 type = VuoRendererComposition::PointGrid;
3279 type = VuoRendererComposition::NoGrid;
3281 settings->setValue(gridTypeSettingsKey, type);
3290void VuoEditor::updateSnapToGrid(
bool snap)
3292 settings->setValue(snapToGridSettingsKey, snap);
3300void VuoEditor::updateColor(
bool isDark)
3302 settings->setValue(darkInterfaceSettingsKey, isDark);
3313void VuoEditor::updateCanvasOpacity(QAction *setOpacityAction)
3315 updateCanvasOpacityTo(setOpacityAction->data().toInt());
3323void VuoEditor::updateCanvasOpacityTo(
int opacity)
3325 settings->setValue(canvasOpacitySettingsKey, opacity);
3326 canvasOpacity = opacity;
3338 VuoEditor::documentationGenerationDirectory = dir;
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;
3362 map<QString, QString> modelExampleCompositionsAndNodeSets;
3366 modelExampleCompositionsAndNodeSets[
"ChangeRipplePeriodically.vuo"] =
"vuo.time";
3367 modelExampleCompositionsAndNodeSets[
"PixellateImageRadially.vuo"] =
"vuo.image";
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";
3387 modelExampleCompositionsAndNodeSets[
"BlendImages.vuo"] =
"vuo.image";
3388 modelExampleCompositionsAndNodeSets[
"CrossfadeAndZoom.vuo"] =
"vuo.image";
3389 modelExampleCompositionsAndNodeSets[
"CrossfadeWithBlobMask.vuo"] =
"vuo.image";
3390 modelExampleCompositionsAndNodeSets[
"CrossfadeWithMovingTiles.vuo"] =
"vuo.image";
3393 return modelExampleCompositionsAndNodeSets;
3403 .append(nodeSetName)
3405 .append(compositionName);
3406 return compositionURL;
3415 return getStoredUserName_Pro();
3417 const char *user = getenv(
"USER");
3430 return getStoredUserProfileURL_Pro();
3442 settings->setValue(subcompositionPrefixSettingsKey, prefix);
3443 subcompositionPrefix = prefix;