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.

502 lines
14KB

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