Vuo  2.1.0
VuoCodeWindow.cc
Go to the documentation of this file.
1 
10 #include "VuoCodeWindow.hh"
11 
12 #include "VuoCodeEditor.hh"
13 #include "VuoCodeEditorStages.hh"
14 #include "VuoCodeGutter.hh"
15 #include "VuoCodeIssueList.hh"
16 #include "VuoCompiler.hh"
17 #include "VuoCompilerCable.hh"
21 #include "VuoCompilerIssue.hh"
24 #include "VuoCompilerType.hh"
25 #include "VuoComposition.hh"
28 #include "VuoEditor.hh"
29 #include "VuoEditorComposition.hh"
30 #include "VuoEditorUtilities.hh"
32 #include "VuoErrorDialog.hh"
33 #include "VuoException.hh"
34 #include "VuoInputEditorManager.hh"
35 #include "VuoInputEditorSession.hh"
36 #include "VuoMetadataEditor.hh"
37 #include "VuoModuleManager.hh"
38 #include "VuoNodeClass.hh"
39 #include "VuoProtocol.hh"
41 #include "VuoShaderIssues.hh"
42 #include "VuoStringUtilities.hh"
43 #include "VuoRecentFileMenu.hh"
45 
50 VuoCodeWindow::VuoCodeWindow(const string &sourcePath)
51 {
52  stages = nullptr;
53  issueList = nullptr;
54  issues = new VuoShaderIssues(); // VuoCodeGutter::paintEvent wants this to be non-null.
55  saveAction = nullptr;
56  toggleInputPortSidebarAction = nullptr;
57  toggleDocumentationSidebarAction = nullptr;
58  zoom11Action = nullptr;
59  zoomInAction = nullptr;
60  zoomOutAction = nullptr;
61  runAction = nullptr;
62  stopAction = nullptr;
63  restartAction = nullptr;
64  reloadAction = nullptr;
65  fileMenu = nullptr;
66  windowMenu = nullptr;
67  recentFileMenu = nullptr;
68  shaderFile = nullptr;
69  includeInRecentFileMenu = true;
70  publishedInputsModified = false;
71  metadataModified = false;
72  closing = false;
73 
74  compiler = new VuoCompiler();
75 
76  VuoCompilerComposition *compilerComposition = new VuoCompilerComposition(new VuoComposition(), nullptr);
77  wrapperComposition = new VuoEditorComposition(this, compilerComposition->getBase());
78  wrapperComposition->setCompiler(compiler);
79 
80  VuoModuleManager *moduleManager = new VuoModuleManager(compiler);
81  moduleManager->setComposition(wrapperComposition);
82  moduleManager->setCodeWindow(this);
83  wrapperComposition->setModuleManager(moduleManager);
84 
85  raiseDocumentAction = new QAction(this);
86  raiseDocumentAction->setCheckable(true);
87  connect(raiseDocumentAction, &QAction::triggered, this, &VuoCodeWindow::setAsActiveWindow);
88 
89  toolbar = nullptr; // VuoEditorWindowToolbar constructor indirectly calls resizeEvent, which uses toolbar if non-null
90  toolbar = new VuoEditorWindowToolbar(this, true);
91  setUnifiedTitleAndToolBarOnMac(true);
92 
93  inputPortSidebar = new VuoPublishedPortSidebar(this, wrapperComposition, true, false);
94  addDockWidget(Qt::LeftDockWidgetArea, inputPortSidebar);
96 
97  setSourcePath(sourcePath);
98 
99  if (!isNewUnsavedDocument())
100  VUserLog("%s: Open", getWindowTitleWithoutPlaceholder().toUtf8().data());
101 
102  stages = new VuoCodeEditorStages(this, shaderFile);
103  connect(stages, &VuoCodeEditorStages::modificationMayHaveChanged, this, &VuoCodeWindow::updateModifiedIndicator);
104  setCentralWidget(stages);
105 
106  bringNodeClassInSyncWithSourceCode();
107 
108  documentationSidebar = new VuoDocumentationSidebar(this);
109  documentationSidebar->setVisible(static_cast<VuoEditor *>(qApp)->getGlobalShaderDocumentationVisibility());
110  connect(documentationSidebar, &VuoDocumentationSidebar::visibilityChanged, this, &VuoCodeWindow::updateDocumentationSidebarMenuItem);
111  connect(documentationSidebar, &VuoDocumentationSidebar::visibilityChanged, static_cast<VuoEditor *>(qApp), &VuoEditor::updateGlobalShaderDocumentationVisibility);
112  addDockWidget(Qt::RightDockWidgetArea, documentationSidebar);
113 
114  resize(700,700);
115  setMinimumWidth(650); // Wide enough for published input ports + gutter + 3 tabs + shader type label.
116  stages->setMinimumWidth(200); // Wide enough for 1 tab.
117  resizeDocks({documentationSidebar}, {250}, Qt::Horizontal);
118 
119  this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
120 
121  inputEditorManager = new VuoInputEditorManager();
122  wrapperComposition->setInputEditorManager(inputEditorManager);
123  connect(inputPortSidebar, &VuoPublishedPortSidebar::visibilityChanged, this, &VuoCodeWindow::updateInputPortSidebarMenuItem);
124  connect(inputPortSidebar, &VuoPublishedPortSidebar::inputEditorRequested, this, &VuoCodeWindow::showPublishedInputEditor);
125  connect(inputPortSidebar, SIGNAL(publishedPortDetailsChangeRequested(VuoRendererPublishedPort *, json_object *)), this, SLOT(changePublishedPortDetails(VuoRendererPublishedPort *, json_object *)));
126  connect(inputPortSidebar, &VuoPublishedPortSidebar::publishedPortNameEditorRequested, this, &VuoCodeWindow::showPublishedPortNameEditor);
127  connect(inputPortSidebar, &VuoPublishedPortSidebar::newPublishedPortRequested, this, &VuoCodeWindow::addPublishedPort);
128  connect(inputPortSidebar, &VuoPublishedPortSidebar::externalPortUnpublicationRequested, this, &VuoCodeWindow::deletePublishedPort);
129 
130  metadataEditor = new VuoMetadataEditor(wrapperComposition, this, Qt::Sheet, true);
131  metadataEditor->setWindowModality(Qt::WindowModal);
132  connect(metadataEditor, &VuoMetadataEditor::finished, this, &VuoCodeWindow::changeMetadata);
133 
134  issueList = new VuoCodeIssueList(this);
135  addDockWidget(Qt::BottomDockWidgetArea, issueList);
136 
137  populateMenus();
138 
139  zoom11();
140 
141  connect(wrapperComposition, &VuoEditorComposition::buildStarted, this, &VuoCodeWindow::showBuildActivityIndicator);
142  connect(wrapperComposition, &VuoEditorComposition::buildFinished, this, &VuoCodeWindow::hideBuildActivityIndicator);
143  connect(wrapperComposition, &VuoEditorComposition::stopFinished, this, &VuoCodeWindow::hideStopActivityIndicator);
145 
146  connect(static_cast<VuoEditor *>(qApp), &VuoEditor::darkInterfaceToggled, this, &VuoCodeWindow::updateColor);
147  updateColor();
148  updateCanvasOpacity();
149  updateModifiedIndicator();
150 
151  static_cast<VuoEditor *>(qApp)->registerOpenDocument(this);
152 }
153 
154 VuoCodeWindow::~VuoCodeWindow(void)
155 {
156  relinquishSourcePath();
157 
158  delete toolbar;
159  delete issues;
160  delete wrapperComposition; // deletes compiler
161 }
162 
166 void VuoCodeWindow::populateMenus(void)
167 {
168  {
169  // "File" menu
170  QMenu *m = new QMenu(menuBar());
171  m->setSeparatorsCollapsible(false);
172  m->setTitle(tr("&File"));
173  fileMenu = m;
174 
175  m->addAction(tr("&New Composition"), static_cast<VuoEditor *>(qApp), &VuoEditor::newComposition, QKeySequence("Ctrl+N"));
176 
177  {
178  // "New Composition from Template" menu
179  QMenu *mm = new QMenu(tr("New Composition from Template"));
180  mm->setSeparatorsCollapsible(false);
181 
182  static_cast<VuoEditor *>(qApp)->populateNewCompositionWithTemplateMenu(mm);
183 
184  m->addMenu(mm);
185  }
186 
187  {
188  // "New Shader" menu
189  QMenu *mm = new QMenu(tr("New Shader"));
190  mm->setSeparatorsCollapsible(false);
191 
192  static_cast<VuoEditor *>(qApp)->populateNewShaderMenu(mm);
193 
194  m->addMenu(mm);
195  }
196 
197  m->addSeparator();
198  m->addAction(tr("&Open…"), static_cast<VuoEditor *>(qApp), &VuoEditor::openFile, QKeySequence("Ctrl+O"));
199 
200  {
201  // "Open Recent" menu
202  recentFileMenu = new VuoRecentFileMenu();
203 
204  connect(recentFileMenu, &VuoRecentFileMenu::recentFileSelected, static_cast<VuoEditor *>(qApp), &VuoEditor::openUrl);
205 
206  m->addMenu(recentFileMenu);
207  }
208 
209  m->addSeparator();
210  saveAction = m->addAction(tr("&Save"), this, &VuoCodeWindow::save, QKeySequence("Ctrl+S"));
211  m->addAction(tr("Save As…"), this, &VuoCodeWindow::saveAs, QKeySequence("Ctrl+Shift+S"));
212 
213  m->addSeparator();
214  m->addAction(tr("Close"), this, &VuoCodeWindow::close, QKeySequence("Ctrl+W"));
215 
216  m->addAction(tr("Quit"), static_cast<VuoEditor *>(qApp), &VuoEditor::quitCleanly, QKeySequence("Ctrl+Q"));
217 
218  m->addAction(tr("About Vuo…"), static_cast<VuoEditor *>(qApp), &VuoEditor::about);
219 
220  menuBar()->addAction(m->menuAction());
221  }
222 
223  {
224  // "Edit" menu
225  QMenu *m = new QMenu(menuBar());
226  m->setSeparatorsCollapsible(false);
227  m->setTitle(tr("Edit"));
228 
229  m->addSeparator();
230  m->addAction(tr("Undo"), stages->currentEditor(), &VuoCodeEditor::undo, QKeySequence("Ctrl+Z"));
231  m->addAction(tr("Redo"), stages->currentEditor(), &VuoCodeEditor::redo, QKeySequence("Ctrl+Shift+Z"));
232 
233  m->addSeparator();
234  m->addAction(tr("Cut"), stages->currentEditor(), &VuoCodeEditor::cut, QKeySequence("Ctrl+X"));
235  m->addAction(tr("Copy"), this, &VuoCodeWindow::copy, QKeySequence("Ctrl+C"));
236  m->addAction(tr("Paste"), stages->currentEditor(), &VuoCodeEditor::paste, QKeySequence("Ctrl+V"));
237  m->addAction(tr("Delete"), [this](){ stages->currentEditor()->textCursor().removeSelectedText(); });
238 
239  m->addSeparator();
240  m->addAction(tr("Select All"), stages->currentEditor(), &VuoCodeEditor::selectAll, QKeySequence("Ctrl+A"));
241 
242  m->addSeparator();
243  m->addAction(tr("Composition Information…"), [this](){ metadataEditor->show(); }, QKeySequence("Ctrl+I"));
244 
245  menuBar()->addAction(m->menuAction());
246  }
247 
248  {
249  // "View" menu
250  QMenu *m = new QMenu(menuBar());
251  m->setSeparatorsCollapsible(false);
252  m->setTitle(tr("&View"));
253 
254  toggleInputPortSidebarAction = m->addAction(tr("Hide Published Ports"), this, &VuoCodeWindow::toggleInputPortSidebarVisibility, QKeySequence("Ctrl+4"));
255  toggleDocumentationSidebarAction = m->addAction("", this, &VuoCodeWindow::toggleDocumentationSidebarVisibility, QKeySequence("Ctrl+5"));
256  updateDocumentationSidebarMenuItem();
257 
258  m->addSeparator();
259  zoom11Action = m->addAction(tr("Actual Size"), this, &VuoCodeWindow::zoom11, QKeySequence("Ctrl+0"));
260  zoomInAction = m->addAction(tr("Zoom In"), this, &VuoCodeWindow::zoomIn, QKeySequence("Ctrl+="));
261  zoomOutAction = m->addAction(tr("Zoom Out"), this, &VuoCodeWindow::zoomOut, QKeySequence("Ctrl+-"));
262 
263  m->addSeparator();
264 
265  {
266  // "Canvas Transparency" menu
267  QMenu *mm = new QMenu(menuBar());
268  mm->setSeparatorsCollapsible(false);
269  mm->setTitle(tr("&Canvas Transparency"));
270 
271  static_cast<VuoEditor *>(qApp)->populateCanvasTransparencyMenu(mm);
272  connect(static_cast<VuoEditor *>(qApp), &VuoEditor::canvasOpacityChanged, this, &VuoCodeWindow::updateCanvasOpacity);
273 
274  m->addMenu(mm);
275  }
276 
277  menuBar()->addAction(m->menuAction());
278  }
279 
280  {
281  // "Run" menu
282  QMenu *m = new QMenu(menuBar());
283  m->setSeparatorsCollapsible(false);
284  m->setTitle(tr("Run"));
285 
286  runAction = m->addAction(tr("Run"), this, &VuoCodeWindow::on_runComposition_triggered, QKeySequence("Ctrl+R"));
287  stopAction = m->addAction(tr("Stop"), this, &VuoCodeWindow::on_stopComposition_triggered, QKeySequence("Ctrl+."));
288  restartAction = m->addAction(tr("Restart"), this, &VuoCodeWindow::on_restartComposition_triggered, QKeySequence("Ctrl+Shift+R"));
289 
290  m->addSeparator();
291  reloadAction = m->addAction(tr("Reload"), [=]{
292  VUserLog("%s: Reload", getWindowTitleWithoutPlaceholder().toUtf8().data());
293  bringNodeClassInSyncWithSourceCode();
294  });
295  reloadAction->setShortcuts({ QKeySequence("Ctrl+Return"), QKeySequence("Alt+Return") });
296 
297  stopAction->setEnabled(false);
298  restartAction->setEnabled(false);
300 
301  menuBar()->addAction(m->menuAction());
302  }
303 
304  {
305  // "Tools" menu
306  QMenu *m = new QMenu(menuBar());
307  m->setSeparatorsCollapsible(false);
308  m->setTitle(tr("Tools"));
309 
310  m->addAction(tr("Open User Library in Finder"), &VuoEditorUtilities::openUserModulesFolder);
311  m->addAction(tr("Open System Library in Finder"), &VuoEditorUtilities::openSystemModulesFolder);
312 
313  menuBar()->addAction(m->menuAction());
314  }
315 
316  {
317  // "Window" menu
318  QMenu *m = new QMenu(menuBar());
319  m->setSeparatorsCollapsible(false);
320  m->setTitle(tr("&Window"));
321 
322  static_cast<VuoEditor *>(qApp)->populateWindowMenu(m, this);
323  windowMenu = m;
324  connect(windowMenu, &QMenu::aboutToShow, this, &VuoCodeWindow::updateWindowMenu);
325 
326  menuBar()->addAction(m->menuAction());
327  }
328 
329  {
330  // "Help" menu
331  QMenu *m = new QMenu(menuBar());
332  m->setSeparatorsCollapsible(false);
333  m->setTitle(tr("&Help"));
334 
335  static_cast<VuoEditor *>(qApp)->populateHelpMenu(m);
336 
337  menuBar()->addAction(m->menuAction());
338  }
339 }
340 
345 {
346  QString selectedTemplate = static_cast<QString>(sender->data().value<QString>());
347  string templatePath = VuoFileUtilities::getVuoFrameworkPath() + "/Resources/" + selectedTemplate.toUtf8().constData() + ".fs";
348 
349  string tmpPath = VuoFileUtilities::makeTmpFile("VuoCodeEditor-untitled", "fs");
350  VuoFileUtilities::copyFile(templatePath, tmpPath);
351 
352  VuoCodeWindow *window = new VuoCodeWindow(tmpPath);
353  VUserLog("%s: New Shader with %s template", window->getWindowTitleWithoutPlaceholder().toUtf8().data(), selectedTemplate.toUtf8().data());
354  window->show();
355 }
356 
361 void VuoCodeWindow::save()
362 {
363  if (isNewUnsavedDocument())
364  saveAs();
365  else
366  {
367  VUserLog("%s: Save", getWindowTitleWithoutPlaceholder().toUtf8().data());
368  saveToPath(windowFilePath());
369  }
370 }
371 
375 void VuoCodeWindow::saveAs()
376 {
377  QFileDialog d(this, Qt::Sheet);
378  d.setAcceptMode(QFileDialog::AcceptSave);
379 
380  if (isNewUnsavedDocument())
381  d.selectFile(static_cast<VuoEditor *>(qApp)->getSubcompositionPrefix() + ".shader.fs");
382  else
383  d.setDirectory(windowFilePath());
384 
385  if (d.exec() == QDialog::Accepted)
386  {
387  string dir, file, ext;
388  VuoFileUtilities::splitPath(d.selectedFiles()[0].toStdString(), dir, file, ext);
389  VUserLog("%s: Save as %s.%s", getWindowTitleWithoutPlaceholder().toUtf8().data(), file.c_str(), ext.c_str());
390  saveToPath(d.selectedFiles()[0]);
391  }
392 }
393 
398 void VuoCodeWindow::saveToPath(QString savePath)
399 {
400  bool saveAborted = false;
401  QString failureDetails = "";
402  QString expectedFileExtension = ".fs";
403  if (! savePath.endsWith(expectedFileExtension))
404  {
405  savePath.append(expectedFileExtension);
406  if (VuoFileUtilities::fileExists(savePath.toStdString()))
407  {
408  saveAborted = true;
409  failureDetails = "A file or folder with the same name already exists.";
410  }
411  }
412 
413  bool saveSucceeded = false;
414  if (! saveAborted)
415  {
416  try
417  {
418  bringStoredShaderInSyncWithSourceCode();
419  shaderFile->save(savePath.toStdString());
420  saveSucceeded = true;
421  }
422  catch (VuoException &e)
423  {
424  failureDetails = e.what();
425  }
426  }
427 
428  if (saveSucceeded)
429  {
430  if (! VuoFileUtilities::arePathsEqual(windowFilePath().toStdString(), savePath.toStdString()))
431  setSourcePath(savePath.toStdString());
432 
434  publishedInputsModified = false;
435  metadataModified = false;
436  updateModifiedIndicator();
437 
438  if (includeInRecentFileMenu)
439  static_cast<VuoEditor *>(qApp)->addFileToAllOpenRecentFileMenus(savePath);
440  }
441  else
442  {
443  QMessageBox fileSaveFailureDialog(this);
444  fileSaveFailureDialog.setWindowFlags(Qt::Sheet);
445  fileSaveFailureDialog.setWindowModality(Qt::WindowModal);
446  fileSaveFailureDialog.setText(tr("The shader could not be saved at “%1”.").arg(savePath));
447  fileSaveFailureDialog.setStyleSheet("#qt_msgbox_informativelabel { font-weight: normal; font-size: 11pt; }");
448  fileSaveFailureDialog.setInformativeText(failureDetails);
449  fileSaveFailureDialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
450  fileSaveFailureDialog.setButtonText(QMessageBox::Save, tr("Save As…"));
451  fileSaveFailureDialog.setIcon(QMessageBox::Warning);
452 
453  switch(fileSaveFailureDialog.exec()) {
454  case QMessageBox::Save:
455  saveAs();
456  break;
457  case QMessageBox::Cancel:
458  break;
459  default:
460  break;
461  }
462  }
463 }
464 
468 bool VuoCodeWindow::isNewUnsavedDocument()
469 {
470  string dir, file, ext;
471  VuoFileUtilities::splitPath(windowFilePath().toStdString(), dir, file, ext);
472 
473  return VuoFileUtilities::arePathsEqual(dir, VuoFileUtilities::getTmpDir()) && VuoStringUtilities::beginsWith(file, "VuoCodeEditor-untitled");
474 }
475 
481 {
482  this->includeInRecentFileMenu = include;
483 }
484 
488 void VuoCodeWindow::closeEvent(QCloseEvent *event)
489 {
490  if (closing)
491  {
492  event->accept();
493  return;
494  }
495 
496  auto closeAndContinueQuit = [this](){
497  if (isNewUnsavedDocument())
498  VuoFileUtilities::deleteFile(windowFilePath().toStdString());
499  else
500  static_cast<VuoEditor *>(qApp)->addFileToRecentlyClosedList(windowFilePath());
501 
502  if (wrapperComposition->isRunning())
503  {
504  connect(wrapperComposition, &VuoEditorComposition::stopFinished, this, &VuoCodeWindow::deleteLater);
506  }
507  else
508  deleteLater();
509 
510  // Don't update the documentation sidebar visibility preference when the sidebar is hidden as a side effect of closing the window.
511  disconnect(documentationSidebar, &VuoDocumentationSidebar::visibilityChanged, static_cast<VuoEditor *>(qApp), &VuoEditor::updateGlobalShaderDocumentationVisibility);
512 
513  closing = true;
514  static_cast<VuoEditor *>(qApp)->continueQuit(this);
515  };
516 
517  if (isWindowModified() || stages->modified())
518  {
519  auto mb = new QMessageBox(this);
520  mb->setWindowFlags(Qt::Sheet);
521  mb->setWindowModality(Qt::WindowModal);
522 
523  mb->setText(tr("Do you want to save the changes made to the document “%1”?").arg(windowTitle()));
524  mb->setStyleSheet("#qt_msgbox_informativelabel { font-weight: normal; font-size: 11pt; }");
525  mb->setInformativeText(tr("Your changes will be lost if you don’t save them."));
526  mb->setIconPixmap(VuoEditorUtilities::vuoLogoForDialogs());
527 
528  mb->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
529  mb->setDefaultButton(QMessageBox::Save);
530 
531  static_cast<QPushButton *>(mb->button(QMessageBox::Discard))->setAutoDefault(false);
532  mb->button(QMessageBox::Discard)->setFocus();
533 
534  connect(mb, &QMessageBox::buttonClicked, this, [=](QAbstractButton *button){
535  auto role = mb->buttonRole(button);
536  if (role == QMessageBox::AcceptRole)
537  {
538  save();
539 
540  if (stages->modified())
541  static_cast<VuoEditor *>(qApp)->cancelQuit();
542  else
543  closeAndContinueQuit();
544  }
545  else if (role == QMessageBox::DestructiveRole)
546  closeAndContinueQuit();
547  else // if (role == QMessageBox::RejectRole)
548  static_cast<VuoEditor *>(qApp)->cancelQuit();
549  });
550 
551  mb->open();
552 
553  event->ignore();
554  }
555  else
556  {
557  event->accept();
558  closeAndContinueQuit();
559  }
560 
561  VUserLog("%s: Close", getWindowTitleWithoutPlaceholder().toUtf8().data());
562 }
563 
568 {
569  VUserLog("%s: Run", getWindowTitleWithoutPlaceholder().toUtf8().data());
570 
571  toolbar->changeStateToBuildPending();
572  updateToolbar();
573 
574  bringNodeClassInSyncWithSourceCode();
575 
576  string snapshot = wrapperComposition->takeSnapshot();
577  wrapperComposition->run(snapshot);
578 }
579 
584 {
585  VUserLog("%s: Stop", getWindowTitleWithoutPlaceholder().toUtf8().data());
586 
587  showStopActivityIndicator();
588 
589  wrapperComposition->stop();
590 }
591 
596 {
599 }
600 
605 void VuoCodeWindow::relinquishSourcePath(void)
606 {
607  delete shaderFile;
608  shaderFile = nullptr;
609 
611 
612  string oldSourcePath = windowFilePath().toStdString();
613  if (! oldSourcePath.empty())
614  {
615  compiler->uninstallNodeClassAtCompositionLocalScope(oldSourcePath);
616 
617  for (VuoNode *oldNode : wrapperComposition->getBase()->getNodes())
618  {
619  if (oldNode->getNodeClass()->getClassName() == getNodeClassName())
620  {
621  VuoNode *replacementNode = wrapperComposition->createNodeWithMissingImplementation(oldNode->getNodeClass(), oldNode);
622  wrapperComposition->updateNodeImplementationInPlace(oldNode->getRenderer(), replacementNode);
623  }
624  }
625 
626  if (isNewUnsavedDocument())
627  VuoFileUtilities::deleteFile(oldSourcePath);
628  }
629 }
630 
635 void VuoCodeWindow::setSourcePath(const string &sourcePath)
636 {
637  relinquishSourcePath();
638 
639  string dir, file, ext;
640  VuoFileUtilities::splitPath(sourcePath, dir, file, ext);
641  try
642  {
643  shaderFile = new VuoShaderFile(VuoFileUtilities::File(dir, file + "." + ext));
644  }
645  catch (VuoException &e)
646  {
647  delete toolbar;
648  delete issues;
649  delete wrapperComposition; // deletes compiler
650  throw;
651  }
652 
653  setWindowFilePath(QString::fromStdString(sourcePath));
654 
655  string title;
656  if (isNewUnsavedDocument())
657  {
658  set<string> takenTitles;
659  for (QMainWindow *openWindow : VuoEditorUtilities::getOpenEditingWindows())
660  takenTitles.insert(openWindow->windowTitle().toStdString());
661  string preferredTitle = "Untitled " + shaderFile->typeName();
662  title = VuoStringUtilities::formUniqueIdentifier(takenTitles, preferredTitle, preferredTitle + " ");
663  }
664  else
665  {
666  title = file + "." + ext;
667  }
668  setWindowTitle(QString::fromStdString(title));
669 #if VUO_PRO
670  toolbar->updateTitle();
671 #endif
672 
673  raiseDocumentAction->setText(windowTitle());
674 
675  compiler->setCompositionPath(sourcePath);
676 
678  updateWrapperComposition();
679  });
680 
681  wrapperComposition->getModuleManager()->updateWithAlreadyLoadedModules();
682 
683  VuoCompilerNodeClass *nodeClass = compiler->getNodeClass(getNodeClassName());
684  if (! nodeClass)
685  {
686  compiler->installNodeClassAtCompositionLocalScope(sourcePath);
687  nodeClass = compiler->getNodeClass(getNodeClassName());
688  }
689 }
690 
695 void VuoCodeWindow::updateWrapperComposition(void)
696 {
697  string oldSnapshot = wrapperComposition->takeSnapshot();
698  wrapperComposition->clear();
699 
700  VuoNode *node = wrapperComposition->createNode(QString::fromStdString(getNodeClassName()))->getBase();
701  wrapperComposition->addNode(node);
702 
703  vector<VuoPort *> inputPorts = node->getInputPorts();
704  for (size_t i = VuoNodeClass::unreservedInputPortStartIndex; i < inputPorts.size(); ++i)
705  {
706  VuoPort *port = node->getInputPorts().at(i);
707  VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(port->getCompiler());
708 
709  string publishedName = port->getClass()->getName();
710 
711  // Rename published ports that happen to have the same name as protocol ports (but aren't actually protocol ports).
712  publishedName = VuoStringUtilities::formUniqueIdentifier([=](const string &name){
713  bool ok = !wrapperComposition->getBase()->getPublishedInputPortWithName(name);
714 
715  if (shaderFile->type() == VuoShaderFile::GLSLImageFilter)
716  ok &= name != "image";
717  else if (shaderFile->type() == VuoShaderFile::GLSLImageGenerator)
718  ok &= name != "width"
719  && name != "height";
720 
721  ok &= name != "time";
722 
723  return ok;
724  }, publishedName);
725 
726  // Rename the `vuo*` published ports to make the composition conform to the protocol.
727  if (shaderFile->type() == VuoShaderFile::GLSLImageFilter)
728  {
729  if (publishedName == "inputImage")
730  publishedName = "image";
731  }
732  else if (shaderFile->type() == VuoShaderFile::GLSLImageGenerator)
733  {
734  if (publishedName == "vuoWidth")
735  publishedName = "width";
736  else if (publishedName == "vuoHeight")
737  publishedName = "height";
738  }
739  if (publishedName == "vuoTime")
740  publishedName = "time";
741 
742  VuoType *dataType = compilerPort->getDataVuoType();
743 
744  VuoCompilerPublishedPort *publishedPort = VuoCompilerPublishedPort::newPort(publishedName, dataType);
745  wrapperComposition->addPublishedPort(static_cast<VuoPublishedPort *>(publishedPort->getBase()), true);
746  wrapperComposition->createRendererForPublishedPortInComposition(static_cast<VuoPublishedPort *>(publishedPort->getBase()), true);
747 
748  VuoCompilerCable *publishedCable = new VuoCompilerCable(nullptr, publishedPort, node->getCompiler(), compilerPort);
749  wrapperComposition->addCable(publishedCable->getBase());
750 
751  if (dataType)
752  {
753  string constantValue = static_cast<VuoCompilerInputEventPort *>(compilerPort)->getData()->getInitialValue();
754  publishedPort->setInitialValue(constantValue);
755  }
756 
757  // Disallow renaming or deleting the published port if it may have been auto-generated by VuoIsfModuleCompiler.
758  string portName = port->getClass()->getName();
759  if (VuoStringUtilities::beginsWith(portName, "inputImage") ||
760  VuoStringUtilities::beginsWith(portName, "vuoWidth") ||
761  VuoStringUtilities::beginsWith(portName, "vuoHeight") ||
762  VuoStringUtilities::beginsWith(portName, "vuoTime") ||
763  VuoStringUtilities::beginsWith(portName, "vuoColorDepth"))
764  static_cast<VuoRendererPublishedPort *>(publishedPort->getBase()->getRenderer())->setPermanent(true);
765  }
766  if (! node->getInputPortWithName("vuoTime"))
767  {
768  VuoCompilerPublishedPort *publishedPort = VuoCompilerPublishedPort::newPort("time", compiler->getType("VuoReal")->getBase());
769  wrapperComposition->addPublishedPort(static_cast<VuoPublishedPort *>(publishedPort->getBase()), true);
770  wrapperComposition->createRendererForPublishedPortInComposition(static_cast<VuoPublishedPort *>(publishedPort->getBase()), true);
771  }
772 
773  vector<VuoPort *> outputPorts = node->getOutputPorts();
774  for (size_t i = VuoNodeClass::unreservedOutputPortStartIndex; i < outputPorts.size(); ++i)
775  {
776  VuoPort *port = node->getOutputPorts().at(i);
777  VuoCompilerPort *compilerPort = static_cast<VuoCompilerPort *>(port->getCompiler());
778 
779  VuoType *dataType = compilerPort->getDataVuoType();
780 
781  string publishedName = port->getClass()->getName();
782  if (dataType->getModuleKey() == "VuoImage")
783  publishedName = "outputImage";
784 
785  VuoCompilerPublishedPort *publishedPort = VuoCompilerPublishedPort::newPort(publishedName, dataType);
786  wrapperComposition->addPublishedPort(static_cast<VuoPublishedPort *>(publishedPort->getBase()), false);
787  wrapperComposition->createRendererForPublishedPortInComposition(static_cast<VuoPublishedPort *>(publishedPort->getBase()), false);
788 
789  VuoCompilerCable *publishedCable = new VuoCompilerCable(node->getCompiler(), compilerPort, nullptr, publishedPort);
790  wrapperComposition->addCable(publishedCable->getBase());
791  }
792 
793  VuoProtocol *protocol = nullptr;
794  if (shaderFile->type() == VuoShaderFile::GLSLImageFilter)
795  protocol = VuoProtocol::getProtocol("VuoImageFilter");
796  else if (shaderFile->type() == VuoShaderFile::GLSLImageGenerator)
797  protocol = VuoProtocol::getProtocol("VuoImageGenerator");
798  else if (shaderFile->type() == VuoShaderFile::GLSLImageTransition)
799  protocol = VuoProtocol::getProtocol("VuoImageTransition");
800 
801  if (protocol)
802  wrapperComposition->addActiveProtocol(protocol, false);
803 
804  VuoCompositionMetadata *metadata = shaderFile->metadata();
805  wrapperComposition->getBase()->setMetadata(metadata, true);
806 
807  string newSnapshot = wrapperComposition->takeSnapshot();
808  if (oldSnapshot != newSnapshot)
809  wrapperComposition->updateRunningComposition(oldSnapshot, newSnapshot);
810 
811  inputPortSidebar->updatePortList();
812  inputPortSidebar->updateActiveProtocol();
813 }
814 
819 void VuoCodeWindow::bringNodeClassInSyncWithSourceCode(void)
820 {
821  bringStoredShaderInSyncWithSourceCode();
822  string sourceCode = shaderFile->fragmentFileContents();
823  compiler->overrideInstalledNodeClass(windowFilePath().toStdString(), sourceCode);
824 }
825 
829 void VuoCodeWindow::bringStoredShaderInSyncWithSourceCode(void)
830 {
831  shaderFile->setFragmentSource(stages->fragment->toPlainText().toStdString());
832 }
833 
837 void VuoCodeWindow::bringStoredShaderInSyncWithPublishedInputPorts(VuoCompilerPublishedPort *publishedInputAdded,
838  const pair<string, string> &publishedInputRenamed,
839  const string &publishedInputRemoved)
840 {
841  vector<VuoShaderFile::Port> shaderInputs = shaderFile->inputPorts();
842 
843  for (auto i = shaderInputs.begin(); i != shaderInputs.end(); )
844  {
845  if (i->key == publishedInputRemoved)
846  i = shaderInputs.erase(i);
847  else
848  ++i;
849  }
850 
851  for (VuoShaderFile::Port &shaderInput : shaderInputs)
852  {
853  if (shaderInput.key == publishedInputRenamed.first)
854  shaderInput.key = publishedInputRenamed.second;
855 
856  VuoPublishedPort *publishedInput = wrapperComposition->getBase()->getPublishedInputPortWithName(shaderInput.key);
857  if (publishedInput)
858  {
859  VuoCompilerPublishedPort *compilerPublishedInput = static_cast<VuoCompilerPublishedPort *>(publishedInput->getCompiler());
860  json_object *oldDetails = shaderInput.vuoPortDetails;
861  shaderInput.vuoPortDetails = compilerPublishedInput->getDetails(true);
862  json_object *scaleToSamplerRectValue = NULL;
863  if (json_object_object_get_ex(oldDetails, "scaleToSamplerRect", &scaleToSamplerRectValue))
864  json_object_object_add(shaderInput.vuoPortDetails, "scaleToSamplerRect", scaleToSamplerRectValue);
865  }
866  }
867 
868  if (publishedInputAdded)
869  {
870  VuoShaderFile::Port addedInput;
871  addedInput.key = publishedInputAdded->getBase()->getClass()->getName();
872  addedInput.vuoTypeName = (publishedInputAdded->getDataVuoType() ? publishedInputAdded->getDataVuoType()->getModuleKey() : "event");
873  addedInput.vuoPortDetails = publishedInputAdded->getDetails(true);
874  shaderInputs.push_back(addedInput);
875  }
876 
877  shaderFile->setInputPorts(shaderInputs);
878 }
879 
884 {
885  delete issues;
886 
888  for (VuoCompilerIssue compilerIssue : compilerIssues->getList())
889  issues->addIssue(VuoShaderFile::Fragment, compilerIssue.getLineNumber(), compilerIssue.getDetails(false));
890 
891  this->issues = issues;
892 
893  // Render the error icons in the gutter.
894  if (stages)
895  static_cast<VuoCodeEditor *>(stages->currentWidget())->gutter->update();
896 
897  if (issueList)
899 }
900 
905 {
906  return VuoCompiler::getModuleKeyForPath(windowFilePath().toStdString());
907 }
908 
912 void VuoCodeWindow::updateModifiedIndicator()
913 {
914  bool modified = stages->modified() || publishedInputsModified || metadataModified;
915  setWindowModified(modified);
916 
917  bool saveEnabled = modified || isNewUnsavedDocument();
918  saveAction->setEnabled(saveEnabled);
919 }
920 
921 void VuoCodeWindow::updateColor()
922 {
923  bool isDark = static_cast<VuoEditor *>(qApp)->isInterfaceDark();
925 
926  QString menuStyle = VUO_QSTRINGIFY(
927  // Sync with VuoEditorWindow::updateColor.
928  // Should parallel VuoDialogForInputEditor::getStyleSheet()'s QComboBox popup menu styles.
929  QMenu {
930  background-color: #404040;
931  }
932  QMenu::item {
933  color: #cfcfcf;
934  padding-left: 22px;
935  padding-right: 36px;
936  height: 21px;
937  }
938  QMenu::item:disabled {
939  color: #707070;
940  }
941  QMenu::item:selected {
942  background-color: #1060d0;
943  color: #ffffff;
944  }
945  QMenu::right-arrow {
946  left: -14px;
947  }
948  QMenu::indicator:checked {
949  image: url(:/Icons/checkmark.svg);
950  width: 11px;
951  }
952  QMenu::indicator:checked,
953  QMenu::icon {
954  margin-left: 6px;
955  }
956  QMenu::icon:checked,
957  QMenu::icon:unchecked {
958  margin-left: 0;
959  }
960  );
961 
962  setStyleSheet(VUO_QSTRINGIFY(
963  // Hide the 1px bright line between VuoPublishedPortSidebar and VuoCodeGutter.
964  QMainWindow::separator {
965  width: 1px;
966  height: 0px;
967  margin: -1px;
968  padding: 0px;
969  background: transparent;
970  }
971 
972  QMainWindow {
973  background: %1;
974  }
975  ).arg(e->gutterColor.name())
976  + (isDark ? menuStyle : ""));
977 }
978 
982 void VuoCodeWindow::updateCanvasOpacity()
983 {
984  int opacity = static_cast<VuoEditor *>(qApp)->getCanvasOpacity();
986 }
987 
988 void VuoCodeWindow::updateToolbar()
989 {
990  toolbar->update(false, stages->currentEditor()->isZoomedToActualSize(), false);
991 }
992 
993 void VuoCodeWindow::resizeEvent(QResizeEvent *event)
994 {
995 #if VUO_PRO
996  if (toolbar)
997  toolbar->updateTitle();
998 #endif
999 
1000  QMainWindow::resizeEvent(event);
1001 }
1002 
1007 {
1008  return raiseDocumentAction;
1009 }
1010 
1015 {
1016  return zoom11Action;
1017 }
1018 
1023 {
1024  return zoomInAction;
1025 }
1026 
1031 {
1032  return zoomOutAction;
1033 }
1034 
1039 {
1040  return fileMenu;
1041 }
1042 
1047 {
1048  return recentFileMenu;
1049 }
1050 
1055 {
1056  if (isMinimized())
1057  showNormal();
1058  else
1059  show();
1060 
1061  raise();
1062  activateWindow();
1063 }
1064 
1068 void VuoCodeWindow::updateWindowMenu()
1069 {
1070  windowMenu->clear();
1071  static_cast<VuoEditor *>(qApp)->populateWindowMenu(windowMenu, this);
1072 }
1073 
1078 {
1079  __block bool isRunning = wrapperComposition->isRunning();
1080  if (! isRunning)
1081  {
1082  string sourcePath = windowFilePath().toStdString();
1083  static_cast<VuoEditor *>(qApp)->getSubcompositionRouter()->applyToAllOtherTopLevelCompositions(nullptr, ^(VuoEditorComposition *topLevelComposition)
1084  {
1085  if (topLevelComposition->isRunning() && ! topLevelComposition->getModuleManager()->findInstancesOfNodeClass(sourcePath).empty())
1086  isRunning = true;
1087  });
1088  }
1089 
1090  reloadAction->setEnabled(isRunning);
1091 }
1092 
1096 void VuoCodeWindow::toggleInputPortSidebarVisibility()
1097 {
1098  bool becomingVisible = ! inputPortSidebar->isVisible();
1099  inputPortSidebar->setVisible(becomingVisible);
1100  updateInputPortSidebarMenuItem();
1101 }
1102 
1106 void VuoCodeWindow::updateInputPortSidebarMenuItem()
1107 {
1108  toggleInputPortSidebarAction->setText(inputPortSidebar->isVisible() ? tr("Hide Published Ports") : tr("Show Published Ports"));
1109 }
1110 
1114 void VuoCodeWindow::toggleDocumentationSidebarVisibility()
1115 {
1116  bool becomingVisible = ! documentationSidebar->isVisible();
1117  documentationSidebar->setVisible(becomingVisible);
1118  updateDocumentationSidebarMenuItem();
1119 }
1120 
1124 void VuoCodeWindow::updateDocumentationSidebarMenuItem()
1125 {
1126  toggleDocumentationSidebarAction->setText(documentationSidebar->isVisible() ? tr("Hide GLSL/ISF Quick Reference") : tr("Show GLSL/ISF Quick Reference"));
1127 }
1128 
1134 void VuoCodeWindow::showPublishedInputEditor(VuoRendererPort *port)
1135 {
1136  VuoInputEditorSession *inputEditorSession = new VuoInputEditorSession(inputEditorManager, wrapperComposition, inputPortSidebar, this);
1137  map<VuoRendererPort *, pair<string, string> > originalAndFinalValueForPort = inputEditorSession->execute(port, true);
1138 
1139  delete inputEditorSession;
1140  inputEditorSession = nullptr;
1141 
1142  bool valueChanged = false;
1143  for (auto i : originalAndFinalValueForPort)
1144  {
1145  VuoRendererPort *port = i.first;
1146  string originalEditingSessionValue = i.second.first;
1147  string finalEditingSessionValue = i.second.second;
1148 
1149  if (finalEditingSessionValue != originalEditingSessionValue)
1150  {
1151  valueChanged = true;
1152  wrapperComposition->updatePortConstant(static_cast<VuoCompilerPort *>(port->getBase()->getCompiler()), finalEditingSessionValue);
1153 
1154  VUserLog("%s: Set port %s to %s",
1155  getWindowTitleWithoutPlaceholder().toUtf8().data(),
1156  port->getBase()->getClass()->getName().c_str(),
1157  finalEditingSessionValue.c_str());
1158  }
1159  else if (port->getConstantAsString() != originalEditingSessionValue)
1160  {
1161  // Edit was canceled. Revert the published input value.
1162  wrapperComposition->updatePortConstant(static_cast<VuoCompilerPort *>(port->getBase()->getCompiler()), originalEditingSessionValue);
1163  }
1164  }
1165 
1166  if (valueChanged)
1167  {
1168  bringStoredShaderInSyncWithPublishedInputPorts();
1169  publishedInputsModified = true;
1170  updateModifiedIndicator();
1171  }
1172 }
1173 
1179 void VuoCodeWindow::changePublishedPortDetails(VuoRendererPublishedPort *port, json_object *newDetails)
1180 {
1181  VUserLog("%s: Set published port '%s' details to %s",
1182  getWindowTitleWithoutPlaceholder().toUtf8().data(),
1183  port->getBase()->getClass()->getName().c_str(),
1184  json_object_to_json_string(newDetails));
1185 
1186  static_cast<VuoCompilerPublishedPortClass *>(port->getBase()->getClass()->getCompiler())->updateDetails(newDetails);
1187 
1188  bringStoredShaderInSyncWithPublishedInputPorts();
1189  publishedInputsModified = true;
1190  updateModifiedIndicator();
1191 }
1192 
1198 void VuoCodeWindow::showPublishedPortNameEditor(VuoRendererPublishedPort *port)
1199 {
1200  string originalName = port->getBase()->getClass()->getName();
1201  inputPortSidebar->updatePortList();
1202  string newName = inputPortSidebar->showPublishedPortNameEditor(port);
1203 
1204  if (originalName != newName)
1205  changePublishedPortName(port, newName);
1206 }
1207 
1213 void VuoCodeWindow::changePublishedPortName(VuoRendererPublishedPort *port, string newName)
1214 {
1215  string oldSnapshot = wrapperComposition->takeSnapshot();
1216 
1217  string oldName = port->getBase()->getClass()->getName();
1218  wrapperComposition->setPublishedPortName(port, newName);
1219 
1220  // setPublishedPortName may have changed the name to ensure it's unique.
1221  newName = port->getBase()->getClass()->getName();
1222 
1223  VUserLog("%s: Rename published port %s to %s",
1224  getWindowTitleWithoutPlaceholder().toUtf8().data(),
1225  oldName.c_str(),
1226  newName.c_str());
1227 
1228  bringStoredShaderInSyncWithPublishedInputPorts(nullptr, {oldName, newName});
1229  publishedInputsModified = true;
1230  updateModifiedIndicator();
1231 
1232  string newSnapshot = wrapperComposition->takeSnapshot();
1233  wrapperComposition->updateRunningComposition(oldSnapshot, newSnapshot);
1234 }
1235 
1241 void VuoCodeWindow::addPublishedPort(string typeName, bool isInput)
1242 {
1243  string oldSnapshot = wrapperComposition->takeSnapshot();
1244 
1245  VuoType *type = (! typeName.empty() ? compiler->getType(typeName)->getBase() : nullptr);
1246  string portName = wrapperComposition->getUniquePublishedPortName(wrapperComposition->getDefaultPublishedPortNameForType(type));
1247 
1248  VUserLog("%s: Add published %s port %s %s",
1249  getWindowTitleWithoutPlaceholder().toUtf8().data(),
1250  isInput ? "input" : "output",
1251  typeName.empty() ? "event" : typeName.c_str(),
1252  portName.c_str());
1253 
1254  VuoCompilerPublishedPort *publishedPort = VuoCompilerPublishedPort::newPort(portName, type);
1255  wrapperComposition->addPublishedPort(static_cast<VuoPublishedPort *>(publishedPort->getBase()), true);
1256  VuoRendererPublishedPort *rpp = wrapperComposition->createRendererForPublishedPortInComposition(static_cast<VuoPublishedPort *>(publishedPort->getBase()), true);
1257 
1258  bringStoredShaderInSyncWithPublishedInputPorts(publishedPort);
1259  publishedInputsModified = true;
1260  updateModifiedIndicator();
1261 
1262  string newSnapshot = wrapperComposition->takeSnapshot();
1263  wrapperComposition->updateRunningComposition(oldSnapshot, newSnapshot);
1264 
1265  inputPortSidebar->updatePortList();
1266  string newName = inputPortSidebar->showPublishedPortNameEditor(static_cast<VuoRendererPublishedPort *>(publishedPort->getBase()->getRenderer()));
1267 
1268  if (portName != newName)
1269  changePublishedPortName(rpp, newName);
1270 }
1271 
1277 void VuoCodeWindow::deletePublishedPort(VuoRendererPublishedPort *port)
1278 {
1279  VUserLog("%s: Remove published input port %s",
1280  getWindowTitleWithoutPlaceholder().toUtf8().data(),
1281  port->getBase()->getClass()->getName().c_str());
1282 
1283  string oldSnapshot = wrapperComposition->takeSnapshot();
1284 
1285  for (VuoCable *cable : port->getBase()->getConnectedCables(true))
1286  wrapperComposition->removeCable(cable->getRenderer());
1287 
1288  string portName = port->getBase()->getClass()->getName();
1289  wrapperComposition->removePublishedPort(static_cast<VuoPublishedPort *>(port->getBase()), true);
1290 
1291  bringStoredShaderInSyncWithPublishedInputPorts(nullptr, {}, portName);
1292  publishedInputsModified = true;
1293  updateModifiedIndicator();
1294 
1295  string newSnapshot = wrapperComposition->takeSnapshot();
1296  wrapperComposition->updateRunningComposition(oldSnapshot, newSnapshot);
1297 }
1298 
1304 void VuoCodeWindow::changeMetadata(int dialogResult)
1305 {
1306  if (dialogResult == QDialog::Accepted)
1307  {
1308  VuoCompositionMetadata *metadata = metadataEditor->toMetadata();
1309  wrapperComposition->getBase()->setMetadata(metadata, true);
1310 
1311  shaderFile->setMetadata(metadata);
1312 
1313  metadataModified = true;
1314  updateModifiedIndicator();
1315 
1316  VUserLog("%s: Set metadata to:\n%s",
1317  getWindowTitleWithoutPlaceholder().toUtf8().data(),
1318  metadata->toCompositionHeader().c_str());
1319  }
1320 }
1321 
1325 void VuoCodeWindow::showBuildActivityIndicator()
1326 {
1327  toolbar->changeStateToBuildInProgress();
1328  updateToolbar();
1329 
1330  runAction->setEnabled(false);
1331  stopAction->setEnabled(true);
1332  restartAction->setEnabled(true);
1333 }
1334 
1338 void VuoCodeWindow::hideBuildActivityIndicator(QString buildError)
1339 {
1340  toolbar->changeStateToRunning();
1341  updateToolbar();
1342 
1343  runAction->setEnabled(false);
1344  stopAction->setEnabled(true);
1345  restartAction->setEnabled(true);
1347 }
1348 
1352 void VuoCodeWindow::showStopActivityIndicator()
1353 {
1354  toolbar->changeStateToStopInProgress();
1355  updateToolbar();
1356 
1357  runAction->setEnabled(!toolbar->isBuildPending());
1358  stopAction->setEnabled(false);
1359  restartAction->setEnabled(false);
1360 }
1361 
1365 void VuoCodeWindow::hideStopActivityIndicator()
1366 {
1367  toolbar->changeStateToStopped();
1368  updateToolbar();
1369 
1370  runAction->setEnabled(true);
1371  stopAction->setEnabled(false);
1372  restartAction->setEnabled(false);
1374 }
1375 
1379 void VuoCodeWindow::zoom11()
1380 {
1381  stages->zoom11();
1382 
1383  zoom11Action->setEnabled(false);
1384  toolbar->update(false, true, false);
1385 }
1386 
1390 void VuoCodeWindow::zoomIn()
1391 {
1392  stages->zoomIn();
1393 
1394  bool isActualSize = stages->currentEditor()->isZoomedToActualSize();
1395  zoom11Action->setEnabled(! isActualSize);
1396  toolbar->update(false, isActualSize, false);
1397 }
1398 
1402 void VuoCodeWindow::zoomOut()
1403 {
1404  stages->zoomOut();
1405 
1406  bool isActualSize = stages->currentEditor()->isZoomedToActualSize();
1407  zoom11Action->setEnabled(! isActualSize);
1408  toolbar->update(false, isActualSize, false);
1409 }
1410 
1414 void VuoCodeWindow::copy()
1415 {
1416  if (stages->currentEditor() == qApp->focusWidget())
1417  stages->currentEditor()->copy();
1418  else if (documentationSidebar->isAncestorOf(qApp->focusWidget()))
1419  QGuiApplication::clipboard()->setText(documentationSidebar->getSelectedText());
1420 }