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