Audio plugin host https://kx.studio/carla
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.

508 lines
15KB

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