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.

503 lines
14KB

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