Vuo  2.1.0
VuoEditorUtilities.cc
Go to the documentation of this file.
1 
10 #include "VuoEditorUtilities.hh"
11 
12 #include "VuoCodeWindow.hh"
13 #include "VuoCompilerNodeClass.hh"
14 #include "VuoEditorWindow.hh"
15 #include "VuoFileUtilities.hh"
16 #include "VuoNodeClass.hh"
17 
18 #ifdef __APPLE__
19 #include <objc/objc-runtime.h>
20 #include <ApplicationServices/ApplicationServices.h> // for kCGEventSourceStateCombinedSessionState
21 #include <Security/Authorization.h>
22 #include <Security/AuthorizationTags.h>
23 #endif
24 
29 {
30  qreal devicePixelRatio = qApp->primaryScreen()->devicePixelRatio();
31  QPixmap logo(QStringLiteral(":/Icons/vuo.png"));
32  logo = logo.scaledToHeight(64 * devicePixelRatio, Qt::SmoothTransformation);
33  logo.setDevicePixelRatio(devicePixelRatio);
34  return logo;
35 }
36 
41 QString VuoEditorUtilities::getHTMLForSVG(QString svgPath, int pointsWide, int pointsHigh)
42 {
43  // QIcon::pixmap might not return the exact requested size
44  // (e.g., if the icon's aspect ratio doesn't match the requested aspect ratio),
45  // so create an exact pixmap and draw the icon on it.
46  qreal dpr = QApplication::desktop()->devicePixelRatio();
47  QPixmap pixmap(pointsWide * dpr, pointsHigh * dpr);
48  pixmap.fill(Qt::transparent);
49  QPainter painter(&pixmap);
50  QIcon(svgPath).paint(&painter, 0, 0, pointsWide * dpr, pointsHigh * dpr);
51 
52  QByteArray bytes;
53  QBuffer buffer(&bytes);
54  pixmap.save(&buffer, "PNG");
55  return QString("<img src='data:image/png;base64,")
56  + bytes.toBase64()
57  + QString("' width=%1 height=%2 />")
58  .arg(pointsWide)
59  .arg(pointsHigh);
60 }
61 
66 {
67  QString path = QString::fromUtf8(VuoFileUtilities::getUserModulesPath().c_str());
68  QDir dir(path);
69  if (!dir.exists())
70  QDir().mkpath(path);
71 
72  if (dir.exists())
73  QDesktopServices::openUrl(QUrl::fromLocalFile(path));
74  else
75  QMessageBox::information(NULL, QObject::tr("Vuo User Library folder"), QObject::tr("Please create this folder if you'd like to install nodes for this user account:\n\n%1").arg(path));
76 }
77 
82 {
83  QString path = QString::fromUtf8(VuoFileUtilities::getSystemModulesPath().c_str());
84  QDir dir(path);
85 
86  // Request authorization to create the folder.
87  if (!dir.exists())
88  {
89  AuthorizationRef auth = NULL;
90  if (AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &auth) == errAuthorizationSuccess)
91  {
92  AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
93  AuthorizationRights rights = {1, &right};
94 
95  const char *promptString = QObject::tr("Vuo wants to create its System Library folder.").toUtf8().constData();
96  AuthorizationItem prompt = {kAuthorizationEnvironmentPrompt, strlen(promptString), (void *)promptString, 0};
97  AuthorizationEnvironment environment = {1, &prompt};
98 
99  if (AuthorizationCopyRights(auth, &rights, &environment,
100  kAuthorizationFlagDefaults
101  | kAuthorizationFlagInteractionAllowed
102  | kAuthorizationFlagPreAuthorize
103  | kAuthorizationFlagExtendRights,
104  NULL) == errAuthorizationSuccess)
105  {
106  const char *args[] = { "-p", strdup(path.toUtf8().data()), NULL };
107  FILE *pipe = NULL;
108 #pragma clang diagnostic push
109 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
110  // See http://www.stevestreeting.com/2011/11/25/escalating-privileges-on-mac-os-x-securely-and-without-using-deprecated-methods/ for info on a non-deprecated path.
111  if (AuthorizationExecuteWithPrivileges(auth, "/bin/mkdir", kAuthorizationFlagDefaults, (char * const *)args, &pipe) == errAuthorizationSuccess)
112 #pragma clang diagnostic pop
113  {
114  // Wait for `mkdir` to complete.
115  int bytesRead = 0;
116  do
117  {
118  char buffer[128];
119  bytesRead = read(fileno(pipe), buffer, sizeof(buffer));
120  } while (bytesRead);
121  fclose(pipe);
122  }
123  free((char *)args[1]);
124  }
125 
126  AuthorizationFree(auth, kAuthorizationFlagDefaults);
127  }
128  }
129 
130  if (dir.exists())
131  QDesktopServices::openUrl(QUrl::fromLocalFile(path));
132  else
133  QMessageBox::information(NULL, QObject::tr("Vuo System Library folder"), QObject::tr("Please create this folder if you'd like to install nodes for all users on this computer:\n\n%1").arg(path));
134 }
135 
144 {
145  // Technically we want to know whether the "Option" key was pressed at the time of the drop, not right at this moment,
146  // but QGraphicsSceneDragDropEvent::modifiers() isn't providing that information reliably on OS X at Qt 5.3.
147 #ifdef __APPLE__
148  bool optionKeyPressed = (CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) & kCGEventFlagMaskAlternate);
149  #else
150  bool optionKeyPressed = (event->modifiers() & Qt::AltModifier);
151  #endif
152 
153  return optionKeyPressed;
154 }
155 
164 bool VuoEditorUtilities::isNodeClassEditable(VuoNodeClass *nodeClass, QString &editLabel, QString &sourcePath)
165 {
166  if (nodeClass->hasCompiler())
167  {
168  string expectedSourcePath = nodeClass->getCompiler()->getSourcePath();
169 
170  if (VuoFileUtilities::fileExists(expectedSourcePath))
171  {
172  string dir, file, ext;
173  VuoFileUtilities::splitPath(expectedSourcePath, dir, file, ext);
174  string sourceKind = (VuoFileUtilities::isCompositionExtension(ext) ?
175  "Composition" :
176  (VuoFileUtilities::isIsfSourceExtension(ext) ? "Shader" :
177  "Node"));
178 
179  editLabel = QObject::tr(("Edit " + sourceKind + "…").c_str());
180  sourcePath = QString::fromStdString(expectedSourcePath);
181 
182  return true;
183  }
184  }
185 
186  return false;
187 }
188 
196 bool VuoEditorUtilities::isNodeClassSuccessorTo(QString oldNodeClass, QString newNodeClass)
197 {
198  map<QString, QString> successorForNodeClass;
199  successorForNodeClass["vuo.mesh.make.lines"] = "vuo.scene.make.lines";
200  successorForNodeClass["vuo.mesh.make.lineStrips"] = "vuo.scene.make.lineStrips";
201  successorForNodeClass["vuo.mesh.make.parametric"] = "vuo.scene.make.parametric";
202  successorForNodeClass["vuo.mesh.make.points"] = "vuo.scene.make.points";
203  successorForNodeClass["vuo.mesh.make.sphere"] = "vuo.scene.make.sphere";
204  successorForNodeClass["vuo.mesh.make.square"] = "vuo.scene.make.square";
205  successorForNodeClass["vuo.mesh.make.triangle"] = "vuo.scene.make.triangle";
206 
207  // Case: newNodeClass is a designated successor to oldNodeClass
208  if (successorForNodeClass[oldNodeClass] == newNodeClass)
209  return true;
210 
211  // Case: oldNodeClass and newNodeClass have identical root names with increasing numerical suffixes, or
212  // newNodeClass is identical to oldNodeClass plus a numerical suffix.
213  QString root = "";
214  QString oldSuffix = "";
215  QString newSuffix = "";
216  int minLength = qMin(oldNodeClass.length(), newNodeClass.length());
217  for (int i = 0; (i < minLength); ++i)
218  {
219  if (oldNodeClass.at(i) == newNodeClass.at(i))
220  root += oldNodeClass.at(i);
221  else
222  break;
223  }
224 
225  oldSuffix = oldNodeClass.right(oldNodeClass.length()-root.length());
226  newSuffix = newNodeClass.right(newNodeClass.length()-root.length());
227 
228  bool oldSuffixIsNumeric = false;
229  int oldSuffixVal = oldSuffix.toInt(&oldSuffixIsNumeric);
230 
231  bool newSuffixIsNumeric = false;
232  int newSuffixVal = newSuffix.toInt(&newSuffixIsNumeric);
233 
234  if (newSuffixIsNumeric && (oldSuffix.isEmpty() || (oldSuffixIsNumeric && (newSuffixVal > oldSuffixVal))))
235  {
236  // Exclude false positives where the numerical endings are meaningful parts of the root,
237  // such as "vuo.osc.message.make.2" -> "vuo.osc.message.make.3" and
238  // "vuo.osc.message.get.1" -> "vuo.osc.message.get.11"
239  bool rootEndsInNumberSegment = false;
240  for (int i = root.length()-1; i >= 0; --i)
241  {
242  if (root.at(i).isDigit())
243  continue;
244  else
245  {
246  if (root.at(i) == '.')
247  rootEndsInNumberSegment = true;
248  break;
249  }
250  }
251 
252  return !rootEndsInNumberSegment;
253  }
254 
255  return false;
256 }
257 
262 {
263  QList<QMainWindow *> openWindows;
264  for (QWidget *openWidget : QApplication::topLevelWidgets())
265  if ((dynamic_cast<VuoEditorWindow *>(openWidget) || dynamic_cast<VuoCodeWindow *>(openWidget)) && ! openWidget->isHidden())
266  openWindows.append(static_cast<QMainWindow *>(openWidget));
267 
268  std::sort(openWindows.begin(), openWindows.end(), [](const QMainWindow *window1, const QMainWindow *window2) {
269  return window1->windowTitle().remove("[*]").compare(window2->windowTitle().remove("[*]"), Qt::CaseInsensitive) < 0;
270  });
271 
272  return openWindows;
273 }
274 
279 {
280  QList<VuoEditorWindow *> openCompositionWindows;
281  for (QMainWindow *openWindow : getOpenEditingWindows())
282  if (dynamic_cast<VuoEditorWindow *>(openWindow))
283  openCompositionWindows.append(static_cast<VuoEditorWindow *>(openWindow));
284 
285  return openCompositionWindows;
286 }
287 
292 {
293  QList<QMainWindow *> openWindows = getOpenEditingWindows();
294 
295  std::sort(openWindows.begin(), openWindows.end(), [](const QMainWindow *window1, const QMainWindow *window2) {
296  id nsView1 = (id)window1->winId();
297  id nsWindow1 = objc_msgSend(nsView1, sel_getUid("window"));
298  long orderedIndex1 = (long)objc_msgSend(nsWindow1, sel_getUid("orderedIndex"));
299 
300  id nsView2 = (id)window2->winId();
301  id nsWindow2 = objc_msgSend(nsView2, sel_getUid("window"));
302  long orderedIndex2 = (long)objc_msgSend(nsWindow2, sel_getUid("orderedIndex"));
303 
304  return orderedIndex1 < orderedIndex2;
305  });
306 
307  return openWindows;
308 }
309 
313 QMainWindow * VuoEditorUtilities::existingWindowWithFile(const QString &filename)
314 {
315  QString canonicalFilePath = QFileInfo(filename).canonicalFilePath();
316 
317  for (QMainWindow *openWindow : getOpenEditingWindows())
318  if (openWindow->windowFilePath() == canonicalFilePath)
319  return openWindow;
320 
321  return NULL;
322 }
323 
329 {
330  VuoEditorWindow *editorWindow = dynamic_cast<VuoEditorWindow *>(window);
331  if (editorWindow)
332  {
333  return editorWindow->getRaiseDocumentAction();
334  }
335  else
336  {
337  VuoCodeWindow *codeWindow = dynamic_cast<VuoCodeWindow *>(window);
338  if (codeWindow)
339  return codeWindow->getRaiseDocumentAction();
340  }
341 
342  return nullptr;
343 }
344 
349 {
350  VuoEditorWindow *editorWindow = dynamic_cast<VuoEditorWindow *>(window);
351  if (editorWindow)
352  {
353  return editorWindow->getRecentFileMenu();
354  }
355  else
356  {
357  VuoCodeWindow *codeWindow = dynamic_cast<VuoCodeWindow *>(window);
358  if (codeWindow)
359  return codeWindow->getRecentFileMenu();
360  }
361 
362  return nullptr;
363 }
364 
368 QMenu * VuoEditorUtilities::getFileMenuForWindow(QMainWindow *window)
369 {
370  VuoEditorWindow *editorWindow = dynamic_cast<VuoEditorWindow *>(window);
371  if (editorWindow)
372  {
373  return editorWindow->getFileMenu();
374  }
375  else
376  {
377  VuoCodeWindow *codeWindow = dynamic_cast<VuoCodeWindow *>(window);
378  if (codeWindow)
379  return codeWindow->getFileMenu();
380  }
381 
382  return nullptr;
383 }
384 
389 {
390  VuoEditorWindow *editorWindow = dynamic_cast<VuoEditorWindow *>(window);
391  if (editorWindow)
392  {
393  return editorWindow->setAsActiveWindow();
394  }
395  else
396  {
397  VuoCodeWindow *codeWindow = dynamic_cast<VuoCodeWindow *>(window);
398  if (codeWindow)
399  return codeWindow->setAsActiveWindow();
400  }
401 }
402 
408 void VuoEditorUtilities::setWindowOpacity(QMainWindow *window, int opacity)
409 {
410 #ifdef __APPLE__
411  id nsView = (id)window->winId();
412  id nsWindow = objc_msgSend(nsView, sel_getUid("window"));
413  objc_msgSend(nsWindow, sel_getUid("setAlphaValue:"), opacity/255.);
414  #endif
415 }