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.

511 lines
15KB

  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. // Intellisense warns about component being uninitialised.
  180. // I'm not sure how a function argument could be uninitialised.
  181. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001)
  182. if (component == nullptr)
  183. return true;
  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 (auto* child : getChildren())
  195. {
  196. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  197. {
  198. if (dw->getContentComponent() == component)
  199. {
  200. std::unique_ptr<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. std::unique_ptr<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.reset();
  236. components.removeFirstMatchingValue (component);
  237. if (components.size() > 0 && tabComponent == nullptr)
  238. addAndMakeVisible (components.getFirst());
  239. }
  240. resized();
  241. // This ensures that the active tab is painted properly when a tab is closed!
  242. if (auto* activeComponent = getActiveDocument())
  243. setActiveDocument (activeComponent);
  244. activeDocumentChanged();
  245. }
  246. else
  247. {
  248. jassertfalse;
  249. }
  250. return true;
  251. JUCE_END_IGNORE_WARNINGS_MSVC
  252. }
  253. int MultiDocumentPanel::getNumDocuments() const noexcept
  254. {
  255. return components.size();
  256. }
  257. Component* MultiDocumentPanel::getDocument (const int index) const noexcept
  258. {
  259. return components [index];
  260. }
  261. Component* MultiDocumentPanel::getActiveDocument() const noexcept
  262. {
  263. if (mode == FloatingWindows)
  264. {
  265. for (auto* child : getChildren())
  266. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  267. if (dw->isActiveWindow())
  268. return dw->getContentComponent();
  269. }
  270. return components.getLast();
  271. }
  272. void MultiDocumentPanel::setActiveDocument (Component* component)
  273. {
  274. jassert (component != nullptr);
  275. if (mode == FloatingWindows)
  276. {
  277. component = getContainerComp (component);
  278. if (component != nullptr)
  279. component->toFront (true);
  280. }
  281. else if (tabComponent != nullptr)
  282. {
  283. jassert (components.indexOf (component) >= 0);
  284. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  285. {
  286. if (tabComponent->getTabContentComponent (i) == component)
  287. {
  288. tabComponent->setCurrentTabIndex (i);
  289. break;
  290. }
  291. }
  292. }
  293. else
  294. {
  295. component->grabKeyboardFocus();
  296. }
  297. }
  298. void MultiDocumentPanel::activeDocumentChanged()
  299. {
  300. }
  301. void MultiDocumentPanel::setMaximumNumDocuments (const int newNumber)
  302. {
  303. maximumNumDocuments = newNumber;
  304. }
  305. void MultiDocumentPanel::useFullscreenWhenOneDocument (const bool shouldUseTabs)
  306. {
  307. numDocsBeforeTabsUsed = shouldUseTabs ? 1 : 0;
  308. }
  309. bool MultiDocumentPanel::isFullscreenWhenOneDocument() const noexcept
  310. {
  311. return numDocsBeforeTabsUsed != 0;
  312. }
  313. //==============================================================================
  314. void MultiDocumentPanel::setLayoutMode (const LayoutMode newLayoutMode)
  315. {
  316. if (mode != newLayoutMode)
  317. {
  318. mode = newLayoutMode;
  319. if (mode == FloatingWindows)
  320. {
  321. tabComponent.reset();
  322. }
  323. else
  324. {
  325. for (int i = getNumChildComponents(); --i >= 0;)
  326. {
  327. std::unique_ptr<MultiDocumentPanelWindow> dw (dynamic_cast<MultiDocumentPanelWindow*> (getChildComponent (i)));
  328. if (dw != nullptr)
  329. {
  330. dw->getContentComponent()->getProperties().set ("mdiDocumentPos_", dw->getWindowStateAsString());
  331. dw->clearContentComponent();
  332. }
  333. }
  334. }
  335. resized();
  336. auto tempComps = components;
  337. components.clear();
  338. for (auto* c : tempComps)
  339. addDocument (c,
  340. Colour ((uint32) static_cast<int> (c->getProperties().getWithDefault ("mdiDocumentBkg_", (int) Colours::white.getARGB()))),
  341. MultiDocHelpers::shouldDeleteComp (c));
  342. }
  343. }
  344. void MultiDocumentPanel::setBackgroundColour (Colour newBackgroundColour)
  345. {
  346. if (backgroundColour != newBackgroundColour)
  347. {
  348. backgroundColour = newBackgroundColour;
  349. setOpaque (newBackgroundColour.isOpaque());
  350. repaint();
  351. }
  352. }
  353. //==============================================================================
  354. void MultiDocumentPanel::paint (Graphics& g)
  355. {
  356. g.fillAll (backgroundColour);
  357. }
  358. void MultiDocumentPanel::resized()
  359. {
  360. if (mode == MaximisedWindowsWithTabs || components.size() == numDocsBeforeTabsUsed)
  361. {
  362. for (auto* child : getChildren())
  363. child->setBounds (getLocalBounds());
  364. }
  365. setWantsKeyboardFocus (components.size() == 0);
  366. }
  367. Component* MultiDocumentPanel::getContainerComp (Component* c) const
  368. {
  369. if (mode == FloatingWindows)
  370. {
  371. for (auto* child : getChildren())
  372. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  373. if (dw->getContentComponent() == c)
  374. return dw;
  375. }
  376. return c;
  377. }
  378. void MultiDocumentPanel::componentNameChanged (Component&)
  379. {
  380. if (mode == FloatingWindows)
  381. {
  382. for (auto* child : getChildren())
  383. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  384. dw->setName (dw->getContentComponent()->getName());
  385. }
  386. else if (tabComponent != nullptr)
  387. {
  388. for (int i = tabComponent->getNumTabs(); --i >= 0;)
  389. tabComponent->setTabName (i, tabComponent->getTabContentComponent (i)->getName());
  390. }
  391. }
  392. void MultiDocumentPanel::updateOrder()
  393. {
  394. auto oldList = components;
  395. if (mode == FloatingWindows)
  396. {
  397. components.clear();
  398. for (auto* child : getChildren())
  399. if (auto* dw = dynamic_cast<MultiDocumentPanelWindow*> (child))
  400. components.add (dw->getContentComponent());
  401. }
  402. else
  403. {
  404. if (tabComponent != nullptr)
  405. {
  406. if (auto* current = tabComponent->getCurrentContentComponent())
  407. {
  408. components.removeFirstMatchingValue (current);
  409. components.add (current);
  410. }
  411. }
  412. }
  413. if (components != oldList)
  414. activeDocumentChanged();
  415. }
  416. } // namespace juce