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.

juce_MultiDocumentPanel.cpp 15KB

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