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.

501 lines
15KB

  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. MultiDocumentPanelWindow::MultiDocumentPanelWindow (const Colour& backgroundColour)
  19. : DocumentWindow (String::empty, backgroundColour,
  20. DocumentWindow::maximiseButton | DocumentWindow::closeButton, false)
  21. {
  22. }
  23. MultiDocumentPanelWindow::~MultiDocumentPanelWindow()
  24. {
  25. }
  26. //==============================================================================
  27. void MultiDocumentPanelWindow::maximiseButtonPressed()
  28. {
  29. if (MultiDocumentPanel* const owner = getOwner())
  30. owner->setLayoutMode (MultiDocumentPanel::MaximisedWindowsWithTabs);
  31. else
  32. jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
  33. }
  34. void MultiDocumentPanelWindow::closeButtonPressed()
  35. {
  36. if (MultiDocumentPanel* const owner = getOwner())
  37. owner->closeDocument (getContentComponent(), true);
  38. else
  39. jassertfalse; // these windows are only designed to be used inside a MultiDocumentPanel!
  40. }
  41. void MultiDocumentPanelWindow::activeWindowStatusChanged()
  42. {
  43. DocumentWindow::activeWindowStatusChanged();
  44. updateOrder();
  45. }
  46. void MultiDocumentPanelWindow::broughtToFront()
  47. {
  48. DocumentWindow::broughtToFront();
  49. updateOrder();
  50. }
  51. void MultiDocumentPanelWindow::updateOrder()
  52. {
  53. if (MultiDocumentPanel* const owner = getOwner())
  54. owner->updateOrder();
  55. }
  56. MultiDocumentPanel* MultiDocumentPanelWindow::getOwner() const noexcept
  57. {
  58. return findParentComponentOfClass<MultiDocumentPanel>();
  59. }
  60. //==============================================================================
  61. class MultiDocumentPanel::TabbedComponentInternal : public TabbedComponent
  62. {
  63. public:
  64. TabbedComponentInternal()
  65. : TabbedComponent (TabbedButtonBar::TabsAtTop)
  66. {
  67. }
  68. void currentTabChanged (int, const String&)
  69. {
  70. if (MultiDocumentPanel* const owner = findParentComponentOfClass<MultiDocumentPanel>())
  71. owner->updateOrder();
  72. }
  73. };
  74. //==============================================================================
  75. MultiDocumentPanel::MultiDocumentPanel()
  76. : mode (MaximisedWindowsWithTabs),
  77. backgroundColour (Colours::lightblue),
  78. maximumNumDocuments (0),
  79. numDocsBeforeTabsUsed (0)
  80. {
  81. setOpaque (true);
  82. }
  83. MultiDocumentPanel::~MultiDocumentPanel()
  84. {
  85. closeAllDocuments (false);
  86. }
  87. //==============================================================================
  88. namespace MultiDocHelpers
  89. {
  90. static bool shouldDeleteComp (Component* const c)
  91. {
  92. return c->getProperties() ["mdiDocumentDelete_"];
  93. }
  94. }
  95. bool MultiDocumentPanel::closeAllDocuments (const bool checkItsOkToCloseFirst)
  96. {
  97. while (components.size() > 0)
  98. if (! closeDocument (components.getLast(), checkItsOkToCloseFirst))
  99. return false;
  100. return true;
  101. }
  102. MultiDocumentPanelWindow* MultiDocumentPanel::createNewDocumentWindow()
  103. {
  104. return new MultiDocumentPanelWindow (backgroundColour);
  105. }
  106. void MultiDocumentPanel::addWindow (Component* component)
  107. {
  108. MultiDocumentPanelWindow* const dw = createNewDocumentWindow();
  109. dw->setResizable (true, false);
  110. dw->setContentNonOwned (component, true);
  111. dw->setName (component->getName());
  112. const var bkg (component->getProperties() ["mdiDocumentBkg_"]);
  113. dw->setBackgroundColour (bkg.isVoid() ? backgroundColour : Colour ((uint32) static_cast <int> (bkg)));
  114. int x = 4;
  115. if (Component* const topComp = getChildComponent (getNumChildComponents() - 1))
  116. if (topComp->getX() == x && topComp->getY() == x)
  117. x += 16;
  118. dw->setTopLeftPosition (x, x);
  119. const var pos (component->getProperties() ["mdiDocumentPos_"]);
  120. if (pos.toString().isNotEmpty())
  121. dw->restoreWindowStateFromString (pos.toString());
  122. addAndMakeVisible (dw);
  123. dw->toFront (true);
  124. }
  125. bool MultiDocumentPanel::addDocument (Component* const component,
  126. const Colour& docColour,
  127. const bool deleteWhenRemoved)
  128. {
  129. // If you try passing a full DocumentWindow or ResizableWindow in here, you'll end up
  130. // with a frame-within-a-frame! Just pass in the bare content component.
  131. jassert (dynamic_cast <ResizableWindow*> (component) == nullptr);
  132. if (component == nullptr || (maximumNumDocuments > 0 && components.size() >= maximumNumDocuments))
  133. return false;
  134. components.add (component);
  135. component->getProperties().set ("mdiDocumentDelete_", deleteWhenRemoved);
  136. component->getProperties().set ("mdiDocumentBkg_", (int) docColour.getARGB());
  137. component->addComponentListener (this);
  138. if (mode == FloatingWindows)
  139. {
  140. if (isFullscreenWhenOneDocument())
  141. {
  142. if (components.size() == 1)
  143. {
  144. addAndMakeVisible (component);
  145. }
  146. else
  147. {
  148. if (components.size() == 2)
  149. addWindow (components.getFirst());
  150. addWindow (component);
  151. }
  152. }
  153. else
  154. {
  155. addWindow (component);
  156. }
  157. }
  158. else
  159. {
  160. if (tabComponent == nullptr && components.size() > numDocsBeforeTabsUsed)
  161. {
  162. addAndMakeVisible (tabComponent = new TabbedComponentInternal());
  163. Array <Component*> temp (components);
  164. for (int i = 0; i < temp.size(); ++i)
  165. tabComponent->addTab (temp[i]->getName(), docColour, temp[i], false);
  166. resized();
  167. }
  168. else
  169. {
  170. if (tabComponent != nullptr)
  171. tabComponent->addTab (component->getName(), docColour, component, false);
  172. else
  173. addAndMakeVisible (component);
  174. }
  175. setActiveDocument (component);
  176. }
  177. resized();
  178. activeDocumentChanged();
  179. return true;
  180. }
  181. bool MultiDocumentPanel::closeDocument (Component* component,
  182. const bool checkItsOkToCloseFirst)
  183. {
  184. if (components.contains (component))
  185. {
  186. if (checkItsOkToCloseFirst && ! tryToCloseDocument (component))
  187. return false;
  188. component->removeComponentListener (this);
  189. const bool shouldDelete = MultiDocHelpers::shouldDeleteComp (component);
  190. component->getProperties().remove ("mdiDocumentDelete_");
  191. component->getProperties().remove ("mdiDocumentBkg_");
  192. if (mode == FloatingWindows)
  193. {
  194. for (int i = getNumChildComponents(); --i >= 0;)
  195. {
  196. if (MultiDocumentPanelWindow* const dw = dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)))
  197. {
  198. if (dw->getContentComponent() == component)
  199. {
  200. ScopedPointer<MultiDocumentPanelWindow> (dw)->clearContentComponent();
  201. break;
  202. }
  203. }
  204. }
  205. if (shouldDelete)
  206. delete component;
  207. components.removeFirstMatchingValue (component);
  208. if (isFullscreenWhenOneDocument() && components.size() == 1)
  209. {
  210. for (int i = getNumChildComponents(); --i >= 0;)
  211. {
  212. ScopedPointer<MultiDocumentPanelWindow> dw (dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)));
  213. if (dw != nullptr)
  214. dw->clearContentComponent();
  215. }
  216. addAndMakeVisible (components.getFirst());
  217. }
  218. }
  219. else
  220. {
  221. jassert (components.indexOf (component) >= 0);
  222. if (tabComponent != nullptr)
  223. {
  224. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  225. if (tabComponent->getTabContentComponent (i) == component)
  226. tabComponent->removeTab (i);
  227. }
  228. else
  229. {
  230. removeChildComponent (component);
  231. }
  232. if (shouldDelete)
  233. delete component;
  234. if (tabComponent != nullptr && tabComponent->getNumTabs() <= numDocsBeforeTabsUsed)
  235. tabComponent = nullptr;
  236. components.removeFirstMatchingValue (component);
  237. if (components.size() > 0 && tabComponent == nullptr)
  238. addAndMakeVisible (components.getFirst());
  239. }
  240. resized();
  241. activeDocumentChanged();
  242. }
  243. else
  244. {
  245. jassertfalse;
  246. }
  247. return true;
  248. }
  249. int MultiDocumentPanel::getNumDocuments() const noexcept
  250. {
  251. return components.size();
  252. }
  253. Component* MultiDocumentPanel::getDocument (const int index) const noexcept
  254. {
  255. return components [index];
  256. }
  257. Component* MultiDocumentPanel::getActiveDocument() const noexcept
  258. {
  259. if (mode == FloatingWindows)
  260. {
  261. for (int i = getNumChildComponents(); --i >= 0;)
  262. if (MultiDocumentPanelWindow* const dw = dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)))
  263. if (dw->isActiveWindow())
  264. return dw->getContentComponent();
  265. }
  266. return components.getLast();
  267. }
  268. void MultiDocumentPanel::setActiveDocument (Component* component)
  269. {
  270. if (mode == FloatingWindows)
  271. {
  272. component = getContainerComp (component);
  273. if (component != nullptr)
  274. component->toFront (true);
  275. }
  276. else if (tabComponent != nullptr)
  277. {
  278. jassert (components.indexOf (component) >= 0);
  279. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  280. {
  281. if (tabComponent->getTabContentComponent (i) == component)
  282. {
  283. tabComponent->setCurrentTabIndex (i);
  284. break;
  285. }
  286. }
  287. }
  288. else
  289. {
  290. component->grabKeyboardFocus();
  291. }
  292. }
  293. void MultiDocumentPanel::activeDocumentChanged()
  294. {
  295. }
  296. void MultiDocumentPanel::setMaximumNumDocuments (const int newNumber)
  297. {
  298. maximumNumDocuments = newNumber;
  299. }
  300. void MultiDocumentPanel::useFullscreenWhenOneDocument (const bool shouldUseTabs)
  301. {
  302. numDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0;
  303. }
  304. bool MultiDocumentPanel::isFullscreenWhenOneDocument() const noexcept
  305. {
  306. return numDocsBeforeTabsUsed != 0;
  307. }
  308. //==============================================================================
  309. void MultiDocumentPanel::setLayoutMode (const LayoutMode newLayoutMode)
  310. {
  311. if (mode != newLayoutMode)
  312. {
  313. mode = newLayoutMode;
  314. if (mode == FloatingWindows)
  315. {
  316. tabComponent = nullptr;
  317. }
  318. else
  319. {
  320. for (int i = getNumChildComponents(); --i >= 0;)
  321. {
  322. if (MultiDocumentPanelWindow* const dw = dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)))
  323. {
  324. dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString());
  325. dw->clearContentComponent();
  326. delete dw;
  327. }
  328. }
  329. }
  330. resized();
  331. const Array <Component*> tempComps (components);
  332. components.clear();
  333. for (int i = 0; i < tempComps.size(); ++i)
  334. {
  335. Component* const c = tempComps.getUnchecked(i);
  336. addDocument (c,
  337. Colour ((uint32) static_cast <int> (c->getProperties().getWithDefault ("mdiDocumentBkg_", (int) Colours::white.getARGB()))),
  338. MultiDocHelpers::shouldDeleteComp (c));
  339. }
  340. }
  341. }
  342. void MultiDocumentPanel::setBackgroundColour (const Colour& newBackgroundColour)
  343. {
  344. if (backgroundColour != newBackgroundColour)
  345. {
  346. backgroundColour = newBackgroundColour;
  347. setOpaque (newBackgroundColour.isOpaque());
  348. repaint();
  349. }
  350. }
  351. //==============================================================================
  352. void MultiDocumentPanel::paint (Graphics& g)
  353. {
  354. g.fillAll (backgroundColour);
  355. }
  356. void MultiDocumentPanel::resized()
  357. {
  358. if (mode == MaximisedWindowsWithTabs || components.size() == numDocsBeforeTabsUsed)
  359. {
  360. for (int i = getNumChildComponents(); --i >= 0;)
  361. getChildComponent (i)->setBounds (getLocalBounds());
  362. }
  363. setWantsKeyboardFocus (components.size() == 0);
  364. }
  365. Component* MultiDocumentPanel::getContainerComp (Component* c) const
  366. {
  367. if (mode == FloatingWindows)
  368. {
  369. for (int i = 0; i < getNumChildComponents(); ++i)
  370. if (MultiDocumentPanelWindow* const dw = dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)))
  371. if (dw->getContentComponent() == c)
  372. return dw;
  373. }
  374. return c;
  375. }
  376. void MultiDocumentPanel::componentNameChanged (Component&)
  377. {
  378. if (mode == FloatingWindows)
  379. {
  380. for (int i = 0; i < getNumChildComponents(); ++i)
  381. if (MultiDocumentPanelWindow* const dw = dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)))
  382. dw->setName (dw->getContentComponent()->getName());
  383. }
  384. else if (tabComponent != nullptr)
  385. {
  386. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  387. tabComponent->setTabName (i, tabComponent->getTabContentComponent (i)->getName());
  388. }
  389. }
  390. void MultiDocumentPanel::updateOrder()
  391. {
  392. const Array <Component*> oldList (components);
  393. if (mode == FloatingWindows)
  394. {
  395. components.clear();
  396. for (int i = 0; i < getNumChildComponents(); ++i)
  397. if (MultiDocumentPanelWindow* const dw = dynamic_cast <MultiDocumentPanelWindow*> (getChildComponent (i)))
  398. components.add (dw->getContentComponent());
  399. }
  400. else
  401. {
  402. if (tabComponent != nullptr)
  403. {
  404. if (Component* const current = tabComponent->getCurrentContentComponent())
  405. {
  406. components.removeFirstMatchingValue (current);
  407. components.add (current);
  408. }
  409. }
  410. }
  411. if (components != oldList)
  412. activeDocumentChanged();
  413. }