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.

542 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../jucer_Headers.h"
  18. #include "jucer_Application.h"
  19. #include "jucer_MainWindow.h"
  20. #include "jucer_OpenDocumentManager.h"
  21. #include "../Code Editor/jucer_SourceCodeEditor.h"
  22. #include "../Project/jucer_NewProjectWizard.h"
  23. #include "../Utility/jucer_JucerTreeViewBase.h"
  24. ScopedPointer<ApplicationCommandManager> commandManager;
  25. //==============================================================================
  26. MainWindow::MainWindow()
  27. : DocumentWindow (IntrojucerApp::getApp().getApplicationName(),
  28. Colour::greyLevel (0.6f),
  29. DocumentWindow::allButtons,
  30. false)
  31. {
  32. setUsingNativeTitleBar (true);
  33. createProjectContentCompIfNeeded();
  34. #if ! JUCE_MAC
  35. setMenuBar (IntrojucerApp::getApp().menuModel);
  36. #endif
  37. setResizable (true, false);
  38. centreWithSize (800, 600);
  39. // Register all the app commands..
  40. commandManager->registerAllCommandsForTarget (this);
  41. commandManager->registerAllCommandsForTarget (getProjectContentComponent());
  42. // update key mappings..
  43. {
  44. commandManager->getKeyMappings()->resetToDefaultMappings();
  45. ScopedPointer <XmlElement> keys (getGlobalProperties().getXmlValue ("keyMappings"));
  46. if (keys != nullptr)
  47. commandManager->getKeyMappings()->restoreFromXml (*keys);
  48. addKeyListener (commandManager->getKeyMappings());
  49. }
  50. // don't want the window to take focus when the title-bar is clicked..
  51. setWantsKeyboardFocus (false);
  52. getLookAndFeel().setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  53. setResizeLimits (600, 500, 32000, 32000);
  54. }
  55. MainWindow::~MainWindow()
  56. {
  57. #if ! JUCE_MAC
  58. setMenuBar (nullptr);
  59. #endif
  60. removeKeyListener (commandManager->getKeyMappings());
  61. // save the current size and position to our settings file..
  62. getGlobalProperties().setValue ("lastMainWindowPos", getWindowStateAsString());
  63. clearContentComponent();
  64. currentProject = nullptr;
  65. }
  66. void MainWindow::createProjectContentCompIfNeeded()
  67. {
  68. if (getProjectContentComponent() == nullptr)
  69. {
  70. clearContentComponent();
  71. setContentOwned (IntrojucerApp::getApp().createProjectContentComponent(), false);
  72. jassert (getProjectContentComponent() != nullptr);
  73. }
  74. }
  75. void MainWindow::makeVisible()
  76. {
  77. restoreWindowPosition();
  78. setVisible (true);
  79. addToDesktop(); // (must add before restoring size so that fullscreen will work)
  80. restoreWindowPosition();
  81. getContentComponent()->grabKeyboardFocus();
  82. }
  83. ProjectContentComponent* MainWindow::getProjectContentComponent() const
  84. {
  85. return dynamic_cast <ProjectContentComponent*> (getContentComponent());
  86. }
  87. void MainWindow::closeButtonPressed()
  88. {
  89. IntrojucerApp::getApp().mainWindowList.closeWindow (this);
  90. }
  91. bool MainWindow::closeProject (Project* project)
  92. {
  93. jassert (project == currentProject && project != nullptr);
  94. if (project == nullptr)
  95. return true;
  96. project->getStoredProperties().setValue (getProjectWindowPosName(), getWindowStateAsString());
  97. if (ProjectContentComponent* const pcc = getProjectContentComponent())
  98. {
  99. pcc->saveTreeViewState();
  100. pcc->saveOpenDocumentList();
  101. pcc->hideEditor();
  102. }
  103. if (! IntrojucerApp::getApp().openDocumentManager.closeAllDocumentsUsingProject (*project, true))
  104. return false;
  105. FileBasedDocument::SaveResult r = project->saveIfNeededAndUserAgrees();
  106. if (r == FileBasedDocument::savedOk)
  107. {
  108. setProject (nullptr);
  109. return true;
  110. }
  111. return false;
  112. }
  113. bool MainWindow::closeCurrentProject()
  114. {
  115. return currentProject == nullptr || closeProject (currentProject);
  116. }
  117. void MainWindow::setProject (Project* newProject)
  118. {
  119. createProjectContentCompIfNeeded();
  120. getProjectContentComponent()->setProject (newProject);
  121. currentProject = newProject;
  122. getProjectContentComponent()->updateMainWindowTitle();
  123. commandManager->commandStatusChanged();
  124. }
  125. void MainWindow::restoreWindowPosition()
  126. {
  127. String windowState;
  128. if (currentProject != nullptr)
  129. windowState = currentProject->getStoredProperties().getValue (getProjectWindowPosName());
  130. if (windowState.isEmpty())
  131. windowState = getGlobalProperties().getValue ("lastMainWindowPos");
  132. restoreWindowStateFromString (windowState);
  133. }
  134. bool MainWindow::canOpenFile (const File& file) const
  135. {
  136. return (! file.isDirectory())
  137. && (file.hasFileExtension (Project::projectFileExtension)
  138. || IntrojucerApp::getApp().openDocumentManager.canOpenFile (file));
  139. }
  140. bool MainWindow::openFile (const File& file)
  141. {
  142. createProjectContentCompIfNeeded();
  143. if (file.hasFileExtension (Project::projectFileExtension))
  144. {
  145. ScopedPointer <Project> newDoc (new Project (file));
  146. if (newDoc->loadFrom (file, true)
  147. && closeCurrentProject())
  148. {
  149. setProject (newDoc.release());
  150. return true;
  151. }
  152. }
  153. else if (file.exists())
  154. {
  155. return getProjectContentComponent()->showEditorForFile (file, true);
  156. }
  157. return false;
  158. }
  159. bool MainWindow::isInterestedInFileDrag (const StringArray& filenames)
  160. {
  161. for (int i = filenames.size(); --i >= 0;)
  162. if (canOpenFile (filenames[i]))
  163. return true;
  164. return false;
  165. }
  166. void MainWindow::filesDropped (const StringArray& filenames, int /*mouseX*/, int /*mouseY*/)
  167. {
  168. for (int i = filenames.size(); --i >= 0;)
  169. {
  170. const File f (filenames[i]);
  171. if (canOpenFile (f) && openFile (f))
  172. break;
  173. }
  174. }
  175. bool MainWindow::shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails,
  176. StringArray& files, bool& canMoveFiles)
  177. {
  178. if (TreeView* tv = dynamic_cast <TreeView*> (sourceDetails.sourceComponent.get()))
  179. {
  180. Array<JucerTreeViewBase*> selected;
  181. for (int i = tv->getNumSelectedItems(); --i >= 0;)
  182. if (JucerTreeViewBase* b = dynamic_cast <JucerTreeViewBase*> (tv->getSelectedItem(i)))
  183. selected.add (b);
  184. if (selected.size() > 0)
  185. {
  186. for (int i = selected.size(); --i >= 0;)
  187. {
  188. if (JucerTreeViewBase* jtvb = selected.getUnchecked(i))
  189. {
  190. const File f (jtvb->getDraggableFile());
  191. if (f.existsAsFile())
  192. files.add (f.getFullPathName());
  193. }
  194. }
  195. canMoveFiles = false;
  196. return files.size() > 0;
  197. }
  198. }
  199. return false;
  200. }
  201. void MainWindow::activeWindowStatusChanged()
  202. {
  203. DocumentWindow::activeWindowStatusChanged();
  204. if (ProjectContentComponent* const pcc = getProjectContentComponent())
  205. pcc->updateMissingFileStatuses();
  206. IntrojucerApp::getApp().openDocumentManager.reloadModifiedFiles();
  207. }
  208. void MainWindow::updateTitle (const String& documentName)
  209. {
  210. String name (IntrojucerApp::getApp().getApplicationName());
  211. if (currentProject != nullptr)
  212. name << " - " << currentProject->getDocumentTitle();
  213. if (documentName.isNotEmpty())
  214. name << " - " << documentName;
  215. setName (name);
  216. }
  217. void MainWindow::showNewProjectWizard()
  218. {
  219. jassert (currentProject == nullptr);
  220. setContentOwned (createNewProjectWizardComponent(), true);
  221. makeVisible();
  222. }
  223. //==============================================================================
  224. ApplicationCommandTarget* MainWindow::getNextCommandTarget()
  225. {
  226. return nullptr;
  227. }
  228. void MainWindow::getAllCommands (Array <CommandID>& commands)
  229. {
  230. const CommandID ids[] = { CommandIDs::closeWindow };
  231. commands.addArray (ids, numElementsInArray (ids));
  232. }
  233. void MainWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  234. {
  235. switch (commandID)
  236. {
  237. case CommandIDs::closeWindow:
  238. result.setInfo ("Close Window", "Closes the current window", CommandCategories::general, 0);
  239. result.defaultKeypresses.add (KeyPress ('w', ModifierKeys::commandModifier, 0));
  240. break;
  241. default:
  242. break;
  243. }
  244. }
  245. bool MainWindow::perform (const InvocationInfo& info)
  246. {
  247. switch (info.commandID)
  248. {
  249. case CommandIDs::closeWindow:
  250. closeButtonPressed();
  251. break;
  252. default:
  253. return false;
  254. }
  255. return true;
  256. }
  257. //==============================================================================
  258. MainWindowList::MainWindowList()
  259. {
  260. }
  261. void MainWindowList::forceCloseAllWindows()
  262. {
  263. windows.clear();
  264. }
  265. bool MainWindowList::askAllWindowsToClose()
  266. {
  267. saveCurrentlyOpenProjectList();
  268. while (windows.size() > 0)
  269. {
  270. if (! windows[0]->closeCurrentProject())
  271. return false;
  272. windows.remove (0);
  273. }
  274. return true;
  275. }
  276. void MainWindowList::createWindowIfNoneAreOpen()
  277. {
  278. if (windows.size() == 0)
  279. createNewMainWindow()->makeVisible();
  280. }
  281. void MainWindowList::closeWindow (MainWindow* w)
  282. {
  283. jassert (windows.contains (w));
  284. #if ! JUCE_MAC
  285. if (windows.size() == 1)
  286. {
  287. JUCEApplication::getInstance()->systemRequestedQuit();
  288. }
  289. else
  290. #endif
  291. {
  292. if (w->closeCurrentProject())
  293. {
  294. windows.removeObject (w);
  295. saveCurrentlyOpenProjectList();
  296. }
  297. }
  298. }
  299. void MainWindowList::openDocument (OpenDocumentManager::Document* doc, bool grabFocus)
  300. {
  301. MainWindow* w = getOrCreateFrontmostWindow();
  302. w->getProjectContentComponent()->showDocument (doc, grabFocus);
  303. }
  304. bool MainWindowList::openFile (const File& file)
  305. {
  306. for (int i = windows.size(); --i >= 0;)
  307. {
  308. MainWindow* const w = windows.getUnchecked(i);
  309. if (w->getProject() != nullptr && w->getProject()->getFile() == file)
  310. {
  311. w->toFront (true);
  312. return true;
  313. }
  314. }
  315. if (file.hasFileExtension (Project::projectFileExtension))
  316. {
  317. ScopedPointer <Project> newDoc (new Project (file));
  318. if (newDoc->loadFrom (file, true))
  319. {
  320. MainWindow* const w = getOrCreateEmptyWindow();
  321. w->setProject (newDoc);
  322. newDoc.release()->setChangedFlag (false);
  323. w->makeVisible();
  324. avoidSuperimposedWindows (w);
  325. jassert (w->getProjectContentComponent() != nullptr);
  326. w->getProjectContentComponent()->reloadLastOpenDocuments();
  327. return true;
  328. }
  329. }
  330. else if (file.exists())
  331. {
  332. MainWindow* const w = getOrCreateFrontmostWindow();
  333. return w->openFile (file);
  334. }
  335. return false;
  336. }
  337. MainWindow* MainWindowList::createNewMainWindow()
  338. {
  339. MainWindow* const w = new MainWindow();
  340. windows.add (w);
  341. w->restoreWindowPosition();
  342. avoidSuperimposedWindows (w);
  343. return w;
  344. }
  345. MainWindow* MainWindowList::getOrCreateFrontmostWindow()
  346. {
  347. if (windows.size() == 0)
  348. {
  349. MainWindow* w = createNewMainWindow();
  350. avoidSuperimposedWindows (w);
  351. w->makeVisible();
  352. return w;
  353. }
  354. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  355. {
  356. MainWindow* mw = dynamic_cast <MainWindow*> (Desktop::getInstance().getComponent (i));
  357. if (windows.contains (mw))
  358. return mw;
  359. }
  360. return windows.getLast();
  361. }
  362. MainWindow* MainWindowList::getOrCreateEmptyWindow()
  363. {
  364. if (windows.size() == 0)
  365. return createNewMainWindow();
  366. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  367. {
  368. MainWindow* mw = dynamic_cast <MainWindow*> (Desktop::getInstance().getComponent (i));
  369. if (windows.contains (mw) && mw->getProject() == nullptr)
  370. return mw;
  371. }
  372. return createNewMainWindow();
  373. }
  374. void MainWindowList::avoidSuperimposedWindows (MainWindow* const mw)
  375. {
  376. for (int i = windows.size(); --i >= 0;)
  377. {
  378. MainWindow* const other = windows.getUnchecked(i);
  379. const Rectangle<int> b1 (mw->getBounds());
  380. const Rectangle<int> b2 (other->getBounds());
  381. if (mw != other
  382. && std::abs (b1.getX() - b2.getX()) < 3
  383. && std::abs (b1.getY() - b2.getY()) < 3
  384. && std::abs (b1.getRight() - b2.getRight()) < 3
  385. && std::abs (b1.getBottom() - b2.getBottom()) < 3)
  386. {
  387. int dx = 40, dy = 30;
  388. if (b1.getCentreX() >= mw->getScreenBounds().getCentreX()) dx = -dx;
  389. if (b1.getCentreY() >= mw->getScreenBounds().getCentreY()) dy = -dy;
  390. mw->setBounds (b1.translated (dx, dy));
  391. }
  392. }
  393. }
  394. void MainWindowList::saveCurrentlyOpenProjectList()
  395. {
  396. Array<File> projects;
  397. Desktop& desktop = Desktop::getInstance();
  398. for (int i = 0; i < desktop.getNumComponents(); ++i)
  399. {
  400. if (MainWindow* const mw = dynamic_cast <MainWindow*> (desktop.getComponent(i)))
  401. if (Project* p = mw->getProject())
  402. projects.add (p->getFile());
  403. }
  404. getAppSettings().setLastProjects (projects);
  405. }
  406. void MainWindowList::reopenLastProjects()
  407. {
  408. Array<File> projects (getAppSettings().getLastProjects());
  409. for (int i = 0; i < projects.size(); ++ i)
  410. openFile (projects.getReference(i));
  411. }
  412. void MainWindowList::sendLookAndFeelChange()
  413. {
  414. for (int i = windows.size(); --i >= 0;)
  415. windows.getUnchecked(i)->sendLookAndFeelChange();
  416. }
  417. Project* MainWindowList::getFrontmostProject()
  418. {
  419. Desktop& desktop = Desktop::getInstance();
  420. for (int i = desktop.getNumComponents(); --i >= 0;)
  421. if (MainWindow* const mw = dynamic_cast <MainWindow*> (desktop.getComponent(i)))
  422. if (Project* p = mw->getProject())
  423. return p;
  424. return nullptr;
  425. }