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.

495 lines
14KB

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