The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

600 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #ifndef __JUCER_APPLICATION_JUCEHEADER__
  19. #define __JUCER_APPLICATION_JUCEHEADER__
  20. #include "../jucer_Headers.h"
  21. #include "jucer_MainWindow.h"
  22. #include "jucer_JuceUpdater.h"
  23. #include "jucer_CommandLine.h"
  24. //==============================================================================
  25. class JucerApplication : public JUCEApplication
  26. {
  27. public:
  28. //==============================================================================
  29. JucerApplication() {}
  30. ~JucerApplication() {}
  31. //==============================================================================
  32. void initialise (const String& commandLine)
  33. {
  34. if (commandLine.isNotEmpty())
  35. {
  36. const int appReturnCode = performCommandLine (commandLine);
  37. if (appReturnCode != commandLineNotPerformed)
  38. {
  39. setApplicationReturnValue (appReturnCode);
  40. quit();
  41. return;
  42. }
  43. }
  44. commandManager = new ApplicationCommandManager();
  45. commandManager->registerAllCommandsForTarget (this);
  46. menuModel = new MainMenuModel();
  47. doExtraInitialisation();
  48. ImageCache::setCacheTimeout (30 * 1000);
  49. if (commandLine.trim().isNotEmpty() && ! commandLine.trim().startsWithChar ('-'))
  50. {
  51. anotherInstanceStarted (commandLine);
  52. }
  53. else
  54. {
  55. Array<File> projects (StoredSettings::getInstance()->getLastProjects());
  56. for (int i = 0; i < projects.size(); ++ i)
  57. openFile (projects.getReference(i));
  58. }
  59. makeSureUserHasSelectedModuleFolder();
  60. if (mainWindows.size() == 0)
  61. createNewMainWindow()->makeVisible();
  62. #if JUCE_MAC
  63. MenuBarModel::setMacMainMenu (menuModel);
  64. #endif
  65. }
  66. void shutdown()
  67. {
  68. #if JUCE_MAC
  69. MenuBarModel::setMacMainMenu (nullptr);
  70. #endif
  71. menuModel = nullptr;
  72. StoredSettings::deleteInstance();
  73. mainWindows.clear();
  74. OpenDocumentManager::deleteInstance();
  75. commandManager = nullptr;
  76. }
  77. //==============================================================================
  78. void systemRequestedQuit()
  79. {
  80. if (cancelAnyModalComponents())
  81. {
  82. new AsyncQuitRetrier();
  83. return;
  84. }
  85. while (mainWindows.size() > 0)
  86. {
  87. if (! mainWindows[0]->closeCurrentProject())
  88. return;
  89. mainWindows.remove (0);
  90. }
  91. quit();
  92. }
  93. void closeWindow (MainWindow* w)
  94. {
  95. jassert (mainWindows.contains (w));
  96. mainWindows.removeObject (w);
  97. #if ! JUCE_MAC
  98. if (mainWindows.size() == 0)
  99. systemRequestedQuit();
  100. #endif
  101. updateRecentProjectList();
  102. }
  103. //==============================================================================
  104. const String getApplicationName()
  105. {
  106. return String (ProjectInfo::projectName) + " " + getApplicationVersion();
  107. }
  108. const String getApplicationVersion()
  109. {
  110. return ProjectInfo::versionString;
  111. }
  112. bool moreThanOneInstanceAllowed()
  113. {
  114. #ifndef JUCE_LINUX
  115. return false;
  116. #else
  117. return true; //xxx should be false but doesn't work on linux..
  118. #endif
  119. }
  120. void anotherInstanceStarted (const String& commandLine)
  121. {
  122. openFile (commandLine.unquoted());
  123. }
  124. virtual void doExtraInitialisation() {}
  125. static JucerApplication* getApp()
  126. {
  127. return dynamic_cast<JucerApplication*> (JUCEApplication::getInstance());
  128. }
  129. //==============================================================================
  130. class MainMenuModel : public MenuBarModel
  131. {
  132. public:
  133. MainMenuModel()
  134. {
  135. setApplicationCommandManagerToWatch (commandManager);
  136. }
  137. const StringArray getMenuBarNames()
  138. {
  139. const char* const names[] = { "File", "Edit", "View", "Window", "Tools", 0 };
  140. return StringArray ((const char**) names);
  141. }
  142. const PopupMenu getMenuForIndex (int topLevelMenuIndex, const String& menuName)
  143. {
  144. PopupMenu menu;
  145. if (topLevelMenuIndex == 0) // "File" menu
  146. {
  147. menu.addCommandItem (commandManager, CommandIDs::newProject);
  148. menu.addSeparator();
  149. menu.addCommandItem (commandManager, CommandIDs::open);
  150. PopupMenu recentFiles;
  151. StoredSettings::getInstance()->recentFiles.createPopupMenuItems (recentFiles, 100, true, true);
  152. menu.addSubMenu ("Open recent file", recentFiles);
  153. menu.addSeparator();
  154. menu.addCommandItem (commandManager, CommandIDs::closeDocument);
  155. menu.addCommandItem (commandManager, CommandIDs::saveDocument);
  156. menu.addCommandItem (commandManager, CommandIDs::saveDocumentAs);
  157. menu.addSeparator();
  158. menu.addCommandItem (commandManager, CommandIDs::closeProject);
  159. menu.addCommandItem (commandManager, CommandIDs::saveProject);
  160. menu.addCommandItem (commandManager, CommandIDs::saveProjectAs);
  161. menu.addSeparator();
  162. menu.addCommandItem (commandManager, CommandIDs::openInIDE);
  163. menu.addCommandItem (commandManager, CommandIDs::saveAndOpenInIDE);
  164. #if ! JUCE_MAC
  165. menu.addSeparator();
  166. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::quit);
  167. #endif
  168. }
  169. else if (topLevelMenuIndex == 1) // "Edit" menu
  170. {
  171. menu.addCommandItem (commandManager, CommandIDs::undo);
  172. menu.addCommandItem (commandManager, CommandIDs::redo);
  173. menu.addSeparator();
  174. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
  175. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
  176. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
  177. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
  178. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::selectAll);
  179. menu.addCommandItem (commandManager, StandardApplicationCommandIDs::deselectAll);
  180. menu.addSeparator();
  181. menu.addCommandItem (commandManager, CommandIDs::toFront);
  182. menu.addCommandItem (commandManager, CommandIDs::toBack);
  183. menu.addSeparator();
  184. menu.addCommandItem (commandManager, CommandIDs::group);
  185. menu.addCommandItem (commandManager, CommandIDs::ungroup);
  186. menu.addSeparator();
  187. menu.addCommandItem (commandManager, CommandIDs::bringBackLostItems);
  188. }
  189. else if (topLevelMenuIndex == 2) // "View" menu
  190. {
  191. menu.addCommandItem (commandManager, CommandIDs::showProjectSettings);
  192. menu.addSeparator();
  193. menu.addCommandItem (commandManager, CommandIDs::test);
  194. menu.addSeparator();
  195. menu.addCommandItem (commandManager, CommandIDs::showGrid);
  196. menu.addCommandItem (commandManager, CommandIDs::enableSnapToGrid);
  197. menu.addSeparator();
  198. menu.addCommandItem (commandManager, CommandIDs::zoomIn);
  199. menu.addCommandItem (commandManager, CommandIDs::zoomOut);
  200. menu.addCommandItem (commandManager, CommandIDs::zoomNormal);
  201. menu.addSeparator();
  202. menu.addCommandItem (commandManager, CommandIDs::useTabbedWindows);
  203. }
  204. else if (topLevelMenuIndex == 3) // "Window" menu
  205. {
  206. menu.addCommandItem (commandManager, CommandIDs::closeWindow);
  207. menu.addSeparator();
  208. const int numDocs = jmin (50, OpenDocumentManager::getInstance()->getNumOpenDocuments());
  209. for (int i = 0; i < numDocs; ++i)
  210. {
  211. OpenDocumentManager::Document* doc = OpenDocumentManager::getInstance()->getOpenDocument(i);
  212. menu.addItem (300 + i, doc->getName());
  213. }
  214. menu.addSeparator();
  215. menu.addCommandItem (commandManager, CommandIDs::closeAllDocuments);
  216. }
  217. else if (topLevelMenuIndex == 4) // "Tools" menu
  218. {
  219. menu.addCommandItem (commandManager, CommandIDs::updateModules);
  220. menu.addCommandItem (commandManager, CommandIDs::showUTF8Tool);
  221. }
  222. return menu;
  223. }
  224. void menuItemSelected (int menuItemID, int topLevelMenuIndex)
  225. {
  226. if (menuItemID >= 100 && menuItemID < 200)
  227. {
  228. // open a file from the "recent files" menu
  229. const File file (StoredSettings::getInstance()->recentFiles.getFile (menuItemID - 100));
  230. getApp()->openFile (file);
  231. }
  232. else if (menuItemID >= 300 && menuItemID < 400)
  233. {
  234. OpenDocumentManager::Document* doc = OpenDocumentManager::getInstance()->getOpenDocument (menuItemID - 300);
  235. MainWindow* w = getApp()->getOrCreateFrontmostWindow();
  236. w->makeVisible();
  237. w->getProjectContentComponent()->showDocument (doc);
  238. getApp()->avoidSuperimposedWindows (w);
  239. }
  240. }
  241. };
  242. //==============================================================================
  243. void getAllCommands (Array <CommandID>& commands)
  244. {
  245. JUCEApplication::getAllCommands (commands);
  246. const CommandID ids[] = { CommandIDs::newProject,
  247. CommandIDs::open,
  248. CommandIDs::showPrefs,
  249. CommandIDs::closeAllDocuments,
  250. CommandIDs::saveAll,
  251. CommandIDs::updateModules,
  252. CommandIDs::showUTF8Tool };
  253. commands.addArray (ids, numElementsInArray (ids));
  254. }
  255. void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result)
  256. {
  257. switch (commandID)
  258. {
  259. case CommandIDs::newProject:
  260. result.setInfo ("New Project...", "Creates a new Jucer project", CommandCategories::general, 0);
  261. result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
  262. break;
  263. case CommandIDs::open:
  264. result.setInfo ("Open...", "Opens a Jucer project", CommandCategories::general, 0);
  265. result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0));
  266. break;
  267. case CommandIDs::showPrefs:
  268. result.setInfo ("Preferences...", "Shows the preferences panel.", CommandCategories::general, 0);
  269. result.defaultKeypresses.add (KeyPress (',', ModifierKeys::commandModifier, 0));
  270. break;
  271. case CommandIDs::closeAllDocuments:
  272. result.setInfo ("Close All Documents", "Closes all open documents", CommandCategories::general, 0);
  273. result.setActive (OpenDocumentManager::getInstance()->getNumOpenDocuments() > 0);
  274. break;
  275. case CommandIDs::saveAll:
  276. result.setInfo ("Save All", "Saves all open documents", CommandCategories::general, 0);
  277. result.setActive (OpenDocumentManager::getInstance()->anyFilesNeedSaving());
  278. break;
  279. case CommandIDs::updateModules:
  280. result.setInfo ("Download the latest JUCE modules", "Checks online for any JUCE modules updates and installs them", CommandCategories::general, 0);
  281. break;
  282. case CommandIDs::showUTF8Tool:
  283. result.setInfo ("UTF-8 String-Literal Helper", "Shows the UTF-8 string literal utility", CommandCategories::general, 0);
  284. break;
  285. default:
  286. JUCEApplication::getCommandInfo (commandID, result);
  287. break;
  288. }
  289. }
  290. bool perform (const InvocationInfo& info)
  291. {
  292. switch (info.commandID)
  293. {
  294. case CommandIDs::newProject: createNewProject(); break;
  295. case CommandIDs::open: askUserToOpenFile(); break;
  296. case CommandIDs::showPrefs: showPrefsPanel(); break;
  297. case CommandIDs::saveAll: OpenDocumentManager::getInstance()->saveAll(); break;
  298. case CommandIDs::closeAllDocuments: closeAllDocuments (true); break;
  299. case CommandIDs::showUTF8Tool: showUTF8ToolWindow(); break;
  300. case CommandIDs::updateModules: runModuleUpdate (String::empty); break;
  301. default: return JUCEApplication::perform (info);
  302. }
  303. return true;
  304. }
  305. //==============================================================================
  306. void showPrefsPanel()
  307. {
  308. jassertfalse;
  309. }
  310. void createNewProject()
  311. {
  312. if (makeSureUserHasSelectedModuleFolder())
  313. {
  314. MainWindow* mw = getOrCreateEmptyWindow();
  315. mw->showNewProjectWizard();
  316. avoidSuperimposedWindows (mw);
  317. }
  318. }
  319. void askUserToOpenFile()
  320. {
  321. FileChooser fc ("Open File");
  322. if (fc.browseForFileToOpen())
  323. openFile (fc.getResult());
  324. }
  325. bool openFile (const File& file)
  326. {
  327. for (int j = mainWindows.size(); --j >= 0;)
  328. {
  329. if (mainWindows.getUnchecked(j)->getProject() != nullptr
  330. && mainWindows.getUnchecked(j)->getProject()->getFile() == file)
  331. {
  332. mainWindows.getUnchecked(j)->toFront (true);
  333. return true;
  334. }
  335. }
  336. if (file.hasFileExtension (Project::projectFileExtension))
  337. {
  338. ScopedPointer <Project> newDoc (new Project (file));
  339. if (newDoc->loadFrom (file, true))
  340. {
  341. MainWindow* w = getOrCreateEmptyWindow();
  342. w->setProject (newDoc.release());
  343. w->makeVisible();
  344. avoidSuperimposedWindows (w);
  345. return true;
  346. }
  347. }
  348. else if (file.exists())
  349. {
  350. MainWindow* w = getOrCreateFrontmostWindow();
  351. const bool ok = w->openFile (file);
  352. w->makeVisible();
  353. avoidSuperimposedWindows (w);
  354. return ok;
  355. }
  356. return false;
  357. }
  358. bool closeAllDocuments (bool askUserToSave)
  359. {
  360. for (int i = OpenDocumentManager::getInstance()->getNumOpenDocuments(); --i >= 0;)
  361. {
  362. OpenDocumentManager::Document* doc = OpenDocumentManager::getInstance()->getOpenDocument (i);
  363. for (int j = mainWindows.size(); --j >= 0;)
  364. mainWindows.getUnchecked(j)->getProjectContentComponent()->hideDocument (doc);
  365. if (! OpenDocumentManager::getInstance()->closeDocument (i, askUserToSave))
  366. return false;
  367. }
  368. return true;
  369. }
  370. void updateRecentProjectList()
  371. {
  372. Array<File> projects;
  373. for (int i = 0; i < mainWindows.size(); ++i)
  374. {
  375. MainWindow* mw = mainWindows[i];
  376. if (mw != nullptr && mw->getProject() != nullptr)
  377. projects.add (mw->getProject()->getFile());
  378. }
  379. StoredSettings::getInstance()->setLastProjects (projects);
  380. }
  381. bool makeSureUserHasSelectedModuleFolder()
  382. {
  383. if (! ModuleList::isLocalModulesFolderValid())
  384. {
  385. if (! runModuleUpdate ("Please select a location to store your local set of JUCE modules,\n"
  386. "and download the ones that you'd like to use!"))
  387. {
  388. AlertWindow::showMessageBox (AlertWindow::WarningIcon,
  389. "Introjucer",
  390. "Unless you create a local JUCE folder containing some modules, you'll be unable to save any projects correctly!\n\n"
  391. "Use the option on the 'Tools' menu to set this up!");
  392. return false;
  393. }
  394. }
  395. return true;
  396. }
  397. bool runModuleUpdate (const String& message)
  398. {
  399. ModuleList list;
  400. list.rescan (ModuleList::getDefaultModulesFolder (nullptr));
  401. JuceUpdater::show (list, mainWindows[0], message);
  402. ModuleList::setLocalModulesFolder (list.getModulesFolder());
  403. return ModuleList::isJuceOrModulesFolder (list.getModulesFolder());
  404. }
  405. ScopedPointer<MainMenuModel> menuModel;
  406. private:
  407. OwnedArray <MainWindow> mainWindows;
  408. MainWindow* createNewMainWindow()
  409. {
  410. MainWindow* mw = new MainWindow();
  411. mainWindows.add (mw);
  412. mw->restoreWindowPosition();
  413. avoidSuperimposedWindows (mw);
  414. return mw;
  415. }
  416. MainWindow* getOrCreateFrontmostWindow()
  417. {
  418. if (mainWindows.size() == 0)
  419. return createNewMainWindow();
  420. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  421. {
  422. MainWindow* mw = dynamic_cast <MainWindow*> (Desktop::getInstance().getComponent (i));
  423. if (mainWindows.contains (mw))
  424. return mw;
  425. }
  426. return mainWindows.getLast();
  427. }
  428. MainWindow* getOrCreateEmptyWindow()
  429. {
  430. if (mainWindows.size() == 0)
  431. return createNewMainWindow();
  432. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  433. {
  434. MainWindow* mw = dynamic_cast <MainWindow*> (Desktop::getInstance().getComponent (i));
  435. if (mainWindows.contains (mw) && mw->getProject() == nullptr)
  436. return mw;
  437. }
  438. return createNewMainWindow();
  439. }
  440. void avoidSuperimposedWindows (MainWindow* const mw)
  441. {
  442. for (int i = mainWindows.size(); --i >= 0;)
  443. {
  444. MainWindow* const other = mainWindows.getUnchecked(i);
  445. const Rectangle<int> b1 (mw->getBounds());
  446. const Rectangle<int> b2 (other->getBounds());
  447. if (mw != other
  448. && std::abs (b1.getX() - b2.getX()) < 3
  449. && std::abs (b1.getY() - b2.getY()) < 3
  450. && std::abs (b1.getRight() - b2.getRight()) < 3
  451. && std::abs (b1.getBottom() - b2.getBottom()) < 3)
  452. {
  453. int dx = 40, dy = 30;
  454. if (b1.getCentreX() >= mw->getScreenBounds().getCentreX()) dx = -dx;
  455. if (b1.getCentreY() >= mw->getScreenBounds().getCentreY()) dy = -dy;
  456. mw->setBounds (b1.translated (dx, dy));
  457. }
  458. }
  459. }
  460. //==============================================================================
  461. static bool cancelAnyModalComponents()
  462. {
  463. const int numModal = ModalComponentManager::getInstance()->getNumModalComponents();
  464. for (int i = numModal; --i >= 0;)
  465. if (ModalComponentManager::getInstance()->getModalComponent(i) != nullptr)
  466. ModalComponentManager::getInstance()->getModalComponent(i)->exitModalState (0);
  467. return numModal > 0;
  468. }
  469. class AsyncQuitRetrier : public Timer
  470. {
  471. public:
  472. AsyncQuitRetrier() { startTimer (500); }
  473. void timerCallback()
  474. {
  475. stopTimer();
  476. delete this;
  477. if (getApp() != nullptr)
  478. getApp()->systemRequestedQuit();
  479. }
  480. JUCE_DECLARE_NON_COPYABLE (AsyncQuitRetrier);
  481. };
  482. };
  483. #endif // __JUCER_APPLICATION_JUCEHEADER__